aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfusion32 <marcopuzziello@gmail.com>2025-05-30 10:32:29 -0300
committerfusion32 <marcopuzziello@gmail.com>2025-05-30 10:32:29 -0300
commitdff5e62f6ebb1b06c234b9144458788893b6ca86 (patch)
tree828c51278c906ad9e71dac3b54b7246a0c04706d
parentf6c8c67094de3fe19f1e7871a7e78a91fb9bf3dd (diff)
downloadgame-dff5e62f6ebb1b06c234b9144458788893b6ca86.tar.gz
game-dff5e62f6ebb1b06c234b9144458788893b6ca86.zip
initial work on `crcombat.cc`
-rw-r--r--TODO.md4
-rw-r--r--src/crcombat.cc243
-rw-r--r--src/crcombat.hh8
-rw-r--r--src/creature.cc2
-rw-r--r--src/creature.hh1
-rw-r--r--src/player.cc27
-rw-r--r--src/player.hh8
-rw-r--r--src/stubs.hh3
8 files changed, 292 insertions, 4 deletions
diff --git a/TODO.md b/TODO.md
index 926cd31..11e3497 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,9 +1,11 @@
## TODO NEXT
-- STRINGS.CC
- TCreature
- TPlayer
- TNonPlayer
+## Stack allocations
+Any functions that use `alloca` or some other form of dynamic stack allocations will cause decompiled functions to be an absolute mess. It usually shows up in the decompiled code as both a size computation like `-(VAR + CONST & 0xfffffff0)`, followed by some assignment. It doesn't make total sense without looking at the disassembly. I've encountered ~30 such computations and expect the functions containing them to be amongt the most challenging/annoying to be properly decompiled.
+
## Estimate
The decompiled file has ~115K lines of C. If we take ~15K lines to be rubbish, this can be round to ~100K. Considering a low estimate of 200 lines per day, the whole process could take up to 500 days which is quite a bit but not impossible. Now considering a high estimate of 1K lines per day, it could take 100 days which is also quite a bit.
diff --git a/src/crcombat.cc b/src/crcombat.cc
index 854ffe2..de6385a 100644
--- a/src/crcombat.cc
+++ b/src/crcombat.cc
@@ -1,4 +1,8 @@
#include "creature.hh"
+#include "player.hh"
+#include "config.hh"
+
+#include "stubs.hh"
TCombat::TCombat(void){
this->Master = NULL;
@@ -28,4 +32,241 @@ TCombat::TCombat(void){
this->LearningPoints = 0;
}
-// TODO(fusion): Probably better to figure out how Object work.
+void TCombat::GetWeapon(void){
+ this->Shield = NONE;
+ this->Close = NONE;
+ this->Missile = NONE;
+ this->Throw = NONE;
+ this->Wand = NONE;
+ this->Ammo = NONE;
+ this->Fist = true;
+
+ // TODO(fusion): Check if `Master` is NULL?
+ TCreature *Master = this->Master;
+ if(!Master->CrObject.exists()){
+ error("TCombat::GetWeapon: Kreatur-Objekt existiert nicht.\n");
+ return;
+ }
+
+ // TODO(fusion): We're iterating over left and right hand slots. Make
+ // it clearer perhaps.
+ for(int Hand = 5; Hand <= 6; Hand += 1){
+ Object Obj = GetBodyObject(Master->ID, Hand);
+ if(Obj == NONE){
+ continue;
+ }
+
+ ObjectType ObjType = Obj.getObjectType();
+
+ if(ObjType.getFlag(RESTRICTLEVEL)){
+ int CurrentLevel = Master->Skills[SKILL_LEVEL]->Get();
+ int MinimumLevel = (int)ObjType.getAttribute(MINIMUMLEVEL);
+ if(CurrentLevel < MinimumLevel){
+ continue;
+ }
+ }
+
+ if(ObjType.getFlag(RESTRICTPROFESSION) && Master->Type == PLAYER){
+ uint32 ProfessionMask = ObjType.getAttribute(PROFESSIONS);
+ uint8 Profession = ((TPlayer*)Master)->GetEffectiveProfession();
+ if((ProfessionMask & (1 << Profession)) == 0){
+ continue;
+ }
+ }
+
+ if(ObjType.getFlag(SHIELD)){
+ this->Shield = Obj;
+ }
+
+ if(ObjType.getFlag(WEAPON)){
+ this->Close = Obj;
+ this->Fist = false;
+ }
+
+ if(ObjType.getFlag(BOW)){
+ this->Missile = Obj;
+ this->Fist = false;
+ }
+
+ if(ObjType.getFlag(THROW)){
+ this->Throw = Obj;
+ this->Fist = false;
+ }
+
+ if(ObjType.getFlag(WAND)){
+ this->Wand = Obj;
+ this->Fist = false;
+ }
+ }
+}
+
+void TCombat::GetAmmo(void){
+ if(this->Missile == NONE){
+ if(this->Throw != NONE){
+ this->Ammo = this->Throw;
+ }else if(this->Wand != NONE){
+ this->Ammo = this->Wand;
+ }
+ return;
+ }
+
+ // TODO(fusion): Check if `Master` is NULL?
+ Object Ammo = GetBodyObject(this->Master->ID, 10);
+ this->Ammo = NONE;
+ if(Ammo != NONE){
+ ObjectType AmmoType = Ammo.getObjectType();
+ if(AmmoType.getFlag(AMMO)){
+ ObjectType BowType = this->Missile.getObjectType();
+ if(AmmoType.getAttribute(AMMOTYPE) == BowType.getAttribute(BOWAMMOTYPE)){
+ this->Ammo = Ammo;
+ }
+ }
+ }
+}
+
+void TCombat::CheckCombatValues(void){
+ Object OldShield = this->Shield;
+ Object OldClose = this->Close;
+ Object OldMissile = this->Missile;
+ Object OldThrow = this->Throw;
+ Object OldWand = this->Wand;
+ Object OldAmmo = this->Ammo;
+ bool OldFist = this->Fist;
+
+ this->GetWeapon();
+ this->GetAmmo();
+ if(OldShield != this->Shield
+ || OldClose != this->Close
+ || OldMissile != this->Missile
+ || OldThrow != this->Throw
+ || OldWand != this->Wand
+ || OldAmmo != this->Ammo
+ || OldFist != this->Fist){
+ this->DelayAttack(2000);
+ }
+}
+
+void TCombat::Attack(void){
+ if(this->AttackDest == 0 || this->Following){
+ return;
+ }
+
+ 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;
+ }
+
+ // NOTE(fusion): This probably has something to do with calling `StopAttack`
+ // with a non zero delay.
+ if(this->LatestAttackTime != 0 && this->LatestAttackTime < RoundNr){
+ this->StopAttack(0);
+ return;
+ }
+
+ if(Master->Type == PLAYER && Target->Type != PLAYER){
+ if(Target->Outfit.OutfitID == 0 && Target->Outfit.ObjectType == 0){
+ this->StopAttack(0);
+ throw TARGETLOST;
+ }
+ }
+
+ if(Master->Type == PLAYER && Target->Type == PLAYER){
+ if(this->SecureMode == 1 && WorldType == NORMAL
+ && !((TPlayer*)Master)->IsAttackJustified(Target->ID)){
+ this->StopAttack(0);
+ throw SECUREMODE;
+ }
+ }
+
+ // TODO(fusion): It is weird that max attack distance is hardcoded.
+ // Actually, it is related to the maximum visible distance on a
+ // creature's viewport.
+ int Distance = ObjectDistance(Master->CrObject, Target->CrObject);
+ if(Distance > 8){
+ this->StopAttack(0);
+ throw TARGETLOST;
+ }
+
+ if(Master->Type == PLAYER && Target->Type == PLAYER){
+ if(((TPlayer*)Master)->GetRealProfession() == PROFESSION_NONE
+ && !CheckRight(Master->ID, ATTACK_EVERYWHERE)){
+ this->StopAttack(0);
+ throw ATTACKNOTALLOWED;
+ }
+ }
+
+ if(Master->Type == PLAYER){
+ if(CheckRight(Master->ID, NO_ATTACK)){
+ this->StopAttack(0);
+ throw ATTACKNOTALLOWED;
+ }
+ }
+
+ if(IsProtectionZone(Master->posx, Master->posy, Master->posz)
+ || IsProtectionZone(Target->posx, Target->posy, Target->posz)){
+ this->StopAttack(0);
+ throw PROTECTIONZONE;
+ }
+
+ Master->BlockLogout(60, Target->Type == PLAYER);
+ Target->BlockLogout(60, false);
+
+ if(Master->Type == PLAYER && Target->Type == PLAYER){
+ ((TPlayer*)Master)->RecordAttack(Target->ID);
+ }
+
+ this->DelayAttack(200);
+
+ // TODO(fusion): This `Range` value doesn't make a lot of sense.
+ int Range = this->GetDistance();
+ if(Range == 1){
+ if(Distance > 1){
+ throw TARGETOUTOFRANGE;
+ }
+ this->CloseAttack(Target);
+ }else{
+ if(Range < 1 || Range > 3){
+ throw ERROR;
+ }
+
+ // TODO(fusion): These are related to the maximum visible distance on
+ // each coordinate.
+ if(std::abs(Master->posx - Target->posx) > 7
+ || std::abs(Master->posy - Target->posy) > 5){
+ throw TARGETOUTOFRANGE;
+ }
+
+ this->RangeAttack(Target);
+ }
+
+ this->DelayAttack(2000);
+
+ if(Target->IsDead){
+ this->StopAttack(0);
+ }
+}
+
+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;
+ }
+}
diff --git a/src/crcombat.hh b/src/crcombat.hh
index ce75002..f015252 100644
--- a/src/crcombat.hh
+++ b/src/crcombat.hh
@@ -13,7 +13,15 @@ struct TCombat{
// REGULAR FUNCTIONS
// =========================================================================
TCombat(void);
+ void GetWeapon(void);
+ void GetAmmo(void);
void CheckCombatValues(void);
+ int GetDistance(void); // TODO
+ void Attack(void);
+ void StopAttack(int Delay);
+ void DelayAttack(int Milliseconds);
+ void CloseAttack(TCreature *Target); // TODO
+ void RangeAttack(TCreature *Target); // TODO
// DATA
// =========================================================================
diff --git a/src/creature.cc b/src/creature.cc
index 549543e..119d0e7 100644
--- a/src/creature.cc
+++ b/src/creature.cc
@@ -97,7 +97,7 @@ void CheckMana(TCreature *Creature, int ManaPoints, int SoulPoints, int Delay){
}
// NOTE(fusion): Maintain largest exhaust?
- uint32 EarliestSpellTime = Delay + ServerMilliseconds;
+ uint32 EarliestSpellTime = ServerMilliseconds + Delay;
if(Creature->EarliestSpellTime < EarliestSpellTime){
Creature->EarliestSpellTime = EarliestSpellTime;
}
diff --git a/src/creature.hh b/src/creature.hh
index bafc55e..69f0ef2 100644
--- a/src/creature.hh
+++ b/src/creature.hh
@@ -75,6 +75,7 @@ struct TCreature: TSkillBase {
// =========================================================================
TCreature(void);
int Damage(TCreature *Attacker, int Damage, int DamageType);
+ void BlockLogout(int Delay, bool BlockProtectionZone);
// VIRTUAL FUNCTIONS
// =========================================================================
diff --git a/src/player.cc b/src/player.cc
index 0ee78f3..04298c6 100644
--- a/src/player.cc
+++ b/src/player.cc
@@ -3,6 +3,33 @@
#include "stubs.hh"
+uint8 TPlayer::GetRealProfession(void){
+ return this->Profession;
+}
+
+uint8 TPlayer::GetEffectiveProfession(void){
+ uint8 Profession = this->Profession;
+ if(Profession >= 10){
+ Profession -= 10;
+ }
+ return Profession;
+}
+
+uint8 TPlayer::GetActiveProfession(void){
+ uint8 Profession;
+ if(CheckRight(this->ID, PREMIUM_ACCOUNT)){
+ Profession = this->Profession;
+ }else{
+ Profession = this->GetEffectiveProfession();
+ }
+ return Profession;
+}
+
+bool TPlayer::GetActivePromotion(void){
+ return CheckRight(this->ID, PREMIUM_ACCOUNT)
+ && this->Profession >= 10;
+}
+
void TPlayer::CheckState(void){
if(this->Connection != NULL){
uint8 State = 0;
diff --git a/src/player.hh b/src/player.hh
index bb58ac3..815289e 100644
--- a/src/player.hh
+++ b/src/player.hh
@@ -68,8 +68,14 @@ struct TPlayerData {
struct TPlayer: TCreature {
// REGULAR FUNCTIONS
// =========================================================================
- void CheckState(void);
+ uint8 GetRealProfession(void);
+ uint8 GetEffectiveProfession(void);
uint8 GetActiveProfession(void);
+ bool GetActivePromotion(void);
+ bool IsAttackJustified(uint32 Victim);
+ void RecordAttack(uint32 Victim);
+
+ void CheckState(void);
// VIRTUAL FUNCTIONS
// =========================================================================
diff --git a/src/stubs.hh b/src/stubs.hh
index 372ca92..6eb3d3d 100644
--- a/src/stubs.hh
+++ b/src/stubs.hh
@@ -21,6 +21,7 @@ extern void Change(Object Obj, ObjectType NewType, uint32 Value);
extern bool CheckRight(uint32 CreatureID, RIGHT Right);
extern void CleanHouseField(int x, int y, int z);
extern void CreatePlayerList(bool Online);
+extern Object GetBodyObject(uint32 CreatureID, int Position);
extern TCreature *GetCreature(uint32 CreatureID);
extern TConnection *GetFirstConnection(void);
extern TConnection *GetNextConnection(void);
@@ -31,6 +32,7 @@ extern void MoveCreatures(int Delay);
extern void NetLoadCheck(void);
extern void NetLoadSummary(void);
extern void NotifyAllCreatures(Object Obj, int Type, Object OldCon);
+extern int ObjectDistance(Object Obj1, Object Obj2);
extern void ProcessCommunicationControl(void);
extern void ProcessConnections(void);
extern void ProcessCreatures(void);
@@ -47,6 +49,7 @@ extern void RefreshSector(int SectorX, int SectorY, int SectorZ, const uint8 *Da
extern void SavePlayerDataOrder(void);
extern void SendAll(void);
extern void SendAmbiente(TConnection *Connection);
+extern void SendClearTarget(TConnection *Connection);
extern void SendMails(TPlayerData *PlayerData);
extern void SendMessage(TConnection *Connection, int Mode, const char *Text, ...) ATTR_PRINTF(3, 4);
extern void SendPlayerData(TConnection *Connection);