aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfusion32 <marcopuzziello@gmail.com>2025-05-30 20:12:36 -0300
committerfusion32 <marcopuzziello@gmail.com>2025-05-30 20:12:36 -0300
commit08780dad536eb5d0544eb2ea70e5443dc237976f (patch)
tree673a57b538ca9016d5b386ff32c3bd67c2190c2f
parentdff5e62f6ebb1b06c234b9144458788893b6ca86 (diff)
downloadgame-08780dad536eb5d0544eb2ea70e5443dc237976f.tar.gz
game-08780dad536eb5d0544eb2ea70e5443dc237976f.zip
more work on `crcombat.cc`
-rw-r--r--reference/types.hh61
-rw-r--r--src/crcombat.cc417
-rw-r--r--src/crcombat.hh17
-rw-r--r--src/creature.cc52
-rw-r--r--src/creature.hh16
-rw-r--r--src/crskill.cc18
-rw-r--r--src/enums.hh63
-rw-r--r--src/magic.cc74
-rw-r--r--src/magic.hh90
-rw-r--r--src/stubs.hh12
10 files changed, 685 insertions, 135 deletions
diff --git a/reference/types.hh b/reference/types.hh
index b87faf2..2147440 100644
--- a/reference/types.hh
+++ b/reference/types.hh
@@ -1,65 +1,4 @@
-
-struct TImpact {
- int (**_vptr.TImpact)(...); // VTABLE?
-};
-
-struct TSummonImpact {
- TImpact super_TImpact; // INHERITANCE?
- TCreature *Actor;
- int Race;
- int Maximum;
-};
-
-struct TSpeedImpact {
- TImpact super_TImpact; // INHERITANCE?
- TCreature *Actor;
- int Percent;
- int Duration;
-};
-
-struct THealingImpact {
- TImpact super_TImpact; // INHERITANCE?
- TCreature *Actor;
- int Power;
-};
-
-struct TOutfitImpact {
- TImpact super_TImpact; // INHERITANCE?
- TCreature *Actor;
- TOutfit Outfit;
- int Duration;
-};
-
-struct TFieldImpact {
- TImpact super_TImpact; // INHERITANCE?
- TCreature *Actor;
- int FieldType;
-};
-
-struct TDrunkenImpact {
- TImpact super_TImpact; // INHERITANCE?
- TCreature *Actor;
- int Power;
- int Duration;
-};
-
-struct TStrengthImpact {
- TImpact super_TImpact; // INHERITANCE?
- TCreature *Actor;
- int Skills;
- int Percent;
- int Duration;
-};
-
-struct TDamageImpact {
- TImpact super_TImpact; // INHERITANCE?
- TCreature *Actor;
- int DamageType;
- int Power;
- bool AllowDefense;
-};
-
struct TCircle {
int x[32];
int y[32];
diff --git a/src/crcombat.cc b/src/crcombat.cc
index de6385a..256f056 100644
--- a/src/crcombat.cc
+++ b/src/crcombat.cc
@@ -1,6 +1,7 @@
#include "creature.hh"
#include "player.hh"
#include "config.hh"
+#include "magic.hh"
#include "stubs.hh"
@@ -10,8 +11,8 @@ TCombat::TCombat(void){
this->EarliestDefendTime = 0;
this->LastDefendTime = 0;
this->LatestAttackTime = 0;
- this->AttackMode = 2;
- this->ChaseMode = 0;
+ this->AttackMode = ATTACK_MODE_BALANCED;
+ this->ChaseMode = CHASE_MODE_NONE;
this->SecureMode = 1;
this->AttackDest = 0;
this->Following = false;
@@ -146,11 +147,184 @@ void TCombat::CheckCombatValues(void){
}
}
+int TCombat::GetDistance(void){
+ int Distance = 0;
+ if(this->Close != NONE || this->Fist){
+ Distance = 1;
+ }else if(this->Throw != NONE){
+ Distance = 2;
+ }else if(this->Missile != NONE || this->Wand != NONE){
+ Distance = 3;
+ }
+ return Distance;
+}
+
+void TCombat::SetAttackDest(uint32 TargetID, bool Follow){
+ if(this->AttackDest == TargetID && this->Following == Follow){
+ return;
+ }
+
+ TCreature *Master = this->Master;
+ if(TargetID == 0 || TargetID == Master->ID){
+ this->StopAttack(0);
+ return;
+ }
+
+ TCreature *Target = GetCreature(TargetID);
+ if(Target == NULL){
+ this->StopAttack(0);
+ return;
+ }
+
+ if(!Follow){
+ if(Master->Type == PLAYER && Target->Type == PLAYER){
+ if(this->SecureMode == 1 && WorldType == NORMAL
+ && !((TPlayer*)Master)->IsAttackJustified(TargetID)){
+ this->StopAttack(0);
+ throw SECUREMODE;
+ }
+ }
+
+ if(Master->Type == PLAYER && !CheckRight(Master->ID, ATTACK_EVERYWHERE)){
+ if(IsProtectionZone(Master->posx, Master->posy, Master->posz)
+ || IsProtectionZone(Target->posx, Target->posy, Target->posz)){
+ this->StopAttack(0);
+ throw PROTECTIONZONE;
+ }
+ }
+
+ if(Master->Type == PLAYER && CheckRight(Master->ID, NO_ATTACK)){
+ this->StopAttack(0);
+ throw ATTACKNOTALLOWED;
+ }
+
+ if(Master->Type == PLAYER && Target->Type == PLAYER){
+ if(((TPlayer*)Master)->GetRealProfession() == PROFESSION_NONE
+ && !CheckRight(Master->ID, ATTACK_EVERYWHERE)){
+ this->StopAttack(0);
+ throw ATTACKNOTALLOWED;
+ }
+ }
+
+ if(WorldType == NON_PVP){
+ if(!Master->IsPeaceful() || !Target->IsPeaceful()){
+ if(Target->Type == NPC){
+ this->StopAttack(0);
+ throw ATTACKNOTALLOWED;
+ }
+ }
+
+ if(Master->Type == PLAYER){
+ if(Target->Type == NPC || !CheckRight(Master->ID, ATTACK_EVERYWHERE)){
+ this->StopAttack(0);
+ throw ATTACKNOTALLOWED;
+ }
+ }
+ }
+ }
+
+ if(Master->Type == PLAYER && Target->Type != PLAYER){
+ if(Target->Outfit.OutfitID == 0 && Target->Outfit.ObjectType == 0){
+ this->StopAttack(0);
+ throw TARGETLOST;
+ }
+ }
+
+ int Distance = ObjectDistance(Master->CrObject, Target->CrObject);
+ if(Distance > 8){
+ this->StopAttack(0);
+ throw TARGETLOST;
+ }
+
+ this->AttackDest = TargetID;
+ this->Following = Follow;
+ if(!Follow){
+ Target->AttackStimulus(Master->ID);
+ Master->BlockLogout(60, Target->Type == PLAYER);
+ if(Master->Type == PLAYER && Target->Type == PLAYER){
+ ((TPlayer*)Master)->RecordAttack(TargetID);
+ }
+ this->LatestAttackTime = 0;
+ }
+}
+
+void TCombat::CanToDoAttack(void){
+ if(this->AttackDest == 0){
+ return;
+ }
+
+ // TODO(fusion): There is some `CanAttack` function inlined here.
+ TCreature *Master = this->Master;
+ if(Master == NULL){
+ error("TCombat::CanAttack: Kein Master gesetzt!\n");
+ throw ERROR;
+ }
+
+ TCreature *Target = GetCreature(this->AttackDest);
+ if(Target == NULL){
+ this->StopAttack(0);
+ throw TARGETLOST;
+ }
+
+ if(Master->Type == PLAYER && Target->Type != PLAYER){
+ if(Target->Outfit.OutfitID == 0 && Target->Outfit.ObjectType == 0){
+ this->StopAttack(0);
+ throw TARGETLOST;
+ }
+ }
+
+ if(!this->Following){
+ if(Master->Type == PLAYER && Target->Type == PLAYER){
+ if(this->SecureMode == 1 && WorldType == NORMAL
+ && !((TPlayer*)Master)->IsAttackJustified(Target->ID)){
+ this->StopAttack(0);
+ throw SECUREMODE;
+ }
+ }
+
+ if(WorldType == NON_PVP){
+ if(Master->IsPeaceful() && Target->IsPeaceful()){
+ if(Master->Type != PLAYER || !CheckRight(Master->ID, ATTACK_EVERYWHERE)){
+ this->StopAttack(0);
+ throw ATTACKNOTALLOWED;
+ }
+ }
+ }
+ }
+
+ int Distance = ObjectDistance(Master->CrObject, Target->CrObject);
+ if(Distance > 8){
+ this->StopAttack(0);
+ throw TARGETLOST;
+ }
+
+ int ChaseMode = this->ChaseMode;
+ if(this->Following){
+ ChaseMode = CHASE_MODE_CLOSE;
+ }
+
+ if(ChaseMode == CHASE_MODE_CLOSE){
+ if(Distance > 1){
+ Master->ToDoGo(Target->posx, Target->posy, Target->posz, false, 3);
+ }
+ }else if(ChaseMode == CHASE_MODE_RANGE){
+ if(Distance > 4){
+ Master->ToDoGo(Target->posx, Target->posy, Target->posz, false, Distance - 4);
+ }else if(Distance < 4){
+ int DestX, DestY, DestZ;
+ if(SearchFlightField(Master->ID, Target->ID, &DestX, &DestY, &DestZ)){
+ Master->ToDoGo(DestX, DestY, DestZ, true, -1);
+ }
+ }
+ }
+}
+
void TCombat::Attack(void){
if(this->AttackDest == 0 || this->Following){
return;
}
+ // TODO(fusion): There is some `CanAttack` function inlined here.
TCreature *Master = this->Master;
if(Master == NULL){
error("TCombat::CanAttack: Kein Master gesetzt!\n");
@@ -232,7 +406,7 @@ void TCombat::Attack(void){
}
this->CloseAttack(Target);
}else{
- if(Range < 1 || Range > 3){
+ if(Range < 2 || Range > 3){
throw ERROR;
}
@@ -243,7 +417,16 @@ void TCombat::Attack(void){
throw TARGETOUTOFRANGE;
}
- this->RangeAttack(Target);
+ // NOTE(fusion): Originally, this was a single function `RangeAttack`
+ // and I quickly realized that readability would be massively improved
+ // by splitting it into `DistanceAttack` and `WandAttack`.
+ if(this->Missile != NONE || this->Throw != NONE){
+ this->DistanceAttack(Target);
+ }else if(this->Wand != NONE){
+ this->WandAttack(Target);
+ }else{
+ throw ERROR;
+ }
}
this->DelayAttack(2000);
@@ -270,3 +453,229 @@ void TCombat::DelayAttack(int Milliseconds){
this->EarliestAttackTime = EarliestAttackTime;
}
}
+
+void TCombat::CloseAttack(TCreature *Target){
+ int Attack;
+ uint16 SkillNr;
+ this->GetAttackValue(&Attack, &SkillNr);
+ if(this->AttackMode == ATTACK_MODE_OFFENSIVE){
+ Attack += (Attack * 2) / 10;
+ }else if(this->AttackMode == ATTACK_MODE_DEFENSIVE){
+ Attack -= (Attack * 4) / 10;
+ }
+
+ TCreature *Master = this->Master;
+ Attack = Master->Skills[SkillNr]->ProbeValue(Attack, this->LearningPoints > 0);
+ if(this->LearningPoints > 0){
+ this->LearningPoints -= 1;
+ }
+
+ int Defense = Target->Combat.GetDefendDamage();
+ int Damage = Attack - Defense;
+ if(Damage < 0){
+ Damage = 0;
+ }
+
+ int DamageDone = Target->Damage(Master, Damage, 1); // DAMAGE_PHYSICAL ?
+ if(DamageDone > 0){
+ this->LearningPoints = 30;
+ }
+
+ int Poison = GetRacePoison(Master->Race);
+ if(Poison != 0){
+ if(DamageDone > 0 || (Attack > Defense && (rand() % 5) == 0)){
+ int PoisonDamage = random(Poison / 2, Poison);
+ if(PoisonDamage > 0){
+ Target->Damage(Master, PoisonDamage, DAMAGE_POISON_PERIODIC);
+ if(Target->Type == PLAYER){
+ SendMessage(Target->Connection, TALK_STATUS_MESSAGE, "You are poisoned.");
+ }
+ }
+ }
+ }
+
+ Object Close = this->Close;
+ if(Close != NONE){
+ ObjectType CloseType = Close.getObjectType();
+ if(CloseType.getFlag(WEAROUT)){
+ uint32 RemainingUses = Close.getAttribute(REMAININGUSES);
+ if(RemainingUses > 1){
+ Change(Close, REMAININGUSES, RemainingUses - 1);
+ }else{
+ ObjectType WearOutType = CloseType.getAttribute(WEAROUTTARGET);
+ Change(Close, WearOutType, 0);
+ this->CheckCombatValues();
+ }
+ }
+ }
+}
+
+void TCombat::WandAttack(TCreature *Target){
+ ASSERT(this->Wand != NONE);
+
+ TCreature *Master = this->Master;
+ int Distance = std::min<int>(
+ std::abs(Master->posx - Target->posx),
+ std::abs(Master->posy - Target->posy));
+
+ ObjectType WandType = this->Wand.getObjectType();
+ if(Distance > (int)WandType.getAttribute(WANDRANGE)){
+ throw TARGETOUTOFRANGE;
+ }
+
+ if(!ThrowPossible(Master->posx, Master->posy, Master->posz,
+ Target->posx, Target->posy, Target->posz, 0)){
+ throw TARGETHIDDEN;
+ }
+
+ int DamageType = WandType.getAttribute(WANDDAMAGETYPE);
+ int AnimType = WandType.getAttribute(WANDMISSILE);
+ int ManaConsumption = WandType.getAttribute(WANDMANACONSUMPTION);
+ int AttackStrength = WandType.getAttribute(WANDATTACKSTRENGTH);
+ int AttackVariation = (int)WandType.getAttribute(WANDATTACKVARIATION);
+
+ // NOTE(fusion): Oof...
+ try{
+ CheckMana(Master, ManaConsumption, 0, 0);
+ }catch(RESULT err){
+ if(err == NOTENOUGHMANA){
+ throw OUTOFAMMO;
+ }else{
+ throw err;
+ }
+ }
+
+ int Damage = AttackStrength + random(-AttackVariation, AttackVariation);
+ if(Target->Damage(Master, Damage, DamageType) > 0){
+ this->LearningPoints = 30;
+ }
+
+ ::Missile(Master->CrObject, Target->CrObject, AnimType);
+}
+
+void TCombat::DistanceAttack(TCreature *Target){
+ ASSERT(this->Missile != NONE || this->Throw != NONE);
+
+ int HitChance;
+ int DamageType;
+ int AnimType;
+ int Fragility;
+ int SpecialEffect;
+ int EffectStrength;
+
+ TCreature *Master = this->Master;
+ int DistanceX = std::abs(Master->posx - Target->posx);
+ int DistanceY = std::abs(Master->posy - Target->posy);
+ int Distance = std::max<int>(DistanceX, DistanceY);
+
+ if(this->Missile != NONE){
+ this->GetAmmo();
+ if(this->Ammo == NONE || !this->Ammo.exists()){
+ throw OUTOFAMMO;
+ }
+
+ ObjectType BowType = this->Missile.getObjectType();
+ ObjectType AmmoType = this->Ammo.getObjectType();
+ if(Distance > (int)BowType.getAttribute(BOWRANGE)){
+ throw TARGETOUTOFRANGE;
+ }
+
+ HitChance = 90;
+ DamageType = DAMAGE_PHYSICAL;
+ AnimType = AmmoType.getAttribute(AMMOMISSILE);
+ Fragility = 100;
+ SpecialEffect = AmmoType.getAttribute(AMMOSPECIALEFFECT);
+ EffectStrength = AmmoType.getAttribute(AMMOEFFECTSTRENGTH);
+ }else{
+ ASSERT(this->Throw != NONE);
+ ObjectType ThrowType = this->Throw.getObjectType();
+ if(Distance > (int)ThrowType.getAttribute(THROWRANGE)){
+ throw TARGETOUTOFRANGE;
+ }
+
+ HitChance = 75;
+ DamageType = DAMAGE_PHYSICAL;
+ AnimType = ThrowType.getAttribute(THROWMISSILE);
+ Fragility = ThrowType.getAttribute(THROWFRAGILITY);
+ SpecialEffect = ThrowType.getAttribute(THROWSPECIALEFFECT);
+ EffectStrength = ThrowType.getAttribute(THROWEFFECTSTRENGTH);
+ }
+
+ if(!ThrowPossible(Master->posx, Master->posy, Master->posz,
+ Target->posx, Target->posy, Target->posz, 0)){
+ throw TARGETHIDDEN;
+ }
+
+ int Difficulty = (Distance >= 2) ? Distance : 5;
+ bool Hit = Master->Skills[SKILL_DISTANCE]->Probe(
+ Difficulty * 15, HitChance, this->LearningPoints > 0);
+ if(this->LearningPoints > 0){
+ this->LearningPoints -= 1;
+ }
+
+ int DropX = Target->posx;
+ int DropY = Target->posy;
+ int DropZ = Target->posz;
+ if(Hit){
+ int Attack;
+ uint16 SkillNr;
+ this->GetAttackValue(&Attack, &SkillNr);
+ if(this->AttackMode == ATTACK_MODE_OFFENSIVE){
+ Attack += (Attack * 2) / 10;
+ }else if(this->AttackMode == ATTACK_MODE_DEFENSIVE){
+ Attack -= (Attack * 4) / 10;
+ }
+
+ Attack = Master->Skills[SkillNr]->ProbeValue(Attack, this->LearningPoints > 0);
+ if(this->LearningPoints > 0){
+ this->LearningPoints -= 1;
+ }
+
+ // TODO(fusion): This doesn't make any sense. The disassembly looks
+ // correct but I feel we're missing something here. Or maybe defense
+ // doesn't work with ranged attacks but worn them out?
+ if(this->Shield != NONE){
+ Target->Combat.GetDefendDamage();
+ }
+
+ if(Target->Damage(Master, Attack, DamageType) > 0){
+ this->LearningPoints = 30;
+ }
+ }else{
+ if(DistanceX > 1 || DistanceY > 1){
+ DropX += (rand() % 3) - 1;
+ DropY += (rand() % 3) - 1;
+ }
+
+ if(!CoordinateFlag(DropX, DropY, DropZ, BANK)
+ || CoordinateFlag(DropX, DropY, DropZ, UNLAY)
+ || !ThrowPossible(Master->posx, Master->posy, Master->posz, DropX, DropY, DropZ, 0)){
+ DropX = Target->posx;
+ DropY = Target->posy;
+ }
+ }
+
+ Object DropCon = GetMapContainer(DropX, DropY, DropZ);
+ ::Missile(Master->CrObject, DropCon, AnimType);
+
+ if(SpecialEffect == 1){ // POISON ARROW
+ if(Hit){
+ Target->Damage(Master, EffectStrength, DAMAGE_POISON_PERIODIC);
+ }
+ }else if(SpecialEffect == 2){ // BURST ARROW
+ int Damage = ComputeDamage(Master, 0, EffectStrength, EffectStrength);
+ TDamageImpact Impact(Master, DAMAGE_PHYSICAL, Damage, false);
+ CircleShapeSpell(Master, DropX, DropY, DropZ, -1,
+ ANIMATION_NONE, 2, &Impact, EFFECT_BURST_ARROW);
+ }
+
+ if(random(0, 99) < Fragility){
+ Delete(this->Ammo, 1);
+ }else{
+ Move(0, this->Ammo, DropCon, 1, false, NONE);
+ }
+
+ if(!Hit){
+ GraphicalEffect(DropX, DropY, DropZ, EFFECT_POFF);
+ }
+}
diff --git a/src/crcombat.hh b/src/crcombat.hh
index f015252..bd22584 100644
--- a/src/crcombat.hh
+++ b/src/crcombat.hh
@@ -16,12 +16,17 @@ struct TCombat{
void GetWeapon(void);
void GetAmmo(void);
void CheckCombatValues(void);
- int GetDistance(void); // TODO
+ int GetDistance(void);
+ void GetAttackValue(int *Attack, uint16 *SkillNr);
+ int GetDefendDamage(void);
+ void SetAttackDest(uint32 TargetID, bool Follow);
+ void CanToDoAttack(void);
void Attack(void);
void StopAttack(int Delay);
void DelayAttack(int Milliseconds);
- void CloseAttack(TCreature *Target); // TODO
- void RangeAttack(TCreature *Target); // TODO
+ void CloseAttack(TCreature *Target);
+ void DistanceAttack(TCreature *Target);
+ void WandAttack(TCreature *Target);
// DATA
// =========================================================================
@@ -30,9 +35,9 @@ struct TCombat{
uint32 EarliestDefendTime;
uint32 LastDefendTime;
uint32 LatestAttackTime;
- uint32 AttackMode;
- uint32 ChaseMode;
- uint32 SecureMode;
+ uint8 AttackMode;
+ uint8 ChaseMode;
+ uint8 SecureMode;
uint32 AttackDest;
bool Following;
Object Shield;
diff --git a/src/creature.cc b/src/creature.cc
index 119d0e7..a02cb82 100644
--- a/src/creature.cc
+++ b/src/creature.cc
@@ -51,54 +51,6 @@ TCreature::TCreature(void) :
}
}
-// TODO(fusion): Probably better to figure out how TCombat and Object work.
-
-// Creature Functions
-//==============================================================================
-// TODO(fusion): This was the first function I attempted to cleanup but soon
-// realized we should start with the building blocks of the codebase, namely
-// TSkill, TSkillBase, etc...
-// We should probably come back to this once we start doing creature functions
-// again.
-void CheckMana(TCreature *Creature, int ManaPoints, int SoulPoints, int Delay){
- if(Creature == NULL){
- error("CheckMana: Übergebene Kreatur existiert nicht.\n");
- throw ERROR;
- }
-
- if(Creature->Type != PLAYER || ManaPoints < 0)
- return;
-
- TSkill *Mana = Creature->Skills[SKILL_MANA];
- if(Mana == NULL){
- error("CheckMana: Kein Skill MANA!\n");
- throw ERROR;
- }
-
- TSkill *Soul = Creature->Skills[SKILL_SOUL];
- if(Soul == NULL){
- error("CheckMana: Kein Skill SOULPOINTS!\n");
- throw ERROR;
- }
-
- if(!CheckRight(Creature->ID, UNLIMITED_MANA)){
- if(Mana->Get() < ManaPoints)
- throw NOTENOUGHMANA;
-
- if(Soul->Get() < SoulPoints)
- throw NOTENOUGHSOULPOINTS;
-
- Mana->Change(-ManaPoints);
- Soul->Change(-SoulPoints);
- }
-
- if(ManaPoints > 0){
- Creature->Skills[SKILL_MAGIC_LEVEL]->Increase(ManaPoints);
- }
-
- // NOTE(fusion): Maintain largest exhaust?
- uint32 EarliestSpellTime = ServerMilliseconds + Delay;
- if(Creature->EarliestSpellTime < EarliestSpellTime){
- Creature->EarliestSpellTime = EarliestSpellTime;
- }
+void TCreature::Attack(void){
+ this->Combat.Attack();
}
diff --git a/src/creature.hh b/src/creature.hh
index 69f0ef2..d5f4f1b 100644
--- a/src/creature.hh
+++ b/src/creature.hh
@@ -74,22 +74,24 @@ struct TCreature: TSkillBase {
// REGULAR FUNCTIONS
// =========================================================================
TCreature(void);
+ void Attack(void);
int Damage(TCreature *Attacker, int Damage, int DamageType);
void BlockLogout(int Delay, bool BlockProtectionZone);
+ void ToDoGo(int DestX, int DestY, int DestZ, bool Dest, int MaxSteps);
// VIRTUAL FUNCTIONS
// =========================================================================
virtual ~TCreature(void); // VTABLE[ 0]
// Duplicate destructor that also calls operator delete. // VTABLE[ 1]
virtual void Death(void); // VTABLE[ 2]
- virtual void MovePossible(void); // VTABLE[ 3]
- virtual void IsPeaceful(void); // VTABLE[ 4]
- virtual void GetMaster(void); // VTABLE[ 5]
- virtual void TalkStimulus(void); // VTABLE[ 6]
- virtual void DamageStimulus(void); // VTABLE[ 7]
+ virtual bool MovePossible(int x, int y, int z, bool Execute, bool Jump); // VTABLE[ 3]
+ virtual bool IsPeaceful(void); // VTABLE[ 4]
+ virtual uint32 GetMaster(void); // VTABLE[ 5]
+ virtual void TalkStimulus(uint32 SpeakerID, const char *Text); // VTABLE[ 6]
+ virtual void DamageStimulus(uint32 AttackerID, int Damage, int DamageType); // VTABLE[ 7]
virtual void IdleStimulus(void); // VTABLE[ 8]
- virtual void CreatureMoveStimulus(void); // VTABLE[ 9]
- virtual void AttackStimulus(void); // VTABLE[10]
+ virtual void CreatureMoveStimulus(uint32 CreatureID, int Type); // VTABLE[ 9]
+ virtual void AttackStimulus(uint32 AttackerID); // VTABLE[10]
// DATA
// =========================================================================
diff --git a/src/crskill.cc b/src/crskill.cc
index ce12cad..384e1fc 100644
--- a/src/crskill.cc
+++ b/src/crskill.cc
@@ -546,7 +546,11 @@ bool TSkillProbe::Probe(int Diff, int Prob, bool Increase){
this->Increase(1);
}
- // TODO(fusion): Not sure what's going on here.
+ // NOTE(fusion): `Diff` is a difficulty threshold and determines how challenging
+ // an action is based on the current skill level `this->Act`. The higher the
+ // skill level, the less difficult the action.
+ // I've only seen it used with SKILL_DISTANCE to determine whether a ranged
+ // attack should hit.
bool Result = true;
if(Diff != 0){
if(this->Act >= (rand() % Diff)){
@@ -1021,7 +1025,7 @@ void TSkillPoison::Event(int Range){
Range = -Range;
}
- Master->Damage(GetCreature(Master->PoisonDamageOrigin), Range, 2);
+ Master->Damage(GetCreature(Master->PoisonDamageOrigin), Range, DAMAGE_POISON);
// NOTE(fusion): I think this is checking whether `Master` is still upon some
// poison field to determine whether we should extend the poison effect?
@@ -1031,7 +1035,7 @@ void TSkillPoison::Event(int Range){
// OpenTibia terms.
ObjectType ObjType = Obj.getObjectType();
if(ObjType.getFlag(AVOID)){
- if(ObjType.getAttribute(AVOIDDAMAGETYPES) == 2){
+ if(ObjType.getAttribute(AVOIDDAMAGETYPES) == DAMAGE_POISON){
this->Cycle += 1;
}
}
@@ -1054,7 +1058,7 @@ void TSkillBurning::Event(int Range){
return;
}
- Master->Damage(GetCreature(Master->FireDamageOrigin), 10, 4);
+ Master->Damage(GetCreature(Master->FireDamageOrigin), 10, DAMAGE_FIRE);
// NOTE(Fusion): Something similar to `TSkillPoison::Event` except we're
// looking for a different field type.
@@ -1062,7 +1066,7 @@ void TSkillBurning::Event(int Range){
while(Obj != NONE){
ObjectType ObjType = Obj.getObjectType();
if(ObjType.getFlag(AVOID)){
- if(ObjType.getAttribute(AVOIDDAMAGETYPES) == 4){
+ if(ObjType.getAttribute(AVOIDDAMAGETYPES) == DAMAGE_FIRE){
this->Cycle += 1;
}
}
@@ -1080,7 +1084,7 @@ void TSkillEnergy::Event(int Range){
return;
}
- Master->Damage(GetCreature(Master->EnergyDamageOrigin), 25, 8);
+ Master->Damage(GetCreature(Master->EnergyDamageOrigin), 25, DAMAGE_ENERGY);
// NOTE(Fusion): Something similar to `TSkillPoison::Event` except we're
// looking for a different field type.
@@ -1088,7 +1092,7 @@ void TSkillEnergy::Event(int Range){
while(Obj != NONE){
ObjectType ObjType = Obj.getObjectType();
if(ObjType.getFlag(AVOID)){
- if(ObjType.getAttribute(AVOIDDAMAGETYPES) == 8){
+ if(ObjType.getAttribute(AVOIDDAMAGETYPES) == DAMAGE_ENERGY){
this->Cycle += 1;
}
}
diff --git a/src/enums.hh b/src/enums.hh
index f7aee3a..d8f5816 100644
--- a/src/enums.hh
+++ b/src/enums.hh
@@ -6,6 +6,31 @@
// TODO(fusion): Probably cleanup these names? Prefix values? Its crazy that
// there are no collision problems (possibly yet).
+enum AnimationType: int {
+ ANIMATION_NONE = 0,
+ ANIMATION_SPEAR = 1,
+ ANIMATION_BOLT = 2,
+ ANIMATION_ARROW = 3,
+ ANIMATION_FIRE = 4,
+ ANIMATION_ENERGY = 5,
+ ANIMATION_POISON_ARROW = 6,
+ ANIMATION_BURST_ARROW = 7,
+ ANIMATION_THROWING_STAR = 8,
+ ANIMATION_THROWING_KNIFE = 9,
+ ANIMATION_SMALL_STONE = 10,
+ ANIMATION_SNOWBALL = 13,
+ ANIMATION_POWER_BOLT = 14,
+ ANIMATION_POISON = 15,
+};
+
+// NOTE(fusion): Not in debug symbols.
+enum AttackMode: uint8 {
+ ATTACK_MODE_NONE = 0,
+ ATTACK_MODE_OFFENSIVE = 1,
+ ATTACK_MODE_BALANCED = 2,
+ ATTACK_MODE_DEFENSIVE = 3,
+};
+
enum BloodType: int {
BT_BLOOD = 0,
BT_SLIME = 1,
@@ -14,6 +39,13 @@ enum BloodType: int {
BT_ENERGY = 4,
};
+// NOTE(fusion): Not in debug symbols.
+enum ChaseMode: uint8 {
+ CHASE_MODE_NONE = 0,
+ CHASE_MODE_CLOSE = 1,
+ CHASE_MODE_RANGE = 2,
+};
+
enum CreatureType: int {
PLAYER = 0,
MONSTER = 1,
@@ -31,6 +63,26 @@ enum CONNECTIONSTATE: int {
CONNECTION_DISCONNECTED = 7,
};
+// NOTE(fusion): Not in debug symbols.
+enum DamageType: int {
+ DAMAGE_NONE = 0x0000,
+ DAMAGE_PHYSICAL = 0x0001,
+ DAMAGE_POISON = 0x0002,
+ DAMAGE_FIRE = 0x0004,
+ DAMAGE_ENERGY = 0x0008,
+ //DAMAGE_DEATH ? = 0x0010
+ DAMAGE_POISON_PERIODIC = 0x0020,
+ DAMAGE_FIRE_PERIODIC = 0x0040,
+ DAMAGE_ENERGY_PERIODIC = 0x0080,
+ DAMAGE_LIFEDRAIN = 0x0100,
+ DAMAGE_MANADRAIN = 0x0200,
+};
+
+enum EffectType: int {
+ EFFECT_POFF = 3,
+ EFFECT_BURST_ARROW = 7, // Review
+};
+
enum FLAG: int {
BANK = 0,
CLIP = 1,
@@ -134,6 +186,17 @@ enum KNOWNCREATURESTATE: int {
KNOWNCREATURE_OUTDATED = 2
};
+enum LiquidType: int {
+ LIQUID_NONE = 0,
+ LIQUID_WATER = 1,
+ LIQUID_WINE = 2,
+ LIQUID_BEER = 3,
+ LIQUID_MUD = 4,
+ LIQUID_BLOOD = 5,
+ LIQUID_SLIME = 6,
+ LIQUID_LEMONADE = 12,
+};
+
// NOTE(fusion): Not in debug symbols.
enum PROFESSION: uint8 {
PROFESSION_NONE = 0,
diff --git a/src/magic.cc b/src/magic.cc
new file mode 100644
index 0000000..f8f0505
--- /dev/null
+++ b/src/magic.cc
@@ -0,0 +1,74 @@
+#include "magic.hh"
+#include "creature.hh"
+
+// TImpact
+// =============================================================================
+void TImpact::handleField(int a, int b, int c){
+ // no-op
+}
+
+void TImpact::handleCreature(TCreature *Victim){
+ // no-op
+}
+
+bool TImpact::isAggressive(void){
+ return true;
+}
+
+// TDamageImpact
+// =============================================================================
+void TDamageImpact::TDamageImpact(TCreature *Actor, int DamageType, int Power, bool AllowDefense){
+ if(Actor == NULL){
+ error("TDamageImpact::TDamageImpact: Actor ist NULL.\n");
+ }
+
+ this->Actor = Actor;
+ this->DamageType = DamageType;
+ this->Power = Power;
+ this->AllowDefense = AllowDefense;
+}
+
+// Magic Related Functions
+// =============================================================================
+void CheckMana(TCreature *Creature, int ManaPoints, int SoulPoints, int Delay){
+ if(Creature == NULL){
+ error("CheckMana: Übergebene Kreatur existiert nicht.\n");
+ throw ERROR;
+ }
+
+ if(Creature->Type != PLAYER || ManaPoints < 0)
+ return;
+
+ TSkill *Mana = Creature->Skills[SKILL_MANA];
+ if(Mana == NULL){
+ error("CheckMana: Kein Skill MANA!\n");
+ throw ERROR;
+ }
+
+ TSkill *Soul = Creature->Skills[SKILL_SOUL];
+ if(Soul == NULL){
+ error("CheckMana: Kein Skill SOULPOINTS!\n");
+ throw ERROR;
+ }
+
+ if(!CheckRight(Creature->ID, UNLIMITED_MANA)){
+ if(Mana->Get() < ManaPoints)
+ throw NOTENOUGHMANA;
+
+ if(Soul->Get() < SoulPoints)
+ throw NOTENOUGHSOULPOINTS;
+
+ Mana->Change(-ManaPoints);
+ Soul->Change(-SoulPoints);
+ }
+
+ if(ManaPoints > 0){
+ Creature->Skills[SKILL_MAGIC_LEVEL]->Increase(ManaPoints);
+ }
+
+ // NOTE(fusion): Maintain largest exhaust?
+ uint32 EarliestSpellTime = ServerMilliseconds + Delay;
+ if(Creature->EarliestSpellTime < EarliestSpellTime){
+ Creature->EarliestSpellTime = EarliestSpellTime;
+ }
+}
diff --git a/src/magic.hh b/src/magic.hh
new file mode 100644
index 0000000..d24edd8
--- /dev/null
+++ b/src/magic.hh
@@ -0,0 +1,90 @@
+#ifndef TIBIA_MAGIC_HH_
+#define TIBIA_MAGIC_HH_ 1
+
+#include "common.hh"
+
+struct TCreature;
+
+struct TImpact{
+ // VIRTUAL FUNCTIONS
+ // =========================================================================
+ virtual void handleField(int a, int b, int c); // VTABLE[0]
+ virtual void handleCreature(TCreature *Victim); // VTABLE[1]
+ virtual bool isAggressive(void); // VTABLE[2]
+
+ // DATA
+ // =========================================================================
+ //void *VTABLE; // IMPLICIT
+};
+
+#if 0
+struct TSummonImpact {
+ TImpact super_TImpact; // INHERITANCE?
+ TCreature *Actor;
+ int Race;
+ int Maximum;
+};
+
+struct TSpeedImpact {
+ TImpact super_TImpact; // INHERITANCE?
+ TCreature *Actor;
+ int Percent;
+ int Duration;
+};
+
+struct THealingImpact {
+ TImpact super_TImpact; // INHERITANCE?
+ TCreature *Actor;
+ int Power;
+};
+
+struct TOutfitImpact {
+ TImpact super_TImpact; // INHERITANCE?
+ TCreature *Actor;
+ TOutfit Outfit;
+ int Duration;
+};
+
+struct TFieldImpact {
+ TImpact super_TImpact; // INHERITANCE?
+ TCreature *Actor;
+ int FieldType;
+};
+
+struct TDrunkenImpact {
+ TImpact super_TImpact; // INHERITANCE?
+ TCreature *Actor;
+ int Power;
+ int Duration;
+};
+
+struct TStrengthImpact {
+ TImpact super_TImpact; // INHERITANCE?
+ TCreature *Actor;
+ int Skills;
+ int Percent;
+ int Duration;
+};
+#endif
+
+struct TDamageImpact: TImpact{
+ // REGULAR FUNCTIONS
+ // =========================================================================
+ TDamageImpact(TCreature *Actor, int DamageType, int Power, bool AllowDefense);
+
+ // VIRTUAL FUNCTIONS
+ // =========================================================================
+ void handleCreature(TCreature *Victim) override;
+
+ // DATA
+ // =========================================================================
+ // TImpact super_TImpact; // IMPLICIT
+ TCreature *Actor;
+ int DamageType;
+ int Power;
+ bool AllowDefense;
+};
+
+void CheckMana(TCreature *Creature, int ManaPoints, int SoulPoints, int Delay);
+
+#endif //TIBIA_MAGIC_HH_
diff --git a/src/stubs.hh b/src/stubs.hh
index 6eb3d3d..622e588 100644
--- a/src/stubs.hh
+++ b/src/stubs.hh
@@ -5,6 +5,7 @@
#include "enums.hh"
#include "connection.hh"
#include "creature.hh"
+#include "magic.hh"
#include "map.hh"
#include "player.hh"
@@ -19,14 +20,22 @@ extern void AnnounceChangedCreature(uint32 CreatureID, int Type);
extern void BroadcastMessage(int Mode, const char *Text, ...) ATTR_PRINTF(2, 3);
extern void Change(Object Obj, ObjectType NewType, uint32 Value);
extern bool CheckRight(uint32 CreatureID, RIGHT Right);
+extern void CircleShapeSpell(TCreature *Actor, int DestX, int DestY, int DestZ,
+ int Range, int Animation, int Radius, TImpact *Impact, int Effect);
extern void CleanHouseField(int x, int y, int z);
+extern int ComputeDamage(TCreature *Creature, int SpellNr, int Damage, int Variation);
extern void CreatePlayerList(bool Online);
+extern void Delete(Object Obj, int Count);
extern Object GetBodyObject(uint32 CreatureID, int Position);
extern TCreature *GetCreature(uint32 CreatureID);
extern TConnection *GetFirstConnection(void);
extern TConnection *GetNextConnection(void);
+extern int GetRacePoison(int Race);
+extern void GraphicalEffect(int x, int y, int z, int Type);
+extern void GraphicalEffect(Object Obj, int Type);
extern void Log(const char *ProtocolName, const char *Text, ...) ATTR_PRINTF(2, 3);
extern void LogoutAllPlayers(void);
+extern void Missile(Object Start, Object Dest, int Type);
extern void Move(uint32 CreatureID, Object Obj, Object Con, int Count, bool NoMerge, Object Ignore);
extern void MoveCreatures(int Delay);
extern void NetLoadCheck(void);
@@ -47,6 +56,7 @@ extern void RefreshCylinders(void);
extern void RefreshMap(void);
extern void RefreshSector(int SectorX, int SectorY, int SectorZ, const uint8 *Data, int Size);
extern void SavePlayerDataOrder(void);
+extern bool SearchFlightField(uint32 FugitiveID, uint32 PursuerID, int *x, int *y, int *z);
extern void SendAll(void);
extern void SendAmbiente(TConnection *Connection);
extern void SendClearTarget(TConnection *Connection);
@@ -55,6 +65,8 @@ extern void SendMessage(TConnection *Connection, int Mode, const char *Text, ...
extern void SendPlayerData(TConnection *Connection);
extern void SendPlayerSkills(TConnection *Connection);
extern void SendPlayerState(TConnection *Connection, uint8 State);
+extern bool ThrowPossible(int FromX, int FromY, int FromZ,
+ int ToX, int ToY, int ToZ, int Power);
extern void WriteKillStatistics(void);
#endif //TIBIA_STUBS_HH_