initial commit - getting at TSkill structs

This commit is contained in:
fusion32 2025-05-16 18:46:18 -03:00
commit 717f5cd9da
11 changed files with 118608 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.vscode
bin
build
local
*.log

9
TODO.md Normal file
View File

@ -0,0 +1,9 @@
## TODO NEXT
- ALL TSkill structures
- TSkillBase
- TCreature
- TPlayer
- TNonPlayer
## 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.

25
build.bat Normal file
View File

@ -0,0 +1,25 @@
@echo off
setlocal
pushd %~dp0
del /q .\build\*
mkdir .\build
pushd .\build
set OUTPUTEXE=out.exe
set OUTPUTPDB=out.pdb
set INSTALLDIR=..\bin
set SRC="../src/creature.cc" "../src/main.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
mkdir "%INSTALLDIR%"
copy /Y "%OUTPUTEXE%" "%INSTALLDIR%"
copy /Y "%OUTPUTPDB%" "%INSTALLDIR%"
popd
popd
endlocal

605
reference/enums.hh Normal file
View File

@ -0,0 +1,605 @@
enum CreatureType: int {
PLAYER=0,
MONSTER=1,
NPC=2
};
enum CONNECTIONSTATE: int {
CONNECTION_FREE=0,
CONNECTION_ASSIGNED=1,
CONNECTION_CONNECTED=2,
CONNECTION_LOGIN=3,
CONNECTION_GAME=4,
CONNECTION_DEAD=5,
CONNECTION_LOGOUT=6,
CONNECTION_DISCONNECTED=7
};
enum KNOWNCREATURESTATE: int {
KNOWNCREATURE_FREE=0,
KNOWNCREATURE_UPTODATE=1,
KNOWNCREATURE_OUTDATED=2
};
enum ToDoType: int {
TDWait=0,
TDGo=1,
TDRotate=2,
TDMove=3,
TDTrade=4,
TDUse=5,
TDTurn=6,
TDAttack=7,
TDTalk=8,
TDChangeState=9
};
enum GAMESTATE: int {
GAME_STARTING=0,
GAME_RUNNING=1,
GAME_CLOSING=2,
GAME_ENDING=3
};
enum STATE: int {
SLEEPING=0,
IDLE=1,
UNDERATTACK=2,
TALKING=3,
LEAVING=4,
ATTACKING=5,
PANIC=6
};
enum SpellShapeType: int {
SHAPE_ACTOR=0,
SHAPE_VICTIM=1,
SHAPE_ORIGIN=2,
SHAPE_DESTINATION=3,
SHAPE_ANGLE=4
};
enum SpellImpactType: int {
IMPACT_DAMAGE=0,
IMPACT_FIELD=1,
IMPACT_HEALING=2,
IMPACT_SPEED=3,
IMPACT_DRUNKEN=4,
IMPACT_STRENGTH=5,
IMPACT_OUTFIT=6,
IMPACT_SUMMON=7
};
enum RESULT: int {
ERROR=-1,
NOERROR=0,
NOTACCESSIBLE=1,
NOTMOVABLE=2,
NOTTAKABLE=3,
NOROOM=4,
OUTOFRANGE=5,
OUTOFHOME=6,
CANNOTTHROW=7,
TOOHEAVY=8,
CROSSREFERENCE=9,
CONTAINERFULL=10,
WRONGPOSITION=11,
WRONGPOSITION2=12,
WRONGCLOTHES=13,
HANDSNOTFREE=14,
HANDBLOCKED=15,
ONEWEAPONONLY=16,
NOMATCH=17,
NOTCUMULABLE=18,
TOOMANYPARTS=19,
EMPTYCONTAINER=20,
SPLITOBJECT=21,
NOKEYMATCH=22,
UPSTAIRS=23,
DOWNSTAIRS=24,
CREATURENOTEXISTING=25,
PLAYERNOTEXISTING=26,
PLAYERNOTONLINE=27,
NAMEAMBIGUOUS=28,
NOTUSABLE=29,
FEDUP=30,
DESTROYED=31,
SPELLUNKNOWN=32,
LOWMAGICLEVEL=33,
MAGICITEM=34,
NOTENOUGHMANA=35,
NOSKILL=36,
TARGETLOST=37,
OUTOFAMMO=38,
NOCREATURE=39,
TOOLONG=40,
TARGETOUTOFRANGE=41,
TARGETHIDDEN=42,
ATTACKNOTALLOWED=43,
NOWAY=44,
LOGINERROR=45,
LOGINABORT=46,
PROTECTIONZONE=47,
ENTERPROTECTIONZONE=48,
EXHAUSTED=49,
NOTINVITED=50,
NOPREMIUMACCOUNT=51,
MOVENOTPOSSIBLE=52,
ALREADYTRADING=53,
PARTNERTRADING=54,
TOOMANYOBJECTS=55,
TOOMANYSLAVES=56,
NOTTURNABLE=57,
SECUREMODE=58,
NOTENOUGHSOULPOINTS=59,
LOWLEVEL=60
};
enum ActionType: int {
ACTION_CREATEONMAP=0,
ACTION_CREATE=1,
ACTION_MONSTERONMAP=2,
ACTION_MONSTER=3,
ACTION_EFFECTONMAP=4,
ACTION_EFFECT=5,
ACTION_TEXTONMAP=6,
ACTION_TEXT=7,
ACTION_CHANGEONMAP=8,
ACTION_CHANGE=9,
ACTION_CHANGEREL=10,
ACTION_SETATTRIBUTE=11,
ACTION_CHANGEATTRIBUTE=12,
ACTION_SETQUESTVALUE=13,
ACTION_DAMAGE=14,
ACTION_SETSTART=15,
ACTION_WRITENAME=16,
ACTION_WRITETEXT=17,
ACTION_LOGOUT=18,
ACTION_MOVEALLONMAP=19,
ACTION_MOVEALL=20,
ACTION_MOVEALLREL=21,
ACTION_MOVETOPONMAP=22,
ACTION_MOVETOP=23,
ACTION_MOVETOPREL=24,
ACTION_MOVE=25,
ACTION_MOVEREL=26,
ACTION_RETRIEVE=27,
ACTION_DELETEALLONMAP=28,
ACTION_DELETETOPONMAP=29,
ACTION_DELETEONMAP=30,
ACTION_DELETE=31,
ACTION_DELETEININVENTORY=32,
ACTION_DESCRIPTION=33,
ACTION_LOADDEPOT=34,
ACTION_SAVEDEPOT=35,
ACTION_SENDMAIL=36,
ACTION_NOP=37
};
enum ModifierType: int {
MODIFIER_NORMAL=0,
MODIFIER_INVERT=1,
MODIFIER_TRUE=2
};
enum ConditionType: int {
CONDITION_ISPOSITION=0,
CONDITION_ISTYPE=1,
CONDITION_ISCREATURE=2,
CONDITION_ISPLAYER=3,
CONDITION_HASFLAG=4,
CONDITION_HASTYPEATTRIBUTE=5,
CONDITION_HASINSTANCEATTRIBUTE=6,
CONDITION_HASTEXT=7,
CONDITION_ISPEACEFUL=8,
CONDITION_MAYLOGOUT=9,
CONDITION_HASPROFESSION=10,
CONDITION_HASLEVEL=11,
CONDITION_HASRIGHT=12,
CONDITION_HASQUESTVALUE=13,
CONDITION_TESTSKILL=14,
CONDITION_COUNTOBJECTS=15,
CONDITION_COUNTOBJECTSONMAP=16,
CONDITION_ISOBJECTTHERE=17,
CONDITION_ISCREATURETHERE=18,
CONDITION_ISPLAYERTHERE=19,
CONDITION_ISOBJECTININVENTORY=20,
CONDITION_ISPROTECTIONZONE=21,
CONDITION_ISHOUSE=22,
CONDITION_ISHOUSEOWNER=23,
CONDITION_ISDRESSED=24,
CONDITION_RANDOM=25
};
enum INSTANCEATTRIBUTE: int {
CONTENT=0,
CHESTQUESTNUMBER=1,
AMOUNT=2,
KEYNUMBER=3,
KEYHOLENUMBER=4,
DOORLEVEL=5,
DOORQUESTNUMBER=6,
DOORQUESTVALUE=7,
CHARGES=8,
TEXTSTRING=9,
EDITOR=10,
CONTAINERLIQUIDTYPE=11,
POOLLIQUIDTYPE=12,
ABSTELEPORTDESTINATION=13,
RESPONSIBLE=14,
REMAININGEXPIRETIME=15,
SAVEDEXPIRETIME=16,
REMAININGUSES=17
};
enum TYPEATTRIBUTE: int {
WAYPOINTS=0,
CAPACITY=1,
CHANGETARGET=2,
KEYDOORTARGET=3,
NAMEDOORTARGET=4,
LEVELDOORTARGET=5,
QUESTDOORTARGET=6,
NUTRITION=7,
INFORMATIONTYPE=8,
FONTSIZE=9,
MAXLENGTH=10,
MAXLENGTHONCE=11,
SOURCELIQUIDTYPE=12,
ABSTELEPORTEFFECT=13,
RELTELEPORTDISPLACEMENT=14,
RELTELEPORTEFFECT=15,
AVOIDDAMAGETYPES=16,
MINIMUMLEVEL=17,
PROFESSIONS=18,
WEIGHT=19,
ROTATETARGET=20,
DESTROYTARGET=21,
BODYPOSITION=22,
SKILLNUMBER=23,
SKILLMODIFICATION=24,
PROTECTIONDAMAGETYPES=25,
DAMAGEREDUCTION=26,
BRIGHTNESS=27,
LIGHTCOLOR=28,
CORPSETYPE=29,
TOTALEXPIRETIME=30,
EXPIRETARGET=31,
TOTALUSES=32,
WEAROUTTARGET=33,
WEAPONTYPE=34,
WEAPONATTACKVALUE=35,
WEAPONDEFENDVALUE=36,
SHIELDDEFENDVALUE=37,
BOWRANGE=38,
BOWAMMOTYPE=39,
THROWRANGE=40,
THROWATTACKVALUE=41,
THROWDEFENDVALUE=42,
THROWMISSILE=43,
THROWSPECIALEFFECT=44,
THROWEFFECTSTRENGTH=45,
THROWFRAGILITY=46,
WANDRANGE=47,
WANDMANACONSUMPTION=48,
WANDATTACKSTRENGTH=49,
WANDATTACKVARIATION=50,
WANDDAMAGETYPE=51,
WANDMISSILE=52,
AMMOTYPE=53,
AMMOATTACKVALUE=54,
AMMOMISSILE=55,
AMMOSPECIALEFFECT=56,
AMMOEFFECTSTRENGTH=57,
ARMORVALUE=58,
ELEVATION=59,
DISGUISETARGET=60,
MEANING=61
};
enum FLAG: int {
BANK=0,
CLIP=1,
BOTTOM=2,
TOP=3,
CONTAINER=4,
CHEST=5,
CUMULATIVE=6,
USEEVENT=7,
CHANGEUSE=8,
FORCEUSE=9,
MULTIUSE=10,
DISTUSE=11,
MOVEMENTEVENT=12,
COLLISIONEVENT=13,
SEPARATIONEVENT=14,
KEY=15,
KEYDOOR=16,
NAMEDOOR=17,
LEVELDOOR=18,
QUESTDOOR=19,
BED=20,
FOOD=21,
RUNE=22,
INFORMATION=23,
TEXT=24,
WRITE=25,
WRITEONCE=26,
LIQUIDCONTAINER=27,
LIQUIDSOURCE=28,
LIQUIDPOOL=29,
TELEPORTABSOLUTE=30,
TELEPORTRELATIVE=31,
UNPASS=32,
UNMOVE=33,
UNTHROW=34,
UNLAY=35,
AVOID=36,
MAGICFIELD=37,
RESTRICTLEVEL=38,
RESTRICTPROFESSION=39,
TAKE=40,
HANG=41,
HOOKSOUTH=42,
HOOKEAST=43,
ROTATE=44,
DESTROY=45,
CLOTHES=46,
SKILLBOOST=47,
PROTECTION=48,
LIGHT=49,
ROPESPOT=50,
CORPSE=51,
EXPIRE=52,
EXPIRESTOP=53,
WEAROUT=54,
WEAPON=55,
SHIELD=56,
BOW=57,
THROW=58,
WAND=59,
AMMO=60,
ARMOR=61,
HEIGHT=62,
DISGUISE=63,
SHOWDETAIL=64,
SPECIALOBJECT=65
};
enum SPECIALMEANING: int {
MONEY_ONE=1,
MONEY_HUNDRED=2,
MONEY_TENTHOUSAND=3,
INVENTORY_RIGHTHAND=10,
INVENTORY_LEFTHAND=11,
INVENTORY_BODY_MALE=12,
INVENTORY_BODY_FEMALE=13,
INVENTORY_CONTAINER=14,
INVENTORY_FOOD=15,
DEPOT_LOCKER=20,
DEPOT_CHEST=21,
PARCEL_NEW=22,
PARCEL_STAMPED=23,
PARCEL_LABEL=24,
LETTER_NEW=25,
LETTER_STAMPED=26,
BLOOD_SPLASH=30,
BLOOD_POOL=31,
RUNE_BLANK=40,
MAGICFIELD_FIRE_DANGEROUS=41,
MAGICFIELD_FIRE_HARMLESS=42,
MAGICFIELD_POISON_DANGEROUS=43,
MAGICFIELD_POISON_HARMLESS=44,
MAGICFIELD_ENERGY_DANGEROUS=45,
MAGICFIELD_ENERGY_HARMLESS=46,
MAGICFIELD_MAGICWALL=47,
MAGICFIELD_RUSHWOOD=48
};
enum RIGHT: int {
PREMIUM_ACCOUNT=0,
NOTATION=1,
NAMELOCK=2,
STATEMENT_REPORT=3,
BANISHMENT=4,
FINAL_WARNING=5,
IP_BANISHMENT=6,
KICK=7,
HOME_TELEPORT=8,
GAMEMASTER_BROADCAST=9,
ANONYMOUS_BROADCAST=10,
NO_BANISHMENT=11,
ALLOW_MULTICLIENT=12,
LOG_COMMUNICATION=13,
READ_GAMEMASTER_CHANNEL=14,
READ_TUTOR_CHANNEL=15,
HIGHLIGHT_HELP_CHANNEL=16,
SEND_BUGREPORTS=17,
NAME_INSULTING=18,
NAME_SENTENCE=19,
NAME_NONSENSICAL_LETTERS=20,
NAME_BADLY_FORMATTED=21,
NAME_NO_PERSON=22,
NAME_CELEBRITY=23,
NAME_COUNTRY=24,
NAME_FAKE_IDENTITY=25,
NAME_FAKE_POSITION=26,
STATEMENT_INSULTING=27,
STATEMENT_SPAMMING=28,
STATEMENT_ADVERT_OFFTOPIC=29,
STATEMENT_ADVERT_MONEY=30,
STATEMENT_NON_ENGLISH=31,
STATEMENT_CHANNEL_OFFTOPIC=32,
STATEMENT_VIOLATION_INCITING=33,
CHEATING_BUG_ABUSE=34,
CHEATING_GAME_WEAKNESS=35,
CHEATING_MACRO_USE=36,
CHEATING_MODIFIED_CLIENT=37,
CHEATING_HACKING=38,
CHEATING_MULTI_CLIENT=39,
CHEATING_ACCOUNT_TRADING=40,
CHEATING_ACCOUNT_SHARING=41,
GAMEMASTER_THREATENING=42,
GAMEMASTER_PRETENDING=43,
GAMEMASTER_INFLUENCE=44,
GAMEMASTER_FALSE_REPORTS=45,
KILLING_EXCESSIVE_UNJUSTIFIED=46,
DESTRUCTIVE_BEHAVIOUR=47,
SPOILING_AUCTION=48,
INVALID_PAYMENT=49,
TELEPORT_TO_CHARACTER=50,
TELEPORT_TO_MARK=51,
TELEPORT_VERTICAL=52,
TELEPORT_TO_COORDINATE=53,
LEVITATE=54,
SPECIAL_MOVEUSE=55,
MODIFY_GOSTRENGTH=56,
SHOW_COORDINATE=57,
RETRIEVE=58,
ENTER_HOUSES=59,
OPEN_NAMEDOORS=60,
INVULNERABLE=61,
UNLIMITED_MANA=62,
KEEP_INVENTORY=63,
ALL_SPELLS=64,
UNLIMITED_CAPACITY=65,
ZERO_CAPACITY=66,
ATTACK_EVERYWHERE=67,
NO_ATTACK=68,
NO_RUNES=69,
NO_LOGOUT_BLOCK=70,
GAMEMASTER_OUTFIT=71,
ILLUMINATE=72,
CHANGE_PROFESSION=73,
IGNORED_BY_MONSTERS=74,
SHOW_KEYHOLE_NUMBERS=75,
CREATE_OBJECTS=76,
CREATE_MONEY=77,
CREATE_MONSTERS=78,
CHANGE_SKILLS=79,
CLEANUP_FIELDS=80,
NO_STATISTICS=81
};
enum TWriterThreadReplyType: int {
REPLY_BROADCAST=0,
REPLY_DIRECT=1,
REPLY_LOGOUT=2
};
enum TWriterThreadOrderType: int {
ORDER_TERMINATE=0,
ORDER_LOGOUT=1,
ORDER_PLAYERLIST=2,
ORDER_KILLSTATISTICS=3,
ORDER_PUNISHMENT=4,
ORDER_CHARACTERDEATH=5,
ORDER_ADDBUDDY=6,
ORDER_REMOVEBUDDY=7,
ORDER_DECREMENTISONLINE=8,
ORDER_SAVEPLAYERDATA=9
};
enum TALK_MODE: int {
TALK_SAY=1,
TALK_WHISPER=2,
TALK_YELL=3,
TALK_PRIVATE_MESSAGE=4,
TALK_CHANNEL_CALL=5,
TALK_GAMEMASTER_REQUEST=6,
TALK_GAMEMASTER_ANSWER=7,
TALK_PLAYER_ANSWER=8,
TALK_GAMEMASTER_BROADCAST=9,
TALK_GAMEMASTER_CHANNELCALL=10,
TALK_GAMEMASTER_MESSAGE=11,
TALK_HIGHLIGHT_CHANNELCALL=12,
TALK_ANONYMOUS_BROADCAST=13,
TALK_ANONYMOUS_CHANNELCALL=14,
TALK_ANONYMOUS_MESSAGE=15,
TALK_ANIMAL_LOW=16,
TALK_ANIMAL_LOUD=17,
TALK_ADMIN_MESSAGE=18,
TALK_EVENT_MESSAGE=19,
TALK_LOGIN_MESSAGE=20,
TALK_STATUS_MESSAGE=21,
TALK_INFO_MESSAGE=22,
TALK_FAILURE_MESSAGE=23
};
enum CHANNEL: int {
GUILD_CHANNEL=0,
GAMEMASTER_CHANNEL=1,
TUTOR_CHANNEL=2,
REQUEST_QUEUE=3,
GAME_CHANNEL=4,
TRADE_CHANNEL=5,
REALLIFE_CHANNEL=6,
HELP_CHANNEL=7
};
enum EventType: int {
EVENT_USE=0,
EVENT_MULTIUSE=1,
EVENT_MOVEMENT=2,
EVENT_COLLISION=3,
EVENT_SEPARATION=4
};
enum ParameterType: int {
PARAMETER_OBJECT=0,
PARAMETER_TYPE=1,
PARAMETER_FLAG=2,
PARAMETER_TYPEATTRIBUTE=3,
PARAMETER_INSTANCEATTRIBUTE=4,
PARAMETER_COORDINATE=5,
PARAMETER_VECTOR=6,
PARAMETER_RIGHT=7,
PARAMETER_SKILL=8,
PARAMETER_NUMBER=9,
PARAMETER_TEXT=10,
PARAMETER_COMPARISON=11
};
enum TReaderThreadOrderType: int {
ORDER_TERMINATE=0,
ORDER_LOADSECTOR=1,
ORDER_LOADCHARACTER=2
};
enum TReaderThreadReplyType: int {
REPLY_SECTORDATA=0,
REPLY_CHARACTERDATA=1
};
enum TOKEN: int {
ENDOFFILE=0,
IDENTIFIER=1,
NUMBER=2,
STRING=3,
BYTES=4,
COORDINATE=5,
SPECIAL=6
};
enum SITUATION: int {
DEFAULT=0,
ADDRESS=1,
ADDRESSQUEUE=2,
BUSY=3,
VANISH=4
};
enum BloodType: int {
BT_BLOOD=0,
BT_SLIME=1,
BT_BONES=2,
BT_FIRE=3,
BT_ENERGY=4
};
enum TWorldType: int {
NORMAL=0,
NON_PVP=1,
PVP_ENFORCED=2
};

