diff options
| author | fusion32 <marcopuzziello@gmail.com> | 2025-06-05 02:00:10 -0300 |
|---|---|---|
| committer | fusion32 <marcopuzziello@gmail.com> | 2025-06-05 02:00:10 -0300 |
| commit | 3c8bcba6b540f5cb84572994d9ec2426dd348a93 (patch) | |
| tree | 54af53f02d549951446d5a87845b42d4ee777559 /src | |
| parent | 2c91caa1dd4ea53dfeb63751d04170d9a9a61474 (diff) | |
| download | game-3c8bcba6b540f5cb84572994d9ec2426dd348a93.tar.gz game-3c8bcba6b540f5cb84572994d9ec2426dd348a93.zip | |
finish `magic.cc`
Diffstat (limited to 'src')
| -rw-r--r-- | src/crcombat.cc | 15 | ||||
| -rw-r--r-- | src/creature.cc | 4 | ||||
| -rw-r--r-- | src/enums.hh | 17 | ||||
| -rw-r--r-- | src/magic.cc | 1235 | ||||
| -rw-r--r-- | src/magic.hh | 126 | ||||
| -rw-r--r-- | src/main.cc | 4 | ||||
| -rw-r--r-- | src/stubs.hh | 6 |
7 files changed, 1306 insertions, 101 deletions
diff --git a/src/crcombat.cc b/src/crcombat.cc index 2843cd3..e1bf72f 100644 --- a/src/crcombat.cc +++ b/src/crcombat.cc @@ -715,14 +715,13 @@ void TCombat::WandAttack(TCreature *Target){ int AttackStrength = WandType.getAttribute(WANDATTACKSTRENGTH); int AttackVariation = (int)WandType.getAttribute(WANDATTACKVARIATION); - // NOTE(fusion): Oof... try{ CheckMana(Master, ManaConsumption, 0, 0); - }catch(RESULT err){ - if(err == NOTENOUGHMANA){ + }catch(RESULT r){ + if(r == NOTENOUGHMANA){ throw OUTOFAMMO; }else{ - throw err; + throw r; } } @@ -836,7 +835,7 @@ void TCombat::DistanceAttack(TCreature *Target){ int Damage = ComputeDamage(Master, 0, EffectStrength, EffectStrength); TDamageImpact Impact(Master, DAMAGE_PHYSICAL, Damage, false); CircleShapeSpell(Master, DropX, DropY, DropZ, -1, - ANIMATION_NONE, 2, &Impact, EFFECT_BURST_ARROW); + ANIMATION_NONE, 2, &Impact, EFFECT_FIRE); } try{ @@ -845,10 +844,10 @@ void TCombat::DistanceAttack(TCreature *Target){ }else{ Move(0, this->Ammo, DropCon, 1, false, NONE); } - }catch(RESULT err){ - if(err != DESTROYED){ + }catch(RESULT r){ + if(r != DESTROYED){ error("TCombat::RangeAttack: Konnte Ammo nicht verschieben/löschen" - " (Exception %d, [%d,%d,%d].\n", err, DropX, DropY, DropZ); + " (Exception %d, [%d,%d,%d].\n", r, DropX, DropY, DropZ); } } diff --git a/src/creature.cc b/src/creature.cc index d1cb7bf..9eadc6e 100644 --- a/src/creature.cc +++ b/src/creature.cc @@ -165,9 +165,9 @@ int TCreature::Damage(TCreature *Attacker, int Damage, int DamageType){ ObjectType WearOutType = ObjType.getAttribute(WEAROUTTARGET); Change(Obj, WearOutType, 0); } - }catch(RESULT err){ + }catch(RESULT r){ error("TCreature::Damage: Exception %d beim Abnutzen von Objekt %d.\n", - err, ObjType.TypeID); + r, ObjType.TypeID); } } }else if(ObjType.getFlag(PROTECTION) && !ObjType.getFlag(CLOTHES)){ diff --git a/src/enums.hh b/src/enums.hh index 7b2fc68..21194dd 100644 --- a/src/enums.hh +++ b/src/enums.hh @@ -18,6 +18,7 @@ enum AnimationType: int { ANIMATION_THROWING_STAR = 8, ANIMATION_THROWING_KNIFE = 9, ANIMATION_SMALL_STONE = 10, + ANIMATION_DEATH = 11, ANIMATION_SNOWBALL = 13, ANIMATION_POWER_BOLT = 14, ANIMATION_POISON = 15, @@ -81,10 +82,9 @@ enum DamageType: int { DAMAGE_POISON = 0x0002, DAMAGE_FIRE = 0x0004, DAMAGE_ENERGY = 0x0008, - //DAMAGE_DEATH ? = 0x0010 - DAMAGE_POISON_PERIODIC = 0x0020, - DAMAGE_FIRE_PERIODIC = 0x0040, - DAMAGE_ENERGY_PERIODIC = 0x0080, + DAMAGE_POISON_PERIODIC = 0x0020, // Better name? + DAMAGE_FIRE_PERIODIC = 0x0040, // Better name? + DAMAGE_ENERGY_PERIODIC = 0x0080, // Better name? DAMAGE_LIFEDRAIN = 0x0100, DAMAGE_MANADRAIN = 0x0200, }; @@ -109,16 +109,19 @@ enum EffectType: int { EFFECT_MANA_HIT = 2, EFFECT_POFF = 3, EFFECT_BLOCK_HIT = 4, - EFFECT_BURST_ARROW = 7, + EFFECT_FIRE_EXPLOSION = 5, + EFFECT_FIRE_BURST = 6, // Not sure about this one. + EFFECT_FIRE = 7, EFFECT_POISON = 9, EFFECT_BONE_HIT = 10, - EFFECT_TELEPORT = 11, + EFFECT_ENERGY = 11, EFFECT_ENERGY_HIT = 12, EFFECT_MAGIC_BLUE = 13, EFFECT_MAGIC_RED = 14, EFFECT_MAGIC_GREEN = 15, EFFECT_FIRE_HIT = 16, EFFECT_POISON_HIT = 17, + EFFECT_DEATH = 18, }; enum FLAG: int { @@ -232,6 +235,8 @@ enum LiquidType: int { LIQUID_MUD = 4, LIQUID_BLOOD = 5, LIQUID_SLIME = 6, + LIQUID_MANA = 10, + LIQUID_LIFE = 11, LIQUID_LEMONADE = 12, }; diff --git a/src/magic.cc b/src/magic.cc index 3db1456..b3d70b1 100644 --- a/src/magic.cc +++ b/src/magic.cc @@ -7,6 +7,26 @@ #include "stubs.hh" #include <fstream> +#include <sstream> + +struct TCircle { + int x[32]; + int y[32]; + int Count; +}; + +struct TSpellList { + uint8 Syllable[10]; + uint8 RuneGr; + uint8 RuneNr; + const char *Comment; + uint16 Level; + uint16 RuneLevel; + uint16 Flags; + int Mana; + int SoulPoints; + int Amount; +}; static TSpellList SpellList[256]; static TCircle Circle[10]; @@ -593,44 +613,6 @@ void AngleShapeSpell(TCreature *Actor, int Angle, int Range, TImpact *Impact, in } } -// Spell Casting -// ============================================================================= -int GetDirection(int dx, int dy){ - // TODO(fusion): This function originally returned directions different from - // the ones used by creatures. I've converted it to use the same values which - // are also defined in `enums.hh` for simplicity. - int Result; - if(dx == 0){ - if(dy < 0){ - Result = DIRECTION_NORTH; - }else if(dy > 0){ - Result = DIRECTION_SOUTH; - }else{ - Result = DIRECTION_INVALID; - } - }else{ - // NOTE(fusion): This function uses the approximate tangent value, avoiding - // floating point calculations, for whatever reason. The tangent is unique - // and odd in the interval (-PI/2, +PI/2). We also need to recall that the - // Y-axis is inverted in Tibia, so we need to negate `dy`. - constexpr int Tangent_67_5 = 618; // => 618 / 256 ~ 2.41 ~ tan(67.5 deg) - constexpr int Tangent_22_5 = 106; // => 106 / 256 ~ 0.41 ~ tan(22.5 deg) - int Tangent = (-dy * 256) / dx; // => (dy * 256) / dx ~ (dy / dx) * 256 - if(Tangent >= Tangent_67_5){ - Result = DIRECTION_NORTH; - }else if(Tangent >= Tangent_22_5){ - Result = (dx < 0) ? DIRECTION_NORTHWEST : DIRECTION_NORTHEAST; - }else if(Tangent >= -Tangent_22_5){ - Result = (dx < 0) ? DIRECTION_WEST : DIRECTION_EAST; - }else if(Tangent >= -Tangent_67_5){ - Result = (dx < 0) ? DIRECTION_SOUTHWEST : DIRECTION_SOUTHEAST; - }else{ - Result = DIRECTION_SOUTH; - } - } - return Result; -} - void CheckSpellbook(TCreature *Actor, int SpellNr){ if(Actor == NULL){ error("CheckSpellbook: Übergebene Kreatur existiert nicht.\n"); @@ -908,6 +890,42 @@ void Combat(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, INT_MAX, Animation, 0, &Impact, Effect); } +int GetDirection(int dx, int dy){ + // TODO(fusion): This function originally returned directions different from + // the ones used by creatures. I've converted it to use the same values which + // are also defined in `enums.hh` for simplicity. + int Result; + if(dx == 0){ + if(dy < 0){ + Result = DIRECTION_NORTH; + }else if(dy > 0){ + Result = DIRECTION_SOUTH; + }else{ + Result = DIRECTION_INVALID; + } + }else{ + // NOTE(fusion): This function uses the approximate tangent value, avoiding + // floating point calculations, for whatever reason. The tangent is unique + // and odd in the interval (-PI/2, +PI/2). We also need to recall that the + // Y-axis is inverted in Tibia, so we need to negate `dy`. + constexpr int Tangent_67_5 = 618; // => 618 / 256 ~ 2.41 ~ tan(67.5 deg) + constexpr int Tangent_22_5 = 106; // => 106 / 256 ~ 0.41 ~ tan(22.5 deg) + int Tangent = (-dy * 256) / dx; // => (dy * 256) / dx ~ (dy / dx) * 256 + if(Tangent >= Tangent_67_5){ + Result = DIRECTION_NORTH; + }else if(Tangent >= Tangent_22_5){ + Result = (dx < 0) ? DIRECTION_NORTHWEST : DIRECTION_NORTHEAST; + }else if(Tangent >= -Tangent_22_5){ + Result = (dx < 0) ? DIRECTION_WEST : DIRECTION_EAST; + }else if(Tangent >= -Tangent_67_5){ + Result = (dx < 0) ? DIRECTION_SOUTHWEST : DIRECTION_SOUTHEAST; + }else{ + Result = DIRECTION_SOUTH; + } + } + return Result; +} + // Spell Functions // ============================================================================= void KillAllMonsters(TCreature *Actor, int Effect, int Radius){ @@ -966,6 +984,72 @@ void KillAllMonsters(TCreature *Actor, int Effect, int Radius){ } } +void CreateField(int x, int y, int z, int FieldType, uint32 Owner, bool Peaceful){ + if(!FieldPossible(x, y, z, FieldType)){ + return; + } + + SPECIALMEANING Meaning; + switch(FieldType){ + case FIELD_TYPE_FIRE:{ + if(Peaceful){ + Meaning = MAGICFIELD_FIRE_HARMLESS; + }else{ + Meaning = MAGICFIELD_FIRE_DANGEROUS; + } + break; + } + + case FIELD_TYPE_POISON:{ + if(Peaceful){ + Meaning = MAGICFIELD_POISON_HARMLESS; + }else{ + Meaning = MAGICFIELD_POISON_DANGEROUS; + } + break; + } + + case FIELD_TYPE_ENERGY:{ + if(Peaceful){ + Meaning = MAGICFIELD_ENERGY_HARMLESS; + }else{ + Meaning = MAGICFIELD_ENERGY_DANGEROUS; + } + break; + } + + case FIELD_TYPE_MAGICWALL:{ + Meaning = MAGICFIELD_MAGICWALL; + break; + } + + case FIELD_TYPE_WILDGROWTH:{ + Meaning = MAGICFIELD_RUSHWOOD; + break; + } + + default:{ + error("CreateField: Ungültiger Feldtyp %d.\n", FieldType); + throw ERROR; + } + } + + // NOTE(fusion): Delete other magic fields? + Object Obj = GetFirstObject(x, y, z); + while(Obj != NONE){ + Object Next = Obj.getNextObject(); + if(Obj.getObjectType().getFlag(MAGICFIELD)){ + Delete(Obj, -1); + } + Obj = Next; + } + + // NOTE(fusion): Create field, at last. + Create(GetMapContainer(x, y, z), + GetSpecialObject(Meaning), + Owner); +} + void CreateField(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, int FieldType){ if(Actor == NULL){ error("CreateField: Ungültige Kreatur übergeben.\n"); @@ -993,7 +1077,7 @@ void CreateField(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints CheckMana(Actor, ManaPoints, SoulPoints, Delay); int Animation = ANIMATION_ENERGY; - if(FieldType == 1){ + if(FieldType == FIELD_TYPE_FIRE){ Animation = ANIMATION_FIRE; } Missile(Actor->CrObject, Target, Animation); @@ -1001,8 +1085,9 @@ void CreateField(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints bool Peaceful = (WorldType == NON_PVP && Actor->IsPeaceful()); CreateField(TargetX, TargetY, TargetZ, FieldType, Actor->ID, Peaceful); - // TODO(fusion): Probably damaging fields? - if(FieldType == 1 || FieldType == 2 || FieldType == 3){ + if(FieldType == FIELD_TYPE_FIRE + || FieldType == FIELD_TYPE_POISON + || FieldType == FIELD_TYPE_ENERGY){ Actor->BlockLogout(60, true); } } @@ -1058,7 +1143,7 @@ void MassCreateField(TCreature *Actor, Object Target, if(Actor->posx != TargetX || Actor->posy != TargetY || Actor->posz != TargetZ){ int Animation = ANIMATION_ENERGY; - if(FieldType == 1){ + if(FieldType == FIELD_TYPE_FIRE){ Animation = ANIMATION_FIRE; } Missile(Actor->CrObject, Target, Animation); @@ -1089,7 +1174,9 @@ void MassCreateField(TCreature *Actor, Object Target, } } - if(FieldType == 1 || FieldType == 2 || FieldType == 3){ + if(FieldType == FIELD_TYPE_FIRE + || FieldType == FIELD_TYPE_POISON + || FieldType == FIELD_TYPE_ENERGY){ Actor->BlockLogout(60, true); } } @@ -1121,7 +1208,7 @@ void CreateFieldWall(TCreature *Actor, Object Target, if(ActorX != TargetX || ActorY != TargetY || ActorZ != TargetZ){ int Animation = ANIMATION_ENERGY; - if(FieldType == 1){ + if(FieldType == FIELD_TYPE_FIRE){ Animation = ANIMATION_FIRE; } Missile(Actor->CrObject, Target, Animation); @@ -1216,7 +1303,9 @@ void CreateFieldWall(TCreature *Actor, Object Target, } } - if(FieldType == 1 || FieldType == 2 || FieldType == 3){ + if(FieldType == FIELD_TYPE_FIRE + || FieldType == FIELD_TYPE_POISON + || FieldType == FIELD_TYPE_ENERGY){ Actor->BlockLogout(60, true); } } @@ -1321,7 +1410,7 @@ void CleanupField(TCreature *Actor){ CleanupField(Actor, Target, 0, 0); } -void Teleport(TCreature *Actor, char *Param){ +void Teleport(TCreature *Actor, const char *Param){ if(Actor == NULL){ error("Teleport: Ungültige Kreatur übergeben.\n"); throw ERROR; @@ -1414,7 +1503,7 @@ void Teleport(TCreature *Actor, char *Param){ } Object Dest = GetMapContainer(DestX, DestY, DestZ); Move(0, Actor->CrObject, Dest, -1, false, NONE); - GraphicalEffect(DestX, DestY, DestZ, EFFECT_TELEPORT); + GraphicalEffect(DestX, DestY, DestZ, EFFECT_ENERGY); }else if(MDGoStrength != Actor->Skills[SKILL_GO_STRENGTH]->MDAct){ Actor->Skills[SKILL_GO_STRENGTH]->SetMDAct(MDGoStrength); AnnounceChangedCreature(Actor->ID, 4); // CREATURE_GO_STRENGTH_CHANGED ? @@ -1455,7 +1544,7 @@ void TeleportToCreature(TCreature *Actor, const char *Name){ } if(Actor == Player){ - GraphicalEffect(Actor->CrObject, EFFECT_TELEPORT); + GraphicalEffect(Actor->CrObject, EFFECT_ENERGY); return; } @@ -1475,7 +1564,7 @@ void TeleportToCreature(TCreature *Actor, const char *Name){ Object Dest = GetMapContainer(DestX, DestY, DestZ); Move(0, Actor->CrObject, Dest, -1, false, NONE); - GraphicalEffect(DestX, DestY, DestZ, EFFECT_TELEPORT); + GraphicalEffect(DestX, DestY, DestZ, EFFECT_ENERGY); Log("banish", "%s teleportiert sich zu %s.\n", Actor->Name, Player->Name); } @@ -1523,7 +1612,7 @@ void TeleportPlayerToMe(TCreature *Actor, const char *Name){ Object Dest = GetMapContainer(DestX, DestY, DestZ); Move(0, Player->CrObject, Dest, -1, false, NONE); - GraphicalEffect(DestX, DestY, DestZ, EFFECT_TELEPORT); + GraphicalEffect(DestX, DestY, DestZ, EFFECT_ENERGY); } void MagicRope(TCreature *Actor, int ManaPoints, int SoulPoints){ @@ -1543,7 +1632,7 @@ void MagicRope(TCreature *Actor, int ManaPoints, int SoulPoints){ Object Dest = GetMapContainer(OrigX, OrigY + 1, OrigZ - 1); Move(0, Actor->CrObject, Dest, -1, false, NONE); - GraphicalEffect(OrigX, OrigY, OrigZ, EFFECT_TELEPORT); + GraphicalEffect(OrigX, OrigY, OrigZ, EFFECT_ENERGY); } void MagicClimbing(TCreature *Actor, int ManaPoints, int SoulPoints, const char *Param){ @@ -1594,7 +1683,7 @@ void MagicClimbing(TCreature *Actor, int ManaPoints, int SoulPoints, const char Object Dest = GetMapContainer(DestX, DestY, DestZ); Move(0, Actor->CrObject, Dest, -1, false, NONE); - GraphicalEffect(OrigX, OrigY, OrigZ, EFFECT_TELEPORT); + GraphicalEffect(OrigX, OrigY, OrigZ, EFFECT_ENERGY); } void MagicClimbing(TCreature *Actor, const char *Param){ @@ -1825,7 +1914,7 @@ void SummonCreature(TCreature *Actor, int ManaPoints, int Race, bool God){ GraphicalEffect(Actor->posx, Actor->posy, Actor->posz, EFFECT_MAGIC_BLUE); } -void SummonCreature(TCreature *Actor, int Mana, const char *RaceName, bool God){ +void SummonCreature(TCreature *Actor, int ManaPoints, const char *RaceName, bool God){ if(Actor == NULL){ error("SummonCreature: Ungültige Kreatur übergeben.\n"); throw ERROR; @@ -1841,7 +1930,7 @@ void SummonCreature(TCreature *Actor, int Mana, const char *RaceName, bool God){ throw CREATURENOTEXISTING; } - SummonCreature(Actor, Mana, Race, God); + SummonCreature(Actor, ManaPoints, Race, God); } void StartMonsterraid(TCreature *Actor, const char *RaidName){ @@ -2025,7 +2114,6 @@ void Heal(TCreature *Actor, int ManaPoints, int SoulPoints, int Amount){ } } - void MassHeal(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, int Amount, int Radius){ if(Actor == NULL){ error("MassHeal: Ungültige Kreatur übergeben.\n"); @@ -2098,7 +2186,7 @@ void MassHeal(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, i } } -void HealFriend(TCreature *Actor, char *TargetName, int ManaPoints, int SoulPoints, int Amount){ +void HealFriend(TCreature *Actor, const char *TargetName, int ManaPoints, int SoulPoints, int Amount){ if(Actor == NULL){ error("HealFriend: Ungültige Kreatur übergeben.\n"); throw ERROR; @@ -2352,7 +2440,7 @@ void CancelInvisibility(TCreature *Actor, Object Target, int ManaPoints, int Sou } } -void CreatureIllusion(TCreature *Actor, const char *RaceName, int ManaPoints, int SoulPoints, int Duration){ +void CreatureIllusion(TCreature *Actor, int ManaPoints, int SoulPoints, const char *RaceName, int Duration){ if(Actor == NULL){ error("CreatureIllusion: Ungültige Kreatur übergeben.\n"); throw ERROR; @@ -2381,7 +2469,7 @@ void CreatureIllusion(TCreature *Actor, const char *RaceName, int ManaPoints, in GraphicalEffect(Actor->posx, Actor->posy, Actor->posz, EFFECT_MAGIC_BLUE); } -void ObjectIllusion(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, int Duration){ +void ObjectIllusion(TCreature *Actor, int ManaPoints, int SoulPoints, Object Target, int Duration){ if(Actor == NULL){ error("ObjectIllusion: Ungültige Kreatur übergeben.\n"); throw ERROR; @@ -2570,7 +2658,7 @@ void Challenge(TCreature *Actor, int ManaPoints, int SoulPoints, int Radius){ } } -void FindPerson(TCreature *Actor, const char *TargetName, int ManaPoints, int SoulPoints){ +void FindPerson(TCreature *Actor, int ManaPoints, int SoulPoints, const char *TargetName){ // TODO(fusion): And we're back. if(Actor == NULL){ error("FindPerson: Ungültige Kreatur übergeben.\n"); @@ -3220,13 +3308,1040 @@ void HomeTeleport(TCreature *Actor, const char *Name){ Object Dest = GetMapContainer(Player->startx, Player->starty, Player->startz); GraphicalEffect(Player->posx, Player->posy, Player->posz, EFFECT_POFF); Move(0, Player->CrObject, Dest, -1, false, NONE); - GraphicalEffect(Player->posx, Player->posy, Player->posz, EFFECT_TELEPORT); + GraphicalEffect(Player->posx, Player->posy, Player->posz, EFFECT_ENERGY); SendMessage(Actor->Connection, TALK_INFO_MESSAGE, "Player %s has been moved to the temple.", Player->Name); Log("banish", "%s teleportiert %s zum Tempel.\n", Actor->Name, Player->Name); } +// Spell Casting +// ============================================================================= +static void SpellFailed(uint32 CreatureID){ + TCreature *Actor = GetCreature(CreatureID); + if(Actor == NULL){ + error("SpellFailed: Kreatur existiert nicht.\n"); + return; + } + + GraphicalEffect(Actor->posx, Actor->posy, Actor->posz, EFFECT_POFF); +} + +static void CharacterRightSpell(uint32 CreatureID, int SpellNr, const char (*SpellStr)[512]){ + TCreature *Actor = GetCreature(CreatureID); + if(Actor == NULL){ + error("CharacterRightSpell: Kreatur existiert nicht.\n"); + throw ERROR; + } + + if(Actor->Type != PLAYER){ + return; + } + + switch(SpellNr) { + case 34: CreateThing(Actor, SpellStr[2], SpellStr[3]); break; + case 35: CreateThing(Actor, SpellStr[2], NULL); break; + case 37: Teleport(Actor, SpellStr[2]); break; + case 40: CreateKnowledge(Actor, SpellStr[3], NULL); break; + case 41: ChangeData(Actor, SpellStr[2]); break; + case 46: CreateKnowledge(Actor, SpellStr[3], SpellStr[4]); break; + case 47: TeleportToCreature(Actor, SpellStr[3]); break; + case 52: TeleportPlayerToMe(Actor, SpellStr[3]); break; + case 53: SummonCreature(Actor, 0, SpellStr[3], true); break; + case 58: GetPosition(Actor); break; + case 63: CreateMoney(Actor, SpellStr[3]); break; + case 64: ChangeProfession(Actor, SpellStr[3]); break; + case 71: EditGuests(Actor); break; + case 72: EditSubowners(Actor); break; + case 73: KickGuest(Actor, SpellStr[3]); break; + case 74: EditNameDoor(Actor); break; + case 96: GetQuestValue(Actor, SpellStr[3]); break; + case 97: SetQuestValue(Actor, SpellStr[3], SpellStr[4]); break; + case 98: CleanupField(Actor); break; + case 99: MagicClimbing(Actor, SpellStr[3]); break; + case 100: ClearQuestValues(Actor); break; + case 101: KillAllMonsters(Actor, EFFECT_DEATH, 2); break; + case 102: StartMonsterraid(Actor, SpellStr[4]); break; + } +} + +static void AccountRightSpell(uint32 CreatureID, int SpellNr, const char (*SpellStr)[512]){ + TCreature *Actor = GetCreature(CreatureID); + if(Actor == NULL){ + error("AccountRightSpell: Kreatur existiert nicht.\n"); + throw ERROR; + } + + if(Actor->Type != PLAYER){ + return; + } + + switch(SpellNr) { + case 57: BanishAccount(Actor, SpellStr[3], atoi(SpellStr[4]), SpellStr[5]); break; + case 60: HomeTeleport(Actor, SpellStr[2]); break; + case 61: DeleteAccount(Actor, SpellStr[4], SpellStr[5]); break; + case 62: SetNameRule(Actor, SpellStr[2]); break; + case 65: Notation(Actor, SpellStr[2], SpellStr[3]); break; + case 66: NameLock(Actor, SpellStr[3]); break; + case 67: KickPlayer(Actor, SpellStr[2]); break; + case 68: DeleteCharacter(Actor, SpellStr[4], SpellStr[5]); break; + case 69: IPBanishment(Actor, SpellStr[3], SpellStr[4]); break; + case 70: BanishCharacter(Actor, SpellStr[3], atoi(SpellStr[4]), SpellStr[5]); break; + } +} + +static void CastSpell(uint32 CreatureID, int SpellNr, const char (*SpellStr)[512]){ + TCreature *Actor = GetCreature(CreatureID); + if(Actor == NULL){ + error("CastSpell: Kreatur existiert nicht.\n"); + throw ERROR; + } + + CheckSpellbook(Actor, SpellNr); + CheckAccount(Actor, SpellNr); + CheckLevel(Actor, SpellNr); + CheckRing(Actor, SpellNr); + + if(Actor->EarliestSpellTime > ServerMilliseconds){ + throw EXHAUSTED; + } + + if(IsAggressiveSpell(SpellNr) && Actor->Type == PLAYER + && !CheckRight(Actor->ID, ATTACK_EVERYWHERE) + && IsProtectionZone(Actor->posx, Actor->posy, Actor->posz)){ + throw PROTECTIONZONE; + } + + int ManaPoints = SpellList[SpellNr].Mana; + int SoulPoints = SpellList[SpellNr].SoulPoints; + switch(SpellNr) { + case 1:{ + int Amount = ComputeDamage(Actor, SpellNr, 20, 10); + Heal(Actor, ManaPoints, SoulPoints, Amount); + break; + } + + case 2:{ + int Amount = ComputeDamage(Actor, SpellNr, 40, 20); + Heal(Actor, ManaPoints, SoulPoints, Amount); + break; + } + + case 3:{ + int Amount = ComputeDamage(Actor, SpellNr, 250, 50); + Heal(Actor, ManaPoints, SoulPoints, Amount); + break; + } + + case 6:{ + MagicGoStrength(Actor, Actor, ManaPoints, SoulPoints, 30, 3); + break; + } + + case 9:{ + SummonCreature(Actor, ManaPoints, SpellStr[3], false); + break; + } + + case 10:{ + Enlight(Actor, ManaPoints, SoulPoints, 6, 500); + break; + } + + case 11:{ + Enlight(Actor, ManaPoints, SoulPoints, 8, 1000); + break; + } + + case 13:{ + int Damage = ComputeDamage(Actor, SpellNr, 150, 50); + AngleCombat(Actor, ManaPoints, SoulPoints, Damage, + EFFECT_ENERGY, 5, 30, DAMAGE_ENERGY); + break; + } + + case 19:{ + int Damage = ComputeDamage(Actor, SpellNr, 30, 10); + AngleCombat(Actor, ManaPoints, SoulPoints, Damage, + EFFECT_FIRE, 4, 45, DAMAGE_FIRE); + break; + } + + case 20:{ + FindPerson(Actor, ManaPoints, SoulPoints, SpellStr[2]); + break; + } + + case 22:{ + int Damage = ComputeDamage(Actor, SpellNr, 60, 20); + AngleCombat(Actor, ManaPoints, SoulPoints, Damage, + EFFECT_FIRE_BURST, 5, 0, DAMAGE_ENERGY); + break; + } + + case 23:{ + int Damage = ComputeDamage(Actor, SpellNr, 120, 80); + AngleCombat(Actor, ManaPoints, SoulPoints, Damage, + EFFECT_FIRE_BURST, 8, 0, DAMAGE_ENERGY); + break; + } + + case 24:{ + int Damage = ComputeDamage(Actor, SpellNr, 250, 50); + MassCombat(Actor, Actor->CrObject, ManaPoints, SoulPoints, Damage, + EFFECT_FIRE_EXPLOSION, 6, DAMAGE_PHYSICAL, ANIMATION_FIRE); + break; + } + + case 29:{ + NegatePoison(Actor, Actor, ManaPoints, SoulPoints); + break; + } + + case 38:{ + CreatureIllusion(Actor, ManaPoints, SoulPoints, SpellStr[4], 200); + break; + } + + case 39:{ + MagicGoStrength(Actor, Actor, ManaPoints, SoulPoints, 70, 2); + break; + } + + case 42:{ + CreateFood(Actor, ManaPoints, SoulPoints); + break; + } + + case 44:{ + Shielding(Actor, ManaPoints, SoulPoints, 200); + break; + } + + case 45:{ + Invisibility(Actor, ManaPoints, SoulPoints, 200); + break; + } + + case 48:{ + CreateArrows(Actor, ManaPoints, SoulPoints, 1, 5); + break; + } + + case 49:{ + CreateArrows(Actor, ManaPoints, SoulPoints, 2, 3); + break; + } + + case 51:{ + CreateArrows(Actor, ManaPoints, SoulPoints, 0, 10); + break; + } + + case 56:{ + int Damage = ComputeDamage(Actor, SpellNr, 200, 50); + MassCombat(Actor, Actor->CrObject, ManaPoints, SoulPoints, Damage, + EFFECT_POISON, 8, DAMAGE_POISON_PERIODIC, ANIMATION_NONE); + break; + } + + case 75:{ + Enlight(Actor, ManaPoints, SoulPoints, 9, 2000); + break; + } + + case 76:{ + MagicRope(Actor, ManaPoints, SoulPoints); + break; + } + + case 79:{ + CreateArrows(Actor, ManaPoints, SoulPoints, 3, 5); + break; + } + + case 80:{ + int Level = Actor->Skills[SKILL_LEVEL]->Get(); + int Damage = (Level * ComputeDamage(Actor, SpellNr, 80, 20)) / 25; + MassCombat(Actor, Actor->CrObject, Level * 4, 0, Damage, + EFFECT_BONE_HIT, 2, DAMAGE_PHYSICAL, ANIMATION_NONE); + break; + } + + case 81:{ + MagicClimbing(Actor, ManaPoints, SoulPoints, SpellStr[3]); + break; + } + + case 82:{ + int Amount = ComputeDamage(Actor, SpellNr, 200, 40); + MassHeal(Actor, Actor->CrObject, ManaPoints, SoulPoints, Amount, 4); + break; + } + + case 84:{ + int Amount = ComputeDamage(Actor,SpellNr,120,40); + HealFriend(Actor, SpellStr[3], ManaPoints, SoulPoints, Amount); + break; + } + + case 85:{ + MassRaiseDead(Actor, Actor->CrObject, ManaPoints, SoulPoints, 4); + break; + } + + case 87:{ + int Damage = ComputeDamage(Actor, SpellNr, 45, 10); + AngleCombat(Actor, ManaPoints, SoulPoints, Damage, + EFFECT_DEATH, 1, 0, DAMAGE_PHYSICAL); + break; + } + + case 88:{ + int Damage = ComputeDamage(Actor, SpellNr, 45, 10); + AngleCombat(Actor, ManaPoints, SoulPoints, Damage, + EFFECT_ENERGY, 1, 0, DAMAGE_ENERGY); + break; + } + + case 89:{ + int Damage = ComputeDamage(Actor, SpellNr, 45, 10); + AngleCombat(Actor, ManaPoints, SoulPoints, Damage, + EFFECT_FIRE, 1, 0, DAMAGE_FIRE); + break; + } + + case 90:{ + CancelInvisibility(Actor, Actor->CrObject, ManaPoints, SoulPoints, 4); + break; + } + + case 92:{ + ObjectType OldType = GetNewObjectType(90, 25); + ObjectType NewType = GetNewObjectType(90, 57); + EnchantObject(Actor, ManaPoints, SoulPoints, OldType, NewType); + break; + } + + case 93:{ + Challenge(Actor, ManaPoints, SoulPoints, 2); + break; + } + + case 94:{ + CreateField(Actor, ManaPoints, SoulPoints, FIELD_TYPE_WILDGROWTH); + break; + } + + case 95:{ + CreateArrows(Actor, ManaPoints, SoulPoints, 4, 1); + break; + } + } + + if(IsAggressiveSpell(SpellNr)){ + Actor->BlockLogout(60, false); + } +} + +static void RuneSpell(uint32 CreatureID, int SpellNr){ + TCreature *Actor = GetCreature(CreatureID); + if(Actor == NULL){ + error("RuneSpell: Kreatur existiert nicht.\n"); + throw ERROR; + } + + uint8 RuneGr = SpellList[SpellNr].RuneGr; + uint8 RuneNr = SpellList[SpellNr].RuneNr; + int ManaPoints = SpellList[SpellNr].Mana; + int SoulPoints = SpellList[SpellNr].SoulPoints; + int Amount = SpellList[SpellNr].Amount; + + if(RuneGr == 0){ + error("RuneSpell: Spell %d ist Runenspruch, hat aber keine Rune.\n", SpellNr); + throw ERROR; + } + + CheckSpellbook(Actor, SpellNr); + CheckAccount(Actor, SpellNr); + CheckLevel(Actor, SpellNr); + CheckRing(Actor, SpellNr); + if(Actor->EarliestSpellTime > ServerMilliseconds){ + throw EXHAUSTED; + } + + bool RuneCreated = false; + ObjectType BlankType = GetSpecialObject(RUNE_BLANK); + + Object RightHand = GetBodyObject(Actor->ID, 5); // RIGHTHAND + if(RightHand.exists() && RightHand.getObjectType() == BlankType){ + CheckMana(Actor, ManaPoints, SoulPoints, 1000); + ObjectType RuneType = GetNewObjectType(RuneGr, RuneNr); + Change(RightHand, RuneType, Amount); + RuneCreated = true; + } + + Object LeftHand = GetBodyObject(Actor->ID, 6); // 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 + // relevant if there wasn't a first cast. + try{ + CheckMana(Actor, ManaPoints, SoulPoints, 1000); + ObjectType RuneType = GetNewObjectType(RuneGr, RuneNr); + Change(LeftHand, RuneType, Amount); + RuneCreated = true; + }catch(...){ + if(!RuneCreated){ + throw; + } + } + } + + if(!RuneCreated){ + throw MAGICITEM; + } + + GraphicalEffect(Actor->posx, Actor->posy, Actor->posz, EFFECT_MAGIC_RED); +} + +static int TypeOfSpell(const char *Text){ + // NOTE(fusion): We're checking `Text` against the first 5 spell syllables + // which are "al", "ad", "ex", "ut", "om". They determine the type of the + // spell. See `CheckForSpell`. + int SpellType = 0; + if(Text != NULL){ + for(int SyllableNr = 1; + SyllableNr < 6; + SyllableNr += 1){ + if(stricmp(Text, SpellSyllable[SyllableNr], 2) == 0){ + SpellType = SyllableNr; + break; + } + } + } + return SpellType; +} + +static int FindSpell(const uint8 *Syllable){ + int BestMatch = 0; + int MinParams = 100; + for(int SpellNr = 0; + SpellNr < NARRAY(SpellList); + SpellNr += 1){ + int Params = 0; + TSpellList *Spell = &SpellList[SpellNr]; + for(int i = 0; + i < NARRAY(Spell->Syllable); + i += 1){ + // NOTE(fusion): SpellSyllable[6] is "para" which refers to a spell + // parameter. + if(Spell->Syllable[i] == 6){ + if(Syllable[i] == 0) + break; + Params += 1; + }else if(Spell->Syllable[i] != Syllable[i]){ + break; + } + + if(Spell->Syllable[i] == 0){ + if(Params < MinParams){ + MinParams = Params; + BestMatch = SpellNr; + } + break; + } + } + } + + return BestMatch; +} + +static int FindSpell(Object Obj){ + if(!Obj.exists()){ + error("FindSpell: Übergebenes Objekt existiert nicht.\n"); + return 0; + } + + ObjectType ObjType = Obj.getObjectType(); + if(!ObjType.getFlag(RUNE)){ + error("FindSpell: Übergebenes Objekt ist nicht magisch.\n"); + return 0; + } + + int Result = 0; + for(int SpellNr = 0; + SpellNr < NARRAY(SpellList); + SpellNr += 1){ + TSpellList *Spell = &SpellList[SpellNr]; + ObjectType RuneType = GetNewObjectType(Spell->RuneGr, Spell->RuneNr); + if(ObjType == RuneType){ + Result = SpellNr; + break; + } + } + return Result; +} + +static void GetSpellString(int SpellNr, char *Text){ + Text[0] = 0; + + if(SpellNr < 1 || SpellNr >= NARRAY(SpellList)){ + error("GetSpellString: Ungültige Zauberspruchnummer %d.\n", SpellNr); + return; + } + + TSpellList *Spell = &SpellList[SpellNr]; + for(int i = 0; i < NARRAY(Spell->Syllable); i += 1){ + const char *Syllable = SpellSyllable[Spell->Syllable[i]]; + if(Syllable[0] == 0){ + break; + } + + // TODO(fusion): Review. I think we don't add a space between the first + // and second syllable and it's probably correct. + if(i >= 2){ + strcat(Text, " "); + } + + strcat(Text, Syllable); + } +} + +void GetMagicItemDescription(Object Obj, char *SpellString, int *MagicLevel){ + SpellString[0] = 0; + *MagicLevel = 0; + + if(!Obj.exists()){ + error("GetMagicItemDescription: Übergebenes Objekt existiert nicht.\n"); + return; + } + + ObjectType ObjType = Obj.getObjectType(); + if(!ObjType.getFlag(RUNE)){ + error("GetMagicItemDescription: Übergebenes Objekt ist nicht magisch.\n"); + return; + } + + int SpellNr = FindSpell(Obj); + if(SpellNr == 0){ + error("GetMagicItemDescription: Objekt %d hat keinen Zauberspruch.\n", ObjType.TypeID); + return; + } + + GetSpellString(SpellNr, SpellString); + *MagicLevel = (int)SpellList[SpellNr].RuneLevel; +} + +void GetSpellbook(uint32 CharacterID, char *Buffer){ + TPlayer *Player = GetPlayer(CharacterID); + if(Player == NULL){ + error("GetSpellbook: Spieler existiert nicht.\n"); + return; + } + + if(Buffer == NULL){ + error("GetSpellbook: Übergebener Puffer existiert nicht.\n"); + return; + } + + // TODO(fusion): We're iterating over all spells for each level. This is bad + // because we could have a list of spell numbers sorted by level and achieve + // the same thing. We also had a hard coded max level but since we're already + // iterating so many times over the spell list, what's another one? + // Also, the overuse of `strcat` doesn't help. + + int MaxLevel = 1; + for(int SpellNr = 0; + SpellNr < NARRAY(SpellList); + SpellNr += 1){ + int Level = (int)SpellList[SpellNr].Level; + if(Level > MaxLevel){ + Level = MaxLevel; + } + } + + char Help[256]; + for(int Level = 1; Level <= MaxLevel; Level += 1){ + bool First = true; + for(int SpellNr = 0; + SpellNr < NARRAY(SpellList); + SpellNr += 1){ + TSpellList *Spell = &SpellList[SpellNr]; + if((int)Spell->Level != Level || !Player->SpellKnown(SpellNr)){ + continue; + } + + if(First){ + sprintf(Help, "Spells for Level %d\n", Level); + strcat(Buffer, Help); + First = false; + } + + GetSpellString(SpellNr, Help); + if(Help[0] == 0){ + error("GetSpellbook: Zauberspruch %d hat keine Zauberformel.\n", SpellNr); + continue; + } + + strcat(Buffer, " "); + strcat(Buffer, Help); + if(Spell->Comment != NULL && Spell->Comment[0] != 0){ + strcat(Buffer, " - "); + strcat(Buffer, Spell->Comment); + if(SpellNr == 9 || SpellNr == 12){ + strcpy(Help, ": var"); + }else if(SpellNr == 80){ + strcpy(Help, ": 4*Level"); + }else{ + sprintf(Help, ": %d", Spell->Mana); + } + strcat(Buffer, Help); + } + strcat(Buffer, "\n"); + } + + if(!First){ + strcat(Buffer, "\n"); + } + } +} + +int GetSpellLevel(int SpellNr){ + if(SpellNr < 1 || SpellNr >= NARRAY(SpellList)){ + error("GetSpellLevel: Ungültige Spruchnummer %d.\n", SpellNr); + return 1; + } + + return (int)SpellList[SpellNr].Level; +} + +int CheckForSpell(uint32 CreatureID, const char *Text){ + // IMPORTANT(fusion): The first syllable also determines the type of the spell + // which is why we can quickly rule out a spell cast if it is unknown. + // It is also handled separately in the parsing loop below to allow for the + // second syllable to be glued together (or not). + int SpellType = TypeOfSpell(Text); + if(SpellType == 0){ + return 0; + } + + // TODO(fusion): Keeping syllables in text form doesn't make sense. `SpellStr` + // should ideally only contain parameters. + + // TODO(fusion): Did we really need to use `stringstream` here? It allocates + // an internal `std::string` from the input string which is just wasteful, + // specially for the minimal processing we're doing here. + + int SyllableCount = 1; + uint8 Syllable[10] = { (uint8)SpellType }; + char SpellStr[10][512] = {}; + strcpy(SpellStr[0], SpellSyllable[SpellType]); + + std::istringstream IS(Text); + IS.get(); + IS.get(); + while(!IS.eof()){ + while(isSpace(IS.peek())){ + IS.get(); + } + + int Index = SyllableCount; + if(IS.peek() == '"'){ + IS.get(); + IS.get(SpellStr[Index], sizeof(SpellStr[0]), '"'); + IS.get(); + }else{ + IS.get(SpellStr[Index], sizeof(SpellStr[0]), ' '); + } + + // TODO(fusion): This could be a problem if there is a "" parameter? + if(SpellStr[Index][0] == 0){ + break; + } + + for(int SyllableNr = 0; + SyllableNr < NARRAY(SpellSyllable); + SyllableNr += 1){ + if(stricmp(SpellStr[Index], SpellSyllable[SyllableNr]) == 0){ + Syllable[Index] = (uint8)SyllableNr; + break; + } + } + + // NOTE(fusion): SpellSyllable[6] is "para" which refers to a spell + // parameter. This is setting up `Syllable` to be used by `FindSpell`. + if(Syllable[Index] == 0){ + Syllable[Index] = 6; + } + + SyllableCount += 1; + } + + int SpellNr = FindSpell(Syllable); + if(SpellNr != 0){ + try{ + switch(SpellType){ + case 1: CharacterRightSpell(CreatureID, SpellNr, SpellStr); break; + case 2: RuneSpell(CreatureID, SpellNr); break; + case 3: CastSpell(CreatureID, SpellNr, SpellStr); break; + case 4: CastSpell(CreatureID, SpellNr, SpellStr); break; + case 5: AccountRightSpell(CreatureID, SpellNr, SpellStr); break; + default:{ + error("CheckForSpell: Spruchklasse %d existiert nicht.\n", SpellType); + break; + } + } + }catch(RESULT r){ + // TODO(fusion): `SpellFailed` is inlined in here but I think it's + // cleaner to keep it this way. + TCreature *Actor = GetCreature(CreatureID); + if(Actor == NULL){ + error("SpellFailed: Kreatur existiert nicht.\n"); + }else{ + if(r != ERROR){ + GraphicalEffect(Actor->posx, Actor->posy, Actor->posz, EFFECT_POFF); + } + + if(Actor->Type == PLAYER){ + SendResult(Actor->Connection, r); + } + } + + return -SpellType; + } + } + + return SpellType; +} + +static void DeleteRune(Object Obj){ + if(!Obj.exists()){ + error("DeleteRune: Übergebenes Objekt existiert nicht.\n"); + throw ERROR; + } + + // TODO(fusion): Should probably check if object is a rune? + uint32 Charges = Obj.getAttribute(CHARGES); + if(Charges > 1){ + Change(Obj, CHARGES, Charges - 1); + }else{ + Delete(Obj, -1); + } +} + +void UseMagicItem(uint32 CreatureID, Object Obj, Object Dest){ + TPlayer *Actor = GetPlayer(CreatureID); + if(Actor == NULL){ + error("UseMagicItem: Kreatur existiert nicht.\n"); + throw ERROR; + } + + if(!Obj.exists()){ + error("UseMagicItem: Übergebenes Objekt existiert nicht.\n"); + throw ERROR; + } + + if(!Dest.exists()){ + error("UseMagicItem: Übergebenes Ziel existiert nicht (Objekt %d).\n", + Obj.getObjectType().TypeID); + throw ERROR; + } + + if(CheckRight(Actor->ID, NO_RUNES)){ + throw NOTUSABLE; + } + + int SpellNr = FindSpell(Obj); + if(SpellNr == 0){ + error("UseMagicItem: Für Objekt %d existiert kein Spruch.\n", + Obj.getObjectType().TypeID); + throw ERROR; + } + + + // NOTE(fusion): This target picking logic is probably to avoid picking + // obviously wrong candidates when there are multiple creatures on a single + // field. + TCreature *Target = NULL; + bool Aggressive = IsAggressiveSpell(SpellNr); + { + if(Dest.getObjectType().isCreatureContainer()){ + Target = GetCreature(Dest); + } + + Object Other = GetFirstContainerObject(Dest.getContainer()); + while(Other != NONE){ + if(Other.getObjectType().isCreatureContainer()){ + uint32 OtherID = Other.getCreatureID(); + if(Target == NULL + || (Aggressive && OtherID != Actor->ID) + || (!Aggressive && OtherID == Actor->ID)){ + Target = GetCreature(OtherID); + Dest = Other; + } + } + Other = Other.getNextObject(); + } + } + + // NOTE(fusion): Rune check. + CheckRuneLevel(Actor, SpellNr); + + if(Actor->EarliestSpellTime > ServerMilliseconds){ + throw EXHAUSTED; + } + + if(Aggressive){ + int DestX, DestY, DestZ; + GetObjectCoordinates(Dest, &DestX, &DestY, &DestZ); + if(!CheckRight(Actor->ID, ATTACK_EVERYWHERE) + && (IsProtectionZone(Actor->posx, Actor->posy, Actor->posz) + || IsProtectionZone(DestX, DestY, DestZ))){ + throw PROTECTIONZONE; + } + + if(Dest.getContainer().getObjectType().isMapContainer()){ + throw NOROOM; + } + } + + try{ + switch(SpellNr){ + case 4:{ + if(Target == NULL){ + throw NOCREATURE; + } + + int Amount = ComputeDamage(Actor, SpellNr, 70, 30); + Heal(Target, -1, 0, Amount); // -1 ? + break; + } + + case 5:{ + if(Target == NULL){ + throw NOCREATURE; + } + + int Amount = ComputeDamage(Actor, SpellNr, 250, 30); + Heal(Target, -1, 0, Amount); // -1 ? + break; + } + + case 7:{ + int Damage = ComputeDamage(Actor, SpellNr, 15, 5); + Combat(Actor, Dest, 0, 0, Damage, EFFECT_FIRE_BURST, + ANIMATION_FIRE, DAMAGE_ENERGY); + break; + } + + case 8:{ + int Damage = ComputeDamage(Actor, SpellNr, 30, 10); + Combat(Actor, Dest, 0, 0, Damage, EFFECT_FIRE_BURST, + ANIMATION_FIRE, DAMAGE_ENERGY); + break; + } + + case 12:{ + if(Target == NULL){ + throw NOCREATURE; + } + + Convince(Actor, Target); + break; + } + + case 14:{ + ObjectIllusion(Actor, 0, 0, Dest, 200); + break; + } + + case 15:{ + int Damage = ComputeDamage(Actor, SpellNr, 20, 5); + MassCombat(Actor, Dest, 0, 0, Damage, EFFECT_FIRE, + 3, DAMAGE_FIRE, ANIMATION_FIRE); + break; + } + + case 16:{ + int Damage = ComputeDamage(Actor, SpellNr, 50, 15); + MassCombat(Actor, Dest, 0, 0, Damage, EFFECT_FIRE, + 4, DAMAGE_FIRE, ANIMATION_FIRE); + break; + } + + case 17:{ + MassCreateField(Actor, Dest, 0, 0, FIELD_TYPE_FIRE, 3); + break; + } + + case 18:{ + int Damage = ComputeDamage(Actor, SpellNr, 60, 40); + MassCombat(Actor, Dest, 0, 0, Damage, EFFECT_FIRE_EXPLOSION, + 1, DAMAGE_PHYSICAL, ANIMATION_FIRE); + break; + } + + case 21:{ + int Damage = ComputeDamage(Actor, SpellNr, 150, 20); + Combat(Actor, Dest, 0, 0, Damage, EFFECT_DEATH, + ANIMATION_DEATH, DAMAGE_PHYSICAL); + break; + } + + case 25:{ + CreateField(Actor, Dest, 0, 0, FIELD_TYPE_FIRE); + break; + } + + case 26:{ + CreateField(Actor, Dest, 0, 0, FIELD_TYPE_POISON); + break; + } + + case 27:{ + CreateField(Actor, Dest, 0, 0, FIELD_TYPE_ENERGY); + break; + } + + case 28:{ + CreateFieldWall(Actor, Dest, 0, 0, FIELD_TYPE_FIRE, 2); + break; + } + + case 30:{ + DeleteField(Actor, Dest, 0, 0); + break; + } + + case 31:{ + if(Target == NULL){ + throw NOCREATURE; + } + + NegatePoison(Actor, Target, 0, 0); + break; + } + + case 32:{ + CreateFieldWall(Actor, Dest, 0, 0, FIELD_TYPE_POISON, 2); + break; + } + + case 33:{ + CreateFieldWall(Actor, Dest, 0, 0, FIELD_TYPE_ENERGY, 3); + break; + } + + case 50:{ + int Damage = ComputeDamage(Actor, SpellNr, 120, 20); + Combat(Actor, Dest, 0, 0, Damage, EFFECT_FIRE_HIT, + ANIMATION_FIRE, DAMAGE_FIRE_PERIODIC); + break; + } + + case 54:{ + if(Target == NULL){ + throw NOCREATURE; + } + + if(Actor->GetEffectiveProfession() != PROFESSION_DRUID){ + throw NOTUSABLE; + } + + int ManaPoints = SpellList[SpellNr].Mana; + int SoulPoints = SpellList[SpellNr].SoulPoints; + MagicGoStrength(Actor, Target, ManaPoints, SoulPoints, -101, 1); + break; + } + + case 55:{ + MassCreateField(Actor, Dest, 0, 0, FIELD_TYPE_ENERGY, 3); + break; + } + + case 77:{ + int Damage = ComputeDamage(Actor, SpellNr, 70, 20); + Combat(Actor, Dest, 0, 0, Damage, EFFECT_POISON_HIT, + ANIMATION_ENERGY, DAMAGE_POISON_PERIODIC); + break; + } + + case 78:{ + CleanupField(Actor, Dest, 0, 0); + break; + } + + case 83:{ + RaiseDead(Actor, Dest, 0, 0); + break; + } + + case 86:{ + CreateField(Actor, Dest, 0, 0, FIELD_TYPE_MAGICWALL); + break; + } + + case 91:{ + MassCreateField(Actor, Dest, 0, 0, FIELD_TYPE_POISON, 3); + break; + } + + default:{ + error("UseMagicItem: Spell %d noch nicht implementiert.\n", SpellNr); + throw ERROR; + } + } + }catch(RESULT r){ + // TODO(fusion): Same as with `CheckForSpell` except that `Actor` is + // already in scope. + if(r != ERROR){ + GraphicalEffect(Actor->posx, Actor->posy, Actor->posz, EFFECT_POFF); + } + SendResult(Actor->Connection, r); + return; + } + + DeleteRune(Obj); + + if(Aggressive){ + Actor->BlockLogout(60, false); + } +} + +void DrinkPotion(uint32 CreatureID, Object Obj){ + TPlayer *Player = GetPlayer(CreatureID); + if(Player == NULL){ + error("DrinkPotion: Kreatur existiert nicht.\n"); + throw ERROR; + } + + if(!Obj.exists()){ + error("DrinkPotion: Übergebenes Objekt existiert nicht.\n"); + throw ERROR; + } + + ObjectType ObjType = Obj.getObjectType(); + if(!ObjType.getFlag(LIQUIDCONTAINER)){ + error("DrinkPotion: Übergebenes Objekt ist kein Flüssigkeitscontainer.\n"); + throw ERROR; + } + + int LiquidType = (int)Obj.getAttribute(CONTAINERLIQUIDTYPE); + if(LiquidType == LIQUID_MANA){ + int Amount = ComputeDamage(NULL, 0, 100, 50); + RefreshMana(Player, 0, 0, Amount); + }else if(LiquidType == LIQUID_LIFE){ + int Amount = ComputeDamage(NULL, 0, 50, 25); + Heal(Player, 0, 0, Amount); + }else{ + error("DrinkPotion: Objekt enthält keinen Zaubertrank.\n"); + throw ERROR; + } + + Change(Obj, CONTAINERLIQUIDTYPE, LIQUID_NONE); +} + // Magic Init Functions // ============================================================================= static void InitCircles(void){ diff --git a/src/magic.hh b/src/magic.hh index 0525ad2..e1dc354 100644 --- a/src/magic.hh +++ b/src/magic.hh @@ -4,6 +4,14 @@ #include "common.hh" #include "creature.hh" +enum : int { + FIELD_TYPE_FIRE = 1, + FIELD_TYPE_POISON = 2, + FIELD_TYPE_ENERGY = 3, + FIELD_TYPE_MAGICWALL = 4, + FIELD_TYPE_WILDGROWTH = 5, +}; + struct TImpact{ // VIRTUAL FUNCTIONS // ========================================================================= @@ -89,25 +97,105 @@ struct TSummonImpact: TImpact { int Maximum; }; -struct TCircle { - int x[32]; - int y[32]; - int Count; -}; - -struct TSpellList { - uint8 Syllable[10]; - uint8 RuneGr; - uint8 RuneNr; - const char *Comment; - uint16 Level; - uint16 RuneLevel; - uint16 Flags; - int Mana; - int SoulPoints; - int Amount; -}; +void ActorShapeSpell(TCreature *Actor, TImpact *Impact, int Effect); +void VictimShapeSpell(TCreature *Actor, TCreature *Victim, + int Range, int Animation, TImpact *Impact, int Effect); +void OriginShapeSpell(TCreature *Actor, int Radius, TImpact *Impact, int Effect); +void CircleShapeSpell(TCreature *Actor, int DestX, int DestY, int DestZ, + int Range, int Animation, int Radius, TImpact *Impact, int Effect); +void DestinationShapeSpell(TCreature *Actor, TCreature *Victim, + int Range, int Animation, int Radius, TImpact *Impact, int Effect); +void AngleShapeSpell(TCreature *Actor, int Angle, int Range, TImpact *Impact, int Effect); +void CheckSpellbook(TCreature *Actor, int SpellNr); +void CheckAccount(TCreature *Actor, int SpellNr); +void CheckLevel(TCreature *Actor, int SpellNr); +void CheckRuneLevel(TCreature *Actor, int SpellNr); +void CheckMagicItem(TCreature *Actor, ObjectType Type); +void CheckRing(TCreature *Actor, int SpellNr); +void CheckAffectedPlayers(TCreature *Actor, int x, int y, int z); +void CheckMana(TCreature *Actor, int ManaPoints, int SoulPoints, int Delay); +int ComputeDamage(TCreature *Actor, int SpellNr, int Damage, int Variation); +bool IsAggressiveSpell(int SpellNr); +void MassCombat(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, + int Damage, int Effect, int Radius, int DamageType, int Animation); +void AngleCombat(TCreature *Actor, int ManaPoints, int SoulPoints, + int Damage, int Effect, int Range, int Angle, int DamageType); +void Combat(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, + int Damage, int Effect, int Animation, int DamageType); +int GetDirection(int dx, int dy); // TODO(fusion): Move this one elsewhere? Maybe `info.cc`. +void KillAllMonsters(TCreature *Actor, int Effect, int Radius); +void CreateField(int x, int y, int z, int FieldType, uint32 Owner, bool Peaceful); +void CreateField(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, int FieldType); +void CreateField(TCreature *Actor, int ManaPoints, int SoulPoints, int FieldType); +void MassCreateField(TCreature *Actor, Object Target, + int ManaPoints, int SoulPoints, int FieldType, int Radius); +void CreateFieldWall(TCreature *Actor, Object Target, + int ManaPoints, int SoulPoints, int FieldType, int Width); +void DeleteField(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints); +void CleanupField(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints); +void CleanupField(TCreature *Actor); +void Teleport(TCreature *Actor, const char *Param); +void TeleportToCreature(TCreature *Actor, const char *Name); +void TeleportPlayerToMe(TCreature *Actor, const char *Name); +void MagicRope(TCreature *Actor, int ManaPoints, int SoulPoints); +void MagicClimbing(TCreature *Actor, int ManaPoints, int SoulPoints, const char *Param); +void MagicClimbing(TCreature *Actor, const char *Param); +void CreateThing(TCreature *Actor, const char *Param1, const char *Param2); +void CreateMoney(TCreature *Actor, const char *Param); +void CreateFood(TCreature *Actor, int ManaPoints, int SoulPoints); +void CreateArrows(TCreature *Actor, int ManaPoints, int SoulPoints, int ArrowType, int Count); +void SummonCreature(TCreature *Actor, int ManaPoints, int Race, bool God); +void SummonCreature(TCreature *Actor, int ManaPoints, const char *RaceName, bool God); +void StartMonsterraid(TCreature *Actor, const char *RaidName); +void RaiseDead(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints); +void MassRaiseDead(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, int Radius); +void Heal(TCreature *Actor, int ManaPoints, int SoulPoints, int Amount); +void MassHeal(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, int Amount, int Radius); +void HealFriend(TCreature *Actor, const char *TargetName, int ManaPoints, int SoulPoints, int Amount); +void RefreshMana(TCreature *Actor, int ManaPoints, int SoulPoints, int Amount); +void MagicGoStrength(TCreature *Actor, TCreature *Target, int ManaPoints, int SoulPoints, int Percent, int Duration); +void Shielding(TCreature *Actor, int ManaPoints, int SoulPoints, int Duration); +void NegatePoison(TCreature *Actor, TCreature *Target, int ManaPoints, int SoulPoints); +void Enlight(TCreature *Actor, int ManaPoints, int SoulPoints, int Radius, int Duration); +void Invisibility(TCreature *Actor, int ManaPoints, int SoulPoints, int Duration); +void CancelInvisibility(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, int Radius); +void CreatureIllusion(TCreature *Actor, int ManaPoints, int SoulPoints, const char *RaceName, int Duration); +void ObjectIllusion(TCreature *Actor, int ManaPoints, int SoulPoints, Object Target, int Duration); +void ChangeData(TCreature *Actor, const char *Param); +void EnchantObject(TCreature *Actor, int ManaPoints, int SoulPoints, ObjectType OldType, ObjectType NewType); +void Convince(TCreature *Actor, TCreature *Target); +void Challenge(TCreature *Actor, int ManaPoints, int SoulPoints, int Radius); +void FindPerson(TCreature *Actor, int ManaPoints, int SoulPoints, const char *TargetName); +void GetPosition(TCreature *Actor); +void GetQuestValue(TCreature *Actor, const char *Param); +void SetQuestValue(TCreature *Actor, const char *Param1, const char *Param2); +void ClearQuestValues(TCreature *Actor); +void CreateKnowledge(TCreature *Actor, const char *Param1, const char *Param2); +void ChangeProfession(TCreature *Actor, const char *Param); +void EditGuests(TCreature *Actor); +void EditSubowners(TCreature *Actor); +void EditNameDoor(TCreature *Actor); +void KickGuest(TCreature *Actor, const char *GuestName); +void Notation(TCreature *Actor, const char *Name, const char *Comment); +void NameLock(TCreature *Actor, const char *Name); +void BanishAccount(TCreature *Actor, const char *Name, int Duration, const char *Reason); +void DeleteAccount(TCreature *Actor, const char *Name, const char *Reason); +void BanishCharacter(TCreature *Actor, const char *Name, int Duration, const char *Reason); +void DeleteCharacter(TCreature *Actor, const char *Name, const char *Reason); +void IPBanishment(TCreature *Actor, const char *Name, const char *Reason); +void SetNameRule(TCreature *Actor, const char *Name); +void KickPlayer(TCreature *Actor, const char *Name); +void HomeTeleport(TCreature *Actor, const char *Name); + +// TODO(fusion): These are unsafe like strcpy. +void GetMagicItemDescription(Object Obj, char *SpellString, int *MagicLevel); +void GetSpellbook(uint32 CharacterID, char *Buffer); + +int GetSpellLevel(int SpellNr); + +int CheckForSpell(uint32 CreatureID, const char *Text); +void UseMagicItem(uint32 CreatureID, Object Obj, Object Dest); +void DrinkPotion(uint32 CreatureID, Object Obj); -void CheckMana(TCreature *Creature, int ManaPoints, int SoulPoints, int Delay); #endif //TIBIA_MAGIC_HH_ diff --git a/src/main.cc b/src/main.cc index d3a0cbd..3925b04 100644 --- a/src/main.cc +++ b/src/main.cc @@ -541,8 +541,8 @@ int main(int argc, char **argv){ // once we identify all throw statements and how to roughly handle them. try{ LaunchGame(); - }catch(RESULT result){ - error("main: Nicht abgefangene Exception %d.\n", result); + }catch(RESULT r){ + error("main: Nicht abgefangene Exception %d.\n", r); }catch(const char *str){ error("main: Nicht abgefangene Exception \"%s\".\n", str); }catch(const std::exception &e){ diff --git a/src/stubs.hh b/src/stubs.hh index 6013a17..a1b451a 100644 --- a/src/stubs.hh +++ b/src/stubs.hh @@ -22,15 +22,12 @@ extern void Change(Object Obj, ObjectType NewType, uint32 Value); extern void CharacterDeathOrder(TCreature *Creature, int OldLevel, uint32 Offender, const char *Remark, bool Unjustified); extern bool CheckRight(uint32 CreatureID, RIGHT Right); -extern void CircleShapeSpell(TCreature *Actor, int DestX, int DestY, int DestZ, - int Range, int Animation, int Radius, TImpact *Impact, int Effect); extern void CleanHouseField(int x, int y, int z); -extern int ComputeDamage(TCreature *Creature, int SpellNr, int Damage, int Variation); extern void ConvinceMonster(TCreature *Master, TCreature *Slave); extern void ChallengeMonster(TCreature *Challenger, TCreature *Monster); extern int CountInventoryObjects(uint32 CreatureID, ObjectType Type, uint32 Value); +extern Object Create(Object Con, ObjectType Type, uint32 Value); extern Object CreateAtCreature(uint32 CreatureID, ObjectType Type, uint32 Value); -extern void CreateField(int x, int y, int z, int FieldType, uint32 Owner, bool Peaceful); extern void CreateMonster(int Race, int x, int y, int z, int Home, uint32 Master, bool ShowEffect); extern void CreatePlayerList(bool Online); extern void CreatePool(Object Con, ObjectType Type, uint32 Value); @@ -92,6 +89,7 @@ extern void SendMessage(TConnection *Connection, int Mode, const char *Text, ... extern void SendPlayerData(TConnection *Connection); extern void SendPlayerSkills(TConnection *Connection); extern void SendPlayerState(TConnection *Connection, uint8 State); +extern void SendResult(TConnection *Connection, RESULT r); extern void ShowGuestList(uint16 HouseID, TPlayer *Player, char *Buffer); extern void ShowSubownerList(uint16 HouseID, TPlayer *Player, char *Buffer); extern void ShowNameDoor(Object Door, TPlayer *Player, char *Buffer); |
