aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorfusion32 <marcopuzziello@gmail.com>2025-06-08 19:54:40 -0300
committerfusion32 <marcopuzziello@gmail.com>2025-06-08 19:54:40 -0300
commiteaa3536c027a4f7c1b879b78ab0ecdefcdb4833d (patch)
tree03f851e558656f8fc36fdb982c28fbc09cef8dac /src
parentab9f606371a1df23e9cacbffa0811db7a9e50100 (diff)
downloadgame-eaa3536c027a4f7c1b879b78ab0ecdefcdb4833d.tar.gz
game-eaa3536c027a4f7c1b879b78ab0ecdefcdb4833d.zip
impl `TCreature::Move` which was absolute hell + some tidying up
Diffstat (limited to 'src')
-rw-r--r--src/cr.hh4
-rw-r--r--src/cract.cc184
-rw-r--r--src/crcombat.cc15
-rw-r--r--src/crmain.cc22
-rw-r--r--src/enums.hh39
-rw-r--r--src/magic.cc16
-rw-r--r--src/objects.hh22
-rw-r--r--src/operate.cc82
-rw-r--r--src/operate.hh10
-rw-r--r--src/stubs.hh4
10 files changed, 361 insertions, 37 deletions
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);