diff options
| author | fusion32 <marcopuzziello@gmail.com> | 2025-05-31 02:25:31 -0300 |
|---|---|---|
| committer | fusion32 <marcopuzziello@gmail.com> | 2025-05-31 02:25:31 -0300 |
| commit | d7c11b271633ee1f5b58d2b679b50cf95b2826e0 (patch) | |
| tree | c08832dff011660dcc2af748b82d5ab0adf82d97 /src/crcombat.cc | |
| parent | 08780dad536eb5d0544eb2ea70e5443dc237976f (diff) | |
| download | game-d7c11b271633ee1f5b58d2b679b50cf95b2826e0.tar.gz game-d7c11b271633ee1f5b58d2b679b50cf95b2826e0.zip | |
done with `crcombat.cc`
Diffstat (limited to 'src/crcombat.cc')
| -rw-r--r-- | src/crcombat.cc | 388 |
1 files changed, 330 insertions, 58 deletions
diff --git a/src/crcombat.cc b/src/crcombat.cc index 256f056..7ad7534 100644 --- a/src/crcombat.cc +++ b/src/crcombat.cc @@ -1,4 +1,5 @@ #include "creature.hh" +#include "monster.hh" #include "player.hh" #include "config.hh" #include "magic.hh" @@ -13,7 +14,7 @@ TCombat::TCombat(void){ this->LatestAttackTime = 0; this->AttackMode = ATTACK_MODE_BALANCED; this->ChaseMode = CHASE_MODE_NONE; - this->SecureMode = 1; + this->SecureMode = SECURE_MODE_ENABLED; this->AttackDest = 0; this->Following = false; this->Shield = NONE; @@ -147,6 +148,164 @@ void TCombat::CheckCombatValues(void){ } } +static int WeaponTypeToSkill(int WeaponType){ + int Result = SKILL_FIST; + switch(WeaponType){ + case WEAPON_NONE: Result = SKILL_FIST; break; + case WEAPON_SWORD: Result = SKILL_SWORD; break; + case WEAPON_CLUB: Result = SKILL_CLUB; break; + case WEAPON_AXE: Result = SKILL_AXE; break; + case WEAPON_SHIELD: Result = SKILL_SHIELDING; break; + case WEAPON_AMMO: ATTR_FALLTHROUGH; + case WEAPON_THROW: Result = SKILL_DISTANCE; break; + } + return Result; +} + +void TCombat::GetAttackValue(int *Value, int *SkillNr){ + int AttackValue; + int WeaponType; + if(this->Close != NONE){ + ObjectType CloseType = this->Close.getObjectType(); + AttackValue = (int)CloseType.getAttribute(WEAPONATTACKVALUE); + WeaponType = (int)CloseType.getAttribute(WEAPONTYPE); + }else if(this->Missile != NONE){ + ObjectType AmmoType = this->Ammo.getObjectType(); + AttackValue = (int)AmmoType.getAttribute(AMMOATTACKVALUE); + WeaponType = WEAPON_AMMO; + }else if(this->Throw != NONE){ + ObjectType ThrowType = this->Throw.getObjectType(); + AttackValue = (int)ThrowType.getAttribute(THROWATTACKVALUE); + WeaponType = WEAPON_THROW; + }else if(this->Wand != NONE){ + AttackValue = 0; + WeaponType = WEAPON_NONE; + }else{ + AttackValue = RaceData[this->Master->Race].Attack; + WeaponType = WEAPON_NONE; + } + + *Value = AttackValue; + *SkillNr = WeaponTypeToSkill(WeaponType); +} + +void TCombat::GetDefendValue(int *Value, int *SkillNr){ + int DefenseValue; + int WeaponType; + if(this->Shield != NONE){ + ObjectType ShieldType = this->Shield.getObjectType(); + DefenseValue = (int)ShieldType.getAttribute(SHIELDDEFENDVALUE); + WeaponType = WEAPON_SHIELD; + }else if(this->Close != NONE){ + ObjectType CloseType = this->Close.getObjectType(); + DefenseValue = (int)CloseType.getAttribute(WEAPONDEFENDVALUE); + WeaponType = (int)CloseType.getAttribute(WEAPONTYPE); + }else if(this->Throw != NONE){ + ObjectType ThrowType = this->Throw.getObjectType(); + DefenseValue = (int)ThrowType.getAttribute(THROWDEFENDVALUE); + WeaponType = WEAPON_THROW; + }else if(this->Missile != NONE){ + // TODO(fusion): This might be correct. Having a bow equipped will reduce + // your defense to zero but having a wand equipped will give you base defense. + DefenseValue = 0; + WeaponType = WEAPON_AMMO; + }else{ + DefenseValue = RaceData[this->Master->Race].Defend; + WeaponType = WEAPON_NONE; + } + + *Value = DefenseValue; + *SkillNr = WeaponTypeToSkill(WeaponType); +} + +int TCombat::GetAttackDamage(void){ + int MaxValue, SkillNr; + this->GetAttackValue(&MaxValue, &SkillNr); + if(this->AttackMode == ATTACK_MODE_OFFENSIVE){ + MaxValue += (MaxValue * 2) / 10; + }else if(this->AttackMode == ATTACK_MODE_DEFENSIVE){ + MaxValue -= (MaxValue * 4) / 10; + } + + TSkill *Skill = this->Master->Skills[SkillNr]; + int Result = Skill->ProbeValue(MaxValue, this->LearningPoints > 0); + if(this->LearningPoints > 0){ + this->LearningPoints -= 1; + } + return Result; +} + +int TCombat::GetDefendDamage(void){ + if(this->EarliestDefendTime > ServerMilliseconds){ + return 0; + } + + this->EarliestDefendTime = this->LastDefendTime + 2000; + this->LastDefendTime = ServerMilliseconds; + + int AttackMode = this->AttackMode; + if(this->Following || this->AttackDest == 0){ + AttackMode = ATTACK_MODE_DEFENSIVE; + } + + int MaxValue, SkillNr; + this->GetDefendValue(&MaxValue, &SkillNr); + if(AttackMode == ATTACK_MODE_OFFENSIVE){ + MaxValue -= (MaxValue * 4) / 10; + }else if(AttackMode == ATTACK_MODE_DEFENSIVE){ + MaxValue += (MaxValue * 8) / 10; + } + + TSkill *Skill = this->Master->Skills[SkillNr]; + bool Increase = (this->Shield != NONE && this->LearningPoints > 0); + int Result = Skill->ProbeValue(MaxValue, Increase); + if(Increase){ + this->LearningPoints -= 1; + } + + Object Shield = this->Shield; + if(Shield != NONE){ + ObjectType ShieldType = Shield.getObjectType(); + if(ShieldType.getFlag(WEAROUT)){ + uint32 RemainingUses = Shield.getAttribute(REMAININGUSES); + if(RemainingUses > 1){ + Change(Shield, REMAININGUSES, RemainingUses - 1); + }else{ + ObjectType WearOutType = ShieldType.getAttribute(WEAROUTTARGET); + Change(Shield, WearOutType, 0); + // TODO(fusion): Probably just check anyways, to be sure. + if(!Shield.exists() || !WearOutType.getFlag(SHIELD)){ + this->CheckCombatValues(); + } + } + } + } + + return Result; +} + +int TCombat::GetArmorStrength(void){ + // TODO(fusion): We're iterating over inventory slots. Make it clearer? + int Armor = 0; + TCreature *Master = this->Master; + for(int Position = 1; Position <= 10; Position += 1){ + Object Obj = GetBodyObject(Master->ID, Position); + if(Obj.exists()){ + ObjectType ObjType = Obj.getObjectType(); + if(ObjType.getFlag(CLOTHES) && ObjType.getFlag(ARMOR) + && (int)ObjType.getAttribute(BODYPOSITION) == Position){ + Armor += (int)ObjType.getAttribute(ARMORVALUE); + } + } + } + + Armor += RaceData[Master->Race].Armor; + if(Armor >= 2){ + Armor = (Armor / 2) + rand() % (Armor / 2); + } + return Armor; +} + int TCombat::GetDistance(void){ int Distance = 0; if(this->Close != NONE || this->Fist){ @@ -159,6 +318,42 @@ int TCombat::GetDistance(void){ return Distance; } +void TCombat::ActivateLearning(void){ + this->LearningPoints = 30; +} + +void TCombat::SetAttackMode(uint8 AttackMode){ + if(AttackMode != ATTACK_MODE_OFFENSIVE + && AttackMode != ATTACK_MODE_BALANCED + && AttackMode != ATTACK_MODE_DEFENSIVE){ + error("TCombat::SetAttackMode: Ungültiger Angriffsmodus %d.\n", AttackMode); + return; + } + + if(this->AttackMode != AttackMode){ + this->DelayAttack(2000); + this->AttackMode = AttackMode; + } +} + +void TCombat::SetChaseMode(uint8 ChaseMode){ + if(ChaseMode != CHASE_MODE_NONE && ChaseMode != CHASE_MODE_CLOSE){ + error("TCombat::SetChaseMode: Ungültiger Verfolgungsmodus %d.\n", ChaseMode); + return; + } + + this->ChaseMode = ChaseMode; +} + +void TCombat::SetSecureMode(uint8 SecureMode){ + if(SecureMode != SECURE_MODE_DISABLED && SecureMode != SECURE_MODE_ENABLED){ + error("TCombat::SetSecureMode: Ungültiger Sicherheitsmodus %d.\n", SecureMode); + return; + } + + this->SecureMode = SecureMode; +} + void TCombat::SetAttackDest(uint32 TargetID, bool Follow){ if(this->AttackDest == TargetID && this->Following == Follow){ return; @@ -178,8 +373,8 @@ void TCombat::SetAttackDest(uint32 TargetID, bool Follow){ if(!Follow){ if(Master->Type == PLAYER && Target->Type == PLAYER){ - if(this->SecureMode == 1 && WorldType == NORMAL - && !((TPlayer*)Master)->IsAttackJustified(TargetID)){ + if(this->SecureMode == SECURE_MODE_ENABLED && WorldType == NORMAL + && !((TPlayer*)Master)->IsAttackJustified(TargetID)){ this->StopAttack(0); throw SECUREMODE; } @@ -275,8 +470,8 @@ void TCombat::CanToDoAttack(void){ if(!this->Following){ if(Master->Type == PLAYER && Target->Type == PLAYER){ - if(this->SecureMode == 1 && WorldType == NORMAL - && !((TPlayer*)Master)->IsAttackJustified(Target->ID)){ + if(this->SecureMode == SECURE_MODE_ENABLED && WorldType == NORMAL + && !((TPlayer*)Master)->IsAttackJustified(Target->ID)){ this->StopAttack(0); throw SECUREMODE; } @@ -319,6 +514,24 @@ void TCombat::CanToDoAttack(void){ } } +void TCombat::StopAttack(int Delay){ + if(Delay == 0){ + this->AttackDest = 0; + if(this->Master->Type == PLAYER){ + SendClearTarget(this->Master->Connection); + } + }else{ + this->LatestAttackTime = RoundNr + Delay; + } +} + +void TCombat::DelayAttack(int Milliseconds){ + uint32 EarliestAttackTime = ServerMilliseconds + Milliseconds; + if(this->EarliestAttackTime < EarliestAttackTime){ + this->EarliestAttackTime = EarliestAttackTime; + } +} + void TCombat::Attack(void){ if(this->AttackDest == 0 || this->Following){ return; @@ -352,8 +565,8 @@ void TCombat::Attack(void){ } if(Master->Type == PLAYER && Target->Type == PLAYER){ - if(this->SecureMode == 1 && WorldType == NORMAL - && !((TPlayer*)Master)->IsAttackJustified(Target->ID)){ + if(this->SecureMode == SECURE_MODE_ENABLED && WorldType == NORMAL + && !((TPlayer*)Master)->IsAttackJustified(Target->ID)){ this->StopAttack(0); throw SECUREMODE; } @@ -436,49 +649,17 @@ void TCombat::Attack(void){ } } -void TCombat::StopAttack(int Delay){ - if(Delay == 0){ - this->AttackDest = 0; - if(this->Master->Type == PLAYER){ - SendClearTarget(this->Master->Connection); - } - }else{ - this->LatestAttackTime = RoundNr + Delay; - } -} - -void TCombat::DelayAttack(int Milliseconds){ - uint32 EarliestAttackTime = ServerMilliseconds + Milliseconds; - if(this->EarliestAttackTime < EarliestAttackTime){ - 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 Attack = this->GetAttackDamage(); int Defense = Target->Combat.GetDefendDamage(); int Damage = Attack - Defense; if(Damage < 0){ Damage = 0; } - int DamageDone = Target->Damage(Master, Damage, 1); // DAMAGE_PHYSICAL ? + int DamageDone = Target->Damage(Master, Damage, DAMAGE_PHYSICAL); if(DamageDone > 0){ - this->LearningPoints = 30; + this->ActivateLearning(); } int Poison = GetRacePoison(Master->Race); @@ -547,7 +728,7 @@ void TCombat::WandAttack(TCreature *Target){ int Damage = AttackStrength + random(-AttackVariation, AttackVariation); if(Target->Damage(Master, Damage, DamageType) > 0){ - this->LearningPoints = 30; + this->ActivateLearning(); } ::Missile(Master->CrObject, Target->CrObject, AnimType); @@ -617,29 +798,18 @@ void TCombat::DistanceAttack(TCreature *Target){ 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; - } + int Attack = this->GetAttackDamage(); // 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? + // doesn't work with ranged attacks but worn them out? But then only + // if the attacker is wearing a shield? This is a probably a bug. if(this->Shield != NONE){ Target->Combat.GetDefendDamage(); } if(Target->Damage(Master, Attack, DamageType) > 0){ - this->LearningPoints = 30; + this->ActivateLearning(); } }else{ if(DistanceX > 1 || DistanceY > 1){ @@ -679,3 +849,105 @@ void TCombat::DistanceAttack(TCreature *Target){ GraphicalEffect(DropX, DropY, DropZ, EFFECT_POFF); } } + +void TCombat::AddDamageToCombatList(uint32 Attacker, uint32 Damage){ + this->CombatDamage += Damage; + for(int i = 0; i < NARRAY(this->CombatList); i += 1){ + if(this->CombatList[i].ID == Attacker){ + this->CombatList[i].Damage += Damage; + this->CombatList[i].TimeStamp = RoundNr; + return; + } + } + + int NextEntryIndex = this->ActCombatEntry; + this->CombatList[NextEntryIndex].ID = Attacker; + this->CombatList[NextEntryIndex].Damage = Damage; + this->CombatList[NextEntryIndex].TimeStamp = RoundNr; + + NextEntryIndex += 1; + if(NextEntryIndex >= NARRAY(this->CombatList)){ + NextEntryIndex = 0; + } + this->ActCombatEntry = NextEntryIndex; +} + +uint32 TCombat::GetDamageByCreature(uint32 CreatureID){ + uint32 Damage = 0; + for(int i = 0; i < NARRAY(this->CombatList); i += 1){ + if(this->CombatList[i].ID == CreatureID){ + Damage = this->CombatList[i].Damage; + break; + } + } + return Damage; +} + +uint32 TCombat::GetMostDangerousAttacker(void){ + uint32 Attacker = 0; + uint32 MaxDamage = 0; + for(int i = 0; i < NARRAY(this->CombatList); i += 1){ + if((RoundNr - this->CombatList[i].TimeStamp) < 60 + && this->CombatList[i].Damage > MaxDamage){ + Attacker = this->CombatList[i].ID; + MaxDamage = this->CombatList[i].Damage; + } + } + return Attacker; +} + +void TCombat::DistributeExperiencePoints(uint32 Exp){ + TCreature *Master = this->Master; + print(3, "%s ist gestorben. Verteile %u EXP...\n", Master->Name, Exp); + if(this->CombatDamage == 0){ + return; + } + + for(int i = 0; i < NARRAY(this->CombatList); i += 1){ + TCreature *Attacker = GetCreature(this->CombatList[i].ID); + if(Attacker == NULL || Attacker->IsDead){ + continue; + } + + int Amount = (int)((Exp * this->CombatList[i].Damage) / this->CombatDamage); + if(Master->Type == PLAYER && Attacker->Type == PLAYER){ + if(((TPlayer*)Master)->GetPartyLeader(true) == ((TPlayer*)Attacker)->GetPartyLeader(true)){ + continue; + } + + int MasterLevel = Master->Skills[SKILL_LEVEL]->Get(); + int AttackerLevel = Attacker->Skills[SKILL_LEVEL]->Get(); + int MaxLevel = (MasterLevel * 11) / 10; + if(AttackerLevel < MaxLevel){ + Amount = ((MaxLevel - AttackerLevel) * Amount) / MasterLevel; + }else{ + Amount = 0; + } + } + + if(Amount > 0){ + if(Attacker->Type == PLAYER){ + // NOTE(fusion): Enable soul regeneration. + int AttackerLevel = Attacker->Skills[SKILL_LEVEL]->Get(); + if(Amount >= AttackerLevel){ + int Interval = 120; + if(((TPlayer*)Attacker)->GetActivePromotion()){ + Interval = 15; + } + + int Count = Attacker->Skills[SKILL_SOUL]->TimerValue() % Interval; + if(Count == 0){ + Count = Interval; + } + + Attacker->SetTimer(SKILL_SOUL, (240 / Interval), Count, Interval, -1); + } + } + + Attacker->Skills[0]->Increase(Amount); + TextualEffect(Attacker->CrObject, 0xD7, "%d", Amount); + } + } +} + + |