115412
reference/game.c Normal file

File diff suppressed because it is too large Load Diff

1712
reference/types.hh Normal file

File diff suppressed because it is too large Load Diff

429
src/creature.cc Normal file
View File

@ -0,0 +1,429 @@
#include "creature.hh"
#include "enums.hh"
// TSkill REGULAR FUNCTIONS
//==============================================================================
TSkill::TSkill(int SkNr, TCreature *Master){
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->NextLevel - this->Exp) * 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){
int SkNr = this->SkNr;
if(SkNr >= NARRAY(Master->Skills)){
error("TSkillBase::SetTimer: Ungueltige SkNr: %d\n", SkNr);
return;
}
// TODO(fusion): I'm not entirely sure wtf is going on here.
TSkill *Other = Master->Skills[SkNr];
Master->DelTimer(SkNr);
if(Other->SetTimer(Cycle, Count, MaxCount, FactorPercent)){
Master->TimerList[Master->FirstFreeTimer] = Other;
Master->FirstFreeTimer += 1;
}
}
}
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 < 1) ? +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
//==============================================================================
TSkillLevel::~TSkillLevel(void){
if(this->Master != NULL){
this->Master->DelTimer(this->SkNr);
}
}
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`?
int Range = 0;
this->Exp += Amount;
while(this->Exp >= this->NextLevel){
this->Act += 1;
this->LastLevel = this->GetExpForLevel(this->Act);
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;
}
int Range = 0;
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` instead of using `LastLevel`
// which makes me wonder whether `LastLevel` is properly initialized.
while(this->Exp < this->LastLevel){
this->Act -= 1;
this->NextLevel = this->LastLevel;
this->LastLevel = this->GetExpForLevel(this->Act);
}
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(Level > 500){
error("TSkillLevel::GetExpForLevel: Level=%d; Formel gegen Überlauf sichern.\n", Level);
return -1;
}
if(this->Delta <= 0){
error("TSkillLevel::GetExpForLevel: Ungültiger Delta-Wert %d.\n", this->Delta);
return 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){
// TODO(fusion): Probably `TSkill::Get` inlined?
int Level = this->Act;
if(Level < this->Min)
Level = this->Min;
Level += this->MDAct + this->DAct;
// ==
int FromLevel = Level - Range;
int ToLevel = Level;
if(Range > 0){
SendMessage(this->Master->Connection, TALK_ANONYMOUS_BROADCAST,
"You advanced from Level %d to Level %d.", FromLevel, ToLevel);
}else if(Range < 0){
SendMessage(this->Master->Connection, TALK_ANONYMOUS_BROADCAST,
"You were downgraded from Level %d to Level %d.", FromLevel, ToLevel);
}
}
return true;
}
// Creature Functions
//==============================================================================
// TODO(fusion): This was the first function I attempted to cleanup but soon
// realized we should start with the building blocks of the codebase, namely
// TSkill, TSkillBase, etc...
// We should probably come back to this once we start doing creature functions
// again.
void CheckMana(TCreature *Creature, int ManaPoints, int SoulPoints, int Delay){
if(Creature == NULL){
error("CheckMana: Übergebene Kreatur existiert nicht.\n");
throw ERROR;
}
if(Creature->Type != PLAYER || ManaPoints < 0)
return;
TSkill *Mana = Creature->Skills[SKILL_MANA];
if(Mana == NULL){
error("CheckMana: Kein Skill MANA!\n");
throw ERROR;
}
TSkill *Soul = Creature->Skills[SKILL_SOUL];
if(Soul == NULL){
error("CheckMana: Kein Skill SOULPOINTS!\n");
throw ERROR;
}
if(!CheckRight(Creature->ID, UNLIMITED_MANA)){
if(Mana->Get() < ManaPoints)
throw NOTENOUGHMANA;
if(Soul->Get() < SoulPoints)
throw NOTENOUGHSOULPOINTS;
Mana->Change(-ManaPoints);
Soul->Change(-SoulPoints);
}
if(ManaPoints > 0){
Creature->Skills[SKILL_MAGIC_LEVEL]->Increase(ManaPoints);
}
// NOTE(fusion): Maintain largest exhaust?
int EarliestSpellTime = Delay + ServerMilliseconds;
if(Creature->EarliestSpellTime < EarliestSpellTime)
Creature->EarliestSpellTime = EarliestSpellTime;
}

