From eaa3536c027a4f7c1b879b78ab0ecdefcdb4833d Mon Sep 17 00:00:00 2001 From: fusion32 Date: Sun, 8 Jun 2025 19:54:40 -0300 Subject: impl `TCreature::Move` which was absolute hell + some tidying up --- src/cr.hh | 4 ++ src/cract.cc | 184 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- src/crcombat.cc | 15 ++--- src/crmain.cc | 22 ++++--- src/enums.hh | 39 ++++++++++-- src/magic.cc | 16 ++--- src/objects.hh | 22 +++++-- src/operate.cc | 82 +++++++++++++++++++++++++ src/operate.hh | 10 +++ src/stubs.hh | 4 ++ 10 files changed, 361 insertions(+), 37 deletions(-) create mode 100644 src/operate.cc create mode 100644 src/operate.hh (limited to 'src') diff --git a/src/cr.hh b/src/cr.hh index 37a9f3d..2c60163 100644 --- a/src/cr.hh +++ b/src/cr.hh @@ -499,6 +499,10 @@ struct TCreature: TSkillBase { void Go(int DestX, int DestY, int DestZ); void Rotate(int Direction); void Rotate(TCreature *Target); + void Move(Object Obj, int DestX, int DestY, int DestZ, uint8 Count); + void Trade(Object Obj, uint32 TradePartner); + void Use(Object Obj1, Object Obj2, uint8 Dummy); + void Turn(Object Obj); void Attack(void); void Execute(void); uint32 CalculateDelay(void); diff --git a/src/cract.cc b/src/cract.cc index c4e85b6..8306ad5 100644 --- a/src/cract.cc +++ b/src/cract.cc @@ -1,4 +1,5 @@ #include "cr.hh" +#include "operate.hh" #include "stubs.hh" @@ -387,7 +388,7 @@ void TCreature::Go(int DestX, int DestY, int DestZ){ } Object Dest = GetMapContainer(DestX, DestY, DestZ); - Move(this->ID, this->CrObject, Dest, -1, false, NONE); + ::Move(this->ID, this->CrObject, Dest, -1, false, NONE); } void TCreature::Rotate(int Direction){ @@ -415,6 +416,185 @@ void TCreature::Rotate(TCreature *Target){ this->Rotate(Direction); } +void TCreature::Move(Object Obj, int DestX, int DestY, int DestZ, uint8 Count){ + // NOTE(fusion): What a disaster. + + if(!Obj.exists()){ + throw NOTACCESSIBLE; + } + + if(Obj == this->CrObject){ + this->Go(DestX, DestY, DestZ); + return; + } + + ObjectType ObjType = Obj.getObjectType(); + if(ObjType.isCreatureContainer()){ + this->Combat.DelayAttack(2000); + } + + int MoveCount = (int)Count; + if(ObjType.getFlag(CUMULATIVE)){ + int ObjAmount = (int)Obj.getAttribute(AMOUNT); + if(MoveCount > ObjAmount){ + MoveCount = ObjAmount; + } + } + + if(DestX == 0xFFFF){ // SPECIAL_COORDINATE ? + // NOTE(fusion): Any inventory slot. + if(DestY == 0){ + // NOTE(fusion): `CheckInventoryDestination` will throw if it's not + // possible to place the object on the chosen container. We want to + // find a slot that doesn't make it throw while giving priority to + // non hand or ammo slots. + for(int Position = INVENTORY_FIRST; + Position <= INVENTORY_LAST; + Position += 1){ + try{ + Object Con = GetBodyContainer(this->ID, Position); + CheckInventoryDestination(Obj, Con, false); + }catch(...){ + continue; + } + + DestY = Position; + if(Position != INVENTORY_RIGHTHAND + && Position != INVENTORY_LEFTHAND + && Position != INVENTORY_AMMO){ + break; + } + } + + // NOTE(fusion): No appropriate inventory slot was found so we now + // fallback to inventory containers. For whatever reason we don't + // give priority to the bag slot. + if(DestY == 0){ + for(int Position = INVENTORY_FIRST; + Position <= INVENTORY_LAST; + Position += 1){ + Object Con = GetBodyObject(this->ID, Position); + if(Con != NONE && Con.getObjectType().getFlag(CONTAINER)){ + try{ + CheckContainerDestination(Obj, Con); + }catch(RESULT r){ + continue; + } + + DestY = Position; + } + } + } + + if(DestY == 0){ + throw NOROOM; + } + } + + Object DestCon = NONE; + Object DestObj = NONE; + if(DestY >= INVENTORY_FIRST && DestY <= INVENTORY_LAST){ + DestCon = GetBodyContainer(this->ID, DestY); + DestObj = GetBodyObject(this->ID, DestY); + }else if(DestY >= 64 && DestY < 80){ + DestCon = GetBodyContainer(this->ID, DestY); + if(DestZ < 254){ + // TODO(fusion): The last argument to `GetObject` is the object + // type we expect to find and it seems that the type id of a map + // container (which is 0) can be used as a wildcard for any object + // it finds. + DestObj = GetObject(this->ID, DestX, DestY, DestZ, DestZ, 0); + }else if(DestZ == 254){ + if(DestCon == NONE){ + throw NOTACCESSIBLE; + } + DestCon = DestCon.getContainer(); + } + }else{ + error("TCreature::Move: Ungültiger Containercode %d.\n", DestY); + throw ERROR; + } + + if(DestObj != NONE){ + ObjectType DestObjType = DestObj.getObjectType(); + if(DestObjType.getFlag(CONTAINER)){ + DestCon = DestObj; + DestObj = NONE; + }else if(DestObjType.getFlag(CUMULATIVE) && DestObjType == ObjType){ + int DestAmount = (int)DestObj.getAttribute(AMOUNT); + int MergeCount = MoveCount; + if((DestAmount + MergeCount) > 100){ + MergeCount = 100 - DestAmount; + } + + if(MergeCount > 0){ + try{ + ::Merge(this->ID, Obj, DestObj, MergeCount, NONE); + MoveCount -= MergeCount; + if(MoveCount <= 0){ + return; + } + }catch(RESULT r){ + if(r == TOOHEAVY){ + throw; + } + } + } + + DestObj = NONE; + } + } + + if(DestCon == NONE){ + throw OUTOFRANGE; + } + + try{ + // TODO(fusion): Find out what is this last parameter to `Move`. + ::Move(this->ID, Obj, DestCon, MoveCount, false, DestObj); + }catch(RESULT r){ + // NOTE(fusion): Attempt to exchange inventory items. + if(DestY < 64 && DestObj != NONE + && (r == NOROOM + || r == HANDSNOTFREE + || r == HANDBLOCKED + || r == ONEWEAPONONLY)){ + Object ObjCon = Obj.getContainer(); + ::Move(this->ID, DestObj, ObjCon, -1, false, NONE); + ::Move(this->ID, Obj, DestCon, MoveCount, false, NONE); + }else{ + throw; + } + } + }else{ + Object Dest = GetMapContainer(DestX, DestY, DestZ); + if(!Dest.exists()){ + throw ERROR; + } + + if(ObjType.getFlag(HANG) + && (CoordinateFlag(DestX, DestY, DestZ, HOOKSOUTH) + || CoordinateFlag(DestY, DestY, DestZ, HOOKEAST)) + && !ObjectInRange(this->ID, Dest, 1)){ + if(this->ToDoClear() && this->Type == PLAYER){ + SendSnapback(this->Connection); + } + + // NOTE(fusion): Pick up object if it's not in our inventory. + if(GetObjectCreatureID(Obj) != this->ID){ + this->ToDoMove(Obj, 0xFFFF, 0, 0, 1); + } + + // NOTE(fusion): Walk to destination then try again. + this->ToDoGo(DestX, DestY, DestZ, false, INT_MAX); + this->ToDoMove(Obj, DestX, DestY, DestZ, 1); + this->ToDoStart(); + }else{ + ::Move(this->ID, Obj, Dest, MoveCount, false, NONE); + } + } +} + void TCreature::Attack(void){ this->Combat.Attack(); } @@ -460,7 +640,7 @@ void TCreature::Execute(void){ } case TDMove:{ - this->Move(TD.Move.Obj, TD.Move.x, TD.Move.y, TD.Move.z, TD.Move.Count); + this->Move(TD.Move.Obj, TD.Move.x, TD.Move.y, TD.Move.z, (uint8)TD.Move.Count); break; } diff --git a/src/crcombat.cc b/src/crcombat.cc index dd732e3..25fff0e 100644 --- a/src/crcombat.cc +++ b/src/crcombat.cc @@ -48,10 +48,10 @@ void TCombat::GetWeapon(void){ return; } - // TODO(fusion): We're iterating over left and right hand slots. Make - // it clearer perhaps. - for(int Hand = 5; Hand <= 6; Hand += 1){ - Object Obj = GetBodyObject(Master->ID, Hand); + for(int Position = INVENTORY_HAND_FIRST; + Position <= INVENTORY_HAND_LAST; + Position += 1){ + Object Obj = GetBodyObject(Master->ID, Position); if(Obj == NONE){ continue; } @@ -111,7 +111,7 @@ void TCombat::GetAmmo(void){ } // TODO(fusion): Check if `Master` is NULL? - Object Ammo = GetBodyObject(this->Master->ID, 10); + Object Ammo = GetBodyObject(this->Master->ID, INVENTORY_AMMO); this->Ammo = NONE; if(Ammo != NONE){ ObjectType AmmoType = Ammo.getObjectType(); @@ -283,10 +283,11 @@ int TCombat::GetDefendDamage(void){ } int TCombat::GetArmorStrength(void){ - // TODO(fusion): We're iterating over inventory slots. Make it clearer? int Armor = 0; TCreature *Master = this->Master; - for(int Position = 1; Position <= 10; Position += 1){ + for(int Position = INVENTORY_FIRST; + Position <= INVENTORY_LAST; + Position += 1){ Object Obj = GetBodyObject(Master->ID, Position); if(Obj.exists()){ ObjectType ObjType = Obj.getObjectType(); diff --git a/src/crmain.cc b/src/crmain.cc index 5d0f2f3..00cd3e2 100644 --- a/src/crmain.cc +++ b/src/crmain.cc @@ -226,7 +226,9 @@ TCreature::~TCreature(void){ } if(this->LoseInventory != 0){ // LOSE_INVENTORY_NONE ? - for(int Position = 1; Position <= 10; Position += 1){ + for(int Position = INVENTORY_FIRST; + Position <= INVENTORY_LAST; + Position += 1){ Object Item = GetBodyObject(this->ID, Position); if(Item == NONE){ continue; @@ -238,7 +240,7 @@ TCreature::~TCreature(void){ continue; } - Move(0, Item, Corpse, -1, false, NONE); + ::Move(0, Item, Corpse, -1, false, NONE); } } @@ -487,10 +489,8 @@ int TCreature::Damage(TCreature *Attacker, int Damage, int DamageType){ Damage = 0; } - // NOTE(fusion): We're iterating over the victim's inventory to apply - // damage reductions, while the damage is greater than zero. - for(int Position = 1; - Position <= 10 && Damage > 0; + for(int Position = INVENTORY_FIRST; + Position <= INVENTORY_LAST && Damage > 0; Position += 1){ Object Obj = GetBodyObject(this->ID, Position); if(Obj == NONE){ @@ -739,7 +739,9 @@ int TCreature::Damage(TCreature *Attacker, int Damage, int DamageType){ if(Damage == HitPoints){ if(this->Type == PLAYER){ - for(int Position = 1; Position <= 10; Position += 1){ + for(int Position = INVENTORY_FIRST; + Position <= INVENTORY_LAST; + Position += 1){ Object Obj = GetBodyObject(this->ID, Position); if(Obj == NONE){ continue; @@ -2026,11 +2028,11 @@ void ProcessMonsterRaids(void){ } TCreature *Creature = Spawned[random(0, NumSpawned - 1)]; - Object Bag = GetBodyObject(Creature->ID, 3); // BAG ? + Object Bag = GetBodyObject(Creature->ID, INVENTORY_BAG); if(Bag == NONE){ try{ - Bag = Create(GetBodyContainer(Creature->ID, 3), - GetSpecialObject(INVENTORY_CONTAINER), + Bag = Create(GetBodyContainer(Creature->ID, INVENTORY_BAG), + GetSpecialObject(DEFAULT_CONTAINER), 0); }catch(RESULT r){ error("ProcessMonsterRaids: Exception %d bei Rasse %d" diff --git a/src/enums.hh b/src/enums.hh index 2505618..6efcf61 100644 --- a/src/enums.hh +++ b/src/enums.hh @@ -221,6 +221,29 @@ enum INSTANCEATTRIBUTE: int { REMAININGUSES = 17, }; +// NOTE(fusion): Not in debug symbols. +enum InventorySlot: int { + INVENTORY_ANY = 0, + INVENTORY_HEAD = 1, + INVENTORY_NECK = 2, + INVENTORY_BAG = 3, + INVENTORY_TORSO = 4, + INVENTORY_RIGHTHAND = 5, + INVENTORY_LEFTHAND = 6, + INVENTORY_LEGS = 7, + INVENTORY_FEET = 8, + INVENTORY_FINGER = 9, + INVENTORY_AMMO = 10, + + // NOTE(fusion): For iterating over inventory slots. + INVENTORY_FIRST = INVENTORY_HEAD, + INVENTORY_LAST = INVENTORY_AMMO, + + // NOTE(fusion): For iterating over hand slots. + INVENTORY_HAND_FIRST = INVENTORY_RIGHTHAND, + INVENTORY_HAND_LAST = INVENTORY_LEFTHAND, +}; + enum KNOWNCREATURESTATE: int { KNOWNCREATURE_FREE = 0, KNOWNCREATURE_UPTODATE = 1, @@ -435,16 +458,20 @@ enum Skill: int { SKILL_SOUL = 22, }; +// IMPORTANT(fusion): All `DEFAULT_*` values here were renamed from `INVENTORY_*` +// to avoid collisions with `InventorySlot`. I could also have chosen other prefix +// for inventory slots but I feel we'd be wasting them here where their meaning +// were also not clear. 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, + DEFAULT_RIGHTHAND = 10, + DEFAULT_LEFTHAND = 11, + DEFAULT_BODY_MALE = 12, + DEFAULT_BODY_FEMALE = 13, + DEFAULT_CONTAINER = 14, + DEFAULT_FOOD = 15, DEPOT_LOCKER = 20, DEPOT_CHEST = 21, PARCEL_NEW = 22, diff --git a/src/magic.cc b/src/magic.cc index 1caf83b..a222b3a 100644 --- a/src/magic.cc +++ b/src/magic.cc @@ -2409,7 +2409,9 @@ void CancelInvisibility(TCreature *Actor, Object Target, int ManaPoints, int Sou // it on pvp enforced worlds. // TODO(fusion): This is probably an inlined function. if(WorldType == PVP_ENFORCED){ - for(int Position = 1; Position <= 10; Position += 1){ + for(int Position = INVENTORY_FIRST; + Position <= INVENTORY_LAST; + Position += 1){ Object Item = GetBodyObject(Victim->ID, Position); if(Item == NONE){ continue; @@ -2516,8 +2518,8 @@ void ChangeData(TCreature *Actor, const char *Param){ return; } - Object RightHand = GetBodyObject(Actor->ID, 5); // RIGHTHAND ? - Object LeftHand = GetBodyObject(Actor->ID, 6); // LEFTHAND ? + Object RightHand = GetBodyObject(Actor->ID, INVENTORY_RIGHTHAND); + Object LeftHand = GetBodyObject(Actor->ID, INVENTORY_LEFTHAND); if(RightHand != NONE && LeftHand != NONE){ SendMessage(Actor->Connection, TALK_FAILURE_MESSAGE, "First drop one object."); return; @@ -2578,9 +2580,9 @@ void EnchantObject(TCreature *Actor, int ManaPoints, int SoulPoints, ObjectType throw ERROR; } - Object Obj = GetBodyObject(Actor->ID, 5); // RIGHTHAND ? + Object Obj = GetBodyObject(Actor->ID, INVENTORY_RIGHTHAND); if(Obj == NONE || Obj.getObjectType() != OldType){ - Obj = GetBodyObject(Actor->ID, 6); // LEFTHAND ? + Obj = GetBodyObject(Actor->ID, INVENTORY_LEFTHAND); if(Obj == NONE || Obj.getObjectType() != OldType){ throw MAGICITEM; } @@ -3670,7 +3672,7 @@ static void RuneSpell(uint32 CreatureID, int SpellNr){ bool RuneCreated = false; ObjectType BlankType = GetSpecialObject(RUNE_BLANK); - Object RightHand = GetBodyObject(Actor->ID, 5); // RIGHTHAND + Object RightHand = GetBodyObject(Actor->ID, INVENTORY_RIGHTHAND); if(RightHand.exists() && RightHand.getObjectType() == BlankType){ CheckMana(Actor, ManaPoints, SoulPoints, 1000); ObjectType RuneType = GetNewObjectType(RuneGr, RuneNr); @@ -3678,7 +3680,7 @@ static void RuneSpell(uint32 CreatureID, int SpellNr){ RuneCreated = true; } - Object LeftHand = GetBodyObject(Actor->ID, 6); // LEFTHAND + Object LeftHand = GetBodyObject(Actor->ID, INVENTORY_LEFTHAND); if(LeftHand.exists() && LeftHand.getObjectType() == BlankType){ // TODO(fusion): Ughh... I'm not sure why we're trying to cast the spell // twice but we need to make so errors from the second cast are only diff --git a/src/objects.hh b/src/objects.hh index 39fa8aa..48dbedf 100644 --- a/src/objects.hh +++ b/src/objects.hh @@ -8,7 +8,7 @@ enum : int{ TYPEID_MAP_CONTAINER = 0, TYPEID_HEAD_CONTAINER = 1, TYPEID_NECK_CONTAINER = 2, - TYPEID_BACKPACK_CONTAINER = 3, + TYPEID_BAG_CONTAINER = 3, TYPEID_TORSO_CONTAINER = 4, TYPEID_RIGHTHAND_CONTAINER = 5, TYPEID_LEFTHAND_CONTAINER = 6, @@ -29,14 +29,14 @@ struct ObjectType { const char *getName(int Count); const char *getDescription(void); - bool isMapContainer(void) const { + bool isMapContainer(void){ return this->TypeID == TYPEID_MAP_CONTAINER; } - bool isBodyContainer(void) const { + bool isBodyContainer(void){ return this->TypeID == TYPEID_HEAD_CONTAINER || this->TypeID == TYPEID_NECK_CONTAINER - || this->TypeID == TYPEID_BACKPACK_CONTAINER + || this->TypeID == TYPEID_BAG_CONTAINER || this->TypeID == TYPEID_TORSO_CONTAINER || this->TypeID == TYPEID_RIGHTHAND_CONTAINER || this->TypeID == TYPEID_LEFTHAND_CONTAINER @@ -46,10 +46,22 @@ struct ObjectType { || this->TypeID == TYPEID_AMMO_CONTAINER; } - bool isCreatureContainer(void) const { + bool isCreatureContainer(void){ return this->TypeID == TYPEID_CREATURE_CONTAINER; } + bool isTwoHanded(void){ + return this->getFlag(CLOTHES) + && this->getAttribute(BODYPOSITION) == 0; + } + + bool isWeapon(void){ + return this->getFlag(WEAPON) + || this->getFlag(BOW) + || this->getFlag(THROW) + || this->getFlag(WAND); + } + bool operator==(const ObjectType &Other) const { return this->TypeID == Other.TypeID; } diff --git a/src/operate.cc b/src/operate.cc new file mode 100644 index 0000000..ef1ba01 --- /dev/null +++ b/src/operate.cc @@ -0,0 +1,82 @@ +#include "operate.hh" + +#include "stubs.hh" + +// TODO(fusion): This could have been a simple return value. +void CheckContainerDestination(Object Obj, Object Con){ + Object Help = Con; + while(Help != NONE && !Help.getObjectType().isMapContainer()){ + if(Help == Obj){ + throw CROSSREFERENCE; + } + Help = Help.getContainer(); + } + + ObjectType ConType = Con.getObjectType(); + if(!ConType.getFlag(CHEST)){ + int ConObjects = CountObjectsInContainer(Con); + int ConCapacity = (int)ConType.getAttribute(CAPACITY); + if(ConObjects >= ConCapacity){ + throw CONTAINERFULL; + } + } +} + +void CheckInventoryDestination(Object Obj, Object Con, bool Split){ + ObjectType ObjType = Obj.getObjectType(); + int ConPosition = GetObjectBodyPosition(Con); + bool HandContainer = ConPosition == INVENTORY_RIGHTHAND + || ConPosition == INVENTORY_LEFTHAND; + + if(!HandContainer && ConPosition != INVENTORY_AMMO){ + if(!ObjType.getFlag(CLOTHES)){ + throw WRONGPOSITION; + } + + int ObjPosition = (int)ObjType.getAttribute(BODYPOSITION); + if(ObjPosition == 0){ + throw WRONGPOSITION2; + }else if(ObjPosition != ConPosition){ + throw WRONGCLOTHES; + } + } + + uint32 CreatureID = GetObjectCreatureID(Con); + if(ObjType.isTwoHanded() && HandContainer){ + Object RightHand = GetBodyObject(CreatureID, INVENTORY_RIGHTHAND); + if(RightHand != NONE && RightHand != Obj){ + throw HANDSNOTFREE; + } + + Object LeftHand = GetBodyObject(CreatureID, INVENTORY_LEFTHAND); + if(LeftHand != NONE && LeftHand != Obj){ + throw HANDSNOTFREE; + } + + return; + } + + if(GetBodyObject(CreatureID, ConPosition) != NONE){ + throw NOROOM; + } + + if(HandContainer){ + for(int Position = INVENTORY_HAND_FIRST; + Position <= INVENTORY_HAND_LAST; + Position += 1){ + Object Other = GetBodyObject(CreatureID, Position); + if(Other != NONE){ + ObjectType OtherType = Other.getObjectType(); + if(OtherType.isTwoHanded()){ + throw HANDBLOCKED; + } + + if(Split || Other != Obj){ + if(OtherType.isWeapon() && ObjType.isWeapon()){ + throw ONEWEAPONONLY; + } + } + } + } + } +} diff --git a/src/operate.hh b/src/operate.hh new file mode 100644 index 0000000..6270ec7 --- /dev/null +++ b/src/operate.hh @@ -0,0 +1,10 @@ +#ifndef TIBIA_OPERATE_HH_ +#define TIBIA_OPERATE_HH_ 1 + +#include "common.hh" +#include "map.hh" + +void CheckContainerDestination(Object Obj, Object Con); +void CheckInventoryDestination(Object Obj, Object Con, bool Split); + +#endif //TIBIA_OPERATE_HH_ diff --git a/src/stubs.hh b/src/stubs.hh index f2f1f0b..867d813 100644 --- a/src/stubs.hh +++ b/src/stubs.hh @@ -27,6 +27,7 @@ extern void CleanHouseField(int x, int y, int z); extern void ConvinceMonster(TCreature *Master, TCreature *Slave); extern void ChallengeMonster(TCreature *Challenger, TCreature *Monster); extern int CountInventoryObjects(uint32 CreatureID, ObjectType Type, uint32 Value); +extern int CountObjectsInContainer(Object Con); extern Object Create(Object Con, ObjectType Type, uint32 Value); extern Object CreateAtCreature(uint32 CreatureID, ObjectType Type, uint32 Value); extern TCreature *CreateMonster(int Race, int x, int y, int z, int Home, uint32 Master, bool ShowEffect); @@ -36,12 +37,14 @@ extern void Delete(Object Obj, int Count); extern bool FieldPossible(int x, int y, int z, int FieldType); extern Object GetBodyObject(uint32 CreatureID, int Position); extern Object GetBodyContainer(uint32 CreatureID, int Position); +extern int GetObjectBodyPosition(Object Obj); extern void GetExitPosition(uint16 HouseID, int *x, int *y, int *z); extern TConnection *GetFirstConnection(void); extern int GetHeight(int x, int y, int z); extern TConnection *GetNextConnection(void); extern const char *GetName(Object Obj); extern Object GetObject(uint32 CreatureID, int x, int y, int z, int RNum, ObjectType Type); +extern uint32 GetObjectCreatureID(Object Obj); extern TPlayer *GetPlayer(uint32 CreatureID); extern void GraphicalEffect(int x, int y, int z, int Type); extern void GraphicalEffect(Object Obj, int Type); @@ -55,6 +58,7 @@ extern void LoadMonsterRaid(const char *FileName, int Start, extern void Log(const char *ProtocolName, const char *Text, ...) ATTR_PRINTF(2, 3); extern void LogoutAllPlayers(void); extern void Missile(Object Start, Object Dest, int Type); +extern void Merge(uint32 CreatureID, Object Obj, Object Dest, int Count, Object Ignore); extern void Move(uint32 CreatureID, Object Obj, Object Con, int Count, bool NoMerge, Object Ignore); extern void NetLoadCheck(void); extern void NetLoadSummary(void); -- cgit v1.2.3