diff options
| -rw-r--r-- | build.bat | 2 | ||||
| -rw-r--r-- | src/creature.cc | 1249 | ||||
| -rw-r--r-- | src/creature.hh | 154 | ||||
| -rw-r--r-- | src/skill.cc | 1252 | ||||
| -rw-r--r-- | src/skill.hh | 159 |
5 files changed, 1413 insertions, 1403 deletions
@@ -10,7 +10,7 @@ set OUTPUTEXE=out.exe set OUTPUTPDB=out.pdb set INSTALLDIR=..\bin -set SRC="../src/creature.cc" "../src/main.cc" "../src/player.cc" +set SRC="../src/creature.cc" "../src/main.cc" "../src/player.cc" "../src/skill.cc" cl -W3 -WX -Zi -we4244 -we4456 -we4457 -wd4996 -std:c++14 -utf-8 -MP8 -DBUILD_DEBUG=1 -DARCH_X64=1 -D_CRT_SECURE_NO_WARNINGS=1 -Fe:"%OUTPUTEXE%" -Fd:"%OUTPUTPDB%" %* %SRC% /link -subsystem:console -incremental:no -opt:ref -dynamicbase "ws2_32.lib" if errorlevel 1 exit /b errorlevel diff --git a/src/creature.cc b/src/creature.cc index b6d2147..f3acdb8 100644 --- a/src/creature.cc +++ b/src/creature.cc @@ -1,1255 +1,6 @@ #include "creature.hh" #include "enums.hh" -#include "player.hh" - -#include <math.h> - -// TSkill REGULAR FUNCTIONS -//============================================================================== -TSkill::TSkill(int SkNr, TCreature *Master){ - // TODO(fusion): I'm not sure we're calling `Reset` here but the decompiled - // function sets the same values as `TSkill::Reset` which makes me wonder - // whether it is inlined. It's probably using `TSkill::Reset()` directly - // instead of virtual dispatch, if that is the case. - this->Reset(); - - this->SkNr = (uint16)SkNr; - this->Master = Master; -} - -int TSkill::Get(void){ - int Value = this->Act; - if(Value < this->Min) - Value = this->Min; - Value += this->MDAct + this->DAct; - return Value; -} - -int TSkill::GetProgress(void){ - int Result = 0; - if(this->NextLevel > this->LastLevel){ - Result = (this->Exp - this->LastLevel) * 100 / (this->NextLevel - this->LastLevel); - - // TODO(fusion): This feels too much for reporting a mostly *impossible* error. - if(Result < 0 || Result > 100){ - error("TSkill::GetProgress: Berechnungsfehler Exp %d, Last %d, Next %d, Prozent %d.\n", - this->Exp, this->LastLevel, this->NextLevel, Result); - - const char *MasterName = "(Unknown"; - if(this->Master != NULL){ - MasterName = this->Master->Name; - } - error("# Spieler %s - Skill %d\n", MasterName, this->SkNr); - Result = 0; - } - } - return Result; -} - -void TSkill::Check(void){ - if(this->Act > this->Max){ - this->Act = this->Max; - } -} - -void TSkill::Change(int Amount){ - this->Set(this->Act + Amount); - - // TODO(fusion): Probably `TSkill::Check` inlined? It doesn't make a whole - // lot of sense because `TSkill::Set` also checks the value. Maybe a custom - // version of it does something differently? - if(this->Act > this->Max){ - this->Act = this->Max; - } -} - -void TSkill::SetMDAct(int MDAct){ - this->MDAct = MDAct; - if(this->SkNr == 4 && this->Master && this->Master->Type == PLAYER){ - // TODO(fusion): Same as `TSkill::Process`. - ((TPlayer*)this->Master)->CheckState(); - } -} - -void TSkill::Load(int Act, int Max, int Min, int DAct, int MDAct, - int Cycle, int MaxCycle, int Count, int MaxCount, int AddLevel, - int Exp, int FactorPercent, int NextLevel, int Delta){ - this->Act = Act; - this->Max = Max; - this->Min = Min; - this->DAct = DAct; - this->MDAct = MDAct; - this->Cycle = Cycle; - this->MaxCycle = MaxCycle; - this->Count = Count; - this->MaxCount = MaxCount; - this->AddLevel = AddLevel; - this->Exp = Exp; - this->FactorPercent = FactorPercent; - this->NextLevel = NextLevel; - this->Delta = Delta; - - // TODO(fusion): Shouldn't we also do the same for `NextLevel`? - this->LastLevel = this->GetExpForLevel(Act); - - TCreature *Master = this->Master; - if(Master && Cycle != 0){ - // NOTE(fusion): It seems we had `TSkillBase::SetTimer` inlined here. - // For whatever reason I hadn't noticed the error message referencing - // it, LOL. - Master->SetTimer(this->SkNr, Cycle, Count, MaxCount, FactorPercent); - } -} - - -void TSkill::Save(int *Act, int *Max, int *Min, int *DAct, int *MDAct, - int *Cycle, int *MaxCycle, int *Count, int *MaxCount, int *AddLevel, - int *Exp, int *FactorPercent, int *NextLevel, int *Delta){ - *Act = this->Act; - *Max = this->Max; - *Min = this->Min; - *DAct = this->DAct; - *MDAct = this->MDAct; - *Cycle = this->Cycle; - *MaxCycle = this->MaxCycle; - *Count = this->Count; - *MaxCount = this->MaxCount; - *AddLevel = this->AddLevel; - *Exp = this->Exp; - *FactorPercent = this->FactorPercent; - *NextLevel = this->NextLevel; - *Delta = this->Delta; -} - -// TSkill VIRTUAL FUNCTIONS -//============================================================================== -TSkill::~TSkill(void){ - if(this->Master != NULL){ - this->Master->DelTimer(this->SkNr); - } -} - -void TSkill::Set(int Value){ - if(Value > this->Max) - Value = this->Max; - this->Act = Value; -} - -void TSkill::Increase(int Amount){ - // no-op -} - -void TSkill::Decrease(int Amount){ - // no-op -} - -int TSkill::GetExpForLevel(int Level){ - return 0; -} - -void TSkill::Advance(int Range){ - // no-op -} - -void TSkill::ChangeSkill(int FactorPercent, int Delta){ - // no-op -} - -int TSkill::ProbeValue(int Max, bool Increase){ - return 0; -} - -bool TSkill::Probe(int Diff, int Prob, bool Increase){ - return false; -} - -bool TSkill::Process(void){ - bool Result = this->Cycle == 0; - if(Result){ - if(this->Master && this->Master->Type == PLAYER){ - // TODO(fusion): Verify if a simple pointer cast works. Maybe `dynamic_cast`? - ((TPlayer*)this->Master)->CheckState(); - } - }else if(this->Count <= 0){ - this->Count = this->MaxCount; - // NOTE(fusion): `Range` should move `Cycle` towards ZERO. - int Range = (this->Cycle < 0) ? +1 : -1; - this->Cycle += Range; - this->Event(Range); - }else{ - this->Count -= 1; - } - return Result; -} - -bool TSkill::SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue){ - this->Cycle = Cycle; - this->Count = Count; - this->MaxCount = MaxCount; - if(this->Master && this->Master->Type == PLAYER){ - // TODO(fusion): Same as `TSkill::Process`. - ((TPlayer*)this->Master)->CheckState(); - } - return true; -} - -bool TSkill::DelTimer(void){ - this->Cycle = 0; - this->Count = 0; - this->MaxCount = 0; - if(this->Master && this->Master->Type == PLAYER){ - // TODO(fusion): Same as `TSkill::Process`. - ((TPlayer*)this->Master)->CheckState(); - } - return true; -} - -int TSkill::TimerValue(void){ - return this->Cycle; -} - -bool TSkill::Jump(int Range){ - return true; -} - -void TSkill::Event(int Range){ - // no-op -} - -void TSkill::Reset(void){ - this->Act = 0; - this->Max = INT_MAX; - this->Min = 0; - this->DAct = 0; - this->MDAct = 0; - this->FactorPercent = 1000; - this->LastLevel = 0; - this->NextLevel = INT_MAX; - this->Exp = 0; - this->Delta = INT_MAX; - this->Cycle = 0; - this->MaxCycle = 0; - this->Count = 0; - this->MaxCount = 0; - this->AddLevel = 0; -} - -// TSkillLevel -//============================================================================== -void TSkillLevel::Increase(int Amount){ - if(Amount < 0){ - error("TSkillLevel::Increase: Amount negativ (%d).\n", Amount); - return; - } - - // BUG(fusion): No bounds check as in `TSkillLevel::Decrease`? - - // TODO(fusion): This could probably be an oversight but the decompiled - // function was calling `GetExpForLevel` twice instead of using `NextLevel` - // which makes me wonder whether `NextLevel` is properly initialized. - int Range = 0; - this->Exp += Amount; - while(this->Exp >= this->NextLevel){ - this->Act += 1; - this->LastLevel = this->NextLevel; - this->NextLevel = this->GetExpForLevel(this->Act + 1); - if(this->NextLevel < 0){ - // BUG(fusion): We don't check if `Master` is valid here? - error("TSkillLevel::Increase: Skill vor Überlauf (%s, Skill %d).\n", this->Master->Name, this->SkNr); - this->NextLevel = this->Exp; - this->Exp -= 1000000; - break; - } - Range += 1; - } - - if(Range != 0){ - this->Jump(Range); - } - - if(this->Master == NULL){ - error("TSkillLevel::Increase: GetMaster liefert NULL zurueck.\n"); - return; - } - - if(this->Master->Type == PLAYER){ - SendPlayerData(this->Master->Connection); - } -} - -void TSkillLevel::Decrease(int Amount){ - if(Amount < 0){ - error("TSkillLevel::Decrease: Amount negativ (%d).\n", Amount); - return; - } - - // TODO(fusion): This is some weird ass comparison. - if(Amount > this->Exp && this->Exp > 100000){ - error("TSkillLevel::Decrease: Amount zu gross(%d).\n", Amount); - return; - } - - if(Amount < this->Exp){ - this->Exp -= Amount; - }else{ - this->Exp = 0; - } - - // TODO(fusion): This could probably be an oversight but the decompiled - // function was calling `GetExpForLevel` twice instead of using `LastLevel` - // which makes me wonder whether `LastLevel` is properly initialized. - int Range = 0; - while(this->Exp < this->LastLevel){ - this->Act -= 1; - this->NextLevel = this->LastLevel; - this->LastLevel = this->GetExpForLevel(this->Act); - Range -= 1; - } - - if(Range != 0){ - this->Jump(Range); - } - - if(this->Master == NULL){ - error("TSkillLevel::Decrease: GetMaster liefert NULL zurueck.\n"); - return; - } - - if(this->Master->Type == PLAYER){ - SendPlayerData(this->Master->Connection); - } -} - -int TSkillLevel::GetExpForLevel(int Level){ - if(Level < 1){ - error("TSkillLevel::GetExpForLevel: Ungültiger Level %d.\n", Level); - return 0; - } - - if(this->Delta <= 0){ - error("TSkillLevel::GetExpForLevel: Ungültiger Delta-Wert %d.\n", this->Delta); - return 0; - } - - if(Level > 500){ - error("TSkillLevel::GetExpForLevel: Level=%d; Formel gegen Überlauf sichern.\n", Level); - return -1; // TODO(fusion): Shouldn't this be 0? - } - - return ((((Level - 6) * Level + 17) * Level - 12) / 6) * this->Delta; -} - -bool TSkillLevel::Jump(int Range){ - if(this->Master == NULL){ - error("TSkillLevel::Jump: GetMaster liefert NULL zurueck!\n"); - return false; - } - - this->Master->Skills[SKILL_HITPOINTS ]->Advance(Range); - this->Master->Skills[SKILL_MANA ]->Advance(Range); - this->Master->Skills[SKILL_GO_STRENGTH ]->Advance(Range); - this->Master->Skills[SKILL_CARRY_WEIGHT]->Advance(Range); - - AnnounceChangedCreature(this->Master->ID, 4); - this->Master->Combat.CheckCombatValues(); - - if(this->Master->Type == PLAYER){ - int ToLevel = this->Get(); - int FromLevel = ToLevel - Range; - if(Range > 0){ - SendMessage(this->Master->Connection, TALK_EVENT_MESSAGE, - "You advanced from Level %d to Level %d.", FromLevel, ToLevel); - }else if(Range < 0){ - SendMessage(this->Master->Connection, TALK_EVENT_MESSAGE, - "You were downgraded from Level %d to Level %d.", FromLevel, ToLevel); - } - } - - return true; -} - -// TSkillProbe -//============================================================================== -void TSkillProbe::Increase(int Amount){ - if(Amount < 0){ - error("TSkillProbe::Increase: Amount negativ (%d).\n", Amount); - return; - } - - int OldProgress = this->GetProgress(); - - // TODO(fusion): This could probably be an oversight but the decompiled - // function was calling `GetExpForLevel` twice instead of using `NextLevel` - // which makes me wonder whether `NextLevel` is properly initialized. - int Range = 0; - this->Exp += Amount; - while(this->Exp >= this->NextLevel){ - this->Act += 1; - this->LastLevel = this->NextLevel; - this->NextLevel = this->GetExpForLevel(this->Act + 1); - if(this->NextLevel < 0){ - // BUG(fusion): We don't check if `Master` is valid here? - error("TSkillProbe::Increase: Skill vor Überlauf (%s, Skill %d).\n", this->Master->Name, this->SkNr); - this->NextLevel = this->Exp; - this->Exp -= 1000; - break; - } - Range += 1; - } - - if(Range != 0){ - this->Jump(Range); - }else{ - TCreature *Master = this->Master; - if(Master && Master->Type == PLAYER){ - int NewProgress = this->GetProgress(); - if(NewProgress != OldProgress){ - if(this->SkNr == SKILL_MAGIC_LEVEL){ - SendPlayerData(Master->Connection); - }else{ - SendPlayerSkills(Master->Connection); - } - } - } - } -} - -void TSkillProbe::Decrease(int Amount){ - if(Amount < 0){ - error("TSkillProbe::Decrease: Amount negativ (%d).\n", Amount); - return; - } - - int OldProgress = this->GetProgress(); - - if(Amount < this->Exp){ - this->Exp -= Amount; - }else{ - this->Exp = 0; - } - - // TODO(fusion): This could probably be an oversight but the decompiled - // function was calling `GetExpForLevel` twice instead of using `LastLevel` - // which makes me wonder whether `LastLevel` is properly initialized. - int Range = 0; - while(this->Exp < this->LastLevel && this->Act > this->Min){ - this->Act -= 1; - this->NextLevel = this->LastLevel; - this->LastLevel = this->GetExpForLevel(this->Act); - Range -= 1; - } - - if(Range != 0){ - this->Jump(Range); - }else{ - TCreature *Master = this->Master; - if(Master && Master->Type == PLAYER){ - int NewProgress = this->GetProgress(); - if(NewProgress != OldProgress){ - if(this->SkNr == SKILL_MAGIC_LEVEL){ - SendPlayerData(Master->Connection); - }else{ - SendPlayerSkills(Master->Connection); - } - } - } - } -} - -int TSkillProbe::GetExpForLevel(int Level){ - if(Level < 0 || Level < this->Min){ - error("TSkillProbe::GetExpForLevel: Ungültiger Level %d.\n", Level); - return 0; - } - - if(this->Delta <= 0){ - error("TSkillProbe::GetExpForLevel: Ungültiger Delta-Wert %d.\n", this->Delta); - return 0; - } - - // TODO(fusion): This part of the decompiled code was a bit weird. I had to - // look into the disassembly to figure the string parameter of `error` below - // and it is weird that we keep going even with invalid values of `FactorPercent`. - int FactorPercent = this->FactorPercent; - if(FactorPercent < 1000){ - const char *MasterName = "---"; - if(this->Master != NULL){ - MasterName = this->Master->Name; - } - error("TSkillProbe::GetExpForLevel: Ungültiger FactorPercent-Wert %d bei %s.\n", FactorPercent, MasterName); - } - - if(FactorPercent < 1050){ - error("TSkillProbe::GetExpForLevel: Ungültiger FactorPercent-Wert %d. Rechne mit 1000 weiter.\n", FactorPercent); - return (Level - this->Min) * this->Delta; - } - - double Base = (double)FactorPercent / 1000.0; - double Power = pow(Base, (double)(Level - this->Min)); - double Result = (double)this->Delta * ((Power - 1.0) / (Base - 1.0)); - return (int)Result; -} - -void TSkillProbe::ChangeSkill(int FactorPercent, int Delta){ - double Progress = 0.0; - if(this->LastLevel < this->NextLevel){ - Progress = (double)(this->Exp - this->LastLevel) - / (double)(this->NextLevel - this->LastLevel); - } - - double Base = (double)FactorPercent / 1000.0; - - // NOTE(fusion): Similar to `this->GetExpForLevel(this->Act)` but inlined? - double LastLevelPower = pow(Base, (double)(this->Act - this->Min)); - double LastLevel = (double)Delta * ((LastLevelPower - 1.0) / (Base - 1.0)); - - // NOTE(fusion): Similar to `this->GetExpForLevel(this->Act + 1)` but inlined? - double NextLevelPower = pow(Base, (double)(this->Act - this->Min + 1)); - double NextLevel = (double)Delta * ((NextLevelPower - 1.0) / (Base - 1.0)); - - // NOTE(fusion): Renormalize experience. - double Exp = LastLevel + Progress * (NextLevel - LastLevel); - - this->FactorPercent = FactorPercent; - this->LastLevel = (int)LastLevel; - this->NextLevel = (int)NextLevel; - this->Delta = Delta; - this->Exp = (int)Exp; - - TCreature *Master = this->Master; - if(Master != NULL && Master->Type == PLAYER){ - if(this->SkNr == SKILL_MAGIC_LEVEL){ - SendPlayerData(Master->Connection); - }else{ - SendPlayerSkills(Master->Connection); - } - } -} - -int TSkillProbe::ProbeValue(int Max, bool Increase){ - if(Increase){ - this->Increase(1); - } - - // TODO(fusion): Some optimizations made the decompilation output for this - // `RandomFactor` look very weird. It looks correct but we should come back - // to it. - int RandomFactor = ((rand() % 100) + (rand() % 100)) / 2; - int MaxValue = Max * (this->Get() * 5 + 50); - int Result = (RandomFactor * MaxValue) / 10000; - return Result; -} - -bool TSkillProbe::Probe(int Diff, int Prob, bool Increase){ - if(Increase){ - this->Increase(1); - } - - // TODO(fusion): Not sure what's going on here. - bool Result = true; - if(Diff != 0){ - if(this->Act >= (rand() % Diff)){ - Result = (rand() % 100) <= Prob; - }else{ - Result = false; - } - } - return Result; -} - -bool TSkillProbe::SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue){ - this->Cycle = Cycle; - this->Count = Count; - this->MaxCount = MaxCount; - - if(this->Master == NULL){ - error("TSkillProbe::SetTimer: GetMaster liefert NULL zurueck!\n"); - return false; - } - - if(this->Master->Type == PLAYER){ - ((TPlayer*)this->Master)->CheckState(); - SendPlayerData(this->Master->Connection); - } - - return true; -} - -bool TSkillProbe::Jump(int Range){ - TCreature *Master = this->Master; - if(Master == NULL){ - error("TSkillProbe::Jump: GetMaster liefert NULL zurueck!\n"); - return false; - } - - if(Master->Type == PLAYER){ - if(this->SkNr == SKILL_MAGIC_LEVEL){ - SendPlayerData(Master->Connection); - }else{ - SendPlayerSkills(Master->Connection); - } - - if(Range > 0){ - switch(this->SkNr){ - case SKILL_MAGIC_LEVEL:{ - SendMessage(Master->Connection, TALK_EVENT_MESSAGE, - "You advanced to magic level %d.", this->Get()); - break; - } - case SKILL_SHIELDING:{ - SendMessage(Master->Connection, TALK_EVENT_MESSAGE, "You advanced in shielding."); - break; - } - case SKILL_DISTANCE:{ - SendMessage(Master->Connection, TALK_EVENT_MESSAGE, "You advanced in distance fighting."); - break; - } - case SKILL_SWORD:{ - SendMessage(Master->Connection, TALK_EVENT_MESSAGE, "You advanced in sword fighting."); - break; - } - case SKILL_CLUB:{ - SendMessage(Master->Connection, TALK_EVENT_MESSAGE, "You advanced in club fighting."); - break; - } - case SKILL_AXE:{ - SendMessage(Master->Connection, TALK_EVENT_MESSAGE, "You advanced in axe fighting."); - break; - } - case SKILL_FIST:{ - SendMessage(Master->Connection, TALK_EVENT_MESSAGE, "You advanced in fist fighting."); - break; - } - case SKILL_FISHING:{ - SendMessage(Master->Connection, TALK_EVENT_MESSAGE, "You advanced in fishing."); - break; - } - } - } - } - - return true; -} - -void TSkillProbe::Event(int Range){ - if(this->Cycle == 0){ - TCreature *Master = this->Master; - if(Master == NULL){ - error("TSkillProbe::Event: GetMaster liefert NULL zurueck!\n"); - return; - } - - this->MDAct = 0; - if(Master->Type == PLAYER){ - // TODO(fusion): This is weird because `SKILL_GO_STRENGTH` should - // be TSkillGoStrength, instead of TSkillProbe. - if(this->SkNr == SKILL_GO_STRENGTH){ - ((TPlayer*)Master)->CheckState(); - } - - SendPlayerSkills(Master->Connection); - } - } -} - -// TSkillAdd -//============================================================================== -void TSkillAdd::Advance(int Range){ - int Increment = Range * this->AddLevel; - int Max = this->Max + Increment; - int Act = this->Act + Increment; - - // TODO(fusion): I'm not sure this is right. Do we fill health and mana when - // the player levels up? - if(Act < Max){ - Act = Max; - } - - this->Act = Act; - this->Max = Max; -} - -// TSkillHitpoints -//============================================================================== -void TSkillHitpoints::Set(int Value){ - TCreature *Master = this->Master; - if(Master != NULL && Master->IsDead && Value > 0){ - error("TSkillHitpoints::Set: HP von toter Kreatur sollen erhöht werden.\n"); - return; - } - - if(Value > this->Max){ - Value = this->Max; - } - this->Act = Value; - - if(Master != NULL){ - if(Master->Type == PLAYER){ - SendPlayerData(Master->Connection); - } - AnnounceChangedCreature(Master->ID, 1); - } -} - -// TSkillMana -//============================================================================== -void TSkillMana::Set(int Value){ - if(Value > this->Max){ - Value = this->Max; - } - this->Act = Value; - - TCreature *Master = this->Master; - if(Master != NULL && Master->Type == PLAYER){ - SendPlayerData(Master->Connection); - } -} - -// TSkillGoStrength -//============================================================================== -bool TSkillGoStrength::SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue){ - // TODO(fusion): The decompiled version of this function was kind of confusing - // with `Master` being checked multiple times and `CheckState` being called up - // to two times. I've done some cleanup that should keep the same behavior but - // we should always keep an eye out for bugs. - - this->Cycle = Cycle; - this->Count = Count; - this->MaxCount = MaxCount; - - if(Cycle == 0){ - this->MDAct = 0; - } - - TCreature *Master = this->Master; - if(Master == NULL){ - error("TSkillGoStrength::SetTimer: GetMaster liefert NULL zurueck!\n"); - return false; - } - - if(Master->Type == PLAYER){ - ((TPlayer*)Master)->CheckState(); - } - AnnounceChangedCreature(Master->ID, 4); - return true; -} - -void TSkillGoStrength::Event(int Range){ - if(this->Cycle != 0){ - return; - } - - TCreature *Master = this->Master; - if(Master == NULL){ - error("TSkillGoStrength::Event: GetMaster liefert NULL zurueck!\n"); - return; - } - - // TODO(fusion): Again, shouldn't `SkNr` always refer to `GO_STRENGTH` here? - this->MDAct = 0; - if(this->SkNr == SKILL_GO_STRENGTH && Master->Type == PLAYER){ - ((TPlayer*)Master)->CheckState(); - } - AnnounceChangedCreature(Master->ID, 4); -} - -// TSkillCarryStrength -//============================================================================== -void TSkillCarryStrength::Set(int Value){ - if(Value > this->Max){ - Value = this->Max; - } - this->Act = Value; - - TCreature *Master = this->Master; - if(Master != NULL && Master->Type == PLAYER){ - SendPlayerData(Master->Connection); - } -} - -// TSkillSoulpoints -//============================================================================== -void TSkillSoulpoints::Set(int Value){ - if(Value > this->Max){ - Value = this->Max; - } - this->Act = Value; - - TCreature *Master = this->Master; - if(Master != NULL && Master->Type == PLAYER){ - SendPlayerData(Master->Connection); - } -} - -int TSkillSoulpoints::TimerValue(void){ - return (this->Cycle - 1) * this->MaxCount + this->Count; -} - -void TSkillSoulpoints::Event(int Range){ - TCreature *Master = this->Master; - if(Master == NULL){ - error("TSkillSoulpoints::Event: GetMaster liefert NULL zurueck!\n"); - return; - } - - // TODO(fusion): Shouldn't this be the same as `Master->Skills[SKILL_SOUL]`? - // Not sure what's going on here. - if(!Master->IsDead){ - Master->Skills[SKILL_SOUL]->Set(Other->Act + 1); - } -} - -// TSkillFed -//============================================================================== -void TSkillFed::Event(int Range){ - TCreature *Master = this->Master; - if(Master == NULL){ - error("TSkillFed::Event: GetMaster liefert NULL zurueck!\n"); - return; - } - - if(Master->IsDead || IsProtectionZone(Master->posx, Master->posy, Master->posz)){ - return; - } - - uint8 Profession = 0; - if(Master->Type == PLAYER){ - Profession = ((TPlayer*)Master)->GetActiveProfession(); - } - - int SecsPerHP = 12; - int SecsPerMana = 6; - switch(Profession){ - case PROFESSION_NONE: - case PROFESSION_KNIGHT:{ - SecsPerHP = 6; - SecsPerMana = 6; - break; - } - - case PROFESSION_PALADIN:{ - SecsPerHP = 8; - SecsPerMana = 4; - break; - } - - case PROFESSION_SORCERER: - case PROFESSION_DRUID:{ - SecsPerHP = 12; - SecsPerMana = 3; - break; - } - - case PROFESSION_ELITE_KNIGHT:{ - SecsPerHP = 4; - SecsPerMana = 6; - break; - } - - case PROFESSION_ROYAL_PALADIN:{ - SecsPerHP = 6; - SecsPerMana = 3; - break; - } - - case PROFESSION_MASTER_SORCERER: - case PROFESSION_ELDER_DRUID:{ - SecsPerHP = 12; - SecsPerMana = 2; - break; - } - - default:{ - error("TSkillFed::Event: Unbekannter Beruf %d.\n", Profession); - break; - } - } - - // TODO(fusion): Not sure about this. - int Timer = this->TimerValue(); - - if(Timer % SecsPerHP == 0){ - Master->Skills[SKILL_HITPOINTS]->Change(1); - } - - if(Timer % SecsPerMana == 0){ - Master->Skills[SKILL_MANA]->Change(2); - } -} - -// TSkillLight -//============================================================================== -bool TSkillLight::SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue){ - // TODO(fusion): I think the decompiled version of most `SetTimer` functions - // look weird because they're calling the base class method `TSkill::SetTimer()` - // which is getting inlined. - this->Cycle = Cycle; - this->Count = Count; - this->MaxCount = MaxCount; - - TCreature *Master = this->Master; - if(Master == NULL){ - error("TSkillLight::SetTimer: GetMaster liefert NULL zurueck!\n"); - return false; - } - - if(Master->Type == PLAYER){ - ((TPlayer*)Master)->CheckState(); - } - - // TODO(fusion): The decompiled function had this logic, but WTF. - // if(GetCreature(Master->ID) != NULL){ - // AnnounceChangedCreature(Master->ID, 2); - // } - AnnounceChangedCreature(Master->ID, 2); - return true; -} - -void TSkillLight::Event(int Range){ - TCreature *Master = this->Master; - if(Master == NULL){ - error("TSkillLight::Event: GetMaster liefert NULL zurueck!\n"); - return; - } - - AnnounceChangedCreature(Master->ID, 2); -} - -// TSkillIllusion -//============================================================================== -bool TSkillIllusion::SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue){ - this->Cycle = Cycle; - this->Count = Count; - this->MaxCount = MaxCount; - - TCreature *Master = this->Master; - if(Master == NULL){ - error("TSkillIllusion::SetTimer: GetMaster liefert NULL zurueck!\n"); - return false; - } - - if(Master->Type == PLAYER){ - ((TPlayer*)Master)->CheckState(); - } - - // TODO(fusion): The decompiled function had this logic, but WTF. - // if(GetCreature(Master->ID) != NULL){ - // AnnounceChangedCreature(Master->ID, 3); - // } - AnnounceChangedCreature(Master->ID, 3); - return true; -} - -void TSkillIllusion::Event(int Range){ - if(this->Cycle == 0){ - TCreature *Master = this->Master; - if(Master == NULL){ - error("TSkillIllusion::Event: GetMaster liefert NULL zurueck!\n"); - return; - } - - if(this->Get() <= 0){ - Master->Outfit = Master->OrgOutfit; - AnnounceChangedCreature(Master->ID, 3); - // TODO(fusion): I'm not sure this is correct. - NotifyAllCreatures(&Master->CrObject, 2, &NONE); - } - } -} - -// TSkillPoison -//============================================================================== -bool TSkillPoison::Process(void){ - bool Result = this->Cycle == 0; - if(Result){ - TCreature *Master = this->Master; - if(Master != NULL && Master->Type == PLAYER){ - ((TPlayer*)Master)->CheckState(); - } - }else if(this->Count <= 0){ - this->Count = this->MaxCount; - - // NOTE(fusion): Not sure what's going on here. - int Range = (this->Cycle * this->FactorPercent) / 1000; - if(Range == 0){ - // NOTE(fusion): This seems the opposite from `TSkill::Process`. - Range = (this->Cycle > 0) ? + 1 : -1; - } - - this->Cycle -= Range; - this->Event(Range); - }else{ - this->Count -= 1; - } - return Result; -} - -bool TSkillPoison::SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue){ - this->Cycle = Cycle; - this->Count = Count; - this->MaxCount = MaxCount; - - TCreature *Master = this->Master; - if(Master != NULL && Master->Type == PLAYER){ - ((TPlayer*)Master)->CheckState(); - } - - if(AdditionalValue == -1){ - AdditionalValue = 50; - } - - if(AdditionalValue < 10){ - AdditionalValue = 10; - } - - if(AdditionalValue > 1000){ - AdditionalValue = 1000; - } - - this->FactorPercent = AdditionalValue; - return true; -} - -void TSkillPoison::Event(int Range){ - TCreature *Master = this->Master; - if(Master == NULL){ - error("TSkillPoison::Event: GetMaster liefert NULL zurueck!\n"); - return; - } - - if(Range < 0){ - Range = -Range; - } - - Master->Damage(GetCreature(Master->PoisonDamageOrigin), Range, 2); - - // NOTE(fusion): I think this is checking whether `Master` is still upon some - // poison field to determine whether we should extend the poison effect? - Object Obj = GetFirstObject(Master->posx, Master->posy, Master->posz); - while(Obj != NONE){ - // NOTE(fusion): I think `ObjectType` is analogous to `ItemType` in - // OpenTibia terms. - ObjectType ObjType = Obj.getObjectType(); - if(ObjType.getFlag(AVOID)){ - if(ObjType.getAttribute(AVOIDDAMAGETYPES) == 2){ - this->Cycle += 1; - } - } - - Obj = Obj.getNextObject(); - } -} - -void TSkillPoison::Reset(void){ - TSkill::Reset(); - this->FactorPercent = 0x32; -} - -// TSkillBurning -//============================================================================== -void TSkillBurning::Event(int Range){ - TCreature *Master = this->Master; - if(Master == NULL){ - error("TSkillBurning::Event: GetMaster liefert NULL zurueck!\n"); - return; - } - - Master->Damage(GetCreature(Master->FireDamageOrigin), 10, 4); - - // NOTE(Fusion): Something similar to `TSkillPoison::Event` except we're - // looking for a different field type. - Object Obj = GetFirstObject(Master->posx, Master->posy, Master->posz); - while(Obj != NONE){ - ObjectType ObjType = Obj.getObjectType(); - if(ObjType.getFlag(AVOID)){ - if(ObjType.getAttribute(AVOIDDAMAGETYPES) == 4){ - this->Cycle += 1; - } - } - - Obj = Obj.getNextObject(); - } -} - -// TSkillEnergy -//============================================================================== -void TSkillEnergy::Event(int Range){ - TCreature *Master = this->Master; - if(Master == NULL){ - error("TSkillEnergy::Event: GetMaster liefert NULL zurueck!\n"); - return; - } - - Master->Damage(GetCreature(Master->EnergyDamageOrigin), 25, 8); - - // NOTE(Fusion): Something similar to `TSkillPoison::Event` except we're - // looking for a different field type. - Object Obj = GetFirstObject(Master->posx, Master->posy, Master->posz); - while(Obj != NONE){ - ObjectType ObjType = Obj.getObjectType(); - if(ObjType.getFlag(AVOID)){ - if(ObjType.getAttribute(AVOIDDAMAGETYPES) == 8){ - this->Cycle += 1; - } - } - - Obj = Obj.getNextObject(); - } -} - -// TSkillBase -//============================================================================== -TSkillBase::TSkillBase(void){ - STATIC_ASSERT(NARRAY(this>Skills) == NARRAY(this->TimerList)); - for(int i = 0; i < NARRAY(this->Skills); i += 1){ - this->Skills[i] = NULL; - this->TimerList[i] = NULL; - } - this->FirstFreeTimer = 0; -} - -TSkillBase::~TSkillBase(void){ - for(int i = 0; i < NARRAY(this->Skills); i += 1){ - delete this->Skills[i]; - } -} - -bool TSkillBase::NewSkill(uint16 SkillNo, TCreature *Creature){ - if(SkillNo >= NARRAY(this->Skills)){ - error("TSkillBase::NewSkill: unbekannte SkillNummer %d\n", SkillNo); - return false; - } - - if(this->Skills[SkillNo] != NULL){ - delete this->Skills[SkillNo]; - this->Skills[SkillNo] = NULL; - } - - TSkill *Skill = NULL; - switch(SkillNo){ - case SKILL_LEVEL: Skill = new TSkillLevel(SkillNo, Creature); break; - case SKILL_MAGIC_LEVEL: ATTR_FALLTHROUGH; - case SKILL_SHIELDING: ATTR_FALLTHROUGH; - case SKILL_DISTANCE: ATTR_FALLTHROUGH; - case SKILL_SWORD: ATTR_FALLTHROUGH; - case SKILL_CLUB: ATTR_FALLTHROUGH; - case SKILL_AXE: ATTR_FALLTHROUGH; - case SKILL_FIST: ATTR_FALLTHROUGH; - case SKILL_FISHING: Skill = new TSkillProbe(SkillNo, Creature); break; - case SKILL_HITPOINTS: Skill = new TSkillHitpoints(SkillNo, Creature); break; - case SKILL_MANA: Skill = new TSkillMana(SkillNo, Creature); break; - case SKILL_GO_STRENGTH: Skill = new TSkillGoStrength(SkillNo, Creature); break; - case SKILL_CARRY_WEIGHT: Skill = new TSkillCarryStrength(SkillNo, Creature); break; - case SKILL_FED: Skill = new TSkillFed(SkillNo, Creature); break; - case SKILL_LIGHT: Skill = new TSkillLight(SkillNo, Creature); break; - case SKILL_ILLUSION: Skill = new TSkillIllusion(SkillNo, Creature); break; - case SKILL_POISON: Skill = new TSkillPoison(SkillNo, Creature); break; - case SKILL_BURNING: Skill = new TSkillBurning(SkillNo, Creature); break; - case SKILL_ENERGY: Skill = new TSkillEnergy(SkillNo, Creature); break; - case SKILL_SOUL: Skill = new TSkillSoulpoints(SkillNo, Creature); break; - default: Skill = new TSkill(SkillNo, Creature); break; - } - - ASSERT(Skill != NULL); - this->Skills[SkillNo] = Skill; - return true; -} - -bool TSkillBase::SetSkills(int Race){ - if(Race < 0 || Race >= 512){ - error("TSkillBase::SetSkills: Ungültige Rassennummer %d.\n", Race); - return false; - } - - // TODO(fusion): This is referencing some global table with probably all - // types of creatures. - int NumSkills = RaceData[Race].Skills; - for(int i = 0; i < NumSkills; i += 1){ - // TODO(fusion): We'd need to implement the `vector` container being used - // across the codebase. - //TSkillData *SkillData = RaceData[Race].Skill(i); - - // BUG(fusion): We don't check if `Skill` is valid? Could be NULL. We - // don't seem to set all `Skill` fields either so there is something - // else going on, probably. - TSkill *Skill = this->Skills[SkillData->Nr]; - Skill->Act = SkillData->Actual; - Skill->Min = SkillData->Minimum; - Skill->Max = SkillData->Maximum; - Skill->LastLevel = 0; - Skill->NextLevel = SkillData->NextLevel; - Skill->Delta = SkillData->NextLevel; - Skill->FactorPercent = SkillData->FactorPercent; - Skill->MaxCount = 0; - Skill->AddLevel = SkillData->AddLevel; - } - - return true; -} - -void TSkillBase::ProcessSkills(void){ - int Index = 0; - int End = this->FirstFreeTimer; - while(Index < End){ - // TODO(fusion): Probably remove if `Skill == NULL`? - TSkill *Skill = this->TimerList[Index]; - if(Skill && !Skill->Process()){ - // NOTE(fusion): A little swap and pop action. - End -= 1; - this->TimerList[Index] = this->TimerList[End]; - this->TimerList[End] = NULL; - }else{ - Index += 1; - } - } - this->FirstFreeTimer = (uint16)End; -} - -bool TSkillBase::SetTimer(uint16 SkNr, int Cycle, int Count, int MaxCount, int AdditionalValue){ - if(SkNr >= NARRAY(this->Skills)){ - error("TSkillBase::SetTimer: Ungueltige SkNr: %d\n", SkNr); - return false; - } - - this->DelTimer(SkNr); - - // BUG(fusion): We don't check if `Skill` is valid here. - TSkill *Skill = this->Skills[SkNr]; - bool Result = Skill->SetTimer(Cycle, Count, MaxCount, AdditionalValue); - if(Result){ - // NOTE(fusion): A little push back action. - // BUG(fusion): We don't check if there is room in `TimerList`. I'd assume - // it is naturally bound by the maximum number of skills? - this->TimerList[this->FirstFreeTimer] = Skill; - this->FirstFreeTimer += 1; - } - - return Result; -} - -void TSkillBase::DelTimer(uint16 SkNr){ - if(SkNr >= NARRAY(this->Skills)){ - error("TSkillBase::DelTimer: Ungueltige SkNr: %d\n", SkNr); - return; - } - - TSkill *Skill = this->Skills[SkNr]; - if(Skill != NULL){ - Skill->DelTimer(); - - int End = this->FirstFreeTimer; - for(int Index = 0; Index < End; i += 1){ - if(Skill == this->TimerList[Index]){ - // NOTE(fusion): A little swap and pop action. - End -= 1; - this->TimerList[Index] = this->TimerList[End]; - this->TimerList[End] = NULL; - break; - } - } - } -} // Creature Functions //============================================================================== diff --git a/src/creature.hh b/src/creature.hh index d6434d7..8f0917a 100644 --- a/src/creature.hh +++ b/src/creature.hh @@ -2,162 +2,10 @@ #define TIBIA_CREATURE_HH_ 1 #include "main.hh" +#include "skill.hh" struct TCreature; -struct TSkill{ - // REGULAR FUNCTIONS - // ========================================================================= - TSkill(int SkNr, TCreature *Master); - int Get(void); - int GetProgress(void); - void Check(void); - void Change(int Amount); - void SetMDAct(int MDAct); - void Load(int Act, int Max, int Min, int DAct, int MDAct, - int Cycle, int MaxCycle, int Count, int MaxCount, int AddLevel, - int Exp, int FactorPercent, int NextLevel, int Delta); - void Save(int *Act, int *Max, int *Min, int *DAct, int *MDAct, - int *Cycle, int *MaxCycle, int *Count, int *MaxCount, int *AddLevel, - int *Exp, int *FactorPercent, int *NextLevel, int *Delta); - - // VIRTUAL FUNCTIONS - // ========================================================================= - virtual ~TSkill(void); // VTABLE[ 0] - // Duplicate destructor that also calls operator delete. // VTABLE[ 1] - virtual void Set(int Value); // VTABLE[ 2] - virtual void Increase(int Amount); // VTABLE[ 3] - virtual void Decrease(int Amount); // VTABLE[ 4] - virtual int GetExpForLevel(int Level); // VTABLE[ 5] - virtual void Advance(int Range); // VTABLE[ 6] - virtual void ChangeSkill(int FactorPercent, int Delta); // VTABLE[ 7] - virtual int ProbeValue(int Max, bool Increase); // VTABLE[ 8] - virtual bool Probe(int Diff, int Prob, bool Increase); // VTABLE[ 9] - virtual bool Process(void); // VTABLE[10] - virtual bool SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue); // VTABLE[11] - virtual bool DelTimer(void); // VTABLE[12] - virtual int TimerValue(void); // VTABLE[13] - virtual bool Jump(int Range); // VTABLE[14] - virtual void Event(int Range); // VTABLE[15] - virtual void Reset(void); // VTABLE[16] - - // DATA - // ========================================================================= - //void *VTABLE; // IMPLICIT - int DAct; // Delta Value - Probably from equipment. - int MDAct; // Delta Magic Value - Probably from spells. - uint16 SkNr; - TCreature *Master; - int Act; // Actual Value (?) - int Max; - int Min; - int FactorPercent; - int LastLevel; - int NextLevel; - int Delta; - int Exp; - int Cycle; - int MaxCycle; - int Count; - int MaxCount; - int AddLevel; -}; - -struct TSkillLevel: TSkill { - void Increase(int Amount) override; - void Decrease(int Amount) override; - int GetExpForLevel(int Level) override; - bool Jump(int Range) override; -}; - -struct TSkillProbe: TSkill { - void Increase(int Amount) override; - void Decrease(int Amount) override; - int GetExpForLevel(int Level) override; - void ChangeSkill(int FactorPercent, int Delta) override; - int ProbeValue(int Max, bool Increase) override; - bool Probe(int Diff, int Prob, bool Increase) override; - bool SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue) override; - bool Jump(int Range) override; - void Event(int Range) override; -}; - -struct TSkillAdd: TSkill { - void Advance(int Range) override; -}; - -struct TSkillHitpoints: TSkillAdd { - void Set(int Value) override; -}; - -struct TSkillMana: TSkillAdd { - void Set(int Value) override; -}; - -struct TSkillGoStrength: TSkillAdd { - bool SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue) override; - void Event(int Range) override; -}; - -struct TSkillCarryStrength: TSkillAdd { - void Set(int Value) override; -}; - -struct TSkillSoulpoints: TSkillAdd { - void Set(int Value) override; - int TimerValue(void) override; - void Event(int Range) override; -}; - -struct TSkillFed: TSkill { - void Event(int Range) override; -}; - -struct TSkillLight: TSkill { - bool SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue) override; - void Event(int Range) override; -}; - -struct TSkillIllusion: TSkill { - bool SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue) override; - void Event(int Range) override; -}; - -struct TSkillPoison: TSkill { - bool Process(void) override; - bool SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue) override; - void Event(int Range) override; - void Reset(void) override; -}; - -struct TSkillBurning: TSkill { - void Event(int Range) override; -}; - -struct TSkillEnergy: TSkill { - void Event(int Range) override; -}; - -struct TSkillBase{ - // REGULAR FUNCTIONS - // ========================================================================= - TSkillBase(void); - ~TSkillBase(void); - bool NewSkill(uint16 SkillNo, TCreature *Creature); - bool SetSkills(int Race); - void ProcessSkills(void); - bool SetTimer(uint16 SkNr, int Cycle, int Count, int MaxCount, int AdditionalValue); - void DelTimer(uint16 SkNr); - - // DATA - // ========================================================================= - TSkill *Skills[25]; - TSkill *TimerList[25]; - uint16 FirstFreeTimer; -}; - -// TODO(fusion): Maybe split this file? -//============================================================================== struct TCombatEntry{ uint32 ID; uint32 Damage; diff --git a/src/skill.cc b/src/skill.cc new file mode 100644 index 0000000..ba060a3 --- /dev/null +++ b/src/skill.cc @@ -0,0 +1,1252 @@ +#include "skill.hh" + +#include "enums.hh" +#include "creature.hh" +#include "player.hh" +#include <math.h> + +// TSkill REGULAR FUNCTIONS +//============================================================================== +TSkill::TSkill(int SkNr, TCreature *Master){ + // TODO(fusion): I'm not sure we're calling `Reset` here but the decompiled + // function sets the same values as `TSkill::Reset` which makes me wonder + // whether it is inlined. It's probably using `TSkill::Reset()` directly + // instead of virtual dispatch, if that is the case. + this->Reset(); + + this->SkNr = (uint16)SkNr; + this->Master = Master; +} + +int TSkill::Get(void){ + int Value = this->Act; + if(Value < this->Min) + Value = this->Min; + Value += this->MDAct + this->DAct; + return Value; +} + +int TSkill::GetProgress(void){ + int Result = 0; + if(this->NextLevel > this->LastLevel){ + Result = (this->Exp - this->LastLevel) * 100 / (this->NextLevel - this->LastLevel); + + // TODO(fusion): This feels too much for reporting a mostly *impossible* error. + if(Result < 0 || Result > 100){ + error("TSkill::GetProgress: Berechnungsfehler Exp %d, Last %d, Next %d, Prozent %d.\n", + this->Exp, this->LastLevel, this->NextLevel, Result); + + const char *MasterName = "(Unknown"; + if(this->Master != NULL){ + MasterName = this->Master->Name; + } + error("# Spieler %s - Skill %d\n", MasterName, this->SkNr); + Result = 0; + } + } + return Result; +} + +void TSkill::Check(void){ + if(this->Act > this->Max){ + this->Act = this->Max; + } +} + +void TSkill::Change(int Amount){ + this->Set(this->Act + Amount); + + // TODO(fusion): Probably `TSkill::Check` inlined? It doesn't make a whole + // lot of sense because `TSkill::Set` also checks the value. Maybe a custom + // version of it does something differently? + if(this->Act > this->Max){ + this->Act = this->Max; + } +} + +void TSkill::SetMDAct(int MDAct){ + this->MDAct = MDAct; + if(this->SkNr == 4 && this->Master && this->Master->Type == PLAYER){ + // TODO(fusion): Same as `TSkill::Process`. + ((TPlayer*)this->Master)->CheckState(); + } +} + +void TSkill::Load(int Act, int Max, int Min, int DAct, int MDAct, + int Cycle, int MaxCycle, int Count, int MaxCount, int AddLevel, + int Exp, int FactorPercent, int NextLevel, int Delta){ + this->Act = Act; + this->Max = Max; + this->Min = Min; + this->DAct = DAct; + this->MDAct = MDAct; + this->Cycle = Cycle; + this->MaxCycle = MaxCycle; + this->Count = Count; + this->MaxCount = MaxCount; + this->AddLevel = AddLevel; + this->Exp = Exp; + this->FactorPercent = FactorPercent; + this->NextLevel = NextLevel; + this->Delta = Delta; + + // TODO(fusion): Shouldn't we also do the same for `NextLevel`? + this->LastLevel = this->GetExpForLevel(Act); + + TCreature *Master = this->Master; + if(Master && Cycle != 0){ + // NOTE(fusion): It seems we had `TSkillBase::SetTimer` inlined here. + // For whatever reason I hadn't noticed the error message referencing + // it, LOL. + Master->SetTimer(this->SkNr, Cycle, Count, MaxCount, FactorPercent); + } +} + + +void TSkill::Save(int *Act, int *Max, int *Min, int *DAct, int *MDAct, + int *Cycle, int *MaxCycle, int *Count, int *MaxCount, int *AddLevel, + int *Exp, int *FactorPercent, int *NextLevel, int *Delta){ + *Act = this->Act; + *Max = this->Max; + *Min = this->Min; + *DAct = this->DAct; + *MDAct = this->MDAct; + *Cycle = this->Cycle; + *MaxCycle = this->MaxCycle; + *Count = this->Count; + *MaxCount = this->MaxCount; + *AddLevel = this->AddLevel; + *Exp = this->Exp; + *FactorPercent = this->FactorPercent; + *NextLevel = this->NextLevel; + *Delta = this->Delta; +} + +// TSkill VIRTUAL FUNCTIONS +//============================================================================== +TSkill::~TSkill(void){ + if(this->Master != NULL){ + this->Master->DelTimer(this->SkNr); + } +} + +void TSkill::Set(int Value){ + if(Value > this->Max) + Value = this->Max; + this->Act = Value; +} + +void TSkill::Increase(int Amount){ + // no-op +} + +void TSkill::Decrease(int Amount){ + // no-op +} + +int TSkill::GetExpForLevel(int Level){ + return 0; +} + +void TSkill::Advance(int Range){ + // no-op +} + +void TSkill::ChangeSkill(int FactorPercent, int Delta){ + // no-op +} + +int TSkill::ProbeValue(int Max, bool Increase){ + return 0; +} + +bool TSkill::Probe(int Diff, int Prob, bool Increase){ + return false; +} + +bool TSkill::Process(void){ + bool Result = this->Cycle == 0; + if(Result){ + if(this->Master && this->Master->Type == PLAYER){ + // TODO(fusion): Verify if a simple pointer cast works. Maybe `dynamic_cast`? + ((TPlayer*)this->Master)->CheckState(); + } + }else if(this->Count <= 0){ + this->Count = this->MaxCount; + // NOTE(fusion): `Range` should move `Cycle` towards ZERO. + int Range = (this->Cycle < 0) ? +1 : -1; + this->Cycle += Range; + this->Event(Range); + }else{ + this->Count -= 1; + } + return Result; +} + +bool TSkill::SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue){ + this->Cycle = Cycle; + this->Count = Count; + this->MaxCount = MaxCount; + if(this->Master && this->Master->Type == PLAYER){ + // TODO(fusion): Same as `TSkill::Process`. + ((TPlayer*)this->Master)->CheckState(); + } + return true; +} + +bool TSkill::DelTimer(void){ + this->Cycle = 0; + this->Count = 0; + this->MaxCount = 0; + if(this->Master && this->Master->Type == PLAYER){ + // TODO(fusion): Same as `TSkill::Process`. + ((TPlayer*)this->Master)->CheckState(); + } + return true; +} + +int TSkill::TimerValue(void){ + return this->Cycle; +} + +bool TSkill::Jump(int Range){ + return true; +} + +void TSkill::Event(int Range){ + // no-op +} + +void TSkill::Reset(void){ + this->Act = 0; + this->Max = INT_MAX; + this->Min = 0; + this->DAct = 0; + this->MDAct = 0; + this->FactorPercent = 1000; + this->LastLevel = 0; + this->NextLevel = INT_MAX; + this->Exp = 0; + this->Delta = INT_MAX; + this->Cycle = 0; + this->MaxCycle = 0; + this->Count = 0; + this->MaxCount = 0; + this->AddLevel = 0; +} + +// TSkillLevel +//============================================================================== +void TSkillLevel::Increase(int Amount){ + if(Amount < 0){ + error("TSkillLevel::Increase: Amount negativ (%d).\n", Amount); + return; + } + + // BUG(fusion): No bounds check as in `TSkillLevel::Decrease`? + + // TODO(fusion): This could probably be an oversight but the decompiled + // function was calling `GetExpForLevel` twice instead of using `NextLevel` + // which makes me wonder whether `NextLevel` is properly initialized. + int Range = 0; + this->Exp += Amount; + while(this->Exp >= this->NextLevel){ + this->Act += 1; + this->LastLevel = this->NextLevel; + this->NextLevel = this->GetExpForLevel(this->Act + 1); + if(this->NextLevel < 0){ + // BUG(fusion): We don't check if `Master` is valid here? + error("TSkillLevel::Increase: Skill vor Überlauf (%s, Skill %d).\n", this->Master->Name, this->SkNr); + this->NextLevel = this->Exp; + this->Exp -= 1000000; + break; + } + Range += 1; + } + + if(Range != 0){ + this->Jump(Range); + } + + if(this->Master == NULL){ + error("TSkillLevel::Increase: GetMaster liefert NULL zurueck.\n"); + return; + } + + if(this->Master->Type == PLAYER){ + SendPlayerData(this->Master->Connection); + } +} + +void TSkillLevel::Decrease(int Amount){ + if(Amount < 0){ + error("TSkillLevel::Decrease: Amount negativ (%d).\n", Amount); + return; + } + + // TODO(fusion): This is some weird ass comparison. + if(Amount > this->Exp && this->Exp > 100000){ + error("TSkillLevel::Decrease: Amount zu gross(%d).\n", Amount); + return; + } + + if(Amount < this->Exp){ + this->Exp -= Amount; + }else{ + this->Exp = 0; + } + + // TODO(fusion): This could probably be an oversight but the decompiled + // function was calling `GetExpForLevel` twice instead of using `LastLevel` + // which makes me wonder whether `LastLevel` is properly initialized. + int Range = 0; + while(this->Exp < this->LastLevel){ + this->Act -= 1; + this->NextLevel = this->LastLevel; + this->LastLevel = this->GetExpForLevel(this->Act); + Range -= 1; + } + + if(Range != 0){ + this->Jump(Range); + } + + if(this->Master == NULL){ + error("TSkillLevel::Decrease: GetMaster liefert NULL zurueck.\n"); + return; + } + + if(this->Master->Type == PLAYER){ + SendPlayerData(this->Master->Connection); + } +} + +int TSkillLevel::GetExpForLevel(int Level){ + if(Level < 1){ + error("TSkillLevel::GetExpForLevel: Ungültiger Level %d.\n", Level); + return 0; + } + + if(this->Delta <= 0){ + error("TSkillLevel::GetExpForLevel: Ungültiger Delta-Wert %d.\n", this->Delta); + return 0; + } + + if(Level > 500){ + error("TSkillLevel::GetExpForLevel: Level=%d; Formel gegen Überlauf sichern.\n", Level); + return -1; // TODO(fusion): Shouldn't this be 0? + } + + return ((((Level - 6) * Level + 17) * Level - 12) / 6) * this->Delta; +} + +bool TSkillLevel::Jump(int Range){ + if(this->Master == NULL){ + error("TSkillLevel::Jump: GetMaster liefert NULL zurueck!\n"); + return false; + } + + this->Master->Skills[SKILL_HITPOINTS ]->Advance(Range); + this->Master->Skills[SKILL_MANA ]->Advance(Range); + this->Master->Skills[SKILL_GO_STRENGTH ]->Advance(Range); + this->Master->Skills[SKILL_CARRY_WEIGHT]->Advance(Range); + + AnnounceChangedCreature(this->Master->ID, 4); + this->Master->Combat.CheckCombatValues(); + + if(this->Master->Type == PLAYER){ + int ToLevel = this->Get(); + int FromLevel = ToLevel - Range; + if(Range > 0){ + SendMessage(this->Master->Connection, TALK_EVENT_MESSAGE, + "You advanced from Level %d to Level %d.", FromLevel, ToLevel); + }else if(Range < 0){ + SendMessage(this->Master->Connection, TALK_EVENT_MESSAGE, + "You were downgraded from Level %d to Level %d.", FromLevel, ToLevel); + } + } + + return true; +} + +// TSkillProbe +//============================================================================== +void TSkillProbe::Increase(int Amount){ + if(Amount < 0){ + error("TSkillProbe::Increase: Amount negativ (%d).\n", Amount); + return; + } + + int OldProgress = this->GetProgress(); + + // TODO(fusion): This could probably be an oversight but the decompiled + // function was calling `GetExpForLevel` twice instead of using `NextLevel` + // which makes me wonder whether `NextLevel` is properly initialized. + int Range = 0; + this->Exp += Amount; + while(this->Exp >= this->NextLevel){ + this->Act += 1; + this->LastLevel = this->NextLevel; + this->NextLevel = this->GetExpForLevel(this->Act + 1); + if(this->NextLevel < 0){ + // BUG(fusion): We don't check if `Master` is valid here? + error("TSkillProbe::Increase: Skill vor Überlauf (%s, Skill %d).\n", this->Master->Name, this->SkNr); + this->NextLevel = this->Exp; + this->Exp -= 1000; + break; + } + Range += 1; + } + + if(Range != 0){ + this->Jump(Range); + }else{ + TCreature *Master = this->Master; + if(Master && Master->Type == PLAYER){ + int NewProgress = this->GetProgress(); + if(NewProgress != OldProgress){ + if(this->SkNr == SKILL_MAGIC_LEVEL){ + SendPlayerData(Master->Connection); + }else{ + SendPlayerSkills(Master->Connection); + } + } + } + } +} + +void TSkillProbe::Decrease(int Amount){ + if(Amount < 0){ + error("TSkillProbe::Decrease: Amount negativ (%d).\n", Amount); + return; + } + + int OldProgress = this->GetProgress(); + + if(Amount < this->Exp){ + this->Exp -= Amount; + }else{ + this->Exp = 0; + } + + // TODO(fusion): This could probably be an oversight but the decompiled + // function was calling `GetExpForLevel` twice instead of using `LastLevel` + // which makes me wonder whether `LastLevel` is properly initialized. + int Range = 0; + while(this->Exp < this->LastLevel && this->Act > this->Min){ + this->Act -= 1; + this->NextLevel = this->LastLevel; + this->LastLevel = this->GetExpForLevel(this->Act); + Range -= 1; + } + + if(Range != 0){ + this->Jump(Range); + }else{ + TCreature *Master = this->Master; + if(Master && Master->Type == PLAYER){ + int NewProgress = this->GetProgress(); + if(NewProgress != OldProgress){ + if(this->SkNr == SKILL_MAGIC_LEVEL){ + SendPlayerData(Master->Connection); + }else{ + SendPlayerSkills(Master->Connection); + } + } + } + } +} + +int TSkillProbe::GetExpForLevel(int Level){ + if(Level < 0 || Level < this->Min){ + error("TSkillProbe::GetExpForLevel: Ungültiger Level %d.\n", Level); + return 0; + } + + if(this->Delta <= 0){ + error("TSkillProbe::GetExpForLevel: Ungültiger Delta-Wert %d.\n", this->Delta); + return 0; + } + + // TODO(fusion): This part of the decompiled code was a bit weird. I had to + // look into the disassembly to figure the string parameter of `error` below + // and it is weird that we keep going even with invalid values of `FactorPercent`. + int FactorPercent = this->FactorPercent; + if(FactorPercent < 1000){ + const char *MasterName = "---"; + if(this->Master != NULL){ + MasterName = this->Master->Name; + } + error("TSkillProbe::GetExpForLevel: Ungültiger FactorPercent-Wert %d bei %s.\n", FactorPercent, MasterName); + } + + if(FactorPercent < 1050){ + error("TSkillProbe::GetExpForLevel: Ungültiger FactorPercent-Wert %d. Rechne mit 1000 weiter.\n", FactorPercent); + return (Level - this->Min) * this->Delta; + } + + double Base = (double)FactorPercent / 1000.0; + double Power = pow(Base, (double)(Level - this->Min)); + double Result = (double)this->Delta * ((Power - 1.0) / (Base - 1.0)); + return (int)Result; +} + +void TSkillProbe::ChangeSkill(int FactorPercent, int Delta){ + double Progress = 0.0; + if(this->LastLevel < this->NextLevel){ + Progress = (double)(this->Exp - this->LastLevel) + / (double)(this->NextLevel - this->LastLevel); + } + + double Base = (double)FactorPercent / 1000.0; + + // NOTE(fusion): Similar to `this->GetExpForLevel(this->Act)` but inlined? + double LastLevelPower = pow(Base, (double)(this->Act - this->Min)); + double LastLevel = (double)Delta * ((LastLevelPower - 1.0) / (Base - 1.0)); + + // NOTE(fusion): Similar to `this->GetExpForLevel(this->Act + 1)` but inlined? + double NextLevelPower = pow(Base, (double)(this->Act - this->Min + 1)); + double NextLevel = (double)Delta * ((NextLevelPower - 1.0) / (Base - 1.0)); + + // NOTE(fusion): Renormalize experience. + double Exp = LastLevel + Progress * (NextLevel - LastLevel); + + this->FactorPercent = FactorPercent; + this->LastLevel = (int)LastLevel; + this->NextLevel = (int)NextLevel; + this->Delta = Delta; + this->Exp = (int)Exp; + + TCreature *Master = this->Master; + if(Master != NULL && Master->Type == PLAYER){ + if(this->SkNr == SKILL_MAGIC_LEVEL){ + SendPlayerData(Master->Connection); + }else{ + SendPlayerSkills(Master->Connection); + } + } +} + +int TSkillProbe::ProbeValue(int Max, bool Increase){ + if(Increase){ + this->Increase(1); + } + + // TODO(fusion): Some optimizations made the decompilation output for this + // `RandomFactor` look very weird. It looks correct but we should come back + // to it. + int RandomFactor = ((rand() % 100) + (rand() % 100)) / 2; + int MaxValue = Max * (this->Get() * 5 + 50); + int Result = (RandomFactor * MaxValue) / 10000; + return Result; +} + +bool TSkillProbe::Probe(int Diff, int Prob, bool Increase){ + if(Increase){ + this->Increase(1); + } + + // TODO(fusion): Not sure what's going on here. + bool Result = true; + if(Diff != 0){ + if(this->Act >= (rand() % Diff)){ + Result = (rand() % 100) <= Prob; + }else{ + Result = false; + } + } + return Result; +} + +bool TSkillProbe::SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue){ + this->Cycle = Cycle; + this->Count = Count; + this->MaxCount = MaxCount; + + if(this->Master == NULL){ + error("TSkillProbe::SetTimer: GetMaster liefert NULL zurueck!\n"); + return false; + } + + if(this->Master->Type == PLAYER){ + ((TPlayer*)this->Master)->CheckState(); + SendPlayerData(this->Master->Connection); + } + + return true; +} + +bool TSkillProbe::Jump(int Range){ + TCreature *Master = this->Master; + if(Master == NULL){ + error("TSkillProbe::Jump: GetMaster liefert NULL zurueck!\n"); + return false; + } + + if(Master->Type == PLAYER){ + if(this->SkNr == SKILL_MAGIC_LEVEL){ + SendPlayerData(Master->Connection); + }else{ + SendPlayerSkills(Master->Connection); + } + + if(Range > 0){ + switch(this->SkNr){ + case SKILL_MAGIC_LEVEL:{ + SendMessage(Master->Connection, TALK_EVENT_MESSAGE, + "You advanced to magic level %d.", this->Get()); + break; + } + case SKILL_SHIELDING:{ + SendMessage(Master->Connection, TALK_EVENT_MESSAGE, "You advanced in shielding."); + break; + } + case SKILL_DISTANCE:{ + SendMessage(Master->Connection, TALK_EVENT_MESSAGE, "You advanced in distance fighting."); + break; + } + case SKILL_SWORD:{ + SendMessage(Master->Connection, TALK_EVENT_MESSAGE, "You advanced in sword fighting."); + break; + } + case SKILL_CLUB:{ + SendMessage(Master->Connection, TALK_EVENT_MESSAGE, "You advanced in club fighting."); + break; + } + case SKILL_AXE:{ + SendMessage(Master->Connection, TALK_EVENT_MESSAGE, "You advanced in axe fighting."); + break; + } + case SKILL_FIST:{ + SendMessage(Master->Connection, TALK_EVENT_MESSAGE, "You advanced in fist fighting."); + break; + } + case SKILL_FISHING:{ + SendMessage(Master->Connection, TALK_EVENT_MESSAGE, "You advanced in fishing."); + break; + } + } + } + } + + return true; +} + +void TSkillProbe::Event(int Range){ + if(this->Cycle == 0){ + TCreature *Master = this->Master; + if(Master == NULL){ + error("TSkillProbe::Event: GetMaster liefert NULL zurueck!\n"); + return; + } + + this->MDAct = 0; + if(Master->Type == PLAYER){ + // TODO(fusion): This is weird because `SKILL_GO_STRENGTH` should + // be TSkillGoStrength, instead of TSkillProbe. + if(this->SkNr == SKILL_GO_STRENGTH){ + ((TPlayer*)Master)->CheckState(); + } + + SendPlayerSkills(Master->Connection); + } + } +} + +// TSkillAdd +//============================================================================== +void TSkillAdd::Advance(int Range){ + int Increment = Range * this->AddLevel; + int Max = this->Max + Increment; + int Act = this->Act + Increment; + + // TODO(fusion): I'm not sure this is right. Do we fill health and mana when + // the player levels up? + if(Act < Max){ + Act = Max; + } + + this->Act = Act; + this->Max = Max; +} + +// TSkillHitpoints +//============================================================================== +void TSkillHitpoints::Set(int Value){ + TCreature *Master = this->Master; + if(Master != NULL && Master->IsDead && Value > 0){ + error("TSkillHitpoints::Set: HP von toter Kreatur sollen erhöht werden.\n"); + return; + } + + if(Value > this->Max){ + Value = this->Max; + } + this->Act = Value; + + if(Master != NULL){ + if(Master->Type == PLAYER){ + SendPlayerData(Master->Connection); + } + AnnounceChangedCreature(Master->ID, 1); + } +} + +// TSkillMana +//============================================================================== +void TSkillMana::Set(int Value){ + if(Value > this->Max){ + Value = this->Max; + } + this->Act = Value; + + TCreature *Master = this->Master; + if(Master != NULL && Master->Type == PLAYER){ + SendPlayerData(Master->Connection); + } +} + +// TSkillGoStrength +//============================================================================== +bool TSkillGoStrength::SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue){ + // TODO(fusion): The decompiled version of this function was kind of confusing + // with `Master` being checked multiple times and `CheckState` being called up + // to two times. I've done some cleanup that should keep the same behavior but + // we should always keep an eye out for bugs. + + this->Cycle = Cycle; + this->Count = Count; + this->MaxCount = MaxCount; + + if(Cycle == 0){ + this->MDAct = 0; + } + + TCreature *Master = this->Master; + if(Master == NULL){ + error("TSkillGoStrength::SetTimer: GetMaster liefert NULL zurueck!\n"); + return false; + } + + if(Master->Type == PLAYER){ + ((TPlayer*)Master)->CheckState(); + } + AnnounceChangedCreature(Master->ID, 4); + return true; +} + +void TSkillGoStrength::Event(int Range){ + if(this->Cycle != 0){ + return; + } + + TCreature *Master = this->Master; + if(Master == NULL){ + error("TSkillGoStrength::Event: GetMaster liefert NULL zurueck!\n"); + return; + } + + // TODO(fusion): Again, shouldn't `SkNr` always refer to `GO_STRENGTH` here? + this->MDAct = 0; + if(this->SkNr == SKILL_GO_STRENGTH && Master->Type == PLAYER){ + ((TPlayer*)Master)->CheckState(); + } + AnnounceChangedCreature(Master->ID, 4); +} + +// TSkillCarryStrength +//============================================================================== +void TSkillCarryStrength::Set(int Value){ + if(Value > this->Max){ + Value = this->Max; + } + this->Act = Value; + + TCreature *Master = this->Master; + if(Master != NULL && Master->Type == PLAYER){ + SendPlayerData(Master->Connection); + } +} + +// TSkillSoulpoints +//============================================================================== +void TSkillSoulpoints::Set(int Value){ + if(Value > this->Max){ + Value = this->Max; + } + this->Act = Value; + + TCreature *Master = this->Master; + if(Master != NULL && Master->Type == PLAYER){ + SendPlayerData(Master->Connection); + } +} + +int TSkillSoulpoints::TimerValue(void){ + return (this->Cycle - 1) * this->MaxCount + this->Count; +} + +void TSkillSoulpoints::Event(int Range){ + TCreature *Master = this->Master; + if(Master == NULL){ + error("TSkillSoulpoints::Event: GetMaster liefert NULL zurueck!\n"); + return; + } + + // TODO(fusion): Shouldn't this be the same as `Master->Skills[SKILL_SOUL]`? + // Not sure what's going on here. + if(!Master->IsDead){ + Master->Skills[SKILL_SOUL]->Set(Other->Act + 1); + } +} + +// TSkillFed +//============================================================================== +void TSkillFed::Event(int Range){ + TCreature *Master = this->Master; + if(Master == NULL){ + error("TSkillFed::Event: GetMaster liefert NULL zurueck!\n"); + return; + } + + if(Master->IsDead || IsProtectionZone(Master->posx, Master->posy, Master->posz)){ + return; + } + + uint8 Profession = 0; + if(Master->Type == PLAYER){ + Profession = ((TPlayer*)Master)->GetActiveProfession(); + } + + int SecsPerHP = 12; + int SecsPerMana = 6; + switch(Profession){ + case PROFESSION_NONE: + case PROFESSION_KNIGHT:{ + SecsPerHP = 6; + SecsPerMana = 6; + break; + } + + case PROFESSION_PALADIN:{ + SecsPerHP = 8; + SecsPerMana = 4; + break; + } + + case PROFESSION_SORCERER: + case PROFESSION_DRUID:{ + SecsPerHP = 12; + SecsPerMana = 3; + break; + } + + case PROFESSION_ELITE_KNIGHT:{ + SecsPerHP = 4; + SecsPerMana = 6; + break; + } + + case PROFESSION_ROYAL_PALADIN:{ + SecsPerHP = 6; + SecsPerMana = 3; + break; + } + + case PROFESSION_MASTER_SORCERER: + case PROFESSION_ELDER_DRUID:{ + SecsPerHP = 12; + SecsPerMana = 2; + break; + } + + default:{ + error("TSkillFed::Event: Unbekannter Beruf %d.\n", Profession); + break; + } + } + + // TODO(fusion): Not sure about this. + int Timer = this->TimerValue(); + + if(Timer % SecsPerHP == 0){ + Master->Skills[SKILL_HITPOINTS]->Change(1); + } + + if(Timer % SecsPerMana == 0){ + Master->Skills[SKILL_MANA]->Change(2); + } +} + +// TSkillLight +//============================================================================== +bool TSkillLight::SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue){ + // TODO(fusion): I think the decompiled version of most `SetTimer` functions + // look weird because they're calling the base class method `TSkill::SetTimer()` + // which is getting inlined. + this->Cycle = Cycle; + this->Count = Count; + this->MaxCount = MaxCount; + + TCreature *Master = this->Master; + if(Master == NULL){ + error("TSkillLight::SetTimer: GetMaster liefert NULL zurueck!\n"); + return false; + } + + if(Master->Type == PLAYER){ + ((TPlayer*)Master)->CheckState(); + } + + // TODO(fusion): The decompiled function had this logic, but WTF. + // if(GetCreature(Master->ID) != NULL){ + // AnnounceChangedCreature(Master->ID, 2); + // } + AnnounceChangedCreature(Master->ID, 2); + return true; +} + +void TSkillLight::Event(int Range){ + TCreature *Master = this->Master; + if(Master == NULL){ + error("TSkillLight::Event: GetMaster liefert NULL zurueck!\n"); + return; + } + + AnnounceChangedCreature(Master->ID, 2); +} + +// TSkillIllusion +//============================================================================== +bool TSkillIllusion::SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue){ + this->Cycle = Cycle; + this->Count = Count; + this->MaxCount = MaxCount; + + TCreature *Master = this->Master; + if(Master == NULL){ + error("TSkillIllusion::SetTimer: GetMaster liefert NULL zurueck!\n"); + return false; + } + + if(Master->Type == PLAYER){ + ((TPlayer*)Master)->CheckState(); + } + + // TODO(fusion): The decompiled function had this logic, but WTF. + // if(GetCreature(Master->ID) != NULL){ + // AnnounceChangedCreature(Master->ID, 3); + // } + AnnounceChangedCreature(Master->ID, 3); + return true; +} + +void TSkillIllusion::Event(int Range){ + if(this->Cycle == 0){ + TCreature *Master = this->Master; + if(Master == NULL){ + error("TSkillIllusion::Event: GetMaster liefert NULL zurueck!\n"); + return; + } + + if(this->Get() <= 0){ + Master->Outfit = Master->OrgOutfit; + AnnounceChangedCreature(Master->ID, 3); + // TODO(fusion): I'm not sure this is correct. + NotifyAllCreatures(&Master->CrObject, 2, &NONE); + } + } +} + +// TSkillPoison +//============================================================================== +bool TSkillPoison::Process(void){ + bool Result = this->Cycle == 0; + if(Result){ + TCreature *Master = this->Master; + if(Master != NULL && Master->Type == PLAYER){ + ((TPlayer*)Master)->CheckState(); + } + }else if(this->Count <= 0){ + this->Count = this->MaxCount; + + // NOTE(fusion): Not sure what's going on here. + int Range = (this->Cycle * this->FactorPercent) / 1000; + if(Range == 0){ + // NOTE(fusion): This seems the opposite from `TSkill::Process`. + Range = (this->Cycle > 0) ? + 1 : -1; + } + + this->Cycle -= Range; + this->Event(Range); + }else{ + this->Count -= 1; + } + return Result; +} + +bool TSkillPoison::SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue){ + this->Cycle = Cycle; + this->Count = Count; + this->MaxCount = MaxCount; + + TCreature *Master = this->Master; + if(Master != NULL && Master->Type == PLAYER){ + ((TPlayer*)Master)->CheckState(); + } + + if(AdditionalValue == -1){ + AdditionalValue = 50; + } + + if(AdditionalValue < 10){ + AdditionalValue = 10; + } + + if(AdditionalValue > 1000){ + AdditionalValue = 1000; + } + + this->FactorPercent = AdditionalValue; + return true; +} + +void TSkillPoison::Event(int Range){ + TCreature *Master = this->Master; + if(Master == NULL){ + error("TSkillPoison::Event: GetMaster liefert NULL zurueck!\n"); + return; + } + + if(Range < 0){ + Range = -Range; + } + + Master->Damage(GetCreature(Master->PoisonDamageOrigin), Range, 2); + + // NOTE(fusion): I think this is checking whether `Master` is still upon some + // poison field to determine whether we should extend the poison effect? + Object Obj = GetFirstObject(Master->posx, Master->posy, Master->posz); + while(Obj != NONE){ + // NOTE(fusion): I think `ObjectType` is analogous to `ItemType` in + // OpenTibia terms. + ObjectType ObjType = Obj.getObjectType(); + if(ObjType.getFlag(AVOID)){ + if(ObjType.getAttribute(AVOIDDAMAGETYPES) == 2){ + this->Cycle += 1; + } + } + + Obj = Obj.getNextObject(); + } +} + +void TSkillPoison::Reset(void){ + TSkill::Reset(); + this->FactorPercent = 0x32; +} + +// TSkillBurning +//============================================================================== +void TSkillBurning::Event(int Range){ + TCreature *Master = this->Master; + if(Master == NULL){ + error("TSkillBurning::Event: GetMaster liefert NULL zurueck!\n"); + return; + } + + Master->Damage(GetCreature(Master->FireDamageOrigin), 10, 4); + + // NOTE(Fusion): Something similar to `TSkillPoison::Event` except we're + // looking for a different field type. + Object Obj = GetFirstObject(Master->posx, Master->posy, Master->posz); + while(Obj != NONE){ + ObjectType ObjType = Obj.getObjectType(); + if(ObjType.getFlag(AVOID)){ + if(ObjType.getAttribute(AVOIDDAMAGETYPES) == 4){ + this->Cycle += 1; + } + } + + Obj = Obj.getNextObject(); + } +} + +// TSkillEnergy +//============================================================================== +void TSkillEnergy::Event(int Range){ + TCreature *Master = this->Master; + if(Master == NULL){ + error("TSkillEnergy::Event: GetMaster liefert NULL zurueck!\n"); + return; + } + + Master->Damage(GetCreature(Master->EnergyDamageOrigin), 25, 8); + + // NOTE(Fusion): Something similar to `TSkillPoison::Event` except we're + // looking for a different field type. + Object Obj = GetFirstObject(Master->posx, Master->posy, Master->posz); + while(Obj != NONE){ + ObjectType ObjType = Obj.getObjectType(); + if(ObjType.getFlag(AVOID)){ + if(ObjType.getAttribute(AVOIDDAMAGETYPES) == 8){ + this->Cycle += 1; + } + } + + Obj = Obj.getNextObject(); + } +} + +// TSkillBase +//============================================================================== +TSkillBase::TSkillBase(void){ + STATIC_ASSERT(NARRAY(this>Skills) == NARRAY(this->TimerList)); + for(int i = 0; i < NARRAY(this->Skills); i += 1){ + this->Skills[i] = NULL; + this->TimerList[i] = NULL; + } + this->FirstFreeTimer = 0; +} + +TSkillBase::~TSkillBase(void){ + for(int i = 0; i < NARRAY(this->Skills); i += 1){ + delete this->Skills[i]; + } +} + +bool TSkillBase::NewSkill(uint16 SkillNo, TCreature *Creature){ + if(SkillNo >= NARRAY(this->Skills)){ + error("TSkillBase::NewSkill: unbekannte SkillNummer %d\n", SkillNo); + return false; + } + + if(this->Skills[SkillNo] != NULL){ + delete this->Skills[SkillNo]; + this->Skills[SkillNo] = NULL; + } + + TSkill *Skill = NULL; + switch(SkillNo){ + case SKILL_LEVEL: Skill = new TSkillLevel(SkillNo, Creature); break; + case SKILL_MAGIC_LEVEL: ATTR_FALLTHROUGH; + case SKILL_SHIELDING: ATTR_FALLTHROUGH; + case SKILL_DISTANCE: ATTR_FALLTHROUGH; + case SKILL_SWORD: ATTR_FALLTHROUGH; + case SKILL_CLUB: ATTR_FALLTHROUGH; + case SKILL_AXE: ATTR_FALLTHROUGH; + case SKILL_FIST: ATTR_FALLTHROUGH; + case SKILL_FISHING: Skill = new TSkillProbe(SkillNo, Creature); break; + case SKILL_HITPOINTS: Skill = new TSkillHitpoints(SkillNo, Creature); break; + case SKILL_MANA: Skill = new TSkillMana(SkillNo, Creature); break; + case SKILL_GO_STRENGTH: Skill = new TSkillGoStrength(SkillNo, Creature); break; + case SKILL_CARRY_WEIGHT: Skill = new TSkillCarryStrength(SkillNo, Creature); break; + case SKILL_FED: Skill = new TSkillFed(SkillNo, Creature); break; + case SKILL_LIGHT: Skill = new TSkillLight(SkillNo, Creature); break; + case SKILL_ILLUSION: Skill = new TSkillIllusion(SkillNo, Creature); break; + case SKILL_POISON: Skill = new TSkillPoison(SkillNo, Creature); break; + case SKILL_BURNING: Skill = new TSkillBurning(SkillNo, Creature); break; + case SKILL_ENERGY: Skill = new TSkillEnergy(SkillNo, Creature); break; + case SKILL_SOUL: Skill = new TSkillSoulpoints(SkillNo, Creature); break; + default: Skill = new TSkill(SkillNo, Creature); break; + } + + ASSERT(Skill != NULL); + this->Skills[SkillNo] = Skill; + return true; +} + +bool TSkillBase::SetSkills(int Race){ + if(Race < 0 || Race >= 512){ + error("TSkillBase::SetSkills: Ungültige Rassennummer %d.\n", Race); + return false; + } + + // TODO(fusion): This is referencing some global table with probably all + // types of creatures. + int NumSkills = RaceData[Race].Skills; + for(int i = 0; i < NumSkills; i += 1){ + // TODO(fusion): We'd need to implement the `vector` container being used + // across the codebase. + //TSkillData *SkillData = RaceData[Race].Skill(i); + + // BUG(fusion): We don't check if `Skill` is valid? Could be NULL. We + // don't seem to set all `Skill` fields either so there is something + // else going on, probably. + TSkill *Skill = this->Skills[SkillData->Nr]; + Skill->Act = SkillData->Actual; + Skill->Min = SkillData->Minimum; + Skill->Max = SkillData->Maximum; + Skill->LastLevel = 0; + Skill->NextLevel = SkillData->NextLevel; + Skill->Delta = SkillData->NextLevel; + Skill->FactorPercent = SkillData->FactorPercent; + Skill->MaxCount = 0; + Skill->AddLevel = SkillData->AddLevel; + } + + return true; +} + +void TSkillBase::ProcessSkills(void){ + int Index = 0; + int End = this->FirstFreeTimer; + while(Index < End){ + // TODO(fusion): Probably remove if `Skill == NULL`? + TSkill *Skill = this->TimerList[Index]; + if(Skill && !Skill->Process()){ + // NOTE(fusion): A little swap and pop action. + End -= 1; + this->TimerList[Index] = this->TimerList[End]; + this->TimerList[End] = NULL; + }else{ + Index += 1; + } + } + this->FirstFreeTimer = (uint16)End; +} + +bool TSkillBase::SetTimer(uint16 SkNr, int Cycle, int Count, int MaxCount, int AdditionalValue){ + if(SkNr >= NARRAY(this->Skills)){ + error("TSkillBase::SetTimer: Ungueltige SkNr: %d\n", SkNr); + return false; + } + + this->DelTimer(SkNr); + + // BUG(fusion): We don't check if `Skill` is valid here. + TSkill *Skill = this->Skills[SkNr]; + bool Result = Skill->SetTimer(Cycle, Count, MaxCount, AdditionalValue); + if(Result){ + // NOTE(fusion): A little push back action. + // BUG(fusion): We don't check if there is room in `TimerList`. I'd assume + // it is naturally bound by the maximum number of skills? + this->TimerList[this->FirstFreeTimer] = Skill; + this->FirstFreeTimer += 1; + } + + return Result; +} + +void TSkillBase::DelTimer(uint16 SkNr){ + if(SkNr >= NARRAY(this->Skills)){ + error("TSkillBase::DelTimer: Ungueltige SkNr: %d\n", SkNr); + return; + } + + TSkill *Skill = this->Skills[SkNr]; + if(Skill != NULL){ + Skill->DelTimer(); + + int End = this->FirstFreeTimer; + for(int Index = 0; Index < End; i += 1){ + if(Skill == this->TimerList[Index]){ + // NOTE(fusion): A little swap and pop action. + End -= 1; + this->TimerList[Index] = this->TimerList[End]; + this->TimerList[End] = NULL; + break; + } + } + } +} diff --git a/src/skill.hh b/src/skill.hh new file mode 100644 index 0000000..9b54649 --- /dev/null +++ b/src/skill.hh @@ -0,0 +1,159 @@ +#ifndef TIBIA_SKILL_HH_ +#define TIBIA_SKILL_HH_ 1 + +#include "main.hh" + +struct TCreature; + +struct TSkill{ + // REGULAR FUNCTIONS + // ========================================================================= + TSkill(int SkNr, TCreature *Master); + int Get(void); + int GetProgress(void); + void Check(void); + void Change(int Amount); + void SetMDAct(int MDAct); + void Load(int Act, int Max, int Min, int DAct, int MDAct, + int Cycle, int MaxCycle, int Count, int MaxCount, int AddLevel, + int Exp, int FactorPercent, int NextLevel, int Delta); + void Save(int *Act, int *Max, int *Min, int *DAct, int *MDAct, + int *Cycle, int *MaxCycle, int *Count, int *MaxCount, int *AddLevel, + int *Exp, int *FactorPercent, int *NextLevel, int *Delta); + + // VIRTUAL FUNCTIONS + // ========================================================================= + virtual ~TSkill(void); // VTABLE[ 0] + // Duplicate destructor that also calls operator delete. // VTABLE[ 1] + virtual void Set(int Value); // VTABLE[ 2] + virtual void Increase(int Amount); // VTABLE[ 3] + virtual void Decrease(int Amount); // VTABLE[ 4] + virtual int GetExpForLevel(int Level); // VTABLE[ 5] + virtual void Advance(int Range); // VTABLE[ 6] + virtual void ChangeSkill(int FactorPercent, int Delta); // VTABLE[ 7] + virtual int ProbeValue(int Max, bool Increase); // VTABLE[ 8] + virtual bool Probe(int Diff, int Prob, bool Increase); // VTABLE[ 9] + virtual bool Process(void); // VTABLE[10] + virtual bool SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue); // VTABLE[11] + virtual bool DelTimer(void); // VTABLE[12] + virtual int TimerValue(void); // VTABLE[13] + virtual bool Jump(int Range); // VTABLE[14] + virtual void Event(int Range); // VTABLE[15] + virtual void Reset(void); // VTABLE[16] + + // DATA + // ========================================================================= + //void *VTABLE; // IMPLICIT + int DAct; // Delta Value - Probably from equipment. + int MDAct; // Delta Magic Value - Probably from spells. + uint16 SkNr; + TCreature *Master; + int Act; // Actual Value (?) + int Max; + int Min; + int FactorPercent; + int LastLevel; + int NextLevel; + int Delta; + int Exp; + int Cycle; + int MaxCycle; + int Count; + int MaxCount; + int AddLevel; +}; + +struct TSkillLevel: TSkill { + void Increase(int Amount) override; + void Decrease(int Amount) override; + int GetExpForLevel(int Level) override; + bool Jump(int Range) override; +}; + +struct TSkillProbe: TSkill { + void Increase(int Amount) override; + void Decrease(int Amount) override; + int GetExpForLevel(int Level) override; + void ChangeSkill(int FactorPercent, int Delta) override; + int ProbeValue(int Max, bool Increase) override; + bool Probe(int Diff, int Prob, bool Increase) override; + bool SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue) override; + bool Jump(int Range) override; + void Event(int Range) override; +}; + +struct TSkillAdd: TSkill { + void Advance(int Range) override; +}; + +struct TSkillHitpoints: TSkillAdd { + void Set(int Value) override; +}; + +struct TSkillMana: TSkillAdd { + void Set(int Value) override; +}; + +struct TSkillGoStrength: TSkillAdd { + bool SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue) override; + void Event(int Range) override; +}; + +struct TSkillCarryStrength: TSkillAdd { + void Set(int Value) override; +}; + +struct TSkillSoulpoints: TSkillAdd { + void Set(int Value) override; + int TimerValue(void) override; + void Event(int Range) override; +}; + +struct TSkillFed: TSkill { + void Event(int Range) override; +}; + +struct TSkillLight: TSkill { + bool SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue) override; + void Event(int Range) override; +}; + +struct TSkillIllusion: TSkill { + bool SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue) override; + void Event(int Range) override; +}; + +struct TSkillPoison: TSkill { + bool Process(void) override; + bool SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue) override; + void Event(int Range) override; + void Reset(void) override; +}; + +struct TSkillBurning: TSkill { + void Event(int Range) override; +}; + +struct TSkillEnergy: TSkill { + void Event(int Range) override; +}; + +struct TSkillBase{ + // REGULAR FUNCTIONS + // ========================================================================= + TSkillBase(void); + ~TSkillBase(void); + bool NewSkill(uint16 SkillNo, TCreature *Creature); + bool SetSkills(int Race); + void ProcessSkills(void); + bool SetTimer(uint16 SkNr, int Cycle, int Count, int MaxCount, int AdditionalValue); + void DelTimer(uint16 SkNr); + + // DATA + // ========================================================================= + TSkill *Skills[25]; + TSkill *TimerList[25]; + uint16 FirstFreeTimer; +}; + +#endif //TIBIA_SKILL_HH_ |