76
src/creature.hh Normal file
View File

@ -0,0 +1,76 @@
#ifndef TIBIA_CREATURE_HH_
#define TIBIA_CREATURE_HH_ 1
#include "main.hh"
struct TSkillBase;
struct TCreature;
struct TPlayer;
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]
//virtual ~TSkill(void); // 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 {
~TSkillLevel(void) override;
void Increase(int Amount) override;
void Decrease(int Amount) override;
int GetExpForLevel(int Level) override;
bool Jump(int Range) override;
};
#endif //TIBIA_CREATURE_HH_

219
src/enums.hh Normal file
View File

@ -0,0 +1,219 @@
#ifndef TIBIA_ENUMS_HH_
#define TIBIA_ENUMS_HH_ 1
#include "main.hh"
// TODO(fusion): Probably cleanup these names? Prefix values? Its crazy that
// there are no collision problems (possibly yet).
enum CreatureType: int {
PLAYER = 0,
MONSTER = 1,
NPC = 2,
};
enum RESULT: int {
ERROR = -1,
NOERROR = 0,
NOTACCESSIBLE = 1,
NOTMOVABLE = 2,
NOTTAKABLE = 3,
NOROOM = 4,
OUTOFRANGE = 5,
OUTOFHOME = 6,
CANNOTTHROW = 7,
TOOHEAVY = 8,
CROSSREFERENCE = 9,
CONTAINERFULL = 10,
WRONGPOSITION = 11,
WRONGPOSITION2 = 12,
WRONGCLOTHES = 13,
HANDSNOTFREE = 14,
HANDBLOCKED = 15,
ONEWEAPONONLY = 16,
NOMATCH = 17,
NOTCUMULABLE = 18,
TOOMANYPARTS = 19,
EMPTYCONTAINER = 20,
SPLITOBJECT = 21,
NOKEYMATCH = 22,
UPSTAIRS = 23,
DOWNSTAIRS = 24,
CREATURENOTEXISTING = 25,
PLAYERNOTEXISTING = 26,
PLAYERNOTONLINE = 27,
NAMEAMBIGUOUS = 28,
NOTUSABLE = 29,
FEDUP = 30,
DESTROYED = 31,
SPELLUNKNOWN = 32,
LOWMAGICLEVEL = 33,
MAGICITEM = 34,
NOTENOUGHMANA = 35,
NOSKILL = 36,
TARGETLOST = 37,
OUTOFAMMO = 38,
NOCREATURE = 39,
TOOLONG = 40,
TARGETOUTOFRANGE = 41,
TARGETHIDDEN = 42,
ATTACKNOTALLOWED = 43,
NOWAY = 44,
LOGINERROR = 45,
LOGINABORT = 46,
PROTECTIONZONE = 47,
ENTERPROTECTIONZONE = 48,
EXHAUSTED = 49,
NOTINVITED = 50,
NOPREMIUMACCOUNT = 51,
MOVENOTPOSSIBLE = 52,
ALREADYTRADING = 53,
PARTNERTRADING = 54,
TOOMANYOBJECTS = 55,
TOOMANYSLAVES = 56,
NOTTURNABLE = 57,
SECUREMODE = 58,
NOTENOUGHSOULPOINTS = 59,
LOWLEVEL = 60,
};
enum RIGHT: int {
PREMIUM_ACCOUNT = 0,
NOTATION = 1,
NAMELOCK = 2,
STATEMENT_REPORT = 3,
BANISHMENT = 4,
FINAL_WARNING = 5,
IP_BANISHMENT = 6,
KICK = 7,
HOME_TELEPORT = 8,
GAMEMASTER_BROADCAST = 9,
ANONYMOUS_BROADCAST = 10,
NO_BANISHMENT = 11,
ALLOW_MULTICLIENT = 12,
LOG_COMMUNICATION = 13,
READ_GAMEMASTER_CHANNEL = 14,
READ_TUTOR_CHANNEL = 15,
HIGHLIGHT_HELP_CHANNEL = 16,
SEND_BUGREPORTS = 17,
NAME_INSULTING = 18,
NAME_SENTENCE = 19,
NAME_NONSENSICAL_LETTERS = 20,
NAME_BADLY_FORMATTED = 21,
NAME_NO_PERSON = 22,
NAME_CELEBRITY = 23,
NAME_COUNTRY = 24,
NAME_FAKE_IDENTITY = 25,
NAME_FAKE_POSITION = 26,
STATEMENT_INSULTING = 27,
STATEMENT_SPAMMING = 28,
STATEMENT_ADVERT_OFFTOPIC = 29,
STATEMENT_ADVERT_MONEY = 30,
STATEMENT_NON_ENGLISH = 31,
STATEMENT_CHANNEL_OFFTOPIC = 32,
STATEMENT_VIOLATION_INCITING = 33,
CHEATING_BUG_ABUSE = 34,
CHEATING_GAME_WEAKNESS = 35,
CHEATING_MACRO_USE = 36,
CHEATING_MODIFIED_CLIENT = 37,
CHEATING_HACKING = 38,
CHEATING_MULTI_CLIENT = 39,
CHEATING_ACCOUNT_TRADING = 40,
CHEATING_ACCOUNT_SHARING = 41,
GAMEMASTER_THREATENING = 42,
GAMEMASTER_PRETENDING = 43,
GAMEMASTER_INFLUENCE = 44,
GAMEMASTER_FALSE_REPORTS = 45,
KILLING_EXCESSIVE_UNJUSTIFIED = 46,
DESTRUCTIVE_BEHAVIOUR = 47,
SPOILING_AUCTION = 48,
INVALID_PAYMENT = 49,
TELEPORT_TO_CHARACTER = 50,
TELEPORT_TO_MARK = 51,
TELEPORT_VERTICAL = 52,
TELEPORT_TO_COORDINATE = 53,
LEVITATE = 54,
SPECIAL_MOVEUSE = 55,
MODIFY_GOSTRENGTH = 56,
SHOW_COORDINATE = 57,
RETRIEVE = 58,
ENTER_HOUSES = 59,
OPEN_NAMEDOORS = 60,
INVULNERABLE = 61,
UNLIMITED_MANA = 62,
KEEP_INVENTORY = 63,
ALL_SPELLS = 64,
UNLIMITED_CAPACITY = 65,
ZERO_CAPACITY = 66,
ATTACK_EVERYWHERE = 67,
NO_ATTACK = 68,
NO_RUNES = 69,
NO_LOGOUT_BLOCK = 70,
GAMEMASTER_OUTFIT = 71,
ILLUMINATE = 72,
CHANGE_PROFESSION = 73,
IGNORED_BY_MONSTERS = 74,
SHOW_KEYHOLE_NUMBERS = 75,
CREATE_OBJECTS = 76,
CREATE_MONEY = 77,
CREATE_MONSTERS = 78,
CHANGE_SKILLS = 79,
CLEANUP_FIELDS = 80,
NO_STATISTICS = 81,
};
// NOTE(fusion): This didn't exist int the decompiled version but is handy when
// accessing a creature's `Skills` table.
enum Skill: int {
SKILL_LEVEL = 0,
SKILL_MAGIC_LEVEL = 1,
SKILL_HITPOINTS = 2,
SKILL_MANA = 3,
SKILL_GO_STRENGTH = 4, // speed?
SKILL_CARRY_WEIGHT = 5, // capacity?
SKILL_FIST = 6,
SKILL_CLUB = 7,
SKILL_SWORD = 8,
SKILL_AXE = 9,
SKILL_DISTANCE = 10,
SKILL_SHIELDING = 11,
// 12?
SKILL_FISHING = 13,
SKILL_FED = 14,
SKILL_LIGHT = 15,
SKILL_ILLUSION = 16,
SKILL_POISON = 17,
SKILL_BURNING = 18,
SKILL_ENERGY = 19,
// 20?
// 21?
SKILL_SOUL = 22,
};
enum TalkMode: int {
TALK_SAY = 1,
TALK_WHISPER = 2,
TALK_YELL = 3,
TALK_PRIVATE_MESSAGE = 4,
TALK_CHANNEL_CALL = 5,
TALK_GAMEMASTER_REQUEST = 6,
TALK_GAMEMASTER_ANSWER = 7,
TALK_PLAYER_ANSWER = 8,
TALK_GAMEMASTER_BROADCAST = 9,
TALK_GAMEMASTER_CHANNELCALL = 10,
TALK_GAMEMASTER_MESSAGE = 11,
TALK_HIGHLIGHT_CHANNELCALL = 12,
TALK_ANONYMOUS_BROADCAST = 13,
TALK_ANONYMOUS_CHANNELCALL = 14,
TALK_ANONYMOUS_MESSAGE = 15,
TALK_ANIMAL_LOW = 16,
TALK_ANIMAL_LOUD = 17,
TALK_ADMIN_MESSAGE = 18,
TALK_EVENT_MESSAGE = 19,
TALK_LOGIN_MESSAGE = 20,
TALK_STATUS_MESSAGE = 21,
TALK_INFO_MESSAGE = 22,
TALK_FAILURE_MESSAGE = 23,
};
#endif //TIBIA_ENUMS_HH_

38
src/main.cc Normal file
View File

@ -0,0 +1,38 @@
#include "main.hh"
#if 0
// TODO(fusion): All globals should probably be packed into a `GlobalState`
// structure to minimize collisions and bugs.
struct GlobalState{
// world state
// creatures
// items
// etc...
};
GlobalState G = {};
#endif
// TODO(fusion): Probably current server time. Used for delays, timers, etc.
int ServerMilliseconds = 0;
void (*ErrorFunction)(const char*) = NULL;
void error(char *Text, ...){
char s[1024];
va_list ap;
va_start(ap, Text);
vsnprintf(s, sizeof(s) - 1, Text, ap);
va_end(ap);
if(ErrorFunction){
ErrorFunction(s);
}else{
printf("%s", s);
}
}
int main(int argc, char **argv){
return 0;
}

78
src/main.hh Normal file
View File

@ -0,0 +1,78 @@
#ifndef TIBIA_MAIN_HH_
#define TIBIA_MAIN_HH_ 1
#include <float.h>
#include <limits.h>
#include <stddef.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef uint8_t uint8;
typedef uint16_t uint16;
typedef uint32_t uint32;
typedef uintptr_t uintptr;
typedef size_t usize;
#define STATIC_ASSERT(expr) static_assert((expr), "static assertion failed: " #expr)
#define NARRAY(arr) (sizeof(arr) / sizeof(arr[0]))
#define ISPOW2(x) ((x) != 0 && ((x) & ((x) - 1)) == 0)
#define KB(x) ((usize)(x) << 10)
#define MB(x) ((usize)(x) << 20)
#define GB(x) ((usize)(x) << 30)
// TODO(fusion): We might not actually compile the output for the decompilation
// step but if we did, we'd need to make sure we're compiling in 32 bits mode.
STATIC_ASSERT(sizeof(bool) == 1);
STATIC_ASSERT(sizeof(int) == 4);
//STATIC_ASSERT(sizeof(void*) == 4);
#if !OS_WINDOWS && !OS_LINUX
# if defined(_WIN32)
# define OS_WINDOWS 1
# elif defined(__linux__) || defined(__gnu_linux__)
# define OS_LINUX 1
# else
# error "Operating system not supported."
# endif
#endif
#if !COMPILER_MSVC && !COMPILER_GCC && !COMPILER_CLANG
# if defined(_MSC_VER)
# define COMPILER_MSVC 1
# elif defined(__GNUC__)
# define COMPILER_GCC 1
# elif defined(__clang__)
# define COMPILER_CLANG 1
# endif
#endif
// NOTE(fusion): Compiler attributes.
#if COMPILER_GCC || COMPILER_CLANG
# define ATTR_FALLTHROUGH __attribute__((fallthrough))
# define ATTR_PRINTF(x, y) __attribute__((format(printf, x, y)))
#else
# define ATTR_FALLTHROUGH
# define ATTR_PRINTF(x, y)
#endif
#if COMPILER_MSVC
# define TRAP() __debugbreak();
#elif COMPILER_GCC || COMPILER_CLANG
# define TRAP() __builtin_trap()
#else
# define TRAP() abort()
#endif
#define ASSERT_ALWAYS(expr) if(!(expr)) { TRAP(); }
#if BUILD_DEBUG
# define ASSERT(expr) ASSERT_ALWAYS(expr)
#else
# define ASSERT(expr) ((void)(expr))
#endif
void error(char *Text, ...) ATTR_PRINTF(1, 2);
#endif //TIBIA_MAIN_HH_