From deff1b8b227d80588e7fa0a40c6f05824412fbb2 Mon Sep 17 00:00:00 2001 From: Mark Samman Date: Sun, 7 Jul 2013 02:30:08 +0200 Subject: [PATCH] Add data and source code files --- .gitattributes | 22 + .gitignore | 165 + LICENSE | 339 + Makefile | 43 + config.lua | 127 + data/XML/admin.xml | 7 + data/XML/commands.xml | 46 + data/XML/mounts.xml | 38 + data/XML/outfits.xml | 86 + data/XML/quests.xml | 14 + data/XML/stages.xml | 9 + data/XML/vocations.xml | 93 + data/actions/actions.xml | 198 + data/actions/lib/actions.lua | 31 + data/actions/scripts/other/blueberrybush.lua | 25 + data/actions/scripts/other/changegold.lua | 18 + .../scripts/other/constructionkits.lua | 14 + data/actions/scripts/other/createbread.lua | 14 + data/actions/scripts/other/decayto.lua | 9 + data/actions/scripts/other/destroy.lua | 3 + data/actions/scripts/other/dice.lua | 9 + data/actions/scripts/other/doors.lua | 77 + .../actions/scripts/other/fireworksrocket.lua | 13 + data/actions/scripts/other/fluids.lua | 61 + data/actions/scripts/other/food.lua | 79 + data/actions/scripts/other/music.lua | 5 + data/actions/scripts/other/partyhat.lua | 7 + data/actions/scripts/other/partytrumpet.lua | 7 + data/actions/scripts/other/piggybank.lua | 11 + data/actions/scripts/other/potions.lua | 148 + data/actions/scripts/other/spellbook.lua | 29 + data/actions/scripts/other/surprisebag.lua | 24 + data/actions/scripts/other/teleport.lua | 11 + data/actions/scripts/other/trap.lua | 5 + data/actions/scripts/other/watch.lua | 20 + data/actions/scripts/other/windows.lua | 8 + data/actions/scripts/quests/annihilator.lua | 47 + data/actions/scripts/quests/quests.lua | 38 + data/actions/scripts/tools/fishing.lua | 17 + data/actions/scripts/tools/machete.lua | 8 + data/actions/scripts/tools/pick.lua | 9 + data/actions/scripts/tools/rope.lua | 21 + data/actions/scripts/tools/scythe.lua | 9 + data/actions/scripts/tools/shovel.lua | 18 + data/creaturescripts/creaturescripts.xml | 6 + data/creaturescripts/lib/creaturescripts.lua | 1 + data/creaturescripts/scripts/firstitems.lua | 13 + data/creaturescripts/scripts/login.lua | 4 + data/creaturescripts/scripts/playerdeath.lua | 58 + data/global.lua | 1432 + data/globalevents/globalevents.xml | 8 + data/globalevents/lib/globalevents.lua | 1 + data/globalevents/scripts/record.lua | 4 + data/globalevents/scripts/startup.lua | 3 + data/items/items.otb | Bin 0 -> 859564 bytes data/items/items.xml | 26423 ++++++++++++++++ data/items/items.xsd | 37 + data/monster/Amazons/amazon.xml | 48 + data/monster/Amazons/valkyrie.xml | 59 + data/monster/Annelids/carrion worm.xml | 40 + data/monster/Annelids/rotworm.xml | 35 + data/monster/Apes/kongra.xml | 50 + data/monster/Apes/merlkin.xml | 67 + data/monster/Apes/sibang.xml | 57 + data/monster/Arachnids/crystal spider.xml | 70 + data/monster/Arachnids/giant spider.xml | 61 + data/monster/Arachnids/poison spider.xml | 36 + data/monster/Arachnids/sandstone scorpion.xml | 61 + data/monster/Arachnids/scorpion.xml | 35 + data/monster/Arachnids/spider.xml | 31 + data/monster/Arachnids/tarantula.xml | 50 + data/monster/Arachnids/wailing widow.xml | 66 + data/monster/Arena/greenhorn/achad.xml | 33 + .../Arena/greenhorn/axeitus headbanger.xml | 36 + data/monster/Arena/greenhorn/bloodpaw.xml | 29 + data/monster/Arena/greenhorn/bovinus.xml | 28 + .../greenhorn/colerian the barbarian.xml | 37 + .../Arena/greenhorn/cursed gladiator.xml | 36 + data/monster/Arena/greenhorn/frostfur.xml | 29 + .../Arena/greenhorn/orcus the cruel.xml | 33 + data/monster/Arena/greenhorn/rocky.xml | 38 + .../monster/Arena/greenhorn/the hairy one.xml | 33 + data/monster/Arena/scrapper/avalanche.xml | 44 + data/monster/Arena/scrapper/drasilla.xml | 47 + .../Arena/scrapper/grimgor guteater.xml | 37 + .../Arena/scrapper/kreebosh the exile.xml | 53 + data/monster/Arena/scrapper/slim.xml | 40 + .../Arena/scrapper/spirit of earth.xml | 32 + .../monster/Arena/scrapper/spirit of fire.xml | 39 + .../Arena/scrapper/spirit of water.xml | 39 + .../Arena/scrapper/the dark dancer.xml | 56 + data/monster/Arena/scrapper/the hag.xml | 51 + .../Arena/warlord/darakan the executioner.xml | 42 + data/monster/Arena/warlord/deathbringer.xml | 55 + .../warlord/fallen mooh'tah master ghar.xml | 49 + .../monster/Arena/warlord/gnorre chyllson.xml | 44 + .../Arena/warlord/norgle glacierbeard.xml | 37 + data/monster/Arena/warlord/svoren the mad.xml | 39 + .../Arena/warlord/the masked marauder.xml | 43 + .../monster/Arena/warlord/the obliverator.xml | 48 + data/monster/Arena/warlord/the pit lord.xml | 44 + data/monster/Arena/warlord/webster.xml | 43 + .../Barbarians/barbarian bloodwalker.xml | 61 + .../Barbarians/barbarian brutetamer.xml | 72 + .../Barbarians/barbarian headsplitter.xml | 59 + .../Barbarians/barbarian skullhunter.xml | 56 + data/monster/Bears/bear.xml | 39 + data/monster/Bears/panda.xml | 40 + data/monster/Bears/polar bear.xml | 39 + data/monster/Bears/undead cavebear.xml | 43 + data/monster/Bio-Elementals/bog raider.xml | 70 + data/monster/Bio-Elementals/carniphila.xml | 54 + data/monster/Bio-Elementals/defiler.xml | 62 + .../Bio-Elementals/haunted treeling.xml | 60 + data/monster/Bio-Elementals/slime summon.xml | 33 + data/monster/Bio-Elementals/slime.xml | 38 + data/monster/Bio-Elementals/slug.xml | 40 + .../Bio-Elementals/son of verminor.xml | 40 + data/monster/Bio-Elementals/spit nettle.xml | 47 + data/monster/Birds/chicken.xml | 31 + data/monster/Birds/flamingo.xml | 20 + data/monster/Birds/parrot.xml | 40 + data/monster/Birds/penguin.xml | 32 + data/monster/Birds/seagull.xml | 24 + data/monster/Birds/terror bird.xml | 44 + data/monster/Blobs/acid blob.xml | 46 + data/monster/Blobs/death blob.xml | 37 + data/monster/Blobs/mercury blob.xml | 51 + data/monster/Bonelords/bonelord.xml | 73 + data/monster/Bonelords/braindeath.xml | 83 + data/monster/Bonelords/elder bonelord.xml | 84 + data/monster/Bonelords/gazer.xml | 48 + data/monster/Bosses/annihilon.xml | 65 + data/monster/Bosses/apprentice sheng.xml | 62 + data/monster/Bosses/barbaria.xml | 64 + data/monster/Bosses/baron brute.xml | 38 + data/monster/Bosses/big boss trolliver.xml | 35 + data/monster/Bosses/bones.xml | 61 + data/monster/Bosses/brutus bloodbeard.xml | 36 + .../Bosses/chizzoron the distorter.xml | 67 + data/monster/Bosses/countess sorrow.xml | 51 + data/monster/Bosses/deadeye devious.xml | 49 + data/monster/Bosses/demodras.xml | 74 + data/monster/Bosses/dharalion.xml | 80 + data/monster/Bosses/dire penguin.xml | 39 + data/monster/Bosses/dracola.xml | 66 + data/monster/Bosses/dreadwing.xml | 32 + data/monster/Bosses/esmeralda.xml | 64 + data/monster/Bosses/fatality.xml | 33 + data/monster/Bosses/fernfang.xml | 74 + data/monster/Bosses/ferumbras.xml | 90 + data/monster/Bosses/fluffy.xml | 56 + data/monster/Bosses/foreman kneebiter.xml | 32 + data/monster/Bosses/general murius.xml | 65 + data/monster/Bosses/ghazbaran.xml | 82 + data/monster/Bosses/glitterscale.xml | 41 + data/monster/Bosses/golgordan.xml | 48 + .../monster/Bosses/grand mother foulscale.xml | 77 + data/monster/Bosses/grorlam.xml | 56 + data/monster/Bosses/hairman the huge.xml | 52 + data/monster/Bosses/handmaiden.xml | 56 + data/monster/Bosses/hellgorak.xml | 73 + data/monster/Bosses/heoni.xml | 48 + data/monster/Bosses/hide.xml | 48 + data/monster/Bosses/koshei the deathless.xml | 77 + data/monster/Bosses/latrivan.xml | 50 + data/monster/Bosses/lethal lissy.xml | 44 + data/monster/Bosses/leviathan.xml | 58 + data/monster/Bosses/lord of the elements.xml | 61 + data/monster/Bosses/mad technomancer.xml | 55 + data/monster/Bosses/madareth.xml | 41 + data/monster/Bosses/man in the cave.xml | 59 + data/monster/Bosses/massacre.xml | 44 + data/monster/Bosses/minishabaal.xml | 77 + data/monster/Bosses/morgaroth.xml | 89 + data/monster/Bosses/mr. punish.xml | 34 + data/monster/Bosses/munster.xml | 38 + data/monster/Bosses/necropharus.xml | 74 + data/monster/Bosses/orshabaal.xml | 130 + data/monster/Bosses/pythius the rotten.xml | 69 + data/monster/Bosses/ron the ripper.xml | 34 + data/monster/Bosses/rotworm queen.xml | 28 + data/monster/Bosses/shardhead.xml | 56 + data/monster/Bosses/snake god essence.xml | 44 + data/monster/Bosses/snake thing.xml | 46 + data/monster/Bosses/stonecracker.xml | 62 + data/monster/Bosses/the abomination.xml | 56 + data/monster/Bosses/the blightfather.xml | 54 + data/monster/Bosses/the bloodtusk.xml | 43 + data/monster/Bosses/the count.xml | 51 + data/monster/Bosses/the evil eye.xml | 80 + data/monster/Bosses/the handmaiden.xml | 55 + data/monster/Bosses/the horned fox.xml | 71 + data/monster/Bosses/the imperor.xml | 88 + data/monster/Bosses/the many.xml | 69 + data/monster/Bosses/the noxious spawn.xml | 70 + data/monster/Bosses/the old widow.xml | 67 + data/monster/Bosses/the plasmother.xml | 62 + data/monster/Bosses/the snapper.xml | 45 + data/monster/Bosses/thul.xml | 46 + data/monster/Bosses/tibia bug.xml | 49 + data/monster/Bosses/tiquandas revenge.xml | 53 + data/monster/Bosses/undead minion.xml | 42 + data/monster/Bosses/ungreez.xml | 65 + data/monster/Bosses/ushuriel.xml | 73 + data/monster/Bosses/warlord ruzad.xml | 52 + data/monster/Bosses/xenia.xml | 54 + data/monster/Bosses/yakchal.xml | 80 + data/monster/Bosses/zugurosh.xml | 76 + data/monster/Bosses/zulazza the corruptor.xml | 80 + data/monster/Canines/crystal wolf.xml | 56 + data/monster/Canines/dog.xml | 24 + data/monster/Canines/gnarlhound.xml | 33 + data/monster/Canines/husky.xml | 26 + data/monster/Canines/war wolf.xml | 40 + data/monster/Canines/werewolf.xml | 69 + data/monster/Canines/winter wolf.xml | 35 + data/monster/Canines/wolf.xml | 35 + data/monster/Chakoyas/chakoya toolshaper.xml | 54 + data/monster/Chakoyas/chakoya tribewarden.xml | 51 + data/monster/Chakoyas/chakoya windcaller.xml | 55 + data/monster/Crustaceans/blood crab.xml | 38 + data/monster/Crustaceans/crab.xml | 38 + .../Crustaceans/crustacea gigantica.xml | 43 + data/monster/Cryo-Elementals/ice golem.xml | 63 + data/monster/Cultists/acolyte of the cult.xml | 67 + data/monster/Cultists/adept of the cult.xml | 69 + .../Cultists/enlightened of the cult.xml | 70 + data/monster/Cultists/novice of the cult.xml | 64 + data/monster/Demons/dark torturer.xml | 70 + data/monster/Demons/demon.xml | 103 + data/monster/Demons/destroyer.xml | 74 + data/monster/Demons/diabolic imp.xml | 79 + data/monster/Demons/fire devil.xml | 58 + data/monster/Demons/fury.xml | 83 + data/monster/Demons/gozzler.xml | 61 + data/monster/Demons/hand of cursed fate.xml | 73 + data/monster/Demons/hellhound.xml | 76 + data/monster/Demons/hellspawn.xml | 71 + data/monster/Demons/juggernaut.xml | 78 + data/monster/Demons/nightmare scion.xml | 68 + data/monster/Demons/nightmare.xml | 78 + data/monster/Demons/nightstalker.xml | 85 + data/monster/Demons/plaguesmith.xml | 88 + data/monster/Demons/shaburak demon.xml | 74 + data/monster/Demons/shaburak prince.xml | 79 + data/monster/Djinns/blue djinn.xml | 70 + data/monster/Djinns/efreet.xml | 86 + data/monster/Djinns/green djinn.xml | 71 + data/monster/Djinns/marid.xml | 89 + data/monster/Dragons/dragon hatchling.xml | 52 + .../monster/Dragons/dragon lord hatchling.xml | 65 + data/monster/Dragons/dragon lord.xml | 76 + data/monster/Dragons/dragon.xml | 74 + data/monster/Dragons/draptor.xml | 57 + .../Dragons/frost dragon hatchling.xml | 50 + data/monster/Dragons/frost dragon.xml | 83 + data/monster/Dragons/ghastly dragon.xml | 86 + data/monster/Dragons/undead dragon.xml | 98 + data/monster/Dragons/wyrm.xml | 71 + data/monster/Dwarves/dwarf geomancer.xml | 67 + data/monster/Dwarves/dwarf guard.xml | 51 + data/monster/Dwarves/dwarf soldier.xml | 51 + data/monster/Dwarves/dwarf.xml | 45 + data/monster/Dworcs/dworc fleshhunter.xml | 51 + data/monster/Dworcs/dworc venomsniper.xml | 52 + data/monster/Dworcs/dworc voodoomaster.xml | 76 + data/monster/Elephants/elephant.xml | 41 + data/monster/Elephants/mammoth.xml | 43 + data/monster/Elves/elf arcanist.xml | 77 + data/monster/Elves/elf scout.xml | 55 + data/monster/Elves/elf.xml | 52 + .../charged energy elemental.xml | 50 + .../Energy-Elementals/energy elemental.xml | 67 + .../Energy-Elementals/energy overlord.xml | 56 + .../massive energy elemental.xml | 58 + .../overcharged energy elemental.xml | 57 + data/monster/Felines/cat.xml | 28 + data/monster/Felines/lion.xml | 38 + data/monster/Felines/midnight panther.xml | 44 + data/monster/Felines/tiger.xml | 37 + data/monster/Frogs/azure frog.xml | 35 + data/monster/Frogs/coral frog.xml | 31 + data/monster/Frogs/crimson frog.xml | 31 + data/monster/Frogs/green frog.xml | 28 + data/monster/Frogs/orchid frog.xml | 28 + data/monster/Frogs/toad.xml | 44 + .../Geo-Elementals/damaged worker golem.xml | 54 + .../Geo-Elementals/earth elemental.xml | 61 + .../monster/Geo-Elementals/earth overlord.xml | 56 + data/monster/Geo-Elementals/gargoyle.xml | 61 + .../Geo-Elementals/jagged earth elemental.xml | 57 + .../massive earth elemental.xml | 64 + data/monster/Geo-Elementals/medusa.xml | 76 + .../Geo-Elementals/muddy earth elemental.xml | 50 + data/monster/Geo-Elementals/stone golem.xml | 47 + data/monster/Geo-Elementals/war golem.xml | 88 + data/monster/Geo-Elementals/worker golem.xml | 71 + data/monster/Ghosts/ghost.xml | 51 + data/monster/Ghosts/phantasm summon.xml | 57 + data/monster/Ghosts/phantasm.xml | 77 + data/monster/Ghosts/spectre.xml | 75 + data/monster/Ghosts/wisp.xml | 54 + data/monster/Giants/behemoth.xml | 74 + data/monster/Giants/cyclops drone.xml | 51 + data/monster/Giants/cyclops smith.xml | 55 + data/monster/Giants/cyclops.xml | 52 + data/monster/Giants/frost giant.xml | 52 + data/monster/Giants/frost giantess.xml | 57 + data/monster/Giants/yeti.xml | 50 + data/monster/Goblins/goblin assassin.xml | 56 + data/monster/Goblins/goblin leader.xml | 52 + data/monster/Goblins/goblin scavenger.xml | 56 + data/monster/Goblins/goblin.xml | 53 + data/monster/Goblins/grynch clan goblin.xml | 102 + data/monster/Grunts/boar.xml | 31 + data/monster/Grunts/pig.xml | 28 + .../monster/Hydro-Elementals/ice overlord.xml | 55 + .../massive water elemental.xml | 52 + .../roaring water elemental.xml | 52 + .../slick water elemental.xml | 55 + .../Hydro-Elementals/water elemental.xml | 52 + data/monster/Insects/ancient scarab.xml | 69 + data/monster/Insects/blue butterfly.xml | 29 + data/monster/Insects/brimstone bug.xml | 69 + data/monster/Insects/bug.xml | 31 + data/monster/Insects/centipede.xml | 36 + data/monster/Insects/cockroach.xml | 28 + data/monster/Insects/insect swarm.xml | 35 + data/monster/Insects/insectoid scout.xml | 43 + data/monster/Insects/lancer beetle.xml | 54 + data/monster/Insects/larva.xml | 37 + data/monster/Insects/purple butterfly.xml | 29 + data/monster/Insects/red butterfly.xml | 29 + data/monster/Insects/sandcrawler.xml | 34 + data/monster/Insects/scarab.xml | 51 + data/monster/Insects/terramite.xml | 46 + data/monster/Insects/wasp.xml | 34 + data/monster/Insects/yellow butterfly.xml | 24 + .../Isle of Evil/berserker chicken.xml | 53 + data/monster/Isle of Evil/boogey.xml | 63 + data/monster/Isle of Evil/demon parrot.xml | 47 + data/monster/Isle of Evil/dirtbeard.xml | 47 + data/monster/Isle of Evil/docter perhaps.xml | 59 + data/monster/Isle of Evil/doom deer.xml | 43 + data/monster/Isle of Evil/evil mastermind.xml | 69 + data/monster/Isle of Evil/evil sheep lord.xml | 52 + data/monster/Isle of Evil/evil sheep.xml | 42 + data/monster/Isle of Evil/hot dog.xml | 48 + data/monster/Isle of Evil/infernal frog.xml | 43 + data/monster/Isle of Evil/killer rabbit.xml | 41 + data/monster/Isle of Evil/mephiles.xml | 54 + data/monster/Isle of Evil/monstor.xml | 51 + data/monster/Isle of Evil/vampire pig.xml | 47 + data/monster/Lizards/battlemaster zunzu.xml | 55 + data/monster/Lizards/draken abomination.xml | 76 + data/monster/Lizards/draken elite.xml | 77 + data/monster/Lizards/draken spellweaver.xml | 67 + data/monster/Lizards/draken warmaster.xml | 54 + data/monster/Lizards/eternal guardian.xml | 43 + data/monster/Lizards/lizard abomination.xml | 48 + data/monster/Lizards/lizard chosen.xml | 65 + data/monster/Lizards/lizard dragon priest.xml | 68 + data/monster/Lizards/lizard high guard.xml | 63 + data/monster/Lizards/lizard legionnaire.xml | 54 + data/monster/Lizards/lizard sentinel.xml | 53 + data/monster/Lizards/lizard snakecharmer.xml | 69 + data/monster/Lizards/lizard templar.xml | 47 + data/monster/Lizards/lizard zaogun.xml | 58 + data/monster/Lizards/wyvern.xml | 60 + data/monster/Minotaurs/minotaur archer.xml | 53 + data/monster/Minotaurs/minotaur guard.xml | 51 + data/monster/Minotaurs/minotaur mage.xml | 67 + data/monster/Minotaurs/minotaur.xml | 48 + data/monster/Misc/badger.xml | 29 + data/monster/Misc/bat.xml | 30 + data/monster/Misc/deer.xml | 29 + data/monster/Misc/dromedary.xml | 32 + data/monster/Misc/hacker.xml | 60 + data/monster/Misc/halloweenhare.xml | 85 + data/monster/Misc/horse(brown).xml | 24 + data/monster/Misc/horse(fire).xml | 24 + data/monster/Misc/horse(grey).xml | 24 + data/monster/Misc/hyaena.xml | 28 + data/monster/Misc/rabbit.xml | 28 + data/monster/Misc/silver rabbit.xml | 27 + data/monster/Misc/skunk.xml | 32 + data/monster/Misc/squirrel.xml | 30 + data/monster/Misc/white deer.xml | 35 + data/monster/Misc/yalahari.xml | 35 + data/monster/Monks/dark monk.xml | 61 + data/monster/Monks/monk.xml | 57 + data/monster/Mutated/mutated bat.xml | 60 + data/monster/Mutated/mutated human.xml | 65 + data/monster/Mutated/mutated rat.xml | 67 + data/monster/Mutated/mutated tiger.xml | 61 + data/monster/Necromancers/necromancer.xml | 71 + data/monster/Necromancers/priestess.xml | 71 + data/monster/Orcs/orc berserker.xml | 47 + data/monster/Orcs/orc leader.xml | 53 + data/monster/Orcs/orc marauder.xml | 51 + data/monster/Orcs/orc rider.xml | 49 + data/monster/Orcs/orc shaman.xml | 61 + data/monster/Orcs/orc spearman.xml | 45 + data/monster/Orcs/orc warlord.xml | 70 + data/monster/Orcs/orc warrior.xml | 46 + data/monster/Orcs/orc.xml | 44 + data/monster/Outlaws/assassin.xml | 62 + data/monster/Outlaws/bandit.xml | 45 + data/monster/Outlaws/black knight.xml | 75 + data/monster/Outlaws/crazed beggar.xml | 53 + data/monster/Outlaws/feverish citizen.xml | 52 + data/monster/Outlaws/gang member.xml | 44 + data/monster/Outlaws/gladiator.xml | 51 + data/monster/Outlaws/hero.xml | 77 + data/monster/Outlaws/hunter.xml | 55 + data/monster/Outlaws/nomad.xml | 48 + data/monster/Outlaws/poacher.xml | 42 + data/monster/Outlaws/primitive.xml | 66 + data/monster/Outlaws/smuggler.xml | 45 + data/monster/Outlaws/stalker.xml | 49 + data/monster/Outlaws/wild warrior.xml | 50 + data/monster/Pharaohs/ashmunrah.xml | 76 + data/monster/Pharaohs/dipthrah.xml | 69 + data/monster/Pharaohs/mahrdis.xml | 79 + data/monster/Pharaohs/morguthis.xml | 80 + data/monster/Pharaohs/omruc.xml | 77 + data/monster/Pharaohs/rahemos.xml | 88 + data/monster/Pharaohs/thalas.xml | 71 + data/monster/Pharaohs/vashresamun.xml | 65 + data/monster/Pirates/pirate buccaneer.xml | 62 + data/monster/Pirates/pirate corsair.xml | 63 + data/monster/Pirates/pirate cutthroat.xml | 57 + data/monster/Pirates/pirate ghost.xml | 59 + data/monster/Pirates/pirate marauder.xml | 60 + data/monster/Pirates/pirate skeleton.xml | 37 + .../blazing fire elemental.xml | 46 + .../blistering fire elemental.xml | 44 + .../Pyro-Elementals/fire elemental.xml | 40 + .../monster/Pyro-Elementals/fire overlord.xml | 57 + .../Pyro-Elementals/hellfire fighter.xml | 63 + .../massive fire elemental.xml | 40 + .../Quaras/quara constrictor scout.xml | 46 + data/monster/Quaras/quara constrictor.xml | 58 + .../Quaras/quara hydromancer scout.xml | 60 + data/monster/Quaras/quara hydromancer.xml | 65 + data/monster/Quaras/quara mantassin scout.xml | 49 + data/monster/Quaras/quara mantassin.xml | 57 + data/monster/Quaras/quara pincher scout.xml | 54 + data/monster/Quaras/quara pincher.xml | 57 + data/monster/Quaras/quara predator scout.xml | 51 + data/monster/Quaras/quara predator.xml | 62 + data/monster/Rats/cave rat.xml | 37 + data/monster/Rats/rat.xml | 39 + data/monster/Reptiles/crocodile.xml | 39 + data/monster/Reptiles/hydra.xml | 72 + data/monster/Reptiles/killer caiman.xml | 47 + data/monster/Rifts/rift brood.xml | 42 + data/monster/Rifts/rift scythe.xml | 54 + data/monster/Rifts/rift worm.xml | 26 + data/monster/Serpents/cobra.xml | 38 + data/monster/Serpents/deepling scout.xml | 56 + data/monster/Serpents/sea serpent.xml | 75 + data/monster/Serpents/serpent spawn.xml | 94 + data/monster/Serpents/snake.xml | 33 + data/monster/Serpents/young sea serpent.xml | 70 + data/monster/Shapeshifters/mimic.xml | 21 + data/monster/Sheeps/black sheep.xml | 30 + data/monster/Sheeps/sheep.xml | 34 + data/monster/Skeletons/betrayed wraith.xml | 83 + data/monster/Skeletons/bonebeast.xml | 66 + data/monster/Skeletons/demon skeleton.xml | 46 + data/monster/Skeletons/dreadbeast.xml | 41 + data/monster/Skeletons/lost soul.xml | 83 + data/monster/Skeletons/skeleton warrior.xml | 43 + data/monster/Skeletons/skeleton.xml | 40 + data/monster/Skeletons/undead gladiator.xml | 72 + data/monster/Sorcerers/dark apprentice.xml | 61 + data/monster/Sorcerers/dark magician.xml | 63 + data/monster/Sorcerers/ice witch.xml | 72 + data/monster/Sorcerers/infernalist.xml | 87 + data/monster/Sorcerers/mad mage.xml | 64 + data/monster/Sorcerers/mad scientist.xml | 68 + data/monster/Sorcerers/warlock.xml | 98 + data/monster/Sorcerers/witch.xml | 59 + data/monster/Tortoises/thornback tortoise.xml | 40 + data/monster/Tortoises/tortoise.xml | 40 + data/monster/Traps/deathslicer.xml | 42 + data/monster/Traps/eye of the seven.xml | 39 + data/monster/Traps/flamethrower.xml | 43 + data/monster/Traps/lavahole.xml | 42 + data/monster/Traps/magicthrower.xml | 43 + data/monster/Traps/plaguethrower.xml | 44 + data/monster/Traps/shredderthrower.xml | 43 + data/monster/Trolls/frost troll.xml | 45 + data/monster/Trolls/island troll.xml | 46 + data/monster/Trolls/swamp troll.xml | 42 + data/monster/Trolls/troll champion.xml | 48 + data/monster/Trolls/troll.xml | 49 + data/monster/Undead Humanoids/banshee.xml | 78 + .../monster/Undead Humanoids/blightwalker.xml | 72 + .../Undead Humanoids/crypt shambler.xml | 53 + data/monster/Undead Humanoids/ghoul.xml | 53 + data/monster/Undead Humanoids/grim reaper.xml | 79 + data/monster/Undead Humanoids/lich.xml | 80 + data/monster/Undead Humanoids/mummy.xml | 62 + data/monster/Undead Humanoids/souleater.xml | 74 + .../monster/Undead Humanoids/tomb servant.xml | 54 + .../Undead Humanoids/undead mine worker.xml | 38 + .../Undead Humanoids/undead prospector.xml | 47 + .../Undead Humanoids/vampire bride.xml | 66 + data/monster/Undead Humanoids/vampire.xml | 74 + data/monster/Undead Humanoids/zombie.xml | 56 + data/monster/monsters.xml | 653 + data/movements/lib/movements.lua | 2 + data/movements/movements.xml | 978 + data/movements/scripts/citizen.lua | 6 + data/movements/scripts/closingdoor.lua | 26 + data/movements/scripts/decay.lua | 5 + data/movements/scripts/dough.lua | 7 + data/movements/scripts/drowning.lua | 14 + data/movements/scripts/snow.lua | 11 + data/movements/scripts/swimming.lua | 17 + data/movements/scripts/tiles.lua | 53 + data/movements/scripts/trap.lua | 27 + data/movements/scripts/walkback.lua | 6 + data/npc/Alice.xml | 5 + data/npc/Deruno.xml | 9 + data/npc/Eryn.xml | 9 + data/npc/Riona.xml | 9 + data/npc/The Forgotten King.xml | 5 + data/npc/The Oracle.xml | 200 + data/npc/Tyoric.xml | 10 + data/npc/Varkhal.xml | 5 + data/npc/lib/npc.lua | 127 + data/npc/lib/npcsystem/keywordhandler.lua | 175 + data/npc/lib/npcsystem/modules.lua | 1177 + data/npc/lib/npcsystem/npchandler.lua | 597 + data/npc/lib/npcsystem/npcsystem.lua | 177 + data/npc/scripts/addons.lua | 45 + data/npc/scripts/bless.lua | 32 + data/npc/scripts/default.lua | 10 + data/npc/scripts/promotion.lua | 14 + data/npc/scripts/runes.lua | 131 + data/raids/raids.xml | 7 + data/spells/lib/spells.lua | 192 + data/spells/scripts/attack/avalanche.lua | 12 + data/spells/scripts/attack/berserk.lua | 11 + data/spells/scripts/attack/death strike.lua | 17 + data/spells/scripts/attack/divine caldera.lua | 11 + data/spells/scripts/attack/divine missile.lua | 17 + data/spells/scripts/attack/energy beam.lua | 11 + data/spells/scripts/attack/energy bomb.lua | 12 + data/spells/scripts/attack/energy field.lua | 9 + data/spells/scripts/attack/energy strike.lua | 17 + data/spells/scripts/attack/energy wall.lua | 12 + data/spells/scripts/attack/energy wave.lua | 12 + data/spells/scripts/attack/envenom.lua | 17 + data/spells/scripts/attack/eternal winter.lua | 11 + data/spells/scripts/attack/ethereal spear.lua | 9 + data/spells/scripts/attack/explosion.lua | 12 + data/spells/scripts/attack/fierce berserk.lua | 11 + data/spells/scripts/attack/fire bomb.lua | 12 + data/spells/scripts/attack/fire field.lua | 9 + data/spells/scripts/attack/fire wall.lua | 12 + data/spells/scripts/attack/fire wave.lua | 11 + data/spells/scripts/attack/fireball.lua | 9 + data/spells/scripts/attack/flame strike.lua | 17 + .../scripts/attack/great energy beam.lua | 11 + data/spells/scripts/attack/great fireball.lua | 12 + data/spells/scripts/attack/groundshaker.lua | 11 + .../scripts/attack/heavy magic missile.lua | 9 + data/spells/scripts/attack/hells core.lua | 11 + data/spells/scripts/attack/holy missile.lua | 9 + data/spells/scripts/attack/ice strike.lua | 17 + data/spells/scripts/attack/ice wave.lua | 11 + data/spells/scripts/attack/icicle.lua | 9 + .../scripts/attack/light magic missile.lua | 9 + data/spells/scripts/attack/poison bomb.lua | 12 + data/spells/scripts/attack/poison field.lua | 9 + data/spells/scripts/attack/poison wall.lua | 12 + .../scripts/attack/rage of the skies.lua | 11 + data/spells/scripts/attack/soul fire.lua | 13 + data/spells/scripts/attack/stalagmite.lua | 9 + data/spells/scripts/attack/stone shower.lua | 12 + data/spells/scripts/attack/sudden death.lua | 9 + data/spells/scripts/attack/terra strike.lua | 17 + data/spells/scripts/attack/terra wave.lua | 11 + data/spells/scripts/attack/thunderstorm.lua | 12 + .../spells/scripts/attack/whirlwind throw.lua | 9 + .../spells/scripts/attack/wrath of nature.lua | 11 + data/spells/scripts/custom/apocalypse.lua | 44 + data/spells/scripts/custom/combustion.lua | 15 + data/spells/scripts/custom/drunk.lua | 13 + data/spells/scripts/custom/magic prison.lua | 16 + data/spells/scripts/custom/polymorph.lua | 31 + data/spells/scripts/healing/antidote rune.lua | 8 + data/spells/scripts/healing/antidote.lua | 8 + .../spells/scripts/healing/divine healing.lua | 17 + data/spells/scripts/healing/heal friend.lua | 10 + .../scripts/healing/intense healing rune.lua | 11 + .../scripts/healing/intense healing.lua | 10 + data/spells/scripts/healing/light healing.lua | 10 + data/spells/scripts/healing/mass healing.lua | 23 + .../scripts/healing/ultimate healing rune.lua | 21 + .../scripts/healing/ultimate healing.lua | 20 + .../spells/scripts/healing/wound cleasing.lua | 24 + data/spells/scripts/party/enchant.lua | 58 + data/spells/scripts/party/heal.lua | 59 + data/spells/scripts/party/protect.lua | 58 + data/spells/scripts/party/train.lua | 59 + .../scripts/summon/animate dead rune.lua | 27 + data/spells/scripts/support/blood rage.lua | 17 + .../scripts/support/cancel invisibility.lua | 10 + data/spells/scripts/support/challenge.lua | 12 + data/spells/scripts/support/charge.lua | 12 + .../scripts/support/desintegrate rune.lua | 25 + .../scripts/support/destroy field rune.lua | 26 + data/spells/scripts/support/great light.lua | 13 + data/spells/scripts/support/haste.lua | 12 + data/spells/scripts/support/invisible.lua | 11 + data/spells/scripts/support/light.lua | 5 + data/spells/scripts/support/magic rope.lua | 20 + data/spells/scripts/support/magic shield.lua | 11 + .../scripts/support/magic wall rune.lua | 7 + data/spells/scripts/support/paralyze rune.lua | 12 + data/spells/scripts/support/protector.lua | 25 + data/spells/scripts/support/sharpshooter.lua | 25 + data/spells/scripts/support/strong haste.lua | 12 + data/spells/scripts/support/swift foot.lua | 20 + .../spells/scripts/support/ultimate light.lua | 5 + .../scripts/support/wild growth rune.lua | 7 + data/spells/spells.xml | 567 + data/talkactions/lib/talkactions.lua | 1 + data/talkactions/scripts/animationeffect.lua | 56 + data/talkactions/scripts/buyprem.lua | 20 + data/talkactions/scripts/changesex.lua | 20 + data/talkactions/scripts/deathlist.lua | 50 + data/talkactions/scripts/leavehouse.lua | 14 + data/talkactions/scripts/magiceffect.lua | 5 + data/talkactions/scripts/save.lua | 19 + data/talkactions/scripts/uptime.lua | 9 + data/talkactions/talkactions.xml | 14 + data/weapons/lib/weapons.lua | 0 data/weapons/scripts/explosive_arrow.lua | 14 + data/weapons/scripts/poison_arrow.lua | 14 + data/weapons/scripts/viper_star.lua | 31 + data/weapons/weapons.xml | 528 + data/world/forgotten-house.xml | 113 + data/world/forgotten-spawn.xml | 1351 + data/world/forgotten.otbm | Bin 0 -> 3719634 bytes schema.sql | 431 + src/account.h | 39 + src/actions.cpp | 592 + src/actions.h | 122 + src/admin.cpp | 719 + src/admin.h | 221 + src/ban.cpp | 364 + src/ban.h | 160 + src/baseevents.cpp | 235 + src/baseevents.h | 92 + src/beds.cpp | 330 + src/beds.h | 113 + src/chat.cpp | 677 + src/chat.h | 136 + src/combat.cpp | 1535 + src/combat.h | 390 + src/commands.cpp | 1505 + src/commands.h | 110 + src/condition.cpp | 1988 ++ src/condition.h | 465 + src/configmanager.cpp | 247 + src/configmanager.h | 158 + src/connection.cpp | 527 + src/connection.h | 182 + src/const.h | 626 + src/container.cpp | 931 + src/container.h | 190 + src/creature.cpp | 1765 ++ src/creature.h | 589 + src/creatureevent.cpp | 451 + src/creatureevent.h | 105 + src/cylinder.cpp | 68 + src/cylinder.h | 256 + src/database.cpp | 120 + src/database.h | 381 + src/databasemanager.cpp | 503 + src/databasemanager.h | 52 + src/databasemysql.cpp | 322 + src/databasemysql.h | 92 + src/definitions.h | 177 + src/depotchest.cpp | 81 + src/depotchest.h | 59 + src/depotlocker.cpp | 79 + src/depotlocker.h | 67 + src/enums.h | 395 + src/fileloader.cpp | 536 + src/fileloader.h | 404 + src/game.cpp | 7025 ++++ src/game.h | 663 + src/globalevent.cpp | 367 + src/globalevent.h | 110 + src/guild.cpp | 84 + src/guild.h | 87 + src/house.cpp | 985 + src/house.h | 345 + src/housetile.cpp | 135 + src/housetile.h | 52 + src/inbox.cpp | 59 + src/inbox.h | 44 + src/ioguild.cpp | 65 + src/ioguild.h | 41 + src/iologindata.cpp | 1345 + src/iologindata.h | 109 + src/iomap.cpp | 528 + src/iomap.h | 155 + src/iomapserialize.cpp | 823 + src/iomapserialize.h | 58 + src/iomarket.cpp | 351 + src/iomarket.h | 66 + src/item.cpp | 1550 + src/item.h | 615 + src/itemloader.h | 191 + src/items.cpp | 1397 + src/items.h | 421 + src/logger.cpp | 75 + src/logger.h | 64 + src/luascript.cpp | 8581 +++++ src/luascript.h | 746 + src/mailbox.cpp | 195 + src/mailbox.h | 65 + src/map.cpp | 1323 + src/map.h | 308 + src/md5.cpp | 262 + src/md5.h | 76 + src/modalwindow.cpp | 104 + src/modalwindow.h | 65 + src/monster.cpp | 2051 ++ src/monster.h | 268 + src/monsters.cpp | 1425 + src/monsters.h | 181 + src/mounts.cpp | 156 + src/mounts.h | 89 + src/movement.cpp | 1081 + src/movement.h | 165 + src/networkmessage.cpp | 203 + src/networkmessage.h | 216 + src/npc.cpp | 3754 +++ src/npc.h | 673 + src/otpch.cpp | 19 + src/otpch.h | 56 + src/otserv.cpp | 443 + src/otsystem.h | 189 + src/outfit.cpp | 217 + src/outfit.h | 103 + src/outputmessage.cpp | 213 + src/outputmessage.h | 214 + src/party.cpp | 537 + src/party.h | 107 + src/player.cpp | 5245 +++ src/player.h | 1459 + src/position.cpp | 73 + src/position.h | 151 + src/protocol.cpp | 202 + src/protocol.h | 123 + src/protocolgame.cpp | 3609 +++ src/protocolgame.h | 370 + src/protocollogin.cpp | 202 + src/protocollogin.h | 64 + src/protocolold.cpp | 86 + src/protocolold.h | 80 + src/quests.cpp | 388 + src/quests.h | 154 + src/raids.cpp | 714 + src/raids.h | 253 + src/rsa.cpp | 141 + src/rsa.h | 47 + src/scheduler.cpp | 178 + src/scheduler.h | 114 + src/scriptmanager.cpp | 110 + src/scriptmanager.h | 35 + src/server.cpp | 269 + src/server.h | 167 + src/sha1.cpp | 569 + src/sha1.h | 84 + src/spawn.cpp | 473 + src/spawn.h | 118 + src/spells.cpp | 2330 ++ src/spells.h | 348 + src/status.cpp | 290 + src/status.h | 90 + src/talkaction.cpp | 181 + src/talkaction.h | 79 + src/tasks.cpp | 151 + src/tasks.h | 107 + src/teleport.cpp | 143 + src/teleport.h | 72 + src/templates.h | 92 + src/thing.cpp | 134 + src/thing.h | 157 + src/tile.cpp | 1911 ++ src/tile.h | 533 + src/tools.cpp | 1247 + src/tools.h | 122 + src/town.h | 118 + src/trashholder.cpp | 127 + src/trashholder.h | 63 + src/vocation.cpp | 308 + src/vocation.h | 148 + src/waitlist.cpp | 155 + src/waitlist.h | 60 + src/waypoints.h | 65 + src/weapons.cpp | 1192 + src/weapons.h | 210 + src/wildcardtree.cpp | 138 + src/wildcardtree.h | 46 + vc12/arch32.props | 13 + vc12/arch64.props | 12 + vc12/debug.props | 17 + vc12/release.props | 17 + vc12/settings.props | 84 + vc12/theforgottenserver.filters | 537 + vc12/theforgottenserver.sln | 26 + vc12/theforgottenserver.vcxproj | 303 + 824 files changed, 152170 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 config.lua create mode 100644 data/XML/admin.xml create mode 100644 data/XML/commands.xml create mode 100644 data/XML/mounts.xml create mode 100644 data/XML/outfits.xml create mode 100644 data/XML/quests.xml create mode 100644 data/XML/stages.xml create mode 100644 data/XML/vocations.xml create mode 100644 data/actions/actions.xml create mode 100644 data/actions/lib/actions.lua create mode 100644 data/actions/scripts/other/blueberrybush.lua create mode 100644 data/actions/scripts/other/changegold.lua create mode 100644 data/actions/scripts/other/constructionkits.lua create mode 100644 data/actions/scripts/other/createbread.lua create mode 100644 data/actions/scripts/other/decayto.lua create mode 100644 data/actions/scripts/other/destroy.lua create mode 100644 data/actions/scripts/other/dice.lua create mode 100644 data/actions/scripts/other/doors.lua create mode 100644 data/actions/scripts/other/fireworksrocket.lua create mode 100644 data/actions/scripts/other/fluids.lua create mode 100644 data/actions/scripts/other/food.lua create mode 100644 data/actions/scripts/other/music.lua create mode 100644 data/actions/scripts/other/partyhat.lua create mode 100644 data/actions/scripts/other/partytrumpet.lua create mode 100644 data/actions/scripts/other/piggybank.lua create mode 100644 data/actions/scripts/other/potions.lua create mode 100644 data/actions/scripts/other/spellbook.lua create mode 100644 data/actions/scripts/other/surprisebag.lua create mode 100644 data/actions/scripts/other/teleport.lua create mode 100644 data/actions/scripts/other/trap.lua create mode 100644 data/actions/scripts/other/watch.lua create mode 100644 data/actions/scripts/other/windows.lua create mode 100644 data/actions/scripts/quests/annihilator.lua create mode 100644 data/actions/scripts/quests/quests.lua create mode 100644 data/actions/scripts/tools/fishing.lua create mode 100644 data/actions/scripts/tools/machete.lua create mode 100644 data/actions/scripts/tools/pick.lua create mode 100644 data/actions/scripts/tools/rope.lua create mode 100644 data/actions/scripts/tools/scythe.lua create mode 100644 data/actions/scripts/tools/shovel.lua create mode 100644 data/creaturescripts/creaturescripts.xml create mode 100644 data/creaturescripts/lib/creaturescripts.lua create mode 100644 data/creaturescripts/scripts/firstitems.lua create mode 100644 data/creaturescripts/scripts/login.lua create mode 100644 data/creaturescripts/scripts/playerdeath.lua create mode 100644 data/global.lua create mode 100644 data/globalevents/globalevents.xml create mode 100644 data/globalevents/lib/globalevents.lua create mode 100644 data/globalevents/scripts/record.lua create mode 100644 data/globalevents/scripts/startup.lua create mode 100644 data/items/items.otb create mode 100644 data/items/items.xml create mode 100644 data/items/items.xsd create mode 100644 data/monster/Amazons/amazon.xml create mode 100644 data/monster/Amazons/valkyrie.xml create mode 100644 data/monster/Annelids/carrion worm.xml create mode 100644 data/monster/Annelids/rotworm.xml create mode 100644 data/monster/Apes/kongra.xml create mode 100644 data/monster/Apes/merlkin.xml create mode 100644 data/monster/Apes/sibang.xml create mode 100644 data/monster/Arachnids/crystal spider.xml create mode 100644 data/monster/Arachnids/giant spider.xml create mode 100644 data/monster/Arachnids/poison spider.xml create mode 100644 data/monster/Arachnids/sandstone scorpion.xml create mode 100644 data/monster/Arachnids/scorpion.xml create mode 100644 data/monster/Arachnids/spider.xml create mode 100644 data/monster/Arachnids/tarantula.xml create mode 100644 data/monster/Arachnids/wailing widow.xml create mode 100644 data/monster/Arena/greenhorn/achad.xml create mode 100644 data/monster/Arena/greenhorn/axeitus headbanger.xml create mode 100644 data/monster/Arena/greenhorn/bloodpaw.xml create mode 100644 data/monster/Arena/greenhorn/bovinus.xml create mode 100644 data/monster/Arena/greenhorn/colerian the barbarian.xml create mode 100644 data/monster/Arena/greenhorn/cursed gladiator.xml create mode 100644 data/monster/Arena/greenhorn/frostfur.xml create mode 100644 data/monster/Arena/greenhorn/orcus the cruel.xml create mode 100644 data/monster/Arena/greenhorn/rocky.xml create mode 100644 data/monster/Arena/greenhorn/the hairy one.xml create mode 100644 data/monster/Arena/scrapper/avalanche.xml create mode 100644 data/monster/Arena/scrapper/drasilla.xml create mode 100644 data/monster/Arena/scrapper/grimgor guteater.xml create mode 100644 data/monster/Arena/scrapper/kreebosh the exile.xml create mode 100644 data/monster/Arena/scrapper/slim.xml create mode 100644 data/monster/Arena/scrapper/spirit of earth.xml create mode 100644 data/monster/Arena/scrapper/spirit of fire.xml create mode 100644 data/monster/Arena/scrapper/spirit of water.xml create mode 100644 data/monster/Arena/scrapper/the dark dancer.xml create mode 100644 data/monster/Arena/scrapper/the hag.xml create mode 100644 data/monster/Arena/warlord/darakan the executioner.xml create mode 100644 data/monster/Arena/warlord/deathbringer.xml create mode 100644 data/monster/Arena/warlord/fallen mooh'tah master ghar.xml create mode 100644 data/monster/Arena/warlord/gnorre chyllson.xml create mode 100644 data/monster/Arena/warlord/norgle glacierbeard.xml create mode 100644 data/monster/Arena/warlord/svoren the mad.xml create mode 100644 data/monster/Arena/warlord/the masked marauder.xml create mode 100644 data/monster/Arena/warlord/the obliverator.xml create mode 100644 data/monster/Arena/warlord/the pit lord.xml create mode 100644 data/monster/Arena/warlord/webster.xml create mode 100644 data/monster/Barbarians/barbarian bloodwalker.xml create mode 100644 data/monster/Barbarians/barbarian brutetamer.xml create mode 100644 data/monster/Barbarians/barbarian headsplitter.xml create mode 100644 data/monster/Barbarians/barbarian skullhunter.xml create mode 100644 data/monster/Bears/bear.xml create mode 100644 data/monster/Bears/panda.xml create mode 100644 data/monster/Bears/polar bear.xml create mode 100644 data/monster/Bears/undead cavebear.xml create mode 100644 data/monster/Bio-Elementals/bog raider.xml create mode 100644 data/monster/Bio-Elementals/carniphila.xml create mode 100644 data/monster/Bio-Elementals/defiler.xml create mode 100644 data/monster/Bio-Elementals/haunted treeling.xml create mode 100644 data/monster/Bio-Elementals/slime summon.xml create mode 100644 data/monster/Bio-Elementals/slime.xml create mode 100644 data/monster/Bio-Elementals/slug.xml create mode 100644 data/monster/Bio-Elementals/son of verminor.xml create mode 100644 data/monster/Bio-Elementals/spit nettle.xml create mode 100644 data/monster/Birds/chicken.xml create mode 100644 data/monster/Birds/flamingo.xml create mode 100644 data/monster/Birds/parrot.xml create mode 100644 data/monster/Birds/penguin.xml create mode 100644 data/monster/Birds/seagull.xml create mode 100644 data/monster/Birds/terror bird.xml create mode 100644 data/monster/Blobs/acid blob.xml create mode 100644 data/monster/Blobs/death blob.xml create mode 100644 data/monster/Blobs/mercury blob.xml create mode 100644 data/monster/Bonelords/bonelord.xml create mode 100644 data/monster/Bonelords/braindeath.xml create mode 100644 data/monster/Bonelords/elder bonelord.xml create mode 100644 data/monster/Bonelords/gazer.xml create mode 100644 data/monster/Bosses/annihilon.xml create mode 100644 data/monster/Bosses/apprentice sheng.xml create mode 100644 data/monster/Bosses/barbaria.xml create mode 100644 data/monster/Bosses/baron brute.xml create mode 100644 data/monster/Bosses/big boss trolliver.xml create mode 100644 data/monster/Bosses/bones.xml create mode 100644 data/monster/Bosses/brutus bloodbeard.xml create mode 100644 data/monster/Bosses/chizzoron the distorter.xml create mode 100644 data/monster/Bosses/countess sorrow.xml create mode 100644 data/monster/Bosses/deadeye devious.xml create mode 100644 data/monster/Bosses/demodras.xml create mode 100644 data/monster/Bosses/dharalion.xml create mode 100644 data/monster/Bosses/dire penguin.xml create mode 100644 data/monster/Bosses/dracola.xml create mode 100644 data/monster/Bosses/dreadwing.xml create mode 100644 data/monster/Bosses/esmeralda.xml create mode 100644 data/monster/Bosses/fatality.xml create mode 100644 data/monster/Bosses/fernfang.xml create mode 100644 data/monster/Bosses/ferumbras.xml create mode 100644 data/monster/Bosses/fluffy.xml create mode 100644 data/monster/Bosses/foreman kneebiter.xml create mode 100644 data/monster/Bosses/general murius.xml create mode 100644 data/monster/Bosses/ghazbaran.xml create mode 100644 data/monster/Bosses/glitterscale.xml create mode 100644 data/monster/Bosses/golgordan.xml create mode 100644 data/monster/Bosses/grand mother foulscale.xml create mode 100644 data/monster/Bosses/grorlam.xml create mode 100644 data/monster/Bosses/hairman the huge.xml create mode 100644 data/monster/Bosses/handmaiden.xml create mode 100644 data/monster/Bosses/hellgorak.xml create mode 100644 data/monster/Bosses/heoni.xml create mode 100644 data/monster/Bosses/hide.xml create mode 100644 data/monster/Bosses/koshei the deathless.xml create mode 100644 data/monster/Bosses/latrivan.xml create mode 100644 data/monster/Bosses/lethal lissy.xml create mode 100644 data/monster/Bosses/leviathan.xml create mode 100644 data/monster/Bosses/lord of the elements.xml create mode 100644 data/monster/Bosses/mad technomancer.xml create mode 100644 data/monster/Bosses/madareth.xml create mode 100644 data/monster/Bosses/man in the cave.xml create mode 100644 data/monster/Bosses/massacre.xml create mode 100644 data/monster/Bosses/minishabaal.xml create mode 100644 data/monster/Bosses/morgaroth.xml create mode 100644 data/monster/Bosses/mr. punish.xml create mode 100644 data/monster/Bosses/munster.xml create mode 100644 data/monster/Bosses/necropharus.xml create mode 100644 data/monster/Bosses/orshabaal.xml create mode 100644 data/monster/Bosses/pythius the rotten.xml create mode 100644 data/monster/Bosses/ron the ripper.xml create mode 100644 data/monster/Bosses/rotworm queen.xml create mode 100644 data/monster/Bosses/shardhead.xml create mode 100644 data/monster/Bosses/snake god essence.xml create mode 100644 data/monster/Bosses/snake thing.xml create mode 100644 data/monster/Bosses/stonecracker.xml create mode 100644 data/monster/Bosses/the abomination.xml create mode 100644 data/monster/Bosses/the blightfather.xml create mode 100644 data/monster/Bosses/the bloodtusk.xml create mode 100644 data/monster/Bosses/the count.xml create mode 100644 data/monster/Bosses/the evil eye.xml create mode 100644 data/monster/Bosses/the handmaiden.xml create mode 100644 data/monster/Bosses/the horned fox.xml create mode 100644 data/monster/Bosses/the imperor.xml create mode 100644 data/monster/Bosses/the many.xml create mode 100644 data/monster/Bosses/the noxious spawn.xml create mode 100644 data/monster/Bosses/the old widow.xml create mode 100644 data/monster/Bosses/the plasmother.xml create mode 100644 data/monster/Bosses/the snapper.xml create mode 100644 data/monster/Bosses/thul.xml create mode 100644 data/monster/Bosses/tibia bug.xml create mode 100644 data/monster/Bosses/tiquandas revenge.xml create mode 100644 data/monster/Bosses/undead minion.xml create mode 100644 data/monster/Bosses/ungreez.xml create mode 100644 data/monster/Bosses/ushuriel.xml create mode 100644 data/monster/Bosses/warlord ruzad.xml create mode 100644 data/monster/Bosses/xenia.xml create mode 100644 data/monster/Bosses/yakchal.xml create mode 100644 data/monster/Bosses/zugurosh.xml create mode 100644 data/monster/Bosses/zulazza the corruptor.xml create mode 100644 data/monster/Canines/crystal wolf.xml create mode 100644 data/monster/Canines/dog.xml create mode 100644 data/monster/Canines/gnarlhound.xml create mode 100644 data/monster/Canines/husky.xml create mode 100644 data/monster/Canines/war wolf.xml create mode 100644 data/monster/Canines/werewolf.xml create mode 100644 data/monster/Canines/winter wolf.xml create mode 100644 data/monster/Canines/wolf.xml create mode 100644 data/monster/Chakoyas/chakoya toolshaper.xml create mode 100644 data/monster/Chakoyas/chakoya tribewarden.xml create mode 100644 data/monster/Chakoyas/chakoya windcaller.xml create mode 100644 data/monster/Crustaceans/blood crab.xml create mode 100644 data/monster/Crustaceans/crab.xml create mode 100644 data/monster/Crustaceans/crustacea gigantica.xml create mode 100644 data/monster/Cryo-Elementals/ice golem.xml create mode 100644 data/monster/Cultists/acolyte of the cult.xml create mode 100644 data/monster/Cultists/adept of the cult.xml create mode 100644 data/monster/Cultists/enlightened of the cult.xml create mode 100644 data/monster/Cultists/novice of the cult.xml create mode 100644 data/monster/Demons/dark torturer.xml create mode 100644 data/monster/Demons/demon.xml create mode 100644 data/monster/Demons/destroyer.xml create mode 100644 data/monster/Demons/diabolic imp.xml create mode 100644 data/monster/Demons/fire devil.xml create mode 100644 data/monster/Demons/fury.xml create mode 100644 data/monster/Demons/gozzler.xml create mode 100644 data/monster/Demons/hand of cursed fate.xml create mode 100644 data/monster/Demons/hellhound.xml create mode 100644 data/monster/Demons/hellspawn.xml create mode 100644 data/monster/Demons/juggernaut.xml create mode 100644 data/monster/Demons/nightmare scion.xml create mode 100644 data/monster/Demons/nightmare.xml create mode 100644 data/monster/Demons/nightstalker.xml create mode 100644 data/monster/Demons/plaguesmith.xml create mode 100644 data/monster/Demons/shaburak demon.xml create mode 100644 data/monster/Demons/shaburak prince.xml create mode 100644 data/monster/Djinns/blue djinn.xml create mode 100644 data/monster/Djinns/efreet.xml create mode 100644 data/monster/Djinns/green djinn.xml create mode 100644 data/monster/Djinns/marid.xml create mode 100644 data/monster/Dragons/dragon hatchling.xml create mode 100644 data/monster/Dragons/dragon lord hatchling.xml create mode 100644 data/monster/Dragons/dragon lord.xml create mode 100644 data/monster/Dragons/dragon.xml create mode 100644 data/monster/Dragons/draptor.xml create mode 100644 data/monster/Dragons/frost dragon hatchling.xml create mode 100644 data/monster/Dragons/frost dragon.xml create mode 100644 data/monster/Dragons/ghastly dragon.xml create mode 100644 data/monster/Dragons/undead dragon.xml create mode 100644 data/monster/Dragons/wyrm.xml create mode 100644 data/monster/Dwarves/dwarf geomancer.xml create mode 100644 data/monster/Dwarves/dwarf guard.xml create mode 100644 data/monster/Dwarves/dwarf soldier.xml create mode 100644 data/monster/Dwarves/dwarf.xml create mode 100644 data/monster/Dworcs/dworc fleshhunter.xml create mode 100644 data/monster/Dworcs/dworc venomsniper.xml create mode 100644 data/monster/Dworcs/dworc voodoomaster.xml create mode 100644 data/monster/Elephants/elephant.xml create mode 100644 data/monster/Elephants/mammoth.xml create mode 100644 data/monster/Elves/elf arcanist.xml create mode 100644 data/monster/Elves/elf scout.xml create mode 100644 data/monster/Elves/elf.xml create mode 100644 data/monster/Energy-Elementals/charged energy elemental.xml create mode 100644 data/monster/Energy-Elementals/energy elemental.xml create mode 100644 data/monster/Energy-Elementals/energy overlord.xml create mode 100644 data/monster/Energy-Elementals/massive energy elemental.xml create mode 100644 data/monster/Energy-Elementals/overcharged energy elemental.xml create mode 100644 data/monster/Felines/cat.xml create mode 100644 data/monster/Felines/lion.xml create mode 100644 data/monster/Felines/midnight panther.xml create mode 100644 data/monster/Felines/tiger.xml create mode 100644 data/monster/Frogs/azure frog.xml create mode 100644 data/monster/Frogs/coral frog.xml create mode 100644 data/monster/Frogs/crimson frog.xml create mode 100644 data/monster/Frogs/green frog.xml create mode 100644 data/monster/Frogs/orchid frog.xml create mode 100644 data/monster/Frogs/toad.xml create mode 100644 data/monster/Geo-Elementals/damaged worker golem.xml create mode 100644 data/monster/Geo-Elementals/earth elemental.xml create mode 100644 data/monster/Geo-Elementals/earth overlord.xml create mode 100644 data/monster/Geo-Elementals/gargoyle.xml create mode 100644 data/monster/Geo-Elementals/jagged earth elemental.xml create mode 100644 data/monster/Geo-Elementals/massive earth elemental.xml create mode 100644 data/monster/Geo-Elementals/medusa.xml create mode 100644 data/monster/Geo-Elementals/muddy earth elemental.xml create mode 100644 data/monster/Geo-Elementals/stone golem.xml create mode 100644 data/monster/Geo-Elementals/war golem.xml create mode 100644 data/monster/Geo-Elementals/worker golem.xml create mode 100644 data/monster/Ghosts/ghost.xml create mode 100644 data/monster/Ghosts/phantasm summon.xml create mode 100644 data/monster/Ghosts/phantasm.xml create mode 100644 data/monster/Ghosts/spectre.xml create mode 100644 data/monster/Ghosts/wisp.xml create mode 100644 data/monster/Giants/behemoth.xml create mode 100644 data/monster/Giants/cyclops drone.xml create mode 100644 data/monster/Giants/cyclops smith.xml create mode 100644 data/monster/Giants/cyclops.xml create mode 100644 data/monster/Giants/frost giant.xml create mode 100644 data/monster/Giants/frost giantess.xml create mode 100644 data/monster/Giants/yeti.xml create mode 100644 data/monster/Goblins/goblin assassin.xml create mode 100644 data/monster/Goblins/goblin leader.xml create mode 100644 data/monster/Goblins/goblin scavenger.xml create mode 100644 data/monster/Goblins/goblin.xml create mode 100644 data/monster/Goblins/grynch clan goblin.xml create mode 100644 data/monster/Grunts/boar.xml create mode 100644 data/monster/Grunts/pig.xml create mode 100644 data/monster/Hydro-Elementals/ice overlord.xml create mode 100644 data/monster/Hydro-Elementals/massive water elemental.xml create mode 100644 data/monster/Hydro-Elementals/roaring water elemental.xml create mode 100644 data/monster/Hydro-Elementals/slick water elemental.xml create mode 100644 data/monster/Hydro-Elementals/water elemental.xml create mode 100644 data/monster/Insects/ancient scarab.xml create mode 100644 data/monster/Insects/blue butterfly.xml create mode 100644 data/monster/Insects/brimstone bug.xml create mode 100644 data/monster/Insects/bug.xml create mode 100644 data/monster/Insects/centipede.xml create mode 100644 data/monster/Insects/cockroach.xml create mode 100644 data/monster/Insects/insect swarm.xml create mode 100644 data/monster/Insects/insectoid scout.xml create mode 100644 data/monster/Insects/lancer beetle.xml create mode 100644 data/monster/Insects/larva.xml create mode 100644 data/monster/Insects/purple butterfly.xml create mode 100644 data/monster/Insects/red butterfly.xml create mode 100644 data/monster/Insects/sandcrawler.xml create mode 100644 data/monster/Insects/scarab.xml create mode 100644 data/monster/Insects/terramite.xml create mode 100644 data/monster/Insects/wasp.xml create mode 100644 data/monster/Insects/yellow butterfly.xml create mode 100644 data/monster/Isle of Evil/berserker chicken.xml create mode 100644 data/monster/Isle of Evil/boogey.xml create mode 100644 data/monster/Isle of Evil/demon parrot.xml create mode 100644 data/monster/Isle of Evil/dirtbeard.xml create mode 100644 data/monster/Isle of Evil/docter perhaps.xml create mode 100644 data/monster/Isle of Evil/doom deer.xml create mode 100644 data/monster/Isle of Evil/evil mastermind.xml create mode 100644 data/monster/Isle of Evil/evil sheep lord.xml create mode 100644 data/monster/Isle of Evil/evil sheep.xml create mode 100644 data/monster/Isle of Evil/hot dog.xml create mode 100644 data/monster/Isle of Evil/infernal frog.xml create mode 100644 data/monster/Isle of Evil/killer rabbit.xml create mode 100644 data/monster/Isle of Evil/mephiles.xml create mode 100644 data/monster/Isle of Evil/monstor.xml create mode 100644 data/monster/Isle of Evil/vampire pig.xml create mode 100644 data/monster/Lizards/battlemaster zunzu.xml create mode 100644 data/monster/Lizards/draken abomination.xml create mode 100644 data/monster/Lizards/draken elite.xml create mode 100644 data/monster/Lizards/draken spellweaver.xml create mode 100644 data/monster/Lizards/draken warmaster.xml create mode 100644 data/monster/Lizards/eternal guardian.xml create mode 100644 data/monster/Lizards/lizard abomination.xml create mode 100644 data/monster/Lizards/lizard chosen.xml create mode 100644 data/monster/Lizards/lizard dragon priest.xml create mode 100644 data/monster/Lizards/lizard high guard.xml create mode 100644 data/monster/Lizards/lizard legionnaire.xml create mode 100644 data/monster/Lizards/lizard sentinel.xml create mode 100644 data/monster/Lizards/lizard snakecharmer.xml create mode 100644 data/monster/Lizards/lizard templar.xml create mode 100644 data/monster/Lizards/lizard zaogun.xml create mode 100644 data/monster/Lizards/wyvern.xml create mode 100644 data/monster/Minotaurs/minotaur archer.xml create mode 100644 data/monster/Minotaurs/minotaur guard.xml create mode 100644 data/monster/Minotaurs/minotaur mage.xml create mode 100644 data/monster/Minotaurs/minotaur.xml create mode 100644 data/monster/Misc/badger.xml create mode 100644 data/monster/Misc/bat.xml create mode 100644 data/monster/Misc/deer.xml create mode 100644 data/monster/Misc/dromedary.xml create mode 100644 data/monster/Misc/hacker.xml create mode 100644 data/monster/Misc/halloweenhare.xml create mode 100644 data/monster/Misc/horse(brown).xml create mode 100644 data/monster/Misc/horse(fire).xml create mode 100644 data/monster/Misc/horse(grey).xml create mode 100644 data/monster/Misc/hyaena.xml create mode 100644 data/monster/Misc/rabbit.xml create mode 100644 data/monster/Misc/silver rabbit.xml create mode 100644 data/monster/Misc/skunk.xml create mode 100644 data/monster/Misc/squirrel.xml create mode 100644 data/monster/Misc/white deer.xml create mode 100644 data/monster/Misc/yalahari.xml create mode 100644 data/monster/Monks/dark monk.xml create mode 100644 data/monster/Monks/monk.xml create mode 100644 data/monster/Mutated/mutated bat.xml create mode 100644 data/monster/Mutated/mutated human.xml create mode 100644 data/monster/Mutated/mutated rat.xml create mode 100644 data/monster/Mutated/mutated tiger.xml create mode 100644 data/monster/Necromancers/necromancer.xml create mode 100644 data/monster/Necromancers/priestess.xml create mode 100644 data/monster/Orcs/orc berserker.xml create mode 100644 data/monster/Orcs/orc leader.xml create mode 100644 data/monster/Orcs/orc marauder.xml create mode 100644 data/monster/Orcs/orc rider.xml create mode 100644 data/monster/Orcs/orc shaman.xml create mode 100644 data/monster/Orcs/orc spearman.xml create mode 100644 data/monster/Orcs/orc warlord.xml create mode 100644 data/monster/Orcs/orc warrior.xml create mode 100644 data/monster/Orcs/orc.xml create mode 100644 data/monster/Outlaws/assassin.xml create mode 100644 data/monster/Outlaws/bandit.xml create mode 100644 data/monster/Outlaws/black knight.xml create mode 100644 data/monster/Outlaws/crazed beggar.xml create mode 100644 data/monster/Outlaws/feverish citizen.xml create mode 100644 data/monster/Outlaws/gang member.xml create mode 100644 data/monster/Outlaws/gladiator.xml create mode 100644 data/monster/Outlaws/hero.xml create mode 100644 data/monster/Outlaws/hunter.xml create mode 100644 data/monster/Outlaws/nomad.xml create mode 100644 data/monster/Outlaws/poacher.xml create mode 100644 data/monster/Outlaws/primitive.xml create mode 100644 data/monster/Outlaws/smuggler.xml create mode 100644 data/monster/Outlaws/stalker.xml create mode 100644 data/monster/Outlaws/wild warrior.xml create mode 100644 data/monster/Pharaohs/ashmunrah.xml create mode 100644 data/monster/Pharaohs/dipthrah.xml create mode 100644 data/monster/Pharaohs/mahrdis.xml create mode 100644 data/monster/Pharaohs/morguthis.xml create mode 100644 data/monster/Pharaohs/omruc.xml create mode 100644 data/monster/Pharaohs/rahemos.xml create mode 100644 data/monster/Pharaohs/thalas.xml create mode 100644 data/monster/Pharaohs/vashresamun.xml create mode 100644 data/monster/Pirates/pirate buccaneer.xml create mode 100644 data/monster/Pirates/pirate corsair.xml create mode 100644 data/monster/Pirates/pirate cutthroat.xml create mode 100644 data/monster/Pirates/pirate ghost.xml create mode 100644 data/monster/Pirates/pirate marauder.xml create mode 100644 data/monster/Pirates/pirate skeleton.xml create mode 100644 data/monster/Pyro-Elementals/blazing fire elemental.xml create mode 100644 data/monster/Pyro-Elementals/blistering fire elemental.xml create mode 100644 data/monster/Pyro-Elementals/fire elemental.xml create mode 100644 data/monster/Pyro-Elementals/fire overlord.xml create mode 100644 data/monster/Pyro-Elementals/hellfire fighter.xml create mode 100644 data/monster/Pyro-Elementals/massive fire elemental.xml create mode 100644 data/monster/Quaras/quara constrictor scout.xml create mode 100644 data/monster/Quaras/quara constrictor.xml create mode 100644 data/monster/Quaras/quara hydromancer scout.xml create mode 100644 data/monster/Quaras/quara hydromancer.xml create mode 100644 data/monster/Quaras/quara mantassin scout.xml create mode 100644 data/monster/Quaras/quara mantassin.xml create mode 100644 data/monster/Quaras/quara pincher scout.xml create mode 100644 data/monster/Quaras/quara pincher.xml create mode 100644 data/monster/Quaras/quara predator scout.xml create mode 100644 data/monster/Quaras/quara predator.xml create mode 100644 data/monster/Rats/cave rat.xml create mode 100644 data/monster/Rats/rat.xml create mode 100644 data/monster/Reptiles/crocodile.xml create mode 100644 data/monster/Reptiles/hydra.xml create mode 100644 data/monster/Reptiles/killer caiman.xml create mode 100644 data/monster/Rifts/rift brood.xml create mode 100644 data/monster/Rifts/rift scythe.xml create mode 100644 data/monster/Rifts/rift worm.xml create mode 100644 data/monster/Serpents/cobra.xml create mode 100644 data/monster/Serpents/deepling scout.xml create mode 100644 data/monster/Serpents/sea serpent.xml create mode 100644 data/monster/Serpents/serpent spawn.xml create mode 100644 data/monster/Serpents/snake.xml create mode 100644 data/monster/Serpents/young sea serpent.xml create mode 100644 data/monster/Shapeshifters/mimic.xml create mode 100644 data/monster/Sheeps/black sheep.xml create mode 100644 data/monster/Sheeps/sheep.xml create mode 100644 data/monster/Skeletons/betrayed wraith.xml create mode 100644 data/monster/Skeletons/bonebeast.xml create mode 100644 data/monster/Skeletons/demon skeleton.xml create mode 100644 data/monster/Skeletons/dreadbeast.xml create mode 100644 data/monster/Skeletons/lost soul.xml create mode 100644 data/monster/Skeletons/skeleton warrior.xml create mode 100644 data/monster/Skeletons/skeleton.xml create mode 100644 data/monster/Skeletons/undead gladiator.xml create mode 100644 data/monster/Sorcerers/dark apprentice.xml create mode 100644 data/monster/Sorcerers/dark magician.xml create mode 100644 data/monster/Sorcerers/ice witch.xml create mode 100644 data/monster/Sorcerers/infernalist.xml create mode 100644 data/monster/Sorcerers/mad mage.xml create mode 100644 data/monster/Sorcerers/mad scientist.xml create mode 100644 data/monster/Sorcerers/warlock.xml create mode 100644 data/monster/Sorcerers/witch.xml create mode 100644 data/monster/Tortoises/thornback tortoise.xml create mode 100644 data/monster/Tortoises/tortoise.xml create mode 100644 data/monster/Traps/deathslicer.xml create mode 100644 data/monster/Traps/eye of the seven.xml create mode 100644 data/monster/Traps/flamethrower.xml create mode 100644 data/monster/Traps/lavahole.xml create mode 100644 data/monster/Traps/magicthrower.xml create mode 100644 data/monster/Traps/plaguethrower.xml create mode 100644 data/monster/Traps/shredderthrower.xml create mode 100644 data/monster/Trolls/frost troll.xml create mode 100644 data/monster/Trolls/island troll.xml create mode 100644 data/monster/Trolls/swamp troll.xml create mode 100644 data/monster/Trolls/troll champion.xml create mode 100644 data/monster/Trolls/troll.xml create mode 100644 data/monster/Undead Humanoids/banshee.xml create mode 100644 data/monster/Undead Humanoids/blightwalker.xml create mode 100644 data/monster/Undead Humanoids/crypt shambler.xml create mode 100644 data/monster/Undead Humanoids/ghoul.xml create mode 100644 data/monster/Undead Humanoids/grim reaper.xml create mode 100644 data/monster/Undead Humanoids/lich.xml create mode 100644 data/monster/Undead Humanoids/mummy.xml create mode 100644 data/monster/Undead Humanoids/souleater.xml create mode 100644 data/monster/Undead Humanoids/tomb servant.xml create mode 100644 data/monster/Undead Humanoids/undead mine worker.xml create mode 100644 data/monster/Undead Humanoids/undead prospector.xml create mode 100644 data/monster/Undead Humanoids/vampire bride.xml create mode 100644 data/monster/Undead Humanoids/vampire.xml create mode 100644 data/monster/Undead Humanoids/zombie.xml create mode 100644 data/monster/monsters.xml create mode 100644 data/movements/lib/movements.lua create mode 100644 data/movements/movements.xml create mode 100644 data/movements/scripts/citizen.lua create mode 100644 data/movements/scripts/closingdoor.lua create mode 100644 data/movements/scripts/decay.lua create mode 100644 data/movements/scripts/dough.lua create mode 100644 data/movements/scripts/drowning.lua create mode 100644 data/movements/scripts/snow.lua create mode 100644 data/movements/scripts/swimming.lua create mode 100644 data/movements/scripts/tiles.lua create mode 100644 data/movements/scripts/trap.lua create mode 100644 data/movements/scripts/walkback.lua create mode 100644 data/npc/Alice.xml create mode 100644 data/npc/Deruno.xml create mode 100644 data/npc/Eryn.xml create mode 100644 data/npc/Riona.xml create mode 100644 data/npc/The Forgotten King.xml create mode 100644 data/npc/The Oracle.xml create mode 100644 data/npc/Tyoric.xml create mode 100644 data/npc/Varkhal.xml create mode 100644 data/npc/lib/npc.lua create mode 100644 data/npc/lib/npcsystem/keywordhandler.lua create mode 100644 data/npc/lib/npcsystem/modules.lua create mode 100644 data/npc/lib/npcsystem/npchandler.lua create mode 100644 data/npc/lib/npcsystem/npcsystem.lua create mode 100644 data/npc/scripts/addons.lua create mode 100644 data/npc/scripts/bless.lua create mode 100644 data/npc/scripts/default.lua create mode 100644 data/npc/scripts/promotion.lua create mode 100644 data/npc/scripts/runes.lua create mode 100644 data/raids/raids.xml create mode 100644 data/spells/lib/spells.lua create mode 100644 data/spells/scripts/attack/avalanche.lua create mode 100644 data/spells/scripts/attack/berserk.lua create mode 100644 data/spells/scripts/attack/death strike.lua create mode 100644 data/spells/scripts/attack/divine caldera.lua create mode 100644 data/spells/scripts/attack/divine missile.lua create mode 100644 data/spells/scripts/attack/energy beam.lua create mode 100644 data/spells/scripts/attack/energy bomb.lua create mode 100644 data/spells/scripts/attack/energy field.lua create mode 100644 data/spells/scripts/attack/energy strike.lua create mode 100644 data/spells/scripts/attack/energy wall.lua create mode 100644 data/spells/scripts/attack/energy wave.lua create mode 100644 data/spells/scripts/attack/envenom.lua create mode 100644 data/spells/scripts/attack/eternal winter.lua create mode 100644 data/spells/scripts/attack/ethereal spear.lua create mode 100644 data/spells/scripts/attack/explosion.lua create mode 100644 data/spells/scripts/attack/fierce berserk.lua create mode 100644 data/spells/scripts/attack/fire bomb.lua create mode 100644 data/spells/scripts/attack/fire field.lua create mode 100644 data/spells/scripts/attack/fire wall.lua create mode 100644 data/spells/scripts/attack/fire wave.lua create mode 100644 data/spells/scripts/attack/fireball.lua create mode 100644 data/spells/scripts/attack/flame strike.lua create mode 100644 data/spells/scripts/attack/great energy beam.lua create mode 100644 data/spells/scripts/attack/great fireball.lua create mode 100644 data/spells/scripts/attack/groundshaker.lua create mode 100644 data/spells/scripts/attack/heavy magic missile.lua create mode 100644 data/spells/scripts/attack/hells core.lua create mode 100644 data/spells/scripts/attack/holy missile.lua create mode 100644 data/spells/scripts/attack/ice strike.lua create mode 100644 data/spells/scripts/attack/ice wave.lua create mode 100644 data/spells/scripts/attack/icicle.lua create mode 100644 data/spells/scripts/attack/light magic missile.lua create mode 100644 data/spells/scripts/attack/poison bomb.lua create mode 100644 data/spells/scripts/attack/poison field.lua create mode 100644 data/spells/scripts/attack/poison wall.lua create mode 100644 data/spells/scripts/attack/rage of the skies.lua create mode 100644 data/spells/scripts/attack/soul fire.lua create mode 100644 data/spells/scripts/attack/stalagmite.lua create mode 100644 data/spells/scripts/attack/stone shower.lua create mode 100644 data/spells/scripts/attack/sudden death.lua create mode 100644 data/spells/scripts/attack/terra strike.lua create mode 100644 data/spells/scripts/attack/terra wave.lua create mode 100644 data/spells/scripts/attack/thunderstorm.lua create mode 100644 data/spells/scripts/attack/whirlwind throw.lua create mode 100644 data/spells/scripts/attack/wrath of nature.lua create mode 100644 data/spells/scripts/custom/apocalypse.lua create mode 100644 data/spells/scripts/custom/combustion.lua create mode 100644 data/spells/scripts/custom/drunk.lua create mode 100644 data/spells/scripts/custom/magic prison.lua create mode 100644 data/spells/scripts/custom/polymorph.lua create mode 100644 data/spells/scripts/healing/antidote rune.lua create mode 100644 data/spells/scripts/healing/antidote.lua create mode 100644 data/spells/scripts/healing/divine healing.lua create mode 100644 data/spells/scripts/healing/heal friend.lua create mode 100644 data/spells/scripts/healing/intense healing rune.lua create mode 100644 data/spells/scripts/healing/intense healing.lua create mode 100644 data/spells/scripts/healing/light healing.lua create mode 100644 data/spells/scripts/healing/mass healing.lua create mode 100644 data/spells/scripts/healing/ultimate healing rune.lua create mode 100644 data/spells/scripts/healing/ultimate healing.lua create mode 100644 data/spells/scripts/healing/wound cleasing.lua create mode 100644 data/spells/scripts/party/enchant.lua create mode 100644 data/spells/scripts/party/heal.lua create mode 100644 data/spells/scripts/party/protect.lua create mode 100644 data/spells/scripts/party/train.lua create mode 100644 data/spells/scripts/summon/animate dead rune.lua create mode 100644 data/spells/scripts/support/blood rage.lua create mode 100644 data/spells/scripts/support/cancel invisibility.lua create mode 100644 data/spells/scripts/support/challenge.lua create mode 100644 data/spells/scripts/support/charge.lua create mode 100644 data/spells/scripts/support/desintegrate rune.lua create mode 100644 data/spells/scripts/support/destroy field rune.lua create mode 100644 data/spells/scripts/support/great light.lua create mode 100644 data/spells/scripts/support/haste.lua create mode 100644 data/spells/scripts/support/invisible.lua create mode 100644 data/spells/scripts/support/light.lua create mode 100644 data/spells/scripts/support/magic rope.lua create mode 100644 data/spells/scripts/support/magic shield.lua create mode 100644 data/spells/scripts/support/magic wall rune.lua create mode 100644 data/spells/scripts/support/paralyze rune.lua create mode 100644 data/spells/scripts/support/protector.lua create mode 100644 data/spells/scripts/support/sharpshooter.lua create mode 100644 data/spells/scripts/support/strong haste.lua create mode 100644 data/spells/scripts/support/swift foot.lua create mode 100644 data/spells/scripts/support/ultimate light.lua create mode 100644 data/spells/scripts/support/wild growth rune.lua create mode 100644 data/spells/spells.xml create mode 100644 data/talkactions/lib/talkactions.lua create mode 100644 data/talkactions/scripts/animationeffect.lua create mode 100644 data/talkactions/scripts/buyprem.lua create mode 100644 data/talkactions/scripts/changesex.lua create mode 100644 data/talkactions/scripts/deathlist.lua create mode 100644 data/talkactions/scripts/leavehouse.lua create mode 100644 data/talkactions/scripts/magiceffect.lua create mode 100644 data/talkactions/scripts/save.lua create mode 100644 data/talkactions/scripts/uptime.lua create mode 100644 data/talkactions/talkactions.xml create mode 100644 data/weapons/lib/weapons.lua create mode 100644 data/weapons/scripts/explosive_arrow.lua create mode 100644 data/weapons/scripts/poison_arrow.lua create mode 100644 data/weapons/scripts/viper_star.lua create mode 100644 data/weapons/weapons.xml create mode 100644 data/world/forgotten-house.xml create mode 100644 data/world/forgotten-spawn.xml create mode 100644 data/world/forgotten.otbm create mode 100644 schema.sql create mode 100644 src/account.h create mode 100644 src/actions.cpp create mode 100644 src/actions.h create mode 100644 src/admin.cpp create mode 100644 src/admin.h create mode 100644 src/ban.cpp create mode 100644 src/ban.h create mode 100644 src/baseevents.cpp create mode 100644 src/baseevents.h create mode 100644 src/beds.cpp create mode 100644 src/beds.h create mode 100644 src/chat.cpp create mode 100644 src/chat.h create mode 100644 src/combat.cpp create mode 100644 src/combat.h create mode 100644 src/commands.cpp create mode 100644 src/commands.h create mode 100644 src/condition.cpp create mode 100644 src/condition.h create mode 100644 src/configmanager.cpp create mode 100644 src/configmanager.h create mode 100644 src/connection.cpp create mode 100644 src/connection.h create mode 100644 src/const.h create mode 100644 src/container.cpp create mode 100644 src/container.h create mode 100644 src/creature.cpp create mode 100644 src/creature.h create mode 100644 src/creatureevent.cpp create mode 100644 src/creatureevent.h create mode 100644 src/cylinder.cpp create mode 100644 src/cylinder.h create mode 100644 src/database.cpp create mode 100644 src/database.h create mode 100644 src/databasemanager.cpp create mode 100644 src/databasemanager.h create mode 100644 src/databasemysql.cpp create mode 100644 src/databasemysql.h create mode 100644 src/definitions.h create mode 100644 src/depotchest.cpp create mode 100644 src/depotchest.h create mode 100644 src/depotlocker.cpp create mode 100644 src/depotlocker.h create mode 100644 src/enums.h create mode 100644 src/fileloader.cpp create mode 100644 src/fileloader.h create mode 100644 src/game.cpp create mode 100644 src/game.h create mode 100644 src/globalevent.cpp create mode 100644 src/globalevent.h create mode 100644 src/guild.cpp create mode 100644 src/guild.h create mode 100644 src/house.cpp create mode 100644 src/house.h create mode 100644 src/housetile.cpp create mode 100644 src/housetile.h create mode 100644 src/inbox.cpp create mode 100644 src/inbox.h create mode 100644 src/ioguild.cpp create mode 100644 src/ioguild.h create mode 100644 src/iologindata.cpp create mode 100644 src/iologindata.h create mode 100644 src/iomap.cpp create mode 100644 src/iomap.h create mode 100644 src/iomapserialize.cpp create mode 100644 src/iomapserialize.h create mode 100644 src/iomarket.cpp create mode 100644 src/iomarket.h create mode 100644 src/item.cpp create mode 100644 src/item.h create mode 100644 src/itemloader.h create mode 100644 src/items.cpp create mode 100644 src/items.h create mode 100644 src/logger.cpp create mode 100644 src/logger.h create mode 100644 src/luascript.cpp create mode 100644 src/luascript.h create mode 100644 src/mailbox.cpp create mode 100644 src/mailbox.h create mode 100644 src/map.cpp create mode 100644 src/map.h create mode 100644 src/md5.cpp create mode 100644 src/md5.h create mode 100644 src/modalwindow.cpp create mode 100644 src/modalwindow.h create mode 100644 src/monster.cpp create mode 100644 src/monster.h create mode 100644 src/monsters.cpp create mode 100644 src/monsters.h create mode 100644 src/mounts.cpp create mode 100644 src/mounts.h create mode 100644 src/movement.cpp create mode 100644 src/movement.h create mode 100644 src/networkmessage.cpp create mode 100644 src/networkmessage.h create mode 100644 src/npc.cpp create mode 100644 src/npc.h create mode 100644 src/otpch.cpp create mode 100644 src/otpch.h create mode 100644 src/otserv.cpp create mode 100644 src/otsystem.h create mode 100644 src/outfit.cpp create mode 100644 src/outfit.h create mode 100644 src/outputmessage.cpp create mode 100644 src/outputmessage.h create mode 100644 src/party.cpp create mode 100644 src/party.h create mode 100644 src/player.cpp create mode 100644 src/player.h create mode 100644 src/position.cpp create mode 100644 src/position.h create mode 100644 src/protocol.cpp create mode 100644 src/protocol.h create mode 100644 src/protocolgame.cpp create mode 100644 src/protocolgame.h create mode 100644 src/protocollogin.cpp create mode 100644 src/protocollogin.h create mode 100644 src/protocolold.cpp create mode 100644 src/protocolold.h create mode 100644 src/quests.cpp create mode 100644 src/quests.h create mode 100644 src/raids.cpp create mode 100644 src/raids.h create mode 100644 src/rsa.cpp create mode 100644 src/rsa.h create mode 100644 src/scheduler.cpp create mode 100644 src/scheduler.h create mode 100644 src/scriptmanager.cpp create mode 100644 src/scriptmanager.h create mode 100644 src/server.cpp create mode 100644 src/server.h create mode 100644 src/sha1.cpp create mode 100644 src/sha1.h create mode 100644 src/spawn.cpp create mode 100644 src/spawn.h create mode 100644 src/spells.cpp create mode 100644 src/spells.h create mode 100644 src/status.cpp create mode 100644 src/status.h create mode 100644 src/talkaction.cpp create mode 100644 src/talkaction.h create mode 100644 src/tasks.cpp create mode 100644 src/tasks.h create mode 100644 src/teleport.cpp create mode 100644 src/teleport.h create mode 100644 src/templates.h create mode 100644 src/thing.cpp create mode 100644 src/thing.h create mode 100644 src/tile.cpp create mode 100644 src/tile.h create mode 100644 src/tools.cpp create mode 100644 src/tools.h create mode 100644 src/town.h create mode 100644 src/trashholder.cpp create mode 100644 src/trashholder.h create mode 100644 src/vocation.cpp create mode 100644 src/vocation.h create mode 100644 src/waitlist.cpp create mode 100644 src/waitlist.h create mode 100644 src/waypoints.h create mode 100644 src/weapons.cpp create mode 100644 src/weapons.h create mode 100644 src/wildcardtree.cpp create mode 100644 src/wildcardtree.h create mode 100644 vc12/arch32.props create mode 100644 vc12/arch64.props create mode 100644 vc12/debug.props create mode 100644 vc12/release.props create mode 100644 vc12/settings.props create mode 100644 vc12/theforgottenserver.filters create mode 100644 vc12/theforgottenserver.sln create mode 100644 vc12/theforgottenserver.vcxproj diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..412eeda78d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,22 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp +*.sln merge=union +*.csproj merge=union +*.vbproj merge=union +*.fsproj merge=union +*.dbproj merge=union + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..ee8fe875c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,165 @@ +################# +## Visual Studio +################# + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml +*.pubxml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + +############# +## Windows detritus +############# + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac crap +.DS_Store + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox + +#Translations +*.mo + +#Mr Developer +.mr.developer.cfg diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..d159169d10 --- /dev/null +++ b/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..6e0396bb05 --- /dev/null +++ b/Makefile @@ -0,0 +1,43 @@ +# Makefile to compile under Debian GNU/Linux +TFS = forgottenserver + +INCLUDEDIRS = -I"." -I"/usr/include/libxml2" -I"/usr/include/luajit-2.0" -I"/usr/include/mysql" + +FLAGS = -D__NO_HOMEDIR_CONF__ -D__LUAJIT__ -DBOOST_DISABLE_ASSERTS -DNDEBUG + +CXXFLAGS = -march=native $(INCLUDEDIRS) $(FLAGS) -Werror -Wall -O3 -ggdb3 -pthread -std=c++11 +CXX = ccache g++ + +LIBS = -lxml2 -lluajit-5.1 -lgmp -lboost_thread -lboost_system -lboost_regex -lmysqlclient /usr/lib/libtcmalloc_minimal.so.4 + +LDFLAGS = $(LIBS) + +CXXSOURCES = actions.cpp admin.cpp ban.cpp baseevents.cpp beds.cpp \ + creature.cpp creatureevent.cpp chat.cpp combat.cpp commands.cpp \ + condition.cpp configmanager.cpp connection.cpp container.cpp \ + cylinder.cpp database.cpp databasemanager.cpp databasemysql.cpp \ + depotchest.cpp depotlocker.cpp fileloader.cpp game.cpp globalevent.cpp \ + guild.cpp house.cpp housetile.cpp ioguild.cpp iologindata.cpp \ + iomap.cpp iomapserialize.cpp iomarket.cpp inbox.cpp item.cpp items.cpp \ + logger.cpp luascript.cpp mailbox.cpp map.cpp md5.cpp modalwindow.cpp \ + monster.cpp monsters.cpp mounts.cpp movement.cpp networkmessage.cpp \ + npc.cpp otserv.cpp outfit.cpp outputmessage.cpp party.cpp player.cpp \ + position.cpp protocol.cpp protocolgame.cpp protocollogin.cpp \ + protocolold.cpp quests.cpp raids.cpp rsa.cpp scheduler.cpp \ + scriptmanager.cpp server.cpp sha1.cpp spawn.cpp spells.cpp status.cpp \ + talkaction.cpp tasks.cpp teleport.cpp thing.cpp tile.cpp tools.cpp \ + trashholder.cpp vocation.cpp waitlist.cpp weapons.cpp wildcardtree.cpp + +OBJDIR = obj +CXXOBJECTS = $(CXXSOURCES:%.cpp=$(OBJDIR)/%.o) + +all: $(TFS) + +clean: + $(RM) $(CXXOBJECTS) $(TFS) + +$(TFS): $(CXXOBJECTS) + $(CXX) $(CXXFLAGS) -o $@ $(CXXOBJECTS) $(LDFLAGS) + +$(OBJDIR)/%.o: %.cpp + $(CXX) -c $(CXXFLAGS) -o $@ $< diff --git a/config.lua b/config.lua new file mode 100644 index 0000000000..869fc78ca2 --- /dev/null +++ b/config.lua @@ -0,0 +1,127 @@ +-- Banishments +broadcastBanishments = "no" +banDays = 7 +finalBanDays = 30 + +-- Combat settings +worldType = "pvp" +hotkeyAimbotEnabled = "yes" +protectionLevel = 1 +killsToRedSkull = 3 +killsToBlackSkull = 6 +killsToBan = 0 +pzLocked = 60000 +criticalHitChance = 0 +removeAmmoWhenUsingDistanceWeapon = "yes" +removeChargesFromRunes = "yes" +removeChargesFromWeapons = "yes" +timeToDecreaseFrags = 24 * 60 * 60 * 1000 +whiteSkullTime = 15 * 60 * 1000 +oldConditionAccuracy = "no" +stairJumpExhaustion = 2000 +experienceByKillingPlayers = "no" + +-- Connection Config +ip = "127.0.0.1" +bindOnlyGlobalAddress = "no" +loginProtocolPort = 7171 +gameProtocolPort = 7172 +adminProtocolPort = 7171 +statusProtocolPort = 7171 +loginTries = 10 +retryTimeout = 5 * 1000 +loginTimeout = 60 * 1000 +maxPlayers = "1000" +motd = "Welcome to The Forgotten Server!" +onePlayerOnlinePerAccount = "yes" +allowClones = "no" +serverName = "Forgotten" +loginMessage = "Welcome to The Forgotten Server!" +adminLogsEnabled = "no" +statusTimeout = 5 * 60 * 1000 +replaceKickOnLogin = "yes" +maxPacketsPerSecond = 25 + +-- Deaths +-- NOTE: Leave deathLosePercent as -1 if you want to use Tibia's +-- death penalty formula. For the old formula, set it to 10. For +-- no skill/experience loss, set it to 0. +deathLosePercent = -1 +deathListEnabled = "yes" +maxDeathRecords = 5 + +-- Houses +housePriceEachSQM = 1000 +houseRentPeriod = "never" + +-- Item Usage +timeBetweenActions = 200 +timeBetweenExActions = 1000 + +-- Map +mapName = "forgotten" +mapAuthor = "Komic" + +-- Market +marketEnabled = "yes" +marketOfferDuration = 30 * 24 * 60 * 60 +premiumToCreateMarketOffer = "yes" +checkExpiredMarketOffersEachMinutes = 60 +maxMarketOffersAtATimePerPlayer = 100 + +-- MySQL +mysqlHost = "localhost" +mysqlUser = "root" +mysqlPass = "" +mysqlDatabase = "theforgottenserver" +mysqlPort = 3306 + +-- Misc. +allowChangeOutfit = "yes" +displayGamemastersWithOnlineCommand = "no" +displayOnOrOffAtCharlist = "no" +freePremium = "no" +kickIdlePlayerAfterMinutes = 15 +maxMessageBuffer = 4 +noDamageToSameLookfeet = "no" + +-- Rates +-- NOTE: rateExp is not used if you have enabled stages in data/XML/stages.xml +rateExp = 5 +rateSkill = 3 +rateLoot = 2 +rateMagic = 3 +rateSpawn = 1 + +-- Server save +-- NOTE: serverSaveHour is the hour of the day when the server save will occur, +-- if you would rather save the server with intervals, disable server save and +-- use autoSaveEachMinutes. +serverSaveEnabled = "no" +serverSaveHour = 10 +shutdownAtServerSave = "yes" +cleanMapAtServerSave = "yes" +autoSaveEachMinutes = 0 +saveGlobalStorage = "no" + +-- Monsters +deSpawnRange = 2 +deSpawnRadius = 50 + +-- Stamina +staminaSystem = "yes" + +-- Startup +-- NOTE: defaultPriority only works on Windows and sets process priority. +defaultPriority = "high" +startupDatabaseOptimization = "yes" + +-- Storage +passwordType = "plain" +mapStorageType = "binary-tilebased" + +-- Status server information +ownerName = "Mark Samman" +ownerEmail = "mark.samman@gmail.com" +url = "http://otland.net/" +location = "Sweden" diff --git a/data/XML/admin.xml b/data/XML/admin.xml new file mode 100644 index 0000000000..ec9d3fcff4 --- /dev/null +++ b/data/XML/admin.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/data/XML/commands.xml b/data/XML/commands.xml new file mode 100644 index 0000000000..17ffe01dcc --- /dev/null +++ b/data/XML/commands.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/XML/mounts.xml b/data/XML/mounts.xml new file mode 100644 index 0000000000..e3b2e6447a --- /dev/null +++ b/data/XML/mounts.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/XML/outfits.xml b/data/XML/outfits.xml new file mode 100644 index 0000000000..a83f9a6169 --- /dev/null +++ b/data/XML/outfits.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/XML/quests.xml b/data/XML/quests.xml new file mode 100644 index 0000000000..47f5b3396c --- /dev/null +++ b/data/XML/quests.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/data/XML/stages.xml b/data/XML/stages.xml new file mode 100644 index 0000000000..6895898c8a --- /dev/null +++ b/data/XML/stages.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/data/XML/vocations.xml b/data/XML/vocations.xml new file mode 100644 index 0000000000..49622c6bda --- /dev/null +++ b/data/XML/vocations.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/actions/actions.xml b/data/actions/actions.xml new file mode 100644 index 0000000000..7ca1226b4f --- /dev/null +++ b/data/actions/actions.xml @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/actions/lib/actions.lua b/data/actions/lib/actions.lua new file mode 100644 index 0000000000..38aee66b36 --- /dev/null +++ b/data/actions/lib/actions.lua @@ -0,0 +1,31 @@ +function destroyItem(cid, itemEx, toPosition) + if itemEx.uid <= 65535 or itemEx.actionid > 0 then + return FALSE + end + + if (itemEx.itemid >= 1724 and itemEx.itemid <= 1741) or (itemEx.itemid >= 2581 and itemEx.itemid <= 2588) or itemEx.itemid == 1770 or itemEx.itemid == 2098 or itemEx.itemid == 1774 or itemEx.itemid == 1775 or itemEx.itemid == 2064 or (itemEx.itemid >= 1747 and itemEx.itemid <= 1753) or (itemEx.itemid >= 1714 and itemEx.itemid <= 1717) or (itemEx.itemid >= 1650 and itemEx.itemid <= 1653) or (itemEx.itemid >= 1666 and itemEx.itemid <= 1677) or (itemEx.itemid >= 1614 and itemEx.itemid <= 1616) or (itemEx.itemid >= 3813 and itemEx.itemid <= 3820) or (itemEx.itemid >= 3807 and itemEx.itemid <= 3810) or (itemEx.itemid >= 2080 and itemEx.itemid <= 2085) or (itemEx.itemid >= 2116 and itemEx.itemid <= 2119) or itemEx.itemid == 2094 or itemEx.itemid == 2095 or itemEx.itemid == 1619 or itemEx.itemid == 2602 or itemEx.itemid == 3805 or itemEx.itemid == 3806 then + if math.random(1, 7) == 1 then + if itemEx.itemid == 1738 or itemEx.itemid == 1739 or (itemEx.itemid >= 2581 and itemEx.itemid <= 2588) or itemEx.itemid == 1770 or itemEx.itemid == 2098 or itemEx.itemid == 1774 or itemEx.itemid == 1775 or itemEx.itemid == 2064 then + doCreateItem(2250, 1, toPosition) + elseif (itemEx.itemid >= 1747 and itemEx.itemid <= 1749) or itemEx.itemid == 1740 then + doCreateItem(2251, 1, toPosition) + elseif (itemEx.itemid >= 1714 and itemEx.itemid <= 1717) then + doCreateItem(2252, 1, toPosition) + elseif (itemEx.itemid >= 1650 and itemEx.itemid <= 1653) or (itemEx.itemid >= 1666 and itemEx.itemid <= 1677) or (itemEx.itemid >= 1614 and itemEx.itemid <= 1616) or (itemEx.itemid >= 3813 and itemEx.itemid <= 3820) or (itemEx.itemid >= 3807 and itemEx.itemid <= 3810) then + doCreateItem(2253, 1, toPosition) + elseif (itemEx.itemid >= 1724 and itemEx.itemid <= 1737) or (itemEx.itemid >= 2080 and itemEx.itemid <= 2085) or (itemEx.itemid >= 2116 and itemEx.itemid <= 2119) or itemEx.itemid == 2094 or itemEx.itemid == 2095 then + doCreateItem(2254, 1, toPosition) + elseif (itemEx.itemid >= 1750 and itemEx.itemid <= 1753) or itemEx.itemid == 1619 or itemEx.itemid == 1741 then + doCreateItem(2255, 1, toPosition) + elseif itemEx.itemid == 2602 then + doCreateItem(2257, 1, toPosition) + elseif itemEx.itemid == 3805 or itemEx.itemid == 3806 then + doCreateItem(2259, 1, toPosition) + end + doRemoveItem(itemEx.uid, 1) + end + doSendMagicEffect(toPosition, CONST_ME_POFF) + return TRUE + end + return FALSE +end \ No newline at end of file diff --git a/data/actions/scripts/other/blueberrybush.lua b/data/actions/scripts/other/blueberrybush.lua new file mode 100644 index 0000000000..28af49e779 --- /dev/null +++ b/data/actions/scripts/other/blueberrybush.lua @@ -0,0 +1,25 @@ +function onUse(cid, item, fromPosition, itemEx, toPosition) + if item.itemid == ITEM_BLUEBERRYBUSH then + doTransformItem(item.uid, ITEM_BUSH) + doCreateItem(ITEM_BLUEBERRY, 3, fromPosition) + addEvent(transformBack, 300000, {position = fromPosition}) + end + return TRUE +end + +function transformBack(parameters) + parameters.position.stackpos = STACKPOS_TOP_MOVEABLE_ITEM_OR_CREATURE + local topThing = getThingfromPos(parameters.position) + if topThing.itemid == ITEM_BLUEBERRY then + addEvent(transformBack, 300000, parameters) + else + for i = STACKPOS_FIRST_ITEM_ABOVE_GROUNDTILE, STACKPOS_TOP_MOVEABLE_ITEM_OR_CREATURE do + parameters.position.stackpos = i + topThing = getThingfromPos(parameters.position) + if topThing.itemid == ITEM_BUSH then + doTransformItem(topThing.uid, ITEM_BLUEBERRYBUSH) + break + end + end + end +end \ No newline at end of file diff --git a/data/actions/scripts/other/changegold.lua b/data/actions/scripts/other/changegold.lua new file mode 100644 index 0000000000..03c2cdc420 --- /dev/null +++ b/data/actions/scripts/other/changegold.lua @@ -0,0 +1,18 @@ +function onUse(cid, item, fromPosition, itemEx, toPosition) + if item.itemid == ITEM_GOLD_COIN and item.type == ITEMCOUNT_MAX then + doChangeTypeItem(item.uid, item.type - item.type) + doPlayerAddItem(cid, ITEM_PLATINUM_COIN, 1) + elseif item.itemid == ITEM_PLATINUM_COIN and item.type == ITEMCOUNT_MAX then + doChangeTypeItem(item.uid, item.type - item.type) + doPlayerAddItem(cid, ITEM_CRYSTAL_COIN, 1) + elseif item.itemid == ITEM_PLATINUM_COIN and item.type < ITEMCOUNT_MAX then + doChangeTypeItem(item.uid, item.type - 1) + doPlayerAddItem(cid, ITEM_GOLD_COIN, ITEMCOUNT_MAX) + elseif item.itemid == ITEM_CRYSTAL_COIN then + doChangeTypeItem(item.uid, item.type - 1) + doPlayerAddItem(cid, ITEM_PLATINUM_COIN, ITEMCOUNT_MAX) + else + return FALSE + end + return TRUE +end \ No newline at end of file diff --git a/data/actions/scripts/other/constructionkits.lua b/data/actions/scripts/other/constructionkits.lua new file mode 100644 index 0000000000..bef9b4a473 --- /dev/null +++ b/data/actions/scripts/other/constructionkits.lua @@ -0,0 +1,14 @@ +local constructionKits = {[3901] = 1652, [3902] = 1658, [3903] = 1666, [3904] = 1670, [3905] = 3813, [3906] = 3817, [3907] = 3821, [3908] = 2602, [3909] = 1614, [3910] = 1615, [3911] = 1616, [3912] = 1619, [3913] = 3805, [3914] = 3807, [3915] = 1740, [3917] = 2084, [3918] = 2095, [3919] = 3809, [3920] = 3811, [3921] = 1716, [3923] = 1774, [3926] = 2080, [3927] = 2098, [3928] = 2104, [3929] = 2101, [3931] = 2105, [3932] = 1724, [3933] = 1728, [3934] = 1732, [3935] = 1775, [3936] = 3832, [3937] = 2064, [3938] = 1750} +function onUse(cid, item, fromPosition, itemEx, toPosition) + if fromPosition.x == CONTAINER_POSITION then + doPlayerSendCancel(cid, "Put the construction kit on the floor first.") + elseif getTileHouseInfo(fromPosition) == FALSE then + doPlayerSendCancel(cid,"You may only construct this inside a house.") + elseif constructionKits[item.itemid] ~= nil then + doTransformItem(item.uid, constructionKits[item.itemid]) + doSendMagicEffect(fromPosition, CONST_ME_POFF) + else + return FALSE + end + return TRUE +end \ No newline at end of file diff --git a/data/actions/scripts/other/createbread.lua b/data/actions/scripts/other/createbread.lua new file mode 100644 index 0000000000..dfeb98eaa6 --- /dev/null +++ b/data/actions/scripts/other/createbread.lua @@ -0,0 +1,14 @@ +local liquidContainers = {1775, 2005, 2006, 2007, 2008, 2009, 2011, 2012, 2013, 2014, 2015, 2023, 2031, 2032, 2033} +function onUse(cid, item, fromPosition, itemEx, toPosition) + if item.itemid == 2692 and isInArray(liquidContainers, itemEx.itemid) == TRUE and itemEx.type == 9 then + doChangeTypeItem(item.uid, item.type - 1) + doPlayerAddItem(cid, 2693, 1) + doChangeTypeItem(itemEx.uid, item.type - item.type) + elseif itemEx.itemid == 1381 then + doChangeTypeItem(item.uid, item.type - 1) + doPlayerAddItem(cid, 2692, 1) + else + return FALSE + end + return TRUE +end \ No newline at end of file diff --git a/data/actions/scripts/other/decayto.lua b/data/actions/scripts/other/decayto.lua new file mode 100644 index 0000000000..1849edcfa8 --- /dev/null +++ b/data/actions/scripts/other/decayto.lua @@ -0,0 +1,9 @@ +local itemIdArray = {[2041] = 2042, [2042] = 2041, [2044] = 2045, [2045] = 2044, [2047] = 2048, [2048] = 2047, [2050] = 2051, [2051] = 2050, [2052] = 2053, [2053] = 2051, [2054] = 2055, [2054] = 2055} +function onUse(cid, item, fromPosition, itemEx, toPosition) + if itemIdArray[item.itemid] ~= nil then + doTransformItem(item.uid, itemIdArray[item.itemid]) + doDecayItem(item.uid) + return TRUE + end + return FALSE +end \ No newline at end of file diff --git a/data/actions/scripts/other/destroy.lua b/data/actions/scripts/other/destroy.lua new file mode 100644 index 0000000000..891cf8dc50 --- /dev/null +++ b/data/actions/scripts/other/destroy.lua @@ -0,0 +1,3 @@ +function onUse(cid, item, fromPosition, itemEx, toPosition) + return destroyItem(cid, itemEx, toPosition) +end \ No newline at end of file diff --git a/data/actions/scripts/other/dice.lua b/data/actions/scripts/other/dice.lua new file mode 100644 index 0000000000..0a21cf513f --- /dev/null +++ b/data/actions/scripts/other/dice.lua @@ -0,0 +1,9 @@ +function onUse(cid, item, fromPosition, itemEx, toPosition) + local value = math.random(5792, 5797) + if fromPosition.x ~= CONTAINER_POSITION then + doSendMagicEffect(fromPosition, CONST_ME_CRAPS) + end + doTransformItem(item.uid, value) + doCreatureSay(cid, getCreatureName(cid) .. ' rolled a ' .. value - 5791 .. '.', TALKTYPE_ORANGE_1) + return TRUE +end \ No newline at end of file diff --git a/data/actions/scripts/other/doors.lua b/data/actions/scripts/other/doors.lua new file mode 100644 index 0000000000..730d9dd23f --- /dev/null +++ b/data/actions/scripts/other/doors.lua @@ -0,0 +1,77 @@ +function onUse(cid, item, fromPosition, itemEx, toPosition) + if isInArray(questDoors, item.itemid) == TRUE then + if getPlayerStorageValue(cid, item.actionid) ~= -1 then + doTransformItem(item.uid, item.itemid + 1) + doTeleportThing(cid, toPosition, TRUE) + else + doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "The door seems to be sealed against unwanted intruders.") + end + return TRUE + elseif isInArray(levelDoors, item.itemid) == TRUE then + if item.actionid > 0 and getPlayerLevel(cid) >= item.actionid - 1000 then + doTransformItem(item.uid, item.itemid + 1) + doTeleportThing(cid, toPosition, TRUE) + else + doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "Only the worthy may pass.") + end + return TRUE + elseif isInArray(keys, item.itemid) == TRUE then + if itemEx.actionid > 0 then + if item.actionid == itemEx.actionid then + if doors[itemEx.itemid] ~= nil then + doTransformItem(itemEx.uid, doors[itemEx.itemid]) + return TRUE + end + end + doPlayerSendCancel(cid, "The key does not match.") + return TRUE + end + return FALSE + elseif isInArray(horizontalOpenDoors, item.itemid) == TRUE then + local newPosition = toPosition + newPosition.y = newPosition.y + 1 + local doorPosition = fromPosition + doorPosition.stackpos = STACKPOS_TOP_MOVEABLE_ITEM_OR_CREATURE + local doorCreature = getThingfromPos(doorPosition) + if doorCreature.itemid ~= 0 then + if getTilePzInfo(doorPosition) == TRUE and getTilePzInfo(newPosition) == FALSE and doorCreature.uid ~= cid then + doPlayerSendCancel(cid, "Sorry, not possible.") + else + doTeleportThing(doorCreature.uid, newPosition, TRUE) + if isInArray(openSpecialDoors, item.itemid) ~= TRUE then + doTransformItem(item.uid, item.itemid - 1) + end + end + return TRUE + end + doTransformItem(item.uid, item.itemid - 1) + return TRUE + elseif isInArray(verticalOpenDoors, item.itemid) == TRUE then + local newPosition = toPosition + newPosition.x = newPosition.x + 1 + local doorPosition = fromPosition + doorPosition.stackpos = STACKPOS_TOP_MOVEABLE_ITEM_OR_CREATURE + local doorCreature = getThingfromPos(doorPosition) + if doorCreature.itemid ~= 0 then + if getTilePzInfo(doorPosition) == TRUE and getTilePzInfo(newPosition) == FALSE and doorCreature.uid ~= cid then + doPlayerSendCancel(cid, "Sorry, not possible.") + else + doTeleportThing(doorCreature.uid, newPosition, TRUE) + if isInArray(openSpecialDoors, item.itemid) ~= TRUE then + doTransformItem(item.uid, item.itemid - 1) + end + end + return TRUE + end + doTransformItem(item.uid, item.itemid - 1) + return TRUE + elseif doors[item.itemid] ~= nil then + if item.actionid == 0 then + doTransformItem(item.uid, doors[item.itemid]) + else + doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "It is locked.") + end + return TRUE + end + return FALSE +end \ No newline at end of file diff --git a/data/actions/scripts/other/fireworksrocket.lua b/data/actions/scripts/other/fireworksrocket.lua new file mode 100644 index 0000000000..a63f7228df --- /dev/null +++ b/data/actions/scripts/other/fireworksrocket.lua @@ -0,0 +1,13 @@ +function onUse(cid, item, fromPosition, itemEx, toPosition) + if fromPosition.x ~= CONTAINER_POSITION then + fireworksEffect = math.random(CONST_ME_FIREWORK_YELLOW, CONST_ME_FIREWORK_BLUE) + doSendMagicEffect(fromPosition, fireworksEffect) + else + doSendMagicEffect(fromPosition, CONST_ME_HITBYFIRE) + doSendMagicEffect(fromPosition, CONST_ME_EXPLOSIONAREA) + doCreatureSay(cid, "Ouch! Rather place it on the ground next time.", TALKTYPE_ORANGE_1) + doCreatureAddHealth(cid, -10) + end + doRemoveItem(cid, item.uid, 1) + return TRUE +end \ No newline at end of file diff --git a/data/actions/scripts/other/fluids.lua b/data/actions/scripts/other/fluids.lua new file mode 100644 index 0000000000..c32ad9792f --- /dev/null +++ b/data/actions/scripts/other/fluids.lua @@ -0,0 +1,61 @@ +-- TODO: Rewrite this script using fluidtypes from LIQUIDS doc-file, +-- and correct itemid's to recieve the liquids. + +local drunk = createConditionObject(CONDITION_DRUNK) +setConditionParam(drunk, CONDITION_PARAM_TICKS, 60000) + +local poison = createConditionObject(CONDITION_POISON) +addDamageCondition(poison, 2, 6000, -5) +addDamageCondition(poison, 3, 6000, -4) +addDamageCondition(poison, 5, 6000, -3) +addDamageCondition(poison, 10, 6000, -2) +addDamageCondition(poison, 20, 6000, -1) + +local fluidType = {3, 4, 5, 7, 10, 11, 13, 15, 19} +local fluidMessage = {"Aah...", "Urgh!", "Mmmh.", "Aaaah...", "Aaaah...", "Urgh!", "Urgh!", "Aah...", "Urgh!"} +function onUse(cid, item, fromPosition, itemEx, toPosition) + if itemEx.itemid == 1 then + if item.type == 0 then + doPlayerSendCancel(cid, "It is empty.") + elseif itemEx.uid == cid then + doChangeTypeItem(item.uid, 0) + if item.type == 3 or item.type == 15 then + doTargetCombatCondition(0, cid, drunk, CONST_ME_NONE) + elseif item.type == 4 then + doTargetCombatCondition(0, cid, poison, CONST_ME_NONE) + elseif item.type == 7 then + doPlayerAddMana(cid, math.random(50, 150)) + doSendMagicEffect(fromPosition, CONST_ME_MAGIC_BLUE) + elseif item.type == 10 then + doCreatureAddHealth(cid, 60) + doSendMagicEffect(fromPosition, CONST_ME_MAGIC_BLUE) + end + for i = 0, table.maxn(fluidType) do + if item.type == fluidType[i] then + doCreatureSay(cid, fluidMessage[i], TALKTYPE_ORANGE_1) + return TRUE + end + end + doCreatureSay(cid, "Gulp.", TALKTYPE_ORANGE_1) + else + local splash = doCreateItem(2025, item.type, toPosition) + doChangeTypeItem(item.uid, 0) + doDecayItem(splash) + end + else + local fluidSource = getFluidSourceType(itemEx.itemid) + if fluidSource ~= 0 then + doChangeTypeItem(item.uid, fluidSource) + elseif item.type == 0 then + doPlayerSendCancel(cid, "It is empty.") + else + if toPosition.x == CONTAINER_POSITION then + toPosition = getCreaturePosition(cid) + end + splash = doCreateItem(2025, item.type, toPosition) + doChangeTypeItem(item.uid, 0) + doDecayItem(splash) + end + end + return TRUE +end \ No newline at end of file diff --git a/data/actions/scripts/other/food.lua b/data/actions/scripts/other/food.lua new file mode 100644 index 0000000000..1f285f47e5 --- /dev/null +++ b/data/actions/scripts/other/food.lua @@ -0,0 +1,79 @@ +local food = +{ + [2362] = {8, "Crunch."}, + [2666] = {15, "Munch."}, + [2667] = {12, "Munch."}, + [2668] = {10, "Mmmm."}, + [2669] = {17, "Munch."}, + [2670] = {4, "Gulp."}, + [2671] = {30, "Chomp."}, + [2672] = {60, "Chomp."}, + [2673] = {5, "Yum."}, + [2674] = {6, "Yum."}, + [2675] = {13, "Yum."}, + [2676] = {8, "Yum."}, + [2677] = {1, "Yum."}, + [2678] = {18, "Slurp."}, + [2679] = {1, "Yum."}, + [2680] = {2, "Yum."}, + [2681] = {9, "Yum."}, + [2682] = {20, "Yum."}, + [2683] = {17, "Munch."}, + [2684] = {8, "Crunch."}, + [2685] = {6, "Munch."}, + [2686] = {9, "Crunch."}, + [2687] = {2, "Crunch."}, + [2688] = {9, "Munch."}, + [2689] = {10, "Crunch."}, + [2690] = {3, "Crunch."}, + [2691] = {8, "Crunch."}, + [2792] = {6, "Munch."}, + [2793] = {9, "Munch."}, + [2695] = {6, "Gulp."}, + [2696] = {9, "Smack."}, + [2787] = {9, "Munch."}, + [2788] = {4, "Munch."}, + [2789] = {22, "Munch."}, + [2790] = {30, "Munch."}, + [2791] = {30, "Munch."}, + [2792] = {6, "Munch."}, + [2794] = {3, "Munch."}, + [2795] = {36, "Munch."}, + [2796] = {5, "Munch."}, + [2793] = {9, "Munch."}, + [5097] = {4, "Yum."}, + [6125] = {8, "Gulp."}, + [6278] = {10, "Mmmm."}, + [6279] = {15, "Mmmm."}, + [6393] = {12, "Mmmm."}, + [6394] = {15, "Mmmm."}, + [6501] = {20, "Mmmm."}, + [6541] = {6, "Gulp."}, + [6542] = {6, "Gulp."}, + [6543] = {6, "Gulp."}, + [6544] = {6, "Gulp."}, + [6545] = {6, "Gulp."}, + [6569] = {1, "Mmmm."}, + [6574] = {4, "Mmmm."}, + [7158] = {15, "Munch."}, + [7159] = {13, "Munch."}, + [7372] = {7, "Yum."}, + [7373] = {7, "Yum."}, + [7374] = {7, "Yum."}, + [7375] = {7, "Yum."}, + [7376] = {7, "Yum."}, + [7377] = {7, "Yum."} +} +function onUse(cid, item, frompos, item2, topos) + if(food[item.itemid] ~= nil) then + if (getPlayerFood(cid) + food[item.itemid][1]) >= 400 then + doPlayerSendCancel(cid, "You are full.") + else + doPlayerFeed(cid, food[item.itemid][1] * 4) + doCreatureSay(cid, food[item.itemid][2], TALKTYPE_ORANGE_1) + doRemoveItem(item.uid, 1) + end + return TRUE + end + return FALSE +end \ No newline at end of file diff --git a/data/actions/scripts/other/music.lua b/data/actions/scripts/other/music.lua new file mode 100644 index 0000000000..2100856983 --- /dev/null +++ b/data/actions/scripts/other/music.lua @@ -0,0 +1,5 @@ +function onUse(cid, item, fromPosition, itemEx, toPosition) + -- TODO: Different music effect for different instruments. + doSendMagicEffect(fromPosition, CONST_ME_SOUND_BLUE) + return TRUE +end \ No newline at end of file diff --git a/data/actions/scripts/other/partyhat.lua b/data/actions/scripts/other/partyhat.lua new file mode 100644 index 0000000000..c45e8e2732 --- /dev/null +++ b/data/actions/scripts/other/partyhat.lua @@ -0,0 +1,7 @@ +function onUse(cid, item, fromPosition, itemEx, toPosition) + if item.uid == getPlayerSlotItem(cid, CONST_SLOT_HEAD).uid then + doSendMagicEffect(getPlayerPosition(cid), CONST_ME_GIFT_WRAPS) + return TRUE + end + return FALSE +end \ No newline at end of file diff --git a/data/actions/scripts/other/partytrumpet.lua b/data/actions/scripts/other/partytrumpet.lua new file mode 100644 index 0000000000..7b365bf804 --- /dev/null +++ b/data/actions/scripts/other/partytrumpet.lua @@ -0,0 +1,7 @@ +function onUse(cid, item, fromPosition, itemEx, toPosition) + doTransformItem(item.uid, 6573) + doCreatureSay(cid, "TOOOOOOT!", TALKTYPE_ORANGE_1) + doSendMagicEffect(fromPosition, CONST_ME_SOUND_BLUE) + doDecayItem(item.uid) + return TRUE +end \ No newline at end of file diff --git a/data/actions/scripts/other/piggybank.lua b/data/actions/scripts/other/piggybank.lua new file mode 100644 index 0000000000..fded8a3287 --- /dev/null +++ b/data/actions/scripts/other/piggybank.lua @@ -0,0 +1,11 @@ +function onUse(cid, item, fromPosition, itemEx, toPosition) + if math.random(1, 6) == 1 then + doSendMagicEffect(fromPosition, CONST_ME_POFF) + doPlayerAddItem(cid, ITEM_GOLD_COIN, 1) + doTransformItem(item.uid, 2115) + else + doSendMagicEffect(fromPosition, CONST_ME_SOUND_YELLOW) + doPlayerAddItem(cid, ITEM_PLATINUM_COIN, 1) + end + return TRUE +end \ No newline at end of file diff --git a/data/actions/scripts/other/potions.lua b/data/actions/scripts/other/potions.lua new file mode 100644 index 0000000000..a56e683357 --- /dev/null +++ b/data/actions/scripts/other/potions.lua @@ -0,0 +1,148 @@ +local ultimateHealthPot = 8473 +local greatHealthPot = 7591 +local greatManaPot = 7590 +local greatSpiritPot = 8472 +local strongHealthPot = 7588 +local strongManaPot = 7589 +local healthPot = 7618 +local manaPot = 7620 +local smallHealthPot = 8704 +local antidotePot = 8474 +local greatEmptyPot = 7635 +local strongEmptyPot = 7634 +local emptyPot = 7636 + +local antidote = createCombatObject() +setCombatParam(antidote, COMBAT_PARAM_TYPE, COMBAT_HEALING) +setCombatParam(antidote, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +setCombatParam(antidote, COMBAT_PARAM_TARGETCASTERORTOPMOST, TRUE) +setCombatParam(antidote, COMBAT_PARAM_AGGRESSIVE, FALSE) +setCombatParam(antidote, COMBAT_PARAM_DISPEL, CONDITION_POISON) + +local exhaust = createConditionObject(CONDITION_EXHAUST_HEAL) +setConditionParam(exhaust, CONDITION_PARAM_TICKS, (getConfigInfo('timeBetweenExActions') - 100)) +-- 1000 - 100 due to exact condition timing. -100 doesn't hurt us, and players don't have reminding ~50ms exhaustion. + +function onUse(cid, item, fromPosition, itemEx, toPosition) + if(itemEx.uid ~= cid or itemEx.itemid ~= 1) then + return TRUE + end + + if(getCreatureCondition(cid, CONDITION_EXHAUST_HEAL) == TRUE) then + doPlayerSendDefaultCancel(cid, RETURNVALUE_YOUAREEXHAUSTED) + return TRUE + end + + if(item.itemid == antidotePot) then + if(doCombat(cid, antidote, numberToVariant(cid)) == LUA_ERROR) then + return FALSE + end + doAddCondition(cid, exhaust) + doCreatureSay(cid, "Aaaah...", TALKTYPE_ORANGE_1) + doRemoveItem(item.uid, 1) + doPlayerAddItem(cid, emptyPot, 1) + elseif(item.itemid == smallHealthPot) then + if(doTargetCombatHealth(0, cid, COMBAT_HEALING, 50, 100, CONST_ME_MAGIC_BLUE) == LUA_ERROR) then + return FALSE + end + doAddCondition(cid, exhaust) + doCreatureSay(cid, "Aaaah...", TALKTYPE_ORANGE_1) + doRemoveItem(item.uid, 1) + doPlayerAddItem(cid, emptyPot, 1) + elseif(item.itemid == healthPot) then + if(doTargetCombatHealth(0, cid, COMBAT_HEALING, 100, 200, CONST_ME_MAGIC_BLUE) == LUA_ERROR) then + return FALSE + end + doAddCondition(cid, exhaust) + doCreatureSay(cid, "Aaaah...", TALKTYPE_ORANGE_1) + doRemoveItem(item.uid, 1) + doPlayerAddItem(cid, emptyPot, 1) + elseif(item.itemid == manaPot) then + if(doTargetCombatMana(0, cid, 70, 130, CONST_ME_MAGIC_BLUE) == LUA_ERROR) then + return FALSE + end + doAddCondition(cid, exhaust) + doCreatureSay(cid, "Aaaah...", TALKTYPE_ORANGE_1) + doRemoveItem(item.uid, 1) + doPlayerAddItem(cid, emptyPot, 1) + elseif(item.itemid == strongHealthPot) then + if(not(isKnight(cid) or isPaladin(cid)) or (getPlayerLevel(cid) < 50)) and not(getPlayerGroupId(cid) >= 2) then + doCreatureSay(cid, "This potion can only be consumed by paladins and knights of level 50 or higher.", TALKTYPE_ORANGE_1) + return TRUE + end + + if(doTargetCombatHealth(0, cid, COMBAT_HEALING, 200, 400, CONST_ME_MAGIC_BLUE) == LUA_ERROR) then + return FALSE + end + doAddCondition(cid, exhaust) + doCreatureSay(cid, "Aaaah...", TALKTYPE_ORANGE_1) + doRemoveItem(item.uid, 1) + doPlayerAddItem(cid, strongEmptyPot, 1) + elseif(item.itemid == strongManaPot) then + if(not(isSorcerer(cid) or isDruid(cid) or isPaladin(cid)) or (getPlayerLevel(cid) < 50)) and not(getPlayerGroupId(cid) >= 2) then + doCreatureSay(cid, "This potion can only be consumed by sorcerers, druids and paladins of level 50 or higher.", TALKTYPE_ORANGE_1) + return TRUE + end + + if(doTargetCombatMana(0, cid, 110, 190, CONST_ME_MAGIC_BLUE) == LUA_ERROR) then + return FALSE + end + doAddCondition(cid, exhaust) + doCreatureSay(cid, "Aaaah...", TALKTYPE_ORANGE_1) + doRemoveItem(item.uid, 1) + doPlayerAddItem(cid, strongEmptyPot, 1) + elseif(item.itemid == greatSpiritPot) then + if(not(isPaladin(cid)) or (getPlayerLevel(cid) < 80)) and not(getPlayerGroupId(cid) >= 2) then + doCreatureSay(cid, "This potion can only be consumed by paladins of level 80 or higher.", TALKTYPE_ORANGE_1) + return TRUE + end + + if(doTargetCombatHealth(0, cid, COMBAT_HEALING, 200, 400, CONST_ME_MAGIC_BLUE) == LUA_ERROR or doTargetCombatMana(0, cid, 110, 190, CONST_ME_MAGIC_BLUE) == LUA_ERROR) then + return FALSE + end + doAddCondition(cid, exhaust) + doCreatureSay(cid, "Aaaah...", TALKTYPE_ORANGE_1) + doRemoveItem(item.uid, 1) + doPlayerAddItem(cid, greatEmptyPot, 1) + elseif(item.itemid == greatHealthPot) then + if(not(isKnight(cid)) or (getPlayerLevel(cid) < 80)) and not(getPlayerGroupId(cid) >= 2) then + doCreatureSay(cid, "This potion can only be consumed by knights of level 80 or higher.", TALKTYPE_ORANGE_1) + return TRUE + end + + if(doTargetCombatHealth(0, cid, COMBAT_HEALING, 500, 700, CONST_ME_MAGIC_BLUE) == LUA_ERROR) then + return FALSE + end + doAddCondition(cid, exhaust) + doCreatureSay(cid, "Aaaah...", TALKTYPE_ORANGE_1) + doRemoveItem(item.uid, 1) + doPlayerAddItem(cid, greatEmptyPot, 1) + elseif(item.itemid == greatManaPot) then + if(not(isSorcerer(cid) or isDruid(cid)) or (getPlayerLevel(cid) < 80)) and not(getPlayerGroupId(cid) >= 2) then + doCreatureSay(cid, "This potion can only be consumed by sorcerers and druids of level 80 or higher.", TALKTYPE_ORANGE_1) + return TRUE + end + + if(doTargetCombatMana(0, cid, 200, 300, CONST_ME_MAGIC_BLUE) == LUA_ERROR) then + return FALSE + end + doAddCondition(cid, exhaust) + doCreatureSay(cid, "Aaaah...", TALKTYPE_ORANGE_1) + doRemoveItem(item.uid, 1) + doPlayerAddItem(cid, greatEmptyPot, 1) + elseif(item.itemid == ultimateHealthPot) then + if(not(isKnight(cid)) or (getPlayerLevel(cid) < 130)) and not(getPlayerGroupId(cid) >= 2) then + doCreatureSay(cid, "This potion can only be consumed by knights of level 130 or higher.", TALKTYPE_ORANGE_1) + return TRUE + end + + if(doTargetCombatHealth(0, cid, COMBAT_HEALING, 800, 1000, CONST_ME_MAGIC_BLUE) == LUA_ERROR) then + return FALSE + end + doAddCondition(cid, exhaust) + doCreatureSay(cid, "Aaaah...", TALKTYPE_ORANGE_1) + doRemoveItem(item.uid, 1) + doPlayerAddItem(cid, greatEmptyPot, 1) + end + return TRUE +end diff --git a/data/actions/scripts/other/spellbook.lua b/data/actions/scripts/other/spellbook.lua new file mode 100644 index 0000000000..20ed3c655f --- /dev/null +++ b/data/actions/scripts/other/spellbook.lua @@ -0,0 +1,29 @@ +function onUse(cid, item, fromPosition, itemEx, toPosition) + local count = getPlayerInstantSpellCount(cid) + local text = "" + local t = {} + for i = 0, count - 1 do + local spell = getPlayerInstantSpellInfo(cid, i) + if spell.level ~= 0 then + if spell.manapercent > 0 then + spell.mana = spell.manapercent .. "%" + end + table.insert(t, spell) + end + end + table.sort(t, function(a, b) return a.level < b.level end) + local prevLevel = -1 + for i, spell in ipairs(t) do + local line = "" + if prevLevel ~= spell.level then + if i ~= 1 then + line = "\n" + end + line = line .. "Spells for Level " .. spell.level .. "\n" + prevLevel = spell.level + end + text = text .. line .. " " .. spell.words .. " - " .. spell.name .. " : " .. spell.mana .. "\n" + end + doShowTextDialog(cid, item.itemid, text) + return TRUE +end \ No newline at end of file diff --git a/data/actions/scripts/other/surprisebag.lua b/data/actions/scripts/other/surprisebag.lua new file mode 100644 index 0000000000..ff1989ec02 --- /dev/null +++ b/data/actions/scripts/other/surprisebag.lua @@ -0,0 +1,24 @@ +local bluePresent = {2687, 6394, 6280, 6574, 6578, 6575, 6577, 6569, 6576, 6572, 2114} +local redPresent = {2152, 2152, 2152, 2153, 5944, 2112, 6568, 6566, 2492, 2520, 2195, 2114, 2114, 2114, 6394, 6394, 6576, 6576, 6578, 6578, 6574, 6574} + +function onUse(cid, item, fromPosition, itemEx, toPosition) + local count = 1 + if item.itemid == 6570 then + local randomChance = math.random(1, 11) + if randomChance == 1 then + count = 10 + elseif randomChance == 2 then + count = 3 + end + doPlayerAddItem(cid, bluePresent[randomChance], count) + elseif item.itemid == 6571 then + local randomChance = math.random(1, 22) + if randomChance > 0 and randomChance < 4 then + count = 10 + end + doPlayerAddItem(cid, redPresent[randomChance], count) + end + doSendMagicEffect(fromPosition, CONST_ME_GIFT_WRAPS) + doRemoveItem(item.uid, 1) + return TRUE +end \ No newline at end of file diff --git a/data/actions/scripts/other/teleport.lua b/data/actions/scripts/other/teleport.lua new file mode 100644 index 0000000000..a76ad0ef13 --- /dev/null +++ b/data/actions/scripts/other/teleport.lua @@ -0,0 +1,11 @@ +local upFloorIds = {1386, 3678, 5543} +function onUse(cid, item, fromPosition, itemEx, toPosition) + if isInArray(upFloorIds, item.itemid) == TRUE then + fromPosition.y = fromPosition.y + 1 + fromPosition.z = fromPosition.z - 1 + else + fromPosition.z = fromPosition.z + 1 + end + doTeleportThing(cid, fromPosition, FALSE) + return TRUE +end \ No newline at end of file diff --git a/data/actions/scripts/other/trap.lua b/data/actions/scripts/other/trap.lua new file mode 100644 index 0000000000..b65becccbd --- /dev/null +++ b/data/actions/scripts/other/trap.lua @@ -0,0 +1,5 @@ +function onUse(cid, item, fromPosition, itemEx, toPosition) + doTransformItem(item.uid, item.itemid - 1) + doSendMagicEffect(fromPosition, CONST_ME_POFF) + return TRUE +end \ No newline at end of file diff --git a/data/actions/scripts/other/watch.lua b/data/actions/scripts/other/watch.lua new file mode 100644 index 0000000000..c22a0ede71 --- /dev/null +++ b/data/actions/scripts/other/watch.lua @@ -0,0 +1,20 @@ +function onUse(cid, item, fromPosition, itemEx, toPosition) + local twentyfour = TRUE + local tibiantime = TRUE + if tibiantime == FALSE then + if twentyfour == TRUE then + time = os.date('%H:%M') + else + time = os.date('%I:%M %p') + end + else + varh = (os.date('%M') * 60 + os.date('%S')) / 150 + tibH = math.floor(varh) + tibM = math.floor(60 * (varh-tibH)) + if tibH < 10 then tibH = '0'..tibH end + if tibM < 10 then tibM = '0'..tibM end + time = (tibH..':'..tibM) + end + doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, 'The time is ' ..time.. '.') + return TRUE +end \ No newline at end of file diff --git a/data/actions/scripts/other/windows.lua b/data/actions/scripts/other/windows.lua new file mode 100644 index 0000000000..637c4b471c --- /dev/null +++ b/data/actions/scripts/other/windows.lua @@ -0,0 +1,8 @@ +local windows = {[6438] = 6436, [6436] = 6438, [6439] = 6437, [6437] = 6439, [6442] = 6440, [6440] = 6442, [6443] = 6441, [6441] = 6443, [6446] = 6444, [6444] = 6446, [6447] = 6445, [6445] = 6447, [6452] = 6450, [6450] = 6452, [6453] = 6451, [6451] = 6453, [6456] = 6454, [6454] = 6456, [6457] = 6455, [6455] = 6457, [6460] = 6458, [6458] = 6460, [6461] = 6459, [6459] = 6461, [6464] = 6462, [6462] = 6464, [6465] = 6463, [6463] = 6465, [6468] = 6466, [6466] = 6468, [6469] = 6467, [6467] = 6469, [6472] = 6470, [6470] = 6472, [6473] = 6471, [6471] = 6473, [6790] = 6788, [6788] = 6790, [6791] = 6789, [6789] = 6791, [7027] = 7025, [7025] = 7027, [7028] = 7026, [7026] = 7028, [7031] = 7029, [7029] = 7031, [7032] = 7030, [7030] = 7032, [10264] = 10266, [10265] = 10267, [10266] = 10264, [10267] = 10265, [10488] = 10490, [10489] = 10491, [10490] = 10488, [10491] = 10489} +function onUse(cid, item, fromPosition, itemEx, toPosition) + if getTileHouseInfo(getPlayerPosition(cid)) ~= FALSE and windows[item.itemid] ~= nil then + doTransformItem(item.uid, windows[item.itemid]) + return TRUE + end + return FALSE +end diff --git a/data/actions/scripts/quests/annihilator.lua b/data/actions/scripts/quests/annihilator.lua new file mode 100644 index 0000000000..aae2006ef2 --- /dev/null +++ b/data/actions/scripts/quests/annihilator.lua @@ -0,0 +1,47 @@ +local playerPosition = { + {x = 247, y = 659, z = 13, stackpos = STACKPOS_TOP_CREATURE}, + {x = 247, y = 660, z = 13, stackpos = STACKPOS_TOP_CREATURE}, + {x = 247, y = 661, z = 13, stackpos = STACKPOS_TOP_CREATURE}, + {x = 247, y = 662, z = 13, stackpos = STACKPOS_TOP_CREATURE} +} + +local newPosition = { + {x = 189, y = 650, z = 13}, + {x = 189, y = 651, z = 13}, + {x = 189, y = 652, z = 13}, + {x = 189, y = 653, z = 13} +} + +-- Do not modify the declaration lines below. +function onUse(cid, item, fromPosition, itemEx, toPosition) + local player = {} + local failed = FALSE + if item.itemid == 1945 then + for i = 1, 4 do + failed = TRUE + player[i] = getThingfromPos(playerPosition[i]) + if player[i].itemid > 0 then + if isPlayer(player[i].uid) == TRUE then + if getPlayerStorageValue(player[i].uid, 30015) == -1 then + if getPlayerLevel(player[i].uid) >= 100 then + failed = FALSE + end + end + end + end + if failed == TRUE then + doPlayerSendCancel(cid, "Sorry, not possible.") + return TRUE + end + end + for i = 1, 4 do + doSendMagicEffect(playerPosition[i], CONST_ME_POFF) + doTeleportThing(player[i].uid, newPosition[i], FALSE) + doSendMagicEffect(newPosition[i], CONST_ME_ENERGYAREA) + end + doTransformItem(item.uid, item.itemid + 1) + elseif item.itemid == 1946 then + doPlayerSendCancel(cid, "Sorry, not possible.") + end + return TRUE +end \ No newline at end of file diff --git a/data/actions/scripts/quests/quests.lua b/data/actions/scripts/quests/quests.lua new file mode 100644 index 0000000000..d342d484b2 --- /dev/null +++ b/data/actions/scripts/quests/quests.lua @@ -0,0 +1,38 @@ +local annihilatorReward = {1990, 2400, 2431, 2494} +function onUse(cid, item, fromPosition, itemEx, toPosition) + if item.uid > 1000 and item.uid < 10000 then + local itemWeight = getItemWeight(item.uid, 1, FALSE) + local playerCap = getPlayerFreeCap(cid) + if isInArray(annihilatorReward, item.uid) == TRUE then + if getPlayerStorageValue(cid, 30015) == -1 then + if playerCap >= itemWeight then + if item.uid == 1990 then + local container = doPlayerAddItem(cid, 1990, 1) + doAddContainerItem(container, 2326, 1) + else + doPlayerAddItem(cid, item.uid, 1) + end + doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, 'You have found a ' .. getItemName(item.uid) .. '.') + setPlayerStorageValue(cid, 30015, 1) + else + doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, 'You have found a ' .. getItemName(item.uid) .. ' weighing ' .. itemWeight .. ' oz it\'s too heavy.') + end + else + doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "It is empty.") + end + elseif getPlayerStorageValue(cid, item.uid) == -1 then + if playerCap >= itemWeight then + doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, 'You have found a ' .. getItemName(item.uid) .. '.') + doPlayerAddItem(cid, item.uid, 1) + setPlayerStorageValue(cid, item.uid, 1) + else + doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, 'You have found a ' .. getItemName(item.uid) .. ' weighing ' .. itemWeight .. ' oz it\'s too heavy.') + end + else + doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "It is empty.") + end + else + return FALSE + end + return TRUE +end \ No newline at end of file diff --git a/data/actions/scripts/tools/fishing.lua b/data/actions/scripts/tools/fishing.lua new file mode 100644 index 0000000000..835a51a4ab --- /dev/null +++ b/data/actions/scripts/tools/fishing.lua @@ -0,0 +1,17 @@ +local useWorms = FALSE +local waterIds = {493, 4608, 4609, 4610, 4611, 4612, 4613, 4614, 4615, 4616, 4617, 4618, 4619, 4620, 4621, 4622, 4623, 4624, 4625} +function onUse(cid, item, fromPosition, itemEx, toPosition) + if isInArray(waterIds, itemEx.itemid) == TRUE then + if itemEx.itemid ~= 493 then + if useWorms == FALSE or useWorms == TRUE and doPlayerRemoveItem(cid, ITEM_WORM, 1) == TRUE then + if math.random(1, (100 + (getPlayerSkill(cid, SKILL_FISHING) / 10))) <= getPlayerSkill(cid, SKILL_FISHING) then + doPlayerAddItem(cid, ITEM_FISH, 1) + end + doPlayerAddSkillTry(cid, SKILL_FISHING, 1) + end + end + doSendMagicEffect(toPosition, CONST_ME_LOSEENERGY) + return TRUE + end + return FALSE +end \ No newline at end of file diff --git a/data/actions/scripts/tools/machete.lua b/data/actions/scripts/tools/machete.lua new file mode 100644 index 0000000000..25dadb3d62 --- /dev/null +++ b/data/actions/scripts/tools/machete.lua @@ -0,0 +1,8 @@ +function onUse(cid, item, fromPosition, itemEx, toPosition) + if itemEx.itemid == 2782 then + doTransformItem(itemEx.uid, 2781) + doDecayItem(itemEx.uid) + return TRUE + end + return destroyItem(cid, itemEx, toPosition) +end \ No newline at end of file diff --git a/data/actions/scripts/tools/pick.lua b/data/actions/scripts/tools/pick.lua new file mode 100644 index 0000000000..26b721bcc7 --- /dev/null +++ b/data/actions/scripts/tools/pick.lua @@ -0,0 +1,9 @@ +function onUse(cid, item, fromPosition, itemEx, toPosition) + if (itemEx.uid <= 65535 or itemEx.actionid > 0) and (itemEx.itemid == 354 or itemEx.itemid == 355) then + doTransformItem(itemEx.uid, 392) + doDecayItem(itemEx.uid) + doSendMagicEffect(toPosition, CONST_ME_POFF) + return TRUE + end + return FALSE +end \ No newline at end of file diff --git a/data/actions/scripts/tools/rope.lua b/data/actions/scripts/tools/rope.lua new file mode 100644 index 0000000000..8d3bcab6a7 --- /dev/null +++ b/data/actions/scripts/tools/rope.lua @@ -0,0 +1,21 @@ +local holeId = {294, 369, 370, 383, 392, 408, 409, 427, 428, 430, 462, 469, 470, 482, 484, 485, 489, 924, 3135, 3136} +function onUse(cid, item, fromPosition, itemEx, toPosition) + if toPosition.x == CONTAINER_POSITION or toPosition.x == 0 and toPosition.y == 0 and toPosition.z == 0 then + return FALSE + end + + local groundTile = getThingfromPos(toPosition) + if groundTile.itemid == 384 or groundTile.itemid == 418 or groundTile.itemid == 8278 then + doTeleportThing(cid, {x = toPosition.x, y = toPosition.y + 1, z = toPosition.z - 1}, FALSE) + elseif isInArray(holeId, itemEx.itemid) == TRUE then + local hole = getThingfromPos({x = toPosition.x, y = toPosition.y, z = toPosition.z + 1, stackpos = STACKPOS_TOP_MOVEABLE_ITEM_OR_CREATURE}) + if hole.itemid > 0 then + doTeleportThing(hole.uid, {x = toPosition.x, y = toPosition.y + 1, z = toPosition.z}, FALSE) + else + doPlayerSendCancel(cid, "Sorry, not possible.") + end + else + return FALSE + end + return TRUE +end diff --git a/data/actions/scripts/tools/scythe.lua b/data/actions/scripts/tools/scythe.lua new file mode 100644 index 0000000000..f075fa2384 --- /dev/null +++ b/data/actions/scripts/tools/scythe.lua @@ -0,0 +1,9 @@ +function onUse(cid, item, fromPosition, itemEx, toPosition) + if itemEx.itemid == 2739 then + doTransformItem(itemEx.uid, 2737) + doCreateItem(2694, 1, toPosition) + doDecayItem(itemEx.uid) + return TRUE + end + return destroyItem(cid, itemEx, toPosition) +end \ No newline at end of file diff --git a/data/actions/scripts/tools/shovel.lua b/data/actions/scripts/tools/shovel.lua new file mode 100644 index 0000000000..7ce29d0288 --- /dev/null +++ b/data/actions/scripts/tools/shovel.lua @@ -0,0 +1,18 @@ +local holes = {468, 481, 483} +function onUse(cid, item, fromPosition, itemEx, toPosition) + if isInArray(holes, itemEx.itemid) == TRUE then + doTransformItem(itemEx.uid, itemEx.itemid + 1) + doDecayItem(itemEx.uid) + elseif itemEx.itemid == 231 then + local rand = math.random(1, 100) + if rand == 1 then + doCreateItem(2159, 1, toPosition) + elseif rand > 95 then + doSummonCreature("Scarab", toPosition) + end + doSendMagicEffect(toPosition, CONST_ME_POFF) + else + return FALSE + end + return TRUE +end \ No newline at end of file diff --git a/data/creaturescripts/creaturescripts.xml b/data/creaturescripts/creaturescripts.xml new file mode 100644 index 0000000000..dcc051aaa6 --- /dev/null +++ b/data/creaturescripts/creaturescripts.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/data/creaturescripts/lib/creaturescripts.lua b/data/creaturescripts/lib/creaturescripts.lua new file mode 100644 index 0000000000..6116bcc01a --- /dev/null +++ b/data/creaturescripts/lib/creaturescripts.lua @@ -0,0 +1 @@ +-- empty file -- diff --git a/data/creaturescripts/scripts/firstitems.lua b/data/creaturescripts/scripts/firstitems.lua new file mode 100644 index 0000000000..8760e8ca78 --- /dev/null +++ b/data/creaturescripts/scripts/firstitems.lua @@ -0,0 +1,13 @@ +local firstItems = {2050, 2382} + +function onLogin(cid) + if getPlayerLastLoginSaved(cid) == 0 then + for i = 1, table.maxn(firstItems) do + doPlayerAddItem(cid, firstItems[i], 1) + end + doPlayerAddItem(cid, getPlayerSex(cid) == 0 and 2651 or 2650, 1) + local bag = doPlayerAddItem(cid, 1987, 1) + doAddContainerItem(bag, 2674, 1) + end + return true +end diff --git a/data/creaturescripts/scripts/login.lua b/data/creaturescripts/scripts/login.lua new file mode 100644 index 0000000000..4b10a5cfbe --- /dev/null +++ b/data/creaturescripts/scripts/login.lua @@ -0,0 +1,4 @@ +function onLogin(cid) + registerCreatureEvent(cid, "PlayerDeath") + return TRUE +end \ No newline at end of file diff --git a/data/creaturescripts/scripts/playerdeath.lua b/data/creaturescripts/scripts/playerdeath.lua new file mode 100644 index 0000000000..941d4fe1d7 --- /dev/null +++ b/data/creaturescripts/scripts/playerdeath.lua @@ -0,0 +1,58 @@ +dofile("./config.lua") + +function onDeath(cid, corpse, killer) + doPlayerSendTextMessage(cid, MESSAGE_EVENT_ADVANCE, "You are dead.") + if deathListEnabled == "yes" then + local byPlayer = 0 + if killer == 0 then + killerName = "field item" + else + if isPlayer(killer) == TRUE then + byPlayer = 1 + end + killerName = getCreatureName(killer) + end + + db.query("INSERT INTO `player_deaths` (`player_id`, `time`, `level`, `killed_by`, `is_player`) VALUES (" .. getPlayerGUID(cid) .. ", " .. os.time() .. ", " .. getPlayerLevel(cid) .. ", '" .. escapeString(killerName) .. "', " .. byPlayer .. ");") + local resultId = db.storeQuery("SELECT `player_id` FROM `player_deaths` WHERE `player_id` = " .. getPlayerGUID(cid) .. ";") + + local deathRecords = 0 + while tmpResultId ~= false do + tmpResultId = result.next(resultId) + deathRecords = deathRecords + 1 + end + + if resultId ~= false then + if sqlType == "mysql" then + while deathRecords > maxDeathRecords do + db.query("DELETE FROM `player_deaths` WHERE `player_id` = " .. getPlayerGUID(cid) .. " ORDER BY `time` LIMIT 1;") + deathRecords = deathRecords - 1 + end + else + while deathRecords > maxDeathRecords do + db.query("DELETE FROM `player_deaths` WHERE `rowid` = (SELECT `rowid` FROM `player_deaths` WHERE `player_id` = " .. getPlayerGUID(cid) .. " ORDER BY `time` LIMIT 1);") + deathRecords = deathRecords - 1 + end + end + end + + if byPlayer == 1 then + local targetGuild = getPlayerGuildId(cid) + if targetGuild ~= 0 then + local killerGuild = getPlayerGuildId(killer) + if killerGuild ~= 0 and targetGuild ~= killerGuild and isInWar(cid, killer) == TRUE then + local warId = false + resultId = db.storeQuery("SELECT `id` FROM `guild_wars` WHERE `status` = 1 AND ((`guild1` = " .. killerGuild .. " AND `guild2` = " .. targetGuild .. ") OR (`guild1` = " .. targetGuild .. " AND `guild2` = " .. killerGuild .. "));") + if resultId ~= false then + warId = result.getDataInt(resultId, "id") + result.free(resultId) + end + + if warId ~= false then + db.query("INSERT INTO `guildwar_kills` (`killer`, `target`, `killerguild`, `targetguild`, `time`, `warid`) VALUES (" .. db.escapeString(getCreatureName(killer)) .. ", " .. db.escapeString(getCreatureName(cid)) .. ", " .. killerGuild .. ", " .. targetGuild .. ", " .. os.time() .. ", " .. warId .. ");") + end + end + end + end + end +end diff --git a/data/global.lua b/data/global.lua new file mode 100644 index 0000000000..13ab4bac90 --- /dev/null +++ b/data/global.lua @@ -0,0 +1,1432 @@ +TRUE = true +FALSE = false + +LUA_ERROR = false +LUA_NO_ERROR = true + +NORTH = 0 +EAST = 1 +SOUTH = 2 +WEST = 3 +SOUTHWEST = 4 +SOUTHEAST = 5 +NORTHWEST = 6 +NORTHEAST = 7 + +COMBAT_FORMULA_UNDEFINED = 0 +COMBAT_FORMULA_LEVELMAGIC = 1 +COMBAT_FORMULA_SKILL = 2 +COMBAT_FORMULA_DAMAGE = 3 + +CONDITION_PARAM_OWNER = 1 +CONDITION_PARAM_TICKS = 2 +CONDITION_PARAM_OUTFIT = 3 +CONDITION_PARAM_HEALTHGAIN = 4 +CONDITION_PARAM_HEALTHTICKS = 5 +CONDITION_PARAM_MANAGAIN = 6 +CONDITION_PARAM_MANATICKS = 7 +CONDITION_PARAM_DELAYED = 8 +CONDITION_PARAM_SPEED = 9 +CONDITION_PARAM_LIGHT_LEVEL = 10 +CONDITION_PARAM_LIGHT_COLOR = 11 +CONDITION_PARAM_SOULGAIN = 12 +CONDITION_PARAM_SOULTICKS = 13 +CONDITION_PARAM_MINVALUE = 14 +CONDITION_PARAM_MAXVALUE = 15 +CONDITION_PARAM_STARTVALUE = 16 +CONDITION_PARAM_TICKINTERVAL = 17 +CONDITION_PARAM_FORCEUPDATE = 18 +CONDITION_PARAM_SKILL_MELEE = 19 +CONDITION_PARAM_SKILL_FIST = 20 +CONDITION_PARAM_SKILL_CLUB = 21 +CONDITION_PARAM_SKILL_SWORD = 22 +CONDITION_PARAM_SKILL_AXE = 23 +CONDITION_PARAM_SKILL_DISTANCE = 24 +CONDITION_PARAM_SKILL_SHIELD = 25 +CONDITION_PARAM_SKILL_FISHING = 26 +CONDITION_PARAM_STAT_MAXHITPOINTS = 27 +CONDITION_PARAM_STAT_MAXMANAPOINTS = 28 +CONDITION_PARAM_STAT_SOULPOINTS = 29 +CONDITION_PARAM_STAT_MAGICPOINTS = 30 +CONDITION_PARAM_STAT_MAXHITPOINTSPERCENT = 31 +CONDITION_PARAM_STAT_MAXMANAPOINTSPERCENT = 32 +CONDITION_PARAM_STAT_SOULPOINTSPERCENT = 33 +CONDITION_PARAM_STAT_MAGICPOINTSPERCENT = 34 +CONDITION_PARAM_PERIODICDAMAGE = 35 +CONDITION_PARAM_SKILL_MELEEPERCENT = 36 +CONDITION_PARAM_SKILL_FISTPERCENT = 37 +CONDITION_PARAM_SKILL_CLUBPERCENT = 38 +CONDITION_PARAM_SKILL_SWORDPERCENT = 39 +CONDITION_PARAM_SKILL_AXEPERCENT = 40 +CONDITION_PARAM_SKILL_DISTANCEPERCENT = 41 +CONDITION_PARAM_SKILL_SHIELDPERCENT = 42 +CONDITION_PARAM_SKILL_FISHINGPERCENT = 43 +CONDITION_PARAM_BUFF_SPELL = 44 +CONDITION_PARAM_SUBID = 45 + +COMBAT_PARAM_TYPE = 1 +COMBAT_PARAM_EFFECT = 2 +COMBAT_PARAM_DISTANCEEFFECT = 3 +COMBAT_PARAM_BLOCKSHIELD = 4 +COMBAT_PARAM_BLOCKARMOR = 5 +COMBAT_PARAM_TARGETCASTERORTOPMOST = 6 +COMBAT_PARAM_CREATEITEM = 7 +COMBAT_PARAM_AGGRESSIVE = 8 +COMBAT_PARAM_DISPEL = 9 + +CALLBACK_PARAM_LEVELMAGICVALUE = 1 +CALLBACK_PARAM_SKILLVALUE = 2 +CALLBACK_PARAM_TARGETTILE = 3 +CALLBACK_PARAM_TARGETCREATURE = 4 + +COMBAT_NONE = 0 +COMBAT_PHYSICALDAMAGE = 1 +COMBAT_ENERGYDAMAGE = 2 +COMBAT_EARTHDAMAGE = 4 +COMBAT_POISONDAMAGE = 4 +COMBAT_FIREDAMAGE = 8 +COMBAT_UNDEFINEDDAMAGE = 16 +COMBAT_LIFEDRAIN = 32 +COMBAT_MANADRAIN = 64 +COMBAT_HEALING = 128 +COMBAT_DROWNDAMAGE = 256 +COMBAT_ICEDAMAGE = 512 +COMBAT_HOLYDAMAGE = 1024 +COMBAT_DEATHDAMAGE = 2048 + +CONDITION_NONE = 0 +CONDITION_POISON = 1 +CONDITION_FIRE = 2 +CONDITION_ENERGY = 4 +CONDITION_BLEEDING = 8 +CONDITION_HASTE = 16 +CONDITION_PARALYZE = 32 +CONDITION_OUTFIT = 64 +CONDITION_INVISIBLE = 128 +CONDITION_LIGHT = 256 +CONDITION_MANASHIELD = 512 +CONDITION_INFIGHT = 1024 +CONDITION_DRUNK = 2048 +CONDITION_EXHAUST_WEAPON = 4096 +CONDITION_FOOD = 8192 +CONDITION_REGENERATION = 8192 +CONDITION_SOUL = 16384 +CONDITION_DROWN = 32768 +CONDITION_MUTED = 65536 +CONDITION_ADVERTISINGTICKS = 131072 +CONDITION_TRADETICKS = CONDITION_ADVERTISINGTICKS +CONDITION_YELLTICKS = 262144 +CONDITION_ATTRIBUTES = 524288 +CONDITION_FREEZING = 1048576 +CONDITION_DAZZLED = 2097152 +CONDITION_CURSED = 4194304 +CONDITION_EXHAUST_COMBAT = 8388608 +CONDITION_EXHAUST_HEAL = 16777216 +CONDITION_PACIFIED = 33554432 + +CONST_SLOT_HEAD = 1 +CONST_SLOT_NECKLACE = 2 +CONST_SLOT_BACKPACK = 3 +CONST_SLOT_ARMOR = 4 +CONST_SLOT_RIGHT = 5 +CONST_SLOT_LEFT = 6 +CONST_SLOT_LEGS = 7 +CONST_SLOT_FEET = 8 +CONST_SLOT_RING = 9 +CONST_SLOT_AMMO = 10 + +CONST_ME_DRAWBLOOD = 0 +CONST_ME_LOSEENERGY = 1 +CONST_ME_POFF = 2 +CONST_ME_BLOCKHIT = 3 +CONST_ME_EXPLOSIONAREA = 4 +CONST_ME_EXPLOSIONHIT = 5 +CONST_ME_FIREAREA = 6 +CONST_ME_YELLOW_RINGS = 7 +CONST_ME_GREEN_RINGS = 8 +CONST_ME_HITAREA = 9 +CONST_ME_TELEPORT = 10 +CONST_ME_ENERGYHIT = 11 +CONST_ME_MAGIC_BLUE = 12 +CONST_ME_MAGIC_RED = 13 +CONST_ME_MAGIC_GREEN = 14 +CONST_ME_HITBYFIRE = 15 +CONST_ME_HITBYPOISON = 16 +CONST_ME_MORTAREA = 17 +CONST_ME_SOUND_GREEN = 18 +CONST_ME_SOUND_RED = 19 +CONST_ME_POISONAREA = 20 +CONST_ME_SOUND_YELLOW = 21 +CONST_ME_SOUND_PURPLE = 22 +CONST_ME_SOUND_BLUE = 23 +CONST_ME_SOUND_WHITE = 24 +CONST_ME_BUBBLES = 25 +CONST_ME_CRAPS = 26 +CONST_ME_GIFT_WRAPS = 27 +CONST_ME_FIREWORK_YELLOW = 28 +CONST_ME_FIREWORK_RED = 29 +CONST_ME_FIREWORK_BLUE = 30 +CONST_ME_STUN = 31 +CONST_ME_SLEEP = 32 +CONST_ME_WATERCREATURE = 33 +CONST_ME_GROUNDSHAKER = 34 +CONST_ME_HEARTS = 35 +CONST_ME_FIREATTACK = 36 +CONST_ME_ENERGYAREA = 37 +CONST_ME_SMALLCLOUDS = 38 +CONST_ME_HOLYDAMAGE = 39 +CONST_ME_BIGCLOUDS = 40 +CONST_ME_ICEAREA = 41 +CONST_ME_ICETORNADO = 42 +CONST_ME_ICEATTACK = 43 +CONST_ME_STONES = 44 +CONST_ME_SMALLPLANTS = 45 +CONST_ME_CARNIPHILA = 46 +CONST_ME_PURPLEENERGY = 47 +CONST_ME_YELLOWENERGY = 48 +CONST_ME_HOLYAREA = 49 +CONST_ME_BIGPLANTS = 50 +CONST_ME_CAKE = 51 +CONST_ME_GIANTICE = 52 +CONST_ME_WATERSPLASH = 53 +CONST_ME_PLANTATTACK = 54 +CONST_ME_TUTORIALARROW = 55 +CONST_ME_TUTORIALSQUARE = 56 +CONST_ME_MIRRORHORIZONTAL = 57 +CONST_ME_MIRRORVERTICAL = 58 +CONST_ME_SKULLHORIZONTAL = 59 +CONST_ME_SKULLVERTICAL = 60 +CONST_ME_ASSASSIN = 61 +CONST_ME_STEPSHORIZONTAL = 62 +CONST_ME_BLOODYSTEPS = 63 +CONST_ME_STEPSVERTICAL = 64 +CONST_ME_YALAHARIGHOST = 65 +CONST_ME_BATS = 66 +CONST_ME_SMOKE = 67 +CONST_ME_INSECTS = 68 +CONST_ME_DRAGONHEAD = 69 +CONST_ME_ORCSHAMAN = 70 +CONST_ME_ORCSHAMAN_FIRE = 71 +CONST_ME_THUNDER = 72 +CONST_ME_FERUMBRAS = 73 +CONST_ME_CONFETTI_HORIZONTAL = 74 +CONST_ME_CONFETTI_VERTICAL = 75 +CONST_ME_NONE = 255 + +CONST_ANI_SPEAR = 0 +CONST_ANI_BOLT = 1 +CONST_ANI_ARROW = 2 +CONST_ANI_FIRE = 3 +CONST_ANI_ENERGY = 4 +CONST_ANI_POISONARROW = 5 +CONST_ANI_BURSTARROW = 6 +CONST_ANI_THROWINGSTAR = 7 +CONST_ANI_THROWINGKNIFE = 8 +CONST_ANI_SMALLSTONE = 9 +CONST_ANI_DEATH = 10 +CONST_ANI_LARGEROCK = 11 +CONST_ANI_SNOWBALL = 12 +CONST_ANI_POWERBOLT = 13 +CONST_ANI_POISON = 14 +CONST_ANI_INFERNALBOLT = 15 +CONST_ANI_HUNTINGSPEAR = 16 +CONST_ANI_ENCHANTEDSPEAR = 17 +CONST_ANI_REDSTAR = 18 +CONST_ANI_GREENSTAR = 19 +CONST_ANI_ROYALSPEAR = 20 +CONST_ANI_SNIPERARROW = 21 +CONST_ANI_ONYXARROW = 22 +CONST_ANI_PIERCINGBOLT = 23 +CONST_ANI_WHIRLWINDSWORD = 24 +CONST_ANI_WHIRLWINDAXE = 25 +CONST_ANI_WHIRLWINDCLUB = 26 +CONST_ANI_ETHEREALSPEAR = 27 +CONST_ANI_ICE = 28 +CONST_ANI_EARTH = 29 +CONST_ANI_HOLY = 30 +CONST_ANI_SUDDENDEATH = 31 +CONST_ANI_FLASHARROW = 32 +CONST_ANI_FLAMMINGARROW = 33 +CONST_ANI_SHIVERARROW = 34 +CONST_ANI_ENERGYBALL = 35 +CONST_ANI_SMALLICE = 36 +CONST_ANI_SMALLHOLY = 37 +CONST_ANI_SMALLEARTH = 38 +CONST_ANI_EARTHARROW = 39 +CONST_ANI_EXPLOSION = 40 +CONST_ANI_CAKE = 41 +CONST_ANI_TARSALARROW = 43 +CONST_ANI_VORTEXBOLT = 44 +CONST_ANI_PRISMATICBOLT = 47 +CONST_ANI_CRYSTALLINEARROW = 48 +CONST_ANI_DRILLBOLT = 49 +CONST_ANI_ENVENOMEDARROW = 50 +CONST_ANI_WEAPONTYPE = 254 +CONST_ANI_NONE = 255 +CONST_ANI_LAST = CONST_ANI_ENVENOMEDARROW + +TALKTYPE_SAY = 1 +TALKTYPE_WHISPER = 2 +TALKTYPE_YELL = 3 +TALKTYPE_PRIVATE_FROM = 4 +TALKTYPE_PRIVATE_TO = 5 +TALKTYPE_CHANNEL_Y = 7 +TALKTYPE_CHANNEL_O = 8 +TALKTYPE_PRIVATE_NP = 10 +TALKTYPE_PRIVATE_PN = 11 +TALKTYPE_BROADCAST = 12 +TALKTYPE_CHANNEL_R1 = 13 +TALKTYPE_PRIVATE_RED_FROM = 14 +TALKTYPE_PRIVATE_RED_TO = 15 +TALKTYPE_ORANGE_1 = 34 +TALKTYPE_ORANGE_2 = 35 + +TALKTYPE_PRIVATE = TALKTYPE_PRIVATE_TO +TALKTYPE_PRIVATE_RED = TALKTYPE_PRIVATE_RED_TO + +-- for internal use +TALKTYPE_RVR_CHANNEL = 256 +TALKTYPE_RVR_ANSWER = 257 +TALKTYPE_RVR_CONTINUE = 258 +TALKTYPE_CHANNEL_R2 = 259 +TALKTYPE_CHANNEL_W = 260 +-- + +MESSAGE_STATUS_CONSOLE_BLUE = 4 +MESSAGE_STATUS_CONSOLE_RED = 12 +MESSAGE_STATUS_DEFAULT = 16 +MESSAGE_STATUS_WARNING = 17 +MESSAGE_EVENT_ADVANCE = 18 +MESSAGE_STATUS_SMALL = 19 +MESSAGE_INFO_DESCR = 20 +MESSAGE_DAMAGE_DEALT = 21 +MESSAGE_DAMAGE_RECEIVED = 22 +MESSAGE_HEALED = 23 +MESSAGE_EXPERIENCE = 24 +MESSAGE_DAMAGE_OTHERS = 25 +MESSAGE_HEALED_OTHERS = 26 +MESSAGE_EXPERIENCE_OTHERS = 27 +MESSAGE_EVENT_DEFAULT = 28 +MESSAGE_EVENT_ORANGE = 34 +MESSAGE_STATUS_CONSOLE_ORANGE = 35 + +TEXTCOLOR_BLUE = 5 +TEXTCOLOR_LIGHTGREEN = 30 +TEXTCOLOR_LIGHTBLUE = 35 +TEXTCOLOR_GREEN = 54 +TEXTCOLOR_TEAL = 65 +TEXTCOLOR_PLATINUMBLUE = 89 +TEXTCOLOR_MAYABLUE = 95 +TEXTCOLOR_LIGHTGREY = 129 +TEXTCOLOR_SKYBLUE = 143 +TEXTCOLOR_DARKRED = 144 +TEXTCOLOR_PURPLE = 154 +TEXTCOLOR_RED = 180 +TEXTCOLOR_ORANGE = 198 +TEXTCOLOR_YELLOW = 210 +TEXTCOLOR_WHITE_EXP = 215 +TEXTCOLOR_NONE = 255 + +MAPMARK_TICK = 0 +MAPMARK_QUESTION = 1 +MAPMARK_EXCLAMATION = 2 +MAPMARK_STAR = 3 +MAPMARK_CROSS = 4 +MAPMARK_TEMPLE = 5 +MAPMARK_KISS = 6 +MAPMARK_SHOVEL = 7 +MAPMARK_SWORD = 8 +MAPMARK_FLAG = 9 +MAPMARK_LOCK = 10 +MAPMARK_BAG = 11 +MAPMARK_SKULL = 12 +MAPMARK_DOLLAR = 13 +MAPMARK_REDNORTH = 14 +MAPMARK_REDSOUTH = 15 +MAPMARK_REDEAST = 16 +MAPMARK_REDWEST = 17 +MAPMARK_GREENNORTH = 18 +MAPMARK_GREENSOUTH = 19 + +ITEM_TYPE_DEPOT = 1 +ITEM_TYPE_MAILBOX = 2 +ITEM_TYPE_TRASHHOLDER = 3 +ITEM_TYPE_CONTAINER = 4 +ITEM_TYPE_DOOR = 5 +ITEM_TYPE_MAGICFIELD = 6 +ITEM_TYPE_TELEPORT = 7 +ITEM_TYPE_BED = 8 +ITEM_TYPE_KEY = 9 +ITEM_TYPE_RUNE = 10 + +CONST_PROP_BLOCKSOLID = 0 +CONST_PROP_HASHEIGHT = 1 +CONST_PROP_BLOCKPROJECTILE = 2 +CONST_PROP_BLOCKPATHFIND = 3 +CONST_PROP_ISVERTICAL = 4 +CONST_PROP_ISHORIZONTAL = 5 +CONST_PROP_MOVEABLE = 6 +CONST_PROP_BLOCKINGANDNOTMOVEABLE = 7 +CONST_PROP_SUPPORTHANGABLE = 8 + +SKILL_FIST = 0 +SKILL_CLUB = 1 +SKILL_SWORD = 2 +SKILL_AXE = 3 +SKILL_DISTANCE = 4 +SKILL_SHIELD = 5 +SKILL_FISHING = 6 +SKILL_MAGLEVEL = 7 +SKILL_LEVEL = 8 + +GUILDLEVEL_MEMBER = 1 +GUILDLEVEL_VICE = 2 +GUILDLEVEL_LEADER = 3 + +SKULL_NONE = 0 +SKULL_YELLOW = 1 +SKULL_GREEN = 2 +SKULL_WHITE = 3 +SKULL_RED = 4 +SKULL_BLACK = 5 +SKULL_ORANGE = 5 + +WORLD_TYPE_NO_PVP = 1 +WORLD_TYPE_PVP = 2 +WORLD_TYPE_PVP_ENFORCED = 3 + +PLAYERSEX_FEMALE = 0 +PLAYERSEX_MALE = 1 + +STACKPOS_GROUND = 0 +STACKPOS_FIRST_ITEM_ABOVE_GROUNDTILE = 1 +STACKPOS_SECOND_ITEM_ABOVE_GROUNDTILE = 2 +STACKPOS_THIRD_ITEM_ABOVE_GROUNDTILE = 3 +STACKPOS_FOURTH_ITEM_ABOVE_GROUNDTILE = 4 +STACKPOS_FIFTH_ITEM_ABOVE_GROUNDTILE = 5 +STACKPOS_TOP_CREATURE = 253 +STACKPOS_TOP_FIELD = 254 +STACKPOS_TOP_MOVEABLE_ITEM_OR_CREATURE = 255 + +RETURNVALUE_NOERROR = 1 +RETURNVALUE_NOTPOSSIBLE = 2 +RETURNVALUE_NOTENOUGHROOM = 3 +RETURNVALUE_PLAYERISPZLOCKED = 4 +RETURNVALUE_PLAYERISNOTINVITED = 5 +RETURNVALUE_CANNOTTHROW = 6 +RETURNVALUE_THEREISNOWAY = 7 +RETURNVALUE_DESTINATIONOUTOFREACH = 8 +RETURNVALUE_CREATUREBLOCK = 9 +RETURNVALUE_NOTMOVEABLE = 10 +RETURNVALUE_DROPTWOHANDEDITEM = 11 +RETURNVALUE_BOTHHANDSNEEDTOBEFREE = 12 +RETURNVALUE_CANONLYUSEONEWEAPON = 13 +RETURNVALUE_NEEDEXCHANGE = 14 +RETURNVALUE_CANNOTBEDRESSED = 15 +RETURNVALUE_PUTTHISOBJECTINYOURHAND = 16 +RETURNVALUE_PUTTHISOBJECTINBOTHHANDS = 17 +RETURNVALUE_TOOFARAWAY = 18 +RETURNVALUE_FIRSTGODOWNSTAIRS = 19 +RETURNVALUE_FIRSTGOUPSTAIRS = 20 +RETURNVALUE_CONTAINERNOTENOUGHROOM = 21 +RETURNVALUE_NOTENOUGHCAPACITY = 22 +RETURNVALUE_CANNOTPICKUP = 23 +RETURNVALUE_THISISIMPOSSIBLE = 24 +RETURNVALUE_DEPOTISFULL = 25 +RETURNVALUE_CREATUREDOESNOTEXIST = 26 +RETURNVALUE_CANNOTUSETHISOBJECT = 27 +RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE = 28 +RETURNVALUE_NOTREQUIREDLEVELTOUSERUNE = 29 +RETURNVALUE_YOUAREALREADYTRADING = 30 +RETURNVALUE_THISPLAYERISALREADYTRADING = 31 +RETURNVALUE_YOUMAYNOTLOGOUTDURINGAFIGHT = 32 +RETURNVALUE_DIRECTPLAYERSHOOT = 33 +RETURNVALUE_NOTENOUGHLEVEL = 34 +RETURNVALUE_NOTENOUGHMAGICLEVEL = 35 +RETURNVALUE_NOTENOUGHMANA = 36 +RETURNVALUE_NOTENOUGHSOUL = 37 +RETURNVALUE_YOUAREEXHAUSTED = 38 +RETURNVALUE_PLAYERISNOTREACHABLE = 39 +RETURNVALUE_CANONLYUSETHISRUNEONCREATURES = 40 +RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE = 41 +RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER = 42 +RETURNVALUE_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE = 43 +RETURNVALUE_YOUMAYNOTATTACKAPERSONWHILEINPROTECTIONZONE = 44 +RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE = 45 +RETURNVALUE_YOUCANONLYUSEITONCREATURES = 46 +RETURNVALUE_CREATUREISNOTREACHABLE = 47 +RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS = 48 +RETURNVALUE_YOUNEEDPREMIUMACCOUNT = 49 +RETURNVALUE_YOUNEEDTOLEARNTHISSPELL = 50 +RETURNVALUE_YOURVOCATIONCANNOTUSETHISSPELL = 51 +RETURNVALUE_YOUNEEDAWEAPONTOUSETHISSPELL = 52 +RETURNVALUE_PLAYERISPZLOCKEDLEAVEPVPZONE = 53 +RETURNVALUE_PLAYERISPZLOCKEDENTERPVPZONE = 54 +RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE = 55 +RETURNVALUE_YOUCANNOTLOGOUTHERE = 56 +RETURNVALUE_YOUNEEDAMAGICITEMTOCASTSPELL = 57 +RETURNVALUE_CANNOTCONJUREITEMHERE = 58 +RETURNVALUE_YOUNEEDTOSPLITYOURSPEARS = 59 +RETURNVALUE_NAMEISTOOAMBIGIOUS = 60 +RETURNVALUE_CANUSEONLYONESHIELD = 61 +RETURNVALUE_NOPARTYMEMBERSINRANGE = 62 +RETURNVALUE_YOUARENOTTHEOWNER = 63 + +PlayerFlag_CannotUseCombat = 0 +PlayerFlag_CannotAttackPlayer = 1 +PlayerFlag_CannotAttackMonster = 2 +PlayerFlag_CannotBeAttacked = 3 +PlayerFlag_CanConvinceAll = 4 +PlayerFlag_CanSummonAll = 5 +PlayerFlag_CanIllusionAll = 6 +PlayerFlag_CanSenseInvisibility = 7 +PlayerFlag_IgnoredByMonsters = 8 +PlayerFlag_NotGainInFight = 9 +PlayerFlag_HasInfiniteMana = 10 +PlayerFlag_HasInfiniteSoul = 11 +PlayerFlag_HasNoExhaustion = 12 +PlayerFlag_CannotUseSpells = 13 +PlayerFlag_CannotPickupItem = 14 +PlayerFlag_CanAlwaysLogin = 15 +PlayerFlag_CanBroadcast = 16 +PlayerFlag_CanEditHouses = 17 +PlayerFlag_CannotBeBanned = 18 +PlayerFlag_CannotBePushed = 19 +PlayerFlag_HasInfiniteCapacity = 20 +PlayerFlag_CanPushAllCreatures = 21 +PlayerFlag_CanTalkRedPrivate = 22 +PlayerFlag_CanTalkRedChannel = 23 +PlayerFlag_TalkOrangeHelpChannel = 24 +PlayerFlag_NotGainExperience = 25 +PlayerFlag_NotGainMana = 26 +PlayerFlag_NotGainHealth = 27 +PlayerFlag_NotGainSkill = 28 +PlayerFlag_SetMaxSpeed = 29 +PlayerFlag_SpecialVIP = 30 +PlayerFlag_NotGenerateLoot = 31 +PlayerFlag_CanTalkRedChannelAnonymous = 32 +PlayerFlag_IgnoreProtectionZone = 33 +PlayerFlag_IgnoreSpellCheck = 34 +PlayerFlag_IgnoreWeaponCheck = 35 +PlayerFlag_CannotBeMuted = 36 +PlayerFlag_IsAlwaysPremium = 37 +PlayerFlag_CanAnswerRuleViolations = 38 + +maleOutfits = {128, 129, 130, 131, 132, 133, 134, 143, 144, 145, 146, 151, 152, 153, 154, 251, 268, 273, 278, 289, 325, 328, 335, 367, 430, 432, 463, 465, 472, 512, 516} +femaleOutfits = {136, 137, 138, 139, 140, 141, 142, 147, 148, 149, 150, 155, 156, 157, 158, 252, 269, 270, 279, 288, 324, 329, 336, 366, 431, 433, 464, 466, 471, 513, 514} + +doors = {[1209] = 1211, [1210] = 1211, [1212] = 1214, [1213] = 1214, [1219] = 1220, [1221] = 1222, [1231] = 1233, [1232] = 1233, [1234] = 1236, [1235] = 1236, [1237] = 1238, [1239] = 1240, [1249] = 1251, [1250] = 1251, [1252] = 1254, [1253] = 1254, [1539] = 1540, [1541] = 1542, [3535] = 3537, [3536] = 3537, [3538] = 3539, [3544] = 3546, [3545] = 3546, [3547] = 3548, [4913] = 4915, [4914] = 4915, [4916] = 4918, [4917] = 4918, [5082] = 5083, [5084] = 5085, [5098] = 5100, [5099] = 5100, [5101] = 5102, [5107] = 5109, [5108] = 5109, [5110] = 5111, [5116] = 5118, [5117] = 5118, [5119] = 5120, [5125] = 5127, [5126] = 5127, [5128] = 5129, [5134] = 5136, [5135] = 5136, [5137] = 5139, [5138] = 5139, [5140] = 5142, [5141] = 5142, [5143] = 5145, [5144] = 5145, [5278] = 5280, [5279] = 5280, [5281] = 5283, [5282] = 5283, [5284] = 5285, [5286] = 5287, [5515] = 5516, [5517] = 5518, [5732] = 5734, [5733] = 5734, [5735] = 5737, [5736] = 5737, [6192] = 6194, [6193] = 6194, [6195] = 6197, [6196] = 6197, [6198] = 6199, [6200] = 6201, [6249] = 6251, [6250] = 6251, [6252] = 6254, [6253] = 6254, [6255] = 6256, [6257] = 6258, [6795] = 6796, [6797] = 6798, [6799] = 6800, [6801] = 6802, [6891] = 6893, [6892] = 6893, [6894] = 6895, [6900] = 6902, [6901] = 6902, [6903] = 6904, [7033] = 7035, [7034] = 7035, [7036] = 7037, [7042] = 7044, [7043] = 7044, [7045] = 7046, [7054] = 7055, [7056] = 7057, [8541] = 8543, [8542] = 8543, [8544] = 8546, [8545] = 8546, [8547] = 8548, [8549] = 8550, [9165] = 9167, [9166] = 9167, [9168] = 9170, [9169] = 9170, [9171] = 9172, [9173] = 9174, [9267] = 9269, [9268] = 9269, [9270] = 9272, [9271] = 9272, [9273] = 9274, [9275] = 9276, [10276] = 10277, [10274] = 10275, [10268] = 10270, [10269] = 10270, [10271] = 10273, [10272] = 10273, [10471] = 10472, [10480] = 10481, [10477] = 10479, [10478] = 10479, [10468] = 10470, [10469] = 10470} +verticalOpenDoors = {1211, 1220, 1224, 1228, 1233, 1238, 1242, 1246, 1251, 1256, 1260, 1540, 3546, 3548, 3550, 3552, 4915, 5083, 5109, 5111, 5113, 5115, 5127, 5129, 5131, 5133, 5142, 5145, 5283, 5285, 5289, 5293, 5516, 5737, 5749, 6194, 6199, 6203, 6207, 6251, 6256, 6260, 6264, 6798, 6802, 6902, 6904, 6906, 6908, 7044, 7046, 7048, 7050, 7055, 8543, 8548, 8552, 8556, 9167, 9172, 9269, 9274, 9274, 9269, 9278, 9282, 10270, 10275, 10279, 10283, 10479, 10481, 10485, 10483} +horizontalOpenDoors = {1214, 1222, 1226, 1230, 1236, 1240, 1244, 1248, 1254, 1258, 1262, 1542, 3537, 3539, 3541, 3543, 4918, 5085, 5100, 5102, 5104, 5106, 5118, 5120, 5122, 5124, 5136, 5139, 5280, 5287, 5291, 5295, 5518, 5734, 5746, 6197, 6201, 6205, 6209, 6254, 6258, 6262, 6266, 6796, 6800, 6893, 6895, 6897, 6899, 7035, 7037, 7039, 7041, 7057, 8546, 8550, 8554, 8558, 9170, 9174, 9272, 9276, 9280, 9284, 10273, 10277, 10281, 10285, 10470, 10472, 10476, 10474} +openSpecialDoors = {1224, 1226, 1228, 1230, 1242, 1244, 1246, 1248, 1256, 1258, 1260, 1262, 3541, 3543, 3550, 3552, 5104, 5106, 5113, 5115, 5122, 5124, 5131, 5133, 5289, 5291, 5293, 5295, 6203, 6205, 6207, 6209, 6260, 6262, 6264, 6266, 6897, 6899, 6906, 6908, 7039, 7041, 7048, 7050, 8552, 8554, 8556, 8558, 9176, 9178, 9180, 9182, 9278, 9280, 9282, 9284, 10279, 10281, 10283, 10285, 10474, 10476, 10483, 10485} +questDoors = {1223, 1225, 1241, 1243, 1255, 1257, 3542, 3551, 5105, 5114, 5123, 5132, 5288, 5290, 5745, 5748, 6202, 6204, 6259, 6261, 6898, 6907, 7040, 7049, 8551, 8553, 9175, 9177, 9277, 9279, 10278, 10280, 10475, 10484} +levelDoors = {1227, 1229, 1245, 1247, 1259, 1261, 3540, 3549, 5103, 5112, 5121, 5130, 5292, 5294, 6206, 6208, 6263, 6265, 6896, 6905, 7038, 7047, 8555, 8557, 9179, 9181, 9281, 9283, 10282, 10284, 10473, 10482, 10780, 10789} +keys = {2086, 2087, 2088, 2089, 2090, 2091, 2092, 10032} + +BLUEBERRYBUSH_DECAY_INTERVAL = 300000 +CONTAINER_POSITION = 0xFFFF +ITEMCOUNT_MAX = 100 + +ITEM_GOLD_COIN = 2148 +ITEM_PLATINUM_COIN = 2152 +ITEM_CRYSTAL_COIN = 2160 +ITEM_FISH = 2667 +ITEM_WORM = 3976 +ITEM_BLUEBERRY = 2677 +ITEM_BLUEBERRYBUSH = 2785 +ITEM_BUSH = 2786 +ITEM_PARCEL = 2595 +ITEM_LABEL = 2599 + +ITEM_FIREFIELD_PVP_FULL = 1487 +ITEM_FIREFIELD_PVP_MEDIUM = 1488 +ITEM_FIREFIELD_PVP_SMALL = 1489 +ITEM_FIREFIELD_PERSISTENT_FULL = 1492 +ITEM_FIREFIELD_PERSISTENT_MEDIUM = 1493 +ITEM_FIREFIELD_PERSISTENT_SMALL = 1494 +ITEM_FIREFIELD_NOPVP = 1500 + +ITEM_POISONFIELD_PVP = 1490 +ITEM_POISONFIELD_PERSISTENT = 1496 +ITEM_POISONFIELD_NOPVP = 1503 + +ITEM_ENERGYFIELD_PVP = 1491 +ITEM_ENERGYFIELD_PERSISTENT = 1495 +ITEM_ENERGYFIELD_NOPVP = 1504 + +ITEM_MAGICWALL = 1497 +ITEM_MAGICWALL_PERSISTENT = 1498 +ITEM_MAGICWALL_SAFE = 11098 + +ITEM_WILDGROWTH = 1499 +ITEM_WILDGROWTH_PERSISTENT = 2721 +ITEM_WILDGROWTH_SAFE = 11099 + +function doPlayerGiveItem(cid, itemid, count, charges) + local isFluidContainer = isItemFluidContainer(itemid) == TRUE + if isFluidContainer and charges == nil then + charges = 1 + end + while count > 0 do + local tempcount = 1 + if(isItemStackable(itemid) == TRUE) then + tempcount = math.min(100, count) + end + local ret = doPlayerAddItem(cid, itemid, tempcount, TRUE, charges) + if ret == false then + ret = doCreateItem(itemid, tempcount, getPlayerPosition(cid)) + end + + if(ret) then + if(isFluidContainer) then + count = count - 1 + elseif isItemRune(itemid) then + return LUA_NO_ERROR + else + count = count - tempcount + end + else + return LUA_ERROR + end + end + return LUA_NO_ERROR +end + +function doCreatureSayWithRadius(cid, text, type, radiusx, radiusy, position) + for i, tid in ipairs(getSpectators(position or getCreaturePosition(cid), radiusx, radiusy, false)) do + if(isPlayer(tid) == TRUE) then + doCreatureSay(cid, text, type, false, tid, position or getCreaturePosition(cid)) + end + end + return TRUE +end + +function doSummonCreatures(monsters, positions) + for _, positions in pairs(positions) do + doSummonCreature(monsters, positions) + end +end + +function setPlayerMultipleStorageValues(cid, storage, value) + for _, storage in pairs(storage) do + setPlayerStorageValue(cid, storage, value) + end +end + +function doPlayerTakeItem(cid, itemid, count) + if(getPlayerItemCount(cid,itemid) >= count) then + while count > 0 do + local tempcount = 0 + if(isItemStackable(itemid) == TRUE) then + tempcount = math.min (100, count) + else + tempcount = 1 + end + local ret = doPlayerRemoveItem(cid, itemid, tempcount) + if(ret ~= LUA_ERROR) then + count = count-tempcount + else + return LUA_ERROR + end + end + if(count == 0) then + return LUA_NO_ERROR + end + else + return LUA_ERROR + end +end + +function doPlayerBuyItem(cid, itemid, count, cost, charges) + if(doPlayerRemoveMoney(cid, cost) == TRUE) then + return doPlayerGiveItem(cid, itemid, count, charges) + end + return LUA_ERROR +end + +function doPlayerSellItem(cid, itemid, count, cost) + if(doPlayerTakeItem(cid, itemid, count) == LUA_NO_ERROR) then + if doPlayerAddMoney(cid, cost) ~= TRUE then + error('Could not add money to ' .. getPlayerName(cid) .. '(' .. cost .. 'gp)') + end + return LUA_NO_ERROR + end + return LUA_ERROR +end + +function isInRange(pos, fromPos, toPos) + if pos.x >= fromPos.x and pos.y >= fromPos.y and pos.z >= fromPos.z and pos.x <= toPos.x and pos.y <= toPos.y and pos.z <= toPos.z then + return TRUE + end + return FALSE +end + +function isPremium(cid) + return (isPlayer(cid) == TRUE and (getPlayerPremiumDays(cid) > 0 or getConfigInfo('freePremium') == "yes")) and true or false +end + +function rows(result) + return function() + print("Deprecated function") + return nil + end +end + +function getMonthDayEnding(day) + if day == "01" or day == "21" or day == "31" then + return "st" + elseif day == "02" or day == "22" then + return "nd" + elseif day == "03" or day == "23" then + return "rd" + else + return "th" + end +end + +function getMonthString(m) + return os.date("%B", os.time{year = 1970, month = m, day = 1}) +end + +function getArticle(str) + return str:find("[AaEeIiOoUuYy]") == 1 and "an" or "a" +end + +function isNumber(str) + return tonumber(str) ~= nil and TRUE or FALSE +end + +function getDistanceBetween(firstPosition, secondPosition) + local xDif = math.abs(firstPosition.x - secondPosition.x) + local yDif = math.abs(firstPosition.y - secondPosition.y) + + local posDif = math.max(xDif, yDif) + if(firstPosition.z ~= secondPosition.z) then + posDif = posDif + 9 + 6 + end + return posDif +end + +function doPlayerAddAddons(cid, addon) + for i = 0, #maleOutfits do + doPlayerAddOutfit(cid, maleOutfits[i], addon) + end + + for i = 0, #femaleOutfits do + doPlayerAddOutfit(cid, femaleOutfits[i], addon) + end +end + +function numRows(result) + local rows = 0 + while result:fetch() do + rows = rows + 1 + end + result:close() + return rows +end + +function isSorcerer(cid) + if(isPlayer(cid) == FALSE) then + debugPrint("isSorcerer: Player not found.") + return false + end + + return (isInArray({1,5}, getPlayerVocation(cid)) == TRUE) +end + +function isDruid(cid) + if(isPlayer(cid) == FALSE) then + debugPrint("isDruid: Player not found.") + return false + end + + return (isInArray({2,6}, getPlayerVocation(cid)) == TRUE) +end + +function isPaladin(cid) + if(isPlayer(cid) == FALSE) then + debugPrint("isPaladin: Player not found.") + return false + end + + return (isInArray({3,7}, getPlayerVocation(cid)) == TRUE) +end + +function isKnight(cid) + if(isPlayer(cid) == FALSE) then + debugPrint("isKnight: Player not found.") + return false + end + + return (isInArray({4,8}, getPlayerVocation(cid)) == TRUE) +end + +function getConfigInfo(info) + if (type(info) ~= 'string') then return nil end + + dofile('config.lua') + return _G[info] +end + +function doPlayerBuyItemContainer(cid, containerid, itemid, count, cost, charges) + if(doPlayerRemoveMoney(cid, cost) ~= TRUE) then + return LUA_ERROR + end + + for i = 1, count do + local container = doCreateItemEx(containerid, 1) + for x = 1, getContainerCapById(containerid) do + doAddContainerItem(container, itemid, charges) + end + + if(doPlayerAddItemEx(cid, container, TRUE) ~= RETURNVALUE_NOERROR) then + return LUA_ERROR + end + end + + return LUA_NO_ERROR +end + +function getDirectionTo(pos1, pos2) + local dir = NORTH + if(pos1.x > pos2.x) then + dir = WEST + if(pos1.y > pos2.y) then + dir = NORTHWEST + elseif(pos1.y < pos2.y) then + dir = SOUTHWEST + end + elseif(pos1.x < pos2.x) then + dir = EAST + if(pos1.y > pos2.y) then + dir = NORTHEAST + elseif(pos1.y < pos2.y) then + dir = SOUTHEAST + end + else + if(pos1.y > pos2.y) then + dir = NORTH + elseif(pos1.y < pos2.y) then + dir = SOUTH + end + end + return dir +end + +function getPlayerLookPos(cid) + return getPosByDir(getThingPos(cid), getPlayerLookDir(cid)) +end + +function getPosByDir(fromPosition, direction, size) + local n = size or 1 + + local pos = fromPosition + if(direction == NORTH) then + pos.y = pos.y - n + elseif(direction == SOUTH) then + pos.y = pos.y + n + elseif(direction == WEST) then + pos.x = pos.x - n + elseif(direction == EAST) then + pos.x = pos.x + n + elseif(direction == NORTHWEST) then + pos.y = pos.y - n + pos.x = pos.x - n + elseif(direction == NORTHEAST) then + pos.y = pos.y - n + pos.x = pos.x + n + elseif(direction == SOUTHWEST) then + pos.y = pos.y + n + pos.x = pos.x - n + elseif(direction == SOUTHEAST) then + pos.y = pos.y + n + pos.x = pos.x + n + end + + return pos +end + +function getCreaturesInRange(position, radiusx, radiusy, showMonsters, showPlayers, showSummons) + local creaturesList = {} + for x = -radiusx, radiusx do + for y = -radiusy, radiusy do + if not (x == 0 and y == 0) then + creature = getTopCreature({x = position.x+x, y = position.y+y, z = position.z}) + if (creature.type == 1 and showPlayers == 1) or (creature.type == 2 and showMonsters == 1 and (showSummons ~= 1 or (showSummons == 1 and getCreatureMaster(creature.uid) == (creature.uid)))) then + table.insert(creaturesList, creature.uid) + end + end + end + end + + local creature = getTopCreature(position) + if (creature.type == 1 and showPlayers == 1) or (creature.type == 2 and showMonsters == 1 and (showSummons ~= 1 or (showSummons == 1 and getCreatureMaster(creature.uid) == (creature.uid)))) then + if not(table.find(creaturesList, creature.uid)) then + table.insert(creaturesList, creature.uid) + end + end + return creaturesList +end + +function addContainerWithItems(cid, container, item, item_count, count) + local Container = doPlayerAddItem(cid, container, 1) + for i = 1, count do + doAddContainerItem(Container, item, item_count) + end +end + +function tableToPos(t) + if type(t) == "table" and #t == 3 and tonumber(table.concat(t)) then + return {x = tonumber(t[1]), y = tonumber(t[2]), z = tonumber(t[3])} + end + return FALSE +end + +function positionExists(pos) + pos.stackpos = -1 + return type(getTileThingByPos(pos)) == "table" +end + +function warnPlayer(cid, msg) + doSendMagicEffect(getPlayerPosition(cid), CONST_ME_POFF) + return doPlayerSendCancel(cid, msg) +end + +string.split = function (str) + local t = {} + local function helper(word) + table.insert(t, word) + return "" + end + + if(not str:gsub("%w+", helper):find("%S")) then + return t + end +end + +string.trim = function(str) + return (string.gsub(str, "^%s*(.-)%s*$", "%1")) +end + +string.explode = function(str, sep) + local pos, t = 1, {} + if #sep == 0 or #str == 0 then + return + end + + for s, e in function() return str:find(sep, pos) end do + table.insert(t, str:sub(pos, s - 1):trim()) + pos = e + 1 + end + + table.insert(t, str:sub(pos):trim()) + return t +end + +function getCount(string) + local b, e = string:find("%d+") + return b and e and tonumber(string:sub(b, e)) or -1 +end + + -- Returns player name if player exists in database or 0 +function playerExists(name) + dofile("./config.lua") + if sqlType == "mysql" then + env = assert(luasql.mysql()) + con = assert(env:connect(mysqlDatabase, mysqlUser, mysqlPass, mysqlHost, mysqlPort)) + else + env = assert(luasql.sqlite3()) + con = assert(env:connect(sqliteDatabase)) + end + + local cur = assert(con:execute("SELECT `name` FROM `players` WHERE `name` = '" .. escapeString(name) .. "';")) + local row = cur:fetch({}, "a") + + local name_ = "" + if row ~= nil then + name_ = row.name + end + + cur:close() + con:close() + env:close() + return name_ +end + +function isSummon(cid) + return (isCreature(cid) == TRUE and (getCreatureMaster(cid) ~= cid)) and TRUE or FALSE +end + +function isPlayerSummon(cid) + return (isSummon(cid) == TRUE and isPlayer(getCreatureMaster(cid)) == TRUE) and TRUE or FALSE +end + +function doCopyItem(item, attributes) + local attributes = attributes or false + + local ret = doCreateItemEx(item.itemid, item.type) + if(attributes) then + if(item.actionid > 0) then + doSetItemActionId(ret, item.actionid) + end + end + + if(isContainer(item.uid) == TRUE) then + for i = (getContainerSize(item.uid) - 1), 0, -1 do + local tmp = getContainerItem(item.uid, i) + if(tmp.itemid > 0) then + doAddContainerItemEx(ret, doCopyItem(tmp, true).uid) + end + end + end + + return getThing(ret) +end + +function getTibianTime() + local worldTime = getWorldTime() + local minutes = worldTime % 60 + + local suffix = 'pm' + if worldTime >= 720 then + suffix = 'am' + if worldTime >= 780 then + worldTime = worldTime - 720 + end + end + + local hours = math.floor(worldTime / 60) + if minutes < 10 then + minutes = '0' .. minutes + end + return hours .. ':' .. minutes .. ' ' .. suffix +end + +looktypes = { + ["citizen"] = { + looktypeMale = 128, + looktypeFemale = 136 + }, + ["hunter"] = { + looktypeMale = 129, + looktypeFemale = 137 + }, + ["mage"] = { + looktypeMale = 130, + looktypeFemale = 138 + }, + ["knight"] = { + looktypeMale = 131, + looktypeFemale = 139 + }, + ["nobleman"] = { + looktypeMale = 132, + looktypeFemale = 140 + }, + ["summoner"] = { + looktypeMale = 133, + looktypeFemale = 141 + }, + ["warrior"] = { + looktypeMale = 134, + looktypeFemale = 142 + }, + ["barbarian"] = { + looktypeMale = 143, + looktypeFemale = 147 + }, + ["druid"] = { + looktypeMale = 144, + looktypeFemale = 148 + }, + ["wizard"] = { + looktypeMale = 145, + looktypeFemale = 149 + }, + ["oriental"] = { + looktypeMale = 146, + looktypeFemale = 150 + }, + ["pirate"] = { + looktypeMale = 151, + looktypeFemale = 155 + }, + ["assassin"] = { + looktypeMale = 152, + looktypeFemale = 156 + }, + ["beggar"] = { + looktypeMale = 153, + looktypeFemale = 157 + }, + ["shaman"] = { + looktypeMale = 154, + looktypeFemale = 158 + }, + ["norseman"] = { + looktypeMale = 251, + looktypeFemale = 252 + }, + ["nightmare"] = { + looktypeMale = 268, + looktypeFemale = 269 + }, + ["jester"] = { + looktypeMale = 273, + looktypeFemale = 270 + }, + ["brotherhood"] = { + looktypeMale = 278, + looktypeFemale = 279 + }, + ["demonhunter"] = { + looktypeMale = 289, + looktypeFemale = 288 + }, + ["yalaharian"] = { + looktypeMale = 325, + looktypeFemale = 324 + }, + ["warmaster"] = { + looktypeMale = 335, + looktypeFemale = 336 + }, + + ["potionbelt"] = { + looktypeMale = 133, + looktypeFemale = 138 + }, + ["tiara"] = { + looktypeMale = 133, + looktypeFemale = 138 + }, + ["ferumbrashat"] = { + looktypeMale = 130, + looktypeFemale = 141 + }, + ["wand"] = { + looktypeMale = 130, + looktypeFemale = 141 + } +} + +function hasAddon(cid, looktype, addon) + if looktypes[looktype] ~= nil then + if looktype == "beggar" or looktype == "hunter" then + if addon == 1 then + return canPlayerWearOutfit(cid, looktypes[looktype].looktypeMale, 1) == TRUE or canPlayerWearOutfit(cid, looktypes[looktype].looktypeFemale, 2) == TRUE + else + return canPlayerWearOutfit(cid, looktypes[looktype].looktypeMale, 2) == TRUE or canPlayerWearOutfit(cid, looktypes[looktype].looktypeFemale, 1) == TRUE + end + end + return canPlayerWearOutfit(cid, looktypes[looktype].looktypeMale, addon) == TRUE or canPlayerWearOutfit(cid, looktypes[looktype].looktypeFemale, addon) == TRUE + end + return false +end + +function addAddon(cid, looktype, addon) + if looktypes[looktype] ~= nil then + if looktype == "beggar" or looktype == "hunter" then + if addon == 1 then + doPlayerAddOutfit(cid, looktypes[looktype].looktypeMale, 1) + doPlayerAddOutfit(cid, looktypes[looktype].looktypeFemale, 2) + else + doPlayerAddOutfit(cid, looktypes[looktype].looktypeMale, 2) + doPlayerAddOutfit(cid, looktypes[looktype].looktypeFemale, 1) + end + else + doPlayerAddOutfit(cid, looktypes[looktype].looktypeMale, addon) + doPlayerAddOutfit(cid, looktypes[looktype].looktypeFemale, addon) + end + doSendMagicEffect(getCreaturePosition(cid), CONST_ME_MAGIC_RED) + end +end + +table.find = function (table, value) + for i, v in pairs(table) do + if(v == value) then + return i + end + end + return nil +end + +table.isStrIn = function (txt, str) + for i, v in pairs(str) do + if(txt:find(v) and not txt:find('(%w+)' .. v) and not txt:find(v .. '(%w+)')) then + return true + end + end + return false +end + +function isMonsterInRange(monsterName, fromPos, toPos) + for _x = fromPos.x, toPos.x do + for _y = fromPos.y, toPos.y do + for _z = fromPos.z, toPos.z do + creature = getTopCreature({x = _x, y = _y, z = _z}) + if creature.type == 2 and getCreatureName(creature.uid):lower() == monsterName:lower() then + return true + end + end + end + end + return false +end + +-- LuaSQL wrapper +luasql_environment = { + connections = {} +} +function luasql_environment:new() return self end +function luasql_environment:connect() + local connection = luasql_connection:new() + table.insert(self.connections, connection) + return connection +end +function luasql_environment:close() + for _, v in pairs(self.connections) do + v:close() + end + self.connections = {} + return true +end + +luasql_connection = { + resultIds = {} +} +function luasql_connection:new() return self end +function luasql_connection:close() + for _, v in ipairs(self.resultIds) do + result.free(v) + end + self.resultIds = {} + return true +end +function luasql_connection:execute(statement) + if statement:sub(1, 6):upper() == "SELECT" then + local cursor = luasql_cursor:new(self, statement) + if cursor.resultId ~= false then + table.insert(self.resultIds, cursor.resultId) + end + return cursor + end + return db.query(statement) +end +function luasql_connection:closedCursor(resultId) + for k, v in ipairs(self.resultIds) do + if v == resultId then + table.remove(self.resultIds, k) + break + end + end +end + +luasql_cursor = { + connection, + resultId +} +function luasql_cursor:new(connection, statement) + self.connection = connection + self.resultId = db.storeQuery(statement) + return self +end +function luasql_cursor:close() + if self.resultId == false then + return true + end + + self.connection:closedCursor(self.resultId) + return result.free(self.resultId) +end +function luasql_cursor:fetch() + if self.resultId == false then + return nil + end + + local ret = result.getAllData(self.resultId) + if ret == false then + self:close() + self.resultId = false + return nil + end + + if result.next(self.resultId) == false then + self:close() + self.resultId = false + end + return ret +end + +luasql = { + mysql = function() return luasql_environment:new() end, + sqlite3 = function() return luasql_environment:new() end, + odbc = function() return luasql_environment:new() end, + postgres = function() return luasql_environment:new() end +} +-- + +function escapeString(str) + str = db.escapeString(str) + if str:len() <= 2 then + return "" + end + return str:sub(2, str:len() - 1) +end + +function createClass(parent) + local newClass = {} + function newClass:new(instance) + local instance = instance or {} + setmetatable(instance, {__index = newClass}) + return instance + end + + if(parent ~= nil) then + setmetatable(newClass, {__index = parent}) + end + + function newClass:getSelf() + return newClass + end + + function newClass:getParent() + return baseClass + end + + function newClass:isa(class) + local tmp = newClass + while(tmp ~= nil) do + if(tmp == class) then + return true + end + + tmp = tmp:getParent() + end + + return false + end + + function newClass:setAttributes(attributes) + for k, v in pairs(attributes) do + newClass[k] = v + end + end + + return newClass +end + +Result = createClass(nil) +Result:setAttributes({ + id = -1, + query = "" +}) + +function Result:getID() + return self.id +end + +function Result:setID(_id) + self.id = _id +end + +function Result:getQuery() + return self.query +end + +function Result:setQuery(_query) + self.query = _query +end + +function Result:create(_query) + self:setQuery(_query) + local _id = db.storeQuery(self:getQuery()) + if(_id) then + self:setID(_id) + end + + return self:getID() +end + +function Result:getRows(free) + local free = free or false + if(self:getID() == -1) then + error("[Result:getRows] Result not set!") + end + + local c = 0 + repeat + c = c + 1 + until not self:next() + + local _query = self:getQuery() + self:free() + if(not free) then + self:create(_query) + end + + return c +end + +function Result:getDataInt(s) + if(self:getID() == -1) then + error("[Result:getDataInt] Result not set!") + end + + return result.getDataInt(self:getID(), s) +end + +function Result:getDataLong(s) + if(self:getID() == -1) then + error("[Result:getDataLong] Result not set!") + end + + return result.getDataLong(self:getID(), s) +end + +function Result:getDataString(s) + if(self:getID() == -1) then + error("[Result:getDataString] Result not set!") + end + + return result.getDataString(self:getID(), s) +end + +function Result:getDataStream(s) + if(self:getID() == -1) then + error("[Result:getDataStream] Result not set!") + end + + return result.getDataStream(self:getID(), s) +end + +function Result:next() + if(self:getID() == -1) then + error("[Result:next] Result not set!") + end + + return result.next(self:getID()) +end + +function Result:free() + if(self:getID() == -1) then + error("[Result:free] Result not set!") + end + + self:setQuery("") + local ret = result.free(self:getID()) + self:setID(-1) + return ret +end + +Result.numRows = Result.getRows +function db.getResult(query) + if(type(query) ~= 'string') then + return nil + end + + local ret = Result:new() + ret:create(query) + return ret +end diff --git a/data/globalevents/globalevents.xml b/data/globalevents/globalevents.xml new file mode 100644 index 0000000000..ddff9211ce --- /dev/null +++ b/data/globalevents/globalevents.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/data/globalevents/lib/globalevents.lua b/data/globalevents/lib/globalevents.lua new file mode 100644 index 0000000000..6de6c7d2af --- /dev/null +++ b/data/globalevents/lib/globalevents.lua @@ -0,0 +1 @@ +-- empty file -- \ No newline at end of file diff --git a/data/globalevents/scripts/record.lua b/data/globalevents/scripts/record.lua new file mode 100644 index 0000000000..1c6a4929b3 --- /dev/null +++ b/data/globalevents/scripts/record.lua @@ -0,0 +1,4 @@ +function onRecord(current, old) + addEvent(broadcastMessage, 150, "New record: " .. current .. " players are logged in.", MESSAGE_STATUS_DEFAULT) + return true +end \ No newline at end of file diff --git a/data/globalevents/scripts/startup.lua b/data/globalevents/scripts/startup.lua new file mode 100644 index 0000000000..056cfc1f31 --- /dev/null +++ b/data/globalevents/scripts/startup.lua @@ -0,0 +1,3 @@ +function onStartup() + db.query("DELETE FROM `guild_wars` WHERE `status` = 0") +end diff --git a/data/items/items.otb b/data/items/items.otb new file mode 100644 index 0000000000000000000000000000000000000000..783578751c4fbcaac252b6371f2c7127226ddfd5 GIT binary patch literal 859564 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 mELvf+Z63ZqE+%~s+W{4zYLEL+cl;b0~iaRW^Q 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

A6^Oiu$UPt0Ek%oi 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 ~o!^56nB5T5Uo5Gi4r64kT~fc33$~kzs;i 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

vFRaW7ozCTUS8~!1Qzp0XGp)ME%`Y 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/items/items.xsd b/data/items/items.xsd new file mode 100644 index 0000000000..2a38550cef --- /dev/null +++ b/data/items/items.xsd @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Amazons/amazon.xml b/data/monster/Amazons/amazon.xml new file mode 100644 index 0000000000..bef2a2abe1 --- /dev/null +++ b/data/monster/Amazons/amazon.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Amazons/valkyrie.xml b/data/monster/Amazons/valkyrie.xml new file mode 100644 index 0000000000..888e934a9b --- /dev/null +++ b/data/monster/Amazons/valkyrie.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Annelids/carrion worm.xml b/data/monster/Annelids/carrion worm.xml new file mode 100644 index 0000000000..1cc9fbf2ba --- /dev/null +++ b/data/monster/Annelids/carrion worm.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Annelids/rotworm.xml b/data/monster/Annelids/rotworm.xml new file mode 100644 index 0000000000..23938cbdc1 --- /dev/null +++ b/data/monster/Annelids/rotworm.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Apes/kongra.xml b/data/monster/Apes/kongra.xml new file mode 100644 index 0000000000..caf7d05e0d --- /dev/null +++ b/data/monster/Apes/kongra.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Apes/merlkin.xml b/data/monster/Apes/merlkin.xml new file mode 100644 index 0000000000..9ed34d9270 --- /dev/null +++ b/data/monster/Apes/merlkin.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Apes/sibang.xml b/data/monster/Apes/sibang.xml new file mode 100644 index 0000000000..bfdf099ce8 --- /dev/null +++ b/data/monster/Apes/sibang.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arachnids/crystal spider.xml b/data/monster/Arachnids/crystal spider.xml new file mode 100644 index 0000000000..24fee5f290 --- /dev/null +++ b/data/monster/Arachnids/crystal spider.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arachnids/giant spider.xml b/data/monster/Arachnids/giant spider.xml new file mode 100644 index 0000000000..150febcefc --- /dev/null +++ b/data/monster/Arachnids/giant spider.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arachnids/poison spider.xml b/data/monster/Arachnids/poison spider.xml new file mode 100644 index 0000000000..cac74afa4b --- /dev/null +++ b/data/monster/Arachnids/poison spider.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arachnids/sandstone scorpion.xml b/data/monster/Arachnids/sandstone scorpion.xml new file mode 100644 index 0000000000..853c0b9d40 --- /dev/null +++ b/data/monster/Arachnids/sandstone scorpion.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arachnids/scorpion.xml b/data/monster/Arachnids/scorpion.xml new file mode 100644 index 0000000000..c60bafb025 --- /dev/null +++ b/data/monster/Arachnids/scorpion.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arachnids/spider.xml b/data/monster/Arachnids/spider.xml new file mode 100644 index 0000000000..b41cc2a322 --- /dev/null +++ b/data/monster/Arachnids/spider.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arachnids/tarantula.xml b/data/monster/Arachnids/tarantula.xml new file mode 100644 index 0000000000..b0b71113e0 --- /dev/null +++ b/data/monster/Arachnids/tarantula.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arachnids/wailing widow.xml b/data/monster/Arachnids/wailing widow.xml new file mode 100644 index 0000000000..d979ba6bbd --- /dev/null +++ b/data/monster/Arachnids/wailing widow.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Arena/greenhorn/achad.xml b/data/monster/Arena/greenhorn/achad.xml new file mode 100644 index 0000000000..74607853cc --- /dev/null +++ b/data/monster/Arena/greenhorn/achad.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/greenhorn/axeitus headbanger.xml b/data/monster/Arena/greenhorn/axeitus headbanger.xml new file mode 100644 index 0000000000..ea5453a5ce --- /dev/null +++ b/data/monster/Arena/greenhorn/axeitus headbanger.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/greenhorn/bloodpaw.xml b/data/monster/Arena/greenhorn/bloodpaw.xml new file mode 100644 index 0000000000..8131dea0af --- /dev/null +++ b/data/monster/Arena/greenhorn/bloodpaw.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/greenhorn/bovinus.xml b/data/monster/Arena/greenhorn/bovinus.xml new file mode 100644 index 0000000000..881118c32e --- /dev/null +++ b/data/monster/Arena/greenhorn/bovinus.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/greenhorn/colerian the barbarian.xml b/data/monster/Arena/greenhorn/colerian the barbarian.xml new file mode 100644 index 0000000000..00cb973b73 --- /dev/null +++ b/data/monster/Arena/greenhorn/colerian the barbarian.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/greenhorn/cursed gladiator.xml b/data/monster/Arena/greenhorn/cursed gladiator.xml new file mode 100644 index 0000000000..e8cd50d5dc --- /dev/null +++ b/data/monster/Arena/greenhorn/cursed gladiator.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/greenhorn/frostfur.xml b/data/monster/Arena/greenhorn/frostfur.xml new file mode 100644 index 0000000000..9bb2e7fc1b --- /dev/null +++ b/data/monster/Arena/greenhorn/frostfur.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/greenhorn/orcus the cruel.xml b/data/monster/Arena/greenhorn/orcus the cruel.xml new file mode 100644 index 0000000000..1f65e87e53 --- /dev/null +++ b/data/monster/Arena/greenhorn/orcus the cruel.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/greenhorn/rocky.xml b/data/monster/Arena/greenhorn/rocky.xml new file mode 100644 index 0000000000..b6f2e9d248 --- /dev/null +++ b/data/monster/Arena/greenhorn/rocky.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/greenhorn/the hairy one.xml b/data/monster/Arena/greenhorn/the hairy one.xml new file mode 100644 index 0000000000..654fa057c1 --- /dev/null +++ b/data/monster/Arena/greenhorn/the hairy one.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/scrapper/avalanche.xml b/data/monster/Arena/scrapper/avalanche.xml new file mode 100644 index 0000000000..75401768f0 --- /dev/null +++ b/data/monster/Arena/scrapper/avalanche.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/scrapper/drasilla.xml b/data/monster/Arena/scrapper/drasilla.xml new file mode 100644 index 0000000000..c5a5f1c830 --- /dev/null +++ b/data/monster/Arena/scrapper/drasilla.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/scrapper/grimgor guteater.xml b/data/monster/Arena/scrapper/grimgor guteater.xml new file mode 100644 index 0000000000..f822d69dd8 --- /dev/null +++ b/data/monster/Arena/scrapper/grimgor guteater.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/scrapper/kreebosh the exile.xml b/data/monster/Arena/scrapper/kreebosh the exile.xml new file mode 100644 index 0000000000..b3c0fcea12 --- /dev/null +++ b/data/monster/Arena/scrapper/kreebosh the exile.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/scrapper/slim.xml b/data/monster/Arena/scrapper/slim.xml new file mode 100644 index 0000000000..a6fabeda44 --- /dev/null +++ b/data/monster/Arena/scrapper/slim.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/scrapper/spirit of earth.xml b/data/monster/Arena/scrapper/spirit of earth.xml new file mode 100644 index 0000000000..f5bc665b23 --- /dev/null +++ b/data/monster/Arena/scrapper/spirit of earth.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/scrapper/spirit of fire.xml b/data/monster/Arena/scrapper/spirit of fire.xml new file mode 100644 index 0000000000..78581b75f0 --- /dev/null +++ b/data/monster/Arena/scrapper/spirit of fire.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/scrapper/spirit of water.xml b/data/monster/Arena/scrapper/spirit of water.xml new file mode 100644 index 0000000000..fc918d235f --- /dev/null +++ b/data/monster/Arena/scrapper/spirit of water.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/scrapper/the dark dancer.xml b/data/monster/Arena/scrapper/the dark dancer.xml new file mode 100644 index 0000000000..006446566c --- /dev/null +++ b/data/monster/Arena/scrapper/the dark dancer.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/scrapper/the hag.xml b/data/monster/Arena/scrapper/the hag.xml new file mode 100644 index 0000000000..563c5f1312 --- /dev/null +++ b/data/monster/Arena/scrapper/the hag.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/warlord/darakan the executioner.xml b/data/monster/Arena/warlord/darakan the executioner.xml new file mode 100644 index 0000000000..e8639ed586 --- /dev/null +++ b/data/monster/Arena/warlord/darakan the executioner.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/warlord/deathbringer.xml b/data/monster/Arena/warlord/deathbringer.xml new file mode 100644 index 0000000000..ef4dc25064 --- /dev/null +++ b/data/monster/Arena/warlord/deathbringer.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/warlord/fallen mooh'tah master ghar.xml b/data/monster/Arena/warlord/fallen mooh'tah master ghar.xml new file mode 100644 index 0000000000..d03e1227c7 --- /dev/null +++ b/data/monster/Arena/warlord/fallen mooh'tah master ghar.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/warlord/gnorre chyllson.xml b/data/monster/Arena/warlord/gnorre chyllson.xml new file mode 100644 index 0000000000..0471bb77ff --- /dev/null +++ b/data/monster/Arena/warlord/gnorre chyllson.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/warlord/norgle glacierbeard.xml b/data/monster/Arena/warlord/norgle glacierbeard.xml new file mode 100644 index 0000000000..68e300d1cd --- /dev/null +++ b/data/monster/Arena/warlord/norgle glacierbeard.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/warlord/svoren the mad.xml b/data/monster/Arena/warlord/svoren the mad.xml new file mode 100644 index 0000000000..e15682c26e --- /dev/null +++ b/data/monster/Arena/warlord/svoren the mad.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/warlord/the masked marauder.xml b/data/monster/Arena/warlord/the masked marauder.xml new file mode 100644 index 0000000000..14b5622482 --- /dev/null +++ b/data/monster/Arena/warlord/the masked marauder.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/warlord/the obliverator.xml b/data/monster/Arena/warlord/the obliverator.xml new file mode 100644 index 0000000000..b24c672459 --- /dev/null +++ b/data/monster/Arena/warlord/the obliverator.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/warlord/the pit lord.xml b/data/monster/Arena/warlord/the pit lord.xml new file mode 100644 index 0000000000..790548d168 --- /dev/null +++ b/data/monster/Arena/warlord/the pit lord.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Arena/warlord/webster.xml b/data/monster/Arena/warlord/webster.xml new file mode 100644 index 0000000000..4ba7b421fb --- /dev/null +++ b/data/monster/Arena/warlord/webster.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Barbarians/barbarian bloodwalker.xml b/data/monster/Barbarians/barbarian bloodwalker.xml new file mode 100644 index 0000000000..866f037c9e --- /dev/null +++ b/data/monster/Barbarians/barbarian bloodwalker.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Barbarians/barbarian brutetamer.xml b/data/monster/Barbarians/barbarian brutetamer.xml new file mode 100644 index 0000000000..793e8583a7 --- /dev/null +++ b/data/monster/Barbarians/barbarian brutetamer.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Barbarians/barbarian headsplitter.xml b/data/monster/Barbarians/barbarian headsplitter.xml new file mode 100644 index 0000000000..8e572ae2f1 --- /dev/null +++ b/data/monster/Barbarians/barbarian headsplitter.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Barbarians/barbarian skullhunter.xml b/data/monster/Barbarians/barbarian skullhunter.xml new file mode 100644 index 0000000000..64a19d93fa --- /dev/null +++ b/data/monster/Barbarians/barbarian skullhunter.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Bears/bear.xml b/data/monster/Bears/bear.xml new file mode 100644 index 0000000000..6ca6a1b569 --- /dev/null +++ b/data/monster/Bears/bear.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bears/panda.xml b/data/monster/Bears/panda.xml new file mode 100644 index 0000000000..914708def9 --- /dev/null +++ b/data/monster/Bears/panda.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bears/polar bear.xml b/data/monster/Bears/polar bear.xml new file mode 100644 index 0000000000..c81893e9ac --- /dev/null +++ b/data/monster/Bears/polar bear.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bears/undead cavebear.xml b/data/monster/Bears/undead cavebear.xml new file mode 100644 index 0000000000..9f4605ec8e --- /dev/null +++ b/data/monster/Bears/undead cavebear.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bio-Elementals/bog raider.xml b/data/monster/Bio-Elementals/bog raider.xml new file mode 100644 index 0000000000..db117ef66f --- /dev/null +++ b/data/monster/Bio-Elementals/bog raider.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bio-Elementals/carniphila.xml b/data/monster/Bio-Elementals/carniphila.xml new file mode 100644 index 0000000000..f1c86d16c6 --- /dev/null +++ b/data/monster/Bio-Elementals/carniphila.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bio-Elementals/defiler.xml b/data/monster/Bio-Elementals/defiler.xml new file mode 100644 index 0000000000..6232942404 --- /dev/null +++ b/data/monster/Bio-Elementals/defiler.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bio-Elementals/haunted treeling.xml b/data/monster/Bio-Elementals/haunted treeling.xml new file mode 100644 index 0000000000..61283b3978 --- /dev/null +++ b/data/monster/Bio-Elementals/haunted treeling.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bio-Elementals/slime summon.xml b/data/monster/Bio-Elementals/slime summon.xml new file mode 100644 index 0000000000..7eb31a88d0 --- /dev/null +++ b/data/monster/Bio-Elementals/slime summon.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Bio-Elementals/slime.xml b/data/monster/Bio-Elementals/slime.xml new file mode 100644 index 0000000000..2f91d13a53 --- /dev/null +++ b/data/monster/Bio-Elementals/slime.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bio-Elementals/slug.xml b/data/monster/Bio-Elementals/slug.xml new file mode 100644 index 0000000000..38a5abf44d --- /dev/null +++ b/data/monster/Bio-Elementals/slug.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bio-Elementals/son of verminor.xml b/data/monster/Bio-Elementals/son of verminor.xml new file mode 100644 index 0000000000..469e1df7bd --- /dev/null +++ b/data/monster/Bio-Elementals/son of verminor.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bio-Elementals/spit nettle.xml b/data/monster/Bio-Elementals/spit nettle.xml new file mode 100644 index 0000000000..991d47795e --- /dev/null +++ b/data/monster/Bio-Elementals/spit nettle.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Birds/chicken.xml b/data/monster/Birds/chicken.xml new file mode 100644 index 0000000000..83c8b659f1 --- /dev/null +++ b/data/monster/Birds/chicken.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Birds/flamingo.xml b/data/monster/Birds/flamingo.xml new file mode 100644 index 0000000000..d0cbf5fcff --- /dev/null +++ b/data/monster/Birds/flamingo.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Birds/parrot.xml b/data/monster/Birds/parrot.xml new file mode 100644 index 0000000000..f3c78a18d3 --- /dev/null +++ b/data/monster/Birds/parrot.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Birds/penguin.xml b/data/monster/Birds/penguin.xml new file mode 100644 index 0000000000..8baa108adf --- /dev/null +++ b/data/monster/Birds/penguin.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Birds/seagull.xml b/data/monster/Birds/seagull.xml new file mode 100644 index 0000000000..070751372f --- /dev/null +++ b/data/monster/Birds/seagull.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Birds/terror bird.xml b/data/monster/Birds/terror bird.xml new file mode 100644 index 0000000000..fb95491897 --- /dev/null +++ b/data/monster/Birds/terror bird.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Blobs/acid blob.xml b/data/monster/Blobs/acid blob.xml new file mode 100644 index 0000000000..c853fa6176 --- /dev/null +++ b/data/monster/Blobs/acid blob.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Blobs/death blob.xml b/data/monster/Blobs/death blob.xml new file mode 100644 index 0000000000..9abdca2621 --- /dev/null +++ b/data/monster/Blobs/death blob.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Blobs/mercury blob.xml b/data/monster/Blobs/mercury blob.xml new file mode 100644 index 0000000000..7b94396e7a --- /dev/null +++ b/data/monster/Blobs/mercury blob.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bonelords/bonelord.xml b/data/monster/Bonelords/bonelord.xml new file mode 100644 index 0000000000..ec1eb69a76 --- /dev/null +++ b/data/monster/Bonelords/bonelord.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bonelords/braindeath.xml b/data/monster/Bonelords/braindeath.xml new file mode 100644 index 0000000000..27cc746fde --- /dev/null +++ b/data/monster/Bonelords/braindeath.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bonelords/elder bonelord.xml b/data/monster/Bonelords/elder bonelord.xml new file mode 100644 index 0000000000..f9f916053c --- /dev/null +++ b/data/monster/Bonelords/elder bonelord.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bonelords/gazer.xml b/data/monster/Bonelords/gazer.xml new file mode 100644 index 0000000000..06344e41bd --- /dev/null +++ b/data/monster/Bonelords/gazer.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/annihilon.xml b/data/monster/Bosses/annihilon.xml new file mode 100644 index 0000000000..bd318ef5bb --- /dev/null +++ b/data/monster/Bosses/annihilon.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/apprentice sheng.xml b/data/monster/Bosses/apprentice sheng.xml new file mode 100644 index 0000000000..ba831bfa3e --- /dev/null +++ b/data/monster/Bosses/apprentice sheng.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/barbaria.xml b/data/monster/Bosses/barbaria.xml new file mode 100644 index 0000000000..6dcb7c8216 --- /dev/null +++ b/data/monster/Bosses/barbaria.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/baron brute.xml b/data/monster/Bosses/baron brute.xml new file mode 100644 index 0000000000..df6e118c04 --- /dev/null +++ b/data/monster/Bosses/baron brute.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/big boss trolliver.xml b/data/monster/Bosses/big boss trolliver.xml new file mode 100644 index 0000000000..be0a62b519 --- /dev/null +++ b/data/monster/Bosses/big boss trolliver.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/bones.xml b/data/monster/Bosses/bones.xml new file mode 100644 index 0000000000..2802f42e85 --- /dev/null +++ b/data/monster/Bosses/bones.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/brutus bloodbeard.xml b/data/monster/Bosses/brutus bloodbeard.xml new file mode 100644 index 0000000000..e582d08f64 --- /dev/null +++ b/data/monster/Bosses/brutus bloodbeard.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/chizzoron the distorter.xml b/data/monster/Bosses/chizzoron the distorter.xml new file mode 100644 index 0000000000..edadb0ac12 --- /dev/null +++ b/data/monster/Bosses/chizzoron the distorter.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Bosses/countess sorrow.xml b/data/monster/Bosses/countess sorrow.xml new file mode 100644 index 0000000000..04e8d2434a --- /dev/null +++ b/data/monster/Bosses/countess sorrow.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/deadeye devious.xml b/data/monster/Bosses/deadeye devious.xml new file mode 100644 index 0000000000..44af64e653 --- /dev/null +++ b/data/monster/Bosses/deadeye devious.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/demodras.xml b/data/monster/Bosses/demodras.xml new file mode 100644 index 0000000000..6231d20f84 --- /dev/null +++ b/data/monster/Bosses/demodras.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/dharalion.xml b/data/monster/Bosses/dharalion.xml new file mode 100644 index 0000000000..b083b5926a --- /dev/null +++ b/data/monster/Bosses/dharalion.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/dire penguin.xml b/data/monster/Bosses/dire penguin.xml new file mode 100644 index 0000000000..88437d762a --- /dev/null +++ b/data/monster/Bosses/dire penguin.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/dracola.xml b/data/monster/Bosses/dracola.xml new file mode 100644 index 0000000000..ee944a0115 --- /dev/null +++ b/data/monster/Bosses/dracola.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/dreadwing.xml b/data/monster/Bosses/dreadwing.xml new file mode 100644 index 0000000000..1c95bb1172 --- /dev/null +++ b/data/monster/Bosses/dreadwing.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/esmeralda.xml b/data/monster/Bosses/esmeralda.xml new file mode 100644 index 0000000000..507e2544f8 --- /dev/null +++ b/data/monster/Bosses/esmeralda.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/fatality.xml b/data/monster/Bosses/fatality.xml new file mode 100644 index 0000000000..0af32751cf --- /dev/null +++ b/data/monster/Bosses/fatality.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Bosses/fernfang.xml b/data/monster/Bosses/fernfang.xml new file mode 100644 index 0000000000..fbdf876b38 --- /dev/null +++ b/data/monster/Bosses/fernfang.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/ferumbras.xml b/data/monster/Bosses/ferumbras.xml new file mode 100644 index 0000000000..a32a940d4c --- /dev/null +++ b/data/monster/Bosses/ferumbras.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/fluffy.xml b/data/monster/Bosses/fluffy.xml new file mode 100644 index 0000000000..57c3cf4d04 --- /dev/null +++ b/data/monster/Bosses/fluffy.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/foreman kneebiter.xml b/data/monster/Bosses/foreman kneebiter.xml new file mode 100644 index 0000000000..ec2e217081 --- /dev/null +++ b/data/monster/Bosses/foreman kneebiter.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/general murius.xml b/data/monster/Bosses/general murius.xml new file mode 100644 index 0000000000..b8c123befd --- /dev/null +++ b/data/monster/Bosses/general murius.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/ghazbaran.xml b/data/monster/Bosses/ghazbaran.xml new file mode 100644 index 0000000000..de5e6fe46d --- /dev/null +++ b/data/monster/Bosses/ghazbaran.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/glitterscale.xml b/data/monster/Bosses/glitterscale.xml new file mode 100644 index 0000000000..95766d020b --- /dev/null +++ b/data/monster/Bosses/glitterscale.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Bosses/golgordan.xml b/data/monster/Bosses/golgordan.xml new file mode 100644 index 0000000000..56e09694bb --- /dev/null +++ b/data/monster/Bosses/golgordan.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/grand mother foulscale.xml b/data/monster/Bosses/grand mother foulscale.xml new file mode 100644 index 0000000000..d19810de1a --- /dev/null +++ b/data/monster/Bosses/grand mother foulscale.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/grorlam.xml b/data/monster/Bosses/grorlam.xml new file mode 100644 index 0000000000..e8a85df915 --- /dev/null +++ b/data/monster/Bosses/grorlam.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/hairman the huge.xml b/data/monster/Bosses/hairman the huge.xml new file mode 100644 index 0000000000..8538a8cecc --- /dev/null +++ b/data/monster/Bosses/hairman the huge.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/handmaiden.xml b/data/monster/Bosses/handmaiden.xml new file mode 100644 index 0000000000..f54d4b3014 --- /dev/null +++ b/data/monster/Bosses/handmaiden.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/hellgorak.xml b/data/monster/Bosses/hellgorak.xml new file mode 100644 index 0000000000..3e1087b3a2 --- /dev/null +++ b/data/monster/Bosses/hellgorak.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/heoni.xml b/data/monster/Bosses/heoni.xml new file mode 100644 index 0000000000..a28f375882 --- /dev/null +++ b/data/monster/Bosses/heoni.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/hide.xml b/data/monster/Bosses/hide.xml new file mode 100644 index 0000000000..20e1c94650 --- /dev/null +++ b/data/monster/Bosses/hide.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/koshei the deathless.xml b/data/monster/Bosses/koshei the deathless.xml new file mode 100644 index 0000000000..14e40ce723 --- /dev/null +++ b/data/monster/Bosses/koshei the deathless.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + --staff + --platinium amulet + --blue robe + --lightning boots + --castle shield + --stealth ring + --dirty cape + + --spell book + --gold ring + --liche staff + + diff --git a/data/monster/Bosses/latrivan.xml b/data/monster/Bosses/latrivan.xml new file mode 100644 index 0000000000..cf8a6c3395 --- /dev/null +++ b/data/monster/Bosses/latrivan.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/lethal lissy.xml b/data/monster/Bosses/lethal lissy.xml new file mode 100644 index 0000000000..2f1977d409 --- /dev/null +++ b/data/monster/Bosses/lethal lissy.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/leviathan.xml b/data/monster/Bosses/leviathan.xml new file mode 100644 index 0000000000..a8a007bf11 --- /dev/null +++ b/data/monster/Bosses/leviathan.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/lord of the elements.xml b/data/monster/Bosses/lord of the elements.xml new file mode 100644 index 0000000000..15ce2b5570 --- /dev/null +++ b/data/monster/Bosses/lord of the elements.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/mad technomancer.xml b/data/monster/Bosses/mad technomancer.xml new file mode 100644 index 0000000000..d44bdd8b5f --- /dev/null +++ b/data/monster/Bosses/mad technomancer.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/madareth.xml b/data/monster/Bosses/madareth.xml new file mode 100644 index 0000000000..4a7c78adc1 --- /dev/null +++ b/data/monster/Bosses/madareth.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/man in the cave.xml b/data/monster/Bosses/man in the cave.xml new file mode 100644 index 0000000000..905fb4066b --- /dev/null +++ b/data/monster/Bosses/man in the cave.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/massacre.xml b/data/monster/Bosses/massacre.xml new file mode 100644 index 0000000000..69c6eb4edc --- /dev/null +++ b/data/monster/Bosses/massacre.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/minishabaal.xml b/data/monster/Bosses/minishabaal.xml new file mode 100644 index 0000000000..6ab4532dcc --- /dev/null +++ b/data/monster/Bosses/minishabaal.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/morgaroth.xml b/data/monster/Bosses/morgaroth.xml new file mode 100644 index 0000000000..b739525348 --- /dev/null +++ b/data/monster/Bosses/morgaroth.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/mr. punish.xml b/data/monster/Bosses/mr. punish.xml new file mode 100644 index 0000000000..9e4f490230 --- /dev/null +++ b/data/monster/Bosses/mr. punish.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/munster.xml b/data/monster/Bosses/munster.xml new file mode 100644 index 0000000000..3db6a182e6 --- /dev/null +++ b/data/monster/Bosses/munster.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/necropharus.xml b/data/monster/Bosses/necropharus.xml new file mode 100644 index 0000000000..87fd8b492c --- /dev/null +++ b/data/monster/Bosses/necropharus.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/orshabaal.xml b/data/monster/Bosses/orshabaal.xml new file mode 100644 index 0000000000..59ba2f7f89 --- /dev/null +++ b/data/monster/Bosses/orshabaal.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/pythius the rotten.xml b/data/monster/Bosses/pythius the rotten.xml new file mode 100644 index 0000000000..8da8eb96dc --- /dev/null +++ b/data/monster/Bosses/pythius the rotten.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/ron the ripper.xml b/data/monster/Bosses/ron the ripper.xml new file mode 100644 index 0000000000..fe85f960f5 --- /dev/null +++ b/data/monster/Bosses/ron the ripper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/rotworm queen.xml b/data/monster/Bosses/rotworm queen.xml new file mode 100644 index 0000000000..b835e9f9df --- /dev/null +++ b/data/monster/Bosses/rotworm queen.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/shardhead.xml b/data/monster/Bosses/shardhead.xml new file mode 100644 index 0000000000..19d0ffafd8 --- /dev/null +++ b/data/monster/Bosses/shardhead.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/snake god essence.xml b/data/monster/Bosses/snake god essence.xml new file mode 100644 index 0000000000..d110585d05 --- /dev/null +++ b/data/monster/Bosses/snake god essence.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/snake thing.xml b/data/monster/Bosses/snake thing.xml new file mode 100644 index 0000000000..34b9a9fd99 --- /dev/null +++ b/data/monster/Bosses/snake thing.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/stonecracker.xml b/data/monster/Bosses/stonecracker.xml new file mode 100644 index 0000000000..6ef6035574 --- /dev/null +++ b/data/monster/Bosses/stonecracker.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/the abomination.xml b/data/monster/Bosses/the abomination.xml new file mode 100644 index 0000000000..53fd598e72 --- /dev/null +++ b/data/monster/Bosses/the abomination.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/the blightfather.xml b/data/monster/Bosses/the blightfather.xml new file mode 100644 index 0000000000..c27eaeba3d --- /dev/null +++ b/data/monster/Bosses/the blightfather.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Bosses/the bloodtusk.xml b/data/monster/Bosses/the bloodtusk.xml new file mode 100644 index 0000000000..57aad6cd96 --- /dev/null +++ b/data/monster/Bosses/the bloodtusk.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/the count.xml b/data/monster/Bosses/the count.xml new file mode 100644 index 0000000000..9d94d9bdb8 --- /dev/null +++ b/data/monster/Bosses/the count.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/the evil eye.xml b/data/monster/Bosses/the evil eye.xml new file mode 100644 index 0000000000..8eed16b1f5 --- /dev/null +++ b/data/monster/Bosses/the evil eye.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Bosses/the handmaiden.xml b/data/monster/Bosses/the handmaiden.xml new file mode 100644 index 0000000000..98ff4675d2 --- /dev/null +++ b/data/monster/Bosses/the handmaiden.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/the horned fox.xml b/data/monster/Bosses/the horned fox.xml new file mode 100644 index 0000000000..d939a994f9 --- /dev/null +++ b/data/monster/Bosses/the horned fox.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/the imperor.xml b/data/monster/Bosses/the imperor.xml new file mode 100644 index 0000000000..85cb8d506f --- /dev/null +++ b/data/monster/Bosses/the imperor.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/the many.xml b/data/monster/Bosses/the many.xml new file mode 100644 index 0000000000..4e1ff17204 --- /dev/null +++ b/data/monster/Bosses/the many.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/the noxious spawn.xml b/data/monster/Bosses/the noxious spawn.xml new file mode 100644 index 0000000000..c6514e81ef --- /dev/null +++ b/data/monster/Bosses/the noxious spawn.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/the old widow.xml b/data/monster/Bosses/the old widow.xml new file mode 100644 index 0000000000..f552826c92 --- /dev/null +++ b/data/monster/Bosses/the old widow.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Bosses/the plasmother.xml b/data/monster/Bosses/the plasmother.xml new file mode 100644 index 0000000000..1fba727454 --- /dev/null +++ b/data/monster/Bosses/the plasmother.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/the snapper.xml b/data/monster/Bosses/the snapper.xml new file mode 100644 index 0000000000..537414eb66 --- /dev/null +++ b/data/monster/Bosses/the snapper.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/thul.xml b/data/monster/Bosses/thul.xml new file mode 100644 index 0000000000..ddca469e30 --- /dev/null +++ b/data/monster/Bosses/thul.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/tibia bug.xml b/data/monster/Bosses/tibia bug.xml new file mode 100644 index 0000000000..af351d445e --- /dev/null +++ b/data/monster/Bosses/tibia bug.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/tiquandas revenge.xml b/data/monster/Bosses/tiquandas revenge.xml new file mode 100644 index 0000000000..17ef623bd6 --- /dev/null +++ b/data/monster/Bosses/tiquandas revenge.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/undead minion.xml b/data/monster/Bosses/undead minion.xml new file mode 100644 index 0000000000..59bbeef312 --- /dev/null +++ b/data/monster/Bosses/undead minion.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/ungreez.xml b/data/monster/Bosses/ungreez.xml new file mode 100644 index 0000000000..c4591d5190 --- /dev/null +++ b/data/monster/Bosses/ungreez.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/ushuriel.xml b/data/monster/Bosses/ushuriel.xml new file mode 100644 index 0000000000..64a81f2ed8 --- /dev/null +++ b/data/monster/Bosses/ushuriel.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/warlord ruzad.xml b/data/monster/Bosses/warlord ruzad.xml new file mode 100644 index 0000000000..bc1eb9f707 --- /dev/null +++ b/data/monster/Bosses/warlord ruzad.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/xenia.xml b/data/monster/Bosses/xenia.xml new file mode 100644 index 0000000000..5cb0c62bf7 --- /dev/null +++ b/data/monster/Bosses/xenia.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/yakchal.xml b/data/monster/Bosses/yakchal.xml new file mode 100644 index 0000000000..fcfbf5bd38 --- /dev/null +++ b/data/monster/Bosses/yakchal.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/zugurosh.xml b/data/monster/Bosses/zugurosh.xml new file mode 100644 index 0000000000..b7a6f68ca3 --- /dev/null +++ b/data/monster/Bosses/zugurosh.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Bosses/zulazza the corruptor.xml b/data/monster/Bosses/zulazza the corruptor.xml new file mode 100644 index 0000000000..f3deff9179 --- /dev/null +++ b/data/monster/Bosses/zulazza the corruptor.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Canines/crystal wolf.xml b/data/monster/Canines/crystal wolf.xml new file mode 100644 index 0000000000..ea125244fd --- /dev/null +++ b/data/monster/Canines/crystal wolf.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Canines/dog.xml b/data/monster/Canines/dog.xml new file mode 100644 index 0000000000..6895513da8 --- /dev/null +++ b/data/monster/Canines/dog.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Canines/gnarlhound.xml b/data/monster/Canines/gnarlhound.xml new file mode 100644 index 0000000000..52201574a2 --- /dev/null +++ b/data/monster/Canines/gnarlhound.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Canines/husky.xml b/data/monster/Canines/husky.xml new file mode 100644 index 0000000000..4b90e61630 --- /dev/null +++ b/data/monster/Canines/husky.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Canines/war wolf.xml b/data/monster/Canines/war wolf.xml new file mode 100644 index 0000000000..4ac7bf3abf --- /dev/null +++ b/data/monster/Canines/war wolf.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Canines/werewolf.xml b/data/monster/Canines/werewolf.xml new file mode 100644 index 0000000000..0572fbd35e --- /dev/null +++ b/data/monster/Canines/werewolf.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Canines/winter wolf.xml b/data/monster/Canines/winter wolf.xml new file mode 100644 index 0000000000..0c650acf2e --- /dev/null +++ b/data/monster/Canines/winter wolf.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Canines/wolf.xml b/data/monster/Canines/wolf.xml new file mode 100644 index 0000000000..4fbb8adacf --- /dev/null +++ b/data/monster/Canines/wolf.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Chakoyas/chakoya toolshaper.xml b/data/monster/Chakoyas/chakoya toolshaper.xml new file mode 100644 index 0000000000..48b93155dc --- /dev/null +++ b/data/monster/Chakoyas/chakoya toolshaper.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Chakoyas/chakoya tribewarden.xml b/data/monster/Chakoyas/chakoya tribewarden.xml new file mode 100644 index 0000000000..9475b13419 --- /dev/null +++ b/data/monster/Chakoyas/chakoya tribewarden.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Chakoyas/chakoya windcaller.xml b/data/monster/Chakoyas/chakoya windcaller.xml new file mode 100644 index 0000000000..9f40884a4a --- /dev/null +++ b/data/monster/Chakoyas/chakoya windcaller.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Crustaceans/blood crab.xml b/data/monster/Crustaceans/blood crab.xml new file mode 100644 index 0000000000..8ada57fa5b --- /dev/null +++ b/data/monster/Crustaceans/blood crab.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Crustaceans/crab.xml b/data/monster/Crustaceans/crab.xml new file mode 100644 index 0000000000..7e613e9084 --- /dev/null +++ b/data/monster/Crustaceans/crab.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Crustaceans/crustacea gigantica.xml b/data/monster/Crustaceans/crustacea gigantica.xml new file mode 100644 index 0000000000..0e5f734afa --- /dev/null +++ b/data/monster/Crustaceans/crustacea gigantica.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Cryo-Elementals/ice golem.xml b/data/monster/Cryo-Elementals/ice golem.xml new file mode 100644 index 0000000000..0faf30fab4 --- /dev/null +++ b/data/monster/Cryo-Elementals/ice golem.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Cultists/acolyte of the cult.xml b/data/monster/Cultists/acolyte of the cult.xml new file mode 100644 index 0000000000..befcc10340 --- /dev/null +++ b/data/monster/Cultists/acolyte of the cult.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Cultists/adept of the cult.xml b/data/monster/Cultists/adept of the cult.xml new file mode 100644 index 0000000000..e3f7529d39 --- /dev/null +++ b/data/monster/Cultists/adept of the cult.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Cultists/enlightened of the cult.xml b/data/monster/Cultists/enlightened of the cult.xml new file mode 100644 index 0000000000..5272a0169c --- /dev/null +++ b/data/monster/Cultists/enlightened of the cult.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Cultists/novice of the cult.xml b/data/monster/Cultists/novice of the cult.xml new file mode 100644 index 0000000000..30c8295a5b --- /dev/null +++ b/data/monster/Cultists/novice of the cult.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Demons/dark torturer.xml b/data/monster/Demons/dark torturer.xml new file mode 100644 index 0000000000..834cced3c0 --- /dev/null +++ b/data/monster/Demons/dark torturer.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Demons/demon.xml b/data/monster/Demons/demon.xml new file mode 100644 index 0000000000..446ae24bef --- /dev/null +++ b/data/monster/Demons/demon.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Demons/destroyer.xml b/data/monster/Demons/destroyer.xml new file mode 100644 index 0000000000..0614bc956e --- /dev/null +++ b/data/monster/Demons/destroyer.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Demons/diabolic imp.xml b/data/monster/Demons/diabolic imp.xml new file mode 100644 index 0000000000..54b1a0288e --- /dev/null +++ b/data/monster/Demons/diabolic imp.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Demons/fire devil.xml b/data/monster/Demons/fire devil.xml new file mode 100644 index 0000000000..98b5e5ae10 --- /dev/null +++ b/data/monster/Demons/fire devil.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Demons/fury.xml b/data/monster/Demons/fury.xml new file mode 100644 index 0000000000..703d6040d9 --- /dev/null +++ b/data/monster/Demons/fury.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Demons/gozzler.xml b/data/monster/Demons/gozzler.xml new file mode 100644 index 0000000000..1bfe23f154 --- /dev/null +++ b/data/monster/Demons/gozzler.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Demons/hand of cursed fate.xml b/data/monster/Demons/hand of cursed fate.xml new file mode 100644 index 0000000000..fd1fd2fe2f --- /dev/null +++ b/data/monster/Demons/hand of cursed fate.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Demons/hellhound.xml b/data/monster/Demons/hellhound.xml new file mode 100644 index 0000000000..75a369a95d --- /dev/null +++ b/data/monster/Demons/hellhound.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Demons/hellspawn.xml b/data/monster/Demons/hellspawn.xml new file mode 100644 index 0000000000..9f2269e48c --- /dev/null +++ b/data/monster/Demons/hellspawn.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Demons/juggernaut.xml b/data/monster/Demons/juggernaut.xml new file mode 100644 index 0000000000..9774b16fd7 --- /dev/null +++ b/data/monster/Demons/juggernaut.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Demons/nightmare scion.xml b/data/monster/Demons/nightmare scion.xml new file mode 100644 index 0000000000..82217f0927 --- /dev/null +++ b/data/monster/Demons/nightmare scion.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Demons/nightmare.xml b/data/monster/Demons/nightmare.xml new file mode 100644 index 0000000000..e00a1bf7d1 --- /dev/null +++ b/data/monster/Demons/nightmare.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Demons/nightstalker.xml b/data/monster/Demons/nightstalker.xml new file mode 100644 index 0000000000..4b8e950371 --- /dev/null +++ b/data/monster/Demons/nightstalker.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Demons/plaguesmith.xml b/data/monster/Demons/plaguesmith.xml new file mode 100644 index 0000000000..b7d423aba1 --- /dev/null +++ b/data/monster/Demons/plaguesmith.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Demons/shaburak demon.xml b/data/monster/Demons/shaburak demon.xml new file mode 100644 index 0000000000..03a1d26633 --- /dev/null +++ b/data/monster/Demons/shaburak demon.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Demons/shaburak prince.xml b/data/monster/Demons/shaburak prince.xml new file mode 100644 index 0000000000..ee7c7eb9f6 --- /dev/null +++ b/data/monster/Demons/shaburak prince.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Djinns/blue djinn.xml b/data/monster/Djinns/blue djinn.xml new file mode 100644 index 0000000000..5036f573d3 --- /dev/null +++ b/data/monster/Djinns/blue djinn.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Djinns/efreet.xml b/data/monster/Djinns/efreet.xml new file mode 100644 index 0000000000..2a2416f623 --- /dev/null +++ b/data/monster/Djinns/efreet.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Djinns/green djinn.xml b/data/monster/Djinns/green djinn.xml new file mode 100644 index 0000000000..ebdd7b0890 --- /dev/null +++ b/data/monster/Djinns/green djinn.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + > + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Djinns/marid.xml b/data/monster/Djinns/marid.xml new file mode 100644 index 0000000000..467c657571 --- /dev/null +++ b/data/monster/Djinns/marid.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Dragons/dragon hatchling.xml b/data/monster/Dragons/dragon hatchling.xml new file mode 100644 index 0000000000..e6fb3b0c08 --- /dev/null +++ b/data/monster/Dragons/dragon hatchling.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Dragons/dragon lord hatchling.xml b/data/monster/Dragons/dragon lord hatchling.xml new file mode 100644 index 0000000000..d8dd512b45 --- /dev/null +++ b/data/monster/Dragons/dragon lord hatchling.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Dragons/dragon lord.xml b/data/monster/Dragons/dragon lord.xml new file mode 100644 index 0000000000..bb994de617 --- /dev/null +++ b/data/monster/Dragons/dragon lord.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Dragons/dragon.xml b/data/monster/Dragons/dragon.xml new file mode 100644 index 0000000000..de46a03fbf --- /dev/null +++ b/data/monster/Dragons/dragon.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Dragons/draptor.xml b/data/monster/Dragons/draptor.xml new file mode 100644 index 0000000000..bd01112e99 --- /dev/null +++ b/data/monster/Dragons/draptor.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Dragons/frost dragon hatchling.xml b/data/monster/Dragons/frost dragon hatchling.xml new file mode 100644 index 0000000000..a017cda975 --- /dev/null +++ b/data/monster/Dragons/frost dragon hatchling.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Dragons/frost dragon.xml b/data/monster/Dragons/frost dragon.xml new file mode 100644 index 0000000000..b0f8959963 --- /dev/null +++ b/data/monster/Dragons/frost dragon.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Dragons/ghastly dragon.xml b/data/monster/Dragons/ghastly dragon.xml new file mode 100644 index 0000000000..0fd689eab0 --- /dev/null +++ b/data/monster/Dragons/ghastly dragon.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Dragons/undead dragon.xml b/data/monster/Dragons/undead dragon.xml new file mode 100644 index 0000000000..51d5b94f81 --- /dev/null +++ b/data/monster/Dragons/undead dragon.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Dragons/wyrm.xml b/data/monster/Dragons/wyrm.xml new file mode 100644 index 0000000000..4d2a167ab9 --- /dev/null +++ b/data/monster/Dragons/wyrm.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Dwarves/dwarf geomancer.xml b/data/monster/Dwarves/dwarf geomancer.xml new file mode 100644 index 0000000000..ddd8bd3666 --- /dev/null +++ b/data/monster/Dwarves/dwarf geomancer.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Dwarves/dwarf guard.xml b/data/monster/Dwarves/dwarf guard.xml new file mode 100644 index 0000000000..9b8a51888e --- /dev/null +++ b/data/monster/Dwarves/dwarf guard.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Dwarves/dwarf soldier.xml b/data/monster/Dwarves/dwarf soldier.xml new file mode 100644 index 0000000000..d3acabd4bd --- /dev/null +++ b/data/monster/Dwarves/dwarf soldier.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Dwarves/dwarf.xml b/data/monster/Dwarves/dwarf.xml new file mode 100644 index 0000000000..d63f79e472 --- /dev/null +++ b/data/monster/Dwarves/dwarf.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Dworcs/dworc fleshhunter.xml b/data/monster/Dworcs/dworc fleshhunter.xml new file mode 100644 index 0000000000..798682f517 --- /dev/null +++ b/data/monster/Dworcs/dworc fleshhunter.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Dworcs/dworc venomsniper.xml b/data/monster/Dworcs/dworc venomsniper.xml new file mode 100644 index 0000000000..a91186a220 --- /dev/null +++ b/data/monster/Dworcs/dworc venomsniper.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Dworcs/dworc voodoomaster.xml b/data/monster/Dworcs/dworc voodoomaster.xml new file mode 100644 index 0000000000..104aad6a9e --- /dev/null +++ b/data/monster/Dworcs/dworc voodoomaster.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Elephants/elephant.xml b/data/monster/Elephants/elephant.xml new file mode 100644 index 0000000000..8842ab6bc2 --- /dev/null +++ b/data/monster/Elephants/elephant.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Elephants/mammoth.xml b/data/monster/Elephants/mammoth.xml new file mode 100644 index 0000000000..50d61a1343 --- /dev/null +++ b/data/monster/Elephants/mammoth.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Elves/elf arcanist.xml b/data/monster/Elves/elf arcanist.xml new file mode 100644 index 0000000000..d091e3f19a --- /dev/null +++ b/data/monster/Elves/elf arcanist.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Elves/elf scout.xml b/data/monster/Elves/elf scout.xml new file mode 100644 index 0000000000..e98087162a --- /dev/null +++ b/data/monster/Elves/elf scout.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Elves/elf.xml b/data/monster/Elves/elf.xml new file mode 100644 index 0000000000..544abb0a47 --- /dev/null +++ b/data/monster/Elves/elf.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Energy-Elementals/charged energy elemental.xml b/data/monster/Energy-Elementals/charged energy elemental.xml new file mode 100644 index 0000000000..4603510248 --- /dev/null +++ b/data/monster/Energy-Elementals/charged energy elemental.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Energy-Elementals/energy elemental.xml b/data/monster/Energy-Elementals/energy elemental.xml new file mode 100644 index 0000000000..4fdc98d24f --- /dev/null +++ b/data/monster/Energy-Elementals/energy elemental.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Energy-Elementals/energy overlord.xml b/data/monster/Energy-Elementals/energy overlord.xml new file mode 100644 index 0000000000..8914ae7402 --- /dev/null +++ b/data/monster/Energy-Elementals/energy overlord.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Energy-Elementals/massive energy elemental.xml b/data/monster/Energy-Elementals/massive energy elemental.xml new file mode 100644 index 0000000000..29b8761898 --- /dev/null +++ b/data/monster/Energy-Elementals/massive energy elemental.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Energy-Elementals/overcharged energy elemental.xml b/data/monster/Energy-Elementals/overcharged energy elemental.xml new file mode 100644 index 0000000000..6dd7a10067 --- /dev/null +++ b/data/monster/Energy-Elementals/overcharged energy elemental.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Felines/cat.xml b/data/monster/Felines/cat.xml new file mode 100644 index 0000000000..a45e7f7a17 --- /dev/null +++ b/data/monster/Felines/cat.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Felines/lion.xml b/data/monster/Felines/lion.xml new file mode 100644 index 0000000000..52e6405c96 --- /dev/null +++ b/data/monster/Felines/lion.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Felines/midnight panther.xml b/data/monster/Felines/midnight panther.xml new file mode 100644 index 0000000000..eb4edaa29a --- /dev/null +++ b/data/monster/Felines/midnight panther.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Felines/tiger.xml b/data/monster/Felines/tiger.xml new file mode 100644 index 0000000000..5f9595cc02 --- /dev/null +++ b/data/monster/Felines/tiger.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Frogs/azure frog.xml b/data/monster/Frogs/azure frog.xml new file mode 100644 index 0000000000..a8f12db7c7 --- /dev/null +++ b/data/monster/Frogs/azure frog.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Frogs/coral frog.xml b/data/monster/Frogs/coral frog.xml new file mode 100644 index 0000000000..3150d1efbd --- /dev/null +++ b/data/monster/Frogs/coral frog.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Frogs/crimson frog.xml b/data/monster/Frogs/crimson frog.xml new file mode 100644 index 0000000000..a81e299064 --- /dev/null +++ b/data/monster/Frogs/crimson frog.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Frogs/green frog.xml b/data/monster/Frogs/green frog.xml new file mode 100644 index 0000000000..5cfaf094ca --- /dev/null +++ b/data/monster/Frogs/green frog.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Frogs/orchid frog.xml b/data/monster/Frogs/orchid frog.xml new file mode 100644 index 0000000000..b60ba119d0 --- /dev/null +++ b/data/monster/Frogs/orchid frog.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Frogs/toad.xml b/data/monster/Frogs/toad.xml new file mode 100644 index 0000000000..ce0010a5df --- /dev/null +++ b/data/monster/Frogs/toad.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Geo-Elementals/damaged worker golem.xml b/data/monster/Geo-Elementals/damaged worker golem.xml new file mode 100644 index 0000000000..e581f2423e --- /dev/null +++ b/data/monster/Geo-Elementals/damaged worker golem.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Geo-Elementals/earth elemental.xml b/data/monster/Geo-Elementals/earth elemental.xml new file mode 100644 index 0000000000..c59610c038 --- /dev/null +++ b/data/monster/Geo-Elementals/earth elemental.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Geo-Elementals/earth overlord.xml b/data/monster/Geo-Elementals/earth overlord.xml new file mode 100644 index 0000000000..3af09756dc --- /dev/null +++ b/data/monster/Geo-Elementals/earth overlord.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Geo-Elementals/gargoyle.xml b/data/monster/Geo-Elementals/gargoyle.xml new file mode 100644 index 0000000000..b72c35f2d5 --- /dev/null +++ b/data/monster/Geo-Elementals/gargoyle.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Geo-Elementals/jagged earth elemental.xml b/data/monster/Geo-Elementals/jagged earth elemental.xml new file mode 100644 index 0000000000..259bb105d9 --- /dev/null +++ b/data/monster/Geo-Elementals/jagged earth elemental.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Geo-Elementals/massive earth elemental.xml b/data/monster/Geo-Elementals/massive earth elemental.xml new file mode 100644 index 0000000000..a5782eb3bd --- /dev/null +++ b/data/monster/Geo-Elementals/massive earth elemental.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Geo-Elementals/medusa.xml b/data/monster/Geo-Elementals/medusa.xml new file mode 100644 index 0000000000..90030a36fe --- /dev/null +++ b/data/monster/Geo-Elementals/medusa.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Geo-Elementals/muddy earth elemental.xml b/data/monster/Geo-Elementals/muddy earth elemental.xml new file mode 100644 index 0000000000..b8db37cfab --- /dev/null +++ b/data/monster/Geo-Elementals/muddy earth elemental.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Geo-Elementals/stone golem.xml b/data/monster/Geo-Elementals/stone golem.xml new file mode 100644 index 0000000000..e4630460d1 --- /dev/null +++ b/data/monster/Geo-Elementals/stone golem.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Geo-Elementals/war golem.xml b/data/monster/Geo-Elementals/war golem.xml new file mode 100644 index 0000000000..bf96062dcb --- /dev/null +++ b/data/monster/Geo-Elementals/war golem.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Geo-Elementals/worker golem.xml b/data/monster/Geo-Elementals/worker golem.xml new file mode 100644 index 0000000000..d691347850 --- /dev/null +++ b/data/monster/Geo-Elementals/worker golem.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Ghosts/ghost.xml b/data/monster/Ghosts/ghost.xml new file mode 100644 index 0000000000..316775af58 --- /dev/null +++ b/data/monster/Ghosts/ghost.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Ghosts/phantasm summon.xml b/data/monster/Ghosts/phantasm summon.xml new file mode 100644 index 0000000000..06eeccd73a --- /dev/null +++ b/data/monster/Ghosts/phantasm summon.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Ghosts/phantasm.xml b/data/monster/Ghosts/phantasm.xml new file mode 100644 index 0000000000..1c54125d8d --- /dev/null +++ b/data/monster/Ghosts/phantasm.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Ghosts/spectre.xml b/data/monster/Ghosts/spectre.xml new file mode 100644 index 0000000000..7bdfcd915e --- /dev/null +++ b/data/monster/Ghosts/spectre.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Ghosts/wisp.xml b/data/monster/Ghosts/wisp.xml new file mode 100644 index 0000000000..ee72eaf68b --- /dev/null +++ b/data/monster/Ghosts/wisp.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Giants/behemoth.xml b/data/monster/Giants/behemoth.xml new file mode 100644 index 0000000000..70bf01107c --- /dev/null +++ b/data/monster/Giants/behemoth.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Giants/cyclops drone.xml b/data/monster/Giants/cyclops drone.xml new file mode 100644 index 0000000000..224ec0e00b --- /dev/null +++ b/data/monster/Giants/cyclops drone.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Giants/cyclops smith.xml b/data/monster/Giants/cyclops smith.xml new file mode 100644 index 0000000000..2f9c796c4f --- /dev/null +++ b/data/monster/Giants/cyclops smith.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Giants/cyclops.xml b/data/monster/Giants/cyclops.xml new file mode 100644 index 0000000000..c13078d532 --- /dev/null +++ b/data/monster/Giants/cyclops.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Giants/frost giant.xml b/data/monster/Giants/frost giant.xml new file mode 100644 index 0000000000..71f7719d84 --- /dev/null +++ b/data/monster/Giants/frost giant.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Giants/frost giantess.xml b/data/monster/Giants/frost giantess.xml new file mode 100644 index 0000000000..44dc31943a --- /dev/null +++ b/data/monster/Giants/frost giantess.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Giants/yeti.xml b/data/monster/Giants/yeti.xml new file mode 100644 index 0000000000..85cff78b48 --- /dev/null +++ b/data/monster/Giants/yeti.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Goblins/goblin assassin.xml b/data/monster/Goblins/goblin assassin.xml new file mode 100644 index 0000000000..e891fecf4d --- /dev/null +++ b/data/monster/Goblins/goblin assassin.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Goblins/goblin leader.xml b/data/monster/Goblins/goblin leader.xml new file mode 100644 index 0000000000..68f2ed1695 --- /dev/null +++ b/data/monster/Goblins/goblin leader.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Goblins/goblin scavenger.xml b/data/monster/Goblins/goblin scavenger.xml new file mode 100644 index 0000000000..650e1f7920 --- /dev/null +++ b/data/monster/Goblins/goblin scavenger.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Goblins/goblin.xml b/data/monster/Goblins/goblin.xml new file mode 100644 index 0000000000..de2bd39268 --- /dev/null +++ b/data/monster/Goblins/goblin.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Goblins/grynch clan goblin.xml b/data/monster/Goblins/grynch clan goblin.xml new file mode 100644 index 0000000000..119c0e5a29 --- /dev/null +++ b/data/monster/Goblins/grynch clan goblin.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Grunts/boar.xml b/data/monster/Grunts/boar.xml new file mode 100644 index 0000000000..79f2b2d1c5 --- /dev/null +++ b/data/monster/Grunts/boar.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Grunts/pig.xml b/data/monster/Grunts/pig.xml new file mode 100644 index 0000000000..89d5589fb0 --- /dev/null +++ b/data/monster/Grunts/pig.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Hydro-Elementals/ice overlord.xml b/data/monster/Hydro-Elementals/ice overlord.xml new file mode 100644 index 0000000000..71d615f308 --- /dev/null +++ b/data/monster/Hydro-Elementals/ice overlord.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Hydro-Elementals/massive water elemental.xml b/data/monster/Hydro-Elementals/massive water elemental.xml new file mode 100644 index 0000000000..9b425a7178 --- /dev/null +++ b/data/monster/Hydro-Elementals/massive water elemental.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Hydro-Elementals/roaring water elemental.xml b/data/monster/Hydro-Elementals/roaring water elemental.xml new file mode 100644 index 0000000000..501df489b9 --- /dev/null +++ b/data/monster/Hydro-Elementals/roaring water elemental.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Hydro-Elementals/slick water elemental.xml b/data/monster/Hydro-Elementals/slick water elemental.xml new file mode 100644 index 0000000000..9a041dd489 --- /dev/null +++ b/data/monster/Hydro-Elementals/slick water elemental.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Hydro-Elementals/water elemental.xml b/data/monster/Hydro-Elementals/water elemental.xml new file mode 100644 index 0000000000..7008e0bebf --- /dev/null +++ b/data/monster/Hydro-Elementals/water elemental.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Insects/ancient scarab.xml b/data/monster/Insects/ancient scarab.xml new file mode 100644 index 0000000000..babd580fbd --- /dev/null +++ b/data/monster/Insects/ancient scarab.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Insects/blue butterfly.xml b/data/monster/Insects/blue butterfly.xml new file mode 100644 index 0000000000..25ca293147 --- /dev/null +++ b/data/monster/Insects/blue butterfly.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Insects/brimstone bug.xml b/data/monster/Insects/brimstone bug.xml new file mode 100644 index 0000000000..940268c924 --- /dev/null +++ b/data/monster/Insects/brimstone bug.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Insects/bug.xml b/data/monster/Insects/bug.xml new file mode 100644 index 0000000000..3e05d8c476 --- /dev/null +++ b/data/monster/Insects/bug.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Insects/centipede.xml b/data/monster/Insects/centipede.xml new file mode 100644 index 0000000000..f35ea15cdc --- /dev/null +++ b/data/monster/Insects/centipede.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Insects/cockroach.xml b/data/monster/Insects/cockroach.xml new file mode 100644 index 0000000000..32f7da3593 --- /dev/null +++ b/data/monster/Insects/cockroach.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Insects/insect swarm.xml b/data/monster/Insects/insect swarm.xml new file mode 100644 index 0000000000..ae63fa6a3b --- /dev/null +++ b/data/monster/Insects/insect swarm.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Insects/insectoid scout.xml b/data/monster/Insects/insectoid scout.xml new file mode 100644 index 0000000000..6cfac95caa --- /dev/null +++ b/data/monster/Insects/insectoid scout.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Insects/lancer beetle.xml b/data/monster/Insects/lancer beetle.xml new file mode 100644 index 0000000000..9c47b72ce8 --- /dev/null +++ b/data/monster/Insects/lancer beetle.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Insects/larva.xml b/data/monster/Insects/larva.xml new file mode 100644 index 0000000000..14a6448a1f --- /dev/null +++ b/data/monster/Insects/larva.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Insects/purple butterfly.xml b/data/monster/Insects/purple butterfly.xml new file mode 100644 index 0000000000..efa88f386f --- /dev/null +++ b/data/monster/Insects/purple butterfly.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Insects/red butterfly.xml b/data/monster/Insects/red butterfly.xml new file mode 100644 index 0000000000..634e8f0b8c --- /dev/null +++ b/data/monster/Insects/red butterfly.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Insects/sandcrawler.xml b/data/monster/Insects/sandcrawler.xml new file mode 100644 index 0000000000..0f5cbd66dd --- /dev/null +++ b/data/monster/Insects/sandcrawler.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Insects/scarab.xml b/data/monster/Insects/scarab.xml new file mode 100644 index 0000000000..2435cbfad7 --- /dev/null +++ b/data/monster/Insects/scarab.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Insects/terramite.xml b/data/monster/Insects/terramite.xml new file mode 100644 index 0000000000..f92d08babb --- /dev/null +++ b/data/monster/Insects/terramite.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Insects/wasp.xml b/data/monster/Insects/wasp.xml new file mode 100644 index 0000000000..babf1624c9 --- /dev/null +++ b/data/monster/Insects/wasp.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Insects/yellow butterfly.xml b/data/monster/Insects/yellow butterfly.xml new file mode 100644 index 0000000000..8d278e7a0f --- /dev/null +++ b/data/monster/Insects/yellow butterfly.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Isle of Evil/berserker chicken.xml b/data/monster/Isle of Evil/berserker chicken.xml new file mode 100644 index 0000000000..ae04187b7c --- /dev/null +++ b/data/monster/Isle of Evil/berserker chicken.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Isle of Evil/boogey.xml b/data/monster/Isle of Evil/boogey.xml new file mode 100644 index 0000000000..5d4ed93e14 --- /dev/null +++ b/data/monster/Isle of Evil/boogey.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Isle of Evil/demon parrot.xml b/data/monster/Isle of Evil/demon parrot.xml new file mode 100644 index 0000000000..0aac9be9c5 --- /dev/null +++ b/data/monster/Isle of Evil/demon parrot.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Isle of Evil/dirtbeard.xml b/data/monster/Isle of Evil/dirtbeard.xml new file mode 100644 index 0000000000..b61407c4d8 --- /dev/null +++ b/data/monster/Isle of Evil/dirtbeard.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Isle of Evil/docter perhaps.xml b/data/monster/Isle of Evil/docter perhaps.xml new file mode 100644 index 0000000000..ff3f278ccd --- /dev/null +++ b/data/monster/Isle of Evil/docter perhaps.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Isle of Evil/doom deer.xml b/data/monster/Isle of Evil/doom deer.xml new file mode 100644 index 0000000000..c7087e9890 --- /dev/null +++ b/data/monster/Isle of Evil/doom deer.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Isle of Evil/evil mastermind.xml b/data/monster/Isle of Evil/evil mastermind.xml new file mode 100644 index 0000000000..1678592d04 --- /dev/null +++ b/data/monster/Isle of Evil/evil mastermind.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Isle of Evil/evil sheep lord.xml b/data/monster/Isle of Evil/evil sheep lord.xml new file mode 100644 index 0000000000..035f39fecd --- /dev/null +++ b/data/monster/Isle of Evil/evil sheep lord.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Isle of Evil/evil sheep.xml b/data/monster/Isle of Evil/evil sheep.xml new file mode 100644 index 0000000000..f1e8cff2e4 --- /dev/null +++ b/data/monster/Isle of Evil/evil sheep.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Isle of Evil/hot dog.xml b/data/monster/Isle of Evil/hot dog.xml new file mode 100644 index 0000000000..c8ce33ac0e --- /dev/null +++ b/data/monster/Isle of Evil/hot dog.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Isle of Evil/infernal frog.xml b/data/monster/Isle of Evil/infernal frog.xml new file mode 100644 index 0000000000..5df4a9c205 --- /dev/null +++ b/data/monster/Isle of Evil/infernal frog.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Isle of Evil/killer rabbit.xml b/data/monster/Isle of Evil/killer rabbit.xml new file mode 100644 index 0000000000..f0f1488314 --- /dev/null +++ b/data/monster/Isle of Evil/killer rabbit.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Isle of Evil/mephiles.xml b/data/monster/Isle of Evil/mephiles.xml new file mode 100644 index 0000000000..dd61b54a5d --- /dev/null +++ b/data/monster/Isle of Evil/mephiles.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Isle of Evil/monstor.xml b/data/monster/Isle of Evil/monstor.xml new file mode 100644 index 0000000000..9faf333d20 --- /dev/null +++ b/data/monster/Isle of Evil/monstor.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Isle of Evil/vampire pig.xml b/data/monster/Isle of Evil/vampire pig.xml new file mode 100644 index 0000000000..737f6a50b4 --- /dev/null +++ b/data/monster/Isle of Evil/vampire pig.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Lizards/battlemaster zunzu.xml b/data/monster/Lizards/battlemaster zunzu.xml new file mode 100644 index 0000000000..6899263c14 --- /dev/null +++ b/data/monster/Lizards/battlemaster zunzu.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Lizards/draken abomination.xml b/data/monster/Lizards/draken abomination.xml new file mode 100644 index 0000000000..9888082368 --- /dev/null +++ b/data/monster/Lizards/draken abomination.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Lizards/draken elite.xml b/data/monster/Lizards/draken elite.xml new file mode 100644 index 0000000000..16e196728b --- /dev/null +++ b/data/monster/Lizards/draken elite.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Lizards/draken spellweaver.xml b/data/monster/Lizards/draken spellweaver.xml new file mode 100644 index 0000000000..34cd9fc825 --- /dev/null +++ b/data/monster/Lizards/draken spellweaver.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Lizards/draken warmaster.xml b/data/monster/Lizards/draken warmaster.xml new file mode 100644 index 0000000000..f89a686888 --- /dev/null +++ b/data/monster/Lizards/draken warmaster.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Lizards/eternal guardian.xml b/data/monster/Lizards/eternal guardian.xml new file mode 100644 index 0000000000..2c8f7321c9 --- /dev/null +++ b/data/monster/Lizards/eternal guardian.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Lizards/lizard abomination.xml b/data/monster/Lizards/lizard abomination.xml new file mode 100644 index 0000000000..7af3fb9527 --- /dev/null +++ b/data/monster/Lizards/lizard abomination.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Lizards/lizard chosen.xml b/data/monster/Lizards/lizard chosen.xml new file mode 100644 index 0000000000..918ebad4c0 --- /dev/null +++ b/data/monster/Lizards/lizard chosen.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Lizards/lizard dragon priest.xml b/data/monster/Lizards/lizard dragon priest.xml new file mode 100644 index 0000000000..5ca7f2d79c --- /dev/null +++ b/data/monster/Lizards/lizard dragon priest.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Lizards/lizard high guard.xml b/data/monster/Lizards/lizard high guard.xml new file mode 100644 index 0000000000..370b463089 --- /dev/null +++ b/data/monster/Lizards/lizard high guard.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Lizards/lizard legionnaire.xml b/data/monster/Lizards/lizard legionnaire.xml new file mode 100644 index 0000000000..61857f4c82 --- /dev/null +++ b/data/monster/Lizards/lizard legionnaire.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Lizards/lizard sentinel.xml b/data/monster/Lizards/lizard sentinel.xml new file mode 100644 index 0000000000..3d6b2072ef --- /dev/null +++ b/data/monster/Lizards/lizard sentinel.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Lizards/lizard snakecharmer.xml b/data/monster/Lizards/lizard snakecharmer.xml new file mode 100644 index 0000000000..5a6283628a --- /dev/null +++ b/data/monster/Lizards/lizard snakecharmer.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Lizards/lizard templar.xml b/data/monster/Lizards/lizard templar.xml new file mode 100644 index 0000000000..9fe0cbd206 --- /dev/null +++ b/data/monster/Lizards/lizard templar.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Lizards/lizard zaogun.xml b/data/monster/Lizards/lizard zaogun.xml new file mode 100644 index 0000000000..918dcdd43f --- /dev/null +++ b/data/monster/Lizards/lizard zaogun.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Lizards/wyvern.xml b/data/monster/Lizards/wyvern.xml new file mode 100644 index 0000000000..13de52fa08 --- /dev/null +++ b/data/monster/Lizards/wyvern.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Minotaurs/minotaur archer.xml b/data/monster/Minotaurs/minotaur archer.xml new file mode 100644 index 0000000000..4a1ecb36bf --- /dev/null +++ b/data/monster/Minotaurs/minotaur archer.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Minotaurs/minotaur guard.xml b/data/monster/Minotaurs/minotaur guard.xml new file mode 100644 index 0000000000..390c77146f --- /dev/null +++ b/data/monster/Minotaurs/minotaur guard.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Minotaurs/minotaur mage.xml b/data/monster/Minotaurs/minotaur mage.xml new file mode 100644 index 0000000000..d3e6b299d0 --- /dev/null +++ b/data/monster/Minotaurs/minotaur mage.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Minotaurs/minotaur.xml b/data/monster/Minotaurs/minotaur.xml new file mode 100644 index 0000000000..d8539124ff --- /dev/null +++ b/data/monster/Minotaurs/minotaur.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Misc/badger.xml b/data/monster/Misc/badger.xml new file mode 100644 index 0000000000..2a6e78e99a --- /dev/null +++ b/data/monster/Misc/badger.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Misc/bat.xml b/data/monster/Misc/bat.xml new file mode 100644 index 0000000000..8c5c1efc72 --- /dev/null +++ b/data/monster/Misc/bat.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Misc/deer.xml b/data/monster/Misc/deer.xml new file mode 100644 index 0000000000..8f0371c5e2 --- /dev/null +++ b/data/monster/Misc/deer.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Misc/dromedary.xml b/data/monster/Misc/dromedary.xml new file mode 100644 index 0000000000..3c241efbc6 --- /dev/null +++ b/data/monster/Misc/dromedary.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Misc/hacker.xml b/data/monster/Misc/hacker.xml new file mode 100644 index 0000000000..3a847abff2 --- /dev/null +++ b/data/monster/Misc/hacker.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Misc/halloweenhare.xml b/data/monster/Misc/halloweenhare.xml new file mode 100644 index 0000000000..f96e1e0873 --- /dev/null +++ b/data/monster/Misc/halloweenhare.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Misc/horse(brown).xml b/data/monster/Misc/horse(brown).xml new file mode 100644 index 0000000000..61c08d12b9 --- /dev/null +++ b/data/monster/Misc/horse(brown).xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Misc/horse(fire).xml b/data/monster/Misc/horse(fire).xml new file mode 100644 index 0000000000..9328175380 --- /dev/null +++ b/data/monster/Misc/horse(fire).xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Misc/horse(grey).xml b/data/monster/Misc/horse(grey).xml new file mode 100644 index 0000000000..8b5928183e --- /dev/null +++ b/data/monster/Misc/horse(grey).xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Misc/hyaena.xml b/data/monster/Misc/hyaena.xml new file mode 100644 index 0000000000..cca04199d5 --- /dev/null +++ b/data/monster/Misc/hyaena.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Misc/rabbit.xml b/data/monster/Misc/rabbit.xml new file mode 100644 index 0000000000..b6e0fa72ee --- /dev/null +++ b/data/monster/Misc/rabbit.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Misc/silver rabbit.xml b/data/monster/Misc/silver rabbit.xml new file mode 100644 index 0000000000..f1a3be110c --- /dev/null +++ b/data/monster/Misc/silver rabbit.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Misc/skunk.xml b/data/monster/Misc/skunk.xml new file mode 100644 index 0000000000..255542e63a --- /dev/null +++ b/data/monster/Misc/skunk.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Misc/squirrel.xml b/data/monster/Misc/squirrel.xml new file mode 100644 index 0000000000..67d31defab --- /dev/null +++ b/data/monster/Misc/squirrel.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Misc/white deer.xml b/data/monster/Misc/white deer.xml new file mode 100644 index 0000000000..d933aec195 --- /dev/null +++ b/data/monster/Misc/white deer.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Misc/yalahari.xml b/data/monster/Misc/yalahari.xml new file mode 100644 index 0000000000..21eae309c0 --- /dev/null +++ b/data/monster/Misc/yalahari.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Monks/dark monk.xml b/data/monster/Monks/dark monk.xml new file mode 100644 index 0000000000..d98f02cf0c --- /dev/null +++ b/data/monster/Monks/dark monk.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Monks/monk.xml b/data/monster/Monks/monk.xml new file mode 100644 index 0000000000..db1b26ecfb --- /dev/null +++ b/data/monster/Monks/monk.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Mutated/mutated bat.xml b/data/monster/Mutated/mutated bat.xml new file mode 100644 index 0000000000..1374d79d42 --- /dev/null +++ b/data/monster/Mutated/mutated bat.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Mutated/mutated human.xml b/data/monster/Mutated/mutated human.xml new file mode 100644 index 0000000000..1389ecb43d --- /dev/null +++ b/data/monster/Mutated/mutated human.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Mutated/mutated rat.xml b/data/monster/Mutated/mutated rat.xml new file mode 100644 index 0000000000..d57d1ce13a --- /dev/null +++ b/data/monster/Mutated/mutated rat.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Mutated/mutated tiger.xml b/data/monster/Mutated/mutated tiger.xml new file mode 100644 index 0000000000..8cfbec8e2c --- /dev/null +++ b/data/monster/Mutated/mutated tiger.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Necromancers/necromancer.xml b/data/monster/Necromancers/necromancer.xml new file mode 100644 index 0000000000..fbd21f73c8 --- /dev/null +++ b/data/monster/Necromancers/necromancer.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Necromancers/priestess.xml b/data/monster/Necromancers/priestess.xml new file mode 100644 index 0000000000..3c145a3a57 --- /dev/null +++ b/data/monster/Necromancers/priestess.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Orcs/orc berserker.xml b/data/monster/Orcs/orc berserker.xml new file mode 100644 index 0000000000..9fa430ec86 --- /dev/null +++ b/data/monster/Orcs/orc berserker.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Orcs/orc leader.xml b/data/monster/Orcs/orc leader.xml new file mode 100644 index 0000000000..823c232543 --- /dev/null +++ b/data/monster/Orcs/orc leader.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Orcs/orc marauder.xml b/data/monster/Orcs/orc marauder.xml new file mode 100644 index 0000000000..e73c479ef0 --- /dev/null +++ b/data/monster/Orcs/orc marauder.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Orcs/orc rider.xml b/data/monster/Orcs/orc rider.xml new file mode 100644 index 0000000000..12ded37a9e --- /dev/null +++ b/data/monster/Orcs/orc rider.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Orcs/orc shaman.xml b/data/monster/Orcs/orc shaman.xml new file mode 100644 index 0000000000..2c4693a316 --- /dev/null +++ b/data/monster/Orcs/orc shaman.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Orcs/orc spearman.xml b/data/monster/Orcs/orc spearman.xml new file mode 100644 index 0000000000..c39421b9b0 --- /dev/null +++ b/data/monster/Orcs/orc spearman.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Orcs/orc warlord.xml b/data/monster/Orcs/orc warlord.xml new file mode 100644 index 0000000000..e374557df9 --- /dev/null +++ b/data/monster/Orcs/orc warlord.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Orcs/orc warrior.xml b/data/monster/Orcs/orc warrior.xml new file mode 100644 index 0000000000..951ef8cd14 --- /dev/null +++ b/data/monster/Orcs/orc warrior.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Orcs/orc.xml b/data/monster/Orcs/orc.xml new file mode 100644 index 0000000000..ca96640d67 --- /dev/null +++ b/data/monster/Orcs/orc.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Outlaws/assassin.xml b/data/monster/Outlaws/assassin.xml new file mode 100644 index 0000000000..f395882c66 --- /dev/null +++ b/data/monster/Outlaws/assassin.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Outlaws/bandit.xml b/data/monster/Outlaws/bandit.xml new file mode 100644 index 0000000000..18fe71e919 --- /dev/null +++ b/data/monster/Outlaws/bandit.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Outlaws/black knight.xml b/data/monster/Outlaws/black knight.xml new file mode 100644 index 0000000000..5d772720af --- /dev/null +++ b/data/monster/Outlaws/black knight.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Outlaws/crazed beggar.xml b/data/monster/Outlaws/crazed beggar.xml new file mode 100644 index 0000000000..600984ee15 --- /dev/null +++ b/data/monster/Outlaws/crazed beggar.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Outlaws/feverish citizen.xml b/data/monster/Outlaws/feverish citizen.xml new file mode 100644 index 0000000000..e7b860fcb8 --- /dev/null +++ b/data/monster/Outlaws/feverish citizen.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Outlaws/gang member.xml b/data/monster/Outlaws/gang member.xml new file mode 100644 index 0000000000..e602b2f7ef --- /dev/null +++ b/data/monster/Outlaws/gang member.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Outlaws/gladiator.xml b/data/monster/Outlaws/gladiator.xml new file mode 100644 index 0000000000..e1d3f6b5ca --- /dev/null +++ b/data/monster/Outlaws/gladiator.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Outlaws/hero.xml b/data/monster/Outlaws/hero.xml new file mode 100644 index 0000000000..365811ec7f --- /dev/null +++ b/data/monster/Outlaws/hero.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Outlaws/hunter.xml b/data/monster/Outlaws/hunter.xml new file mode 100644 index 0000000000..d6c5bb46b9 --- /dev/null +++ b/data/monster/Outlaws/hunter.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Outlaws/nomad.xml b/data/monster/Outlaws/nomad.xml new file mode 100644 index 0000000000..d669d3db3d --- /dev/null +++ b/data/monster/Outlaws/nomad.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Outlaws/poacher.xml b/data/monster/Outlaws/poacher.xml new file mode 100644 index 0000000000..30be35c672 --- /dev/null +++ b/data/monster/Outlaws/poacher.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Outlaws/primitive.xml b/data/monster/Outlaws/primitive.xml new file mode 100644 index 0000000000..16872de590 --- /dev/null +++ b/data/monster/Outlaws/primitive.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Outlaws/smuggler.xml b/data/monster/Outlaws/smuggler.xml new file mode 100644 index 0000000000..388bf7bb41 --- /dev/null +++ b/data/monster/Outlaws/smuggler.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Outlaws/stalker.xml b/data/monster/Outlaws/stalker.xml new file mode 100644 index 0000000000..77fb1cfbaf --- /dev/null +++ b/data/monster/Outlaws/stalker.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Outlaws/wild warrior.xml b/data/monster/Outlaws/wild warrior.xml new file mode 100644 index 0000000000..568e403224 --- /dev/null +++ b/data/monster/Outlaws/wild warrior.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Pharaohs/ashmunrah.xml b/data/monster/Pharaohs/ashmunrah.xml new file mode 100644 index 0000000000..31c031e49c --- /dev/null +++ b/data/monster/Pharaohs/ashmunrah.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Pharaohs/dipthrah.xml b/data/monster/Pharaohs/dipthrah.xml new file mode 100644 index 0000000000..4735d1eeaf --- /dev/null +++ b/data/monster/Pharaohs/dipthrah.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Pharaohs/mahrdis.xml b/data/monster/Pharaohs/mahrdis.xml new file mode 100644 index 0000000000..fb6b76f7d8 --- /dev/null +++ b/data/monster/Pharaohs/mahrdis.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Pharaohs/morguthis.xml b/data/monster/Pharaohs/morguthis.xml new file mode 100644 index 0000000000..48bd49b9f9 --- /dev/null +++ b/data/monster/Pharaohs/morguthis.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Pharaohs/omruc.xml b/data/monster/Pharaohs/omruc.xml new file mode 100644 index 0000000000..2372c1268b --- /dev/null +++ b/data/monster/Pharaohs/omruc.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Pharaohs/rahemos.xml b/data/monster/Pharaohs/rahemos.xml new file mode 100644 index 0000000000..ea01485552 --- /dev/null +++ b/data/monster/Pharaohs/rahemos.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Pharaohs/thalas.xml b/data/monster/Pharaohs/thalas.xml new file mode 100644 index 0000000000..f1b2ad4fb2 --- /dev/null +++ b/data/monster/Pharaohs/thalas.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Pharaohs/vashresamun.xml b/data/monster/Pharaohs/vashresamun.xml new file mode 100644 index 0000000000..b6f49a5156 --- /dev/null +++ b/data/monster/Pharaohs/vashresamun.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Pirates/pirate buccaneer.xml b/data/monster/Pirates/pirate buccaneer.xml new file mode 100644 index 0000000000..883cc7406b --- /dev/null +++ b/data/monster/Pirates/pirate buccaneer.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Pirates/pirate corsair.xml b/data/monster/Pirates/pirate corsair.xml new file mode 100644 index 0000000000..3e94ecf76c --- /dev/null +++ b/data/monster/Pirates/pirate corsair.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Pirates/pirate cutthroat.xml b/data/monster/Pirates/pirate cutthroat.xml new file mode 100644 index 0000000000..91884274cf --- /dev/null +++ b/data/monster/Pirates/pirate cutthroat.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Pirates/pirate ghost.xml b/data/monster/Pirates/pirate ghost.xml new file mode 100644 index 0000000000..bdf993d9c3 --- /dev/null +++ b/data/monster/Pirates/pirate ghost.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Pirates/pirate marauder.xml b/data/monster/Pirates/pirate marauder.xml new file mode 100644 index 0000000000..3e3b58d027 --- /dev/null +++ b/data/monster/Pirates/pirate marauder.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Pirates/pirate skeleton.xml b/data/monster/Pirates/pirate skeleton.xml new file mode 100644 index 0000000000..1f27da5352 --- /dev/null +++ b/data/monster/Pirates/pirate skeleton.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Pyro-Elementals/blazing fire elemental.xml b/data/monster/Pyro-Elementals/blazing fire elemental.xml new file mode 100644 index 0000000000..b3baf43654 --- /dev/null +++ b/data/monster/Pyro-Elementals/blazing fire elemental.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Pyro-Elementals/blistering fire elemental.xml b/data/monster/Pyro-Elementals/blistering fire elemental.xml new file mode 100644 index 0000000000..9e03dc44b3 --- /dev/null +++ b/data/monster/Pyro-Elementals/blistering fire elemental.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Pyro-Elementals/fire elemental.xml b/data/monster/Pyro-Elementals/fire elemental.xml new file mode 100644 index 0000000000..6c3f7b33e8 --- /dev/null +++ b/data/monster/Pyro-Elementals/fire elemental.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Pyro-Elementals/fire overlord.xml b/data/monster/Pyro-Elementals/fire overlord.xml new file mode 100644 index 0000000000..51329cc397 --- /dev/null +++ b/data/monster/Pyro-Elementals/fire overlord.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Pyro-Elementals/hellfire fighter.xml b/data/monster/Pyro-Elementals/hellfire fighter.xml new file mode 100644 index 0000000000..84b5d4bf26 --- /dev/null +++ b/data/monster/Pyro-Elementals/hellfire fighter.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Pyro-Elementals/massive fire elemental.xml b/data/monster/Pyro-Elementals/massive fire elemental.xml new file mode 100644 index 0000000000..f680bda4cf --- /dev/null +++ b/data/monster/Pyro-Elementals/massive fire elemental.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Quaras/quara constrictor scout.xml b/data/monster/Quaras/quara constrictor scout.xml new file mode 100644 index 0000000000..62bc4c204f --- /dev/null +++ b/data/monster/Quaras/quara constrictor scout.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Quaras/quara constrictor.xml b/data/monster/Quaras/quara constrictor.xml new file mode 100644 index 0000000000..4f6570db3e --- /dev/null +++ b/data/monster/Quaras/quara constrictor.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Quaras/quara hydromancer scout.xml b/data/monster/Quaras/quara hydromancer scout.xml new file mode 100644 index 0000000000..871f49138d --- /dev/null +++ b/data/monster/Quaras/quara hydromancer scout.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Quaras/quara hydromancer.xml b/data/monster/Quaras/quara hydromancer.xml new file mode 100644 index 0000000000..e64779603a --- /dev/null +++ b/data/monster/Quaras/quara hydromancer.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Quaras/quara mantassin scout.xml b/data/monster/Quaras/quara mantassin scout.xml new file mode 100644 index 0000000000..a2cae7c6a3 --- /dev/null +++ b/data/monster/Quaras/quara mantassin scout.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Quaras/quara mantassin.xml b/data/monster/Quaras/quara mantassin.xml new file mode 100644 index 0000000000..1e0ba1f83a --- /dev/null +++ b/data/monster/Quaras/quara mantassin.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Quaras/quara pincher scout.xml b/data/monster/Quaras/quara pincher scout.xml new file mode 100644 index 0000000000..62f93a512e --- /dev/null +++ b/data/monster/Quaras/quara pincher scout.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Quaras/quara pincher.xml b/data/monster/Quaras/quara pincher.xml new file mode 100644 index 0000000000..ff7920d13b --- /dev/null +++ b/data/monster/Quaras/quara pincher.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Quaras/quara predator scout.xml b/data/monster/Quaras/quara predator scout.xml new file mode 100644 index 0000000000..d329211311 --- /dev/null +++ b/data/monster/Quaras/quara predator scout.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Quaras/quara predator.xml b/data/monster/Quaras/quara predator.xml new file mode 100644 index 0000000000..80955447fe --- /dev/null +++ b/data/monster/Quaras/quara predator.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Rats/cave rat.xml b/data/monster/Rats/cave rat.xml new file mode 100644 index 0000000000..fbb2987d38 --- /dev/null +++ b/data/monster/Rats/cave rat.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Rats/rat.xml b/data/monster/Rats/rat.xml new file mode 100644 index 0000000000..0bcfea1735 --- /dev/null +++ b/data/monster/Rats/rat.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Reptiles/crocodile.xml b/data/monster/Reptiles/crocodile.xml new file mode 100644 index 0000000000..14b75ad790 --- /dev/null +++ b/data/monster/Reptiles/crocodile.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Reptiles/hydra.xml b/data/monster/Reptiles/hydra.xml new file mode 100644 index 0000000000..cbf840a083 --- /dev/null +++ b/data/monster/Reptiles/hydra.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Reptiles/killer caiman.xml b/data/monster/Reptiles/killer caiman.xml new file mode 100644 index 0000000000..7d71e62e0a --- /dev/null +++ b/data/monster/Reptiles/killer caiman.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Rifts/rift brood.xml b/data/monster/Rifts/rift brood.xml new file mode 100644 index 0000000000..ea50daa96e --- /dev/null +++ b/data/monster/Rifts/rift brood.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Rifts/rift scythe.xml b/data/monster/Rifts/rift scythe.xml new file mode 100644 index 0000000000..51007d6513 --- /dev/null +++ b/data/monster/Rifts/rift scythe.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Rifts/rift worm.xml b/data/monster/Rifts/rift worm.xml new file mode 100644 index 0000000000..f60552674d --- /dev/null +++ b/data/monster/Rifts/rift worm.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Serpents/cobra.xml b/data/monster/Serpents/cobra.xml new file mode 100644 index 0000000000..13e35ac7e8 --- /dev/null +++ b/data/monster/Serpents/cobra.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Serpents/deepling scout.xml b/data/monster/Serpents/deepling scout.xml new file mode 100644 index 0000000000..ba55c19773 --- /dev/null +++ b/data/monster/Serpents/deepling scout.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Serpents/sea serpent.xml b/data/monster/Serpents/sea serpent.xml new file mode 100644 index 0000000000..28b222fc2d --- /dev/null +++ b/data/monster/Serpents/sea serpent.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Serpents/serpent spawn.xml b/data/monster/Serpents/serpent spawn.xml new file mode 100644 index 0000000000..54ca1f3057 --- /dev/null +++ b/data/monster/Serpents/serpent spawn.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Serpents/snake.xml b/data/monster/Serpents/snake.xml new file mode 100644 index 0000000000..4b765f5976 --- /dev/null +++ b/data/monster/Serpents/snake.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Serpents/young sea serpent.xml b/data/monster/Serpents/young sea serpent.xml new file mode 100644 index 0000000000..dca585f60b --- /dev/null +++ b/data/monster/Serpents/young sea serpent.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Shapeshifters/mimic.xml b/data/monster/Shapeshifters/mimic.xml new file mode 100644 index 0000000000..f4a7303f74 --- /dev/null +++ b/data/monster/Shapeshifters/mimic.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Sheeps/black sheep.xml b/data/monster/Sheeps/black sheep.xml new file mode 100644 index 0000000000..73e249e42c --- /dev/null +++ b/data/monster/Sheeps/black sheep.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Sheeps/sheep.xml b/data/monster/Sheeps/sheep.xml new file mode 100644 index 0000000000..398f153714 --- /dev/null +++ b/data/monster/Sheeps/sheep.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Skeletons/betrayed wraith.xml b/data/monster/Skeletons/betrayed wraith.xml new file mode 100644 index 0000000000..9ececff723 --- /dev/null +++ b/data/monster/Skeletons/betrayed wraith.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Skeletons/bonebeast.xml b/data/monster/Skeletons/bonebeast.xml new file mode 100644 index 0000000000..74504fb31c --- /dev/null +++ b/data/monster/Skeletons/bonebeast.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Skeletons/demon skeleton.xml b/data/monster/Skeletons/demon skeleton.xml new file mode 100644 index 0000000000..5c04314303 --- /dev/null +++ b/data/monster/Skeletons/demon skeleton.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Skeletons/dreadbeast.xml b/data/monster/Skeletons/dreadbeast.xml new file mode 100644 index 0000000000..065a111467 --- /dev/null +++ b/data/monster/Skeletons/dreadbeast.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Skeletons/lost soul.xml b/data/monster/Skeletons/lost soul.xml new file mode 100644 index 0000000000..d95ed7a4b5 --- /dev/null +++ b/data/monster/Skeletons/lost soul.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Skeletons/skeleton warrior.xml b/data/monster/Skeletons/skeleton warrior.xml new file mode 100644 index 0000000000..828c43bc86 --- /dev/null +++ b/data/monster/Skeletons/skeleton warrior.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Skeletons/skeleton.xml b/data/monster/Skeletons/skeleton.xml new file mode 100644 index 0000000000..b0fa19148f --- /dev/null +++ b/data/monster/Skeletons/skeleton.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Skeletons/undead gladiator.xml b/data/monster/Skeletons/undead gladiator.xml new file mode 100644 index 0000000000..e95a075519 --- /dev/null +++ b/data/monster/Skeletons/undead gladiator.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Sorcerers/dark apprentice.xml b/data/monster/Sorcerers/dark apprentice.xml new file mode 100644 index 0000000000..d9047579aa --- /dev/null +++ b/data/monster/Sorcerers/dark apprentice.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Sorcerers/dark magician.xml b/data/monster/Sorcerers/dark magician.xml new file mode 100644 index 0000000000..2541997986 --- /dev/null +++ b/data/monster/Sorcerers/dark magician.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Sorcerers/ice witch.xml b/data/monster/Sorcerers/ice witch.xml new file mode 100644 index 0000000000..10294cad9d --- /dev/null +++ b/data/monster/Sorcerers/ice witch.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Sorcerers/infernalist.xml b/data/monster/Sorcerers/infernalist.xml new file mode 100644 index 0000000000..1e48d86455 --- /dev/null +++ b/data/monster/Sorcerers/infernalist.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Sorcerers/mad mage.xml b/data/monster/Sorcerers/mad mage.xml new file mode 100644 index 0000000000..2c56d96223 --- /dev/null +++ b/data/monster/Sorcerers/mad mage.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + item id="7895" chance="800"/> + + diff --git a/data/monster/Sorcerers/mad scientist.xml b/data/monster/Sorcerers/mad scientist.xml new file mode 100644 index 0000000000..189f9891c9 --- /dev/null +++ b/data/monster/Sorcerers/mad scientist.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Sorcerers/warlock.xml b/data/monster/Sorcerers/warlock.xml new file mode 100644 index 0000000000..52879f9265 --- /dev/null +++ b/data/monster/Sorcerers/warlock.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Sorcerers/witch.xml b/data/monster/Sorcerers/witch.xml new file mode 100644 index 0000000000..1fb1730d7e --- /dev/null +++ b/data/monster/Sorcerers/witch.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Tortoises/thornback tortoise.xml b/data/monster/Tortoises/thornback tortoise.xml new file mode 100644 index 0000000000..38e58265b2 --- /dev/null +++ b/data/monster/Tortoises/thornback tortoise.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Tortoises/tortoise.xml b/data/monster/Tortoises/tortoise.xml new file mode 100644 index 0000000000..82a7144c8c --- /dev/null +++ b/data/monster/Tortoises/tortoise.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Traps/deathslicer.xml b/data/monster/Traps/deathslicer.xml new file mode 100644 index 0000000000..27fcc97396 --- /dev/null +++ b/data/monster/Traps/deathslicer.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Traps/eye of the seven.xml b/data/monster/Traps/eye of the seven.xml new file mode 100644 index 0000000000..50f82d61f3 --- /dev/null +++ b/data/monster/Traps/eye of the seven.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Traps/flamethrower.xml b/data/monster/Traps/flamethrower.xml new file mode 100644 index 0000000000..51e09fc99b --- /dev/null +++ b/data/monster/Traps/flamethrower.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Traps/lavahole.xml b/data/monster/Traps/lavahole.xml new file mode 100644 index 0000000000..f222fd300b --- /dev/null +++ b/data/monster/Traps/lavahole.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Traps/magicthrower.xml b/data/monster/Traps/magicthrower.xml new file mode 100644 index 0000000000..e2cc0dee20 --- /dev/null +++ b/data/monster/Traps/magicthrower.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Traps/plaguethrower.xml b/data/monster/Traps/plaguethrower.xml new file mode 100644 index 0000000000..107bcc3faf --- /dev/null +++ b/data/monster/Traps/plaguethrower.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Traps/shredderthrower.xml b/data/monster/Traps/shredderthrower.xml new file mode 100644 index 0000000000..ac6e72f615 --- /dev/null +++ b/data/monster/Traps/shredderthrower.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Trolls/frost troll.xml b/data/monster/Trolls/frost troll.xml new file mode 100644 index 0000000000..9bcf3ea0c0 --- /dev/null +++ b/data/monster/Trolls/frost troll.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Trolls/island troll.xml b/data/monster/Trolls/island troll.xml new file mode 100644 index 0000000000..6c788354aa --- /dev/null +++ b/data/monster/Trolls/island troll.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Trolls/swamp troll.xml b/data/monster/Trolls/swamp troll.xml new file mode 100644 index 0000000000..a5c0266db4 --- /dev/null +++ b/data/monster/Trolls/swamp troll.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Trolls/troll champion.xml b/data/monster/Trolls/troll champion.xml new file mode 100644 index 0000000000..31f1b3b66c --- /dev/null +++ b/data/monster/Trolls/troll champion.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Trolls/troll.xml b/data/monster/Trolls/troll.xml new file mode 100644 index 0000000000..c5c65c83c8 --- /dev/null +++ b/data/monster/Trolls/troll.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Undead Humanoids/banshee.xml b/data/monster/Undead Humanoids/banshee.xml new file mode 100644 index 0000000000..61675b230e --- /dev/null +++ b/data/monster/Undead Humanoids/banshee.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Undead Humanoids/blightwalker.xml b/data/monster/Undead Humanoids/blightwalker.xml new file mode 100644 index 0000000000..37d2ced624 --- /dev/null +++ b/data/monster/Undead Humanoids/blightwalker.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Undead Humanoids/crypt shambler.xml b/data/monster/Undead Humanoids/crypt shambler.xml new file mode 100644 index 0000000000..cae1e52b07 --- /dev/null +++ b/data/monster/Undead Humanoids/crypt shambler.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Undead Humanoids/ghoul.xml b/data/monster/Undead Humanoids/ghoul.xml new file mode 100644 index 0000000000..c43d15a5cf --- /dev/null +++ b/data/monster/Undead Humanoids/ghoul.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Undead Humanoids/grim reaper.xml b/data/monster/Undead Humanoids/grim reaper.xml new file mode 100644 index 0000000000..643e2dd10c --- /dev/null +++ b/data/monster/Undead Humanoids/grim reaper.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Undead Humanoids/lich.xml b/data/monster/Undead Humanoids/lich.xml new file mode 100644 index 0000000000..74be8fc3f4 --- /dev/null +++ b/data/monster/Undead Humanoids/lich.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Undead Humanoids/mummy.xml b/data/monster/Undead Humanoids/mummy.xml new file mode 100644 index 0000000000..dde2bdc433 --- /dev/null +++ b/data/monster/Undead Humanoids/mummy.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Undead Humanoids/souleater.xml b/data/monster/Undead Humanoids/souleater.xml new file mode 100644 index 0000000000..d5a4b5a0fa --- /dev/null +++ b/data/monster/Undead Humanoids/souleater.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Undead Humanoids/tomb servant.xml b/data/monster/Undead Humanoids/tomb servant.xml new file mode 100644 index 0000000000..41f474f422 --- /dev/null +++ b/data/monster/Undead Humanoids/tomb servant.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/Undead Humanoids/undead mine worker.xml b/data/monster/Undead Humanoids/undead mine worker.xml new file mode 100644 index 0000000000..c2871f7fe8 --- /dev/null +++ b/data/monster/Undead Humanoids/undead mine worker.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Undead Humanoids/undead prospector.xml b/data/monster/Undead Humanoids/undead prospector.xml new file mode 100644 index 0000000000..4a9a9197e1 --- /dev/null +++ b/data/monster/Undead Humanoids/undead prospector.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Undead Humanoids/vampire bride.xml b/data/monster/Undead Humanoids/vampire bride.xml new file mode 100644 index 0000000000..942bfe5be3 --- /dev/null +++ b/data/monster/Undead Humanoids/vampire bride.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Undead Humanoids/vampire.xml b/data/monster/Undead Humanoids/vampire.xml new file mode 100644 index 0000000000..d3126961ec --- /dev/null +++ b/data/monster/Undead Humanoids/vampire.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/Undead Humanoids/zombie.xml b/data/monster/Undead Humanoids/zombie.xml new file mode 100644 index 0000000000..1df432c1d2 --- /dev/null +++ b/data/monster/Undead Humanoids/zombie.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters.xml b/data/monster/monsters.xml new file mode 100644 index 0000000000..e460836e40 --- /dev/null +++ b/data/monster/monsters.xml @@ -0,0 +1,653 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/movements/lib/movements.lua b/data/movements/lib/movements.lua new file mode 100644 index 0000000000..b081a0ffed --- /dev/null +++ b/data/movements/lib/movements.lua @@ -0,0 +1,2 @@ +-- Nothing -- + diff --git a/data/movements/movements.xml b/data/movements/movements.xml new file mode 100644 index 0000000000..a7bee1f93c --- /dev/null +++ b/data/movements/movements.xml @@ -0,0 +1,978 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/movements/scripts/citizen.lua b/data/movements/scripts/citizen.lua new file mode 100644 index 0000000000..244e8e06b0 --- /dev/null +++ b/data/movements/scripts/citizen.lua @@ -0,0 +1,6 @@ +function onStepIn(cid, item, position, fromPosition) + if item.actionid > 30020 and item.actionid < 30100 then + doPlayerSetTown(cid, item.actionid - 30020) + end + return TRUE +end \ No newline at end of file diff --git a/data/movements/scripts/closingdoor.lua b/data/movements/scripts/closingdoor.lua new file mode 100644 index 0000000000..f8d914e19c --- /dev/null +++ b/data/movements/scripts/closingdoor.lua @@ -0,0 +1,26 @@ +function onStepOut(cid, item, position, fromPosition) + local newPosition = {x = position.x, y = position.y, z = position.z} + if isInArray(verticalOpenDoors, item.itemid) == TRUE then + newPosition.x = newPosition.x + 1 + else + newPosition.y = newPosition.y + 1 + end + doRelocate(position, newPosition) + + local tmpPos = {x = position.x, y = position.y, z = position.z, stackpos = -1} + local tileCount = getTileThingByPos(tmpPos) + local i = 1 + local tmpItem = {uid = 1} + while(tmpItem.uid ~= 0 and i < tileCount) do + tmpPos.stackpos = i + tmpItem = getTileThingByPos(tmpPos) + if tmpItem.uid ~= item.uid and tmpItem.uid ~= 0 and isMoveable(tmpItem.uid) == TRUE then + doRemoveItem(tmpItem.uid) + else + i = i + 1 + end + end + + doTransformItem(item.uid, item.itemid - 1) + return TRUE +end \ No newline at end of file diff --git a/data/movements/scripts/decay.lua b/data/movements/scripts/decay.lua new file mode 100644 index 0000000000..b078bd8c97 --- /dev/null +++ b/data/movements/scripts/decay.lua @@ -0,0 +1,5 @@ +function onStepIn(cid, item, position, fromPosition) + doTransformItem(item.uid, item.itemid + 1) + doDecayItem(item.uid) + return TRUE +end \ No newline at end of file diff --git a/data/movements/scripts/dough.lua b/data/movements/scripts/dough.lua new file mode 100644 index 0000000000..c1e772e214 --- /dev/null +++ b/data/movements/scripts/dough.lua @@ -0,0 +1,7 @@ +function onAddItem(moveitem, tileitem, position) + if moveitem.itemid == 2693 then + doTransformItem(moveitem.uid, 2689) + doSendMagicEffect(position, CONST_ME_HITBYFIRE) + end + return TRUE +end \ No newline at end of file diff --git a/data/movements/scripts/drowning.lua b/data/movements/scripts/drowning.lua new file mode 100644 index 0000000000..e8f23d9963 --- /dev/null +++ b/data/movements/scripts/drowning.lua @@ -0,0 +1,14 @@ +local condition = createConditionObject(CONDITION_DROWN) +setConditionParam(condition, CONDITION_PARAM_PERIODICDAMAGE, -20) +setConditionParam(condition, CONDITION_PARAM_TICKS, -1) +setConditionParam(condition, CONDITION_PARAM_TICKINTERVAL, 2000) + +function onStepIn(cid, item, pos) + if isPlayer(cid) == TRUE then + doAddCondition(cid, condition) + end +end + +function onStepOut(cid, item, pos) + doRemoveCondition(cid, CONDITION_DROWN) +end \ No newline at end of file diff --git a/data/movements/scripts/snow.lua b/data/movements/scripts/snow.lua new file mode 100644 index 0000000000..545d7d1a28 --- /dev/null +++ b/data/movements/scripts/snow.lua @@ -0,0 +1,11 @@ +function onStepOut(cid, item, position, fromPosition) + if isPlayer(cid) and not isPlayerGhost(cid) then + if item.itemid == 670 then + doTransformItem(item.uid, 6594) + else + doTransformItem(item.uid, item.itemid + 15) + end + doDecayItem(item.uid) + end + return true +end diff --git a/data/movements/scripts/swimming.lua b/data/movements/scripts/swimming.lua new file mode 100644 index 0000000000..1d41f46bc8 --- /dev/null +++ b/data/movements/scripts/swimming.lua @@ -0,0 +1,17 @@ +local outfit = + { + lookType = 267, + lookHead = 0, + lookBody = 0, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0 + } + +function onStepIn(cid, item, position, fromPosition) + doSetCreatureOutfit(cid, outfit, -1) +end + +function onStepOut(cid, item, position, fromPosition) + doRemoveCondition(cid, CONDITION_OUTFIT) +end \ No newline at end of file diff --git a/data/movements/scripts/tiles.lua b/data/movements/scripts/tiles.lua new file mode 100644 index 0000000000..c6d0aaf4a0 --- /dev/null +++ b/data/movements/scripts/tiles.lua @@ -0,0 +1,53 @@ +local increasingItemID = {416, 446, 3216} +local decreasingItemID = {417, 447, 3217} +function onStepIn(cid, item, position, fromPosition) + if isInArray(increasingItemID, item.itemid) == TRUE then + doTransformItem(item.uid, item.itemid + 1) + if item.actionid > 1000 then + getLevelTile(cid, item, position) + elseif getTilePzInfo(position) == TRUE then + getDepotItems(cid, item) + end + elseif item.itemid == 426 then + doTransformItem(item.uid, 425) + if item.actionid > 1000 then + getLevelTile(cid, item, position) + elseif getTilePzInfo(position) == TRUE then + getDepotItems(cid, item) + end + end + return TRUE +end + +function onStepOut(cid, item, position, fromPosition) + if isInArray(decreasingItemID, item.itemid) == TRUE then + doTransformItem(item.uid, item.itemid - 1) + elseif item.itemid == 425 then + doTransformItem(item.uid, item.itemid + 1) + end + return TRUE +end + +function getLevelTile(cid, item, position) + if isPlayer(cid) == TRUE then + if getPlayerLevel(cid) < item.actionid - 1000 then + doTeleportThing(cid, {x = getPlayerPosition(cid).x, y = getPlayerPosition(cid).y, z = getPlayerPosition(cid).z + 1}, FALSE) + doSendMagicEffect(position, CONST_ME_MAGIC_BLUE) + end + end + return TRUE +end + +function getDepotItems(cid, item) + if item.actionid > 100 then + if isPlayer(cid) == TRUE then + depotItems = getPlayerDepotItems(cid, item.actionid - 100) - 3 + if depotItems == 1 then + doPlayerSendTextMessage(cid, MESSAGE_EVENT_DEFAULT, "Your depot contains 1 item.") + else + doPlayerSendTextMessage(cid, MESSAGE_EVENT_DEFAULT, "Your depot contains " ..depotItems.. " items.") + end + end + end + return TRUE +end \ No newline at end of file diff --git a/data/movements/scripts/trap.lua b/data/movements/scripts/trap.lua new file mode 100644 index 0000000000..758b1da737 --- /dev/null +++ b/data/movements/scripts/trap.lua @@ -0,0 +1,27 @@ +function onStepIn(cid, item, pos) + if item.itemid == 2579 then + if isPlayer(cid) ~= TRUE then + doTargetCombatHealth(0, cid, COMBAT_PHYSICALDAMAGE, -15, -30, CONST_ME_NONE) + doTransformItem(item.uid, item.itemid - 1) + end + else + if isPlayer(cid) == TRUE then + doTargetCombatHealth(0, cid, COMBAT_PHYSICALDAMAGE, -50, -100, CONST_ME_NONE) + doTransformItem(item.uid, item.itemid + 1) + end + end + return TRUE +end + +function onStepOut(cid, item, pos) + doTransformItem(item.uid, item.itemid - 1) + return TRUE +end + +function onRemoveItem(item, tile, pos) + if getDistanceBetween(getThingPos(item.uid), pos) > 0 then + doTransformItem(item.uid, item.itemid - 1) + doSendMagicEffect(getThingPos(item.uid), CONST_ME_POFF) + end + return TRUE +end \ No newline at end of file diff --git a/data/movements/scripts/walkback.lua b/data/movements/scripts/walkback.lua new file mode 100644 index 0000000000..4fedfaeb9b --- /dev/null +++ b/data/movements/scripts/walkback.lua @@ -0,0 +1,6 @@ +function onStepIn(cid, item, position, fromPosition) + if item.uid > 0 and item.uid <= 65535 and isPlayer(cid) == TRUE then + doTeleportThing(cid, fromPosition, FALSE) + end + return TRUE +end \ No newline at end of file diff --git a/data/npc/Alice.xml b/data/npc/Alice.xml new file mode 100644 index 0000000000..72b8c8dbe2 --- /dev/null +++ b/data/npc/Alice.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/data/npc/Deruno.xml b/data/npc/Deruno.xml new file mode 100644 index 0000000000..d58ef972eb --- /dev/null +++ b/data/npc/Deruno.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/data/npc/Eryn.xml b/data/npc/Eryn.xml new file mode 100644 index 0000000000..aced03d9e9 --- /dev/null +++ b/data/npc/Eryn.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/data/npc/Riona.xml b/data/npc/Riona.xml new file mode 100644 index 0000000000..bdbea4fb21 --- /dev/null +++ b/data/npc/Riona.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/data/npc/The Forgotten King.xml b/data/npc/The Forgotten King.xml new file mode 100644 index 0000000000..d89581bf84 --- /dev/null +++ b/data/npc/The Forgotten King.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/data/npc/The Oracle.xml b/data/npc/The Oracle.xml new file mode 100644 index 0000000000..785fe2727a --- /dev/null +++ b/data/npc/The Oracle.xml @@ -0,0 +1,200 @@ + + + + + + + hello + greet + + + + = 8 then + selfSay(getCreatureName(cid) .. ", ARE YOU PREPARED O FACE YOUR DESTINY?") + _state.b1 = (isPremium(cid) == TRUE) + _state.topic = 1 + else + selfSay("CHILD! COME BACK WHEN YOU HAVE GROWN UP!") + _state.isidle = true + end + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + farewell + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/npc/Tyoric.xml b/data/npc/Tyoric.xml new file mode 100644 index 0000000000..204a43f649 --- /dev/null +++ b/data/npc/Tyoric.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/data/npc/Varkhal.xml b/data/npc/Varkhal.xml new file mode 100644 index 0000000000..2a87fb5f76 --- /dev/null +++ b/data/npc/Varkhal.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/data/npc/lib/npc.lua b/data/npc/lib/npc.lua new file mode 100644 index 0000000000..5984e9f9d5 --- /dev/null +++ b/data/npc/lib/npc.lua @@ -0,0 +1,127 @@ +-- Including the Advanced NPC System +dofile('data/npc/lib/npcsystem/npcsystem.lua') + +function getDistanceToCreature(id) + if id == 0 or id == nil then + selfGotoIdle() + end + + local creaturePosition = getCreaturePosition(id) + cx = creaturePosition.x + cy = creaturePosition.y + cz = creaturePosition.z + if cx == nil then + return nil + end + + sx, sy, sz = selfGetPosition() + return math.max(math.abs(sx - cx), math.abs(sy - cy)) +end + +function moveToPosition(x,y,z) + selfMoveTo(x, y, z) +end + +function moveToCreature(id) + if id == 0 or id == nil then + selfGotoIdle() + end + + tx, ty, tz = getCreaturePosition(id) + if tx == nil then + selfGotoIdle() + else + moveToPosition(tx, ty, tz) + end +end + +function selfGotoIdle() + following = false + attacking = false + selfAttackCreature(0) + target = 0 +end + +function isPlayerPremiumCallback(cid) + return isPremium(cid) +end + +function msgcontains(message, keyword) + local message, keyword = message:lower(), keyword:lower() + if message == keyword then + return true + end + + return message:find(keyword) and not message:find('(%w+)' .. keyword) +end + +function selfSayChannel(cid, message) + return selfSay(message, cid, FALSE) +end + +function doPosRemoveItem(_itemid, n, position) + local thing = getThingfromPos({x = position.x, y = position.y, z = position.z, stackpos = 1}) + if thing.itemid == _itemid then + doRemoveItem(thing.uid, n) + else + return false + end + return true +end + +function doNpcSellItem(cid, itemid, amount, subType, ignoreCap, inBackpacks, backpack) + local amount, subType, ignoreCap, item = amount or 1, subType or 0, ignoreCap and TRUE or FALSE, 0 + ignoreCap = FALSE + if isItemStackable(itemid) then + if(inBackpacks) then + stuff = doCreateItemEx(backpack, 1) + item = doAddContainerItem(stuff, itemid, math.min(100, amount)) + else + stuff = doCreateItemEx(itemid, math.min(100, amount)) + end + return doPlayerAddItemEx(cid, stuff, ignoreCap) ~= RETURNVALUE_NOERROR and 0 or amount, 0 + end + + local a = 0 + if(inBackpacks) then + local container, b = doCreateItemEx(backpack, 1), 1 + for i = 1, amount do + local item = doAddContainerItem(container, itemid, subType) + if(isInArray({(getContainerCapById(backpack) * b), amount}, i) == TRUE) then + if(doPlayerAddItemEx(cid, container, ignoreCap) ~= RETURNVALUE_NOERROR) then + b = b - 1 -- + break + end + a = i -- a = a + i + if(amount > i) then + container = doCreateItemEx(backpack, 1) + b = b + 1 + end + end + end + return a, b + end + + for i = 1, amount do -- normal method for non-stackable items + local item = doCreateItemEx(itemid, subType) + if(doPlayerAddItemEx(cid, item, ignoreCap) ~= RETURNVALUE_NOERROR) then + break + end + a = i + end + return a, 0 +end + +local func = function(pars) + if isPlayer(pars.pcid) == TRUE then + doCreatureSay(pars.cid, pars.text, pars.type, false, pars.pcid, getCreaturePosition(pars.cid)) + pars.e.done = TRUE + end +end + +function doCreatureSayWithDelay(cid, text, type, delay, e, pcid) + if isPlayer(pcid) == TRUE then + e.done = FALSE + e.event = addEvent(func, delay < 1 and 1000 or delay, {cid=cid, text=text, type=type, e=e, pcid=pcid}) + end +end diff --git a/data/npc/lib/npcsystem/keywordhandler.lua b/data/npc/lib/npcsystem/keywordhandler.lua new file mode 100644 index 0000000000..1e9dde31f2 --- /dev/null +++ b/data/npc/lib/npcsystem/keywordhandler.lua @@ -0,0 +1,175 @@ +-- Advanced NPC System by Jiddo + +if(KeywordHandler == nil) then + KeywordNode = { + keywords = nil, + callback = nil, + parameters = nil, + children = nil, + parent = nil + } + + -- Created a new keywordnode with the given keywords, callback function and parameters and without any childNodes. + function KeywordNode:new(keys, func, param) + local obj = {} + obj.keywords = keys + obj.callback = func + obj.parameters = param + obj.children = {} + setmetatable(obj, self) + self.__index = self + return obj + end + + -- Calls the underlying callback function if it is not nil. + function KeywordNode:processMessage(cid, message) + return (self.callback == nil or self.callback(cid, message, self.keywords, self.parameters, self)) + end + + -- Returns true if message contains all patterns/strings found in keywords. + function KeywordNode:checkMessage(message) + local ret = true + if(self.keywords.callback ~= nil) then + return self.keywords.callback(self.keywords, message) + end + for i, v in ipairs(self.keywords) do + if(type(v) == 'string') then + local a, b = string.find(message, v) + if(a == nil or b == nil) then + ret = false + break + end + end + end + return ret + end + + -- Returns the parent of this node or nil if no such node exists. + function KeywordNode:getParent() + return self.parent + end + + -- Returns an array of the callback function parameters assosiated with this node. + function KeywordNode:getParameters() + return self.parameters + end + + -- Returns an array of the triggering keywords assosiated with this node. + function KeywordNode:getKeywords() + return self.keywords + end + + -- Adds a childNode to this node. Creates the childNode based on the parameters (k = keywords, c = callback, p = parameters) + function KeywordNode:addChildKeyword(keywords, callback, parameters) + local new = KeywordNode:new(keywords, callback, parameters) + return self:addChildKeywordNode(new) + end + + -- Adds a pre-created childNode to this node. Should be used for example if several nodes should have a common child. + function KeywordNode:addChildKeywordNode(childNode) + table.insert(self.children, childNode) + childNode.parent = self + return childNode + end + + KeywordHandler = { + root = nil, + lastNode = nil + } + + -- Creates a new keywordhandler with an empty rootnode. + function KeywordHandler:new() + local obj = {} + obj.root = KeywordNode:new(nil, nil, nil) + setmetatable(obj, self) + self.__index = self + return obj + end + + -- Resets the lastNode field, and this resetting the current position in the node hierarchy to root. + function KeywordHandler:reset() + self.lastNode = nil + end + + -- Makes sure the correct childNode of lastNode gets a chance to process the message. + function KeywordHandler:processMessage(cid, message) + local node = self:getLastNode() + if(node == nil) then + error('No root node found.') + return false + end + + local ret = self:processNodeMessage(node, cid, message) + if(ret) then + return true + end + + if node:getParent() then + node = node:getParent() -- Search through the parent. + local ret = self:processNodeMessage(node, cid, message) + if(ret) then + return true + end + end + + if(node ~= self:getRoot()) then + node = self:getRoot() -- Search through the root. + local ret = self:processNodeMessage(node, cid, message) + if(ret) then + return true + end + end + return false + end + + -- Tries to process the given message using the node parameter's children and calls the node's callback function if found. + -- Returns the childNode which processed the message or nil if no such node was found. + function KeywordHandler:processNodeMessage(node, cid, message) + local messageLower = string.lower(message) + for i, childNode in pairs(node.children) do + if(childNode:checkMessage(messageLower)) then + local oldLast = self.lastNode + self.lastNode = childNode + childNode.parent = node -- Make sure node is the parent of childNode (as one node can be parent to several nodes). + if(childNode:processMessage(cid, message)) then + return true + else + self.lastNode = oldLast + end + end + end + return false + end + + -- Returns the root keywordnode + function KeywordHandler:getRoot() + return self.root + end + + -- Returns the last processed keywordnode or root if no last node is found. + function KeywordHandler:getLastNode() + return self.lastNode or self:getRoot() + end + + -- Adds a new keyword to the root keywordnode. Returns the new node. + function KeywordHandler:addKeyword(keys, callback, parameters) + return self:getRoot():addChildKeyword(keys, callback, parameters) + end + + -- Moves the current position in the keyword hierarchy count steps upwards. Count defalut value = 1. + -- This function MIGHT not work properly yet. Use at your own risk. + function KeywordHandler:moveUp(count) + local steps = count + if(steps == nil) then + steps = 1 + end + for i = 1, steps,1 do + if(self.lastNode == nil) then + break + else + self.lastNode = self.lastNode:getParent() or self:getRoot() + end + end + return self.lastNode + end +end diff --git a/data/npc/lib/npcsystem/modules.lua b/data/npc/lib/npcsystem/modules.lua new file mode 100644 index 0000000000..f9f3779245 --- /dev/null +++ b/data/npc/lib/npcsystem/modules.lua @@ -0,0 +1,1177 @@ +-- Advanced NPC System by Jiddo + +if(Modules == nil) then + -- default words for greeting and ungreeting the npc. Should be a table containing all such words. + FOCUS_GREETWORDS = {"hi", "hello"} + FOCUS_FAREWELLWORDS = {"bye", "farewell"} + + -- The words for requesting trade window. + SHOP_TRADEREQUEST = {"trade"} + + -- The word for accepting/declining an offer. CAN ONLY CONTAIN ONE FIELD! Should be a teble with a single string value. + SHOP_YESWORD = {"yes"} + SHOP_NOWORD = {"no"} + + -- Pattern used to get the amount of an item a player wants to buy/sell. + PATTERN_COUNT = "%d+" + + -- Constants used to separate buying from selling. + SHOPMODULE_SELL_ITEM = 1 + SHOPMODULE_BUY_ITEM = 2 + SHOPMODULE_BUY_ITEM_CONTAINER = 3 + + -- Constants used for shop mode. Notice: addBuyableItemContainer is working on all modes + SHOPMODULE_MODE_TALK = 1 -- Old system used before Tibia 8.2: sell/buy item name + SHOPMODULE_MODE_TRADE = 2 -- Trade window system introduced in Tibia 8.2 + SHOPMODULE_MODE_BOTH = 3 -- Both working at one time + + -- Used shop mode + SHOPMODULE_MODE = SHOPMODULE_MODE_BOTH + + Modules = { + parseableModules = {} + } + + StdModule = {} + + -- These callback function must be called with parameters.npcHandler = npcHandler in the parameters table or they will not work correctly. + -- Notice: The members of StdModule have not yet been tested. If you find any bugs, please report them to me. + -- Usage: + -- keywordHandler:addKeyword({"offer"}, StdModule.say, {npcHandler = npcHandler, text = "I sell many powerful melee weapons."}) + function StdModule.say(cid, message, keywords, parameters, node) + local npcHandler = parameters.npcHandler + if(npcHandler == nil) then + error("StdModule.say called without any npcHandler instance.") + end + local onlyFocus = (parameters.onlyFocus == nil or parameters.onlyFocus == true) + if(not npcHandler:isFocused(cid) and onlyFocus) then + return false + end + + local parseInfo = {[TAG_PLAYERNAME] = getCreatureName(cid)} + npcHandler:say(npcHandler:parseMessage(parameters.text or parameters.message, parseInfo), cid, parameters.publicize and true) + if(parameters.reset == true) then + npcHandler:resetNpc() + elseif(parameters.moveup ~= nil and type(parameters.moveup) == "number") then + npcHandler.keywordHandler:moveUp(parameters.moveup) + end + + return true + end + + --Usage: + -- local node1 = keywordHandler:addKeyword({"promot"}, StdModule.say, {npcHandler = npcHandler, text = "I can promote you for 20000 gold coins. Do you want me to promote you?"}) + -- node1:addChildKeyword({"yes"}, StdModule.promotePlayer, {npcHandler = npcHandler, cost = 20000, level = 20}, text = "Congratulations! You are now promoted.") + -- node1:addChildKeyword({"no"}, StdModule.say, {npcHandler = npcHandler, text = "Allright then. Come back when you are ready."}, reset = true) + function StdModule.promotePlayer(cid, message, keywords, parameters, node) + local npcHandler = parameters.npcHandler + if(npcHandler == nil) then + error("StdModule.promotePlayer called without any npcHandler instance.") + end + if(not npcHandler:isFocused(cid)) then + return false + end + + if(isPlayerPremiumCallback == nil or isPlayerPremiumCallback(cid) == true or parameters.premium == false) then + local promotedVoc = getPromotedVocation(getPlayerVocation(cid)) + if(getPlayerStorageValue(cid, 30018) == TRUE) then + npcHandler:say("You are already promoted!", cid) + elseif(getPlayerLevel(cid) < parameters.level) then + npcHandler:say("I am sorry, but I can only promote you once you have reached level " .. parameters.level .. ".", cid) + elseif(doPlayerRemoveMoney(cid, parameters.cost) ~= TRUE) then + npcHandler:say("You do not have enough money!", cid) + else + doPlayerSetVocation(cid, promotedVoc) + npcHandler:say(parameters.text, cid) + end + else + npcHandler:say("You need a premium account in order to get promoted.", cid) + end + npcHandler:resetNpc() + return true + end + + function StdModule.learnSpell(cid, message, keywords, parameters, node) + local npcHandler = parameters.npcHandler + if(npcHandler == nil) then + error("StdModule.learnSpell called without any npcHandler instance.") + end + + if(not npcHandler:isFocused(cid)) then + return false + end + + if(isPlayerPremiumCallback == nil or isPlayerPremiumCallback(cid) or not parameters.premium) then + if getPlayerLearnedInstantSpell(cid, parameters.spellName) == TRUE then + npcHandler:say("You already know this spell.", cid) + elseif getPlayerLevel(cid) < parameters.level then + npcHandler:say("You need to obtain a level of " .. parameters.level .. " or higher to be able to learn " .. parameters.spellName .. ".", cid) + elseif getPlayerVocation(cid) ~= parameters.vocation and getPlayerVocation(cid) ~= parameters.vocation + 4 and vocation ~= 9 then + npcHandler:say("This spell is not for your vocation", cid) + elseif doPlayerRemoveMoney(cid, parameters.price) == FALSE then + npcHandler:say("You do not have enough money, this spell costs " .. parameters.price .. " gold.", cid) + else + npcHandler:say("You have learned " .. parameters.spellName .. ".", cid) + playerLearnInstantSpell(cid, parameters.spellName) + end + else + npcHandler:say("You need a premium account in order to buy " .. parameters.spellName .. ".", cid) + end + + npcHandler:resetNpc() + return true + end + + function StdModule.bless(cid, message, keywords, parameters, node) + local npcHandler = parameters.npcHandler + if(npcHandler == nil) then + error("StdModule.bless called without any npcHandler instance.") + end + + if(not npcHandler:isFocused(cid) or getWorldType() == WORLD_TYPE_PVP_ENFORCED) then + return false + end + + if(isPlayerPremiumCallback == nil or isPlayerPremiumCallback(cid) == true or parameters.premium == false) then + if getPlayerBlessing(cid, parameters.bless) then + npcHandler:say("Gods have already blessed you with this blessing!", cid) + elseif doPlayerRemoveMoney(cid, parameters.cost) == FALSE then + npcHandler:say("You don't have enough money for blessing.", cid) + else + npcHandler:say("You have been blessed by one of the five gods!", cid) + doPlayerAddBlessing(cid, parameters.bless) + end + else + npcHandler:say("You need a premium account in order to be blessed.", cid) + end + + npcHandler:resetNpc() + return true + end + + function StdModule.travel(cid, message, keywords, parameters, node) + local npcHandler = parameters.npcHandler + if(npcHandler == nil) then + error("StdModule.travel called without any npcHandler instance.") + end + if(not npcHandler:isFocused(cid)) then + return false + end + if(isPlayerPremiumCallback == nil or isPlayerPremiumCallback(cid) == true or parameters.premium == false) then + if(isPlayerPzLocked(cid)) then + npcHandler:say("First get rid of those blood stains! You are not going to ruin my vehicle!", cid) + elseif(parameters.level ~= nil and getPlayerLevel(cid) < parameters.level) then + npcHandler:say("You must reach level " .. parameters.level .. " before I can let you go there.", cid) + elseif(doPlayerRemoveMoney(cid, parameters.cost) ~= TRUE) then + npcHandler:say("You don't have enough money.", cid) + else + npcHandler:say(parameters.msg or "Set the sails!", cid) + npcHandler:releaseFocus(cid) + doSendMagicEffect(getCreaturePosition(cid), CONST_ME_TELEPORT) + doTeleportThing(cid, parameters.destination) + doSendMagicEffect(parameters.destination, CONST_ME_TELEPORT) + end + else + npcHandler:say("I'm sorry, but you need a premium account in order to travel onboard our ships.", cid) + end + npcHandler:resetNpc() + return true + end + + FocusModule = { + npcHandler = nil + } + + -- Creates a new instance of FocusModule without an associated NpcHandler. + function FocusModule:new() + local obj = {} + setmetatable(obj, self) + self.__index = self + return obj + end + + -- Inits the module and associates handler to it. + function FocusModule:init(handler) + self.npcHandler = handler + for i, word in pairs(FOCUS_GREETWORDS) do + local obj = {} + table.insert(obj, word) + obj.callback = FOCUS_GREETWORDS.callback or FocusModule.messageMatcher + handler.keywordHandler:addKeyword(obj, FocusModule.onGreet, {module = self}) + end + + for i, word in pairs(FOCUS_FAREWELLWORDS) do + local obj = {} + table.insert(obj, word) + obj.callback = FOCUS_FAREWELLWORDS.callback or FocusModule.messageMatcher + handler.keywordHandler:addKeyword(obj, FocusModule.onFarewell, {module = self}) + end + + return true + end + + -- Greeting callback function. + function FocusModule.onGreet(cid, message, keywords, parameters) + parameters.module.npcHandler:onGreet(cid) + return true + end + + -- UnGreeting callback function. + function FocusModule.onFarewell(cid, message, keywords, parameters) + if(parameters.module.npcHandler:isFocused(cid)) then + parameters.module.npcHandler:onFarewell(cid) + return true + else + return false + end + end + + -- Custom message matching callback function for greeting messages. + function FocusModule.messageMatcher(keywords, message) + for i, word in pairs(keywords) do + if(type(word) == "string") then + if string.find(message, word) and not string.find(message, "[%w+]" .. word) and not string.find(message, word .. "[%w+]") then + return true + end + end + end + return false + end + + KeywordModule = { + npcHandler = nil + } + -- Add it to the parseable module list. + Modules.parseableModules["module_keywords"] = KeywordModule + + function KeywordModule:new() + local obj = {} + setmetatable(obj, self) + self.__index = self + return obj + end + + function KeywordModule:init(handler) + self.npcHandler = handler + return true + end + + -- Parses all known parameters. + function KeywordModule:parseParameters() + local ret = NpcSystem.getParameter("keywords") + if(ret ~= nil) then + self:parseKeywords(ret) + end + end + + function KeywordModule:parseKeywords(data) + local n = 1 + for keys in string.gmatch(data, "[^;]+") do + local i = 1 + + local keywords = {} + for temp in string.gmatch(keys, "[^,]+") do + table.insert(keywords, temp) + i = i + 1 + end + + if(i ~= 1) then + local reply = NpcSystem.getParameter("keyword_reply" .. n) + if(reply ~= nil) then + self:addKeyword(keywords, reply) + else + print("[Warning] NpcSystem:", "Parameter '" .. "keyword_reply" .. n .. "' missing. Skipping...") + end + else + print("[Warning] NpcSystem:", "No keywords found for keyword set #" .. n .. ". Skipping...") + end + + n = n+1 + end + end + + function KeywordModule:addKeyword(keywords, reply) + self.npcHandler.keywordHandler:addKeyword(keywords, StdModule.say, {npcHandler = self.npcHandler, onlyFocus = true, text = reply, reset = true}) + end + + TravelModule = { + npcHandler = nil, + destinations = nil, + yesNode = nil, + noNode = nil, + } + -- Add it to the parseable module list. + Modules.parseableModules["module_travel"] = TravelModule + + function TravelModule:new() + local obj = {} + setmetatable(obj, self) + self.__index = self + return obj + end + + function TravelModule:init(handler) + self.npcHandler = handler + self.yesNode = KeywordNode:new(SHOP_YESWORD, TravelModule.onConfirm, {module = self}) + self.noNode = KeywordNode:new(SHOP_NOWORD, TravelModule.onDecline, {module = self}) + self.destinations = {} + return true + end + + -- Parses all known parameters. + function TravelModule:parseParameters() + local ret = NpcSystem.getParameter("travel_destinations") + if(ret ~= nil) then + self:parseDestinations(ret) + + self.npcHandler.keywordHandler:addKeyword({"destination"}, TravelModule.listDestinations, {module = self}) + self.npcHandler.keywordHandler:addKeyword({"where"}, TravelModule.listDestinations, {module = self}) + self.npcHandler.keywordHandler:addKeyword({"travel"}, TravelModule.listDestinations, {module = self}) + + end + end + + function TravelModule:parseDestinations(data) + for destination in string.gmatch(data, "[^;]+") do + local i = 1 + + local name = nil + local x = nil + local y = nil + local z = nil + local cost = nil + local premium = false + + for temp in string.gmatch(destination, "[^,]+") do + if(i == 1) then + name = temp + elseif(i == 2) then + x = tonumber(temp) + elseif(i == 3) then + y = tonumber(temp) + elseif(i == 4) then + z = tonumber(temp) + elseif(i == 5) then + cost = tonumber(temp) + elseif(i == 6) then + premium = temp == "true" + else + print("[Warning : " .. getCreatureName(getNpcCid()) .. "] NpcSystem:", "Unknown parameter found in travel destination parameter.", temp, destination) + end + i = i + 1 + end + + if(name ~= nil and x ~= nil and y ~= nil and z ~= nil and cost ~= nil) then + self:addDestination(name, {x=x, y=y, z=z}, cost, premium) + else + print("[Warning] NpcSystem:", "Parameter(s) missing for travel destination:", name, x, y, z, cost, premium) + end + end + end + + function TravelModule:addDestination(name, position, price, premium) + table.insert(self.destinations, name) + + local parameters = { + cost = price, + destination = position, + premium = premium, + module = self + } + local keywords = {} + table.insert(keywords, name) + + local keywords2 = {} + table.insert(keywords2, "bring me to " .. name) + local node = self.npcHandler.keywordHandler:addKeyword(keywords, TravelModule.travel, parameters) + self.npcHandler.keywordHandler:addKeyword(keywords2, TravelModule.bringMeTo, parameters) + node:addChildKeywordNode(self.yesNode) + node:addChildKeywordNode(self.noNode) + + if(npcs_loaded_travel[getNpcCid()] == nil) then + npcs_loaded_travel[getNpcCid()] = getNpcCid() + self.npcHandler.keywordHandler:addKeyword({'yes'}, TravelModule.onConfirm, {module = self}) + self.npcHandler.keywordHandler:addKeyword({'no'}, TravelModule.onDecline, {module = self}) + end + end + + function TravelModule.travel(cid, message, keywords, parameters, node) + local module = parameters.module + if(not module.npcHandler:isFocused(cid)) then + return false + end + + local npcHandler = module.npcHandler + + shop_destination[cid] = parameters.destination + shop_cost[cid] = parameters.cost + shop_premium[cid] = parameters.premium + shop_npcuid[cid] = getNpcCid() + + local cost = parameters.cost + local destination = parameters.destination + local premium = parameters.premium + + module.npcHandler:say("Do you want to travel to " .. keywords[1] .. " for " .. cost .. " gold coins?", cid) + return true + end + + function TravelModule.onConfirm(cid, message, keywords, parameters, node) + local module = parameters.module + if(not module.npcHandler:isFocused(cid)) then + return false + end + + if shop_npcuid[cid] ~= getNpcCid() then + return false + end + + local npcHandler = module.npcHandler + + local parentParameters = node:getParent():getParameters() + local cost = shop_cost[cid] + local destination = shop_destination[cid] + local premium = shop_premium[cid] + + if(not isPlayerPremiumCallback or isPlayerPremiumCallback(cid) or shop_premium[cid] ~= true) then + if(doPlayerRemoveMoney(cid, cost) ~= TRUE) then + npcHandler:say("You do not have enough money!", cid) + elseif(isPlayerPzLocked(cid)) then + npcHandler:say("Get out of there with this blood.", cid) + else + npcHandler:say("It was a pleasure doing business with you.", cid) + npcHandler:releaseFocus(cid) + doTeleportThing(cid, destination, 0) + doSendMagicEffect(destination, 10) + end + else + npcHandler:say("I can only allow premium players to travel there.", cid) + end + + npcHandler:resetNpc() + return true + end + + -- onDecline keyword callback function. Generally called when the player sais "no" after wanting to buy an item. + function TravelModule.onDecline(cid, message, keywords, parameters, node) + local module = parameters.module + if(not module.npcHandler:isFocused(cid)) or shop_npcuid[cid] ~= getNpcCid() then + return false + end + local parentParameters = node:getParent():getParameters() + local parseInfo = { + [TAG_PLAYERNAME] = getCreatureName(cid), + } + local msg = module.npcHandler:parseMessage(module.npcHandler:getMessage(MESSAGE_DECLINE), parseInfo) + module.npcHandler:say(msg, cid) + module.npcHandler:resetNpc() + return true + end + + function TravelModule.bringMeTo(cid, message, keywords, parameters, node) + local module = parameters.module + if(not module.npcHandler:isFocused(cid)) then + return false + end + + local cost = parameters.cost + local destination = parameters.destination + local premium = parameters.premium + + if(not isPlayerPremiumCallback or isPlayerPremiumCallback(cid) or parameters.premium ~= true) then + if(doPlayerRemoveMoney(cid, cost) == TRUE) then + doTeleportThing(cid, destination, 0) + doSendMagicEffect(destination, 10) + end + end + return true + end + + function TravelModule.listDestinations(cid, message, keywords, parameters, node) + local module = parameters.module + if(not module.npcHandler:isFocused(cid)) then + return false + end + + local msg = "I can bring you to " + --local i = 1 + local maxn = table.maxn(module.destinations) + for i, destination in pairs(module.destinations) do + msg = msg .. destination + if(i == maxn -1) then + msg = msg .. " and " + elseif(i == maxn) then + msg = msg .. "." + else + msg = msg .. ", " + end + i = i + 1 + end + + module.npcHandler:say(msg, cid) + module.npcHandler:resetNpc() + return true + end + + ShopModule = { + npcHandler = nil, + yesNode = nil, + noNode = nil, + noText = "", + maxCount = 100, + amount = 0 + } + + -- Add it to the parseable module list. + Modules.parseableModules["module_shop"] = ShopModule + + -- Creates a new instance of ShopModule + function ShopModule:new() + local obj = {} + setmetatable(obj, self) + self.__index = self + return obj + end + + -- Parses all known parameters. + function ShopModule:parseParameters() + local ret = NpcSystem.getParameter("shop_buyable") + if(ret ~= nil) then + self:parseBuyable(ret) + end + + local ret = NpcSystem.getParameter("shop_sellable") + if(ret ~= nil) then + self:parseSellable(ret) + end + + local ret = NpcSystem.getParameter("shop_buyable_containers") + if(ret ~= nil) then + self:parseBuyableContainers(ret) + end + end + + -- Parse a string contaning a set of buyable items. + function ShopModule:parseBuyable(data) + for item in string.gmatch(data, "[^;]+") do + local i = 1 + + local name = nil + local itemid = nil + local cost = nil + local subType = nil + local realName = nil + + for temp in string.gmatch(item, "[^,]+") do + if(i == 1) then + name = temp + elseif(i == 2) then + itemid = tonumber(temp) + elseif(i == 3) then + cost = tonumber(temp) + elseif(i == 4) then + subType = tonumber(temp) + elseif(i == 5) then + realName = temp + else + print("[Warning : " .. getCreatureName(getNpcCid()) .. "] NpcSystem:", "Unknown parameter found in buyable items parameter.", temp, item) + end + i = i + 1 + end + + if(SHOPMODULE_MODE == SHOPMODULE_MODE_TRADE) then + if(itemid ~= nil and cost ~= nil) then + if subType == nil and isItemFluidContainer(itemid) then + print("[Warning : " .. getCreatureName(getNpcCid()) .. "] NpcSystem:", "SubType missing for parameter item:", item) + else + self:addBuyableItem(nil, itemid, cost, subType, realName) + end + else + print("[Warning] NpcSystem:", "Parameter(s) missing for item:", itemid, cost) + end + else + if(name ~= nil and itemid ~= nil and cost ~= nil) then + if subType == nil and isItemFluidContainer(itemid) then + print("[Warning : " .. getCreatureName(getNpcCid()) .. "] NpcSystem:", "SubType missing for parameter item:", item) + else + local names = {} + table.insert(names, name) + self:addBuyableItem(names, itemid, cost, subType, realName) + end + else + print("[Warning] NpcSystem:", "Parameter(s) missing for item:", name, itemid, cost) + end + end + end + end + + -- Parse a string contaning a set of sellable items. + function ShopModule:parseSellable(data) + for item in string.gmatch(data, "[^;]+") do + local i = 1 + + local name = nil + local itemid = nil + local cost = nil + local realName = nil + local subType = nil + + for temp in string.gmatch(item, "[^,]+") do + if(i == 1) then + name = temp + elseif(i == 2) then + itemid = tonumber(temp) + elseif(i == 3) then + cost = tonumber(temp) + elseif(i == 4) then + realName = temp + elseif(i == 5) then + subType = tonumber(temp) + else + print("[Warning : " .. getCreatureName(getNpcCid()) .. "] NpcSystem:", "Unknown parameter found in sellable items parameter.", temp, item) + end + i = i + 1 + end + + if(SHOPMODULE_MODE == SHOPMODULE_MODE_TRADE) then + if(itemid ~= nil and cost ~= nil) then + self:addSellableItem(nil, itemid, cost, realName, subType) + else + print("[Warning] NpcSystem:", "Parameter(s) missing for item:", itemid, cost) + end + else + if(name ~= nil and itemid ~= nil and cost ~= nil) then + local names = {} + table.insert(names, name) + self:addSellableItem(names, itemid, cost, realName, subType) + else + print("[Warning] NpcSystem:", "Parameter(s) missing for item:", name, itemid, cost) + end + end + end + end + + -- Parse a string contaning a set of buyable items. + function ShopModule:parseBuyableContainers(data) + for item in string.gmatch(data, "[^;]+") do + local i = 1 + + local name = nil + local container = nil + local itemid = nil + local cost = nil + local subType = nil + local realName = nil + + for temp in string.gmatch(item, "[^,]+") do + if(i == 1) then + name = temp + elseif(i == 2) then + itemid = tonumber(temp) + elseif(i == 3) then + itemid = tonumber(temp) + elseif(i == 4) then + cost = tonumber(temp) + elseif(i == 5) then + subType = tonumber(temp) + elseif(i == 6) then + realName = temp + else + print("[Warning : " .. getCreatureName(getNpcCid()) .. "] NpcSystem:", "Unknown parameter found in buyable items parameter.", temp, item) + end + i = i + 1 + end + + if(name ~= nil and container ~= nil and itemid ~= nil and cost ~= nil) then + if subType == nil and isItemFluidContainer(itemid) then + print("[Warning : " .. getCreatureName(getNpcCid()) .. "] NpcSystem:", "SubType missing for parameter item:", item) + else + local names = {} + table.insert(names, name) + self:addBuyableItemContainer(names, container, itemid, cost, subType, realName) + end + else + print("[Warning] NpcSystem:", "Parameter(s) missing for item:", name, container, itemid, cost) + end + end + end + + -- Initializes the module and associates handler to it. + function ShopModule:init(handler) + self.npcHandler = handler + self.yesNode = KeywordNode:new(SHOP_YESWORD, ShopModule.onConfirm, {module = self}) + self.noNode = KeywordNode:new(SHOP_NOWORD, ShopModule.onDecline, {module = self}) + self.noText = handler:getMessage(MESSAGE_DECLINE) + + if(SHOPMODULE_MODE ~= SHOPMODULE_MODE_TALK) then + for i, word in pairs(SHOP_TRADEREQUEST) do + local obj = {} + table.insert(obj, word) + obj.callback = SHOP_TRADEREQUEST.callback or ShopModule.messageMatcher + handler.keywordHandler:addKeyword(obj, ShopModule.requestTrade, {module = self}) + end + end + + return true + end + + -- Custom message matching callback function for requesting trade messages. + function ShopModule.messageMatcher(keywords, message) + for i, word in pairs(keywords) do + if(type(word) == "string") then + if string.find(message, word) and not string.find(message, "[%w+]" .. word) and not string.find(message, word .. "[%w+]") then + return true + end + end + end + + return false + end + + -- Resets the module-specific variables. + function ShopModule:reset() + self.amount = 0 + end + + -- Function used to match a number value from a string. + function ShopModule:getCount(message) + local ret = 1 + local b, e = string.find(message, PATTERN_COUNT) + if b ~= nil and e ~= nil then + ret = tonumber(string.sub(message, b, e)) + end + + if(ret <= 0) then + ret = 1 + elseif(ret > self.maxCount) then + ret = self.maxCount + end + + return ret + end + + -- Adds a new buyable item. + -- names = A table containing one or more strings of alternative names to this item. Used only for old buy/sell system. + -- itemid = The itemid of the buyable item + -- cost = The price of one single item + -- subType - The subType of each rune or fluidcontainer item. Can be left out if it is not a rune/fluidcontainer. Default value is 1. + -- realName - The real, full name for the item. Will be used as ITEMNAME in MESSAGE_ONBUY and MESSAGE_ONSELL if defined. Default value is nil (getItemName will be used) + function ShopModule:addBuyableItem(names, itemid, cost, itemSubType, realName) + if(SHOPMODULE_MODE ~= SHOPMODULE_MODE_TALK) then + if itemSubType == nil then + itemSubType = 1 + end + + local shopItem = self:getShopItem(itemid, itemSubType) + if shopItem == nil then + table.insert(self.npcHandler.shopItems, {id = itemid, buy = cost, sell = -1, subType = itemSubType, name = realName or getItemName(itemid)}) + else + shopItem.buy = cost + end + end + + if(names ~= nil and SHOPMODULE_MODE ~= SHOPMODULE_MODE_TRADE) then + for i, name in pairs(names) do + local parameters = { + itemid = itemid, + cost = cost, + eventType = SHOPMODULE_BUY_ITEM, + module = self, + realName = realName or getItemName(itemid), + subType = itemSubType or 1 + } + + keywords = {} + table.insert(keywords, "buy") + table.insert(keywords, name) + local node = self.npcHandler.keywordHandler:addKeyword(keywords, ShopModule.tradeItem, parameters) + node:addChildKeywordNode(self.yesNode) + node:addChildKeywordNode(self.noNode) + end + end + + if(npcs_loaded_shop[getNpcCid()] == nil) then + npcs_loaded_shop[getNpcCid()] = getNpcCid() + self.npcHandler.keywordHandler:addKeyword({'yes'}, ShopModule.onConfirm, {module = self}) + self.npcHandler.keywordHandler:addKeyword({'no'}, ShopModule.onDecline, {module = self}) + end + end + + function ShopModule:getShopItem(itemId, itemSubType) + if isItemFluidContainer(itemId) then + for i = 1, #self.npcHandler.shopItems do + local shopItem = self.npcHandler.shopItems[i] + if shopItem.id == itemId and shopItem.subType == itemSubType then + return shopItem + end + end + else + for i = 1, #self.npcHandler.shopItems do + local shopItem = self.npcHandler.shopItems[i] + if shopItem.id == itemId then + return shopItem + end + end + end + return nil + end + + -- Adds a new buyable container of items. + -- names = A table containing one or more strings of alternative names to this item. + -- container = Backpack, bag or any other itemid of container where bought items will be stored + -- itemid = The itemid of the buyable item + -- cost = The price of one single item + -- subType - The subType of each rune or fluidcontainer item. Can be left out if it is not a rune/fluidcontainer. Default value is 1. + -- realName - The real, full name for the item. Will be used as ITEMNAME in MESSAGE_ONBUY and MESSAGE_ONSELL if defined. Default value is nil (getItemName will be used) + function ShopModule:addBuyableItemContainer(names, container, itemid, cost, subType, realName) + if(names ~= nil) then + for i, name in pairs(names) do + local parameters = { + container = container, + itemid = itemid, + cost = cost, + eventType = SHOPMODULE_BUY_ITEM_CONTAINER, + module = self, + realName = realName or getItemName(itemid), + subType = subType or 1 + } + + keywords = {} + table.insert(keywords, "buy") + table.insert(keywords, name) + local node = self.npcHandler.keywordHandler:addKeyword(keywords, ShopModule.tradeItem, parameters) + node:addChildKeywordNode(self.yesNode) + node:addChildKeywordNode(self.noNode) + end + end + end + + -- Adds a new sellable item. + -- names = A table containing one or more strings of alternative names to this item. Used only by old buy/sell system. + -- itemid = The itemid of the sellable item + -- cost = The price of one single item + -- realName - The real, full name for the item. Will be used as ITEMNAME in MESSAGE_ONBUY and MESSAGE_ONSELL if defined. Default value is nil (getItemName will be used) + function ShopModule:addSellableItem(names, itemid, cost, realName, itemSubType) + if(SHOPMODULE_MODE ~= SHOPMODULE_MODE_TALK) then + if itemSubType == nil then + itemSubType = 0 + end + + local shopItem = self:getShopItem(itemid, itemSubType) + if shopItem == nil then + table.insert(self.npcHandler.shopItems, {id = itemid, buy = -1, sell = cost, subType = itemSubType, name = realName or getItemName(itemid)}) + else + shopItem.sell = cost + end + end + + if(names ~= nil and SHOPMODULE_MODE ~= SHOPMODULE_MODE_TRADE) then + for i, name in pairs(names) do + local parameters = { + itemid = itemid, + cost = cost, + eventType = SHOPMODULE_SELL_ITEM, + module = self, + realName = realName or getItemName(itemid) + } + + keywords = {} + table.insert(keywords, "sell") + table.insert(keywords, name) + local node = self.npcHandler.keywordHandler:addKeyword(keywords, ShopModule.tradeItem, parameters) + node:addChildKeywordNode(self.yesNode) + node:addChildKeywordNode(self.noNode) + end + end + end + + -- onModuleReset callback function. Calls ShopModule:reset() + function ShopModule:callbackOnModuleReset() + self:reset() + return true + end + + -- Callback onBuy() function. If you wish, you can change certain Npc to use your onBuy(). + function ShopModule:callbackOnBuy(cid, itemid, subType, amount, ignoreCap, inBackpacks) + local shopItem = self:getShopItem(itemid, subType) + if shopItem == nil then + error("[ShopModule.onBuy] shopItem == nil") + return false + end + + if shopItem.buy == -1 then + error("[ShopModule.onSell] attempt to buy a non-buyable item") + return false + end + + local backpack = 1988 + local totalCost = amount * shopItem.buy + if(inBackpacks) then + totalCost = isItemStackable(itemid) == TRUE and totalCost + 20 or totalCost + (math.max(1, math.floor(amount / getContainerCapById(backpack))) * 20) + end + + local parseInfo = { + [TAG_PLAYERNAME] = getPlayerName(cid), + [TAG_ITEMCOUNT] = amount, + [TAG_TOTALCOST] = totalCost, + [TAG_ITEMNAME] = shopItem.name + } + + if(getPlayerMoney(cid) < totalCost) then + local msg = self.npcHandler:getMessage(MESSAGE_NEEDMONEY) + msg = self.npcHandler:parseMessage(msg, parseInfo) + doPlayerSendCancel(cid, msg) + return false + end + + local subType = shopItem.subType or 1 + local a, b = doNpcSellItem(cid, itemid, amount, subType, ignoreCap, inBackpacks, backpack) + if(a < amount) then + local msgId = MESSAGE_NEEDMORESPACE + if(a == 0) then + msgId = MESSAGE_NEEDSPACE + end + + local msg = self.npcHandler:getMessage(msgId) + parseInfo[TAG_ITEMCOUNT] = a + msg = self.npcHandler:parseMessage(msg, parseInfo) + doPlayerSendCancel(cid, msg) + self.npcHandler.talkStart[cid] = os.time() + + if(a > 0) then + doPlayerRemoveMoney(cid, ((a * shopItem.buy) + (b * 20))) + return true + end + + return false + else + local msg = self.npcHandler:getMessage(MESSAGE_BOUGHT) + msg = self.npcHandler:parseMessage(msg, parseInfo) + doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, msg) + doPlayerRemoveMoney(cid, totalCost) + self.npcHandler.talkStart[cid] = os.time() + return true + end + end + + -- Callback onSell() function. If you wish, you can change certain Npc to use your onSell(). + function ShopModule:callbackOnSell(cid, itemid, subType, amount, ignoreEquipped, _) + local shopItem = self:getShopItem(itemid, subType) + if shopItem == nil then + error("[ShopModule.onSell] items[itemid] == nil") + return false + end + + if shopItem.sell == -1 then + error("[ShopModule.onSell] attempt to sell a non-sellable item") + return false + end + + local parseInfo = { + [TAG_PLAYERNAME] = getPlayerName(cid), + [TAG_ITEMCOUNT] = amount, + [TAG_TOTALCOST] = amount * shopItem.sell, + [TAG_ITEMNAME] = shopItem.name + } + + if not isItemFluidContainer(itemid) then + subType = -1 + end + + if doPlayerRemoveItem(cid, itemid, amount, subType, ignoreEquipped) then + local msg = self.npcHandler:getMessage(MESSAGE_SOLD) + msg = self.npcHandler:parseMessage(msg, parseInfo) + doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, msg) + doPlayerAddMoney(cid, amount * shopItem.sell) + self.npcHandler.talkStart[cid] = os.time() + return true + else + local msg = self.npcHandler:getMessage(MESSAGE_NEEDITEM) + msg = self.npcHandler:parseMessage(msg, parseInfo) + doPlayerSendCancel(cid, msg) + self.npcHandler.talkStart[cid] = os.time() + return false + end + end + + -- Callback for requesting a trade window with the NPC. + function ShopModule.requestTrade(cid, message, keywords, parameters, node) + local module = parameters.module + if(not module.npcHandler:isFocused(cid)) then + return false + end + + if(not module.npcHandler:onTradeRequest(cid)) then + return false + end + + local itemWindow = {} + for i = 1, #module.npcHandler.shopItems do + table.insert(itemWindow, module.npcHandler.shopItems[i]) + end + + if(itemWindow[1] == nil) then + local parseInfo = { [TAG_PLAYERNAME] = getPlayerName(cid) } + local msg = module.npcHandler:parseMessage(module.npcHandler:getMessage(MESSAGE_NOSHOP), parseInfo) + module.npcHandler:say(msg, cid) + return true + end + + local parseInfo = { [TAG_PLAYERNAME] = getPlayerName(cid) } + local msg = module.npcHandler:parseMessage(module.npcHandler:getMessage(MESSAGE_SENDTRADE), parseInfo) + openShopWindow(cid, itemWindow, + function(cid, itemid, subType, amount, ignoreCap, inBackpacks) module.npcHandler:onBuy(cid, itemid, subType, amount, ignoreCap, inBackpacks) end, + function(cid, itemid, subType, amount, ignoreCap, inBackpacks) module.npcHandler:onSell(cid, itemid, subType, amount, ignoreCap, inBackpacks) end) + module.npcHandler:say(msg, cid) + return true + end + + -- onConfirm keyword callback function. Sells/buys the actual item. + function ShopModule.onConfirm(cid, message, keywords, parameters, node) + local module = parameters.module + if(not module.npcHandler:isFocused(cid)) or shop_npcuid[cid] ~= getNpcCid() then + return false + end + shop_npcuid[cid] = 0 + + local parentParameters = node:getParent():getParameters() + local parseInfo = { + [TAG_PLAYERNAME] = getPlayerName(cid), + [TAG_ITEMCOUNT] = shop_amount[cid], + [TAG_TOTALCOST] = shop_cost[cid] * shop_amount[cid], + [TAG_ITEMNAME] = shop_rlname[cid] + } + + if(shop_eventtype[cid] == SHOPMODULE_SELL_ITEM) then + local ret = doPlayerSellItem(cid, shop_itemid[cid], shop_amount[cid], shop_cost[cid] * shop_amount[cid]) + if(ret == LUA_NO_ERROR) then + local msg = module.npcHandler:getMessage(MESSAGE_ONSELL) + msg = module.npcHandler:parseMessage(msg, parseInfo) + module.npcHandler:say(msg, cid) + else + local msg = module.npcHandler:getMessage(MESSAGE_MISSINGITEM) + msg = module.npcHandler:parseMessage(msg, parseInfo) + module.npcHandler:say(msg, cid) + end + elseif(shop_eventtype[cid] == SHOPMODULE_BUY_ITEM) then + local cost = shop_cost[cid] * shop_amount[cid] + if getPlayerMoney(cid) < cost then + local msg = module.npcHandler:getMessage(MESSAGE_MISSINGMONEY) + msg = module.npcHandler:parseMessage(msg, parseInfo) + module.npcHandler:say(msg, cid) + return false + end + + local a, b = doNpcSellItem(cid, shop_itemid[cid], shop_amount[cid], shop_subtype[cid], false, false, 1988) + if(a < shop_amount[cid]) then + local msgId = MESSAGE_NEEDMORESPACE + if(a == 0) then + msgId = MESSAGE_NEEDSPACE + end + + local msg = module.npcHandler:getMessage(msgId) + msg = module.npcHandler:parseMessage(msg, parseInfo) + module.npcHandler:say(msg, cid) + if(a > 0) then + doPlayerRemoveMoney(cid, a * shop_cost[cid]) + if shop_itemid[cid] == ITEM_PARCEL then + doNpcSellItem(cid, ITEM_LABEL, shop_amount[cid], shop_subtype[cid], true, false, 1988) + end + return true + end + return false + else + local msg = module.npcHandler:getMessage(MESSAGE_ONBUY) + msg = module.npcHandler:parseMessage(msg, parseInfo) + module.npcHandler:say(msg, cid) + doPlayerRemoveMoney(cid, cost) + if shop_itemid[cid] == ITEM_PARCEL then + doNpcSellItem(cid, ITEM_LABEL, shop_amount[cid], shop_subtype[cid], true, false, 1988) + end + return true + end + elseif(shop_eventtype[cid] == SHOPMODULE_BUY_ITEM_CONTAINER) then + local ret = doPlayerBuyItemContainer(cid, shop_container[cid], shop_itemid[cid], shop_amount[cid], shop_cost[cid] * shop_amount[cid], shop_subtype[cid]) + if(ret == LUA_NO_ERROR) then + local msg = module.npcHandler:getMessage(MESSAGE_ONBUY) + msg = module.npcHandler:parseMessage(msg, parseInfo) + module.npcHandler:say(msg, cid) + else + local msg = module.npcHandler:getMessage(MESSAGE_MISSINGMONEY) + msg = module.npcHandler:parseMessage(msg, parseInfo) + module.npcHandler:say(msg, cid) + end + end + + module.npcHandler:resetNpc() + return true + end + + -- onDecline keyword callback function. Generally called when the player sais "no" after wanting to buy an item. + function ShopModule.onDecline(cid, message, keywords, parameters, node) + local module = parameters.module + if(not module.npcHandler:isFocused(cid)) or shop_npcuid[cid] ~= getNpcCid() then + return false + end + shop_npcuid[cid] = 0 + + local parentParameters = node:getParent():getParameters() + local parseInfo = { + [TAG_PLAYERNAME] = getPlayerName(cid), + [TAG_ITEMCOUNT] = shop_amount[cid], + [TAG_TOTALCOST] = shop_cost[cid] * shop_amount[cid], + [TAG_ITEMNAME] = shop_rlname[cid] + } + + local msg = module.npcHandler:parseMessage(module.noText, parseInfo) + module.npcHandler:say(msg, cid) + module.npcHandler:resetNpc() + return true + end + + -- tradeItem callback function. Makes the npc say the message defined by MESSAGE_BUY or MESSAGE_SELL + function ShopModule.tradeItem(cid, message, keywords, parameters, node) + local module = parameters.module + if(not module.npcHandler:isFocused(cid)) then + return false + end + + if(not module.npcHandler:onTradeRequest(cid)) then + return true + end + + local count = module:getCount(message) + module.amount = count + + shop_amount[cid] = module.amount + shop_cost[cid] = parameters.cost + shop_rlname[cid] = parameters.realName + shop_itemid[cid] = parameters.itemid + shop_container[cid] = parameters.container + shop_npcuid[cid] = getNpcCid() + shop_eventtype[cid] = parameters.eventType + shop_subtype[cid] = parameters.subType + + local parseInfo = { + [TAG_PLAYERNAME] = getPlayerName(cid), + [TAG_ITEMCOUNT] = shop_amount[cid], + [TAG_TOTALCOST] = shop_cost[cid] * shop_amount[cid], + [TAG_ITEMNAME] = shop_rlname[cid] + } + + if(shop_eventtype[cid] == SHOPMODULE_SELL_ITEM) then + local msg = module.npcHandler:getMessage(MESSAGE_SELL) + msg = module.npcHandler:parseMessage(msg, parseInfo) + module.npcHandler:say(msg, cid) + elseif(shop_eventtype[cid] == SHOPMODULE_BUY_ITEM) then + local msg = module.npcHandler:getMessage(MESSAGE_BUY) + msg = module.npcHandler:parseMessage(msg, parseInfo) + module.npcHandler:say(msg, cid) + elseif(shop_eventtype[cid] == SHOPMODULE_BUY_ITEM_CONTAINER) then + local msg = module.npcHandler:getMessage(MESSAGE_BUY) + msg = module.npcHandler:parseMessage(msg, parseInfo) + module.npcHandler:say(msg, cid) + end + return true + end +end diff --git a/data/npc/lib/npcsystem/npchandler.lua b/data/npc/lib/npcsystem/npchandler.lua new file mode 100644 index 0000000000..2b3f08589d --- /dev/null +++ b/data/npc/lib/npcsystem/npchandler.lua @@ -0,0 +1,597 @@ +-- Advanced NPC System by Jiddo + +if(NpcHandler == nil) then + -- Constant talkdelay behaviors. + TALKDELAY_NONE = 0 -- No talkdelay. Npc will reply immedeatly. + TALKDELAY_ONTHINK = 1 -- Talkdelay handled through the onThink callback function. (Default) + TALKDELAY_EVENT = 2 -- Not yet implemented + + -- Currently applied talkdelay behavior. TALKDELAY_ONTHINK is default. + NPCHANDLER_TALKDELAY = TALKDELAY_ONTHINK + + -- Constant indexes for defining default messages. + MESSAGE_GREET = 1 -- When the player greets the npc. + MESSAGE_FAREWELL = 2 -- When the player unGreets the npc. + MESSAGE_BUY = 3 -- When the npc asks the player if he wants to buy something. + MESSAGE_ONBUY = 4 -- When the player successfully buys something via talk. + MESSAGE_BOUGHT = 5 -- When the player bought something through the shop window. + MESSAGE_SELL = 6 -- When the npc asks the player if he wants to sell something. + MESSAGE_ONSELL = 7 -- When the player successfully sells something via talk. + MESSAGE_SOLD = 8 -- When the player sold something through the shop window. + MESSAGE_MISSINGMONEY = 9 -- When the player does not have enough money. + MESSAGE_NEEDMONEY = 10 -- Same as above, used for shop window. + MESSAGE_MISSINGITEM = 11 -- When the player is trying to sell an item he does not have. + MESSAGE_NEEDITEM = 12 -- Same as above, used for shop window. + MESSAGE_NEEDSPACE = 13 -- When the player don't have any space to buy an item + MESSAGE_NEEDMORESPACE = 14 -- When the player has some space to buy an item, but not enough space + MESSAGE_IDLETIMEOUT = 15 -- When the player has been idle for longer then idleTime allows. + MESSAGE_WALKAWAY = 16 -- When the player walks out of the talkRadius of the npc. + MESSAGE_DECLINE = 17 -- When the player says no to something. + MESSAGE_SENDTRADE = 18 -- When the npc sends the trade window to the player + MESSAGE_NOSHOP = 19 -- When the npc's shop is requested but he doesn't have any + MESSAGE_ONCLOSESHOP = 20 -- When the player closes the npc's shop window + MESSAGE_ALREADYFOCUSED = 21 -- When the player already has the focus of this npc. + MESSAGE_WALKAWAY_MALE = 22 -- When a male player walks out of the talkRadius of the npc. + MESSAGE_WALKAWAY_FEMALE = 23 -- When a female player walks out of the talkRadius of the npc. + + -- Constant indexes for callback functions. These are also used for module callback ids. + CALLBACK_CREATURE_APPEAR = 1 + CALLBACK_CREATURE_DISAPPEAR = 2 + CALLBACK_CREATURE_SAY = 3 + CALLBACK_ONTHINK = 4 + CALLBACK_GREET = 5 + CALLBACK_FAREWELL = 6 + CALLBACK_MESSAGE_DEFAULT = 7 + CALLBACK_PLAYER_ENDTRADE = 8 + CALLBACK_PLAYER_CLOSECHANNEL = 9 + CALLBACK_ONBUY = 10 + CALLBACK_ONSELL = 11 + CALLBACK_ONADDFOCUS = 18 + CALLBACK_ONRELEASEFOCUS = 19 + CALLBACK_ONTRADEREQUEST = 20 + + -- Addidional module callback ids + CALLBACK_MODULE_INIT = 12 + CALLBACK_MODULE_RESET = 13 + + -- Constant strings defining the keywords to replace in the default messages. + TAG_PLAYERNAME = "|PLAYERNAME|" + TAG_ITEMCOUNT = "|ITEMCOUNT|" + TAG_TOTALCOST = "|TOTALCOST|" + TAG_ITEMNAME = "|ITEMNAME|" + + NpcHandler = { + keywordHandler = nil, + focuses = nil, + talkStart = nil, + idleTime = 86400, + talkRadius = 3, + talkDelayTime = 1, -- Seconds to delay outgoing messages. + talkDelay = nil, + callbackFunctions = nil, + modules = nil, + shopItems = nil, -- They must be here since ShopModule uses 'static' functions + eventSay = nil, + eventDelayedSay = nil, + topic = nil, + messages = { + -- These are the default replies of all npcs. They can/should be changed individually for each npc. + [MESSAGE_GREET] = "Greetings, |PLAYERNAME|.", + [MESSAGE_FAREWELL] = "Good bye, |PLAYERNAME|.", + [MESSAGE_BUY] = "Do you want to buy |ITEMCOUNT| |ITEMNAME| for |TOTALCOST| gold coins?", + [MESSAGE_ONBUY] = "Here you are.", + [MESSAGE_BOUGHT] = "Bought |ITEMCOUNT|x |ITEMNAME| for |TOTALCOST| gold.", + [MESSAGE_SELL] = "Do you want to sell |ITEMCOUNT| |ITEMNAME| for |TOTALCOST| gold coins?", + [MESSAGE_ONSELL] = "Here you are, |TOTALCOST| gold.", + [MESSAGE_SOLD] = "Sold |ITEMCOUNT|x |ITEMNAME| for |TOTALCOST| gold.", + [MESSAGE_MISSINGMONEY] = "You don't have enough money.", + [MESSAGE_NEEDMONEY] = "You don't have enough money.", + [MESSAGE_MISSINGITEM] = "You don't have so many.", + [MESSAGE_NEEDITEM] = "You do not have this object.", + [MESSAGE_NEEDSPACE] = "You do not have enough capacity.", + [MESSAGE_NEEDMORESPACE] = "You do not have enough capacity for all items.", + [MESSAGE_IDLETIMEOUT] = "Good bye.", + [MESSAGE_WALKAWAY] = "Good bye.", + [MESSAGE_DECLINE] = "Then not.", + [MESSAGE_SENDTRADE] = "Of course, just browse through my wares.", + [MESSAGE_NOSHOP] = "Sorry, I'm not offering anything.", + [MESSAGE_ONCLOSESHOP] = "Thank you, come back whenever you're in need of something else.", + [MESSAGE_ALREADYFOCUSED]= "|PLAYERNAME|, I am already talking to you.", + [MESSAGE_WALKAWAY_MALE] = "Good bye.", + [MESSAGE_WALKAWAY_FEMALE] = "Good bye." + } + } + + -- Creates a new NpcHandler with an empty callbackFunction stack. + function NpcHandler:new(keywordHandler) + local obj = {} + obj.callbackFunctions = {} + obj.modules = {} + obj.eventSay = {} + obj.eventDelayedSay = {} + obj.topic = {} + obj.focuses = {} + obj.talkStart = {} + obj.talkDelay = {} + obj.keywordHandler = keywordHandler + obj.messages = {} + obj.shopItems = {} + + setmetatable(obj.messages, self.messages) + self.messages.__index = self.messages + + setmetatable(obj, self) + self.__index = self + return obj + end + + -- Re-defines the maximum idle time allowed for a player when talking to this npc. + function NpcHandler:setMaxIdleTime(newTime) + self.idleTime = newTime + end + + -- Attackes a new keyword handler to this npchandler + function NpcHandler:setKeywordHandler(newHandler) + self.keywordHandler = newHandler + end + + -- Function used to change the focus of this npc. + function NpcHandler:addFocus(newFocus) + if(self:isFocused(newFocus)) then + return + end + + table.insert(self.focuses, newFocus) + self.topic[newFocus] = 0 + local callback = self:getCallback(CALLBACK_ONADDFOCUS) + if(callback == nil or callback(newFocus)) then + self:processModuleCallback(CALLBACK_ONADDFOCUS, newFocus) + end + self:updateFocus() + end + + -- Function used to verify if npc is focused to certain player + function NpcHandler:isFocused(focus) + for k,v in pairs(self.focuses) do + if v == focus then + return true + end + end + return false + end + + -- This function should be called on each onThink and makes sure the npc faces the player it is talking to. + -- Should also be called whenever a new player is focused. + function NpcHandler:updateFocus() + for pos, focus in pairs(self.focuses) do + if(focus ~= nil) then + doNpcSetCreatureFocus(focus) + return + end + end + doNpcSetCreatureFocus(0) + end + + -- Used when the npc should un-focus the player. + function NpcHandler:releaseFocus(focus) + if(shop_cost[focus] ~= nil) then + table.remove(shop_amount, focus) + table.remove(shop_cost, focus) + table.remove(shop_rlname, focus) + table.remove(shop_itemid, focus) + table.remove(shop_container, focus) + table.remove(shop_npcuid, focus) + table.remove(shop_eventtype, focus) + table.remove(shop_subtype, focus) + table.remove(shop_destination, focus) + table.remove(shop_premium, focus) + end + + if self.eventDelayedSay[focus] then + self:cancelNPCTalk(self.eventDelayedSay[focus]) + end + + if(not self:isFocused(focus)) then + return + end + + local pos = nil + for k,v in pairs(self.focuses) do + if v == focus then + pos = k + end + end + table.remove(self.focuses, pos) + + self.eventSay[focus] = nil + self.eventDelayedSay[focus] = nil + self.talkStart[focus] = nil + self.topic[focus] = nil + + local callback = self:getCallback(CALLBACK_ONRELEASEFOCUS) + if(callback == nil or callback(focus)) then + self:processModuleCallback(CALLBACK_ONRELEASEFOCUS, focus) + end + + if isPlayer(focus) == TRUE then + closeShopWindow(focus) --Even if it can not exist, we need to prevent it. + self:updateFocus() + end + end + + -- Returns the callback function with the specified id or nil if no such callback function exists. + function NpcHandler:getCallback(id) + local ret = nil + if(self.callbackFunctions ~= nil) then + ret = self.callbackFunctions[id] + end + return ret + end + + -- Changes the callback function for the given id to callback. + function NpcHandler:setCallback(id, callback) + if(self.callbackFunctions ~= nil) then + self.callbackFunctions[id] = callback + end + end + + -- Adds a module to this npchandler and inits it. + function NpcHandler:addModule(module) + if(self.modules ~= nil) then + table.insert(self.modules, module) + module:init(self) + end + end + + -- Calls the callback function represented by id for all modules added to this npchandler with the given arguments. + function NpcHandler:processModuleCallback(id, ...) + local ret = true + for i, module in pairs(self.modules) do + local tmpRet = true + if(id == CALLBACK_CREATURE_APPEAR and module.callbackOnCreatureAppear ~= nil) then + tmpRet = module:callbackOnCreatureAppear(...) + elseif(id == CALLBACK_CREATURE_DISAPPEAR and module.callbackOnCreatureDisappear ~= nil) then + tmpRet = module:callbackOnCreatureDisappear(...) + elseif(id == CALLBACK_CREATURE_SAY and module.callbackOnCreatureSay ~= nil) then + tmpRet = module:callbackOnCreatureSay(...) + elseif(id == CALLBACK_PLAYER_ENDTRADE and module.callbackOnPlayerEndTrade ~= nil) then + tmpRet = module:callbackOnPlayerEndTrade(...) + elseif(id == CALLBACK_PLAYER_CLOSECHANNEL and module.callbackOnPlayerCloseChannel ~= nil) then + tmpRet = module:callbackOnPlayerCloseChannel(...) + elseif(id == CALLBACK_ONBUY and module.callbackOnBuy ~= nil) then + tmpRet = module:callbackOnBuy(...) + elseif(id == CALLBACK_ONSELL and module.callbackOnSell ~= nil) then + tmpRet = module:callbackOnSell(...) + elseif(id == CALLBACK_ONTRADEREQUEST and module.callbackOnTradeRequest ~= nil) then + tmpRet = module:callbackOnTradeRequest(...) + elseif(id == CALLBACK_ONADDFOCUS and module.callbackOnAddFocus ~= nil) then + tmpRet = module:callbackOnAddFocus(...) + elseif(id == CALLBACK_ONRELEASEFOCUS and module.callbackOnReleaseFocus ~= nil) then + tmpRet = module:callbackOnReleaseFocus(...) + elseif(id == CALLBACK_ONTHINK and module.callbackOnThink ~= nil) then + tmpRet = module:callbackOnThink(...) + elseif(id == CALLBACK_GREET and module.callbackOnGreet ~= nil) then + tmpRet = module:callbackOnGreet(...) + elseif(id == CALLBACK_FAREWELL and module.callbackOnFarewell ~= nil) then + tmpRet = module:callbackOnFarewell(...) + elseif(id == CALLBACK_MESSAGE_DEFAULT and module.callbackOnMessageDefault ~= nil) then + tmpRet = module:callbackOnMessageDefault(...) + elseif(id == CALLBACK_MODULE_RESET and module.callbackOnModuleReset ~= nil) then + tmpRet = module:callbackOnModuleReset(...) + end + if(not tmpRet) then + ret = false + break + end + end + return ret + end + + -- Returns the message represented by id. + function NpcHandler:getMessage(id) + local ret = nil + if(self.messages ~= nil) then + ret = self.messages[id] + end + return ret + end + + -- Changes the default response message with the specified id to newMessage. + function NpcHandler:setMessage(id, newMessage) + if(self.messages ~= nil) then + self.messages[id] = newMessage + end + end + + -- Translates all message tags found in msg using parseInfo + function NpcHandler:parseMessage(msg, parseInfo) + local ret = msg + for search, replace in pairs(parseInfo) do + ret = string.gsub(ret, search, replace) + end + return ret + end + + -- Makes sure the npc un-focuses the currently focused player + function NpcHandler:unGreet(cid) + if(not self:isFocused(cid)) then + return + end + + local callback = self:getCallback(CALLBACK_FAREWELL) + if(callback == nil or callback()) then + if(self:processModuleCallback(CALLBACK_FAREWELL)) then + local msg = self:getMessage(MESSAGE_FAREWELL) + local parseInfo = { [TAG_PLAYERNAME] = getPlayerName(cid) } + msg = self:parseMessage(msg, parseInfo) + self:say(msg, cid, true) + self:releaseFocus(cid) + end + end + end + + -- Greets a new player. + function NpcHandler:greet(cid) + if(cid ~= 0) then + local callback = self:getCallback(CALLBACK_GREET) + if(callback == nil or callback(cid)) then + if(self:processModuleCallback(CALLBACK_GREET, cid)) then + local msg = self:getMessage(MESSAGE_GREET) + local parseInfo = { [TAG_PLAYERNAME] = getCreatureName(cid) } + msg = self:parseMessage(msg, parseInfo) + self:say(msg, cid, true) + else + return + end + else + return + end + end + self:addFocus(cid) + end + + -- Handles onCreatureAppear events. If you with to handle this yourself, please use the CALLBACK_CREATURE_APPEAR callback. + function NpcHandler:onCreatureAppear(cid) + local callback = self:getCallback(CALLBACK_CREATURE_APPEAR) + if(callback == nil or callback(cid)) then + if(self:processModuleCallback(CALLBACK_CREATURE_APPEAR, cid)) then + -- + end + end + end + + -- Handles onCreatureDisappear events. If you with to handle this yourself, please use the CALLBACK_CREATURE_DISAPPEAR callback. + function NpcHandler:onCreatureDisappear(cid) + local callback = self:getCallback(CALLBACK_CREATURE_DISAPPEAR) + if(callback == nil or callback(cid)) then + if(self:processModuleCallback(CALLBACK_CREATURE_DISAPPEAR, cid)) then + if(self:isFocused(cid)) then + self:unGreet(cid) + end + end + end + end + + -- Handles onCreatureSay events. If you with to handle this yourself, please use the CALLBACK_CREATURE_SAY callback. + function NpcHandler:onCreatureSay(cid, msgtype, msg) + local callback = self:getCallback(CALLBACK_CREATURE_SAY) + if(callback == nil or callback(cid, msgtype, msg)) then + if(self:processModuleCallback(CALLBACK_CREATURE_SAY, cid, msgtype, msg)) then + if(not self:isInRange(cid)) then + return + end + + if(self.keywordHandler ~= nil) then + if(self:isFocused(cid) and (msgtype == TALKTYPE_PRIVATE_PN) or (not self:isFocused(cid))) then + local ret = self.keywordHandler:processMessage(cid, msg) + if(not ret) then + local callback = self:getCallback(CALLBACK_MESSAGE_DEFAULT) + if(callback ~= nil and callback(cid, msgtype, msg)) then + self.talkStart[cid] = os.time() + end + else + self.talkStart[cid] = os.time() + end + end + end + end + end + end + + -- Handles onPlayerEndTrade events. If you wish to handle this yourself, use the CALLBACK_PLAYER_ENDTRADE callback. + function NpcHandler:onPlayerEndTrade(cid) + local callback = self:getCallback(CALLBACK_PLAYER_ENDTRADE) + if(callback == nil or callback(cid)) then + if(self:processModuleCallback(CALLBACK_PLAYER_ENDTRADE, cid, msgtype, msg)) then + if(self:isFocused(cid)) then + local parseInfo = { [TAG_PLAYERNAME] = getPlayerName(cid) } + local msg = self:parseMessage(self:getMessage(MESSAGE_ONCLOSESHOP), parseInfo) + self:say(msg, cid) + end + end + end + end + + -- Handles onPlayerCloseChannel events. If you wish to handle this yourself, use the CALLBACK_PLAYER_CLOSECHANNEL callback. + function NpcHandler:onPlayerCloseChannel(cid) + local callback = self:getCallback(CALLBACK_PLAYER_CLOSECHANNEL) + if(callback == nil or callback(cid)) then + if(self:processModuleCallback(CALLBACK_PLAYER_CLOSECHANNEL, cid, msgtype, msg)) then + if(self:isFocused(cid)) then + self:unGreet(cid) + end + end + end + end + + -- Handles onBuy events. If you wish to handle this yourself, use the CALLBACK_ONBUY callback. + function NpcHandler:onBuy(cid, itemid, subType, amount, ignoreCap, inBackpacks) + local callback = self:getCallback(CALLBACK_ONBUY) + if(callback == nil or callback(cid, itemid, subType, amount, ignoreCap, inBackpacks)) then + if(self:processModuleCallback(CALLBACK_ONBUY, cid, itemid, subType, amount, ignoreCap, inBackpacks)) then + -- + end + end + end + + -- Handles onSell events. If you wish to handle this yourself, use the CALLBACK_ONSELL callback. + function NpcHandler:onSell(cid, itemid, subType, amount, ignoreCap, inBackpacks) + local callback = self:getCallback(CALLBACK_ONSELL) + if(callback == nil or callback(cid, itemid, subType, amount, ignoreCap, inBackpacks)) then + if(self:processModuleCallback(CALLBACK_ONSELL, cid, itemid, subType, amount, ignoreCap, inBackpacks)) then + -- + end + end + end + + -- Handles onTradeRequest events. If you wish to handle this yourself, use the CALLBACK_ONTRADEREQUEST callback. + function NpcHandler:onTradeRequest(cid) + local callback = self:getCallback(CALLBACK_ONTRADEREQUEST) + if(callback == nil or callback(cid)) then + if(self:processModuleCallback(CALLBACK_ONTRADEREQUEST, cid)) then + return true + end + end + return false + end + + -- Handles onThink events. If you wish to handle this yourself, please use the CALLBACK_ONTHINK callback. + function NpcHandler:onThink() + local callback = self:getCallback(CALLBACK_ONTHINK) + if(callback == nil or callback()) then + if(NPCHANDLER_TALKDELAY == TALKDELAY_ONTHINK) then + for cid, talkDelay in pairs(self.talkDelay) do + if(talkDelay.time ~= nil and talkDelay.message ~= nil and os.time() >= talkDelay.time) then + selfSay(talkDelay.message, cid, talkDelay.publicize and TRUE or FALSE) + self.talkDelay[cid] = nil + end + end + end + + if(self:processModuleCallback(CALLBACK_ONTHINK)) then + for pos, focus in pairs(self.focuses) do + if(focus ~= nil) then + if(not self:isInRange(focus)) then + self:onWalkAway(focus) + elseif(self.talkStart[focus] ~= nil and (os.time() - self.talkStart[focus]) > self.idleTime) then + self:unGreet(focus) + else + self:updateFocus() + end + end + end + end + end + end + + -- Tries to greet the player with the given cid. + function NpcHandler:onGreet(cid) + if(self:isInRange(cid)) then + if(not self:isFocused(cid)) then + self:greet(cid) + return + end + end + end + + -- Simply calls the underlying unGreet function. + function NpcHandler:onFarewell(cid) + self:unGreet(cid) + end + + -- Should be called on this npc's focus if the distance to focus is greater then talkRadius. + function NpcHandler:onWalkAway(cid) + if(self:isFocused(cid)) then + local callback = self:getCallback(CALLBACK_CREATURE_DISAPPEAR) + if(callback == nil or callback()) then + if(self:processModuleCallback(CALLBACK_CREATURE_DISAPPEAR, cid)) then + local msg = self:getMessage(MESSAGE_WALKAWAY) + local playerName = getPlayerName(cid) + if not playerName then + playerName = -1 + end + + local parseInfo = { [TAG_PLAYERNAME] = playerName } + local message = self:parseMessage(msg, parseInfo) + + local msg_male = self:getMessage(MESSAGE_WALKAWAY_MALE) + local message_male = self:parseMessage(msg_male, parseInfo) + local msg_female = self:getMessage(MESSAGE_WALKAWAY_FEMALE) + local message_female = self:parseMessage(msg_female, parseInfo) + if message_female ~= message_male then + if getPlayerSex(cid) == 0 then + selfSay(message_female) + else + selfSay(message_male) + end + elseif message ~= "" then + selfSay(message) + end + self:releaseFocus(cid) + end + end + end + end + + -- Returns true if cid is within the talkRadius of this npc. + function NpcHandler:isInRange(cid) + local distance = isPlayer(cid) == TRUE and getDistanceTo(cid) or -1 + if distance == -1 then + return false + end + + return (distance <= self.talkRadius) + end + + -- Resets the npc into its initial state (in regard of the keywordhandler). + -- All modules are also receiving a reset call through their callbackOnModuleReset function. + function NpcHandler:resetNpc() + if(self:processModuleCallback(CALLBACK_MODULE_RESET)) then + self.keywordHandler:reset() + end + end + + function NpcHandler:cancelNPCTalk(events) + for aux = 1, #events do + stopEvent(events[aux].event) + end + events = nil + end + + function NpcHandler:doNPCTalkALot(msgs, interval, pcid) + if self.eventDelayedSay[pcid] then + self:cancelNPCTalk(self.eventDelayedSay[pcid]) + end + + self.eventDelayedSay[pcid] = {} + local ret = {} + for aux = 1, #msgs do + self.eventDelayedSay[pcid][aux] = {} + doCreatureSayWithDelay(getNpcCid(), msgs[aux], TALKTYPE_PRIVATE_NP, ((aux-1) * (interval or 10000)) + 1000, self.eventDelayedSay[pcid][aux], pcid) + table.insert(ret, self.eventDelayedSay[pcid][aux]) + end + return(ret) + end + + -- Makes the npc represented by this instance of NpcHandler say something. + -- This implements the currently set type of talkdelay. + -- shallDelay is a boolean value. If it is false, the message is not delayed. Default value is true. + function NpcHandler:say(message, focus, publicize, shallDelay, delay) + if(type(message) == "table") then + return self:doNPCTalkALot(message, delay or 10000, focus) + end + + if self.eventDelayedSay[focus] then + self:cancelNPCTalk(self.eventDelayedSay[focus]) + end + + local shallDelay = not shallDelay and true or shallDelay + if(NPCHANDLER_TALKDELAY == TALKDELAY_NONE or shallDelay == false) then + selfSay(message, focus, publicize and TRUE or FALSE) + return + end + + stopEvent(self.eventSay[focus]) + self.eventSay[focus] = addEvent(function(x) if isPlayer(x[3]) then doCreatureSay(x[1], x[2], TALKTYPE_PRIVATE_NP, false, x[3], getCreaturePosition(x[1])) end end, self.talkDelayTime * 1000, {getNpcCid(), message, focus}) + end +end diff --git a/data/npc/lib/npcsystem/npcsystem.lua b/data/npc/lib/npcsystem/npcsystem.lua new file mode 100644 index 0000000000..8a8b12a06f --- /dev/null +++ b/data/npc/lib/npcsystem/npcsystem.lua @@ -0,0 +1,177 @@ +-- Advanced NPC System by Jiddo + +shop_amount = {} +shop_cost = {} +shop_rlname = {} +shop_itemid = {} +shop_container = {} +shop_npcuid = {} +shop_eventtype = {} +shop_subtype = {} +shop_destination = {} +shop_premium = {} + +npcs_loaded_shop = {} +npcs_loaded_travel = {} + +if(NpcSystem == nil) then + -- Loads the underlying classes of the npcsystem. + dofile('data/npc/lib/npcsystem/keywordhandler.lua') + dofile('data/npc/lib/npcsystem/npchandler.lua') + dofile('data/npc/lib/npcsystem/modules.lua') + + -- Global npc constants: + + -- Greeting and unGreeting keywords. For more information look at the top of modules.lua + FOCUS_GREETWORDS = {'hi', 'hello'} + FOCUS_FAREWELLWORDS = {'bye', 'farewell'} + + -- The word for requesting trade window. For more information look at the top of modules.lua + SHOP_TRADEREQUEST = {'trade'} + + -- The word for accepting/declining an offer. CAN ONLY CONTAIN ONE FIELD! For more information look at the top of modules.lua + SHOP_YESWORD = {'yes'} + SHOP_NOWORD = {'no'} + + -- Pattern used to get the amount of an item a player wants to buy/sell. + PATTERN_COUNT = '%d+' + + -- Talkdelay behavior. For more information, look at the top of npchandler.lua. + NPCHANDLER_TALKDELAY = TALKDELAY_ONTHINK + + -- Constant strings defining the keywords to replace in the default messages. + -- For more information, look at the top of npchandler.lua... + TAG_PLAYERNAME = '|PLAYERNAME|' + TAG_ITEMCOUNT = '|ITEMCOUNT|' + TAG_TOTALCOST = '|TOTALCOST|' + TAG_ITEMNAME = '|ITEMNAME|' + + NpcSystem = {} + + -- Gets an npcparameter with the specified key. Returns nil if no such parameter is found. + function NpcSystem.getParameter(key) + local ret = getNpcParameter(tostring(key)) + if((type(ret) == 'number' and ret == 0) or ret == nil) then + return nil + else + return ret + end + end + + -- Parses all known parameters for the npc. Also parses parseable modules. + function NpcSystem.parseParameters(npcHandler) + local ret = NpcSystem.getParameter('idletime') + if(ret ~= nil) then + npcHandler.idleTime = tonumber(ret) + end + local ret = NpcSystem.getParameter('talkradius') + if(ret ~= nil) then + npcHandler.talkRadius = tonumber(ret) + end + local ret = NpcSystem.getParameter('message_greet') + if(ret ~= nil) then + npcHandler:setMessage(MESSAGE_GREET, ret) + end + local ret = NpcSystem.getParameter('message_farewell') + if(ret ~= nil) then + npcHandler:setMessage(MESSAGE_FAREWELL, ret) + end + local ret = NpcSystem.getParameter('message_decline') + if(ret ~= nil) then + npcHandler:setMessage(MESSAGE_DECLINE, ret) + end + local ret = NpcSystem.getParameter('message_needmorespace') + if(ret ~= nil) then + npcHandler:setMessage(MESSAGE_NEEDMORESPACE, ret) + end + local ret = NpcSystem.getParameter('message_needspace') + if(ret ~= nil) then + npcHandler:setMessage(MESSAGE_NEEDSPACE, ret) + end + local ret = NpcSystem.getParameter('message_sendtrade') + if(ret ~= nil) then + npcHandler:setMessage(MESSAGE_SENDTRADE, ret) + end + local ret = NpcSystem.getParameter('message_noshop') + if(ret ~= nil) then + npcHandler:setMessage(MESSAGE_NOSHOP, ret) + end + local ret = NpcSystem.getParameter('message_oncloseshop') + if(ret ~= nil) then + npcHandler:setMessage(MESSAGE_ONCLOSESHOP, ret) + end + local ret = NpcSystem.getParameter('message_onbuy') + if(ret ~= nil) then + npcHandler:setMessage(MESSAGE_ONBUY, ret) + end + local ret = NpcSystem.getParameter('message_onsell') + if(ret ~= nil) then + npcHandler:setMessage(MESSAGE_ONSELL, ret) + end + local ret = NpcSystem.getParameter('message_missingmoney') + if(ret ~= nil) then + npcHandler:setMessage(MESSAGE_MISSINGMONEY, ret) + end + local ret = NpcSystem.getParameter('message_needmoney') + if(ret ~= nil) then + npcHandler:setMessage(MESSAGE_NEEDMONEY, ret) + end + local ret = NpcSystem.getParameter('message_missingitem') + if(ret ~= nil) then + npcHandler:setMessage(MESSAGE_MISSINGITEM, ret) + end + local ret = NpcSystem.getParameter('message_needitem') + if(ret ~= nil) then + npcHandler:setMessage(MESSAGE_NEEDITEM, ret) + end + local ret = NpcSystem.getParameter('message_idletimeout') + if(ret ~= nil) then + npcHandler:setMessage(MESSAGE_IDLETIMEOUT, ret) + end + local ret = NpcSystem.getParameter('message_walkaway') + if(ret ~= nil) then + npcHandler:setMessage(MESSAGE_WALKAWAY, ret) + end + local ret = NpcSystem.getParameter('message_alreadyfocused') + if(ret ~= nil) then + npcHandler:setMessage(MESSAGE_ALREADYFOCUSED, ret) + end + local ret = NpcSystem.getParameter('message_buy') + if(ret ~= nil) then + npcHandler:setMessage(MESSAGE_BUY, ret) + end + local ret = NpcSystem.getParameter('message_sell') + if(ret ~= nil) then + npcHandler:setMessage(MESSAGE_SELL, ret) + end + local ret = NpcSystem.getParameter('message_bought') + if(ret ~= nil) then + npcHandler:setMessage(MESSAGE_BOUGHT, ret) + end + local ret = NpcSystem.getParameter('message_sold') + if(ret ~= nil) then + npcHandler:setMessage(MESSAGE_SOLD, ret) + end + local ret = NpcSystem.getParameter('message_walkaway_male') + if(ret ~= nil) then + npcHandler:setMessage(MESSAGE_WALKAWAY_MALE, ret) + end + local ret = NpcSystem.getParameter('message_walkaway_female') + if(ret ~= nil) then + npcHandler:setMessage(MESSAGE_WALKAWAY_FEMALE, ret) + end + + -- Parse modules. + for parameter, module in pairs(Modules.parseableModules) do + local ret = NpcSystem.getParameter(parameter) + if(ret ~= nil) then + local number = tonumber(ret) + if(number ~= 0 and module.parseParameters ~= nil) then + local instance = module:new() + npcHandler:addModule(instance) + instance:parseParameters() + end + end + end + end +end \ No newline at end of file diff --git a/data/npc/scripts/addons.lua b/data/npc/scripts/addons.lua new file mode 100644 index 0000000000..6ab1be9884 --- /dev/null +++ b/data/npc/scripts/addons.lua @@ -0,0 +1,45 @@ +local keywordHandler = KeywordHandler:new() +local npcHandler = NpcHandler:new(keywordHandler) +NpcSystem.parseParameters(npcHandler) + +function onCreatureAppear(cid) npcHandler:onCreatureAppear(cid) end +function onCreatureDisappear(cid) npcHandler:onCreatureDisappear(cid) end +function onCreatureSay(cid, type, msg) npcHandler:onCreatureSay(cid, type, msg) end +function onThink() npcHandler:onThink() end + +function buyAddons(cid, message, keywords, parameters, node) + --TODO: buyAddons function in modules.lua + if(not npcHandler:isFocused(cid)) then + return false + end + + local addon = parameters.addon + local cost = parameters.cost + local premium = (parameters.premium ~= nil and parameters.premium) + + if isPlayerPremiumCallback == nil or (isPlayerPremiumCallback(cid) and premium) then + if doPlayerRemoveMoney(cid, cost) == TRUE then + doPlayerAddAddons(cid, addon) + npcHandler:say('There, you are now able to use all addons!', cid) + else + npcHandler:say('Sorry, you do not have enough money.', cid) + end + else + npcHandler:say('I only serve customers with premium accounts.', cid) + end + + keywordHandler:moveUp(1) + return true +end + +local node1 = keywordHandler:addKeyword({'first addon'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = 'Do you want to buy the first addons set for 5000 gold coins?'}) + node1:addChildKeyword({'yes'}, buyAddons, {addon = 1, cost = 5000, premium = true}) + node1:addChildKeyword({'no'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, moveup = 1, text = 'Too expensive, eh?'}) + +local node2 = keywordHandler:addKeyword({'second addon'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = 'Would you like to buy the second addons set for 10000 gold coins?'}) + node2:addChildKeyword({'yes'}, buyAddons, {addon = 2, cost = 10000, premium = true}) + node2:addChildKeyword({'no'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, moveup = 1, text = 'Too expensive, eh?'}) + +keywordHandler:addKeyword({'addon'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = 'I sell the first addons set for 5000 gold coins and the second addons set for 10000 gold coins.'}) + +npcHandler:addModule(FocusModule:new()) \ No newline at end of file diff --git a/data/npc/scripts/bless.lua b/data/npc/scripts/bless.lua new file mode 100644 index 0000000000..215e125b3d --- /dev/null +++ b/data/npc/scripts/bless.lua @@ -0,0 +1,32 @@ +local keywordHandler = KeywordHandler:new() +local npcHandler = NpcHandler:new(keywordHandler) +NpcSystem.parseParameters(npcHandler) + + +function onCreatureAppear(cid) npcHandler:onCreatureAppear(cid) end +function onCreatureDisappear(cid) npcHandler:onCreatureDisappear(cid) end +function onCreatureSay(cid, type, msg) npcHandler:onCreatureSay(cid, type, msg) end +function onThink() npcHandler:onThink() end + + +local node1 = keywordHandler:addKeyword({'first bless'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = 'Do you want to buy the first blessing for 10000 gold?'}) + node1:addChildKeyword({'yes'}, StdModule.bless, {npcHandler = npcHandler, bless = 1, premium = true, cost = 10000}) + node1:addChildKeyword({'no'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, reset = true, text = 'Too expensive, eh?'}) + +local node2 = keywordHandler:addKeyword({'second bless'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = 'Do you want to buy the second blessing for 10000 gold?'}) + node2:addChildKeyword({'yes'}, StdModule.bless, {npcHandler = npcHandler, bless = 2, premium = true, cost = 10000}) + node2:addChildKeyword({'no'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, reset = true, text = 'Too expensive, eh?'}) + +local node3 = keywordHandler:addKeyword({'third bless'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = 'Do you want to buy the third blessing for 10000 gold?'}) + node3:addChildKeyword({'yes'}, StdModule.bless, {npcHandler = npcHandler, bless = 3, premium = true, cost = 10000}) + node3:addChildKeyword({'no'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, reset = true, text = 'Too expensive, eh?'}) + +local node4 = keywordHandler:addKeyword({'fourth bless'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = 'Do you want to buy the fourth blessing for 10000 gold?'}) + node4:addChildKeyword({'yes'}, StdModule.bless, {npcHandler = npcHandler, bless = 4, premium = true, cost = 10000}) + node4:addChildKeyword({'no'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, reset = true, text = 'Too expensive, eh?'}) + +local node5 = keywordHandler:addKeyword({'fifth bless'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = 'Do you want to buy the fifth blessing for 10000 gold?'}) + node5:addChildKeyword({'yes'}, StdModule.bless, {npcHandler = npcHandler, bless = 5, premium = true, cost = 10000}) + node5:addChildKeyword({'no'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, reset = true, text = 'Too expensive, eh?'}) + +npcHandler:addModule(FocusModule:new()) \ No newline at end of file diff --git a/data/npc/scripts/default.lua b/data/npc/scripts/default.lua new file mode 100644 index 0000000000..022d9603fc --- /dev/null +++ b/data/npc/scripts/default.lua @@ -0,0 +1,10 @@ +local keywordHandler = KeywordHandler:new() +local npcHandler = NpcHandler:new(keywordHandler) +NpcSystem.parseParameters(npcHandler) + +function onCreatureAppear(cid) npcHandler:onCreatureAppear(cid) end +function onCreatureDisappear(cid) npcHandler:onCreatureDisappear(cid) end +function onCreatureSay(cid, type, msg) npcHandler:onCreatureSay(cid, type, msg) end +function onThink() npcHandler:onThink() end + +npcHandler:addModule(FocusModule:new()) diff --git a/data/npc/scripts/promotion.lua b/data/npc/scripts/promotion.lua new file mode 100644 index 0000000000..3e258ebf1b --- /dev/null +++ b/data/npc/scripts/promotion.lua @@ -0,0 +1,14 @@ +local keywordHandler = KeywordHandler:new() +local npcHandler = NpcHandler:new(keywordHandler) +NpcSystem.parseParameters(npcHandler) + +function onCreatureAppear(cid) npcHandler:onCreatureAppear(cid) end +function onCreatureDisappear(cid) npcHandler:onCreatureDisappear(cid) end +function onCreatureSay(cid, type, msg) npcHandler:onCreatureSay(cid, type, msg) end +function onThink() npcHandler:onThink() end + +local node1 = keywordHandler:addKeyword({'promot'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = 'I can promote you for 20000 gold coins. Do you want me to promote you?'}) + node1:addChildKeyword({'yes'}, StdModule.promotePlayer, {npcHandler = npcHandler, cost = 20000, level = 20, text = 'Congratulations! You are now promoted.'}) + node1:addChildKeyword({'no'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = 'Alright then, come back when you are ready.', reset = true}) + +npcHandler:addModule(FocusModule:new()) diff --git a/data/npc/scripts/runes.lua b/data/npc/scripts/runes.lua new file mode 100644 index 0000000000..4f02826d24 --- /dev/null +++ b/data/npc/scripts/runes.lua @@ -0,0 +1,131 @@ +local keywordHandler = KeywordHandler:new() +local npcHandler = NpcHandler:new(keywordHandler) +NpcSystem.parseParameters(npcHandler) +local talkState = {} + +function onCreatureAppear(cid) npcHandler:onCreatureAppear(cid) end +function onCreatureDisappear(cid) npcHandler:onCreatureDisappear(cid) end +function onCreatureSay(cid, type, msg) npcHandler:onCreatureSay(cid, type, msg) end +function onThink() npcHandler:onThink() end + +local shopModule = ShopModule:new() +npcHandler:addModule(shopModule) + +shopModule:addBuyableItem({'spellbook'}, 2175, 150, 'spellbook') +shopModule:addBuyableItem({'magic lightwand'}, 2163, 400, 'magic lightwand') + +shopModule:addBuyableItem({'small health'}, 8704, 20, 1, 'small health potion') +shopModule:addBuyableItem({'health potion'}, 7618, 45, 1, 'health potion') +shopModule:addBuyableItem({'mana potion'}, 7620, 50, 1, 'mana potion') +shopModule:addBuyableItem({'strong health'}, 7588, 100, 1, 'strong health potion') +shopModule:addBuyableItem({'strong mana'}, 7589, 80, 1, 'strong mana potion') +shopModule:addBuyableItem({'great health'}, 7591, 190, 1, 'great health potion') +shopModule:addBuyableItem({'great mana'}, 7590, 120, 1, 'great mana potion') +shopModule:addBuyableItem({'great spirit'}, 8472, 190, 1, 'great spirit potion') +shopModule:addBuyableItem({'ultimate health'}, 8473, 310, 1, 'ultimate health potion') +shopModule:addBuyableItem({'antidote potion'}, 8474, 50, 1, 'antidote potion') + +shopModule:addSellableItem({'normal potion flask', 'normal flask'}, 7636, 5, 'empty small potion flask') +shopModule:addSellableItem({'strong potion flask', 'strong flask'}, 7634, 10, 'empty strong potion flask') +shopModule:addSellableItem({'great potion flask', 'great flask'}, 7635, 15, 'empty great potion flask') + +shopModule:addBuyableItem({'instense healing'}, 2265, 95, 1, 'intense healing rune') +shopModule:addBuyableItem({'ultimate healing'}, 2273, 175, 1, 'ultimate healing rune') +shopModule:addBuyableItem({'magic wall'}, 2293, 350, 3, 'magic wall rune') +shopModule:addBuyableItem({'destroy field'}, 2261, 45, 3, 'destroy field rune') +shopModule:addBuyableItem({'light magic missile'}, 2287, 40, 10, 'light magic missile rune') +shopModule:addBuyableItem({'heavy magic missile'}, 2311, 120, 10, 'heavy magic missile rune') +shopModule:addBuyableItem({'great fireball'}, 2304, 180, 4, 'great fireball rune') +shopModule:addBuyableItem({'explosion'}, 2313, 250, 6, 'explosion rune') +shopModule:addBuyableItem({'sudden death'}, 2268, 350, 3, 'sudden death rune') +shopModule:addBuyableItem({'death arrow'}, 2263, 300, 3, 'death arrow rune') +shopModule:addBuyableItem({'paralyze'}, 2278, 700, 1, 'paralyze rune') +shopModule:addBuyableItem({'animate dead'}, 2316, 375, 1, 'animate dead rune') +shopModule:addBuyableItem({'convince creature'}, 2290, 80, 1, 'convince creature rune') +shopModule:addBuyableItem({'chameleon'}, 2291, 210, 1, 'chameleon rune') +shopModule:addBuyableItem({'desintegrate'}, 2310, 80, 3, 'desintegreate rune') + +shopModule:addBuyableItemContainer({'bp ap'}, 2002, 8378, 2000, 1, 'backpack of antidote potions') +shopModule:addBuyableItemContainer({'bp slhp'}, 2000, 8610, 400, 1, 'backpack of small health potions') +shopModule:addBuyableItemContainer({'bp hp'}, 2000, 7618, 900, 1, 'backpack of health potions') +shopModule:addBuyableItemContainer({'bp mp'}, 2001, 7620, 1000, 1, 'backpack of mana potions') +shopModule:addBuyableItemContainer({'bp shp'}, 2000, 7588, 2000, 1, 'backpack of strong health potions') +shopModule:addBuyableItemContainer({'bp smp'}, 2001, 7589, 1600, 1, 'backpack of strong mana potions') +shopModule:addBuyableItemContainer({'bp ghp'}, 2000, 7591, 3800, 1, 'backpack of great health potions') +shopModule:addBuyableItemContainer({'bp gmp'}, 2001, 7590, 2400, 1, 'backpack of great mana potions') +shopModule:addBuyableItemContainer({'bp gsp'}, 1999, 8376, 3800, 1, 'backpack of great spirit potions') +shopModule:addBuyableItemContainer({'bp uhp'}, 2000, 8377, 6200, 1, 'backpack of ultimate health potions') + +shopModule:addBuyableItem({'wand of vortex', 'vortex'}, 2190, 500, 'wand of vortex') +shopModule:addBuyableItem({'wand of dragonbreath', 'dragonbreath'}, 2191, 1000, 'wand of dragonbreath') +shopModule:addBuyableItem({'wand of decay', 'decay'}, 2188, 5000, 'wand of decay') +shopModule:addBuyableItem({'wand of draconia', 'draconia'}, 8921, 7500, 'wand of draconia') +shopModule:addBuyableItem({'wand of cosmic energy', 'cosmic energy'}, 2189, 10000, 'wand of cosmic energy') +shopModule:addBuyableItem({'wand of inferno', 'inferno'}, 2187, 15000, 'wand of inferno') +shopModule:addBuyableItem({'wand of starstorm', 'starstorm'}, 8920, 18000, 'wand of starstorm') +shopModule:addBuyableItem({'wand of voodoo', 'voodoo'}, 8922, 22000, 'wand of voodoo') + +shopModule:addBuyableItem({'snakebite rod', 'snakebite'}, 2182, 500, 'snakebite rod') +shopModule:addBuyableItem({'moonlight rod', 'moonlight'}, 2186, 1000, 'moonlight rod') +shopModule:addBuyableItem({'necrotic rod', 'necrotic'}, 2185, 5000, 'necrotic rod') +shopModule:addBuyableItem({'northwind rod', 'northwind'}, 8911, 7500, 'northwind rod') +shopModule:addBuyableItem({'terra rod', 'terra'}, 2181, 10000, 'terra rod') +shopModule:addBuyableItem({'hailstorm rod', 'hailstorm'}, 2183, 15000, 'hailstorm rod') +shopModule:addBuyableItem({'springsprout rod', 'springsprout'}, 8912, 18000, 'springsprout rod') +shopModule:addBuyableItem({'underworld rod', 'underworld'}, 8910, 22000, 'underworld rod') + +shopModule:addSellableItem({'wand of vortex', 'vortex'}, 2190, 250, 'wand of vortex') +shopModule:addSellableItem({'wand of dragonbreath', 'dragonbreath'}, 2191, 500, 'wand of dragonbreath') +shopModule:addSellableItem({'wand of decay', 'decay'}, 2188, 2500, 'wand of decay') +shopModule:addSellableItem({'wand of draconia', 'draconia'}, 8921, 3750, 'wand of draconia') +shopModule:addSellableItem({'wand of cosmic energy', 'cosmic energy'}, 2189, 5000, 'wand of cosmic energy') +shopModule:addSellableItem({'wand of inferno', 'inferno'},2187, 7500, 'wand of inferno') +shopModule:addSellableItem({'wand of starstorm', 'starstorm'}, 8920, 9000, 'wand of starstorm') +shopModule:addSellableItem({'wand of voodoo', 'voodoo'}, 8922, 11000, 'wand of voodoo') + +shopModule:addSellableItem({'snakebite rod', 'snakebite'}, 2182, 250,'snakebite rod') +shopModule:addSellableItem({'moonlight rod', 'moonlight'}, 2186, 500, 'moonlight rod') +shopModule:addSellableItem({'necrotic rod', 'necrotic'}, 2185, 2500, 'necrotic rod') +shopModule:addSellableItem({'northwind rod', 'northwind'}, 8911, 3750, 'northwind rod') +shopModule:addSellableItem({'terra rod', 'terra'}, 2181, 5000, 'terra rod') +shopModule:addSellableItem({'hailstorm rod', 'hailstorm'}, 2183, 7500, 'hailstorm rod') +shopModule:addSellableItem({'springsprout rod', 'springsprout'}, 8912, 9000, 'springsprout rod') +shopModule:addSellableItem({'underworld rod', 'underworld'}, 8910, 11000, 'underworld rod') + + +function creatureSayCallback(cid, type, msg) + if(not npcHandler:isFocused(cid)) then + return false + end + + local talkUser = NPCHANDLER_CONVBEHAVIOR == CONVERSATION_DEFAULT and 0 or cid + + local items = {[1] = 2190, [2] = 2182, [5] = 2190, [6] = 2182} + if(msgcontains(msg, 'first rod') or msgcontains(msg, 'first wand')) then + if(isSorcerer(cid) or isDruid(cid)) then + if(getPlayerStorageValue(cid, 30002) == -1) then + selfSay('So you ask me for a {' .. getItemNameById(items[getPlayerVocation(cid)]) .. '} to begin your advanture?', cid) + talkState[talkUser] = 1 + else + selfSay('What? I have already gave you one {' .. getItemNameById(items[getPlayerVocation(cid)]) .. '}!', cid) + end + else + selfSay('Sorry, you aren\'t a druid either a sorcerer.', cid) + end + elseif(msgcontains(msg, 'yes')) then + if(talkState[talkUser] == 1) then + doPlayerAddItem(cid, items[getPlayerVocation(cid)], 1) + selfSay('Here you are young adept, take care yourself.', cid) + setPlayerStorageValue(cid, 30002, 1) + end + talkState[talkUser] = 0 + elseif(msgcontains(msg, 'no') and isInArray({1}, talkState[talkUser]) == TRUE) then + selfSay('Ok then.', cid) + talkState[talkUser] = 0 + end + + return true +end + +npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback) +npcHandler:addModule(FocusModule:new()) diff --git a/data/raids/raids.xml b/data/raids/raids.xml new file mode 100644 index 0000000000..3b870dc58d --- /dev/null +++ b/data/raids/raids.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/data/spells/lib/spells.lua b/data/spells/lib/spells.lua new file mode 100644 index 0000000000..37c580106d --- /dev/null +++ b/data/spells/lib/spells.lua @@ -0,0 +1,192 @@ +--Pre-made areas + +--Waves +AREA_WAVE4 = { +{1, 1, 1, 1, 1}, +{0, 1, 1, 1, 0}, +{0, 1, 1, 1, 0}, +{0, 0, 3, 0, 0} +} + +AREA_SQUAREWAVE5 = { +{1, 1, 1}, +{1, 1, 1}, +{1, 1, 1}, +{0, 1, 0}, +{0, 3, 0} +} + +--Diagonal waves +AREADIAGONAL_WAVE4 = { +{0, 0, 0, 0, 1, 0}, +{0, 0, 0, 1, 1, 0}, +{0, 0, 1, 1, 1, 0}, +{0, 1, 1, 1, 1, 0}, +{1, 1, 1, 1, 1, 0}, +{0, 0, 0, 0, 0, 3} +} + +AREADIAGONAL_SQUAREWAVE5 = { +{1, 1, 1, 0, 0}, +{1, 1, 1, 0, 0}, +{1, 1, 1, 0, 0}, +{0, 0, 0, 1, 0}, +{0, 0, 0, 0, 3} +} + +--Beams +AREA_BEAM1 = { +{3} +} + +AREA_BEAM5 = { +{1}, +{1}, +{1}, +{1}, +{3} +} + +AREA_BEAM7 = { +{1}, +{1}, +{1}, +{1}, +{1}, +{1}, +{3} +} + +--Diagonal Beams +AREADIAGONAL_BEAM5 = { +{1, 0, 0, 0, 0}, +{0, 1, 0, 0, 0}, +{0, 0, 1, 0, 0}, +{0, 0, 0, 1, 0}, +{0, 0, 0, 0, 3} +} + +AREADIAGONAL_BEAM7 = { +{1, 0, 0, 0, 0, 0, 0}, +{0, 1, 0, 0, 0, 0, 0}, +{0, 0, 1, 0, 0, 0, 0}, +{0, 0, 0, 1, 0, 0, 0}, +{0, 0, 0, 0, 1, 0, 0}, +{0, 0, 0, 0, 0, 1, 0}, +{0, 0, 0, 0, 0, 0, 3} +} + +--Circles +AREA_CIRCLE2X2 = { +{0, 1, 1, 1, 0}, +{1, 1, 1, 1, 1}, +{1, 1, 3, 1, 1}, +{1, 1, 1, 1, 1}, +{0, 1, 1, 1, 0} +} + +AREA_CIRCLE3X3 = { +{0, 0, 1, 1, 1, 0, 0}, +{0, 1, 1, 1, 1, 1, 0}, +{1, 1, 1, 1, 1, 1, 1}, +{1, 1, 1, 3, 1, 1, 1}, +{1, 1, 1, 1, 1, 1, 1}, +{0, 1, 1, 1, 1, 1, 0}, +{0, 0, 1, 1, 1, 0, 0} +} + +-- Crosses +AREA_CROSS1X1 = { +{0, 1, 0}, +{1, 3, 1}, +{0, 1, 0} +} + +AREA_CROSS5X5 = { +{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0}, +{0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0}, +{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0}, +{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}, +{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, +{1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1}, +{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, +{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}, +{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0}, +{0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0}, +{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0} +} + +AREA_CROSS6X6 = { +{0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}, +{0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0}, +{0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0}, +{0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0}, +{0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0}, +{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, +{1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1}, +{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, +{0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0}, +{0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0}, +{0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0}, +{0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0}, +{0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0} +} + +--Squares +AREA_SQUARE1X1 = { +{1, 1, 1}, +{1, 3, 1}, +{1, 1, 1} +} + +-- Walls +AREA_WALLFIELD = { +{1, 1, 3, 1, 1} +} + +AREADIAGONAL_WALLFIELD = { +{0, 0, 0, 0, 1}, +{0, 0, 0, 1, 1}, +{0, 1, 3, 1, 0}, +{1, 1, 0, 0, 0}, +{1, 0, 0, 0, 0}, +} + +-- Spells-only arrays + +--This HUGE array contains all corpses of the game, until protocol 8.0 +-- It is used on animate dead rune and on undead legion spell. No unmoveable corpses are there. +CORPSES = { +2806,2807,2808,2809,2810,2811,2812,2813,2814,2815,2816,2817,2818,2819,2820,2821,2822,2823, +2824,2825,2826,2827,2828,2829,2830,2831,2832,2833,2834,2835,2836,2837,2838,2839,2840,2841, +2842,2843,2844,2845,2846,2847,2848,2849,2850,2851,2852,2853,2854,2855,2856,2857,2858,2859, +2879,2880,2881,2882,2883,2884,2885,2886,2887,2888,2889,2890,2891,2892,2893,2894,2895,2896, +2897,2898,2899,2900,2901,2902,2903,2904,2905,2906,2907,2908,2909,2910,2911,2912,2913,2914, +2915,2916,2917,2918,2919,2920,2921,2922,2923,2924,2925,2926,2927,2928,2929,2930,2931,2932, +2933,2934,2935,2936,2937,2938,2939,2940,2941,2942,2943,2944,2945,2946,2947,2948,2949,2950, +2951,2952,2953,2954,2955,2956,2957,2958,2959,2960,2961,2962,2963,2964,2965,2966,2967,2968, +2969,2970,2971,2972,2973,2974,2975,2976,2977,2978,2979,2980,2981,2982,2983,2984,2985,2986, +2987,2988,2989,2990,2991,2992,2993,2994,2995,2996,2997,2998,2999,3000,3001,3002,3003,3004, +3005,3006,3007,3008,3009,3010,3011,3012,3013,3014,3015,3016,3017,3018,3019,3020,3021,3022, +3023,3024,3025,3026,3027,3028,3029,3030,3031,3032,3033,3034,3035,3036,3037,3038,3039,3040, +3041,3042,3043,3044,3045,3046,3047,3048,3049,3050,3051,3052,3053,3054,3055,3056,3057,3058, +3059,3060,3061,3062,3063,3064,3065,3066,3067,3068,3069,3070,3071,3072,3073,3074,3075,3076, +3077,3078,3079,3080,3081,3082,3083,3084,3085,3086,3087,3088,3089,3090,3091,3092,3093,3094, +3095,3096,3097,3098,3099,3100,3101,3102,3103,3104,3105,3106,3107,3108,3109,3110,3111,3112, +3113,3114,3115,3116,3117,3118,3119,3120,3121,3128,3129,3130,3131,3132,3133,3134,4252,4253, +4254,4255,4256,4257,4258,4259,4260,4261,4262,4263,4264,4265,4266,4267,4268,4269,4270,4271, +4272,4273,4274,4275,4276,4277,4278,4279,4280,4281,4282,4283,4284,4285,4286,4287,4288,4289, +4290,4291,4292,4293,4294,4295,4296,4297,4298,4299,4300,4301,4302,4303,4304,4305,4306,4307, +4308,4309,4310,4311,4312,4313,4314,4315,4316,4317,4318,4319,4320,4321,4322,4323,4324,4325, +4326,4327,5522,5523,5524,5525,5526,5527,5528,5529,5530,5531,5532,5533,5534,5535,5536,5537, +5538,5540,5541,5542,5565,5566,5567,5568,5625,5626,5627,5628,5629,5630,5666,5667,5668,5688, +5689,5690,5727,5728,5729,5762,5765,5766,5767,5931,5932,5933,5934,5935,5936,5965,6022,6082, +6083,6084,6303,6304,6305,6307,6308,6309,6310,6313,6314,6315,6317,6318,6319,6321,6322,6323, +6325,6326,6327,6328,6329,6330,6333,6334,6335,6337,6338,6339,6341,6342,6343,6345,6346,6347, +6349,6350,6351,6355,6365,6366,6367,6520,6521,6522,6560,7092,7093,7094,7256,7257,7258,7283, +7284,7285,7317,7318,7319,7321,7322,7323,7325,7326,7328,7329,7331,7332,7333,7335,7336,7337, +7339,7340,7341,7345,7346,7347,7623,7624,7625,7626,7627,7629,7630,7631,7638,7639,7640,7741, +7742,7743,7848,7849,7908,7927,7928,7929,7931,7970,7971,8272} + +-- This array contains all destroyable field items +FIELDS = {1487,1488,1489,1490,1491,1492,1493,1494,1495,1496,1500,1501,1502,1503,1504} diff --git a/data/spells/scripts/attack/avalanche.lua b/data/spells/scripts/attack/avalanche.lua new file mode 100644 index 0000000000..0bb485e380 --- /dev/null +++ b/data/spells/scripts/attack/avalanche.lua @@ -0,0 +1,12 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_ICEAREA) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ICE) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -0.366, 0, -0.641, 0) + +local area = createCombatArea(AREA_CIRCLE3X3) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/berserk.lua b/data/spells/scripts/attack/berserk.lua new file mode 100644 index 0000000000..506861169f --- /dev/null +++ b/data/spells/scripts/attack/berserk.lua @@ -0,0 +1,11 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_HITAREA) +setCombatFormula(combat, COMBAT_FORMULA_SKILL, 0, -90, 0.7, -50) + +local area = createCombatArea(AREA_SQUARE1X1) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/death strike.lua b/data/spells/scripts/attack/death strike.lua new file mode 100644 index 0000000000..a08823840a --- /dev/null +++ b/data/spells/scripts/attack/death strike.lua @@ -0,0 +1,17 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_DEATHDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MORTAREA) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -0.4, 0, -0.5, 0) + +local distanceCombat = createCombatObject() +setCombatParam(distanceCombat, COMBAT_PARAM_TYPE, COMBAT_DEATHDAMAGE) +setCombatParam(distanceCombat, COMBAT_PARAM_EFFECT, CONST_ME_MORTAREA) +setCombatParam(distanceCombat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_DEATH) +setCombatFormula(distanceCombat, COMBAT_FORMULA_LEVELMAGIC, -0.4, 0, -0.5, 0) + +function onCastSpell(cid, var) + if(variantToNumber(var) ~= 0) then + return doCombat(cid, distanceCombat, var) + end + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/divine caldera.lua b/data/spells/scripts/attack/divine caldera.lua new file mode 100644 index 0000000000..ea2bc3029b --- /dev/null +++ b/data/spells/scripts/attack/divine caldera.lua @@ -0,0 +1,11 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_HOLYDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_HOLYAREA) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -1.0, 0, -1.3, 0) + +local area = createCombatArea(AREA_CIRCLE3X3) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/divine missile.lua b/data/spells/scripts/attack/divine missile.lua new file mode 100644 index 0000000000..f79af5a656 --- /dev/null +++ b/data/spells/scripts/attack/divine missile.lua @@ -0,0 +1,17 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_HOLYDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_HOLYDAMAGE) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -0.4, 0, -0.5, 0) + +local distanceCombat = createCombatObject() +setCombatParam(distanceCombat, COMBAT_PARAM_TYPE, COMBAT_HOLYDAMAGE) +setCombatParam(distanceCombat, COMBAT_PARAM_EFFECT, CONST_ME_HOLYDAMAGE) +setCombatParam(distanceCombat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_SMALLHOLY) +setCombatFormula(distanceCombat, COMBAT_FORMULA_LEVELMAGIC, -0.4, 0, -0.5, 0) + +function onCastSpell(cid, var) + if(variantToNumber(var) ~= 0) then + return doCombat(cid, distanceCombat, var) + end + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/energy beam.lua b/data/spells/scripts/attack/energy beam.lua new file mode 100644 index 0000000000..0d00356e7b --- /dev/null +++ b/data/spells/scripts/attack/energy beam.lua @@ -0,0 +1,11 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_ENERGYAREA) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -1.0, 0, -1.5, 0) + +local area = createCombatArea(AREA_BEAM5, AREADIAGONAL_BEAM5) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/energy bomb.lua b/data/spells/scripts/attack/energy bomb.lua new file mode 100644 index 0000000000..d3bbb2e72c --- /dev/null +++ b/data/spells/scripts/attack/energy bomb.lua @@ -0,0 +1,12 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_ENERGYHIT) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGYBALL) +setCombatParam(combat, COMBAT_PARAM_CREATEITEM, ITEM_ENERGYFIELD_PVP) + +local area = createCombatArea(AREA_SQUARE1X1) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/energy field.lua b/data/spells/scripts/attack/energy field.lua new file mode 100644 index 0000000000..5eec370351 --- /dev/null +++ b/data/spells/scripts/attack/energy field.lua @@ -0,0 +1,9 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_ENERGYHIT) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGYBALL) +setCombatParam(combat, COMBAT_PARAM_CREATEITEM, ITEM_ENERGYFIELD_PVP) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/energy strike.lua b/data/spells/scripts/attack/energy strike.lua new file mode 100644 index 0000000000..f2c4ed8db9 --- /dev/null +++ b/data/spells/scripts/attack/energy strike.lua @@ -0,0 +1,17 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_ENERGYAREA) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -0.4, 0, -0.5, 0) + +local distanceCombat = createCombatObject() +setCombatParam(distanceCombat, COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +setCombatParam(distanceCombat, COMBAT_PARAM_EFFECT, CONST_ME_ENERGYAREA) +setCombatParam(distanceCombat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGY) +setCombatFormula(distanceCombat, COMBAT_FORMULA_LEVELMAGIC, -0.4, 0, -0.5, 0) + +function onCastSpell(cid, var) + if(variantToNumber(var) ~= 0) then + return doCombat(cid, distanceCombat, var) + end + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/energy wall.lua b/data/spells/scripts/attack/energy wall.lua new file mode 100644 index 0000000000..e599e2f7e5 --- /dev/null +++ b/data/spells/scripts/attack/energy wall.lua @@ -0,0 +1,12 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_ENERGYHIT) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGYBALL) +setCombatParam(combat, COMBAT_PARAM_CREATEITEM, ITEM_ENERGYFIELD_PVP) + +local area = createCombatArea(AREA_WALLFIELD, AREADIAGONAL_WALLFIELD) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/energy wave.lua b/data/spells/scripts/attack/energy wave.lua new file mode 100644 index 0000000000..07f4c07f72 --- /dev/null +++ b/data/spells/scripts/attack/energy wave.lua @@ -0,0 +1,12 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_ENERGYAREA) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGY) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -1.3, -30, -1.7, 0) + +local area = createCombatArea(AREA_SQUAREWAVE5, AREADIAGONAL_SQUAREWAVE5) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/envenom.lua b/data/spells/scripts/attack/envenom.lua new file mode 100644 index 0000000000..3deac23d4f --- /dev/null +++ b/data/spells/scripts/attack/envenom.lua @@ -0,0 +1,17 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_POISONDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_POISONAREA) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_POISON) + +local condition = createConditionObject(CONDITION_POISON) +setConditionParam(condition, CONDITION_PARAM_DELAYED, TRUE) +setConditionParam(condition, CONDITION_PARAM_MINVALUE, 20) +setConditionParam(condition, CONDITION_PARAM_MAXVALUE, 70) +setConditionParam(condition, CONDITION_PARAM_STARTVALUE, 5) +setConditionParam(condition, CONDITION_PARAM_TICKINTERVAL, 6000) +setConditionParam(condition, CONDITION_PARAM_FORCEUPDATE, TRUE) +setCombatCondition(combat, condition) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/eternal winter.lua b/data/spells/scripts/attack/eternal winter.lua new file mode 100644 index 0000000000..b14dc95243 --- /dev/null +++ b/data/spells/scripts/attack/eternal winter.lua @@ -0,0 +1,11 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_ICETORNADO) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -1.0, 0, -1.6, 0) + +local area = createCombatArea(AREA_CROSS5X5) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/ethereal spear.lua b/data/spells/scripts/attack/ethereal spear.lua new file mode 100644 index 0000000000..b53d5bd031 --- /dev/null +++ b/data/spells/scripts/attack/ethereal spear.lua @@ -0,0 +1,9 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +setCombatParam(combat, COMBAT_PARAM_BLOCKARMOR, TRUE) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ETHEREALSPEAR) +setCombatFormula(combat, COMBAT_FORMULA_SKILL, 0, -20, 0.8, 0) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end diff --git a/data/spells/scripts/attack/explosion.lua b/data/spells/scripts/attack/explosion.lua new file mode 100644 index 0000000000..4dfa0c5d00 --- /dev/null +++ b/data/spells/scripts/attack/explosion.lua @@ -0,0 +1,12 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_EXPLOSIONAREA) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_EXPLOSION) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -0.5, -30, -1.1, 0) + +local area = createCombatArea(AREA_CROSS1X1) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/fierce berserk.lua b/data/spells/scripts/attack/fierce berserk.lua new file mode 100644 index 0000000000..e9311af3bc --- /dev/null +++ b/data/spells/scripts/attack/fierce berserk.lua @@ -0,0 +1,11 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_HITAREA) +setCombatFormula(combat, COMBAT_FORMULA_SKILL, 0.8, 0, 1.6, 0) + +local area = createCombatArea(AREA_SQUARE1X1) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/fire bomb.lua b/data/spells/scripts/attack/fire bomb.lua new file mode 100644 index 0000000000..5efe4cc61d --- /dev/null +++ b/data/spells/scripts/attack/fire bomb.lua @@ -0,0 +1,12 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_HITBYFIRE) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) +setCombatParam(combat, COMBAT_PARAM_CREATEITEM, ITEM_FIREFIELD_PVP_FULL) + +local area = createCombatArea(AREA_SQUARE1X1) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/fire field.lua b/data/spells/scripts/attack/fire field.lua new file mode 100644 index 0000000000..8bb5c83270 --- /dev/null +++ b/data/spells/scripts/attack/fire field.lua @@ -0,0 +1,9 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_HITBYFIRE) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) +setCombatParam(combat, COMBAT_PARAM_CREATEITEM, ITEM_FIREFIELD_PVP_FULL) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/fire wall.lua b/data/spells/scripts/attack/fire wall.lua new file mode 100644 index 0000000000..703af53cb4 --- /dev/null +++ b/data/spells/scripts/attack/fire wall.lua @@ -0,0 +1,12 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_HITBYFIRE) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) +setCombatParam(combat, COMBAT_PARAM_CREATEITEM, ITEM_FIREFIELD_PVP_FULL) + +local area = createCombatArea(AREA_WALLFIELD, AREADIAGONAL_WALLFIELD) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/fire wave.lua b/data/spells/scripts/attack/fire wave.lua new file mode 100644 index 0000000000..bfdc92f0db --- /dev/null +++ b/data/spells/scripts/attack/fire wave.lua @@ -0,0 +1,11 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_HITBYFIRE) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -0.8, 0, -1.3, 0) + +local area = createCombatArea(AREA_WAVE4, AREADIAGONAL_WAVE4) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/fireball.lua b/data/spells/scripts/attack/fireball.lua new file mode 100644 index 0000000000..601895a4aa --- /dev/null +++ b/data/spells/scripts/attack/fireball.lua @@ -0,0 +1,9 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_FIREATTACK) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -0.2, 0, -0.4, 0) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/flame strike.lua b/data/spells/scripts/attack/flame strike.lua new file mode 100644 index 0000000000..717c5f5883 --- /dev/null +++ b/data/spells/scripts/attack/flame strike.lua @@ -0,0 +1,17 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_HITBYFIRE) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -0.4, 0, -0.5, 0) + +local distanceCombat = createCombatObject() +setCombatParam(distanceCombat, COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +setCombatParam(distanceCombat, COMBAT_PARAM_EFFECT, CONST_ME_HITBYFIRE) +setCombatParam(distanceCombat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) +setCombatFormula(distanceCombat, COMBAT_FORMULA_LEVELMAGIC, -0.4, 0, -0.5, 0) + +function onCastSpell(cid, var) + if(variantToNumber(var) ~= 0) then + return doCombat(cid, distanceCombat, var) + end + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/great energy beam.lua b/data/spells/scripts/attack/great energy beam.lua new file mode 100644 index 0000000000..93f40905bd --- /dev/null +++ b/data/spells/scripts/attack/great energy beam.lua @@ -0,0 +1,11 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_ENERGYAREA) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -1.3, 0, -1.7, 0) + +local area = createCombatArea(AREA_BEAM7, AREADIAGONAL_BEAM7) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/great fireball.lua b/data/spells/scripts/attack/great fireball.lua new file mode 100644 index 0000000000..8d73d40571 --- /dev/null +++ b/data/spells/scripts/attack/great fireball.lua @@ -0,0 +1,12 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_FIREAREA) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -0.2, -30, -0.4, 0) + +local area = createCombatArea(AREA_CIRCLE3X3) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/groundshaker.lua b/data/spells/scripts/attack/groundshaker.lua new file mode 100644 index 0000000000..43882511b5 --- /dev/null +++ b/data/spells/scripts/attack/groundshaker.lua @@ -0,0 +1,11 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_GROUNDSHAKER) +setCombatFormula(combat, COMBAT_FORMULA_SKILL, 0.3, 0, 0.6, 0) + +local area = createCombatArea(AREA_CIRCLE3X3) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/heavy magic missile.lua b/data/spells/scripts/attack/heavy magic missile.lua new file mode 100644 index 0000000000..83712b0dc9 --- /dev/null +++ b/data/spells/scripts/attack/heavy magic missile.lua @@ -0,0 +1,9 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_ENERGYAREA) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGY) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -0.2, 0, -0.4, 0) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/hells core.lua b/data/spells/scripts/attack/hells core.lua new file mode 100644 index 0000000000..24000d3c07 --- /dev/null +++ b/data/spells/scripts/attack/hells core.lua @@ -0,0 +1,11 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_FIREAREA) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -1.0, 0, -1.6, 0) + +local area = createCombatArea(AREA_CROSS5X5) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/holy missile.lua b/data/spells/scripts/attack/holy missile.lua new file mode 100644 index 0000000000..f262cffd57 --- /dev/null +++ b/data/spells/scripts/attack/holy missile.lua @@ -0,0 +1,9 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_HOLYDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_HOLYDAMAGE) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_HOLY) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -0.2, 0, -0.4, 0) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end diff --git a/data/spells/scripts/attack/ice strike.lua b/data/spells/scripts/attack/ice strike.lua new file mode 100644 index 0000000000..1ff997b724 --- /dev/null +++ b/data/spells/scripts/attack/ice strike.lua @@ -0,0 +1,17 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_ICEATTACK) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -0.4, 0, -0.5, 0) + +local distanceCombat = createCombatObject() +setCombatParam(distanceCombat, COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE) +setCombatParam(distanceCombat, COMBAT_PARAM_EFFECT, CONST_ME_ICEATTACK) +setCombatParam(distanceCombat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_SMALLICE) +setCombatFormula(distanceCombat, COMBAT_FORMULA_LEVELMAGIC, -0.4, 0, -0.5, 0) + +function onCastSpell(cid, var) + if(variantToNumber(var) ~= 0) then + return doCombat(cid, distanceCombat, var) + end + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/ice wave.lua b/data/spells/scripts/attack/ice wave.lua new file mode 100644 index 0000000000..2e259f7383 --- /dev/null +++ b/data/spells/scripts/attack/ice wave.lua @@ -0,0 +1,11 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_ICEAREA) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -0.8, 0, -1.3, 0) + +local area = createCombatArea(AREA_WAVE4, AREADIAGONAL_WAVE4) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/icicle.lua b/data/spells/scripts/attack/icicle.lua new file mode 100644 index 0000000000..186d2b987a --- /dev/null +++ b/data/spells/scripts/attack/icicle.lua @@ -0,0 +1,9 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_ICEAREA) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ICE) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -0.2, 0, -0.4, 0) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/light magic missile.lua b/data/spells/scripts/attack/light magic missile.lua new file mode 100644 index 0000000000..b9d4294d3c --- /dev/null +++ b/data/spells/scripts/attack/light magic missile.lua @@ -0,0 +1,9 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_ENERGYAREA) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGY) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -0.1, 0, -0.2, 0) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/poison bomb.lua b/data/spells/scripts/attack/poison bomb.lua new file mode 100644 index 0000000000..afdc0477fc --- /dev/null +++ b/data/spells/scripts/attack/poison bomb.lua @@ -0,0 +1,12 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_GREEN_RINGS) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_POISON) +setCombatParam(combat, COMBAT_PARAM_CREATEITEM, ITEM_POISONFIELD_PVP) + +local area = createCombatArea(AREA_SQUARE1X1) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/poison field.lua b/data/spells/scripts/attack/poison field.lua new file mode 100644 index 0000000000..7d8d01e5b8 --- /dev/null +++ b/data/spells/scripts/attack/poison field.lua @@ -0,0 +1,9 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_GREEN_RINGS) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_POISON) +setCombatParam(combat, COMBAT_PARAM_CREATEITEM, ITEM_POISONFIELD_PVP) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/poison wall.lua b/data/spells/scripts/attack/poison wall.lua new file mode 100644 index 0000000000..15a8d2bdf8 --- /dev/null +++ b/data/spells/scripts/attack/poison wall.lua @@ -0,0 +1,12 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_GREEN_RINGS) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_POISON) +setCombatParam(combat, COMBAT_PARAM_CREATEITEM, ITEM_POISONFIELD_PVP) + +local area = createCombatArea(AREA_WALLFIELD, AREADIAGONAL_WALLFIELD) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/rage of the skies.lua b/data/spells/scripts/attack/rage of the skies.lua new file mode 100644 index 0000000000..6c0cc2dbab --- /dev/null +++ b/data/spells/scripts/attack/rage of the skies.lua @@ -0,0 +1,11 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_BIGCLOUDS) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -0.8, 0, -1.4, 0) + +local area = createCombatArea(AREA_CROSS5X5) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/soul fire.lua b/data/spells/scripts/attack/soul fire.lua new file mode 100644 index 0000000000..58f5ddd484 --- /dev/null +++ b/data/spells/scripts/attack/soul fire.lua @@ -0,0 +1,13 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_HITBYFIRE) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) + +local condition = createConditionObject(CONDITION_FIRE) +setConditionParam(condition, CONDITION_PARAM_DELAYED, 1) +addDamageCondition(condition, 10, 2000, -10) +setCombatCondition(combat, condition) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/stalagmite.lua b/data/spells/scripts/attack/stalagmite.lua new file mode 100644 index 0000000000..ec823b8178 --- /dev/null +++ b/data/spells/scripts/attack/stalagmite.lua @@ -0,0 +1,9 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_STONES) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_EARTH) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -0.2, 0, -0.4, 0) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/stone shower.lua b/data/spells/scripts/attack/stone shower.lua new file mode 100644 index 0000000000..d4b48c4244 --- /dev/null +++ b/data/spells/scripts/attack/stone shower.lua @@ -0,0 +1,12 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_STONES) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_EARTH) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -0.366, 0, -0.641, 0) + +local area = createCombatArea(AREA_CIRCLE3X3) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/sudden death.lua b/data/spells/scripts/attack/sudden death.lua new file mode 100644 index 0000000000..27f7e3df15 --- /dev/null +++ b/data/spells/scripts/attack/sudden death.lua @@ -0,0 +1,9 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_DEATHDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MORTAREA) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_SUDDENDEATH) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -1.3, -30, -1.8, 0) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/terra strike.lua b/data/spells/scripts/attack/terra strike.lua new file mode 100644 index 0000000000..bf7a369e21 --- /dev/null +++ b/data/spells/scripts/attack/terra strike.lua @@ -0,0 +1,17 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_CARNIPHILA) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -0.4, 0, -0.5, 0) + +local distanceCombat = createCombatObject() +setCombatParam(distanceCombat, COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +setCombatParam(distanceCombat, COMBAT_PARAM_EFFECT, CONST_ME_CARNIPHILA) +setCombatParam(distanceCombat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_SMALLEARTH) +setCombatFormula(distanceCombat, COMBAT_FORMULA_LEVELMAGIC, -0.4, 0, -0.5, 0) + +function onCastSpell(cid, var) + if(variantToNumber(var) ~= 0) then + return doCombat(cid, distanceCombat, var) + end + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/terra wave.lua b/data/spells/scripts/attack/terra wave.lua new file mode 100644 index 0000000000..cc753f457f --- /dev/null +++ b/data/spells/scripts/attack/terra wave.lua @@ -0,0 +1,11 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_SMALLPLANTS) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -1.5, 0, -2.0, 0) + +local area = createCombatArea(AREA_SQUAREWAVE5, AREADIAGONAL_SQUAREWAVE5) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/thunderstorm.lua b/data/spells/scripts/attack/thunderstorm.lua new file mode 100644 index 0000000000..0ac8efb1c5 --- /dev/null +++ b/data/spells/scripts/attack/thunderstorm.lua @@ -0,0 +1,12 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_ENERGYHIT) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGYBALL) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -0.366, 0, -0.641, 0) + +local area = createCombatArea(AREA_CIRCLE3X3) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/attack/whirlwind throw.lua b/data/spells/scripts/attack/whirlwind throw.lua new file mode 100644 index 0000000000..ea6258378e --- /dev/null +++ b/data/spells/scripts/attack/whirlwind throw.lua @@ -0,0 +1,9 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +setCombatParam(combat, COMBAT_PARAM_BLOCKARMOR, TRUE) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_WEAPONTYPE) +setCombatFormula(combat, COMBAT_FORMULA_SKILL, 0, -20, 0.5, 0) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end diff --git a/data/spells/scripts/attack/wrath of nature.lua b/data/spells/scripts/attack/wrath of nature.lua new file mode 100644 index 0000000000..c88ea47247 --- /dev/null +++ b/data/spells/scripts/attack/wrath of nature.lua @@ -0,0 +1,11 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_PLANTATTACK) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -0.8, 0, -1.4, 0) + +local area = createCombatArea(AREA_CROSS6X6) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end diff --git a/data/spells/scripts/custom/apocalypse.lua b/data/spells/scripts/custom/apocalypse.lua new file mode 100644 index 0000000000..adaa761af0 --- /dev/null +++ b/data/spells/scripts/custom/apocalypse.lua @@ -0,0 +1,44 @@ +local combat = createCombatObject() + +arr = { +{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0}, +{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0}, +{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}, +{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, +{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, +{1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1}, +{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, +{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, +{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}, +{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0}, +{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0}, +} + +local area = createCombatArea(arr) +setCombatArea(combat, area) + +function spellCallback(param) + if param.count > 0 or math.random(0, 1) == 1 then + doSendMagicEffect(param.pos, CONST_ME_HITBYFIRE) + doAreaCombatHealth(param.cid, COMBAT_FIREDAMAGE, param.pos, 0, -100, -100, CONST_ME_EXPLOSIONHIT) + end + + if(param.count < 5) then + param.count = param.count + 1 + addEvent(spellCallback, math.random(1000, 4000), param) + end +end + +function onTargetTile(cid, pos) + local param = {} + param.cid = cid + param.pos = pos + param.count = 0 + spellCallback(param) +end + +setCombatCallback(combat, CALLBACK_PARAM_TARGETTILE, "onTargetTile") + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/custom/combustion.lua b/data/spells/scripts/custom/combustion.lua new file mode 100644 index 0000000000..673d333fa9 --- /dev/null +++ b/data/spells/scripts/custom/combustion.lua @@ -0,0 +1,15 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_FIREAREA) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, -1.3, -30, -1.7, 0) + +local condition = createConditionObject(CONDITION_FIRE) +setConditionParam(condition, CONDITION_PARAM_DELAYED, 1) +addDamageCondition(condition, 5, 3000, -25) +addDamageCondition(condition, 1, 5000, -666) +setCombatCondition(combat, condition) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end diff --git a/data/spells/scripts/custom/drunk.lua b/data/spells/scripts/custom/drunk.lua new file mode 100644 index 0000000000..612e18f843 --- /dev/null +++ b/data/spells/scripts/custom/drunk.lua @@ -0,0 +1,13 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_RED) + +local condition = createConditionObject(CONDITION_DRUNK) +setConditionParam(condition, CONDITION_PARAM_TICKS, 20000) +setCombatCondition(combat, condition) + +local area = createCombatArea( { {1, 1, 1}, {1, 3, 1}, {1, 1, 1} } ) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end diff --git a/data/spells/scripts/custom/magic prison.lua b/data/spells/scripts/custom/magic prison.lua new file mode 100644 index 0000000000..19533e2da1 --- /dev/null +++ b/data/spells/scripts/custom/magic prison.lua @@ -0,0 +1,16 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGY) +setCombatParam(combat, COMBAT_PARAM_CREATEITEM, ITEM_MAGICWALL) + +local arr = { +{1, 1, 1}, +{1, 2, 1}, +{1, 1, 1} +} + +local area = createCombatArea(arr) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end diff --git a/data/spells/scripts/custom/polymorph.lua b/data/spells/scripts/custom/polymorph.lua new file mode 100644 index 0000000000..df5bc0ad04 --- /dev/null +++ b/data/spells/scripts/custom/polymorph.lua @@ -0,0 +1,31 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_GREEN) + +local condition = createConditionObject(CONDITION_OUTFIT) +setConditionParam(condition, CONDITION_PARAM_TICKS, 20000) +addOutfitCondition(condition, 0, 230, 0, 0, 0, 0) +addOutfitCondition(condition, 0, 231, 0, 0, 0, 0) +addOutfitCondition(condition, 0, 232, 0, 0, 0, 0) +addOutfitCondition(condition, 0, 233, 0, 0, 0, 0) +addOutfitCondition(condition, 0, 234, 0, 0, 0, 0) +addOutfitCondition(condition, 0, 235, 0, 0, 0, 0) +addOutfitCondition(condition, 0, 236, 0, 0, 0, 0) +addOutfitCondition(condition, 0, 237, 0, 0, 0, 0) +addOutfitCondition(condition, 0, 238, 0, 0, 0, 0) +addOutfitCondition(condition, 0, 239, 0, 0, 0, 0) +addOutfitCondition(condition, 0, 240, 0, 0, 0, 0) +addOutfitCondition(condition, 0, 241, 0, 0, 0, 0) +addOutfitCondition(condition, 0, 242, 0, 0, 0, 0) +addOutfitCondition(condition, 0, 243, 0, 0, 0, 0) +addOutfitCondition(condition, 0, 244, 0, 0, 0, 0) +addOutfitCondition(condition, 0, 245, 0, 0, 0, 0) +addOutfitCondition(condition, 0, 246, 0, 0, 0, 0) +addOutfitCondition(condition, 0, 247, 0, 0, 0, 0) +setCombatCondition(combat, condition) + +local area = createCombatArea( { {1, 1, 1}, {1, 3, 1}, {1, 1, 1} } ) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end diff --git a/data/spells/scripts/healing/antidote rune.lua b/data/spells/scripts/healing/antidote rune.lua new file mode 100644 index 0000000000..2dcfe5116d --- /dev/null +++ b/data/spells/scripts/healing/antidote rune.lua @@ -0,0 +1,8 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, 0) +setCombatParam(combat, COMBAT_PARAM_DISPEL, CONDITION_POISON) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/healing/antidote.lua b/data/spells/scripts/healing/antidote.lua new file mode 100644 index 0000000000..2dcfe5116d --- /dev/null +++ b/data/spells/scripts/healing/antidote.lua @@ -0,0 +1,8 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, 0) +setCombatParam(combat, COMBAT_PARAM_DISPEL, CONDITION_POISON) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/healing/divine healing.lua b/data/spells/scripts/healing/divine healing.lua new file mode 100644 index 0000000000..16dd0732bf --- /dev/null +++ b/data/spells/scripts/healing/divine healing.lua @@ -0,0 +1,17 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_HEALING) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, FALSE) +setCombatParam(combat, COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) + +function onGetFormulaValues(cid, level, maglevel) + local min = (level * 3 + maglevel * 3) * 2.08 + local max = (level * 3 + maglevel * 3) * 2.7 + return min, max +end + +setCombatCallback(combat, CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/healing/heal friend.lua b/data/spells/scripts/healing/heal friend.lua new file mode 100644 index 0000000000..db545138d3 --- /dev/null +++ b/data/spells/scripts/healing/heal friend.lua @@ -0,0 +1,10 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_HEALING) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, FALSE) +setCombatParam(combat, COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, 2.08, 0, 2.7, 0) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/healing/intense healing rune.lua b/data/spells/scripts/healing/intense healing rune.lua new file mode 100644 index 0000000000..1fdad43bc7 --- /dev/null +++ b/data/spells/scripts/healing/intense healing rune.lua @@ -0,0 +1,11 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_HEALING) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, FALSE) +setCombatParam(combat, COMBAT_PARAM_TARGETCASTERORTOPMOST, TRUE) +setCombatParam(combat, COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, 1.335, 0, 1.58, 0) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/healing/intense healing.lua b/data/spells/scripts/healing/intense healing.lua new file mode 100644 index 0000000000..1d2b390aea --- /dev/null +++ b/data/spells/scripts/healing/intense healing.lua @@ -0,0 +1,10 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_HEALING) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, FALSE) +setCombatParam(combat, COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, 1.335, 0, 1.58, 0) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/healing/light healing.lua b/data/spells/scripts/healing/light healing.lua new file mode 100644 index 0000000000..6389b022e3 --- /dev/null +++ b/data/spells/scripts/healing/light healing.lua @@ -0,0 +1,10 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_HEALING) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, FALSE) +setCombatParam(combat, COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) +setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, 0.08, 0, 0.33, 0) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end diff --git a/data/spells/scripts/healing/mass healing.lua b/data/spells/scripts/healing/mass healing.lua new file mode 100644 index 0000000000..f5598dae20 --- /dev/null +++ b/data/spells/scripts/healing/mass healing.lua @@ -0,0 +1,23 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_HEALING) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, FALSE) +setCombatParam(combat, COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) + +function onGetFormulaValues(cid, level, maglevel) + min = (level * 1 + maglevel * 4) * 2.08 + max = (level * 1 + maglevel * 4) * 2.7 + if min < 250 then + min = 250 + end + return min, max +end + +setCombatCallback(combat, CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +local area = createCombatArea(AREA_CIRCLE3X3) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/healing/ultimate healing rune.lua b/data/spells/scripts/healing/ultimate healing rune.lua new file mode 100644 index 0000000000..0d8846ca0b --- /dev/null +++ b/data/spells/scripts/healing/ultimate healing rune.lua @@ -0,0 +1,21 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_HEALING) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, FALSE) +setCombatParam(combat, COMBAT_PARAM_TARGETCASTERORTOPMOST, TRUE) +setCombatParam(combat, COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) + +function onGetFormulaValues(cid, level, maglevel) + min = (level * 1 + maglevel * 4) * 2.08 + max = (level * 1 + maglevel * 4) * 2.7 + if min < 250 then + min = 250 + end + return min, max +end + +setCombatCallback(combat, CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/healing/ultimate healing.lua b/data/spells/scripts/healing/ultimate healing.lua new file mode 100644 index 0000000000..bbb7e7bfad --- /dev/null +++ b/data/spells/scripts/healing/ultimate healing.lua @@ -0,0 +1,20 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_HEALING) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, FALSE) +setCombatParam(combat, COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) + +function onGetFormulaValues(cid, level, maglevel) + min = (level * 1 + maglevel * 4) * 2.08 + max = (level * 1 + maglevel * 4) * 2.7 + if min < 250 then + min = 250 + end + return min, max +end + +setCombatCallback(combat, CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/healing/wound cleasing.lua b/data/spells/scripts/healing/wound cleasing.lua new file mode 100644 index 0000000000..f1b5cc82dd --- /dev/null +++ b/data/spells/scripts/healing/wound cleasing.lua @@ -0,0 +1,24 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_HEALING) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, FALSE) +setCombatParam(combat, COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) + +function getCombatFormulas(cid, lv, maglv) + local formula_min = ((lv*3 + maglv*2) * 0.55) + 15 + local formula_max = ((lv*4 + maglv*1) * 0.95) + 20 + + if(formula_max < formula_min) then + --Normalize values + local tmp = formula_max + formula_max = formula_min + formula_min = tmp + end + return formula_min, formula_max +end + +setCombatCallback(combat, CALLBACK_PARAM_LEVELMAGICVALUE, "getCombatFormulas") + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/party/enchant.lua b/data/spells/scripts/party/enchant.lua new file mode 100644 index 0000000000..be975ed221 --- /dev/null +++ b/data/spells/scripts/party/enchant.lua @@ -0,0 +1,58 @@ +local combat = createCombatObject() +local area = createCombatArea(AREA_CROSS5X5) +setCombatArea(combat, area) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_RED) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, FALSE) + +local condition = createConditionObject(CONDITION_ATTRIBUTES) +setConditionParam(condition, CONDITION_PARAM_SUBID, 3) +setConditionParam(condition, CONDITION_PARAM_BUFF_SPELL, TRUE) +setConditionParam(condition, CONDITION_PARAM_TICKS, 2 * 60 * 1000) +setConditionParam(condition, CONDITION_PARAM_STAT_MAGICLEVEL, 1) + +local baseMana = 120 +function onCastSpell(cid, var) + local pos = getCreaturePosition(cid) + + local membersList = getPartyMembers(cid) + if(membersList == nil or type(membersList) ~= 'table' or table.maxn(membersList) <= 1) then + doPlayerSendCancel(cid, "No party members in range.") + doSendMagicEffect(pos, CONST_ME_POFF) + return LUA_ERROR + end + + local affectedList = {} + for _, pid in ipairs(membersList) do + if(getDistanceBetween(getCreaturePosition(pid), pos) <= 36) then + table.insert(affectedList, pid) + end + end + + local tmp = table.maxn(affectedList) + if(tmp <= 1) then + doPlayerSendCancel(cid, "No party members in range.") + doSendMagicEffect(pos, CONST_ME_POFF) + return LUA_ERROR + end + + local mana = math.ceil((0.9 ^ (tmp - 1) * baseMana) * tmp) + if(getPlayerMana(cid) < mana) then + doPlayerSendDefaultCancel(cid, RETURNVALUE_NOTENOUGHMANA) + doSendMagicEffect(pos, CONST_ME_POFF) + return LUA_ERROR + end + + if(doCombat(cid, combat, var) ~= LUA_NO_ERROR) then + doPlayerSendDefaultCancel(cid, RETURNVALUE_NOTPOSSIBLE) + doSendMagicEffect(pos, CONST_ME_POFF) + return LUA_ERROR + end + + doPlayerAddMana(cid, -(mana - baseMana), FALSE) + doPlayerAddManaSpent(cid, (mana - baseMana)) + for _, pid in ipairs(affectedList) do + doAddCondition(pid, condition) + end + + return LUA_NO_ERROR +end diff --git a/data/spells/scripts/party/heal.lua b/data/spells/scripts/party/heal.lua new file mode 100644 index 0000000000..3fe7203dd7 --- /dev/null +++ b/data/spells/scripts/party/heal.lua @@ -0,0 +1,59 @@ +local combat = createCombatObject() +local area = createCombatArea(AREA_CROSS5X5) +setCombatArea(combat, area) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, FALSE) + +local condition = createConditionObject(CONDITION_REGENERATION) +setConditionParam(condition, CONDITION_PARAM_SUBID, 1) +setConditionParam(condition, CONDITION_PARAM_BUFF_SPELL, TRUE) +setConditionParam(condition, CONDITION_PARAM_TICKS, 2 * 60 * 1000) +setConditionParam(condition, CONDITION_PARAM_HEALTHGAIN, 20) +setConditionParam(condition, CONDITION_PARAM_HEALTHTICKS, 2000) + +local baseMana = 120 +function onCastSpell(cid, var) + local pos = getCreaturePosition(cid) + + local membersList = getPartyMembers(cid) + if(membersList == nil or type(membersList) ~= 'table' or table.maxn(membersList) <= 1) then + doPlayerSendCancel(cid, "No party members in range.") + doSendMagicEffect(pos, CONST_ME_POFF) + return LUA_ERROR + end + + local affectedList = {} + for _, pid in ipairs(membersList) do + if(getDistanceBetween(getCreaturePosition(pid), pos) <= 36) then + table.insert(affectedList, pid) + end + end + + local tmp = table.maxn(affectedList) + if(tmp <= 1) then + doPlayerSendCancel(cid, "No party members in range.") + doSendMagicEffect(pos, CONST_ME_POFF) + return LUA_ERROR + end + + local mana = math.ceil((0.9 ^ (tmp - 1) * baseMana) * tmp) + if(getPlayerMana(cid) < mana) then + doPlayerSendDefaultCancel(cid, RETURNVALUE_NOTENOUGHMANA) + doSendMagicEffect(pos, CONST_ME_POFF) + return LUA_ERROR + end + + if(doCombat(cid, combat, var) ~= LUA_NO_ERROR) then + doPlayerSendDefaultCancel(cid, RETURNVALUE_NOTPOSSIBLE) + doSendMagicEffect(pos, CONST_ME_POFF) + return LUA_ERROR + end + + doPlayerAddMana(cid, -(mana - baseMana), FALSE) + doPlayerAddManaSpent(cid, (mana - baseMana)) + for _, pid in ipairs(affectedList) do + doAddCondition(pid, condition) + end + + return LUA_NO_ERROR +end diff --git a/data/spells/scripts/party/protect.lua b/data/spells/scripts/party/protect.lua new file mode 100644 index 0000000000..cbfce45cc3 --- /dev/null +++ b/data/spells/scripts/party/protect.lua @@ -0,0 +1,58 @@ +local combat = createCombatObject() +local area = createCombatArea(AREA_CROSS5X5) +setCombatArea(combat, area) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_GREEN) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, FALSE) + +local condition = createConditionObject(CONDITION_ATTRIBUTES) +setConditionParam(condition, CONDITION_PARAM_SUBID, 2) +setConditionParam(condition, CONDITION_PARAM_BUFF_SPELL, TRUE) +setConditionParam(condition, CONDITION_PARAM_TICKS, 2 * 60 * 1000) +setConditionParam(condition, CONDITION_PARAM_SKILL_SHIELD, 2) + +local baseMana = 90 +function onCastSpell(cid, var) + local pos = getCreaturePosition(cid) + + local membersList = getPartyMembers(cid) + if(membersList == nil or type(membersList) ~= 'table' or table.maxn(membersList) <= 1) then + doPlayerSendCancel(cid, "No party members in range.") + doSendMagicEffect(pos, CONST_ME_POFF) + return LUA_ERROR + end + + local affectedList = {} + for _, pid in ipairs(membersList) do + if(getDistanceBetween(getCreaturePosition(pid), pos) <= 36) then + table.insert(affectedList, pid) + end + end + + local tmp = table.maxn(affectedList) + if(tmp <= 1) then + doPlayerSendCancel(cid, "No party members in range.") + doSendMagicEffect(pos, CONST_ME_POFF) + return LUA_ERROR + end + + local mana = math.ceil((0.9 ^ (tmp - 1) * baseMana) * tmp) + if(getPlayerMana(cid) < mana) then + doPlayerSendDefaultCancel(cid, RETURNVALUE_NOTENOUGHMANA) + doSendMagicEffect(pos, CONST_ME_POFF) + return LUA_ERROR + end + + if(doCombat(cid, combat, var) ~= LUA_NO_ERROR) then + doPlayerSendDefaultCancel(cid, RETURNVALUE_NOTPOSSIBLE) + doSendMagicEffect(pos, CONST_ME_POFF) + return LUA_ERROR + end + + doPlayerAddMana(cid, -(mana - baseMana), FALSE) + doPlayerAddManaSpent(cid, (mana - baseMana)) + for _, pid in ipairs(affectedList) do + doAddCondition(pid, condition) + end + + return LUA_NO_ERROR +end diff --git a/data/spells/scripts/party/train.lua b/data/spells/scripts/party/train.lua new file mode 100644 index 0000000000..91fbe6005b --- /dev/null +++ b/data/spells/scripts/party/train.lua @@ -0,0 +1,59 @@ +local combat = createCombatObject() +local area = createCombatArea(AREA_CROSS5X5) +setCombatArea(combat, area) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_RED) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, FALSE) + +local condition = createConditionObject(CONDITION_ATTRIBUTES) +setConditionParam(condition, CONDITION_PARAM_SUBID, 1) +setConditionParam(condition, CONDITION_PARAM_BUFF_SPELL, TRUE) +setConditionParam(condition, CONDITION_PARAM_TICKS, 2 * 60 * 1000) +setConditionParam(condition, CONDITION_PARAM_SKILL_MELEE, 3) +setConditionParam(condition, CONDITION_PARAM_SKILL_DISTANCE, 3) + +local baseMana = 60 +function onCastSpell(cid, var) + local pos = getCreaturePosition(cid) + + local membersList = getPartyMembers(cid) + if(membersList == nil or type(membersList) ~= 'table' or table.maxn(membersList) <= 1) then + doPlayerSendCancel(cid, "No party members in range.") + doSendMagicEffect(pos, CONST_ME_POFF) + return LUA_ERROR + end + + local affectedList = {} + for _, pid in ipairs(membersList) do + if(getDistanceBetween(getCreaturePosition(pid), pos) <= 36) then + table.insert(affectedList, pid) + end + end + + local tmp = table.maxn(affectedList) + if(tmp <= 1) then + doPlayerSendCancel(cid, "No party members in range.") + doSendMagicEffect(pos, CONST_ME_POFF) + return LUA_ERROR + end + + local mana = math.ceil((0.9 ^ (tmp - 1) * baseMana) * tmp) + if(getPlayerMana(cid) < mana) then + doPlayerSendDefaultCancel(cid, RETURNVALUE_NOTENOUGHMANA) + doSendMagicEffect(pos, CONST_ME_POFF) + return LUA_ERROR + end + + if(doCombat(cid, combat, var) ~= LUA_NO_ERROR) then + doPlayerSendDefaultCancel(cid, RETURNVALUE_NOTPOSSIBLE) + doSendMagicEffect(pos, CONST_ME_POFF) + return LUA_ERROR + end + + doPlayerAddMana(cid, -(mana - baseMana), FALSE) + doPlayerAddManaSpent(cid, (mana - baseMana)) + for _, pid in ipairs(affectedList) do + doAddCondition(pid, condition) + end + + return LUA_NO_ERROR +end diff --git a/data/spells/scripts/summon/animate dead rune.lua b/data/spells/scripts/summon/animate dead rune.lua new file mode 100644 index 0000000000..12b8dadca5 --- /dev/null +++ b/data/spells/scripts/summon/animate dead rune.lua @@ -0,0 +1,27 @@ +local function doTargetCorpse(cid, pos) + local getPos = pos + getPos.stackpos = 255 + corpse = getThingfromPos(getPos) + if(corpse.uid > 0 and isCreature(corpse.uid) == FALSE and isInArray(CORPSES, corpse.itemid) == TRUE) then + doRemoveItem(corpse.uid) + local creature = doSummonCreature(cid, "Skeleton", pos) + doConvinceCreature(cid, creature) + doSendMagicEffect(pos, CONST_ME_MAGIC_BLUE) + return LUA_NO_ERROR + end + + doSendMagicEffect(getPlayerPosition(cid), CONST_ME_POFF) + doPlayerSendDefaultCancel(cid, RETURNVALUE_NOTPOSSIBLE) + return LUA_ERROR +end + +function onCastSpell(cid, var) + local pos = variantToPosition(var) + if(pos.x ~= 0 and pos.y ~= 0 and pos.z ~= 0) then + return doTargetCorpse(cid, pos) + end + + doSendMagicEffect(getPlayerPosition(cid), CONST_ME_POFF) + doPlayerSendDefaultCancel(cid, RETURNVALUE_NOTPOSSIBLE) + return LUA_ERROR +end \ No newline at end of file diff --git a/data/spells/scripts/support/blood rage.lua b/data/spells/scripts/support/blood rage.lua new file mode 100644 index 0000000000..6eeaad8dd3 --- /dev/null +++ b/data/spells/scripts/support/blood rage.lua @@ -0,0 +1,17 @@ +local conditionAttrib = createConditionObject(CONDITION_ATTRIBUTES) + +setConditionParam(conditionAttrib, CONDITION_PARAM_TICKS, 10000) +setConditionParam(conditionAttrib, CONDITION_PARAM_SKILL_SHIELDPERCENT, 0) +setConditionParam(conditionAttrib, CONDITION_PARAM_SKILL_MELEEPERCENT, 135) + +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, 0) +setCombatCondition(combat, conditionAttrib) + +function onCastSpell(cid, var) + if(doCombat(cid, combat, var) == LUA_NO_ERROR) then + return LUA_NO_ERROR + end + return LUA_ERROR +end diff --git a/data/spells/scripts/support/cancel invisibility.lua b/data/spells/scripts/support/cancel invisibility.lua new file mode 100644 index 0000000000..53069850aa --- /dev/null +++ b/data/spells/scripts/support/cancel invisibility.lua @@ -0,0 +1,10 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +setCombatParam(combat, COMBAT_PARAM_DISPEL, CONDITION_INVISIBLE) + +local area = createCombatArea(AREA_CIRCLE3X3) +setCombatArea(combat, area) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/support/challenge.lua b/data/spells/scripts/support/challenge.lua new file mode 100644 index 0000000000..4441a2c165 --- /dev/null +++ b/data/spells/scripts/support/challenge.lua @@ -0,0 +1,12 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_RED) + +local area = createCombatArea(AREA_SQUARE1X1) +setCombatArea(combat, area) + +function onTargetCreature(cid, target) return doChallengeCreature(cid, target) end +setCombatCallback(combat, CALLBACK_PARAM_TARGETCREATURE, "onTargetCreature") + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/support/charge.lua b/data/spells/scripts/support/charge.lua new file mode 100644 index 0000000000..b2011a3aca --- /dev/null +++ b/data/spells/scripts/support/charge.lua @@ -0,0 +1,12 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_GREEN) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, FALSE) + +local condition = createConditionObject(CONDITION_HASTE) +setConditionParam(condition, CONDITION_PARAM_TICKS, 5000) +setConditionFormula(condition, 0.9, 0, 0.9, 0) +setCombatCondition(combat, condition) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end diff --git a/data/spells/scripts/support/desintegrate rune.lua b/data/spells/scripts/support/desintegrate rune.lua new file mode 100644 index 0000000000..886ab3c227 --- /dev/null +++ b/data/spells/scripts/support/desintegrate rune.lua @@ -0,0 +1,25 @@ +local function doRemoveObject(cid, pos) + pos.stackpos = 255 + local object = getThingfromPos(pos) + + if(object.uid > 65535 and isCreature(object.uid) == FALSE and isMovable(object.uid) == TRUE and object.actionid == 0 and getTilePzInfo(pos) == FALSE) then + doRemoveItem(object.uid) + doSendMagicEffect(pos, CONST_ME_BLOCKHIT) + return LUA_NO_ERROR + end + + doPlayerSendDefaultCancel(cid, RETURNVALUE_NOTPOSSIBLE) + doSendMagicEffect(getPlayerPosition(cid), CONST_ME_POFF) + return LUA_ERROR +end + +function onCastSpell(cid, var) + local pos = variantToPosition(var) + if(pos.x ~= 0 and pos.y ~= 0 and pos.z ~= 0) then + return doRemoveObject(cid, pos) + end + + doPlayerSendDefaultCancel(cid, RETURNVALUE_NOTPOSSIBLE) + doSendMagicEffect(getPlayerPosition(cid), CONST_ME_POFF) + return LUA_ERROR +end \ No newline at end of file diff --git a/data/spells/scripts/support/destroy field rune.lua b/data/spells/scripts/support/destroy field rune.lua new file mode 100644 index 0000000000..c755088f47 --- /dev/null +++ b/data/spells/scripts/support/destroy field rune.lua @@ -0,0 +1,26 @@ +local function doRemoveField(cid, pos) + pos.stackpos = 254 + local field = getThingfromPos(pos) + local playerPos = getPlayerPosition(cid) + + if(field.uid > 0 and isInArray(FIELDS, field.itemid) == TRUE) then + doRemoveItem(field.uid) + doSendMagicEffect(pos, CONST_ME_POFF) + return LUA_NO_ERROR + end + + doPlayerSendDefaultCancel(cid, RETURNVALUE_NOTPOSSIBLE) + doSendMagicEffect(playerPos, CONST_ME_POFF) + return LUA_ERROR +end + +function onCastSpell(cid, var) + local pos = variantToPosition(var) + if(pos.x ~= 0 and pos.y ~= 0 and pos.z ~= 0) then + return doRemoveField(cid, pos) + end + + doPlayerSendDefaultCancel(cid, RETURNVALUE_NOTPOSSIBLE) + doSendMagicEffect(getPlayerPosition(cid), CONST_ME_POFF) + return LUA_ERROR +end \ No newline at end of file diff --git a/data/spells/scripts/support/great light.lua b/data/spells/scripts/support/great light.lua new file mode 100644 index 0000000000..df72c8069f --- /dev/null +++ b/data/spells/scripts/support/great light.lua @@ -0,0 +1,13 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, FALSE) + +local condition = createConditionObject(CONDITION_LIGHT) +setConditionParam(condition, CONDITION_PARAM_LIGHT_LEVEL, 8) +setConditionParam(condition, CONDITION_PARAM_LIGHT_COLOR, 215) +setConditionParam(condition, CONDITION_PARAM_TICKS, ((11*60)+35)*1000) --11 minutes and 35 seconds(time in ms) +setCombatCondition(combat, condition) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/support/haste.lua b/data/spells/scripts/support/haste.lua new file mode 100644 index 0000000000..2ff78e9837 --- /dev/null +++ b/data/spells/scripts/support/haste.lua @@ -0,0 +1,12 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, 0) + +local condition = createConditionObject(CONDITION_HASTE) +setConditionParam(condition, CONDITION_PARAM_TICKS, 33000) +setConditionFormula(condition, 0.3, -24, 0.3, -24) +setCombatCondition(combat, condition) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/support/invisible.lua b/data/spells/scripts/support/invisible.lua new file mode 100644 index 0000000000..300f51bdbd --- /dev/null +++ b/data/spells/scripts/support/invisible.lua @@ -0,0 +1,11 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_RED) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, 0) + +local condition = createConditionObject(CONDITION_INVISIBLE) +setConditionParam(condition, CONDITION_PARAM_TICKS, 200000) +setCombatCondition(combat, condition) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/support/light.lua b/data/spells/scripts/support/light.lua new file mode 100644 index 0000000000..fd725728a5 --- /dev/null +++ b/data/spells/scripts/support/light.lua @@ -0,0 +1,5 @@ +function onCastSpell(cid, var) + local pos = getPlayerPosition(cid) + doSendMagicEffect(pos, CONST_ME_MAGIC_BLUE) + return doSetCreatureLight(cid, 10, 120, 30000) +end \ No newline at end of file diff --git a/data/spells/scripts/support/magic rope.lua b/data/spells/scripts/support/magic rope.lua new file mode 100644 index 0000000000..d0a1065344 --- /dev/null +++ b/data/spells/scripts/support/magic rope.lua @@ -0,0 +1,20 @@ +local ArrayRopeSpot = {384, 418, 8278} + +function onCastSpell(cid, var) + local pos = getPlayerPosition(cid) + pos.stackpos = 0 + local grounditem = getThingfromPos(pos) + + if(isInArray(ArrayRopeSpot, grounditem.itemid) == TRUE) then + local newpos = pos + newpos.y = newpos.y + 1 + newpos.z = newpos.z - 1 + doTeleportThing(cid, newpos, 0) + doSendMagicEffect(pos, CONST_ME_TELEPORT) + return LUA_NO_ERROR + else + doPlayerSendDefaultCancel(cid, RETURNVALUE_NOTPOSSIBLE) + doSendMagicEffect(pos, CONST_ME_POFF) + return LUA_ERROR + end +end \ No newline at end of file diff --git a/data/spells/scripts/support/magic shield.lua b/data/spells/scripts/support/magic shield.lua new file mode 100644 index 0000000000..1b0f7c894a --- /dev/null +++ b/data/spells/scripts/support/magic shield.lua @@ -0,0 +1,11 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, 0) + +local condition = createConditionObject(CONDITION_MANASHIELD) +setConditionParam(condition, CONDITION_PARAM_TICKS, 200000) +setCombatCondition(combat, condition) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/support/magic wall rune.lua b/data/spells/scripts/support/magic wall rune.lua new file mode 100644 index 0000000000..2c92d9742c --- /dev/null +++ b/data/spells/scripts/support/magic wall rune.lua @@ -0,0 +1,7 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGY) +setCombatParam(combat, COMBAT_PARAM_CREATEITEM, ITEM_MAGICWALL) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end diff --git a/data/spells/scripts/support/paralyze rune.lua b/data/spells/scripts/support/paralyze rune.lua new file mode 100644 index 0000000000..0ea43b0807 --- /dev/null +++ b/data/spells/scripts/support/paralyze rune.lua @@ -0,0 +1,12 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_RED) + +local condition = createConditionObject(CONDITION_PARALYZE) +setConditionParam(condition, CONDITION_PARAM_TICKS, 20000) +--setConditionParam(condition, CONDITION_PARAM_SPEED, -200) +setConditionFormula(condition, -0.9, 0, -0.9, 0) +setCombatCondition(combat, condition) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end diff --git a/data/spells/scripts/support/protector.lua b/data/spells/scripts/support/protector.lua new file mode 100644 index 0000000000..a4e37517b2 --- /dev/null +++ b/data/spells/scripts/support/protector.lua @@ -0,0 +1,25 @@ +local conditionAttrib = createConditionObject(CONDITION_ATTRIBUTES) +local conditionExhaustCombat = createConditionObject(CONDITION_EXHAUST_COMBAT) +local conditionExhaustHeal = createConditionObject(CONDITION_EXHAUST_HEAL) +local conditionPacified = createConditionObject(CONDITION_PACIFIED) + +setConditionParam(conditionAttrib, CONDITION_PARAM_TICKS, 10000) +setConditionParam(conditionAttrib, CONDITION_PARAM_SKILL_SHIELDPERCENT, 220) +setConditionParam(conditionExhaustCombat, CONDITION_PARAM_TICKS, 10000) +setConditionParam(conditionExhaustHeal, CONDITION_PARAM_TICKS, 10000) +setConditionParam(conditionPacified, CONDITION_PARAM_TICKS, 10000) + +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, 0) +setCombatCondition(combat, conditionAttrib) +setCombatCondition(combat, conditionExhaustCombat) +setCombatCondition(combat, conditionExhaustHeal) +setCombatCondition(combat, conditionPacified) + +function onCastSpell(cid, var) + if(doCombat(cid, combat, var) == LUA_NO_ERROR) then + return LUA_NO_ERROR + end + return LUA_ERROR +end diff --git a/data/spells/scripts/support/sharpshooter.lua b/data/spells/scripts/support/sharpshooter.lua new file mode 100644 index 0000000000..b4e8f47a26 --- /dev/null +++ b/data/spells/scripts/support/sharpshooter.lua @@ -0,0 +1,25 @@ +local conditionAttrib = createConditionObject(CONDITION_ATTRIBUTES) +local conditionSlow = createConditionObject(CONDITION_HASTE) +local conditionExhaustHeal = createConditionObject(CONDITION_EXHAUST_HEAL) + +setConditionParam(conditionAttrib, CONDITION_PARAM_TICKS, 10000) +setConditionParam(conditionAttrib, CONDITION_PARAM_SKILL_DISTANCEPERCENT, 150) + +setConditionParam(conditionSlow, CONDITION_PARAM_TICKS, 10000) +setConditionFormula(conditionSlow, -0.7, 0, -0.7, 0) + +setConditionParam(conditionExhaustHeal, CONDITION_PARAM_TICKS, 10000) + +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, 0) +setCombatCondition(combat, conditionAttrib) +setCombatCondition(combat, conditionSlow) +setCombatCondition(combat, conditionExhaustHeal) + +function onCastSpell(cid, var) + if(doCombat(cid, combat, var) == LUA_NO_ERROR) then + return LUA_NO_ERROR + end + return LUA_ERROR +end diff --git a/data/spells/scripts/support/strong haste.lua b/data/spells/scripts/support/strong haste.lua new file mode 100644 index 0000000000..c9d21e8d98 --- /dev/null +++ b/data/spells/scripts/support/strong haste.lua @@ -0,0 +1,12 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, 0) + +local condition = createConditionObject(CONDITION_HASTE) +setConditionParam(condition, CONDITION_PARAM_TICKS, 22000) +setConditionFormula(condition, 0.7, -56, 0.7, -56) +setCombatCondition(combat, condition) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/scripts/support/swift foot.lua b/data/spells/scripts/support/swift foot.lua new file mode 100644 index 0000000000..63329dae14 --- /dev/null +++ b/data/spells/scripts/support/swift foot.lua @@ -0,0 +1,20 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +setCombatParam(combat, COMBAT_PARAM_AGGRESSIVE, 0) + +local exhaust = createConditionObject(CONDITION_EXHAUST_COMBAT) +setConditionParam(exhaust, CONDITION_PARAM_TICKS, 10000) +setCombatCondition(combat, exhaust) + +local condition = createConditionObject(CONDITION_HASTE) +setConditionParam(condition, CONDITION_PARAM_TICKS, 10000) +setConditionFormula(condition, 0.8, -72, 0.8, -72) +setCombatCondition(combat, condition) + +local disable = createConditionObject(CONDITION_PACIFIED) +setConditionParam(disable, CONDITION_PARAM_TICKS, 10000) +setCombatCondition(combat, disable) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end diff --git a/data/spells/scripts/support/ultimate light.lua b/data/spells/scripts/support/ultimate light.lua new file mode 100644 index 0000000000..e5fc269619 --- /dev/null +++ b/data/spells/scripts/support/ultimate light.lua @@ -0,0 +1,5 @@ +function onCastSpell(cid, var) + local pos = getPlayerPosition(cid) + doSendMagicEffect(pos, CONST_ME_MAGIC_BLUE) + return doSetCreatureLight(cid, 11, 215, (60*33+10)*1000) +end \ No newline at end of file diff --git a/data/spells/scripts/support/wild growth rune.lua b/data/spells/scripts/support/wild growth rune.lua new file mode 100644 index 0000000000..d688fefdfc --- /dev/null +++ b/data/spells/scripts/support/wild growth rune.lua @@ -0,0 +1,7 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_EARTH) +setCombatParam(combat, COMBAT_PARAM_CREATEITEM, ITEM_WILDGROWTH) + +function onCastSpell(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/spells/spells.xml b/data/spells/spells.xml new file mode 100644 index 0000000000..409bb971ed --- /dev/null +++ b/data/spells/spells.xml @@ -0,0 +1,567 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/talkactions/lib/talkactions.lua b/data/talkactions/lib/talkactions.lua new file mode 100644 index 0000000000..585eb19d62 --- /dev/null +++ b/data/talkactions/lib/talkactions.lua @@ -0,0 +1 @@ +-- Nothing -- diff --git a/data/talkactions/scripts/animationeffect.lua b/data/talkactions/scripts/animationeffect.lua new file mode 100644 index 0000000000..94e858cfe5 --- /dev/null +++ b/data/talkactions/scripts/animationeffect.lua @@ -0,0 +1,56 @@ +function onSay(cid, words, param) + if isPlayer(cid) == TRUE and param ~= "" and getPlayerAccess(cid) > 0 then + local position = getCreaturePosition(cid) + doSendDistanceShoot(position, {x = position.x - 7, y = position.y - 4, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x - 7, y = position.y - 3, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x - 7, y = position.y - 2, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x - 7, y = position.y - 1, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x - 7, y = position.y, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x - 7, y = position.y + 1, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x - 7, y = position.y + 2, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x - 7, y = position.y + 3, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x - 7, y = position.y + 4, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x - 7, y = position.y + 5, z = position.z}, param) + + doSendDistanceShoot(position, {x = position.x + 7, y = position.y - 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x + 7, y = position.y - 4, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x + 7, y = position.y - 3, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x + 7, y = position.y - 2, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x + 7, y = position.y - 1, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x + 7, y = position.y, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x + 7, y = position.y + 1, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x + 7, y = position.y + 2, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x + 7, y = position.y + 3, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x + 7, y = position.y + 4, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x + 7, y = position.y + 5, z = position.z}, param) + + doSendDistanceShoot(position, {x = position.x - 7, y = position.y - 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x - 6, y = position.y - 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x - 5, y = position.y - 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x - 4, y = position.y - 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x - 3, y = position.y - 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x - 2, y = position.y - 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x - 1, y = position.y - 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x, y = position.y - 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x + 1, y = position.y - 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x + 2, y = position.y - 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x + 3, y = position.y - 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x + 4, y = position.y - 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x + 5, y = position.y - 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x + 6, y = position.y - 5, z = position.z}, param) + + doSendDistanceShoot(position, {x = position.x - 6, y = position.y + 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x - 5, y = position.y + 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x - 4, y = position.y + 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x - 3, y = position.y + 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x - 2, y = position.y + 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x - 1, y = position.y + 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x, y = position.y + 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x + 1, y = position.y + 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x + 2, y = position.y + 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x + 3, y = position.y + 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x + 4, y = position.y + 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x + 5, y = position.y + 5, z = position.z}, param) + doSendDistanceShoot(position, {x = position.x + 6, y = position.y + 5, z = position.z}, param) + end +end \ No newline at end of file diff --git a/data/talkactions/scripts/buyprem.lua b/data/talkactions/scripts/buyprem.lua new file mode 100644 index 0000000000..c5ebc3b408 --- /dev/null +++ b/data/talkactions/scripts/buyprem.lua @@ -0,0 +1,20 @@ +local config = { + days = 90, + maxDays = 365, + price = 10000 +} + +function onSay(cid, words, param) + if getPlayerPremiumDays(cid) <= config.maxDays then + if doPlayerRemoveMoney(cid, config.price) == TRUE then + doPlayerAddPremiumDays(cid, config.days) + doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "You have bought " .. config.days .." days of premium account.") + else + doPlayerSendCancel(cid, "You don't have enough money, " .. config.maxDays .. " days premium account costs " .. config.price .. " gold coins.") + doSendMagicEffect(getPlayerPosition(cid), CONST_ME_POFF) + end + else + doPlayerSendCancel(cid, "You can not buy more than " .. config.maxDays .. " days of Premium Account.") + doSendMagicEffect(getPlayerPosition(cid), CONST_ME_POFF) + end +end \ No newline at end of file diff --git a/data/talkactions/scripts/changesex.lua b/data/talkactions/scripts/changesex.lua new file mode 100644 index 0000000000..c734a28bd3 --- /dev/null +++ b/data/talkactions/scripts/changesex.lua @@ -0,0 +1,20 @@ +local config = { + premiumDaysCost = 3 +} + +function onSay(cid, words, param) + if getPlayerAccess(cid) > 0 then + doPlayerSetSex(cid, getPlayerSex(cid) == PLAYERSEX_FEMALE and PLAYERSEX_MALE or PLAYERSEX_FEMALE) + doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "You have changed your sex.") + return + end + + if getPlayerPremiumDays(cid) < config.premiumDaysCost then + doPlayerRemovePremiumDays(cid, config.premiumDaysCost) + doPlayerSetSex(cid, getPlayerSex(cid) == PLAYERSEX_FEMALE and PLAYERSEX_MALE or PLAYERSEX_FEMALE) + doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "You have changed your sex for ".. config.premiumDaysCost .." days of your premium account.") + else + doPlayerSendCancel(cid, "You do not have enough premium days, changing sex costs ".. config.premiumDaysCost .." days of your premium account.") + doSendMagicEffect(getPlayerPosition(cid), CONST_ME_POFF) + end +end diff --git a/data/talkactions/scripts/deathlist.lua b/data/talkactions/scripts/deathlist.lua new file mode 100644 index 0000000000..eb9f7844e8 --- /dev/null +++ b/data/talkactions/scripts/deathlist.lua @@ -0,0 +1,50 @@ +dofile("./config.lua") + +function onSay(cid, words, param) + if sqlType == "mysql" then + env = luasql.mysql() + sql = env:connect(mysqlDatabase, mysqlUser, mysqlPass, mysqlHost, mysqlPort) + else -- sqlite + env = luasql.sqlite3() + sql = env:connect(sqliteDatabase) + end + local res = sql:execute("SELECT `name`, `id` FROM `players` WHERE `name` = '" .. escapeString(param) .. "';") + local row = res:fetch({}, "a") + res:close() + if row ~= nil then + local targetName = row.name + local targetGUID = row.id + local str = "" + local breakline = "" + local result = sql:execute("SELECT `time`, `level`, `killed_by`, `is_player` FROM `player_deaths` WHERE `player_id` = " .. targetGUID .. " ORDER BY `time` DESC;") + row = result:fetch() + while row do + if str ~= "" then + breakline = "\n" + end + local date = os.date("*t", row.time) + + local article = "" + if tonumber(row.is_player) ~= TRUE then + row.killed_by = string.lower(row.killed_by) + article = getArticle(row.killed_by) .. " " + end + + if date.day < 10 then date.day = "0" .. date.day end + if date.hour < 10 then date.hour = "0" .. date.hour end + if date.min < 10 then date.min = "0" .. date.min end + if date.sec < 10 then date.sec = "0" .. date.sec end + str = str .. breakline .. " " .. date.day .. getMonthDayEnding(date.day) .. " " .. getMonthString(date.month) .. " " .. date.year .. " " .. date.hour .. ":" .. date.min .. ":" .. date.sec .. " Died at Level " .. row.level .. " by " .. article .. row.killed_by .. "." + row = result:fetch() + end + result:close() + if str == "" then + str = "No deaths." + end + doPlayerPopupFYI(cid, "Deathlist for player, " .. targetName .. ".\n\n" .. str) + else + doPlayerSendCancel(cid, "A player with that name does not exist.") + end + sql:close() + env:close() +end diff --git a/data/talkactions/scripts/leavehouse.lua b/data/talkactions/scripts/leavehouse.lua new file mode 100644 index 0000000000..a51f665daa --- /dev/null +++ b/data/talkactions/scripts/leavehouse.lua @@ -0,0 +1,14 @@ +function onSay(cid, words, param) + if getTileHouseInfo(getPlayerPosition(cid)) ~= FALSE then + if getHouseOwner(getTileHouseInfo(getPlayerPosition(cid))) == getPlayerGUID(cid) then + setHouseOwner(getTileHouseInfo(getPlayerPosition(cid)), 0) + doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "You have successfully left your house.") + else + doPlayerSendCancel(cid, "You are not the owner of this house.") + doSendMagicEffect(getPlayerPosition(cid), CONST_ME_POFF) + end + else + doPlayerSendCancel(cid, "You are not inside a house.") + doSendMagicEffect(getPlayerPosition(cid), CONST_ME_POFF) + end +end \ No newline at end of file diff --git a/data/talkactions/scripts/magiceffect.lua b/data/talkactions/scripts/magiceffect.lua new file mode 100644 index 0000000000..be6b4fcdf9 --- /dev/null +++ b/data/talkactions/scripts/magiceffect.lua @@ -0,0 +1,5 @@ +function onSay(cid, words, param) + if isPlayer(cid) == TRUE and param ~= "" and getPlayerAccess(cid) > 0 then + doSendMagicEffect(getCreaturePosition(cid), param) + end +end \ No newline at end of file diff --git a/data/talkactions/scripts/save.lua b/data/talkactions/scripts/save.lua new file mode 100644 index 0000000000..cdb684e662 --- /dev/null +++ b/data/talkactions/scripts/save.lua @@ -0,0 +1,19 @@ +local savingEvent = 0 + +function onSay(cid, words, param) + if getPlayerAccess(cid) ~= 0 then + if isNumber(param) == TRUE then + stopEvent(savingEvent) + save(tonumber(param) * 60 * 1000) + else + saveData() + end + end +end + +function save(delay) + saveData() + if delay > 0 then + savingEvent = addEvent(save, delay, delay) + end +end \ No newline at end of file diff --git a/data/talkactions/scripts/uptime.lua b/data/talkactions/scripts/uptime.lua new file mode 100644 index 0000000000..b5313acde5 --- /dev/null +++ b/data/talkactions/scripts/uptime.lua @@ -0,0 +1,9 @@ +function onSay(cid, words, param) + local hours = math.ceil(getWorldUpTime() / 3600) - 1 + local minutes = math.ceil((getWorldUpTime() - (3600 * hours)) / 60) + if minutes == 60 then + minutes = 0 + hours = hours + 1 + end + doPlayerSendTextMessage(cid, MESSAGE_STATUS_CONSOLE_BLUE, "Uptime: " .. hours .. " hours and " .. minutes .. " minutes.") +end \ No newline at end of file diff --git a/data/talkactions/talkactions.xml b/data/talkactions/talkactions.xml new file mode 100644 index 0000000000..36e16a2337 --- /dev/null +++ b/data/talkactions/talkactions.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/data/weapons/lib/weapons.lua b/data/weapons/lib/weapons.lua new file mode 100644 index 0000000000..e69de29bb2 diff --git a/data/weapons/scripts/explosive_arrow.lua b/data/weapons/scripts/explosive_arrow.lua new file mode 100644 index 0000000000..708539fc55 --- /dev/null +++ b/data/weapons/scripts/explosive_arrow.lua @@ -0,0 +1,14 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_BLOCKARMOR, 1) +setCombatParam(combat, COMBAT_PARAM_BLOCKSHIELD, 1) +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +setCombatParam(combat, COMBAT_PARAM_EFFECT, CONST_ME_FIREAREA) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_BURSTARROW) +setCombatFormula(combat, COMBAT_FORMULA_SKILL, 1, 0, 1, 0) + +local area = createCombatArea( { {1, 1, 1}, {1, 3, 1}, {1, 1, 1} } ) +setCombatArea(combat, area) + +function onUseWeapon(cid, var) + return doCombat(cid, combat, var) +end diff --git a/data/weapons/scripts/poison_arrow.lua b/data/weapons/scripts/poison_arrow.lua new file mode 100644 index 0000000000..7fb29b528f --- /dev/null +++ b/data/weapons/scripts/poison_arrow.lua @@ -0,0 +1,14 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_BLOCKARMOR, 1) +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_POISONDAMAGE) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_POISONARROW) +setCombatFormula(combat, COMBAT_FORMULA_SKILL, 1, 0, 1, 0) + +local condition = createConditionObject(CONDITION_POISON) +setConditionParam(condition, CONDITION_PARAM_DELAYED, 1) +addDamageCondition(condition, 10, 2000, -1) +setCombatCondition(combat, condition) + +function onUseWeapon(cid, var) + return doCombat(cid, combat, var) +end \ No newline at end of file diff --git a/data/weapons/scripts/viper_star.lua b/data/weapons/scripts/viper_star.lua new file mode 100644 index 0000000000..a23cc719af --- /dev/null +++ b/data/weapons/scripts/viper_star.lua @@ -0,0 +1,31 @@ +local combat = createCombatObject() +setCombatParam(combat, COMBAT_PARAM_BLOCKARMOR, 1) +setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +setCombatParam(combat, COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_GREENSTAR) +setCombatFormula(combat, COMBAT_FORMULA_SKILL, 0, 0, 1.0, 0) + +local xCombat = createCombatObject() +setCombatParam(xCombat, COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) + +local condition = createConditionObject(CONDITION_POISON) +setConditionParam(condition, CONDITION_PARAM_DELAYED, 1) +addDamageCondition(condition, 4, 2000, -2) +addDamageCondition(condition, 6, 2000, -1) +setCombatCondition(xCombat, condition) + +function onUseWeapon(cid, var) + local ret = doCombat(cid, combat, var) + if(ret == LUA_ERROR) then + return LUA_ERROR + end + + local target = variantToNumber(var) + if(target ~= 0) then + -- chance to poison the enemy + local chance = math.random(0, 100) + if(chance > 90) then + ret = doCombat(cid, xCombat, var) + end + end + return ret +end \ No newline at end of file diff --git a/data/weapons/weapons.xml b/data/weapons/weapons.xml new file mode 100644 index 0000000000..15debc15a6 --- /dev/null +++ b/data/weapons/weapons.xml @@ -0,0 +1,528 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/world/forgotten-house.xml b/data/world/forgotten-house.xml new file mode 100644 index 0000000000..39e6e8dca7 --- /dev/null +++ b/data/world/forgotten-house.xml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/world/forgotten-spawn.xml b/data/world/forgotten-spawn.xml new file mode 100644 index 0000000000..f3a38f5a61 --- /dev/null +++ b/data/world/forgotten-spawn.xml @@ -0,0 +1,1351 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/world/forgotten.otbm b/data/world/forgotten.otbm new file mode 100644 index 0000000000000000000000000000000000000000..e2f70373ebb21501aa0e8d8d991846768df8356e GIT binary patch literal 3719634 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

K4g2@W|6j@=^W%c#rD; znfktid{jBxck6?BNIm1P%Sp@E`?1_JzK#7>+-$$?GVCXx=O6FUF56re3$BX=*TsVC zW>LN z@n~0ccSAnf72Vz7(XROXetN?1rziY=dcyCoC*_m!N%^FFNp#pQ%;O>&5BX3 zSxHa$4My#OvWr{AmClsmG(DUsRKsl<}x2$0OxUGL!w`0XH)&+uL|# zKW&EE>lWXkNBQ-7w98MI)r`w(#$`3*vYVC9%4g-X@;UjOd`>bn(-NBYAf_cr7sE5FAh z`@thm`FcEZnfNt4a#?^!dG&g*PH&Ts>fiBfl8^8nk9=9qyj1i?T~w98f3(Sqw}!F9CYI$M-4$`|E}@+J9_d`Z3}UzRV+m*vZS>lBZS1CNT12alBT zs3^yy%5pqX#-pMfkCgGKD90nWH+WFrt<&$&BVWgpkMg;-M|tIZt4F(B{qgz8alyP~ z``hFr_x@kQqsvFTT>bGL)&Gz8NV{+3(JohAM=P$Q71z;<>ugoNDqoea%Gcy;@-_LI zd|kdSUze}J4zj&mKN4`qLqsnqTQpTgA9FHo?@kkkuywHV5mBYJrdK-^i zdEk-lHt`6*jYoKoN5<3Ral_-DO+4~@LF6O96SRp({@Hu-(Y`jW_Z~KpNh2mFS2R5` zm5dj+eZa;a{459V7G{Vm6Eo8;T)P=6lNZY*MjP+N_h^-lFY%$3%N&e8p@+YbFVk5w zUr?ymhM2G>{-f6A76j8A!R&@vtFo73xbdaAZtY^$R|6(|HDJP5115ZRU{XFQpOjC^ zr{q)eDfyIqT0SkGmQVM+dVoi+?(oQ)ad@;hME17t>FZl>jM}>^`*MYeR=eh{za_z~ zCE&O74LyQ+D*(X^9Kr5Y6D+H;y;X8;>)*{5sHPItiuLM9y+43&CLG^(efucAcS|1W zTsk1#{+i^;{B0HHbyNvz zEcMrV-Z5?0zy%eNq?rX`kb z3jT2AuoC+5*0u(d&}&=&S=;9KvJI)tH598Ldynt6t^a+RliK!z#Xs*}ZERP~oq2jA zs}+66pvSko<@lCeXIW)AZYg6_(RU~?s&cqf%)1RY_1oW1|9&^yPrLf|IGFJ`nDIE6 z@i>{4&&p@zv+_CloP16`C!d$k%jf0uea9IdxqHH+CB6Pa3z;k*WD=37OYyd@nNVc1 z29aqS{fV4uFiOTS%A8`FkjyDLEebTaO-(@{!}gfuTpc7`GqNQ#S|t_GD(mqi#}OHB+zU3{Wrh z-$te#G3{?t`7*!DOute0an>%&JEOE?t|~h6p3IclooA-{y|o9tI}mQ_x4$3DWW4rk znGARPX_xmd)CCvnf(v!Qg}f+VlrPE`b_MukiaWd16%6f&fM=M7qTJ5Nu&Swd3)&0wP7gMM9k+g9w5y5L)XVM%? zJ_EXg1Iye=_gH3#SXOGflvTM)S;{?bIf!wqdbjUMS?ZCg*t>+<9f19O2Oib`H}T-J zfu)=6uaDm{6SDb!Hycm8lW@tdxMWvcvMVm>Rr#uXRlX`;lds9w~s*;BMOehYy_wH6n%nh{J31k)FSRrbRzZuyI3 z&dvzta|*%s7k_mrhGp6JTJoWRW$r8xtoUmCB~t2L2e@TB-Rk2Lw_j_G&`M*pl4rEq z(OfT6_v9^&(fVQA2(2>d@um$FJ*=NhdSm z%pbh_&|s?4{(2@XGwABa^7Z{%zF|M@x8q)~_V>PFeAD<=#qIL#^6m2N@*VOW@*VOW zZ?!X8cOL#0`{9h{emJ6cz3y+vy(|jvJ3W^VuceCEq3A?e=(NKJch0$0KDtD$4Po+~w^KJo0SlYj~tR`KW&H#DgP| z@uvOP=qK$r=_mIq?^4;=1@;&lB@;%`_>(O!TbX+?f*Uocw<$y=ck9?PWmwDMO z$D`&&jz`Ud9FHo?@u1x0ZRgD%d3J?Iw)+|$c_vFfvfbD6XzTIL$D1pnZ`4m)kLSzp z-V4Ty#&;@iKVRReY?p7BZNin~!6Wr}RFvaE zxy#$mJNL+b@W_>Rk4K)FZ{kt?-ib$BkG9*T?8M|hXl{{7szo$cCsyX5-1Y`kK;>Uxv!mhYDDmhX}8k?)c33GZ2N zdhgVGr`|gq7aldgm+M*jF8MC`ZaE%lhet&@9yQ-`JgVH6mz{kcJ^j}H`ue$XzmBJ^ z7u)T!zn_RN^Rw6MoxNV~?DcwQuh&ESu117PcGllN+Z&f?$F9(2ejJ#Q+r6* zt%;r-<$G=1vN3M?tnATB{U$luFsk@wZHG+CJu+2x|2O1_HFMB8J?NYsbWRUC=ZEBn zELHWNz_jNvuE7MJ6{^$h1fN2XkkN zQQly|sI~l!b|<$5t;{A`*#fQZhiP!j#Nn3vINU1Mg#GV4SML@Rf|*qW+iiV)n?km- zdn^;N%pyXt%5Tm!CR%}LW$~buGFokq^{Ym>wZ}I9_xFE3s}RL%^H$FnOFWoSvA%smv3?cj&3agm zV)Be#*}PFl1a>)#k*^#Xh{YbU)@S|h0d{5SK@mHK8nG5TBbHTnTEZxuB2&>x?vW`e zIWk#R$fO>bigG-vEXN~dJhD9T$Y7W}q8==w5hr=XNgi>MM_dG>@=^Jyd{jOrACr&C z$K>PkarwA>yl)ZVkv!tjX15-pA(NRxChs~SlNmrJ^~hAc#~?D<2{Ji`k;&kZ$=Lvz zEJ|eZxrR*lrpiJSv8FM+6>q zZFi>5isU2h$Vcu0@yI&BBgYu|$l0^UBP)%3q#Yh((UnRhe)kBWYUya}~)<&eX`HKQdGCoc(tmzph8xdNk(|OqGJnVEIkspyCkspx{$%o`a@*(-Kd{{m#AMRT&c(h}zF3yliMv=*!B9n+r zK5UW6hb=Nyf37}xkjZw)+s0 zw{b~=Os-py$#IEHyIy%Fxx^^x!6*@SAsb#0GZ5{_H8z%7R(ZdLBh zB{v4RC5O0GG?!@Q0|BkPID=L=Mio6cFiIJjigG+s#-pMfkK}+HG2Cvw;n7x2-9sVM z{Q@ns))G=LKcJN{qg9DHQ>Re0GRzwi)h?d89EGD*Y3S0^x~)@hPmWq;%W}8qZj-1C z5TlCUU7{*+y$5Eyth+_RH%au(+qcU(AI0zWQT$#X#qae|{XY3V`9AqR`F{C+`F{C+ z`2qO>`2qQXzDNCd#1D@wG(4*8qkKG4#-pMfk6e-9k$ODx=nao5yFDH$<55wL2W97# z1E9wv?e4%MCvH5_zQ?1=H+$s7eOnJsxVQ1B$8@*ON54lN^L?Xyv@1F1@1XN{(D^&) z{2r1Yk{^;Ek`KrSt!Q}Je*>ilMzs&^6f$Ye1i zQ*!UL{dQ+bJo3r7Nm6nUwWKhJdMAF9%xuWy8kWq|weH2blpr(J>G?WGl9{@~>}24| zw2L@r@~|^`*qJ=+OdpXSkspyCkq^m-9@-{ z*TabGVZ`+?;(8gCkIF~oqw+ENn0!n=CLfoN%g5#8ed`U6tP(tOsf9;HR}mgb8Xjqf zM_sejDym;5M<$iXRCK8!lZ}u`Ju=xDGPwysh70FzZ6TASAyd)hk(88?sVGOL%5pqX z#v^Ail2T%fYt^IRX?dOKOiAOLAL@*Jdik9g%WqM3x;1xH@P5?WnzFP?W`(@zS zj)5ok_rnBK{nffx>F<|8hBL^-9*G03X#^=Ma6PSjy1>aY`a*oizMKO#ROKO!HJ z56Oq*L-JwyuzXlP+_w<$$VB6j6DuBhy$z3gz9YvYpYV8O;oy;lgGaWzbZqkeO*!o+}g(gZQCV5TVeb`UKOrv`@QxE3rk|R8n8?jn`Iu2nN=)P zCR-KdSXNn%WnQkuGWEDslw*`KG8N@`Q0`U_9=S;W79Q2_tv&Kl+~bkixWS`cH{FSE zyKZt7jJOI$Tm>Vpl2Q4nd{jOvACr&C$K+%3arwA>Tt42n%J67w;lWHYGMQv#DrU0z z4dhEXtyCk70x_!C8eQA^r`jKx{x?})xV3Z5qix02H9nSYDLs^4P;G@Ls$duRHV^+5 zvv#s-*Amq-JNnJ`;j;z(wp!WIwXJ__+s{+iEk%ZsGd!cnR5FT+E+s~hG6Se6C#7|j zB}X=JJMJ2)*cz>CLVlp>d~&Se)1mi+{A-=J=zu4 zo#c%}V!|)wC;U==!Y}0~{L+3>J}IA+Ps*p{Q}QYKlzdt~EuWT8_kHP)M_ZqNBWoF% zeEcGlvnetS)_WcpVbb9f`_zXoX{xh*9b>$~hIIs_*?W zo2O%VGoP93TFooZ7rO;E!w7){&YUsR#Crog}RleP@5^^ls{nw(p+k#i0v z#+n>3){5q&C+DeWgcWbjdFs)xcyrEEPtGfDmU9lKyTXHmb;g5f#)E0bgK5TtZB{-j zpOw$b=j3zpIr*G?UOq3Mm(TYdta#+=2#@YvHDubN`Ug3JG0M?^QLd9P%JmUOS*;j_ zV^pzTq^f_L6RoPut6TiRv5nBm!HZT7CA2tXxM~OIWkq2Ba^!? z(vyoiJSzIu7akb|9&M!c%mkql#U6vN7ptd#r!Z53Q=a zIY-G|PkO57*XxD?t#j;THjm@bYK6HfywzIh#&Pu>Br1s`Q58)j>nLRsRZ)&im3z-+ z99MUR2glWd>wdv?zu>xG;AJwdvJU5*wP>S78!hriSqH1-NLnu0a)}r2uD4jK=C)|f zS*gpmT&|V}v-xthtbe6#IW3(!{yqWK+&!XN(T^ci^DQ`*Ni~+azQVG~P7Q7;<5p3= zL_h8752HM?#3(mxO(ykuZ>~Gm@Mz1szAi*2E0MhAGYOe|BqNi0WOBeF6X``?%|#~d zn@sDZqm+BnlgqiD^yC4^ZPJtN zk*VnZk*RX;%H|0B8Xo0!=hKuQ;<=3nKb~VgIKqB{9(SG(j<6LM`pWf%er@Z|=iIbX ztG&K2SgFJ4Z~uTRh5zpdjAP5H8?M%dM{}SpWtalhyas8wQzWH3hG6xQp?dYn16cN>e?g`865zC4m zb+~oMLi-bR+hLULF-m)kQpTvv*oYjJL?=_fWF=?9O5#m^5UUo`7rh7T zZDg|D9VDgtzuBYuz1gGcdy~@c34NPq!nb)Qe4A&&w|yq%lk!RVqx3qIXv2$t$$GsnYs@<=bc~M$;K~WVHAi_ z-He^<`O6OuDU6aajPgN6W^zRsqm6&36s%XO}5x$_tDprt+t1{mPD}b61$W&_%Cx`hG1ZiTji@#@~`S< z(BvYR0U+4&Rqv}K*sfdumv(3O4|8fpwbj&*K;WAl-k{p<>$)^nI(=1&G*;%i3|3jr z0!z6kc~zF<^`UUAc@1Ieqe{*KO@6f|22LeVp5HLz6F-pWJS8N#NItrtzmt&OeFiJf}73CPE zj8Vn2^@Tkil${|CI6T7fs9&{eZ%b~xn}ST1SCeTU-^k>sM5b;wUG&JL9+}i5lXXve za?ByqK8ovg35>GNNl#nn^+gU^HymqdwGY1f6vQoCkfX}kxw^_ltL>rwlnJ*$+>#ik zF3z~69=A%^*?RngTR3j1?@3hE_a>^{(shXn-)zFnQ8*s?48kM7{DMd7dnPQOU3gSF z-mdm|q&?~3vx|H4oX@&L>Kk~JPnVB=k1BWhNcpz;Xt#9CIjegxC#8j2_jT{JQ0qSb zjM787s9WIm$Eba7)n5mZ z!R!>RK(x{rqil>(mEDO(+w=Suqre`cb|AKwWla@yHCtGbfRm$rha7VCxDATNw+U7r?&>gm$q%5Zg1ar9etgX~Di4j6D zlgT7*q$~r;ByQXiVHxY5yrogkB*K;1%0>B^Yg_-bxUQVNw)H=)|2ntvU#rkRpSsnv z+x`ml~pZcyu(PGcU{)x3em1daz`F3{1>T0fJT6NPJQ{#|5t&IqsXs`c$yIgv2db~x6v8^)T+ zS@GCi@vviXsbBSoV@PeO`HN&uJEO_;Gmxxj^jo9)i*b^lk2a(OQO)H!Q&9E2f0xtl zS8az;+GCWR_ZYRq=vQ)5Mk`RQy4oe(t@}S+cV#uof3FITGJ-AhF3BFZa;yPVYRDj! z=B?edy2ugCVn?u|=LJ%hGHw;+Xr)ZHD$0?mvK*O|NmNBS9+dTHH?3WwDnTXv)%64A z-*4xG->74xvQU56-Zz&&suq>B-%J6;7fQK{tL36QewJ#VD7O7pVgjVUjlLur+aww9h3>>X?7OKbGxTPg-!0wRiamzUaw~PU| z93r??mU!VXS-V&XXh!X-ifm;&jIv!zRO;o(wD&Q0mYHuYB9lfpB&yxE`qm;-7v83> z?W{*8WpY$e-XoKGWGZeV)AmPZ!Z-D3Uq{{z9__Z(B|qbmpK;00cu363XXUf3WWRPkb9y(xxPD$%Og8eQA^N3uq7%Yr6HIjC_< zBivF?j_Q7~*R6dWdA{n&QLcf%MUE;rx6V=iC<%|sRj)_8Pd#7vk$B|75|4`SR3tOG zup~1%&#-24MNCq1N+BujBA_0Dv2d!g<#^+}Rx*<<$V|mL&(y^)TJ5y;>-n%_{<@P* z`#REj{tnhrE|Qt6j18kij8e~B)xEPzBOZ0n)qP$qoaer9n~LG#Z>deho^OIlRvuU=kpo7=8fF7B)FzeQgTxPXmlr1qT6aT=0lx4wU)HZr3fA>m`_+aZk5bq#88P3;8 zPo=9%Po?AL^zePM9_`*%Y0}eV>)MkXwE~x)EDI9V3U#P4Yuz0vZjH1%P#&aQBtPBt zDw*qc!CZC?mo98v(=M^e58EbnIlA;t_Q1ZD&u=tYrZG)g9kki0-o6v{6oY!ZuK7_8 z0n{@&Sm){l;nsEC$wWA=)dVGke-~Gilpw;u<^$X_8#kwulZJ|}W zFRp{yyWe(Dqp(uPa}H%m9m<>)%COyeuC!A`ZdLzM-4tKQ4e3U8Yj`m=-ZS^^f+3_Q zhS1JrJbT-CH;}=!owE&x$i1z*wcZUGS;KS-VR#lc-b-SjZryNgIDF&H@%cJBSNxkm z#+n0S#RG)&XKq*Pug2<)K?agTdDTYfx5w1=kGwF3tQO33jf!v1B&c?8PobJ6f?yiq zMz_vGyE%5Lq17%Ul8|~AH`9@An__OA{NzJ1%M-isX4g1&sYfhBL9C)2vGy5T_tZUN zxxhe-^Pytxrr7f>VwIFD#umPRcKuhkc)N_@a~re)pU&mubQ@lrp|7dr{q)eY5BB# zT0Y(PQam1Q1pc${{-F&rEzNb5VU(<4l$Ulfsc`(AbLkqQ3>G9_`dO!e2Lr^^0u7@47LQrhismz1>oDR`7muSdHbz8gH+ z?XU}d#)UrPLZ9(Kn3d1UXXUfv~M1b_jpu&=aDP# z+j!Ki=IuP%t*?Kg(vy$$)9cYbQEuar_P6oK_?VZp+cY2T*7uUPx|fYt>fNLskDR9P z$Z3l8k$;+^XI{eb$cG5)BfMu`@{xc?w#OsyK(Rh}SIXnI-R3%f-y$D%TkAOR$h%lQ z9_^f6zYu#{84j8S7xjXRdcj4#;G$oYFUl9?i}EG;l6*)f@8IOu`JgR)Peh(Xul<}x2$0KDtDta*BQRVRNlV%f-yoY-S9@X!U_sFv{@=^VF z9xP%#IA}KUNc%hR$d9~yjePWbQ0_it@W`TPUMgOxi)lQv_?ee>)!<#`<$_F3jt~~f zuL0}DOpMyH{34fcOw$kBxrgPFmP}KQ8K$Wt)C#q4CahhO`5kKg$?ump-dDE2 zz+<1NwwC`W7jLZR@@^_XeA5x@Iahi;s;Q4<6RSTRu7~beX3tn=BP_F11l!=Z)%|2$ zD*bE~+IZ?MEpyrKM*JIUXtFQBjUZmHUphO+50>{2h2yznAK2@LPGbTc~fI zj}4EC-zFdJ7V6T*4GlBX9R@6uw#w>p4Y_Frzg%R4s%FWw4XmyWel15Di>Hx#7{knU z%}86@mBizOIsB>}C(L2Hb*ZD31=Tj&msdcT&9>QrD&#ca66dpxRMrhv9fLoYgMk6G zeQmA6)jF0~WN8_5I%JWxM!rAZMoA^Ub(iJYhZSG-on>}{VCr#eqV-!JEc*{BwBPzr zu>JcI@77wp_dVnL#t$lPmv5JEmv5KvknfQ1knafZj`dyd`tL-%XMErIf&XrVe7AhJ ze7Ag$e2;vOd{1~co}K#a)MuwYJN3mQYjc-;mwcCe_q+AW7kH!{9u?(yq>M*JIUanD z>A^8{tA5yj?XPeDoA0+9*S#OscK3c{{Mh(O#qIL#^6m2N@*VOW@*VOW;oUs#{;=j_ z&qn;Hl{=4}?q{d_+39|E+RrZeF8MC`E;;+J@ymD1cgyLw@+#jW-y^^GQ^)<8rr z@(#;8Hp-t@yB%Lt+$rBF-zncI-zDEA-zDEA-!0!Q-!0!Q-y`26-y`1>-sO3(>v^y1 zd9Ukvuj_rEe4l)ue4l*3e7}6Ze82pF{DAy`{6OFFf=BX=M@82&9x3BdQI1EI<#?ow zM{d&asInZ7JZZoq^>|baHy<1?xAM4E|MZ*BM*6M(HukH}js10bKWJVKnwNv-<)C>x zBtIlSBtIk{kPpZQIcJo2Rr9?1_L83!KqdvWh_=H`r#N7~_$c6g*6 z9_<-XeeZ}&JI&X(SQupsjIsqr?QP=U%G>j3Ww&Ux53Tx>QQWxkHAmbivju&0bAxNS znd=Q#a+P`??;Qbwzy^|fF2Z#@$5Zf*{nyTj)0u(>;IZjZ>1$dAa6$cN-Z@*(+< zd{{m#AC?dI%?%zco&Vid9_jOc%mPL&UG+2*tvq=~E6)SavNUkVNFv=-G|%iT zW%fht=5=E`Mi-+bMdH9X{=jTsSZ$DheZO*ChJqK_ z+WSexee!+see!+s{qp_t{qp_t1M&m%1M&mmt^=;{@yPh_sQ7Um-FT#oM@2avRhHwC zG9DG>c%+O+9yj7qWjP+*H}vG=puPw7J*e+N$8$)2NPb9uNIoDRkPpZQM*JIUd~;E%I?#-^2PI*7vaEIU+wI zKO#ROACeErhvY-@VfnCpSU%jB4?NQ6*J(TZ>pPnr;&*~GWntn znaCs4&b#x;s+Fv`(-ATK4n)}q#nQ{L{{H*Hte_ru`^#`m! zkoum}ls8U$y>Z&>jniIltoF(G$@j_k$>}%a*)ONxD$D6VW%{ouKhSq$c2LiQdLGpO zkoAYGKVH0JScaE;bDCb>w8$=!;a^O{D}OB{D^!=J|rKK56Oq+!}4MIa9=*~h$kKu z^~ED)JSxiZsInZ7l<~;HhDVjxZl#vVPe5Ve5xC>VH`K8HoSYE_D{p@NlT=8JKTecCk#mEni173G6WNGs%Sk8R73FwP?$$dV;doTs=%?#H_2eOuezX1&j zdjF~P!+O6}Kbz$BhH-tTd2O$b+#;_(RS(wtg6na?^|;`ATyVWE$`|E}@K zpY1mKzg54R_t&lWpGrTh_gnSTlh?d*#QfecF6K97#+SE4Zj;x1Ft>+*-)~P;doTp?eRz%kL(wZD$DVp?Buk|SfBaek#?JTv}Z;=9%(fDmVs{h-0ut@F- zk9HaB{LMIjGtS?P^E)e_mCwp&<#X~m`J8-CJ};k_&&%ig)(0N>H-Yg;{_w~@&w@wF zcvO_*QDr$EDdSP`ph{{!`C7e{x4N4q%su;*fD<)e*@9nO#|AE!`1cDLemuU7N= zS+LcQw*A!~T-*8|exLkqS*_;$!E!5n(zET_hui)Nf5E(E?W1jfi{}4Bny-Dl?YC3; z$+o{vW&Y0j&xEX37hXbrwC!(zSM!Ix>(#}7T+Ke&_B)XOkwQ1Sj3%?oKq|Tl&`%lt zigKi@EJrnER4dBYYt37_mv$|vQM@=5uW zd`dngpOR0@r{&Y~>Aok!cqC7FRQ%jO3iHV>9;wHpqEB}5NEwfOn&44oIUXtFQBjUZ zmHVCy&*(d&?~J}Pj%QXrE1#9m%ID;B@;UjOd|o~;pO??~QBjUZ%6L?i<3ZW;M>`wpyQ1%kzAKJrRlX`; zm9NUzKJ7j4*=X(aRM!3E+7~@7)~k7)GuJ;0$(`n3tyA3R+x{k}I8L@3XU+B6y8dQ- z!pHv;KK`HZ@&AO6|0m^>@=5umd`dngpOR0>r{&Y~Y5BB#Mm{5-k+*H^y8OQT>T9O^jv9{{k5@b@KPo>eKPo>aKPEpWKPEpe zKQ2EmKOXKu!;=d@tE;=MLbfEM@2avDdSO5jz^W{c%+O+ML8an_23(uyTXHS zbZ(i4TaEu#m6Tg~sJ8jOKBu%jo?D@J z+EmwLV$8EO=56cz92BeP+SaGz%0OB*KZNS9+&-0`ePUc~&sTF7#o)3F#+4s~WL(#G zRjM;xQraIR=QRw*M=(R#X1 zna(O&PiK_vhZXpUi{lX&$0II|N4mvvZOfxJdelab+UU{ijd)=oNqWqdkJ<7uTRwKZ zB`f5#jZWL>w2e+zqZ=Q-BbXNp5Nuhhm*}y~+QTvt!HP#~5g}ORTCnxIN4TXDx2!bW z+MGR{Nk=dcw=b!ze+mP^wuI~FMzPGx3|O|!9_juNjs=Nj(u`&Mmo%nxF2b_?_{KkI z3#rnBZ`=0o<@a)t&JVeh)%=h<(hahh$q}h-!~NY9FmHc^QUCS=`bh}-?LG5qj(*#g z<{OKgfwY?XyLATA+SWfy!$2CTAMJHCzt=Wwx$RaUL#ZLTa&S)>-HO&@nlfG$tw$+k z`mSg_JyPyg;C=UdZFItT(s;^iCHYbLQTb8%G5In1G5In1artriaryCZJ=(R>{pESz z3FArQsftJCcw{_yRFvb9G9DG>c%+O+ML8Z-4%eeyEB*96+H$DVCrN3ApKWKu_K*2= z%i#||>*n;dODm`Iey8((r}KWN`-J?2{Dl03{G|M({G|M({FMBZ{FMAu-*Uktet1-L zI`K#ukBV|Usw~GNmr8i#0~e2qrz+x+&s;omV!|VbJsy<1<$_21Si0Cg-f9jpYBg6{ z4HL=9gdDY5&DkNJj8EpAhia?-5ue)rk~`Av+K4u6MF#&8`?=8e^RK?o|NS3TT|3M5 zQ~fhiq_&;s>a51N+AjWWTS5P4HeiJ9S>*fq;AEI>r_uez{LwRsZx-2KoPRmhjQ`KF zpMRO#ZIaM-`p>FxGKF8NW~a9O+KKK zHuWsxA-8$Bw)tT$-X3nvnd65~X0trw&812`!F~8ts_8TnvyoNKl8mN(B<{;z7*akG z8B)cX%KAk>#+8N3klOtIMq9L77)@F-kU$1f(LG@vj&$mgP8r`Sx5d}ItEUIMQ&hVu ztflwAW|iRfAF*WqX%*U%VKZ|67dKYRKMBFIJ><@fzI1i8m|dY~P1Q+(d1wc#g(k33z%tjsSmx%9#OJ1s#J5v<-3Xx? zNV@a(2CDh7dQ@|#hh=Wnu*^pnmU*j)yz$a}w^p&t+QF@&Yl)SSGBOqA$W&R5N6L6q zl;e>y9u?(yRJm`h-uHl4mJb>qGCrJ~)fxDxjgH#rsEv+RBkqi`+47hzk3H10 zn`6~HXXsx|^W!!@Zu8^Sy#CF`;}0|8-YT?XsatPIXE66YXne@{a7BE>@vSJwx4Y9u zH4xQ`Ho~&quGBAkA{dBZMH}J9{c5-9zJ*bBeruWP_krZGE?tmm>p5qFT$R=n36j^& zjkbCCdZAl%KLyKMWs7<}+V!f_e!tUxe@%N%Gd1`3*HZa!+bvivl@o4r!fj5}XzM+; z6E)hL)#}W5(&i^^e$pj-vRY=za&F_~bjp^eYifN0>Xy`MwOTE; z)VfPO8HYHrK*2XY1{~w@JzPVDsyIL@7ods)n<4~oVq^OQMHN&Rzx6!N`JdB& zb@#k8^Uh4`gI2Gz*53PBYp=b}+WYKt{-D%c0XC<#SD?)j7J!;40O;+)%bIokVQH{w zt^=F4jKFpv{6JLmm_LIBtYQ=%USi|lzkw%D0k9HH&x)Yz4X;t%<-uEVC8vL{uh_}H z-2(qVs#<{GQ?mpo*TZe!;4$RWQnUZKZ8SG;JYCKEXSOh@o))e*7MQ03u>CB6vDhnvHk>aff-Wb5E+>L6 zcOuB?(3NOjiRP7PUg>G#->Xr&8l|gIy4q9PA8_|vi{`axUNh)yaax_(w((`9sud`- z0aVkgzFh%TD=Pr%IskP?2B34(YrYP)iY|07ODyx7^`dd#uWg%;?4^* zKvxCVRRPE=-n0n(3N6gAq4v6dRomkDw{$JRnqp2%4U>8WzuH$cxZ9}3I0X`RRk5da zk7O;{?DJw_?{arz)+1Ef9;O&vskfK0bW1T_i(+*#tSW7FG5@0yV+=0FyZ;zmjMtqQ zT#T0k*jguC-Z=5@mciCi`yZTUU)8FiQa%yie6r%EcUTND~(EcFMA%w!par=U12O#m}8go$S7e* zDS2|n1}TADV+GL8Bd;Wnyy`InKUVCfzLEXGz4)n=5$7aCb!Mglq3Q3?A^EGBv zupJ)YZ9^2O#dt3Tyv4W|@MSSFOEKrMhz)IN3@N61h^0n4IFaF_|Cm%7UneA(RE)>5 z;-Sa5F^}g&hR0nZ!?z0TdWnPB+To|l8sjRj)TVYb(3`$tWSkk&WTDXi4vma6!=5qE zjA-K0TE%nKnNjI#{pZfknL4lbS=iW!dLM{DdAqcRm zKGgXunpZKYeU61)S`=cJh7BfZWMD`wyZm#CSfk;9A*Qz5)?q_U|6V3ygET;HI;!Ph zJrZa~3s4o#pfnu`9W@1@u7k}qaPQ7caVu=31`>FAhsB>3fwa zHMi%BgG18L9)rjGV3k2_u-n97nz&Tz2rmtv{xL9n8|zXkzT}Flnl>acM6m??8Z-XE z72sF6{Dqo6PXWK8%-b^9po9hFrSWxIqh7SSf0PjTiYkyRUjlDwe!VmSU5tN^5YXBv z0CZjCr$LQe#Q|+PTGMMAmvOvjEkLW^z-v0Hbcudzl8E^HD>VrLTJt%0r4H0Oi`q=l zeTp3%L{{=6f=sg8ndf8h5EqVt`YF6(^z#s1n-R~59>xEyy>fD^3y z1HWfSIZ~W7;FB7uZU&#!)}^ZWH|uHUmLAuTRYsatA?4s zi;P{0aZEC;I{?*TO~r~IT4RWW*kD@2u@KkfZ{)g-fVW1`4{DqNwVL@$VnA6EKh6fK z)In*gdr3RK1dwXMmOa8^M#Sir>zgBvuaNiR7QC864nS?5G2?EntifAD!m*FL8_;!8 zcXytsH$M#r{JO06ys3%}+B^il=?HW`zlnj9dtcLe_wW9@-6ebv=00X|pz)J}~sXh4*W`AmBjm~3q4-* zjJ73x-UXBDic)_^1;*7Lo^+J@XN@qd3|E=7F8c<9>Aex_DU{AADB6MeDkiix;;-jK z(Dj@Mx}Fn3*K^KoEb_6)$08q#d_3~;$j2iek9;EXiO44+pOCx_Q*da-2@WMk;7}NW zLlhk9PH;#M9Gb+yA$@R24i3|tc>#y?!J+HmFpV4>@NJlaqcSzGQ~*Kl&}~_eSMKnSKk~8nm{h!a z#R^S3c(Z2LU(ihxQ>qGea&>!*;*lkG$Y@M3%>f}$iwCP|t{>t=$&Ym&)8}M5dorCp zna-X}Fib^075P-;Q;|KRt@dE*Lq$7`V;8Bh{&S3=5Y&v*0 z9Xy*3o=pJEMLrk#T;y|+&qqEV`F!N_kuOBP5cxvn3w;3u4s|FvOw+;O5DyO1$iZPg za&U+ThiT;C5DyO1$iZR0FMy80k#6Gvh1Psu6hurVM|$KaPaNfe<18l<7SmIU>8Zu^ z)M9#aDe|Srmm*(^d^z&v$d@Buj(jEZmB?2jU-1~Iha2t92{7s&z?i1H03#kSrjY~2 zeB^);4;a(P0V5tTrjY~2d|x=62pj~#k$yPJ{-f+Z&R&Axx({giVeNIF(D6KWeNaih z_b#tr(&F2X8^*e2#$3jW$}ykn(LRvVqA?frGIrbr{it``1^vG5c&}jHo2v|TB8ST+ ztZ9rBgG%^qSF|~YdLkW|d*W`Q&hA1<@hPnrQQrkt?;G`!?AUB_rEI?KA14<`9 zK5gQoKme3702M?4bOuxhO`0uqu%Xo6seq;I9Vb`%<4sPmX!SC>09}l-a_3LLuPfer#fD04oR#6^4&#DK zfq#iKC?-|X6ZjoL#{X@fcznImpEW+B9uW~;?#?!q5K2?jEb)_CNwE=sG&*d8|6#b(3yppg4 z%4`B<)*Nxl{+^HHCV`g|gAzKy{BQwvdAh|)s0 z^@X@4{pfrsfl;$G`mUv*9Mh-0TATo^{MCK)LV-*|~Lw@83nCjs$ z4uCM8vA$k=4__2)mA0J(A5~hH-d#-ZF1mMp_gt(q!i3dQl$JaNI`EdP>A+j6^T22> z+vV0+wmKTk46y7n-pO0O)LCw?xS(rWx#)IR20iXqD$1IH$#Zx>tHS|png9T=c<`D= z4pj4zgHk*IO(O@9c)*xO4i58u0d^vARQh)k{88zlAGrIm$@O~RPDotOqxtJOdofl@ zTp#lTc|R%{i_(~%#ry4s@o0{xrg0w;@Fwkes+(||evUHXI)BsKFIRb9NJA6SY^Nts zYNHf@I*$iH(;S8ZpcAT`a{^nXV88IukLk$+5GyhC+hjoWKGGF#i?jtq=TbSx0XpY? zscRw!tH6YQJ5_-x*;(@vDCp|mdRAM`K&&TJK45_(OVtl~!gS zt1BaP-}%6j;zdW9nwGG-4i9J#?`=&Ye&UJ!b&Nn6rXIcNSq6!%a_MIMtuSj2LnYRJ zTT^@-<0sGfq;itB9zX0*=rx-@c&E2R{k(9n{nMQO!B3UO=(5udoK?ApYShCL*NEFR zhv2c=Z`RWx8%|90QtiL~vAT`pvD!sc71gux7$a=badKaG`$?a_tn~~M``sgFGJ|94 zvQ}kI(58G!Ct0me>A0dPN0gcA%bt9vOPz|Rt!d+?%Q!C16po^<)29Vya^7^t)zwjw zcGQ7fOCvyQM1Z%3k)H>GR~Nynr4dl2JmsP1)ek781VDKbfKmpaQed!&f=zO;ndT4^ zu;AM<2co}BN1g)Fd;AnpdGIg&f#%In5GmyZQ7={WAtbQX6`iwn0s+eEKn0`*S1#KP zW&mBK{D>dqwjjtrZlvyAXClYKXYc@Dg@7+N0KcMCxU#`{O%L|;TrL@S%3y=ag1qk$ z3Qn$Pka~pDLvFA~gEsi%++A6$jr02`vk9)*3NCysv6c0et$blsuM5d90!@T5e=$;51mBpR}e^eIhp7hkVct!jAo)#B9EqYnF zm}zmTP6iV}OYU=uHH+9K2bjw)Ilvy3*vrJ_1m*ITq`OvkyURXTyQSbSuN{YWmc$TsI5wCj06;Dt*L>sv6c0et$iXHaSf-H!Mlaq2=1w;s z`4ZqTu5O@LX8XnW$GbQxv(2;q>pmLbr1iQ_;5am{`&mEd1PH9LsE_4b+n6;yvyF8% z$D=v!ik*~<`w)Qw?>Cgoj=K!8)$D3aR6!kxZVom~xC$TYm~a)q*&px(H!-Rpthuv| zH|)|q6}wF9ut0O51(WJMyzg?_8%<2|He=4(V2$=9F+??Bh~(H{K63CXLk6X;1JJbZ zEF6dwks!K5<32fxr|WV1cPoDbRx#hv!0O$NKkW3b8py+XUT|p+gwX|R54_IM?LXZv zy-^MFDyVeRw@YlO%`QJ~rVHvo&ZrXdc;DM#m{i%h_YSeE)KrTPli~QfqSFJ{DIM=u z{@d$AVkV)L#nXtYRAy@|SY1hmGVQPKnx0*DA4TI(fAbwm8JwKHb7%+qtDYqG29TWB zRVdT$#bM7Qo#RIFgbecLXWZdg7I)M+O=qIGqh9GyrT)AK?&ujprHwl(x$U}R(g$}K z1Ndgr(X8Z^gBUVSclkDsZV-gt12aydhzUf zu(dC%#^7F7t(ucsk0@nVvfVB$>tiECp}nU~ZH6ybI__{+jyUlr$OwgmRwddsjL@HT zSo$d1tb4{WIy=hjg2r5Tz|D22PUB2JmGb@J$oBfEEvE)5w8pK5_tx2cT)>U=t54)5t+&zHel7a8&g9 z{0D9<#_KB(>7?u-h}sfyo;&;l!B%81~Z`SHk2Yks>X_tj{w z`h=r3R;?b5^S;M+J*3HKvz+wmBm6fNPw8@lUUw%wGVK_^Llg{9k6?gl&bVNJc(9vB z4tDdAgIGL>O(S28d@b^|$k!rY>pMepB5-_o|J1Fsr+?)6=Ek9jpBtQoCa{e|=@Wr^XKvO1y4e@3ya7 zw_Z@n48n-H#fZ7DNX3H6XnZIG@RgQmQ_>i2`4Fo@!87)47v;l$3LFfoB1%WR-HSoF zQueyMR}ObNMS1y8uC~6d>CAdmq@=AeyD7~>0v!y*{7=MuJvzets!|KlN}OXplT*+9mnxXoCD1h%uWjc$!?Os}^x{b7%$M>@FFzm2-$S4THo zz!}0l5`mie;&kT1yUncR5ks5pgh-nT1KL!iZXDF5^%{nHe)CuAjykMx1VUZbbkT!Q zSJ1(LJ_yWNcO5f7TvhZ?XcPcI4GVwwQb<+*vz&2v0jtg7EC*+`QkP;l^6?3s=wbGCCR4z2sK+DgkTixOZO2T*qcKH>0_k zRBf~GkU7XTgz%SXh7>5pgVHo|u$hk>MB+hY8aZIZgTpj(aG38KvPZUmRJ%vDcU(J6 zzxU&p`|->D_~m|%WN$^j75P@=TaiDA{6XXoB7YG1cI4ZUZ%4k}=XY?(58yD3U%(+A z9HxB%Q!ryBK!Y?4TDm z89}v@Y4nFQ1@-j99@MWzOry{I&|5CiNM{yVJ6NdJFaBO5Q^5Yf-%$c_<6`IZ$PJ&= z`YB5;>5UbC6!K=B)WVFL7dfO*bWq!EygAcsf#39@K<`_xMt$|74oY}yaJ6cu?CK@$ z4*ON)H6Jk6a>g^=nh%gQ3!L(uVRf(coeje-buQMJ=Iqvuob3X>t^?n6(9atHt<^es zbsfB>gQss*VguBg$-IXRR0#oS8l|)lVAp3WLKV*q@@0D(S{hK_>x?Z&0vF;zXq^FD8 zy4Czuh4rYZ{`H=kUzl!h>VPlLD+%;H1n_mm_YiQ`?a=x8?Htqxt#dvLm0bT+-IATG zfwszMfH)$7TvwFPeNThCu4n=E)4JUOzODeIEb$3=V*q?fz_33+E?eSDzSp*+8Z)vo zL5u~ds)5vRhVF})LBfplJ7yS5EO5du@TlGCz{q05jcSwIT?cMuAzjFo+y`haJGy3e zfo2)mYovg={w$mFozI|cyuo{Kn#X`#4sZeuSlkOr1f%>o!#oa=wNfITa>PhIMRC^RpK7bVqDM=Wg8d!dZMs# zF)x6~DEPiENN{zhj#@ut=wpM1G{`j%1@C1&B*6U=5+|mEr#BQYm;5vBz|wVKnPxHs zksh*wNY_DR8u|LAswW}AF6AL$qz@R;LvZ+dh9l_NE&;WW#FgHWp*GFjiCj{;A!27nE6UNcY~w z{sg!dAr7A9k}NU5OPlYBQB>bSI70 zC*>f!<3|dw+~GFw8HL-t=hDnx6xht!`pxd^MVn4@{)D!MH!txJqp^^g0Sh%~=uy)Q zTI>>!Nv4r+L=I?O2d`=5U^5>%h{S`)G;(mj$J0y-Cjv(;W8%U4d4=J=o#!3+`_4su zli!NwRy4P)>36fXtZAP=h~|T6KFFIf53J<6x1+QjrR^wfTdAszx9Bxc%A24xjaLCE z9&DzOgUx*8AQBHE)5t+29x$en1IBz`ubl`Swf^t{xgYleqrT|b>_^3Yrme zvksgW^=UmXD{lJaU6?wkhsLVw^byn5D_UFpj2c)?ovS&&ymnb9tX+KVik=es*!)^F z*VKC3f8fXm)eHnw&Ad1Lg;IaS9jLkpO5G#? zXsVB;ve5Bodj07#0P4&V*rWtDc5K>_2sZUK*i3Wk4_M+sWEwe$%tsCw@qjUn9B+vS zjA`WHFyD7t4~upEo%)Ze_qh5hnR72WXTMe5Pbc5Hs&i}}(YNZ%B^Ph|JDBX=6;nN5 zG+lZ7noqRfu~ITNfA!cU?sTK+j*ophHr9KeOQyp0-j|Y#$p??uuPJsvMVz(K0&?{| zptZLK=t_5o|HZ-wXmul?x47s7CV;NQ^^?`FgSmHrs z8aZI#<2N2dAKCsdG`nW;>+D`DBR7&vT@l5W;SZ3pu~oi zyzJS?s{;3OIQU+D;u@uUSN*{DUMku2?Br2CVej3)ooro{av8p$xh>!y60b3 zr+uxu6};)e{Q-A0pzF$?t5a&6!T}Ljv-RV5L0V@ zsNQ<<_hB)q)X!FXf21z|OKKDgRRH5OS7KbLeP5|@I8(1SHD5u&%*YxuE2T&USg4Ay z%QRusF+|s~!8GzikV_6;(GEcKDGwO&WmAWKk?)!ka z1K|GEUI3usX!tDxD*GKgH%CCtCF>El1?4PibZEwhm}fY}F_DT=!b)TuZ}isdLSr0R6iMZjM0J zZRtk7fCoy&2LQLLzX4dm^A~k11f{d6d?y%`QU+it{dwI{0a%P*CR@!(Zvd(pu$ks$ zI@p{|)w%4Zr-&F}$+N&R%~2^}i3gTxH?YSocRS-Q;?3taov&yNqVas=b|@ut5QEYgo4&XH(cv_n(Wp<6(nl zAmG=<%8f|EtjgKn%VLen zU`So8rrPNY8`MA8U^>FI*W`}Cuk6wfYOz5Yz_0M~rY-PmKj^{N`%Y{dYRLS&zRZmc zDg*qU8n=5JQdb^X&2KVniNP9`!G_Y?b zI9t5tBPVXecd(g{9M{4JN2P%dSHGoo*Rz~|r{wNPC#vr{w7l29(H#*R!#-%bVRhex zH>~cx?R!I=itY`I@e_o5E)%BP#HP!*y-k<-gL+D`=`#ORqi3_qG}cs-==d6}_~JM{ zJ#f0Z5W6a#ygtCLHsY{L5r;{2(E|gMit!l0u3{Xm*j0>kO6+PS*rkdv$+Qh&cDSrb z76=%usbU^2SR-`|fwsYpA?h}e_k|YlD{38Dz^|}}VaxubRXc{LAU5Hk$U!c?1KND#pcLN$Xg+cf!M6k8z9a3QF!|;*$o&d8u-~spE9$`X#Jgqn z-&Xi-IWYfOmGv{it(4hv8As^@m+@$LklG(Oj(ux*V0GuK+gAVms?|4%ZMWl3j&Hl* z?+C$m-vGcG-Py6Bg3J+*A#KEAjUo<1lvQAZsg+y;_*KFI3;gQU^BR7@ccz+*1?0N3 z09r{2P$dVYuV?b(kwY8Zpj4lLvVzB-F$ZOZq-Ih7s5nroVMVupV-nCR2x#TUxVtn| z#>+U6rwly@a;XE_v=g6R0s_C(3Gikeh#}I&5YxmBp6)eOlYAS=9cxqqLrfEi9niWC zXw%3KK`A-dM2|s)yp80CIc)MUhfN;lu*t(5MtKzZqsSjc{wVUD$af;&iF_yW$B{pd z{Bh)u`wp{!L%6|VnnNMr5DyO1$iZPga&U+ThiT;C5DyO1$iZR0?=VZRz3z2xc~4@; zli2Yjc07rlyOHlkz8m>&y&$)a<5bFb?SW+`J2e!ME)l7x4Hf{*Wc#)Uas%u`d+R-%tMNYc}Vdv4=Enz zw{Rat{wVTCkw1!jC-R-hcOu`3{Bh)uBYzzEW69GWBq!idKY_zE&nLhk9vr5TgTs8} z;1CZE)5yUg9vr5TgTuV!!NDr;3y$Ns_G|pbUr*w%C-K*l_;WY%-N<(%-;Mlf+272$UopPjlaMl9vr5TgTs8};1CZE)5yUg9vr5TgTs7Ze;nEV zQSBbp-eL9Q@8|Ky^Z4U={P8^gdJ*}H$X`VMBJ!7!zl{844_K_d9_%n_>QQyh$AnsVx#diD@=Z|XD@i>}~qxm?$t@!v_{p@XLlFH^Hc;#%M zD$@exdM)b#sOtbU&88r*#DmB*auAu1930}oVH!C&;M*X;d6IWnV6{j`5-H}c)ccO&18{AuJ*BYztC)5xDi{w(rmkw5E;18~US z;4n=;fkQkvOd|(}`N+W`9vr5TgF`$xOd|(}dCA*2I1xCAgA;+HCiC>)^Yq{I^xyOJ z?~BM^ME)Z37m>e={AJ`XBYzqBtH@tP{wnfUeenSf^%poy(|_O)PrIh!sUN=W*Uwl! z^<&53^+k>yl4FNy+QSa<*kKwucEAS*{dyvB)J-gYe;vQSj^AIWU*1IiCh|9tzlr>9 z)4pl0)2?{hHO+O}6Hj}leg6N9 z?FG+~?M8lN`;i}u1OGqtri-2fdaM7TH(k7I^Dz554@Xb`hHit|wtr+L74rtsBX7^R z*dy=ju(`yobI04JzK!qrQ#0QF*>M?f_Uvd&XaCe=tADd^-`<(^2GL`ycN%@{cGwg0 zf#Sq>EjosL-^QA)0*)ctr@@fg&bY6mppUZWfi=3>V}oz@qoyEN=K*>_#W$=d-mARhtWG<^nW@!&O$9F*oG2cUQWnnn&R@xU^T97N_N zZ=>u);HZ@0B>1CJhIHriVF$o-_oTb^xu0Qsk?{a{NYZ@Hb z+v2Ds;|pr<`@~U6M*8P<`sa1}=XLt;P2_JPe-rte$lpf(HuATTzm0q^^1aCSBH!!w ze-*&Ge1QG>{y3)B<3_!i;hz)4{yLZ#WO>lQs1`;D7OAGb)Uk_y8rt7;aFHr)X797 z<6`{u8oNpYp^g0&V7eT)gS)#p2VDgmIveNm*Mo)NB12TR)*rf!@~M}oprIR7(Q^fKk&Ocs)1d- zUv~9p8L_awruRMdYVNnTFGhU@WA3Q#II0_R9fa8|fIjzZ!v|g3-=iF~ zr#y%U$MwBKrWAr|(9L@EU%Gz!Q#jO<#HH>Wa;Y~JQ07vvjGW(u4vP(JtpL;@h*@JA zY(B? z^4L)GfM0XMkYY6Qd@>J{D!y^Cet~9xU|M^?SXedN^K?*svd!U-t>sbx?d!LBaK9%c ze-8%*&dHn!HB=Z{y?OJ!SqZoM7b7_1K?Vy5Z1l=N*Ea0 z{MFPe9`^bKppCpTrW=Xwj$Ybb>D(Q2rG^%^Uv+8SH@(w;)i){!#nrkimYPSR`{`Ph z;o`NLDvJ7UrWwVxU$eS?O1dy5E*Q92_^Kiu<0@eNf-=U{jnX?A7*~v=7~?cxF{#w` zvhXKsV4cFGQuBDfQuk{vVvW?X!8GF>_!`$B*L9Gaj{E0HzLIKQs-|0SRw8o zB>-SCU#%Mf04pr*7*JM7`{RlLY=#AB6952gD*|OP{#+%VZfgav;sd<;5-oVmCvLzC z-;OrLjlKF4%+;sBs}Tg$8V%0pfLe@mHK0lZly%V`=?A4Qg3{Cx=#d3LU!2ga-ue15 zDC;Jxud3R1@K!3r6~GW4J+Y?`;Md%wFD1{kDFFiwhs#h5FpVRCD;~I}jX!f8aJt@e z+;I-$UOAKy9`fBzq3daB$U{ZHbl~AMWGDK^4Tjy{{>=BVOLu-1Vc71pnZt4Ch{u?3 zeIs%Fh&Aook!X(gG@JYFsJp=ZGwL$F)s9Z^Q|L_$FtRX7sTL+FwlTys_dt+q3;^16 z-1k0E>iiTamGuG8blf8qY&G;e>Hw%pfn}OT07K*L{SGkd55SnFfAFPvz?epk-^@o2 z81aBHjT|uI!C@LXo-r?ZJ21de;Ps^`A2tP1jWd784IkA_&7&7=6{o(r0I=@2et!*@ zRV~iHfto6bZJbwmpeq2hJsIcF#i^s2&VXN6{30AS6yvWQ;!w?C7*ZEKFXGi=lx7!_ z*u^*jxa{ruv)_OH{yNM*wH}=Ge#@Du(CN?_)3cLWXnmUo^4TbzjndgDowE`PMLcpY zO6Q_2&CfDcH(8S)@e?Y*GfBDA-C#Sxr`D zQ>S0ny#j#Nm^nSc>fsasb3A#na!?K2I9o4|9K{KK}7$4Q|Ms@rPN?CQlTeS@O%-#8! zyd^mRP2YuOc*l9GmPrmo|myn3G7;|FvBD{43qR5ZCIm@!vX8Jhz+f3 zzcbRVP3!Wv3ehzWp<>!0gk3u9j!6pf%L(zz2|eKJ`jsgUH9#v=0WH4*S~(YZbsea_ z9yw5za{*A{1|rD;18s*8ICLExrqS!*&~)Cq1&x|{RiC-3 zt?j_ARZQ**Lyl|RPP{2MR3VLrhGv>iybnir*mn@$hlV{M{fXCMbob=f5#OtPml>%7 zR5fDVU2DwnF(xjKrqi!+tqE z1l}5P+C29v5)amv1-i|{25hL|{E52qof^D!c5MqdN-(Jy?|$Il0;HW+;nXi@dU32M zXm%KI{HH2zFt|#0wg+qdauU2>>z~=IcjiLpLl?6CiTrHjXCprw`MJo?MSd>wbCI8q z{Cwo+BR?;BJ2b$dC^<71Iv=`lda81@vyp>Cdf+f!Fa?KraF|984)NeHjT{{2C2vQ? zF*u}u3=ZM^EO0Dn(RCP(TGG7h5B>1wnFZ&f$Ka@6eD!Bujsc^}-j`Eqc@*aPvo(_&_wcFy5{By!ltBTZf zh(GG7k-uGaj8iIX{lS-xXKLc~vrAwV<25>cRlmxt=~p>e=eZJo(@hn>RRRBy7oAwl z?`g{#57y?a3idtbNDN|w0-4xSEYhb6WZ-My;7hFt@nx->ynZ3Tq}+ioWsrOM82KR` zD?MPLmyRU|y0)OzbLBv7^j}VNUQTpgPIO*Q^j?YlO5|4}zY_V?$gf6zb-u}ut}gJ* zTDi@&+2-1{x!yJOoErh_KvDxD#SMr|6Q>{&4*jd!odF4vKq-THlTqiN` zc>(a+9>*7>M^8R$LKoB(ZwJ#4VR*#aX6M{Cj_DBv{1+uM7)V2N%|L24s)y!v<*k+% zLmp>)wY>0t*zl~L@Obqx?9zzonPC_7cZr5w@Vm7SI5Ml}%BsbVADLtAMwtHcF41eQb)b031@KKk6X@0w5)CH^Rb~YGdqW~xyN;92unq3al68ac4V z1J*S1k;s9l>!34@960bnQV(A;O2MH~3=Y$bVsMBDhiT;aP&_zHBgbjxBL|0gaF|98 z4)}H!I1xB%RpSw){W7l`@QiK)c&2{n_8WB^oY?cTs9CM^wMg;Lw&N%b5<2V!>fa>w zj<;%Cz`xy2*V$E{i_2;$auT|(mLiK;dMt%5hpuEP68YK4&qjVW@^g`&i~L;V=ORBJ z`T5AtM}B_g^g5ej^($y?JOifvUc#9rFQv~cGu1Jo&#Y+5`vEH-h>J5Lu z;kEQh1Ybx5Us%@t!@u8m!D0OEb<6PX;Kit4TsfY<|S{37&vqt|C)aOI8+ZfwA6ZEIBF@i=m$#R z(7?hUrWxhBrej6%ObtO6O7u4FDNSc}^r?m#*rW-zI+Efj zpc8AMu0Hnu8ZN6QDchWcpeO4}ZEv^34&%h4jl2oF^wDt!nY&vMBu1J+@h9Ij(wa`KUeI}GH z%`S0uW!nn~MrE%>rH`dB)(P<^fbzi-l5d>b1 zoKAPvwu_JVf>L7@Y*Om^ypJ^+IASyxcc0&lVC>KZ1p#_uJfEX;ua~>Oo<~^ zP>;oWS6qc&j7X_YJ$_0ty6BNTx^(*A!+m_c?!eRZ(dG8CR?U6{H@c$BwQ7zNm@#mI z#wVtmE{Cp|Vy3QRp=sn8CmxebBOi%;Byyli4ph^~0RtZ#wSLP01&2CoD4jNx7zBr| zgTpj(aEJ$oY2>sko_3*Whxd_=YUl9!Q?Axii;nmY3Lkzl-rzromxnfyC*Ks*$2C-#Dq3wFiBGt)+jB(8udEXNEt&k zr2$`+fLwAwn~qx#fatYR3@j}KKqNg7ndWpRVCo3Xcj{IQBAsmjj0yop%79U$fGKmd zVMrFYKk)n7r3PEg`r7067NSr#=ly^x(|}rPny=im__s1SI;Qy*oA|f$1d1SQ9TfU5hnxrCi%+Vp5u<7&NQb-H2CwOUQzw0WJb04+Ye z>5L(DMGxU!McnO}!rir{?#RWs0-{B(e^GH}qK-twOojqxej;Wn1hG&f6XU8CA^Bt# zIE97Mka5MkZdZ#*5@L;pCf1nGOBjIGW*<;Z>mYjb(PU66r2`vX(Z>0#!^jiwtb<(> zr4Mk=b|zDwD4Vro(E;C_-=nh2=Umz@nyZ4PVRf(M=dZKU;o^L07Ip0|F>g(;QWkPw zSQumEqq$(sAJLs;!J1X9jcE)~jA4UmVh+6Gfod8#0L@1ZHt}FHjT~6wL1Y>^h|G5# zN(KUsIz!^{1x%e3@rUg3Wp%d##e243t1I4T1z<(2-&Tn)!`S$!eUNww&J*D`CfvpA zew?xBDDpeTi;1Ge>T$wgsrTMqF^Qt3dReY`hlPW2IRUgh#^MU$a%x)6%fKs9O7w`h2|0O!(xSAG%FgqNiz9u$>RD=fK@{ALH|zUDvy(?r2m?ov~<+ z`3jZ*Xe2 z8HkD>AUb)iV{bSFh?0X&a?sWM^Is{`1)Vk<#&fn7SW*I(3WG?60psg^2mSz~>wqzh zUI&b><1f?5J79Dj9Hx{b5ncQA2OJuP>d1;Ao_ zpT}i2WBc<=K&_|Us>+KCJXmM@^hDel8}RE{nXb4ByjrgS`++&ul-Rd8Tw9DMf9z@> zvf<>aHyQSn(hL7y8tyJNS54evd!m&AFzJ(s99^Y%Co8C-Q$DoqNS|_4D$;*kch9Lh zKwKAH33U{VZ$Nfb<`i7mWlK62oQ z2Z?FqAQ3+kIXKKm4i5O>AUObsXJvqQslshW%OdFLQ_f(3jMDubHwl){}nYVmiqi;B?u(2250qoLwK$d^M+t%p=w2a zValUyp*xlqtXV_94Oqa+Ti`T}_hu7t;MDcG$T8o18K-0*9B_2ekk%P#5U%;LS z9F?V>1bWmZBB)^J{bg%< z=3UMly%NopRJW4mRw`yX;Q}mmAFxc*oxl;L+^80J-?sg*;(=-!`9$O(;(9yTfZQP6Uq1pihE7co_0|<2b564)cFy)3ZGD zBi<){R8phXBXQCL)eqVx9ncplptJMXJ80Qt`ctlgM~&1sRi%qAbcXx1%XA(VPJ3$5 zj=Trcw43!fns(_wuiR(GrM;|}nPVfJ`e&@}kv?N}9#1xrUc1^E5m=~^fnBB-#6mGp%DNM)AR_g6%P*6$iZPga&U+ThiT;C5DyO1 z$iZR0Z^WGl9F>os1bQPV^Yt%#=q)5zx{pNo9n0Fr#qX$2m}XPv|Nm~+zdIu#4vg~D5O-g|`$R_e>h z(?tMODB%m!gd70HW4vkP02B{E)5zx|pO1VYa==IrFs6MWi=%Y?ONyg1@{@q0GV<z99nJn3(B)_@TT_V- zTO!S;+E(1ek15?>scE^nA*>jY)BBZ$g4re@*q}ZIzG?a%^^9Z{;W&`G^>ZU>_@tzwjE&h=o0c&spu9PHwOZ5lZ^%|{L}@xU>Se4-A^^KLZJ z--bVKRyYDil*UcN)0lYNBTD6_-NQeXxAxV8QPQK6MEmQdSA0G4{&p}*a$GKYuwA?# zIYuFmJ(ahf1RRym`jnw_$w?mqb<|BdFdRfvKDMS9>#RPf97)!hwvNOHZ`?GZ9;SZN z^z@{A`kB_YG$A2%n!5c81JYtgG~d8tt6f)UO={DIV6|zNiH6JkNuPdLR9A1=C07vvzPfj;0D*%S1-o zV5sES==Kh@i-nP66!O?p8Cvujd%h$%Dnsjb$D+HPZfFipExKcL)ALldxT1Yg-@}$% znx(h5kI2$og>R-y%X%Ngdt^&1PXCr&#v03>L_G4BYZ4(UmOX#`shV^ANNc5LI3z1p zM^cRE9RH@mipw1Q_!q{hQ?N@ExTfKOA|4c?G-KNE={zdudOMZ$*Nc}_4&R9Ow}T&i zJ@Wo`FvisqgAUr0$1ytBI;|YbInIaoXW4I^)N-$OBM0R+kJ|iMY*4B)07?mLqF~cmFtBY`Wx$diu#mT5 z1sLfc2c{<1W5BrGvD0^}9dO(coiGK*9l-vIKEVx``vFsr4}WE#h7^eE-Jf5BX{V|W z5SHP+>mFSPk!j=y+(VSzy59Ls`&(mOW?YPKFt_SFDz@CVI=}B-OsN;OrteF)CpvZ9 zvHCaY#Oj@>-?6$69Ne+Gf0KN@)TwaYIR1%RSugdvSelIodBP2H9R&fjUf0rH_|~|i z0ld{K{%LnW>pTZ|O~-wB1gHujP}ZT4)2acK)pZ{+Q>PJW88 zZ=}pdH{|Z6_InA=dr`V)rSDVZZboU-)%_oe%gxfvDRbUrYL2pOx}es_f3|&z1*w80z02@V-9ARYd-yHc(gtU{_t_r|xMPmO(#t0w4Nrl z?hpLDs>Z_Kq}x0Iu&|gj?qwBhr)ca_Kw^?<0u*b+V~AshZ>rMhd zT?dhASojq06d|<#D3Fp2QF4C%fVJ% z$2ik*f4G4Ts@DIhvL8&65JMF8z*k}Vrd0y3lz>_viTJo~q;ybe{L5yn{wpoON|Ec^ zpICS!hedAqM2XYe8#A11M0m4Klr%p{ax!{xo;hZq}XaSS91Vm)n~7~ zUR%p4m9;6BU(I@I&2>6qU+X)s0#sQDRMVU%0i}3Qnnn&l^O3Jc4lG>H&wQS#YQveybk$#0(hCy@07e@#jcDR8!|K zeDq^_GYDYH)&Zsp@SMuvIrI4duz)3da2^Q6YRcIII4e5|V-WvsdzY$2H$&$b&)HM) z>>-}B2O|fd`5Di602B`_)9GBFshKdIt3=nm|GTdBw|X4AGq*_;myI_XB`m9sGB2 zNNyY$QdfNAz>w1Q7o<1c&fizn*igau-&Dc+&6xdDSVQc#v5X;V4;xIULuXX-9~hw< z*w(j0=n-jRL$!Kdp2G&i?!u=zko$Vc_XqxlR)WG?SZw3Z`(iW!NQrjF1fZM=K+|d6 zKmBzgJT1@T>ATqJ??nQz7RH{ZL3tm>KOY84spI6j_(yfy#nZl+L5FL_gR)%wEm8ui zhCB|n(4(ySzm4wu-P`c}I)IIl_Y+3<9Y)>&-^z*8E#F+d>$K(Q4pV1On+frOEB1W( zz(MK{>pYm&uBzwQ2bB)&4>&j2c695Ji3iMfZ=wa(wi|Pm+l8$$q>|e7A~sa`DDFIg zVU1=etdSouq~;+<8`f03E86^N8BDU;jK6P%A<+N=hG_P~27UYr`1;5d$W;iu52(%` z$^@^4eRHv(?hpL0ANkmqVZbR6E8f6q>bMIUhp|%Ge?{F2;tEUK2H0XAD$2JK&>*gh zUhILmn15AefvqNhEeh(CID-S9z)AJ^fB8{P8`Hx?9a zj-Z=9BjH!`Z|3OQ&3UicZr0Ho>RRod>R6pq@noc~*rzyF7o760b=B8$oM){IVNDfh z01~%=)*Jz7(;SHas(4VEMh-ypkpoaX*i0h_k$4c9Mh+PBeMc@n037WM1{m)SzY9h^ zhiAMyJa0Xm0;qNZ{Q$to$oJtCt$uLI9c>UVXd15J+fI?Us!s`pTlHDeqV7&FId1ug zmY>qxp7r7T+YUf$+%9#B-L~epXea1S>b#Q>xijy>%y+s<>kg)*ob{DWwt#d;TsH*I zk?Sr~kD%MI0<=5;Xw&orc*O(NG;&ayj~syF0caXI0L6pNG;$D`@Af3SP~gyj21m^j ze@n9j9#qoN0i&q|zbQEdjFaL8hX-J)uoDXqNg0r9|<|7A|co0brBJ+_020l1y!_xua)Dpy@|_yESV6GpZ9CYP1^ zMdfT@E9QSoh6%*#-DJEA>UT0;N&!~&0oK%-gl7p8Ss<=ZcX1q9ON?yZ);E69#E0#7 zgIK$RT`>mi8e2~kV_+{7=gruFK{2>g^FFW@qTsCttx0!RF*MoUm8pPM?SOW&*i{#3 zsTZ!qJfN!%+_`ZE3-_pI2Z|&anaWvolZ(6g?Ww5^) z&709&wWh0C&4*1_qqG{OwXV`yl-8oOCZ#q|0jR(MplME4fK5EuOd|)8`N%;e9x$en z14cYxOd|(}dC7x=dG|!%An3jbIGiLdC22j(9|X=VccR1KRyy%kI`LL|`gXoQdpq*m zk>8H|PULqYzZ3bL$k*M7f3I^rO6yTt?~4==spkM=8svZx4;a(P0b@RLz=#KoY2<(r z4;a(P!C}5HQoaZ{y!m+?j@nxEXDokRlTSIN<}i9lpgT^fNyticlQYs~_dVStWjs?e z$0wC^;+|qC(G;WCR18NMSia~=hn1$6wRlUj9=+D)sKn7-cd?I_-u2w#{=S=jzMEj! zu%>6BjqWV85zUQg-m|7>p?e9Ndr`UKot z(|8hi;(=!xIiSo(4k+>9F^wEN;x{ATjC?clP08Dd2pqLT`Nxz(07fpySE{4~M&8Cv z>Kec}J8i+}UIZBF;T&hDEf}R=fH95y02pEnFp`7AG`$QCx1Ue|huQ;&uj}K!>_SG~ z!C^d+;}r4W&`l7hR7m~jx(+jWpLh%zrU<2d~lRsi%}2XU(pq9NBh&G zTZ@ej!(+=;_~#+ET-s+?w_H$r(*Bz10~ch=e1G7t`UbngJD%9qGs1~+Y^qtgN636aJJ00#$^l*YwLQFTUR|NJ4en>Ac zV5=@-rfHNgPCO==Mve{UBL}p2pqfSwEckY;fJos87}JC&-WLxJ)5!6(`N+W`{z2s6 zFdsQM#Dl{$a&W-6V+9-vA8;r2RcAP!Ac<=x&XjADEM!v#UEUy1;EuL8G>L+i??t4c4{ zv8L8IT(N%}le7nfNt&>6Z5s~+CniY`L!^fdRqY?q-3b3Gh=4C8KuZa{stl+KzK(y5 zc3i(};RBPvlF}hgmNJO=#f&yWpE!m%^M)Bu93EjDcUrRhe(=P>=c7ftKI82D%iVmq zZr5e>rlBY5U6)ZUL~ZkV!Kc@C65cvbeMZ^amQTmEf9@Ukr!MWUQ9N^t&S0Loj1Rj% z^9krMKHU5#4s3N|rhJNprinRB5|1^ekz%RCRN-v`HV#3#7M*cGLmyy4W z{8i+yB7YV6E6Lkg6-0^%z?ddNo+pC9q3hr*`{HTev@h~H?YA9| zM-&x+r~tUE0O%lTHE~O6xTnF?G96%=VF0Gv0}c|!K?ZajK;oPJh2n zf4@#Vyovlx6ZzZ7-$wp6^0$%iMZOpLUgUdy@kIM_EA5*m4ro_A?V9E~?TM#7 z(_E(=@w8*w7dP0e>qoX1`H}5Leq{TRAB%(V0Eg~|;HX=s`x4`;FMmjL0Xn{ZTr9(eSaw zwIXWoxetAW!Q12yeR`pD#`od0l0_d+d+6g8UTQz8f{cMjRi>ylBebU1F*|O;Z*T0l z39Ie+fJD^tiMYpBclP<%>fT7l8m(0@K+a&VaMJ0}5-O2htM<-k6j0HSvD8CV_0hg-ZY4Zw;cm%*XRnm}o4O{eBS zElnS92e0A?H*Rs!$8`W*t$3>jiiz#U{4Y^>DwM&)$I>#rZbvcoqhsP7XfHG?I-B~tQk0|S(I>|lt5`} zr5|oLVJgY|T%jtHtl+FxSSLC?b{BorjDzMY#2kXWiun@V`I}CIv zt-`zw>gPV%0vA@|#1)64GSUst!)m`?5)m`?5=X#fYIhjn=>Mr}z z>Mr}z>Mr{#>aV6;?3FcL?3FcZCDq0=HYmP;Z<=@mw0J<9Mh;Z-kpoct%gDiIK5`I= z2a##yfPoJV9wLFGI_s+QL3enVimm;DAxkR2@HVjOVp3MChp~DX$Kyv7A3#-l08~x$ zKy@-ZlVo<%=>br6+rxQ=+=W`nY@!F32@9QV>c$+VpB=zc4M0@{z%ni=t?k)7_0jsx zjB?p2y1bR+U=;#&UHrYTEe)XiVyoGP?dt@|>jcT`X$Q;e8PhkBzlr=!8~NMF z-$wp6^1aCSBHxRAPx3bOz#;#F!!-T|hj?(9Mh*`1k%L1#I7}l4hj?(9Mh*`1-Tox> zP6Uq1f*sS&adAp@r)M^Ls2I)V;r5n<;j zJM(&*$N9$2f`FKLT*pM3Z=^r=x7PFmqt9$VHfRn6H79Z(yR@$S^nvdXJ@f$;=Lipd zSj2fWrfd4fYEf)8%?IEx&V1w;A|4ys>;@Y;OldZG4>lCzyayX9<#?zR2l#Ei1M*`2 z_a@(IhY`pX)_^w6c@pr72d`=5;58pPc*TR)G;%yG9)PBi1Iv8hc@=O}-r;j4fKeuj z^YpELz~~2k0Hf=KM}4;2=cGZTFLM(Vvwwiu3q(3q0wP__&aJ)PeAHR-3PWxASR~o|e-7z*n7OhSv>t`O8 zT7&T`Rm}@~&m5?l0{)@UJ#$T8?NY9yese1}vTuitZE#~=G5=@9^Zmv=CFYnXie;r? zAx8OPjR}mws1hc>FU_(rN*Gl_#!6kZ=B`1yPLJ}~5XNP~xJ-@zcANoUaSw9Sj05nB z2d`=5_}_fw_+LCIO(O@Ocsy+yISw^1d2n#M0*8Kl865hVwI}c5Q2gOCjS_Io)#$-x z&nhm#p?C#{T!kJ@drkYoH!=+XfA0UqylzgqcM>EO&W^yL00M`yCH$uB2pr0e@SFA@!6E(-9NIX1UpQ)okaobKcIiI_ zE;vNXA2p=FQM>SeQ_mRzqj3osDV0-72{7J!=hL;~`Qm3$z_fc%ukymhp`KyxeO4jiWGNpOe< zhiT;CFdsQM#Dl{$a&U+ThiT;CFy9v`Cjv+1{E67|M|CRwBD@`csI%!8Mdla8AC=$x z%#A(*?pgDpKW?VIYk&9qp}&uoz3V!$1K>w4sAs^MHT~h7M=s4ASvtKBa%^9ADIpc$^x{h(CkzY-s0Gko#hk04+XvosWRh7o!9$_;$E} zNZ|k?)12l3k$4c9MvkY=M-C$KfH936FyaAY8aX~PFL`i~bAUs?p#u*6jx3>2bI&)b z9|5CRUI3#61NV?9sM{+6_@<__HLHP0vl)m~6CGP8J^Ya;U{y};k34}*)#0O8T=XOf zz*6@|p72(w*Y}+QwUYiizSEom0&@+D4>0@QMejY0mP2vTFKNWz{~iE~pL4%J&_$%1JbAc#mX%;6G5)PnNXA^*bm} zmbLxv(eT8_ZQ$csu+6sNn!M z(+m&#K|I(@BL|WB$U!6?C!0nNBJm(HjT|?c?;C#LP-+eiCCK?SIrnP98CZ4EkHBzLyK=2%Kxzb=F?F#q zW`Ly@fn~bvfdwMB+#)hEj~x#%h9C&R1#P!0U~I6t(E}^?dJ~D`r!#utz3L@ z;Nr4haC*6@0n^LGNxFRERO?$^_Sc7Bsz92mqbLa}nyTpWbED_Wr|*8qpO!{rMPtR8 zG$0gtfN0fUSl!>QePMNfyB1h&fP$zAP~dgTyyTQ5?YYdWdT)SV0o1+mRUPo|F)qWi zfadGQuUwGl2nRm-!c#8nQXpcNX#x{##AAqQMT08HJGI&TF4%NnlM$7v5>_9{K8+e<<_!n^y_xc+chLXM zO7qP0EmF_9Z#`%JpvKf&&zV0gX0NA1leIzpKMORi74sW)B&et zw-8=CD!*Az)!szuaD0k;Z`r(!;sL071_Daay=e3S#b?3DmoNP(wqRr&fKhP@M$^P8 z7{$K{|0X={(RJDs#TBM~aowrc2VW8q)T_wAY}dc!fTSG-F6~H;wQeW4Xjg$tyVAo} zw;Oo}2YPJ`cW_9bJ}_x#3gb18G~roPes#}r=YR>gd~nKdzf9C7}J8A;B& zjMdIp8KUOA)mh6p$!ntqYih*%%?C` z;{tLG?LuBd9MD<|o?AP;P#bqZ)dn6Y<$M5|t`!X|CR1RkF%SuX=1*X0;R!575QvN* zje$swfk>4C#*N=RJzk9i#@E+Qj|GeyKMoADr@%Bv+d-tQ$O2QY1&kZkA0J%jG7ClcS?PK*420w@hA z)&Nk6m^u-l05s6Z!6rGd)Do~H2bO8%9Yos0rR$#2_%|5IsuMX7QAzsmD-Q)@aILrc z!I*Em&LCPa<|D^F;*X6ZlAa4SG%)^xM}qX44^cl4^Z3vh~f zoML{}5A-@t(YU9t?2OhaEf|I&VALpJw8Rk@MtM!WJNhaEur$;Uz^VvbwHY9i97KW* zBGXmR8z2&odo+)L$TV`o1$VS#@w4bAS@v0UlRa^CxU`;OEMC6BDZOTg>+(&1>)`S# zU!39Yp?lY51^WJ}D>wXYnkzT`(T^)u;_cFAxp!qv#r%rt)f-|wC_|nQS}^Jp17yHo;ic#*z&kha@=UzhYOeKI^Hsk+<5m-;T~PbDW;Og4niIr8h_wW zU}A@P1){oBJUD9n{a^*>DZoe>C)Gtidc#s(1d&1-M5cAaqY#KRI6-8(8oE}VXhZ&Z z&qj}>#K?_ijvcwlJ5)3=@H@J%jjSGI*AB3wH#$FYHG0!I`{-)uTG6yY*bh0FL$wkFAA^A=N?@m7&MQo8#C4(kyIv8x)0tdCYmO$ ztiOK>!(2Xc3==<+dNIa)1OK)bo@efy{%ag8I(25< z^lVRZHj=ZEoa;%>MRG2Z^GJSKC+E)J;RCt;Yu$gfQFrDRYmjQ`w$>v4+B?m!)318$ z`(J9?3?T2F+8^L2s?a@KWtuOypS>;JA5~HVIweVV+x>iS)WRY|<`^6r z68}GQZyu&ux1M!nI9*AfLDm%-se6p>(53{jGhMCB3_PvP&m@B4lCuD6GCzVn^o z`=_3I*8Qxt?)9u^uf5h@YwwqCQhhCdxc;=4ezE?k_@mYoxuoEskl#1tOQF>-HHY%n z17u_b8C}mrN{5WnA>)P4P!>2%=jb}6bG*=bUFWd0e-A;@;bHk+oKnk)1ofc=^`Qjy zp#=Tm;D>`B4t_ZJk>E!LHU04{>BvxLE<_j|8Ez>@2U=!FoktTS*8bQY9b)spv$#3x zjP;PZxwzn~cEi_l$oDDuN{6pyaQIpe4mIhplB7mU>CMISntEjCdSX|rhgbQu?3kc` zB0j)uK^&J5D~)+YvJs6&Z9 z)PBNQYw}N{hXlLu6T?z`2Armh)P&vU{c8;BTxy__`sv~p8a_*$kf!x@AlxC zM!`Kg)kXYhUPN?jJxMo7(oK2|1o!A!6Wt_x!bSG4=_c9JO*7Q^`6-fepUjlt!0)1aDg#E$y2j3rjfA9mr z4+K9D`~X+8?%n0n9VV8+5B6M4Z{eYQui>F3%4>MI{!h%qdmL>Zwd8oSKi96`j>D${Mzt@uD z(_GJ|AfdqM@BnJ=Lq7FF}>I4VdRKy^@`wM*AF)wbp}2>z-4-L z%+s)=Hmhxk_`OywxiSvr$~aV4M(46Q#O2{h*x@qkBp)u@&iR(Z+&p75?&r5hxW#%p z25&vlg{@^Qj$+|F5UrD0J`)4cmQuD`eRV*lxjJF1D1xnJqWVyx4Vp@ap=EHqDg8)r zh*=Mg2c;h!*?FoK?9j3v9M_?T2e-Pn^N3xoX?pnf^6TY8{jOK_r<_`o^pry{$?ntG z?pD1D8SCwK*GlBAJi?dgJvPz_Se$vx!RWVBk5w@8VF88du_hvU`tCgXbfvzS;d8sk zotx7r6m{IWch%Ow&#Q>zF5=(wTEIogIbm~K(AKi}rTV0tzkNu9ZRUD@SzmFWnO+mw z+;>UZRe60e(yme_nI8^m^DpZvYOUvMNfplnD28WS$ zG$BKJb6xfF=&iT6p6%r}*Hv#h@1i~C|L)u4_O;tXsO*~X)PC1w^G z&GzXlr^|=oR!fPhjDZkZUOl9oT`31LUNxkgx5Ui5?W@K|+;jF%ZuL#ocSt|}agCz; zr}UGy=v&sOb?4z%Zuy~``kF%050GzKpG4K6+XGWF({IQAhkCj2fHNxP&no5MMCfB-Xf3d}5XA??eB1SO?I$uc-x zq#p==AUHgn9vmLh;b9pZ9@_>4c=$-!XP*bW;*VMmy(fR9oLcTwIa_`If+F*C;M}HB zwdQ$GjjDi4r@s46Zg_A7AM$18Rm&k?R{mJAuBb!4qWopyL%x{wE9g8G?mVG8>>@l+ z9(EDF9uGTr>&xheU4+g#>oqGshCbpVen8X9BkdxNxCqTGV>_*b05$Jp((H`>fzqSx zj7Obucg;C7>Kt|D`zuNBi7LGr67YpCOx$3Z3zT+Cr=ga?X`c1qw4zx{(F%byz%oWK zt~uUFJrN(PKYr^S?oD2}x0IgY0hYRKVQHDm7nagtsSQ|q!g_FAFCCVa!69fpI0W_R z5JpOejOBElfrz(iJ@!@}&1Ls?J67E(=RNQ>zgPK^9v-!h`egXlI;uj7&^lJpRMLTz zZ5D@+kJ22U(7TR*Mv-{j!RG^r$14wckK6n}MV#Kv`bYW7@QDddHE#9&yUKbZHJxb1 zy51>f|Aj(MIN){2*!wRhCz|z-Uok#8sT5v=B*V!zM2G+ZsGc3!I|2k?^)Kg-O7=>_Y-AtoJvYerq1Y8C~Zp_0%Mjx&`GkNQxi z&mZB(dSR3*JV(el1u)jjbv2Oirh2Ohg>6FbzU-l3X$s?a*qNQKp% zdOy!$N!69&PqtIPGM@JNM4y8^?L2zR&v{NekG~dp#)lxEitZU_oVSvbkY{Y}PX?Z~ zxh>A-%;ecvp6xlpOGR=jB}jrdPG#OfLzPZLEpx<>MoFhVmceO?^-j6Lp{?{Y!MpVZ zho^9OTDEyGZ+TzvV8(ndZzP?IU(dy_=W=5EeDL$Z&j&vr{DR}p=d~}y=t7Jx#OR`p zd?@N-j4sCLVowiJvphx(k`y9|9Wo)yt%^b$ot~v7vZE!I@qD+u;Iu?IEwSv>6P!jU zokm!8>JJWorNiH{%?poSa%26ANtb****%xi;!APMd?IJOjmp!*tJ|qo~yTf5dUhw4zTz)G_TsywOsA-Jp9xm_({UTGCI{r zrv^z%woE$Zp*KB*Cxqh!N!n`}omNSwL6R`Gl%8_v+1_$q;n`E}E4+K^d8HlO>fPFb zlgy*t9(wj#^(?oPYwiFY=W_>KYrj=+Egf*(fq)r#t~-y9^IcE(UXSGsTN=EP>*Gd@ zZp7$juEd+cZw9}~h+a3__=~kB{KOwtmgz4zNT+(sq*J|gYPJkc&C;paGB`C`Z~KQU z68|W@Q$G%n-dpdR>ieYXAs%|`d8M6is26vO&ztIhSM9AEbdLC(Y9H}Al_NffUv^X_KL?a-gWKa$=`$<>^oYkBT(;I+X)Br^xVqdKykGLQcoxbSzH$h>ky` zb(#XbAUWeBKz>v0j8FgMo?ID-(=*t5&*^L(quZ*l@{xSjwt6+;E1cpQe4O>H&;L|) zO&G#j?uNBx&hNlkI-D(o!`XUp$V!K&<&a-7Jrf*)N{5?e@IFBZfY$#Fr#pJn|JQs6-^Wy7HSJE1_k~UbTmtig) z=9axYRvPcsYibZRkM* zc3KX=N%BgJt}x=+0)j?jaWxiKyB2Vi1N#CvX^}~ZTt5XLN{GxUGB{&bS?dQ zt=q5gRlmYg@_JcxBJ4V5evSZT!!f(Tz0;usTOkm&jKxhXoEM@(pl8_=LXeUxuceEj zq!*Z>q;x3h*uwSTaB+GDlN-qo6Q#q%GWl-?hlkSPVcG7wn!&uvLv}45(qF?v>7N!J zHG}acJ(HBJJ|3#l<)^$!uM~gh;*>Y(mBK^cvhhy6p7-*|m6X#yFQ5;0DN6ic?ewTW zoOjx%4fNR7hY?Qur~u#AAom&P_9sfu_>6(RpX&9_8Rzz|x1Aa1BVyz^YcuCLYYV<# zy4Cmndfa^0<-g`fKxlIFc zmcbz^{VXMT@&#q5!~%}!&G-g0buL|CnLT~nEgdIYj(U89iS^(xkq#5f;4qPXHaI-2 z2Zsk|3Yzh)mq)8!m)ld%_Ht8B&39t=_V&b{U_9qOQ~Y>Yb>98zvs>p4dq?7e5#Q!6 zjB^r&n7BCF8qF?_NzrKLC#V-)KrOdZDYv_G4hPL>JX=2J$od`y_Bjq^ZTP?R=Xq!F zOy)fCReb#UT{qNDd>f6n8Dj{%r1803-UtXZP^nY5S90#XPMr^YtrFm85}}- z9tt6)L&!2XOsogL7#to-hlldxHS{JrdU^EL+gs1}a+}`1G}7s;t-c+alU{Q7smJ^& zhfD55?+RV2?xUA4JEP3J`nc?jzodS;+|F}(+`k`mrSg#XN?De)(J%79&Ki;8;P8lZ z)m8kO3cTvlwKwMTLRX#JL*LbLwL?Q+9mZ-xix#RUX_sYsmWD{DA(p{mZap}pr9;{> zI8>#>(lR)-toK9?Jk+D`uuRXxLr=uvp)FzDC>$P^~K>gnr^rVGIUPD-5BS|9>`7S z7UCi8rbm}QqPx`1F&^uc*?4`&wuu}Xr=Fy7mg!mAC7sq-2B!_ygF{+6R4s!;OFFbH zgTupmPvpQuoe2+pTootlgbF2ly}@TkMSPdDet&x zA6aK3J@lNKYPRMyiQ#1{y6x1olC||re!Ue&@@a2hlU#~R#U2=g^{eanc4A4J!<02 zOJ+pb>!$Gv4q7Wo^DKkY9_ztrfONQ928Xe9$XW)6ll7jBL3&5MN1rHk;GxigN1e&? z*LNW!BiyV=g^VHt^AJ@VRLZ0q4{03jFikjP&&RZl9H+?%LXMAyr&x zTV7~eUYOPo1UMWnI?@SSo-}Icx#*0J!i&ymju#zSHD^kbYMPWzqe<#48e*AFg}!u{ zTLy=(bog2ZhpP495R?ur%ixef4-Xz~!$V(qgNM2R9_ljOs4jzt(&3?azr)v4WQXed@`ZHC4n^7OJLUO|y?Vxqz3TDgax1qt zZ7(}F%2wZLWw`9zXRWkzUvX{@6A%zroY5agzv4W;)~`5^Z(~;-UcLuibsmwo?Nw*& zj0>F~Sh+e~ub4JbK&#a|wAC`bbSb?^yOd6AEQ4PT4s)eL+A=s)rNh!PIJB(yL;*b1 zd+<;%U7GR>bMVlMd+^ZR1s*z?i7)j95qM}$3y&H?zFxkCjEr#JbR8IgjAt%6j)}Ka z7-Uo#PT3X;*K#RdYkAdI(zTXX-Obm0bi@;h>l1qQ#N)vAZf+=CZ)Nn7`?@pw@cxa| zc%z$VtIuXPS{ZdWNCxNDjmjxE>y!&M-E0*>hDvD_;pO>F=PsoQ1zN4Hq1Be@Dq1L= z##siZNwvQIT_37CXiX_!BZbz;lGa$-^3;d86h_`D^Bb5~&p=uhkhZkt1r-C!e}z8% z(>waU{PWI@41W0B{lW3+iH9FP`*3<{u* z6+CM-9PEI!zEp|h6?stAI0RLtLshS8;#ECZf+gciGmiA%X;wkMpbPr8Q?v^9$a2S@ zg{rk~|Xluuy6`q`Vm~UcgARRrBjvnZCGzBOW24gW8i@~l1 zrOIL`7DKTZa$Ne+$`EnsXJNxJ8;;p9Dd4 z4kzhwvJ4I<>&Xup>5#Du4jJ?&tlq*y`Q8H$*ZaxvAgn$MJjQFheXBp16po}5N79KS z>BNzoSQrgH8hkYPXz;P%W5LIQj|Cquyz|Yw@feNAXk123kU&OGgp6f65i-&tV;LMW z)`LSvI%F(^Lqh<`R=x)3a;`}LhszyCjdyOY;H0h4+N%ZXc^k0ws^ceX7jDr!2RG?Dm%kj5-% z)O2K1G$w87gN&20gqtj()mTn3`1xnUr^>8TI&5VNTg%(Zp5otjZX>trb5+!M+h6>_Vyo|{-b=mh zJRDL7zT+Zr(=FKEbDmXQ}#-?WL!c$*O zEMtc=gntswklkCHAsj!rzE6e+KkPBg&ilWuVRM-M`+wd4aaJV%aaN@MYc8At2jU-A zxDEJQYaZ|i9F1Os4mc3K|2*hC{@Kex=kbrs4myt)(1XKX(GJ;M>nA_z8gd@ra)zA8 zl@3)sTp!hpFVwG}58Kv1+&*mE|I>&$71CTBv`4{8Q!Ep_G(bAsErY|@dT_`}hoWV0 zh)FMEnJ~u!PDXkz516RuU}8Ds7sX*x;re;JQqlJ-ayy)4fh#R@A>qJS;t_}mgqYT? zxXrqad?^ga2&V~zn`Jv%xjJ6MO?GgTd{GSWXO9yM=%)kn(zS>?f1GeWQa zDP1cggFF8X)?qtFh9ZY8N4plIu^5fTXe`FM7Gtp(i^W(h#2VWMtT&XEW+*GoXtGvYl=VwA4WL24z^$$tB-?S@UZtAm+8psf z;HMolOqUJKvCIWFY9lXCM;XI>wZUIz@RuC3na2o-*KBWxJORZ3l9%2 z+Sl-q-DiM@>wg`OTF1QVk9W(%tC+X=qXL{BdNU6$+6hAN-_hkXF}U;JtjlR)D00|x z5{o}Ai%A0MPx?P@hWU@1Vg1)!c2%M;J4&qRa1c>!DZcAWII_3jbtZgp&XM)jycMS| zbu!2CCUfR?GG}fl^U>6);8Ve;f=>mX4n7@xI{38kCVb&0H$uxY-AY5G!^1K-Jgf(Y zhje&Y28V}qcvuF9hxMND{Ums3O@cpa4e~9T+d)RLf>T`ifPd-(GFsW<6zwCl$TW|F z@r;aM5&l~Duol1N z<4Il-+#2t)#g$V$#) zz_W}$TvNs`(B2A+D@A?zx4cx$(G0k=at?Ry8dNJzl!0u5pEfC-Hd*Ej5p9zGnl?E- zc&A(%qI8H`26ug|bc55;!r_Muk6N&B-Y$z1&gA7k&iLg&&VBYQSXx2lC?0T7HCv@# zR}9pFL;`rg=g6`qY2|j<4z_YTYzKXk(F>A6=l)~z@lZE6%uW93gf@5hTv|6JKW0bw<`|>OZj}oE-X{PR^Q+8 z0%*KbO1aV*YdJ3`E29->++oFebOktt)|72lLay=4WlOshdpNm?y;=t48i1>CXj;ae z&X*1)%i#33_2ASjoj$gFU46COi5)JK9sOyUay~0OYPmC##m#8sSmbyXJHbbTj|LwN zJ{Ei|_*n3<;Nv4%){I7uMUM9@b4I*c7#WQmiyXHc4Gs@4UV_7;CS+ft-}r)zmnSiT zjCC7*sf=LcbxDlIV>I3vHJ2Uk(PaQF%Ul*Cxon`NbZA)yhn930Sq6uZ_27_cElOep zBN@R688Wr{81bVo$S77u#(Xb=jC9CY2B)vA2ZxMw`pPojoujX$L&h>VJgf^3k6L}i zuEj%k?}3N%zbhWK`glL^sMW_v)*7RcW0B)odvIUNI)nQf>y)k^K zH3YFr{vJIQhn+5^VrLk3O~p{`D9sf$fit{1pGdgFPvH(f$w_~O)yw`#e}&a6e^}Zg zmQ(qx>y(ZBGy#S(f}G@ZjHcU0@HE(SiQz?sBHZ+v8r-bs5`mj^XjulwThd`<85}a! zgF{9-WGsW@D)ityRAef`d@^^tS;J9pMT&h2A*{mz|*j5BH>ZRb{sz9Ho43(z~|oUnf6KuM#5pF^FsYOfD0Mf!$V7N zcxVS39$H1?D81$f56u?wk!6nj;UnzBgODs9wW{v$sCrAM-cCK1(J5Ct}=$S z;E+FHmA#KLr>r4c`qRO~E;!2un@4f!QN*JOqk(iOq}7#>woI49S2|QJgAWED3JyWx zL&4!D9a@&bhl3CIgbX~?mGIF1ErTk5Dy;pW7fZ4Hx>LgiQ;cS!ALn^q@#K; zQfIF)P)+IzayLSlukL8O*u{enzje0MY8LZM+>CG z)3TF)TX{|JjZ9>kJsCNb>2}gblRldCv80bBeJttY6PZp=Mo#rit0!~|{)Kv6@QYdi zjZ7-#?^epsJ7r2KequS2kREl$FVpQGHoDKlrf%rJuBYas?U06FA@6t^9&<=mq6;3I z))%S2O}p&SZp_DHKHfGTcj-&bL)kjhl(?e}iocP>9i*j0+A=tNtp|stbi8O89B$H~ zWf>es)_dX_9_nRySZ1h$hje&YW+;S*b$!rpYX>}}!^1K-Jfy?JGB`Y}_eAr%;Gz62 z9@0N8JZd7|61yHO8QjNr;76A8bGcKA&={P=PkL-M4}e~W?}4!RcL&|C`;Ddjh7r|icxp@vdYC;(oi_(FhO zDc(YWVJRNQp;^ioZF}$oSH&PSEfbS4lnyz|!aHw9L$0ON=dZW={6#mI7UEZ_A*sCZ ztHcc*PMNZ~zvu?p#vHPxY|0F>r4(77X!m?!8`A1{NLwa=A+0Z@Lt5!L(=zyEE&y0+ zP5?pS5VQ;qE$J|_3=R+UbTq^LTX-nnC&5E@?}XCz70qdWde`FgE@H2+D zM=zx*ZYZq=Iv){z=#OXeiN4N&`p}W&;gWikhteyLW9?BZk58^Xsytj&)Z3WN^y}Lf zKg%K@WA%(P>RWpN`9E{LdxW3umtx!5+bCt>JpV=I`C)pb z33{5Tpro0W2};@}ohDfZr#04t(+26#w+s$T>2R|Q4kPP5K@SggK0GQS>W94O(L@4d z6bX<~&*NXq+bkeD9~j50+KPiw9ZEf~P=;0=DP68O*?~i~M1`AH4{%eRxUA~*QBMeJ zor=r0tvg!;#RYES05@|4bVLQ|rS|7W| z-N}!u$0_WwU)Zz_fRPv@ZUyd(X;tYm*nV&3JzlXB)= z&c{EF*2F0-R@~57%ft~)lupAegVQGK!Qn3*>XyM_Egi0w!J%ZmCr;s^9)X8t!T=uC z-5!4-qi)8*iWSIM<}5R0lo>MWX8c$o`{xuZ@?-fSM!2g=_Ni!SDF;rXgl05`mhKrX zT9xS=bQy^oB|@tb|A9_?!L5|PZ6$F=zt;uzjmic~Eze=8MK&xg?|3l|K`qK5Xji?? zWjtwFytVWD>q*mMKa$W?;m;~@tIyvnd{#)OHH_p7yd~gInl1Eq{-cz-3H`%5$o8Xh z6y;TlUk#wV@2kgC6xB(Lceq@hIuL(I*`8MTVfnOzu4G*#$ORj9>eY1;C6)`ZY^abe zEc6|3Q(1I0`(q1>z6AYct{#%DJkL_aGn@Z~FMfyKZK9tB$i;BCjH}@;9qyLF7lOlB z=`gko4n^z1AtoK~Sq6s=dU!CQhKB+d9tt;nXg%W>JZjvypdn)+mk4AO{g9D_j56Ul z#TR50SGYjJou7WSD`xFPu{I+#J8Uo8wn^bNuRVj%VEqelPgF;P-;x4}L%R z{owb5KM4LH_=DgNdX9I&L!Ac?%bY)ghje&Y28W0B;P8+R56j^2kPZ*a;P9|6ya^I` z)Ft>+nwvnTl>gMqkRt#_y2rxkk5>=lnKJrG89_^fC+^Wu2dx5Y+Oo9+ZjB|5k|hKs zAz0u~s~Ei1SVFbp(ra`4MjVE3&1CaK?@+BnUOs(8C+hKZ%Xu|iewTi{7QTw)@}T%P z9yI0r8axDrw>+rGhnp^+@?hnM5uOYqBI;p!{9$_hVS4;wBH~f-N5LNje-wPiUkvp( zr)Od`6Qh|J&Du!QOMky@*54L&w%M4^_Cy)n)a!7wOccOPI@~OS!_9hdxJieWWpHRo zhn8hWZ*5&B8QURCP-L8P?f<=mBC1z-Ex#kG-QOs!;VRQc$in} zX*o*y;h}u5byUq2X%9aMpt;;1)WH0BP*=l0t_hgB-?!w)zDV5tj}s+N1{&M1_Y^Y0 z|HS4LKnFY-Rwd3mH{i=-j-$4BKK}#X@eO4$XLG(x+xZaFTwUPI6;;5z>-m2@1xyw5 zL%6Urhn{cDbEU9mr*U!?O|ne1Kwmoab(HkU;Lb;Nr9pa5GB{Kpls-4O^O@w3e_RZ1 z$`395I`I6Uf8Ps6`h76SfHz|vJZkKSBkSBMh2l^}N{ft!2gp=xp0PV~Bozhllmx z@Q@A<%i!BQQ*$qeUrpz=(C^INo;nEW@U1?7uJ~z12rYctbTqTF8l^~Fu(^xzs(!%{ zr4sbLcRy=baCU#NeKFY=J+%3I?~6kUR;^TGmS}$#^Jg}9XgC5LpnSxlTQ88sUO*d5A}Ga^Okz*vV2=Th11?Q)>k;~b^YN( zdmA1#4f}Mp_bohXBzY^3ntY{y9;SaDrhgu${~iT@6#P-}N5N-;&jg32K)A)ZO_(Jf7;0wVQgD(bO489orS@37Up9O!`(|_=gpQulg zawW;%_^qCaBptrO;cFTHck;tm>F~7--YFNpN{6pyaM#C^t>Ew#4qs%`fA0$(b<^cw zRc^H$tjd2Jtm?Vx*3Z0lZmD*w+;{SWu=n!2vG;=C3w}TN{owb5 z-w*yE_=DgNd~}MR;HaB{H~MQIC7!H}z--4tOP+?2WwxJTBppVU!C@pFMwY>0WIZ@c zq{GCrXWRNSUVr$#l}Fu?d;HMb?%sCdX}3r1i$Cj*njUzV9(d>;;0J5!w&ibOewSYV zcofS=v3z7p|MbbDX>Du&Ze^Q^^TL8r*L>$rapK|ho@z5c%p|#-CcWl;3?U6>g}y(d$~vzhh^Fg59#o*3=R+L z!Qmkt9+tu3Asrr;!Qo-O$FIFSdh6}2XM4GgU!SJkPt)$FY4_9kWg+-N@P*(D!54!s z244)m82nlAXThHZf7atyc&MH5uuQw*Asrr;!Qo*&I6S1o!!kHLq{G89I6SQP__dcu zZ@s#nViI^&;>AmlMUAXDYM5ymsJ0PjUWT@PjVmzKS^5 zF5;kzaCrxv`(c$gl|r}rs)#t6oB6)GogLYI_qzYx@BUYpP3LQC_(I2FXuIY8NZPij z2P-s92(7Y=1f4vAXJ4+8eTgEND4Jkm8GL_mcqknnmcij6 z9Uhj!;bFZUguav=cUMpHakaiF-)GfNT&?d59;{W45Q+bq9uOYo zzkikg;w!G?L%wFFbziil5~IbIx#Vc4bXsQ_{BZEY!J#f3>XyNwE*;jE!H))q5$Vk(4G&#n z@UYA!2M@h>1P`U-V9Vg}kPZ*adwvxJ7g`Sv59#>KGB`Z=#g*{jl72mp=8}3nPQKLh zE_j;XC(jf6@ZgeqJCE2A^2ZYBTRV<-|2xtB@8m0&!7)dfUzIy{Pa&tTcKCg{V~#ET zhK65uJJt;?0{6Ic`*phGt=yz+_4%E;id6m0RKdr&k2{v zH$(VPhBBOJ73Qz-pXe3=pOen*SNBdjw?B+>vXzl*r)-_^SGS$V#~lh^JYkverPb1D zuw`%>XFWLdrNi7ZI4q?@&@wo*toK|V@X!%?cqnY>2g`&VJf!3Ek$O4|4?X3_p-P8` z_9)?@?|$Mp9m#-4eRId3qJT_oRDO~6$MKeSkYHr_pf(JkNWSL-T7x3rG0*V_rt6vZ0@|f zXIgo8&r0zI;+~lAdE{(+Z0T%!Z0U`~y|LUI%e^zsv3Is)NHc-KPK_eHX-rM8lTJH? z(*#S|F})7HJ2;$$Lst@}mce0aJ$Sd?;P4Z^jYmzd-w!;vDIav_zB0W&=#Eo*dm4Rk zCUf!J_YTf(^)c7(c)sSd>7jk;p?&G0ed-}5^o4h4v@zNrqx~^D@UT0hebku>A1Iqn z#se{gzbVZuyfB=EzW&- zR?kzJfC3+RsIl=&HM2bONXoBSnc4Y)9b0`MTYdlNi}>^Tnrj|$-CwN(aWu7Te$%ti zM<~_q%2;w%;ID6=X68bmp}G)goaL+~4UtY8EQ3Q@I(#jI9|;aErNhWFIAp8~Z!R*N zuZsX4mbnOUt#o);2FH!ogTq5QF0%{{59#o*3=R*y9*o?$Xd!MPUBt!Sp^|HEfbVDO*%X+gX1*o!67CcGM2${nsmrm28W0Bo}h<^Iv*bT zm^S{ho}h(?bX;W_oX(RDkD4T2(97JA(aaLh7>p5QJWqV&nI&XoM-TEdVNK+>$SD7( zK*sfk>}z-kr*kaB6CUVI=j?jS%axt8iMyV(rhB{Qq_FYS`SGn?^Gfj?Z};O4ObG0D z#vpT^?d_ho-19i{No4Rn!S@E=8+>o@y~0DaW{Z#A6ztR_>@4TEH)&tW+n4h8rM!Kq zZ-4Op!S@H>AN)Y@1HlgjKM?$2;hibY!5AHk(ZL?lkP%79ScWuYq(jE?i9c%v8SB9z zBONl9!673ZGM2&NVZH5?x_v+H+@Oy<;moZq20pI4N;8H|N9dTs_M5TH&yJ zxb;&RhaDfj10QjQ+(>QvH?%Z8Vl!UUB~GZZ#*KIUB*$j_Ms^`K+x|AqY{tC2xitBqb4Q~#r=ok{zGy9 zp?Saa4K4kWEwt1vIL$KM2P5e)vYhk6^~k)&8lTFCkw6$}RSP4nUXRYV)}nQHN`D?p ze{u^*%JFW>iKLw9rkqU5$)q$8(B=fS`Q2`M9IP9d>+94;B3y_i^X(@l+VpTckY7gTaX>yaU@Q=){~} z6aN7}7?~I1>4+cLN{9y2dJ-pzd&Vq*|^;&lH#pus0saH7lS_Y?H>C|f(oO<<* z9qJ{$Y5$?{I~0D0!tYS{9}a#v_~GD(gC7ZgB>0iwM}i*>el+;e;75Df4-fH$hh^FW z59#o*3=R+L&-}9r@X&k(9w+%)&JK78hleCQ^u!Dv%@Z@oC=+BbYMgQ`q>qL4v5-EN zMja1+d_i*wA5J>HD8(;T9FOIRr~08KPAqkPF80KN)^T5_b1^5J$CvquXRQKGK9%CL z^CuVh@>u8T*U8FL*SyVZSZTr==6b&X=DG}Fu4cnrErq$S@db|skgjX@tMtqe(mI<8 zX}zofRT;ywPNDmA6|mF+RS5b*UvQ1kQf6rJ&E+s+a6i?3T7A0v-xR_t1feu-XhIQp==OpWEc1k!21}={mceO__2AH#4r$Ba5R?u#%iu7w-tz<-9`)Kn zp8^Fkx)ab3iUG(-LPmjurxk^e(Q6Gjw7wGNI|PiB2qV3|fb$ekFsdJx{&79vfR^GG zM=5ThrC%$?N4&-r_tXzb=P@&kbeY4*vi+EuC_G1;{m0v;is#%Z{v7f-hnPQhey$r` zbjmq*iati+-^w}fjGkVezvmn9`TKf4Ei>Pm&pV^PS#co|pe5J^SLW}wTySNcW?sDO zpJ%&h3!k#P*tPHo(8LB!P+!5`GJOYU=`ghn4oT~UcRqLlH6_BzGDa87-*X^{351ws z8FfBPbkUYI;SDzyJ#f=x18y1?aH={6Zklw!O(B8<)zNTMN8>h4Dxjs1f|jBMKPgI} zRZ;R`#UZo=LQ5d7QsFRSq8T#u{?&V(jpM8LJIjfyL_|ILixrE9OVvqq`6YY*JJea1 z?&}B2IIqELvD#?5^gtMlHGG z0P%2t#pV2@s=MNH{`a!=;Blpjpt`Hh<6kMeYSR8y;H%E#PfA{Ojo-k>CY#Wqt?EeH zYMEZAaZSh4E`hYmGDfsPI;1UwL)Ch42ug=r724%iN|#$H-_&p;bl|4mgquQ!E~qX) z>;A}412=(i)3*e1x8CoBTMae-d3p#csBox%tvW%72Nh%xRPaGicKA&nlEr1sC`0^Q zBYx_*0sr0i((~E_%j;d_dXVcuZgi0wL2d-O*+p&!xhbTH#A|n%HB@cxRDUOws_Q$U zKc|OZ)UPj#UUv-mzEx!W6V8w++rL|%U)ZHrv7x05H=MzDsT=ob#a5rohi2vZU-bV8^Lo!n|gHLYnt6AssH`MdR9RJ8$1 zG2iO@Z~y5JeB-0)0$SjfU|f5p;$6}l{$|Gx>TW{pL>Rkpja{5h*g;n#4s;b1_*+2% zQw=J(S|b)DWd}cvZ1Ah_{90YdkQ0b!EwfPzIW2u5rF2|bd$r%JOfYkzVmpj#>XfId zr}9+wRGzAy%2U_V!A}Q29sG3gGr`XUKNI{+@Uy|s20t78Y|m3$c<2EsJS_9T6dtvj z{#l;9zWCrfUs~hK4mimMPL{ESlWmE}pH&FMP8P7UEXyv~G3O2O557}gN-j8;l^@tZ zyp*rfL=E<(cpHQ!bj+GTk9H^^X@_MVxY7>kaJLMOKdlFcv2++)2FITj!~e2I2nY&< zTP1#%6U(xz*u1_s)kGiMG81;5 zjjnMT;;Bzu41IM8%q@e%SF;zWY9b8F8WTJrfTfHfI7s5})Ib42bq3rNEYR}&XD0KX zS%xZZv=}!%b+x+zdbPU&dbPU&dNmuMO;25Z*qPtZ?N_X5y7KpH3UXLPt~_elT(wQ^ z=3U5ZY076_C2PMO?Cz{jBtov)bdA%G2bz+7i zcZ~C1b{$0wJwXg0J<25-fE!UrixbgI}H1PpN3B{8zc} zH&JjcWBs*^_17}iU(3DVdhqMPuLr*#{6_E_!EXe=5&UNGo561ezu9vyfk(|0G8v#x z>J|_5E`?aRxynv4o&>2$rSi7O*UZZshY> z$_C$V$~QU1WWM;H`oN!P9njEc?ce@k{jJMHM;Bh+a3Ef>2Gy) z=Wlf9Z*=GXA3OGGXctdg8@(q#JyiUXKM_;&p$uoVQvpjSDF%s{`nev@AZe@;X{2Sw zJo-nOXpGY7m3q#!qN5SSj-r~b5eQ@HFttogFavL5_*6ad=eu;erJnL*e@+rwWqT$6 z&9l^3+G*SWu$3n}f26j+PxHuJa(?HJmORroIuoNaF**~Yvu&fZF*+NgvoZ=zZgtSC zxyOe!D=C_utek@3?{XRpJxzgOnR++i^pl-GP=N(QeH`udyd@0Fh{sW#QrWr zCg*GDmnFaN-l^x!^g9-~*mPe;p`xjEB z{FM)Acw>TAk{v!dyOpjqYrPa50dTFTE` zam_!9(gfx?Z_Kucb7hEg$&`M+Yt*?iM7MKxM4UTkM{sRb>4PK}oYDJ$Z|1H?@Z0F0 zuNC!g=;w9QhI5tjSLFmeUsWjO&=-^Z1sn$EXD6FN}FCI#&*i4@&w*G56{Zh9->Ex8>#oe3ByX-tX z8td$^TzZuldb(4doW%V+lqF;Bo%$?yY^dCHx9iA%g%3L$E zTh?F%*}4yFgTuL@os??BleXgA-td5SnQ04zpQh|^ujgEKnZ_^84{I2p(o#G#q&`iusIL-tX%a|Pp4zo7siiF3 zJycanP4b$kQY~&(D%k4#!@6Lvxi|l%+9T4t3|>24bH6J2f6-#FhfX-o>GcXd-1Yk8 z`n?QdsEaHsHPo{)a;v;u@cL?)2^DJxx4kE(7Hkb7go=HfwIxK zZa4p&T0y%i#d}sXsT5uXsqU?eC7p+zs#4}1p|qwf^T^Ou;2SJxQXzc8vGcoy#(nMm_nRyR&=0B{$<5}mw(@z=KTzidgJ`i+zF;t#eLPSOoDI*^} zITNEZF@lqf;8aGw6`hUISs67UeCny@U%#VnxLzTiGR@9(>=ayK-9^1O%yHk2!8YR6 zfJIk-`kCc6&fw5>8-j163l3A$7apcHGt0K{sRa*vrxrczoytBuJQNdjpPyf4pB^6d z=)%wKAtNIk=!aN~nWR0-yz8kW6W?hJg^cWR8-dnD(z*2ex%B(FZbY09az4oUE^;Br zg&-HY$i*NRgIo;KL=p8hcReaCxqD|d?N0GX#IEOz3O8IH=?_uG!&4ops!R+j% zclM_)Jkzko_SIIO$B7=gTFIQzKEp*HfbfuX@tFqH`W$%^P_#yUOKa5Ew8ruo>77q_ z(Sa6 z;0KhKb2#Mkv-+(%ra3lpL05cS#rLW!i{5L#;?liodDZ5AHhR_O-lx85T0Q}B)p_&- zdey^CZ_%zA)!)$^hIWaVg&6b;{5;QlY?(>WqLBRH_T^`t5@<{1_$zhC z_`>={EdSUxt*T_*t^KRTG_9NR)t*f~Q;Kd>nJc+40*`)@4b9bEik2#7Xk?XAx4ov{ zu07QcV|l;rnjPd_*=vh>;H*O)zgyo5y;k8&A6$Q`>j4jUZ?_9d@$T{U#g5XsiC(vR zGf8l6%`!ZHyHU9*_l7h6kZ%1qDi4V_%eCuiP6*Kk9SDKB@EaMRp{msfRO{2Qen0?KWrk`wICF(q zLe*ejF#zx#csyM=#e`xgEOY~^j{vhtj;tXhv~nPA+aEN|!D$_DJMY|Zv} zk^Uh4L2h@E+d*yzDQ2BI^?a&Rt(5MwDcMTsTSg+uU#8 z_Sp$yyLB4hi|pQ_1|f4?B4OFYBj032xx!`fVO3u z@m61V2Nved)Ewq5rOkY+@4xFjyQBqrSbrO1o*9|YYk14(PTS}cuqL`;t$PEkE6V?n z0vl&5+;Fxely!rEt!@ywSeXa|;Skh~1cDmH;HJk#_*6JP)#D_%>2VUYYIFEY^ehTz zViZ3T(*xa@8c0kHB&G(t$Y7AcAVXbbD9BKd;Vv>9WLQY*{9Ae&rzRb)ptrU zMSV^QQuR3x^$DRq$*K=ztMB_YiJ{W;FFT=5c_ntZ_Ifxluh!zPO~i}6w-Wh#4BI<; zh>+57DCL#vx%LJs<^c!VCo5j@hA#}?1qW?;vf~|9O>fv2w)|NwuZG%X%3GZgugmoA z&L6MZhufBKAidT1$M9lv(ZO6%5A){IDCS%zzenV{K8z*I8%w#9?JqrmZqZknFmEhh z>E{#&>Bi^{q&s}M+~BL*DSS1DhpL{L!ctetU`|57(t2HqogbZnpc02T3|evoYtf*_i9`$-!|K z&C4eGmWD}a>H7z`u!ckbb}Zb~nQ)U4jz9cEE|zeAHWc*+Kz zmO1tXPX!1Rl@3MAocMxWGl%CpA)Puq7Fm5(GWR=Hdx;Gds~o}4qwaQu(Tc=M$%!VBXW?j9vmLh;b9pZ9_Vo> zj_>8sTW_l#m$R+h=3>6nJ=Jihd#d41_f*3jP8zhHQ`gK8`;CBk|R$oS4r7XKj&!#!F>fZX(x>dqW z_a3-e*8VN+f}3S<*D)s(Hi|DiK2#N9=x(Vf51o&WE2>|fhdT%=Z32)w3&osdvX zy=~j-`=iaxxaN{M#6iP!lcO*6ke@j5wDF;Jh+EGA9pXe+4+M2nq%Sm0Bu@0m0fLHp z2x@4&!zrxlTU=gG$G%o`);l`V)m#b#3A%v<-9UnFfQ!IS?+0Ts7>mJJ40bJsVlfnp zp;!!cErw$;9E;&t496mTYMS|QrzT18QJ}%6de5^+s3;LCmTeFgN`!@F8-zd=%cWrz1I^h zgi33o9LLqe*?4d;WB<3pYMY?7PERT?MciV$l*JFN<`*^ zvwlkT)_xwE)!8yX97(5tVqYrHFV!QKdTA(asA*`xl&()fLC3 zp(`4y(xIv=o9?cerB4`ebr_5hELAw2tvN2&hVbFp${aE*_(r;iCP%u5CP%mlWiCoU z{8+QON(YSiWS!qB9r2kvzeF=KqXzp+T_g6GpAd{XkDeAffC$u%xcWJ^{mQsmoevdcZ@lsYZ`M#-eRE0ac9)?NzZD=o$((Su8d{oJiofFk7%ac zMGGzKd=R%gEt+YZd`r7(ec%(*G^vzzJ^G?G>OvZ#?6jdCHhF;$eJ7?%AT1rf8ku2f zIg<&b4lXsAj~aiyuyN|CfX2GIt2aW-#I5dRmU7V`#5lu5!@{EITdK})Fe)> zX;*fNZ)KYmJO36&&b^Fdewan;#aLc>E?&VwF0M@nYC+j^Ves7JPe{x6Pmwn#3`-9`t z6AwRpZgBGM#9&=<{c}5i;>$aW!hUaA>*=$K+pEi2^s(K>{PrAAA-4Mbe$#DdYfv1V^A^mY z^QZ=!4LPHR2oJVH&gjRwLvwoD&`%$>a3YS1Yi`)97Sl@-zZwH;jP!n;WB~RZF?!&F8rW zy>6>9uxG-?f{laOdOV-b7#ET{>VE8Q(LRbF_Keuo1OSz6r{KazoyFZS+V1^P_el>w zoBJo$#@KK{dd98W^j_O|MX%`1(zU9`(Jiy0t zWN9qd@>niT9H)Ha$v>VWPw>!1iTj$1kA=Y`9rGbAVW#-+pQR(4UZ3c8-vpT3GuiFF z$#l_FH)X0)2t^*OH8C*J?ZJuaLBrhel6%sL_V^o%VRa0G9YKLz+eGfPFJ+!E#xpZ!M84Z?{YfiU(wne87JX-d%`_FG?>I9d^#Qp_uz4m+8nmt@rhH_-TbL-HQZh9W%Z$H&Y ztzCCj=#9DC3rc2-a;uNQlPB%It#W*T3VlrKwoCLusyi;nWA+{A)@#11%cH=Z#ip)< z)p`%hcU+D>YETc4n~ubHbEN@iz7J{? z7~;R*qyL6Ee&x|@m~%}YyoWhfvBM~K-^PDUG!59zURw;<&072&RR0W+)x`~v)x`}u zOK!~6VuL|UJ=zRD^YuLh7iSs5%Gl6iu-FVgwLZKM8GKlH({u2Y51=TY4CL_2fDf;% zm0xziY#2ZLq4;11xA`;!$^l#h8O+TDvav2qNB|j zRp#3xH0+pMJ4$1}PCgqY%|AFhic9=+=%aY=!$QZf`vW!k$(;#f|Gw-P#wS(Oc-L>^ zpdRPPL4BNHoTBP1K@$ff*xS1!<;BixT_a?%`$xKcHyU@3#@(ZF_h`AhlW{CYV=)?w z(OBDPJVxU&8jsPqjlOr=*gq0u@uQ7EIK!Y>a_(8GR;G1)elvhuN}3z*fZ2)r?o z{+g&R=oG2!fRhDwZuXP5sGj6L>oT`@>l@1a9_5&*5BGF7e0nPIeR*32lQ#E*mR4<4 zUEdDs6f{*I5bA6iO*xMruT0mcggVHy^90F9hoM{6g-VIf+*n>k*w|%%dJvfY%A2PzHa|{k1yayj1%#7}4=jm>Cp6+Jn>27wW?s@0Z zA4j_9jVym0>0T`F#qz!_>nX+7j{EgR6-Es!-tuy``!Rpuom0M`)cGXRgL|!)eIGav z6Cz4`(6bE&X*Cwomf5C)v~);Y=Ccw|wH_R*(qU;C9B$I#W*HnZ)_b?!b-6wD zY%jOzgok#WHWR%fd1z1bz>4NSY;PGpjQ<`vkM|KDrS~4CPaef+CcQfod?xrz@Y&$A z!DoZd_Bb4ZY7n$6^N9_}NQaDNaClgM;8h|#q{G89I6S0Nzh%;?A3Y9Yzp=Oe-uimW zr(Lab8%IA*dmpF0kJH}A@yCpX z^;$-!9_iF0Nx7ENoAx|SeNR*0)71Agd=`Q)1YZcg5PUKCV(`V_i@~1-e-`{%@Mk^k zfrt3PLy~$eqf?J`>XD>e%bxb!&12BJc?^0t-+aH@JOSm$DPwdmM)zWL&qjW_dOt?@ zV{|`8_w!Ns2f-f%e-QkE@OYSeHH`GdJs62Gj4bmaIAo+l#xgi$vO*hZ z{WQFuhS$^ZdK%sf!54xr1YZcg7<@7KV(`V_&w@V-{w(;jo_158cv9{*es>@1K$I8o zcm06GXKC*Gd53Rlcm2G>cdvVXQP0Rd=LwSU8sDq0-`2DuA4k5Q?;hU|en0qw;17a7 z2>w8L>#EN44hU+N1UJpRp(P0;t;--|nTIa$kUc!K(+>}QRR|uMvBN`;Ug06{z(WU1 z;h}u+KsLSkFnk||@5AtYnD#sh{wVmP;E#gO1fK~$6MQE4Z1CCOv%zP3`~eT~g@^dU zLww<(bt^oC!=oPc`@3zBsprW4(g}=|14gohk;Xq5=`j?HG>?Uma2RQx3nSUVs9BLP zFMS;5Pa@|c=Ovq-#7^Zs&YR(n^E~=-o>!A!_>;N}_pUhsRt?*+df{C@EJ!S4ru5d1;#2f-iotZ(3<{)UIvE$~pg;GqbE zhaN}5Lu+7o)MH71avC!AxWjLTz)065j4W$4%rDGn^$RUuSXu5sOGeOgdiwqX1YZoknEZ>u z7lk($==0sag*@LAxz}1#<}OZs}!*OR`U^p{D0ne>-Q-$?pK(l?U6ne@%1ZzlcuzO;XTH-c{l-weJPd^7m-L-FU~$Rm+Q-BauffsaMxM^;#yK^3ju@A2X&M!rxd<@HdthoOY-j z+F_ac;4K~A$mTkIk@mev`(C7dFVg0K}zD&Iv!8d|$1m6h08GJMNX7J76&yOXJjz^w|Jejx( zz7%{Z_)_rY;LE|6gD(eP3BD41CHRW)R$LvkAD$nNJP~=)aw#}GqY#z8hkbQYVg(IYr)rouLWNVz8-u% z_|o-fmm zjo=%>H-c{j-weJPd^7lF@aLxz=cgmjM4nCD2VV-l6nrW8a`5Hg%fXj}uLNHSz7l*z zc;b!+LC?>){m;)@Qjh#WJ;>JeaVmDFBYX1&hp+Q*D`#s5e3cGg%i!>p4qwaQ@U`CN zQRDaffk#~@@$ZZH_eK2sBK}_uz8ZWr_-gRA;A_Fxg0BT%5569JJ@|UN|F|E*LwC`7lxyWX``GomWF8p0i%I%gH9R4mpxa-;40dJ+l+p=AMU0-S6%e3!h+V?W; z-w3`Dd?WZq@Xg?x!8e0%27i7o*VFmP3y~LdeFa|%z7%{Z_;T>&;LE|6gRcZ%3BD41 zMR@D_I_LI3KOcD^@}lKZaCpd1@URr#y8hl%Ib46qPkU58?Xe6G+9RFzSO%v(=uO<< zSK*&pdp=D(>iUcSU&Q|};{O-vpVi>2!B>N?244%l7JM!ETJZJY>%rH9uebZRuD|#L z9`Xx3EaO*rNQZ}IaCk_Ehh=bhSPu>l>F}@&4iEIEU)~oy>bj2KUdC@PqhX6 z;2Xg=f^P=j489qBv&Zk0FMm+JW&A~X(kV}p{FZHh!Sng0j0cw^uS8zW_z-+4_)_qt z;LE|6gD(eP4!#n6CHPA472&P%;gb0+Nw%){q(8rG{P~s0t8M$O9q?28;Aa^ee$wG* z861An;b$2fe%6IIafP3h{yF7Q*L(c=BK~|4f4+#nSA(wxUk$z*d@cA|@U`G;!PkSY z2VW1q-tMou-s2B=$S?4)j9=j)9Uhj!;UOI!mcij+Jvcn1!^1K-JkXndd0+6T>pgyZ z8Na=Z-(JSA8^Je%Zv@{6z8QQo_-63U9=}tM{6RgI@eB1xryk4T)MGt3^+=~4%iz?5 z9=~!d==qJE-(Lr5pWj4o^?kWck1Zu}$%+0!s^uguJF$L%D#dU#==t@|$y^H5hpi}3 za;b|@YJGd@Pw6l&MGIc;!j%6loHcn4o4!9NaN#hA6JZuN~6A^)HN6h}CLYGozXE7#%B zaSD{{P?XOsLAj%Za;-~lxS!yr{=!+7(eaLS>XM{HOX(@6o)=wn`t$3Nz2yd93jRtx z!Iy)-Qg3jY_hufoZh1fOsBtvi@uIqePJMAB@}}i#@YUd}!B>N?1z!uk7JM!Edhqq& z>%rHBH~j(+`3oMF>6aJjPk1OD9+tu3Asrr;!Qo*&I6S1o!!kHL(8HsihrJ(o)HoWy zy^P;p#&0j<*NxyC!8d|$1m6t48GJMNW{=;gNB*E5%lL(Qq*IS&aO$xhoO+~Fk7aP` zL62W+9KEIS^S5gJeBLj`=XRHj{5FrQM&6OKY~(Ag6w)cMM$}sx_xwdU3amlUzn4IP zrTholIa_KY%eOkC&~iVYRrLUQO9P}oV6xJ`^Jm)|*Qw+;H6*8!kN=Yz+5VU^Kfle$ zR(FRxmZ|zTJcPUIk8hRz_sM>#>^md(QZmuZW_Vogm*Nwl%PD?2mMgb3GJdy-+w#7= zv*LmuSdn>rUt{Mj_YDnDAHmx4jwP(6<15SH5R?u<%iz$m9voWI@t$Sy72(Y_4i9aD z!$W&^pA#N6_P!tdQDg6|jJ5rd?nIyLdQqK7sOV_qYAjb{xq3UJ^Bv2z;A_Fxg0BT% z5569JJ@~rtriWmp9(<9We371njMCv@85|zggTq5QJS>C5LpnSxgTupmJ5p*C{H-c{l-weJPe6z=Y)T8!Lk7e3NJ<_SiGC1{E4^BPO zsmC%n^+^9%@Q(%mSn!Xv{YQJB4=8wa($+7bKOaVTtW zHTY`q)!=Kv*MhGFUkkn-d_DMj@bz|()S^31frmN?9+v53cu0qbWpH>%hlgcwcvue( z59#o*3=R+U1W653?}ak3_sYg2XSO%va>C|HxoO-MW|5)&k1^-y^kG1_)!_!E{pV7#%$Z^Z1;7h@mf-ePM z4!#_GIrwt$mEbGESAwqyZw)ph=1+ZU2jxnVAK4lVlKyEJfy?JGB`ZYoBntc z57*!L?Pc2aGVOYqcD+oyH-c{j-w3`Dd^7lF@Xg?xJ$|7c@ueQi@TMN=)MFW(daMVh z9_iF$DZFXN^NC!ilaW)A)46VgF9lx;z7%{p_;T>&;LE{Rg0BQ$3BDpcJnA9Ng!w(6 zjGT&`wpS!9NoGqrpEK{G-7?8vHw7 zq`cM0waE39AN(W1KN9>S!9N=OqrpEK{G-9Y^JU73{AlE^@Y)q#yTWT%cI?Q1~1QpF`n$ zIQZeq{G89I6SNehljpf1`nmf z!!kHLq{G8<*@xNTf!_GzSoj_b-(%r>EbTcS{CM!=!H)+&5&T5(6TwdeKN`B4t_ZJk>E#y z9|?XW_|f1;gC7lkw1+P|#1kHt;<>eh`lVC9WhZ^zI`u1^`t|lH^;-{4{nDx5GC1|4 zH*s?;JdcIvvG6>Wb{r3WJoxe8$Ag~;ej@mZ;3tBge4$_Q^VbMYu1fI(wv(|u+2auy zi7|{U!yHD^VPv`LH`QTeJvfY{!^ko?jHJWJGB`X=*4Mb;L3-nvUAfckj@%QuH*#O( z{>TH72O|$f9*#T`c~r8ASa9(Hx7@JAPWn;l4XeC(RSa==VG%xBGzvD@#e(N8zq<-ntZyB8W|9|G*Gs?~+z3-za zO-neWy~rKWK%;X`$c;vKPuQIeGlSSU=bQs_&X}1cmy~G9;!fI$$Tb~wV;TPRG9f7W8x34d_N@8q3a^2pr$#T|MZ*RE6k;toGlzwhto+D$@J z8=g2O`|nlkR$=-!@a|JyvW`uy3SN#?A-MA^dUD5O(L!le@-N=*JfWB1?PMXdI=J1G z`xkOexqr=Y*Gc)A)bIt?d;aDuQ|aHcGnL_0vwEA2sq_>kF!~)t6X>2MP~l0!Q{nr* z(caxlU+kqX_Ad0&KFB`EzHjxCrS(Rzw4o8y*n8P1>}4Zi*#!7U{&Q}YSBThelFi2r z`48KNT^m!u<4y=wI}p`wOX~bzhBMYis(-v4Zl$odUw~dDJ6TN1v1-x~w z1;RT-@A-3TYXScvskMNAk=R`=dS+^M4Lz&h=&=g>TW_p}zMfePeOOp+ zLZ|NWZ&X?x{AsI+_Sd=BozzoUQ)wkdFQ0nV)nDnR7mwAen%PKL)d;=Ht^Sn1 zcvQdh)u7b=N@Z_cz0a*dS@^s0{7299PSA#v{!1g?-2TE*y)v`mlwEVyzKzPv-)-lm zt0NmjN6yD7i$?XPH?ox)QmNVX+QBAe;jh{Ap40o^nv{k5sVOE4FG~&9bjp4=!DEldOwpA&-pU&TTYrh8<@cb^CsgL-%fZ2e>V(!fAilz_JM142>Gf{?bqg*o_WIuYjaG$Dq-_U zpW4)w&1%Pg;Q6=2bcGKM^=pf=@Rqe+v}&}R^lQT3&*y_)zgFCG%FS-k>k?a)qXtx~ z@=?L9%0ivps)Sxu>trjTZJ|p;LWtF?S%XJ>o5vQxW9U%9jj?bH@h zYs^lzkUQCI?vlIUE_S55aN32_Zg#A@;k)6x;d|hF;CtYE7VT3_n_nKL$bU=1#Q1*3 z#Q1v>OpG_O>Cl39R{o~L)0qyhv*~a@)1mk|O_!#_Kc>|;A^~v4z`Wrb+ua=Fo*w1OwH@(hHs<*32_4YTZ-u@=lGdF<>tzW|A zWMW>}SJsCrnY~^W(ZpM?I(R*OM$l_f>d!?htG9+WoOX+9-k>ae;;r}3Ej5IEEb|5> zWS&`A)X?#|1@HCC2XHnZBjzz(j>h$FIZtsN@y!o%+z4j^x-%Mb*qP2lAf&K~- z)8?Kg%W{mF#cBBFGd5G3?B=t+vD75nd@ih5HV2=M?n{`o&iL!oeWBG5Y*9kZJuT-v z7tP?UO6@(UWuf&~X8Gw|@)$V3m{ooa|O5=T?zFvYo?!;_FE3S&#gEMaaoS`-Njt zgQw5WulelWCig!IdjEo!mL@g71Rwg73QIU7&mY-8k*W zY4^ogc*)+t&K-$f1iT0LJr`o!_FRnKxac-z&!zAp=V;TKR86|vM4D8^xydxCK8j7M z+fAxMxT*9>$5ieNhXAJ1`EIz$^0>)T2sa%Tk2cv}m6THMl-lSBGaEo^|v}-J<(>lj>D5 zsS2r*3G}!LR0ub1ieJ)siz)KBDN+bGF&;M^3NP?8&T!LVcACsSM%#EH^2@S~7w6;t zmfuY`UW)wHWK_Qu+jP->p)1U8mqJK-yDtyvll`tLq)*gemR^5_^hs{B^k0Sf-_rlH zaGm^>->`qSx2QkwBTegGJ&jgRqt!Fi8{}l`f#uMkE>nsIJR9(AJkKvb%PFGF%lMYK zkZX`6-;}6MyQfv;l8K&=8?c1|#bNhO%bHkkVH2y6mN&6}TVy)jZW0w<(#eWR^mrq@ z5#C7pCN36}<$l(33NM8l0(98V=`>p_|656d5jm2%kkvk(fp~s`6Wbga>aofEr z)Me;LQ1^ZqBV#XW_et#sekZo?13&k;tBBk^1D!nV`*}3eU_jP2tOqJc?hHPMaa+Q`uVtj?H6Fkl&Rk`uJM!`nLhz0B?Xdz|m+|+;_|K zv*zzmXbD_g2&JphL{j&#IML>&%iG*^DWp$Km&Z+)!VmRW!E|}N5#9)IBz+^?n#(5* zYcAi>nHW!JVk|_*Z`wR!eemCCMzQACU#$zaR&4l(#mv~hasOsx{AJyxzgj{k&By(7 zGK@s#MoYR1z&qVw&PZHt)-46XpK$KrUWXfO}<3r_U|kgl!j$gqsuxVC{-6V zFAS^}`-=rof7w>TM`7DSvbKd}ZRQd-8e|^U+%Uu!at-5rArc1rLc~XWXl48QjwxyR zfbc{AI*$aj;M9tfHG?}_<-@ljQHhf^LNKa2)(9~Xxobnzw&+qby?*sHhh_^+5?U;T z(QC$4y3tfBRI|SBSC^>_U5gsBqsOM*gU)(Gkyv-{IV;UOEriNgo+dStG3^`H@;_N` zoh0G(f?mrWsdBPb^~$(ZgRU}!Dz?dU`c8pCsGP!X3~gcYeqT$gwWZ(sTU+|2W^EbP z=g0o&#d=)#ryuCp2FZHZ5BqG ztX+qs{_|Wq0%+Gsscxj(W1myB$DEJ)Nuz!VrbExs{Nzr`sUNxx~+2~lS_X|_sq1@{o1(CuEAa@XNTmyV+=qdy8_t#8#_ zI1OE>X0R+&(_77I=%H1~`@t%rkfT#m9?sJANX$fglbT+4>g17(sr1`jQ|X&0Q|XUn zOr`9xpDM8paPRT?xE!C#dQU{odiuBGG**1b>e zwn3SXQ<^@d344*~#n63R7lBnE4E{{7y;Z;$9jicp7^+qQZ{MEqba6f|%biTVUp}VX zcd{nholLZMg6UPr!MLe(ZYtew0zvzoT#DAx})EkcZrwPZ;kd>{jUMD{Wbk% zwmAQ5&}_f$e+}7Rzh!?#yQrVG>5pxj{_xN;(k%TvgB-wfK%V*@=RmCI0G@-v(|#-0 z@8JjaxLUtXKPY$ovc@3pLvmM!LvmM-4Ds-E$UUQ-VxqmNOtfDFrZ=>XKQa49zIip# z!D0t_^KrlFzo}&MJ¬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 z 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%0A<+~p6!6d zbI$Ad&UMe*v(}F9Dt+L(A6dRLVRVYoDMqIlT?wO0j4m;{oRN-8?c8bRA;R|fSz|l* ziZ#)2ub7VzIw<3Z2+KRZvmtrrc>O=YQ;3f5W(yuDEr9*FHuQzl^3JS$XI8#5E8i8% zca_NNMb`4J5+yjS>(4y9N;DO7TXW_AD82v@3~xllfvGm9+qHQO$Gsd4!&G>Oa5(1W zXcnfz(JY>a;Y@|2QJBwce*gXKP~)QWbtm1RTiu{r-Jn|?r6*zZh|wcPj~KlPqgRYx zF?z-5OBj7(^oh}z>1^F`$LTIdnFdz3d^pXCg_4?I%{c62_weLW}7!YGXjDbx1_Q&nh zp9xT_2dFC}29i51Q{x$05Lj8cYLM=2u)Wq!#FH4D-p!#Q^v zelb-L;E3np15@F!oO$7}*YmKKsc=~7dDzBOILu-`6XY@Nj*e+}bWFRWW7<8vD*USO ztHQ4e9~V9@d|ddr@Co4)!Y71J2%i)_DST4+r0^->Q^Kc&PYItEJ}rD&__XjD;WNT# zgwF_{6+SC`R`{&&IpK4{=Y-D*pBFwad|vpx@CD%u!WV=u2wxPwD11@)qVPgNghD}# zLP3;5A#jR;Qw*G9;1mOADR7noXDM)M4(!k9J}#Axw;WNT#h0h9~ z6+SC`PWYVgIpK4{=Y`J;pBFwad_nkv@CD%u!WV@v3SShyD7;V*p->Q`P!Oe12%KWz z6a%LiIK{wO3Y?|DSqhvbamIKT!Y!yV-i7e~WK4GEW4`rcCLPdBj{D(SA~xY z9~V9@d|dd1@Co4)hKF%|!bss>byCbpF(=97xI0NE$K5G0r^K8h6XP(3_$e|uBTS1q zE#|bWo0fGm!e@lf2%ix?D|}Y?tngXkbHe9@&k3IsJ}-P;_`L9W;S0hSgf9qR5WXmU zQTU?pMd5{l2!(BV z&4);@ih0$TmN$~6!pDV=3m+FgE_}l9&@Cod-7kCNPKY@n<|LWF;NT>gZ23tsC&ipn zol~lFO8AuUY2nktr-e@opAkMId`9?;@LA!r!e@og3ZD}`Cwxx$obY+!^TOwa&kJ7= zz94)-_=507;ful-g)a&(6htT##3&R*DHH;y7&yhiDF#k4aFzmRDR7noXGxqR4|4f> z#l_$lG1 zgr5?AO89Bvr-h#uemWBm*x`1<4yLjfc6c6kFcl6vm=_K^JP$jV3Wpt@haF6X!w%-- zc;GFWb1epA~*q_&MR{gr5_BPI!gz3gH#P zD>8A19UceR!Bla99iE3BOohV^=7qx!&%+L;!eNK!VFy#;u!H$b+?TUsx%!u@_wDOb z+;Jm*{=x8C(D{dwUk@k0z88L7cu-$XeMsrUO25bSqVS8tFABftc-qdDYKKa-L#5iG zQtfo!@bLNE^F~TC#!NCs51F5cZT7uUwGC|ZHi2zS)poGW^Dv94@bki9hnHUvenI#J z;fRsrh!Ioah!OIN5w5xA>{zb;`_j9d>v;P@kvJ>nY^ZxY*b%eJ7;vg!G+IeJ6#V6n;|p zN#Uo2pAvpb_$lG1g`XCFTKMUVy|BaWLw%k?ucweNvG~Q-~pQq64DdbaoZ7leHxZ2PEAo=x3^6Sy?>%#YS z?f*dOBT66DSRnkO@QcDPI-d5wO4(m2`zvLCrTjZ@c(};V8!5>cGs!&q(?5lW0a3|M z#6TMm6et+t0fSyoFUEmG$2{^wRBjj2V@e-a`UKOX!jB3+D*UMMW5SOKKPLQ`@Z-Xd z3qLOWxbPFgPY6FD{Dkn6!cPi6Dg31HQ^HRPKPCK>@YBLi3qLLVwD2>+&j>#w{EYCk z!p{mnEBvhRbHdLFKPUX0@CxA-!YhPVIIa$59r%Z#i=KZh`Sp16>xuB|!VlG9A5;3c z(kIk`g?%s=(b)ANsAQ(`hnS2n?r`TJRk-uR?? z;ZsVVR{9Lnqr#60KPvpF@MFS{2|p(MnDFDmj|)F8{J8KF!cPc4A^e2!lfq95KPmjA z@KeH12|p$Lle?2|p+NobU?a6~Zfo zS2!N^$|pm=JO5Pj>*?gzGvU{TC)Ep|Qu?&gXVfc&Ule{(_(jK~UMagOWml!_s+8U5 z4G*2{ypfWOF_VnZL*^%98(tBt#d4D$wyDK(ldt*KKAil*a?{Gv*j3rGG6bl~)|N@e z7MMxKR+%AVb@0iu)xlSHtq%SAo*yr?Od2v(v)1<}RE;vMMj2MaOf^@kJPK0Hm8vpa zsp3RSc=4rLtJ@mN(7GDR@KJXSW%#tIhO1j{NuGP#DM`L-Tl;WnDc`lN z{a#!eyv4KzZ+#krqfE69tU(Un{&;cx`5#yPO@% z)xTW5Z(pBU*WNNmhj`K78?AeY?~B(xtn_<&kNiQU!s~_C3vUqKAiP0%gYdlYyzsp6 zyyIziHcMW&PF7y`Q2aW0Xvn&UOG~{W%j(!=U-8~q|Dg9@&y?4fXo1@L5(QY2zys?i zq&GZRs($E$b`k+^RA<#RWYxgYL`@mIO)};AtZY6jiy#Dgdd1bTSNpL+1f!&_hc#@t>$?c%TzeJC-a#LaydJetADwA-@d-| zf^5{~(x}U&QI|`jE~h5pO&Gsl_Y1cP<2PUPZo(YFU(I4QgY^bq3%8L!eg(V?H6_hl zhUzTCc>r}U;O$jS1itb9{ez9}m&+pB$ik?nXF)EtrRK4dkQ$fYlO zxA0QtA-on%YW_b2S~#iwq3de-p1=HwXGF!Ey)Bw{fB5g+5$(fN-4m_idHBavIAYJd zaMR+zjx35nvYRg*eoMJK=251#-}jM8tKT1lO!$;btB2AmV?)zFw z{y^y?N*~pdQh2@adg1lL8-zCqZxG%fJTE*iJTE-&xa`0Oyz0KcE{c&YnSPKhcqCi! zXr`b(tD`=vqdu#nKC7c4tD_;SqamxKA*&;w)sfHY$Y*urOLZi@7jemSin_Q{)RlA! zi&|-az=}F6kA}3FUJLN7e0^5FAuErDPURc2@(o$}d{!PEA(hW(<@2TTX@5pP_5NG0 z6#dTg=wBuMxl#SCQT?q^{jE{`u?a(+R$>@(^a}%$eqlf=S%H-|mb7KMYJh%oLyNIT zJfp?fqY=_%qZbB;CZk*W2>)+yGeB7 zOvQdscDFC=E>k<#AeT)L3ND)r(l)xumx0_`XVy2mneY2D*- zL9)T~L}@8EcS)O7Xvpel$m(dw>d0qx zH)~WkYg9LE!ib|u6(f#*VZ_ldj5s9|Z5i}g%lY4!cK@~D2(W1itr{U$vTYvRzt zJ%VP^d0Mnt&(4}T64m(Idd+%()ks41V{wLpd_lI=WNbDwx~0an)JT?6C+thUea7)tvl=Z{h7Ky@W~ZH@7SD(@ zUEvwgn zsx_nV@sXwsJ!7iYGp1@iW2$-5y9eK-sS&A$kC*PKNawww?t|XsX-)|lWNL8#czk_pH}*erOD+PEM$!8_{5I!uVbKi z&s@hRc0^>VZd0CGReEz+|8%zCnbLwXc|rMxtb9XOz9B0w2PvP=%ICB4`BM3`wHwuL zjY&&3p>JytMA>U4*9zDcRt6Rx8LM)0!`OO31#HU7H)Z9UvhvN5 zd>NdwD6yl!Gt2m}Ufx_HmrevNCXVjhq6@S|*L%w|KG^e1U6-E3s94N#guC=qoOwFV zJmZ<@Zlu)&p}o~)EH%2NWODQKGCiR7%e)Ab;YFYfWup6&HVWe7JK??iHcD$3lhV3F ziN_j@4efZM5WOYc{t&vJy)?bjFYfk-Jqt_QzlWaa-J#<_zAoK?omqd48k@47g7Km= zI>qP|qf?B|gwZ8Nml$1QbcvCS4CU<)rf)gh(ms5AwLN~e+|Er~Je&?~-p-v{e_UPO z5$WK&)DNXft)Q&qVSnL~<4*@={lf<3omtt=tSr2$$EHvE)VR64_#I zjlH{B2IoxyEAMgz@*lf&?b_glzkX{7SD9*~7_H%Xv<6e*aKiIw3#P(hE%UF@0$M9UD7aUsqT_CL36;uv^l!fo;_Kyo~#(mwo!(YVh_6zw>eS+x2kt;VU+S4}0eQL_{AVop0z2868bJL?(3%eJ>IlCjEV`V_5WI zqeE<%On(|h*^!t&LSuMSFv2SMQJxVB>SbQa0O513JKKlS_G`wlY3)XO0QVLD!@PpmH zZfWk8=5E#8oo*rGI|kqPxtgC??fHRc_<@z4M?7QSg#k_0_L9!2y_aHH{gAL;%fyX}N&mVd}@2TJ5H_I8G_llZo%!d>!QyM{K!Xf_KMW(`<-lRZV3;u|HQ=A(&Xu>9BkV^X zcKb$5rkMLZW@5o$qTTZTXWxv!LP38T{rfI(h0^^0%vWORQco!8i=nbn%5ZQPrOao& zJt8+T-)p;}UE2-q+Ky;{;<|SHXX&yIZd~wM>)?(EueFZHqie0>iR&7S-buPXqD?xx zZ71pIUZKNvYR99CGVHNkl;I`WC1bjbS!HjfX_V2Yzhar*Sf-aURM1NqegBJA`n`cZTS-BDYY}%A96e&` zzEnEBh=!7L;6uqd@S)@!_>j(lrx%3rZ9KvF1HOKNuV3J2MjKkmYrdZb>hqAajbHuh zr)~eiH4muA43N&X&%opCE(69y^@Ab}8p-mHvwsXeab3qR7jxD8lo#X5%Vmh_lws}2 z6mxt0-*XMo1TXoQIHs1yXjo^{eRvtxv2 z=Usc4$jCVxX%!tCLN|C>eXyYb)k!0fo0p#mlCyus(-)XsZ*jxFuHx98y0&h0S{P90BPSBEQ#S8c~r*EJ%; z@40qRh6*|;!@;Rjn;@M}U03g;HktYwrSBYIaYtjedb@T##p}20X}^Ad+Q+Z1c#i>V zo?dJq{kjcr_yccxlUD}muT0$M=|RWvq#JxGIw}~?WIm` z_x7>@gXunn1?zp|2r|XoSG@i{Y44*9*A#tEhan3V^*xhh(y^~U31@#4P8;va`%A-# z4?`u~fh>0*%N?QYI*!yI7wqTY+ zD#<2K3W~XZ?X4motkKe8s$Wgj&!+k_XZFWU)t@#M!Y=wiOdp8p12KJ2^uf3z494`q zm_C#|_bBFm)lP4h4N-7iG<7C^8JtM7>!oco{AAL>Fcv=*XX5II69v#V63dLlG9$6f zNGx+DmbntkT#03_#4@9?%xEk#n#dG$8~v>AXd;LeBTCYi`}=u_tqe{cN%cudWW#B4 z5*rYt)L}eu;cOGBh$fHJe;jQ{=agk45tOCjNJ=7mpa&+TF+paG`wd)Tk8!_&Q_fhj zziKpYHE{a4N||5sQSK`DHQsMsjPuS_3UUIuN^D+jyHXNogUJ_Ir-^`jj;p*EkhBHAO*gIZaUz9>Vk~ z%J^+HH;|@f>a@v#F-3+^5GOxCnrwrR-K^8BNPT2)k!3Zsoj%I)Zi=r>e z!9_W^D2odP5efw{3I$OLg}^BWPBCzbfl~~erNCJVoTb27a*i%`EJntXt%I?2>j0kv zN{X+>#p6lwcv76*wlR%9$&_8Ak;SRTV(yc^N;I{^#(AiCHM=^teQXt!4ozdcb;k$9 z!`<;1@89`7@Ui%wzqED=`nQ;QpLC{EE+Kk%Y`D@Y>L8CU@rys64}zWMD_>Lj$xN9 zQ~lr6r5+S>H~7$zx>3yC#FE4iwEk6|jnv@6^OP7|!pe1=hblSTjPp>XG8FXnA@>F* zk|03uMB=|?;u;N^E>3S zshBerbEXr{bixUmr(@y__Wr1BCMhN{oq8tOc|PO!F_Uq>nM{`rr?`t3%{TAhh4u)i6B$%99rx8zUsw;HUClfejjl6~d&U=uydY+hlf&VH znEhekppscib9|jOX)Aof8Cq5u9ffnov{a-yk~n71i!?9Nyh!s#g3yBS1>p^$?wC)l6#-;chB*vt+D@9S%2ZVH`^Ys>{QvBboGuJ@bQMbQ^UUnHHn7D=}?X0fbL5VcScw@@IN zV|k%Kwno5WU>5_sD02%%Bbvsgz*!2MrNCJd=g70Xw>a{g(&v@_i0M(`M};31epL7| z;m3p@6MjtiapA{>9~XXH_zB@Bgr5+8LikDHCxxFBep2`;;irV35`IefY2l}ZpB8@F zagC}!Q!G38T(~IDJs&Q#b3Y2dDuD9xu6Q237_qJRk)@Kyoq@^osxkRHy+uu)zf9ie z{ql6dpMi6cAM1jCi+vFY2!6H`v5 zn!Pe^_G|5nI?8!6aeIe4uW8z!tbvHykr%?Hd*nqrf8-^lFMAMpT^~(oM@2g-+R>PH zETJ6}?U-oCV%qV9c3iaMq8(3Z>7{z)1&S*7VWfXr&C%Q(-W~* z%a}?3PsYq;EG6?)%v{D;GEc|MW$cY&Y7-FLvqO0J^QfWb%SHFbE|yDaX0aw`It$4^>arhzS@+lnEXUy9@bDE_>675DodImv5~IW zvZ+{BVxUdGKS={^4e(#rDaww#7T)tZ@;cw^I+~OmRmm}w@I9_$ua~^DWwSJ}*Q`m( zkG#GjSs>eM4F2tyvZJqtk6D^tyuK-V8I&9AWpB=7V12{aSI1uCi;KrzXL>wAjte>N zNIG8}c?EMYB5>qYpNoIT6&`ubGZnJz_Q*0&k4oeyMP7B0qZGNwOo??y5C3oa7-jg9 z*|FD3HJ2ZIy|ke0xT-o%F&cC{D|Wn8EFGLrB<`P(`zI8blL>NC$Vnlm66BPSQ$kKB z$Y~*`Gr>F|b5F!xp2(VeqSRb07$kNw5-WpUX$!-+G;@jp zmll+rrtBYiGo4PNTFm`pd)iTcxNOdi(?L0`K4h8qS!Zt!ye*gI20RE7JzWJS_0M z!1FP9LEr^}7h>?Dz>5MereM-f$}6*-pfcMDWR0&#%Fk!%=d*O##|4br7y8?U5~|<4 z`iB{1wC2;dH#%y3n@2^;1dMeq6R>LyD)Xp?j`?@A!Y0divZB)4c-iKYsUj`YjJ#F8 z@Ot#V&oXVQn`$!c6im{9Os4*C5HSe6dfFwZityUejS zG(G=V>6=R5(sV8SxbWk`k2|hd;e#hf-YA_XVS8Kvxf6=v3B~Y)Vt7I^J}La9@RPz%3O^y9FIwN0x`mI>uwasxx;X0#mol&^XD4b`7pA~*q_*voSa9MM@J%`I$zi?UW z7cOi2m0lWWvM!v-x^O1rLK#f45b*VH*kc*K91EkO48zX37Umhk589r4!{$jWhkmT| zO{H%!tq@)zyu$GCrGSdJuFKm||4Ji;GiH^>Eara3pQTj3i5Cfs@z!-smY$co^HO)7 z>el*5bY68{cq4iV?1GpVWY`6fE|TQ$O=2_-CSCk-7|Dw{zK?nF%}CotmsZHJEU3cf zD|ZvCwC`G_ed+21sTNW#q$WXXgwzPBO^{kc!eX-4U}jV^HhZjQpP|aDvfIg3rQ6BH zGP8x;>MXZ9%QfrBttsIabIo?(r@W>_pqOj%V?bs(=Sx}6m82}UOgc(c*@kcMtt#1; zPDr-inTjS&QZy}-MzanN0`wsrw1Ix%d6a(Pd6Yh`^Q^w3^PJM>mHtTI)e+tx zyg_(_@cgs-dd_o7pI7>$%-3}23)bl28@P3JNMF&hM^0f0UuVyqlqs)&w)7i0<@L{H z3!aY)bca?}|05rcIMX*|l{93PG-Q-;vjJY(m_{Y}tde|INxno0#-)5JpEgaSLe{8| zH7aC{9Fd8I#M+AO?Tc(d?k;Vo!-eGvyuuU}|-{X)~1d?ly6G3!@j z*008_UobU|OH)?9DJ$QUm2ZmV%ixbGS%#AhJhKev7r18`%r7`(=mUGpvpxj# z7-x%hCxpC(J&7*~w6G`fy?{%)<}R`0{YPJaULu{Z23#WDUwh?|+}3CP?I(Waru8|` z#NW`-lhRfSQp;t^=+m5((bsgIkF;I>QC3?UuMt+(M#1=#IwlPd+e|QA>NeSE=dVgz zm{r=jtI~FEweV`;)xxWV*9fl>UL(9lc&*`K7_K!^k}+nI;ohokbVSn;Wx2=tjs}KJ z&Udsjkp31^c|R&sHRaG$WoT+<{-LRwVCW_ll>{`rmst|kU{^_K!x_+$_}Z2?;!{=< zHf3m=G7N2IGQy)CO+0u4rC)dg zrC)dgrOD_8O+GIweM#xdnv8@u2yYPHAUywqCZ89TzNGZ!%w)ueGu$w$d(meb%|kDR zsVAAHUJfN#<Pzrl29Ch}&HaSw#(5MGaX+`4UB_JD=5& z&+5pR>WC&Lg|AWJYgG6eJ$#s$UeHAJqSBX?zO0E!c(d?k;myKZ&~l+hR9D8p&6l`?ptEj(Xn zr66CKLpMSR=3|25SvW<{)Dv8PcMIk#>Ld_Ezv-U>PfP3-R_= z=q`d*Xym39%Fqg%NzyCLtq8AVdO+|+Du1M{s6_UJW3(QcaO76;i7X4pVLeF298bb3 zQh#?FZ9;jcNL6Y3^Bp5rZKUuilw}%G9+1h;o~T7NpD>onFqV8;Uh5{LF|EUc3QZ_@ zn4t;f6-^+oY64NdLHYbEnjl`yOb~o-pgDoh4K!QWa|2u`b+q`9U5@4Tuapk&W%aN6 zxNypsnDuJv<(lHa zHBrvTvj5`mZ!}SuV_EYnUWe`uC|K(eZ>Atev1XG&i?!&{QHwRF<&9+N>!rh28lg5k zZ~K-%L~eVXUkVn6tK$-T2;cL&B$bz5D^&#=mFvUCvY_?VEUlG-qEW_=4)ZI@mtS!K zeQT4_}ydSEeAirc$qfNLPZNk-%&grF=Od4J* zW^KGrSR2#pNat6U>Wm&fuvAAGeuAVfmZ_%{R{DDhfmh@%z1h&s*3gVgy)nBIIzj2v1 zHYl&dHVS&CyzR}KlG}|}@R8r3AzfRxQ4#k}+PQ!5JFc}|`yuVzbBOj4+PU$->#>6} zG_*r99o!T-;|Gm8Nasb_sXdfVF*~U}Tq5WNouspCba5*rg1fk_!J({+(&EP38d$0I zCP$fjv5jTh3MdOF^2__4E%^cuKFRZ3X+c>ZH;kjR73fQ*lxqo7s$Wp*zy3C*#az4Z zYWF=KJ~&d$tw*raS)rT$(xGm16-IZ{%cwtgbGwK^=rI}4dPMJG@clC44Ax_G^Pbwj z!ccG*3x6d?oR_wE9ULJ2T{{4} zLR>yn;&d_hr@vy)Yw^7sI^<3IK7Z9(UfF^-?PUFNllI3=Do41c?pF^Oi0K0{T`fWS zfYHM=I2g+e#xiOS$_%QCp@bg35tX`Pez9%1Y#(AhHc!hXT$aO$0?>!!`i7%kYL_y; z;}dg7V(!Q_+$%BnO3b}-4Rg>43^#3xce2~>L>Z@H`piA)ce3AQK1(-anRoId zs^$h+xajgMU2FQ#tzC*`|e$`(Y9`Zwl#D9=NrJz4pltb9*azBj2q z9GC3PD(THC>CGr9?|UIN(}o~=)0b7$msQkPswi!x{v>|=ieJCt*RKc1RA4jr3YkPmz_Wn}a@#(cBlBg0!*%!#!Rbg55 zZSt2RWRNmkc@AnfZcsaXgWBO6BFXm}%7%CiM7xheq7R8aOge9XhDqnnDG>M_m#%Gy!(}O^x&xXYH2}P_iKL5@F9M8i9g|=sr6))^kkL5e7g!=Nf~^w z-L02>*zL{A_GV}xF9@6>dPwX%PQ*2D(cH9vd2hi6ZI>E{R&~fLfEem z4+tL+J|KKR_#isI#yfO;{X)mrFYJkxjDh9-SqJ*F4)kXo=r3`=hQ3!k&g_;j%!1Ax zK3Hc#*P!^iBRnQB$ga$*Wzf2^4co8zaDq|CGOu&M8G0p*F(@2*)iWHvhDgU)V~FU( zq_c3?=m3Yw z6y7PkQ+Su~F5z9myBtr(%JTL%O2@zQ_8(^pq8$*wy4&B%Fgmh2I+x8mC)yhnJC@E+m4!h5k__PQ@( zdXw{^y>EpJxKFG;upQ+)}ijKL*1ngVNc@?zf66gEQ7V_LIx(M zmp1IS3@>^ZZyBCF?bX(1@0)lJ z{qnnCeq+#4$v{#vn4Ga4L|OFj&am|l`Lx=8R{Q&2Q@03tZubn3a~gff%LU;3Yjs5_c*GMH+ky5eQqwLt+k*$%}vJ%}DomSYRR&u&s#XdMSG+d&8}E z*k2pt{WjL)*edr*-pK=({xoS6;08O$Uf2zkvRg+d3ESS>f@{~50r;KUk zKk{zscOLAP)A6qL^m73Qcl!CLOHWhKZyB^AK z>33H2S))U2j!a%CbH=nnk>*62Cy5o#i!?9NyhsZq@$y(;om9IZ=7N}uWb*P|jICP~ zb5YDf!KkpPP*AOff+&T8al&$;Pz=OkAQl6$NFsw#SPI0YKwJvMCDl5{yEMPs+~pl% zOgE`x$t@vl8q-bPRnmEjcvZ}+WHK68jTu&3<1u}lH=O^$$JKGlaM3hQ8MfNQ3$DO> z67MJ{c=N~@PEb%>nKelnHrk}oVe=%J?2VJLSyQBYpJTI4NoI;N?1)p8q1I{2(8+09 z#D(KDWuT~-W8kM_e`Y91>t|wDW@1-n?)gNQ<&_QTU?pMd5{l2!(V;fTF-&7zP|>BUMh`K&YBF&Cs?jagB5j-` zMrvH7agoMFnjnd4Cq$YMX+oq)SvM(sQuw6sDdAJXr-V-lpB6qXd|LRl@EIDxUN}0~tMJoOU|vyOCPE9l4P*!^}|T3ht3|eA(hg3JzJx zW}nG#q5!SE$pldIO{8sc#?55Vg`268`foNG=(n(tI&PsX*PXW#q3o?hD0_WHNu#z;8Vxpnk_?_e`P7+6(Wgj9OhEe-wTecW zfA3+qfik4sU^1ZJK)N4KIq2)J8!E1Q09^rNc!=JPdM&*+(eIunY zuH!4YY#&N*WJc0AQJRNaZmPI$*L#XNKLPR^PP>^h+nG5VVAy-}InVHf+07K>Gs9a< z28>%s=Sj0$Nas10TS)(Z{m9C#qTecuZY7gqx01;@=#ML-SpV^P&#;~T_=0EH=|4gm z8|@=TgE1c=!#gnd9zSw1lDVETY?A9GbG^wx_IfgT!F-fVo|*b6jbIP`sOTRh9aTa0 zV@9`BIzJ}T$BcyXk5e6c^2bH`IF04y`f)Kok(h|@WZPj^A?R zlaxjW4BHZ)Jdf@L3qE-vbTi9P=HD63pNi16PgRopDam|_GCbqdpp;$=_k zcI>A&m`u@56oy0eH<&akyTJsJ-bmZFx}i6c&gQ?7^|066DEdv6-t+v;uHST-n-sU3 zNauOTn?%2vbic4@@Xf43U6(TKx;N8(^do$7*JAKtzm{&H3cr?krQE`(^CG$B!gX!f z?NNp*yra>&TPf|~WY@b@_TH+_aI4865F6sbe`7rOZ#o7>m=-n>VQ0OKLm_+n?aXkD zyOSd(hxR*(@N&6}<2;eOI4BFD3{~G{GB9fe>8xUf(XG&!mWs5JBv!qWIysT7q)vA6 zm7=d=A?d4V9O)aVh}ZH)W_amsVulyirr7LF)JB=xIA-x*$=zn7O6YjE*Fs3FvfIhzTy{H$#ckfbZWsLy(iw+4NaxUc2k9JI?-2b?(hKH9AjbB&m52~yo!folb>i@|jQ*9lxtkaes#u$W{2 zSZ|C_;Ra?{!A8>9tv3>3Pu@g?v`wnwHXC-VH|T1-O;_V>x*Bg6e!KA7h2Jjx4&iqQ zzeD&P!tWG*r|>(4-+7V$()6xMY>!*33%~0Ezoc;2MSdq?Mdfvuz4g!Y`w1&9s9gG2 zR${BXnA_*AyHZM4s*;sPD&`J*zgtzw4f$2lw8}^cNaiQNOm`1gtBy5F*GlbL<<}{{ z&hzQ!>T3DCTIQ@~O?%u7DE-<`7jys07AehMvPSeZMh^z9p^O<+EQ0|i^DFM;TG7{% zPOUIaT0w_>miZS>Unlyym=4ROu$cRANTbXzAd?1Qy&POGhc{##T`%41W8Dz4?t(aeFsN;d(JeKmrAD$;_*UUtg>MzURrogH+k|fu zzD@Xc;oF687rtHi4#UH>zC)xPBJB`qCrKPCb{Z*2>=bjSn7hcN_q)iX!@I=XCFX7! zwp(@X7QS2f9^re0?-9O7_+H_Ah3^%( z_(9E{S<^;=mc#FU-0=E$4yu5{TIb*z4%&lT> zH73+<)hb|{@NL4k3Ew7syYTJ8w+r7ce24HI!gmPYA$+Isox*nt-zj{T@Lj@p3Ew4r zxA5J-y?jF@IAu!3g0VyukgLX_X*!8e4p@r!uJc`FMPl7 z{lX6jKOp>o@B_jR3O^|Ppzwpj4+%dc{E+ZN!Z&Ni*{m67vu2#loN+iuZ4qgUNLxhO z;u9(wU-(wxTZL~GzD@Wx;oF37V*lrNdrZ zJ8&VfTlC$sV>g)$?{3<`CBPmr_lUX2n5b}1?ABh9_KLJuq`f3j?LLwAiL_6oeMW-D z{lfPP-!FW>@B_jR2tOeFfbfID4+=kMcriEPW6wca!sY%UF%OA(h)m8zhc%5I)--ll z)7W86V|NR`Tln3=?>0P)d-sTRk4X23bPq{a-KKj*y4OfS@?K-&xGNc?PkZJ*qX*i3 zlp*qIBGmdBW{7-_nJHKBd1mIAp@MN{SgA6slrmp188prp5E1}4PO%eCE;HZ{w3jG7XD@7Ul#slR>Jw_ev$4M>3)&!k1aXO z8H(XL%o%FZ<8at!s1W?aHcf@O>u$<0s&^9^b(wpZp@PpaGvUZ*nR%}-!#~FiSCoH3 zZ4B`jnNgHz4qM`jG>5b=8O`FPdG|{M>BX0+gT3a<)S(P%H2nU!-uqASG0)^HBho!0-AfWfcCSeHigd3?_mRX<-e;ts_C7K1 z6Z6vqsqWJPKP~VxRKpm4hRhjnk_&WnGJS6Hm(Q`4zFfide&z31{;v|t)FHg8J}joWPC=9 z&lm$i{!Bc|epcXT1%5ULKgU`*a(|8$FixL~9s4|Kic_WHbYAHNr56=9;r9!_U-oKPkp1 z$)MP$s2Fsd;j%qr?kj(a917k*4g`uh?5(4c8>;cqN&j_Ym3F~V0(oFJspO`lx_A}^PU8LeB(pkw(qTfV1&mi1PIvF<`9R}Yl z=FNHLZ!r?eZxMcr@LPo6TIUA%K_Z@rxV7F(@DSb~Rag)jyu{D$@VLgUlD?tI5enYW z>={0fx}hb=K*5cT5$#6G@MOr1%{1>u(iI2N$+)SBjGM%~DQ4c>NbqKnZl(!5ba6A8 ze13k5n74>|3z@9&R#r%{TWKE;A>2wDb=;=TaGP}oxOJQL154EfZWn&L@Y{voF8mJm zy$^e1-@zXBd(4P=hcUx4`cBe+-x+t3{=3Yu19AiNPRiJ7J6-JGRl~*pU8;J8(v_-c zrShv}@+z6UO86?r)0=|3#Jh`Ta21bA%v!GEQIjppxr$#Q`U=u%=nB$#TL6QSt;QyI zY9-y>%M1m{h0*mN460$dWw?t8^HT&qSmvMkeFPkdXeJXyXSK{*Ew5M0>(z?N8sTe% zuMxgR_*&s>g|8L9R`@#M>x8cpzAh70*kM6{FL-~){f0%bv{(jLn99!}tKW zGhv&_c34TCC6a-aE(Z{wK#ek2U$DrYYM^=5S9o#4Kr2QY`xS}+z*OsG z2mms$*Yvp5CHEbcCz{`DQoL&6i1$V~V$!S=-Wa#R1>urq*dgJUWq#fiun)q+#6Fh5 zSL3w1G#s`JZGhL3hKX?fpPH?qFRoUIRx3oS6{6J&;TqvF26TU9f1hB&+0y|PSV2msezQ8P)k8x9UnB`(H%b5SIMTh}}fmVW`gAOa51}mA8 zj?NnBu-EAJK&)5>qn!q$nX=ib7?vB;9&e>#TrW4*%gyz2bG^c_LHGvY8-#BVzESu_ z;Twf-6uwFLCgGceZ_0!bc9?PAQ`)>Mz#{7@>?APDGA%?>M=(A{g7g!ndZ=Nl$?$rI zy{7-av%v8IA1t>Lk399l2P^U8FbqGuu@b*7R6;#|A^8y#KKX;27%7WoaMJ{M(ZN^C z_?a*+Dd4r0_~{as+{TsO2i>tdnZY>6Q7mnym0B&X3aPIeqk({u7%q#s|J8Nf#Z@B* z-@CYK$JO#}wN}KdwZLAZ@--@7qw=*XU#s%9p}Yky*~K7yo$z(S*EycX4)t22 z{Gwk$sN6E{1e+FSSPA2>8dfvDu*!5mTXyHW~ z*kvVLuE02P!0D6t~ z9psuz;Tl-OVjj?6i(+<`wU;CIT5@0gt^F#+eL_9tJM?K7X_flrf%(K&WRQW))*m^Z!*CbeM?pP= zH1C2IxRhx5fL99Vf`T-n`!gQzR|NM{7ZvZf@C3z>NEEXt9gxfcMfU*dls#Y-6m$QJ zNADobMzUP53t9+k|h+te|0sg@zXnjIj*64J`6r4U0U5MFz6#!z>nx1hYI3 zvy8+g2W+zpw}fCI8Dhdf13B@-O3Qe6!(|&3z-TM>5^90@(319r?Q(Ow+}tiVw<`=g zgzpf(L--EiJB9BQzEk*4;k$(I6243Lu1pwVhgs?iKt>1_*%ibs5t!x9!z@o>meCm| z*k+mNb{AG!x3`rU#y8&k(tp^?0W6UL14jFm1*1)jT4A|q<+um)EyHmYK3Ikq0vrjM zG^)Gh^=^5+TVC&0RQ3qpBYcnWJ;L`2-z$8t@O@scdhk*@>aogk0~V%D)ad@9My6(- zjXq>@o{sLtn0Tn*0GV7w9T4+?moG{PYvFzh4~hxTSKY^{MKe}!EaOfw#)(^^48B8tjz|7U~T4FnC5TR z{Q;^;>03zWP0kk5xevERbXa5I^Xtkd7Vj$@t+sOE?e~d3MDTtb2Ad6x?^fx7^`-}x zriK&V^uw9dnr#%MHQP)ud#|}&G1#uyY&QwJL{+{+I7L3z`S-Q3)zDTX~DOo#xR=RL@bx=w=R?Y?2)@69!ora2bVR8K$`mOiLrNGnRp| zCWBRDGPFduPaLD*fN^|NStY58w6E=!-@E1a9?z$K12;9ipg~G>^0d}&3sW&ySn_dk zH)~+`f+msS{~l8xdf^@ly~ks?$7I0RYZZsfY%l3lv{y~ESIm86ZuTnok;#{y_KCTV zcX@vI#3zXRDYM08_EUy$KJAyx{^-t-cU%WN6veUwMu&w5#5^E74hlag{GjlI!Vd{Q zB>a%@L&7&-qTbEArP`d_^P#fMytn+IT^+yVR&Nn~i|AWO|3!kN|FUPcin&$Ht-4p+ zN)jcuiL_0mZ6a;cJ>z!a+l6lzzFqhZ;X8!y5WYkBPT@O+?~J|MNlW;|V3(M?#M~7- zwkvjQx0t)d+^srylf*}^dqmnJ(jJla#NO`}X|G6oMcOOt_wk+=28PET`*=5x2#5RP zeUjNHnf=napG<@~G~IrB6lvR^xP@uf8|{Fk56Gee(taTJ?Vv~pMLHAZ&TCVivR?;)KBAnp#(7Le$+=(P0{3{Bx-pXKP=iJEy=RY2#_KP5|k<1q8MZn0Ko*102nZXD6K`$_j2gh zFXx=|>+Y9xn4FMj089oZ=LBE?g5hg=pR;$>y(&0RSNp$S?f(YDlMO~U7%6#z;s!Bq5c9)iQmqe@DPzU_u$UhqDANo~^8APxALU7k ze^e(w#*-BLn4L^Ka-*$Bo6)vzY)4xywxe~I<^odWci;UlV1J=b7zhG1ans+Ht0s{h|J&Cso2rF z5<6PAfa6u+SA|~{epUFdKL%dXhyD8FapDP2aLD+L$EfdbJgy@__Nea^_)dZEB*<~( zoli#Dyi3e?iTSQ4nBBWbVs`Hq>D?l|Tcmf#q~8?jH>om)av1xYESMeUZ<3A?63BaK zCTZ`X43Xa=!lHkh2_nB6yXJQ(LvQ?^3jaNxr0jd+BHkMp@xHi--;ay<029phgG|tE zA7lnx9R6S&ia%80e@F&R`a?3P_#ZJNy5WyvO+I9@)jHPF51D_fb*!b=E7$9Jk`?j> z(pl|pAfmp{im+;Cx!cjox1%{7$E(7x3co7+s_+}168~u(pV9GI$qTC!O2S+izhW?E*UpvVabO9RdpkxwXB(f|yM~j80=9n4JX4?4%alz}-pn zC{`4`DD#TOgm{r8ig$_BB~q72T}A@ATX?tdZsFa+dxZB0?-AZ3yguHrUmtJSua7tE z*WXD`HE830gEsCr#2fb;L~0bNQKUwaXiy`c9iR)E#B5^0eD0x%bZ+@?BHe-*rowHv zSL+V^TV`!{;LX+icPIaa32`$`V?NC+ft8_IPYX0l;g;KIUCZq{3U3kKD!f&AtMFFg zZNl4F$gvM}ZFl%9p8exTxwbn!VV`ISX0*eMZCAeijw?I1QrCVbj~yw}A*4e{Aw>#8 z3PL(lq*F+zBf%l>jrcdYRZS`!{ExoF#7iUXv||UWiM9vZ6<&6yj1kCDs|20!vKn(2ImQNh0{Lm|9(w z)kXQED@D45bP4HBk!~T~LV8l9M@Ub`hecK@Hs@k&BFZ=QY8_dOP3%%_?}|-?a`Fs( z2MsMjM|O+e9hU-)MUNcW9hcH0v7WdTm}6peBy36&!I4dOC2gwd?xamM-IM^#2XF2NiEJuUz2H_3D8-zCsZxr4ryivI6!-iz~__s}RrqAbWvi=T# z5n~gSN|V8oW=OJGHQ&wV)O?SQpS-eb&8#J5w209nMvEA&DWg@4Rxw(|XafU1Pf}Ey zXlW*22CyTADCd#mI#h)ze~N6Y-^Wo?Q%hf@DAY}!aIZ)gcpPt zgcpQ&3hxx&DZDe|yY{=-8@WO4cgKl);>0JT1avd&*pfP8nT}YdBbF(|LM&5= zWnhDqqaG@j>5OGMgG}&mQT7*Qe^K@q<)1F$UBbJBcM0zn-YvXac(?E#;XT58g!g3p zTa4{4#x@jV8;ZdO^i;8byJDHHSf(qM>5j|kj%B)IneJEyHpsu#x^I5FjW0c^V6~1P zQtgSQdxCWEbknEUeN}T!pGLRhV{X%D@GkV)E=`YB)$6gUdOcQEug9tygf|Fp5Z)lX zQFx>9M&XUZO$&8o#z;Q@&?H(DAM3T}EraIGpJJEQ{OK!u)^#;Q>wVY1MM#T~mK13f z(ki4iMcRb4326fYZzNf^eU{Eq@wmx|;=R?OcngDiFe%(jh56tj)Nv-%G3FgUWVi4U zH$FnzLOPyaOuozpMO_AphTK~z!-pJOO$KH|abx@jJ1f%JJGOns5&E_5v!1YPuHeFU z8Qm_U+do5Bwttq#4&fcbJA`)#F9N_Y3bA-Y@MIU|fJg%(4LB(Tc}STJ882zS!_48wysZx_hhgO~B8d^5AF=aQ ze=B2X6NAiww~YsxH=isSB%OD(2cS%NovQJ^Kb8Dli4Sj|BNL%!ri zLJv#Ok}>*=`*O!9!<)EJ(`eZ7mIy*u2_}2r#$#`fQ<{;2J;wdVCe1AcFv>QB(C;wH z5{%gd1ubU5o0F^&?{k$WZ{~~HCU2p)CMofXJ80@A|0<)u=kondpOUAiZt)jRibk2Q zxy&?W_{7xo&Djj5X&1k3IZZ)+Lt=)4eADKP2_jxIq(j$g-K(De3}s$MB18@*nbC{_ zO!ECkFe$hNb_KVD;$h zbKhRgiu;HpyTSVe_X+M#1^b2c3mHg}0U-mK>2$9qtG)3Y7v)>QPxeDu0Zn$e55ZA>%1BE@WKDM2buZ znaKEXRBDdK)}d(Atk#F4v2|k-8;h-jUUCY22<0q6AC8MY9+v`TMUQ+q9+xs4B^EuY_i0ywcY7$xJl|=(`R-zBb@nB3f$>ud?@(e{0y`XS(#uuSM%mx&6|5QukI7x zC%jL1pYVR+{lfc&_X{5oJ|KKR_&{b}-W$)Cd(&wzoZ1`DczeT)x0-`)p}zISGJUa3 zUo6ufm(w51^v5#&u?%dma?}l`GN@ucMH`3(2ZCU5@}QhBC?^cc34?OdknkblL&ArI z4+|d_J}i7#_=xZk;UmIFGEN?hjUP-6fs+Sg8wP_7=on)s55+P=vCL2`GaQ#Q9Lo&H zGQ+V9YzR&siDgD&nUNq9{5vZ9M`i!0>>rhX#)OXv9}_+%d|ddr@Nwbe!Y71J2%j(< zE1BMBOc)7koOTuXOEn$GXzYg3*rd_eq|sm!`q0?jW3kLwEHf6%jK}4S$1>xw%y=vV zlhm7H$EJU>9>!S!cn@bH6|B}h?VZd-DxI8}WEO9AcvA0An!LxaGj&P(NnSfn8Pobl zk)~qOlt|MeO^Y;55?=mZt>Y!{v@!kd@yKiDuB02Zgk)y;I;OX=TO{37300+66YO41 zuzNMZ?$rdmPk5j3KH+`B`-S%l?-$-Ld_ees@B!finF)MvJW1}2C&|6>#JV@0Sog&; zeX$I*wAgWh1gcs>mxHlPe=O4US4GA9-J|uid_^|L{;lsj*g^vgy5k4Y(B;(}4*pR{4kippa!PxkrSY{}eftJC^ zP&GJtIF=cXWrpK&hU0QZVwsUx2G#^8!z4L5s>GwRe^mC5%KlOLXH58*@G;?I!pDV= z3m+FgE_}l9s7u3a-I%z6!X8ibiHy5PW0OW>lSX4VjK*#li)F@Q87Ljx4b6kQ$77lC zSY|vfXFM)vB9%$HFPLOms=wnxEG{n7?vN|Ec7=%xyia-0^HWUl#b)Z_D9Bm#BxQ85 zX)@O7#q=rCIf0%c{p&Vza9wFyGShH3db()3KTSb)w$l{UG+Z(>CS!denN)Aam`Tma zRde$G*4z!-aLz7dGohs%Uh7(f|foONN27Kq(@h`Ski|{KL3XyJaY92Pq`*f z9>NAkU;~e0LUUW&;d4Y#-x7S(5b8S<>hl;gp|mmah!(7dzRrNYc9PH5LTO9z!G0)h z347d1y(eO{sK``vOXM0GDp@GZ08#X18>!pW#PRmKBm^D>nI>%ra$AHWXSW-QQBdVpmvQSd~|# z?}{xifxbdIKHI{_#pM>n5@wpO_%s^K3|@q-zI+J_{Rq2wG!pWJ4Lk~m(w>IWmIPb^R7@jB%p7gL0?N?&Cit8@)DFrphE;#(`c=w|JKug8~8VF9gQ`=YHs7^YTc}N zEE~6^|8C{KFr==^(W~;;DwpGUrL&rGS~a)E(DLEap)y7AATc*6}DDmUtSf^C%ozGtJntH9}!42?{4Q3tHQmyQ4F# z89p|!_H8f==luP@2hr$Bc{Jcn1b89@c=P5fgGl8zZ_)8q9!m+$zEWMyl|(CvRuZjD z8W?3U%3_qosHBXF7!@%p&Iq+=lkqvS>XzTUIYhy3P3AXm$)1SfnbWw>$FXY$oMwCgp4 zF?d4|np@bFAU%2w7t}W{-z)*mEn&(?bM)35ST)x8i*;cA!&Y1U!&cj=U@0z>SLLcz z)#X*z%wM-YDYvQ!u8FiJ(wZW?&an7}J#UV#i?Lx6)w+**HXC*_S#jL3eZN5k8z19l z0zJ$quX?;(g+}sE^7a_0WoLNT6uMc010WPkxn7@nVG4aEki3~-jZx=KRG48fxv;AsS_PuN4LwjqCR?&=Q~a3bjEQ45#qm2Ad$hI~m1j^EM5gx9fO^j)=~*iQ#wZ>Vl6?*TW!OWkkj@@rC3ez^X+%TN;iZec z$$ha^6GU#Sl;LL$S1H4Vh_xu_UTay%S{(T`nZG9U*D1rv%sQD-ty#Y(sx|A9*`N&P z!y6_8wKmA)Bx*yYoh(tSy!C6ShmgaVeCleckR4=r=SO^ty|b4;x8LkCLeiwP@6M@B1TvOikor*du7 zL!IU~Y5gLBSJbO4f}4C+DabqBYZUYfPI!&dsCvoW@HI+vSI`=zxkkB8X%7RJFxM&V z=7~dTB`D4N<{LEcRbH}ine$~PD5C^rqMPbAN5=kOKGw8}UCM{P%XbqyboCFLbNR0X z9v^TheVRM;OP|s4vpkklMp=xq7-cakDWf7rMU09wLjP)7aq+^n^6qVu>(E@r*bK|@ znEQe4BX$nDHGLl<2i-VNHX72V8yV2e8PJW07^WK;(9IF(#-lUR@fsfU4lz;B6I{H8 zhSpW`$_g4bW$k)_OlW+&+w$2fx7xA7JT<}wrEhWY zvsZ4yQcl%hql_?brArfATAJ9>(!`b)FSb+0wiw%DY>Tl2#-v-Yb91$Bj{j~+|J|DW z+r_!(fQE2x#mfy_+2w+*>~g_Y?Q%giw;jpnpq@E}^#D3XiCaB!3pFztxXHA6CTN4J zc4%W`D=w$BjV-^GouzMOXX#tDvvgNUnV<}*#0zJskD{R%=4t3yIy~71M zj>J3ZqHXdv&K)lAu}9veEBLCrT?%qQ-Q~)jc^+0%#zvwLhCKxE)LZ!eO;^rrR(PP} zp&vsX-(xAPyn9;e+|zR79&aMB!$eLN0M^%i>cb_AebM)IcLHSv=7I7&5b1z1|Lf{UNdpWgIKk^Yzko!*>1aZBxq#1D4{KcIs;latRBlrsh8%!5+R zoeMt~elGmnaO{`!;qKz5E9X|NFK*WH79DTJ5xX=^?f=V}mp5IxFy`gWI^Lq=tvFWe zeg&jZ5ulp~5W4ZGfI>HOxzGMgK?k8Ak0L=qBXMkjf(}AK9!0t!iNS+z4nj8`MY;^d zr7bmomi7Nx*8gW&|4VIeUitH^{CQUXJS#7YEPmDeMOOYID}RxZ&tKdcR|xC`WR=ff zX0y7?W_6j#Dkqni-$FmIrR9SyEgx)Y`CuzuVAvL8Ta0Znwo}Fq7Na!6VKGYou>PZe z;LUgw*~Ph}ZAVl6U6L?p zCEp6!z1>$r{c8H1-UxT8mPRZI?op6)s6EO=x$NDMp1s0rnIa6y1VL_(td{d9Wx8sahzs>%ulGLdUsmJ-L@xOLw>uJ z#JTke1^=~cbxJ|Z7LunNPbqCv28>^nrh%s>h?8gX;2D{mHlM}JGcnIomq6^C_Ha}` zkLl;4Ul<+C3o?E7PdzS3kNkBZnak8~5WGxXW{F6KF1wV>7MDRjiE zm_|WXoPDlKxodvkJE;A;eaf!{rNz}c&;eyo1*&ztxHvEwXmCJ!GR01Ab{xWl13br)O-_kUHOA7(pDPvoVZ85gR*hv{XV(f^qBgSsZ*cD?}jNQy)!WNjk{DMAy zMFo9qbiL1?V}(+d_>w0~GjOYQ-{E_4p`HssJ>z_V917g;2~(7rLs92K(ZGeamLS(u zCKoC@7b*uX^iH^;#%#qa2d+QXYka#qjPR*G^v6^mlhByyila1VLVIVzxz5~4R}S!c zMSs3M(%qR*+nKvukwa6*8~?n5M4CGjO4lqj?7>C`e@|iAQ&{#Cxc!u|FUGzY`(hlV zi~}(a#5fS+Fl8KyaVW-NCitcgeGYxx=g`O8{6!BRl(NJFo-obGg=Wr$W;_}VJ~qwB zfMzDh3pA*wgkBhsKltn~rme-GWZrLzdj|Q#Psa?jr^C-=Pt@cQ^&)$W$`c-t$Se;C zEb`FpljIv3805$(XTsgiJcuif>|zyzUCxBtop~5D;d!%+RSfQTCfr|B9gbixtHY53 zb)-NYDU`=4<5-MiF^S}mvD%$vEZw=DAl;Rq z?zaZrafAN?8fl2d(zO0zX9zzcZ@D!S##_#e%=daG`|-l|)#x9T+@}*w`B}!|D0ai@p&C# z4Bv#(A*4e{M~W1L6oeE~q*F+zkWL|?xuQI4Q2zAif1AD-?el(;3;pfVv|WZl50lV4 z(a5~Rn?;A19b!T+F|kJ^$s0=AN#ZCW)@{; zF?B$fkS-xzDbg*ZTS#|`^a$w@((?wVp*+*9)@dl*F6e1kk?OL>6=fZiF)?}$8if^- zF4FZfM84yirb120L_UPhc9KPPQ6|Abc>llQH3^`30y+q8eu=A9q6Gx^s0|4n+)WIO2} z@qwx>;x#+3EyH^hywC|XqnPViAd1#Mocjfq`o-j&8KHCh5{ZAw>QQI4f%O6Y<3~sQ zLrHuE2n@74KmI8y|NhMbAciK~EWb9(ug&snq?Q;hsVe3pzJbJiB&EKPDhi&?Q5k+N z46Zjlq?F~E>da54xIO&53Vdi>SC=2SaVrwF5$1NNz>u~pq#fy*4xK6B3~vAypr$Q_ zhA_8BQM%DQmv66$5~{al*yK({h8ADH-TkqU4Mvc5*!nZhQHDRy1a0+iWIeo}ud26441i5O>0M;=$~RhvE}S zH{JTMw`t$*!tT5+n^Rjhr?yN^){)~XGRn!il2GHD;ZF9G&2Xpw!JYaCcd8fV$7P}p z9d58ngU+(5&kVNk3pnu^euLCE@IK;>L0D9QlW(jcK>qceR{Q!6bcNN%@EyCp<3HD? zZ~Gur&OoSmB=fOg5PG2ha)VH2u*DLo8J1uf91?=^rl;9&N2A-3Og?f2mHi}C=8<^{ zB-0+W3iY~O^|4*`v0e4CUG=p?c!%%~;T^&Y2;_phxFALWWtu|^VswJR@u*XbPUX>A zTj%rb*=p9Ft!C{t)eMC|O&fo_u4+Vjz|VZlbjo*RGw8@>06WqQyt3!aBK>yC;1$nMHZ*A zEnTnZy5CEH2kdzPXY-6az(D~~Pc!=mKDr|Q;kgo20!!G?qFQO=InDT9r^9Nm%&^)r zVji&CA2yM}d1t~_W3alTrdoo}6~b1h!%RE*Jx-UI%n-(rAtr3_Ql);RIh^WBC;DP^ zD5iD1s5)L$9WSaCbft_gF}lR)5~Dk1bc@j~Mzk`vYB*cGwI4^(#=fRtYDtC7|tDFqa`??!B9(h z+s*YD4ZiNtC`WV2u-kHBFp-_Qta(FqLnu752!$VEk<|fR*^usY0LB<`AUF5Fko59aXltDYas;XBgn`N14sYu4*qHw~oo&9M#o5>Eq} zTp4H(vyn``4YpCtMl$t50e*ys1vHWV6K-0Q=uM<^9iWMHD@Ad#Yr{v91w#U1It0{$(!#s%m;%YR=^g3|d2FTFb-)e(pV zDTJmyj(dkC;GhCe`oPY<$};D5Mdu?m!!-tkN5f@=_ma6z_j8Ca%1A>8kKs0@tJxmn zhq2oHqVN*#sDV98*DScLKn4R=pbr`Q0^P^x!3ieA_6BV&!K+IsY6;f1PRdwrp*lfz z?-8MED7T$OvD|iRZ^=96+zL1lx`!e=;!-+7DYC}=ZFD=5Nljrxq^b09^}%$_?P#P# zUmJ}T@S*}}>* zVTmc=doQkfh3jVAxHY5O0)bq5ROfrFGFe(Rt%rfKzAw~dxYq*(<9Lh%2D?JQ;BHH3 zBw}^obt-IdS*Y(vC~ZtuLMUws4YtgkV$jzbkq>r!+7g=E86R^v{KAH`Bhr~K2PwZw z)oEsio!d398^Gn~-)JG+;>MZNsqKUAP}C*URtaj$zBw{3T!O>t6Js-nUG4^0 zV@$gkMi9hAFai@10W$=nSBvVsT2${%m)rY<^a<%pk$xflLi$r=K*)fQ0U@C;KzX*@ zUAsh&!tJ_^>*l>&H|K|QdbtqJ59dH5lhrF$<8}Hz-LL4=ias!7|d=t$UL32q%7Q7I$Nl>2aBU=()Xr^vXFaUl~aG9hHbkr1*`IeFBaoIGJ<#Z&K56wiNSB94*hx8Cz(_x^l(d%|}> zjonv!A~!Cvaf)#}H`IJ9o=M7&XW0{CPeg^}pt{}Uy>RbB|0EIG5{xkK*46`9WP!cM z#YP#SyP05gdFK;$nJg;H-`SBX(VC=B(GX0GpbXrW+DgqoJ9-PJM4``+9d zJV`HlWy{xU2QR)+G0Jie#8e!@T16rIMja{pvJW)Vk!+$ftDJOZkY_%}U7ItXzw$WV zGjDTnycH6a+J{TQi}TZe-TBv&P&Rq!25b>9t{4C=uUnG7HF3QV}b znZsfZ$4qDzybPr*!OPVV(ML%C8M=yw@FwI4>yjlxFh+5+S3@9frRg7Bt$%Pe|BZb? zgW(r-{F09MYj6}kE__`0xbO+#6T&BiPdFahn(4#-XB7JAAN0{b=;QzLV_&Ep0rO*D z%%1pC?TOsj{obLmGab*$k7wn_v+^*-I>g)Ev#`e!K3e7{YGi%SrLVQa$dr_WrIxU9 zF0|rNRw49W$p@cC89#Pmql_Orw^7E=6aPQ#_mMjP|J{DSgv(E4{a5P)8%4AJlLPJy z4#36Or0T__>cyn$#iZ)Zl<+CxQ^Kc&PYa(GJ}rD&_zdckhE~)k{X>1yKh&q1;Wj^+ zwQI5_wl=P1?SiTnSPp|zS^24~{8UyRrkFhI73{GD_wK&`kcT_Yd19?J~bL^D{WWd^U;XTHEb*4GJUzUT=)>NR7{ z04|te3;3~MHEK{Vm*;=mFAaLR`2M?0*y20H`pEblKiNmd?|EVXcPP00Hjv&WAHW?7 zoqPBVR{~rZ|9}Ss1&3Z61zUo*xO%x-&5pS*mVqjv|As1-;G@`myi@RVo^?NE*opU3 zeRksgR9^|oyxnC6DC6sYzUr47pkRDP_a^Le;!i2XEK*MBntHi9{y<`@uL8hK9&I_? zTpN>M98Zb_lWcW>x3^%D8OuF1Fv${Mrm>>;lg`@)u*OG0*kDX*F(BqZV1|)wP3U2yaUvM7&5cI=qGuh2SGj zUb|qBon+&JL6+c!CL(4DRxyOl60Bk)Y#Y2TgF%MyN)-_d86b>F2BBV!BJO-)@+jiY z3)fM_eN=U03>?18bqpL{rjDT>VSO1xKf-_GDQ`mm(mIo~+`WF}kh?|6AGzd8KFUeY zd<=Cccz87KDMlG8nu%P3naIErblRB7z`bLnvzHl*JC-r_3e0GnMR0a9ZZeP^H@Y3k zWREz(y5`Ra9`cg{tZ;nXbb@w;HRRj`rJ+H!?%FjxK^NRUnYwB!{WqQdo54SOX=-r9 zB+K@leb=h~Fy15!_h)$D2Gu8 z`$>Pl56k^p<{x10(Jie3$t3gPYHomnAK-ZIf}v8tIPWuL*H`E#nuJ^3ReemXSIgP3 z#*76mxhDvd?99Vii`*u5i%k~<<((ydL$ldP(4wwbw1u~ zeE`S}s)h|RPWhp%2sAPgZ)Z2U(}yH7#G3F{JDDHO$mNG0jmr+VkcQ)1QNz52`Ji3m=&9 z77TO}LcrmQwSiR+cBndm2|Fyo%Ok|a68C!^up!W3gJ%f!?WEsJvTLMJLq-+TQN?sL zjrJJ20F6QEZNM?0GbkpFcn2QN?dmO><05)Ab!w&ojoh_~Se& z=D3>sxJct9@r?)LB2BPebBvi#Sre=)g`h0%bwOEk14n%5>Arz>c9O1yik9$OY}`!+ zW$g^tyP#~~LSsvarfvWl+euy^Ky6E)yktiU4DeP9-TgR0S8%X|A(Bn#u*PnOQDK+_ znc(_KnL6oy2-R|ud2(o;q;psRBrH2B2Qf=&0+a!1KDnGDK|m@~|nz4naMpNZASxHOAp?B{C2HfsyGsPwb8c56r8DPZ2Sx|cI2 z(ws_}$vHi2p#pSEA7DerSDmL;<+nPoG; zU;RPwq%KVRZ z=4+ni3I+ccJL7#jby`&xtIXn`8O+Xe^-KO1UeWINgkNj&%$fw(BEeU^q%{iCk~IqY zRwSHBy0vv$`$sOY9%b>ry8G5G3%GCHq;cFJodekh>HplB8#MG^G2!&6JF|6Z@31y) zU7G(;flt_pA-vvcp(6MZ2%r8f3n>dJr$|LeMMy9G;CQ0`N@mxjp3h5v4Dlz+j zx8>eqvKb9Z#_R6)BWeWb!F1SoVO4qiMw_BD%Q)LDb`>CsSI>T6CYk9R!0(wTVVuHx6B1cxp!l!m$T-g)-Kohu(e_rg0Q}Bz z+9}_}@7k}YXV!IQBR#X>XM#IcsSBIxD&65-04HCoROrMNLWwpIT<5QNT6@Hs z0Z0F}M?Jx=V2v{OnCt$VZ?C&@-8(+?q4&P!SKj}j>%Hk&CQy5w@*Kg}DX)1(DsMEF z`D;E%Y?usAZaf+_2D6Pj<6s-I4!+t&)WK@aWgX#R1=0sbX6K!cuw52)Iw(xD? z+rqbn?+D)!z9W1`_^$9>;k&|jGjlHJVcvVt-;UiiQ=#$70>Tvi{7cp0`7!)K^8U!+1;${Zmn!+eK7ql3k|IH zw;~|~J{dv?FbQ(=e6j?~{9WxZ%5j9iap>=9sLrEsgurp===noE&mSRh9zswv$=Xw> z_7ti;g=$Zs+!wwtd|&v!@B`ro!ViQW2tO2lDEv_PVWuiT4|C(g7S80;?%o87!@I$>#}+sey;Xa zYdP4df>Gw-y|rlm@V@v2*9StKfDb(8h=s=-vGAB779LX==V=(nqj1E+aTwq+hwdJ8 zsO>z&;%3&+k!R}D2V7w(*-{64=VSTIJw~Lr~XyF2j-bX!p2#h8E zZzfbJ5FLNr8lrPs%0!4PK_=ql)dq3$w&S*-3qqVsi+6c;2$m&Y@`N5)v%l z(nNks6Zx%lioY#nTgY}mu#t`G?TE7@&Q8kN6|yU2H@iTP-^xztp@K!x$JiW7548*` zSc3I$8>K&MamepvbATFY4*A_I3RMy`L=N_Qy;9+Kc!0Mw72o1ioR`Pa=$Gmcf_=Z% zlv-RI*fyDDX>6NF0eid2zdceET?~Ph091AhV+;+zTZhS3i`4a)`+oV%{u|O%hY(;10`))>RmxoW- z`q+GB2bUjZ2OqRMr3~klr<7syJ<|g68CNTR%nLs=I^2Aw+IcS0xsh=GoY_&+bCE8% z_=({Ij0s9b7qG2moNY0;$>dxAw#np^k2_-SaFK|EuGOt}R_r%;I zlltwE$%h^Hr~(uFqVGS5c_4_^6-F%GSCJCgZr9_J$~I2kpLs4SN&j;QQgNu$ia@C=VB!y0!?84ei7 zD&klgpOEb!X3V_K$)xzXnCD_%kf}=>y@l*TqzjQQ^_H?r;g`ZMg>P~7;nbfB-{NY?J`-Fh zL84msVSf;Ai%;ki-ZnuPvu!eVB>nH@EtuPScgBuLJ0k6nbR~~G^UmB6b63n=F?Y%Q zPABb(w8!NxwwygN_l$`$_e9z!i3_3oBJGQ`FVX=?Yz+q@9f))w(xG%Z6n-fDQ1}ru z`9ZI4Mr0DBEh4pu)G7sA4NqQ{*!o2B9+g(;^+E5a+C*$4@dG^hgzr+~^McUSwk+A(HG*O7 z$Y8>`9qGS9`ma;}g4yjdyPal-NAYqUkJBL?Vsu0d?sqPj!Dv+lF$>15*71I8A)?>t z3U$&yG_TWSk~evDs))`=#)^=d2td1eBXL*z7|VL(Yc!>qa}{nIYp6-l##Ky%~zw&d?|2SR;2 z!$)VjW9jZln)RiJapZ<&*k<+R+FyH7J(T3W4TTYg zIS8A0j7hM>PeOklSu5xgsBT6*YBvXHCRBGCv^GSq5TOh9M&XUZn}jzBZxY_*c<4deo=Uc#w>_QydnWz&EdPbN)%rAh zf!1d{LcAVoeU>NNp5jTXoA}71Zz4B2?%Vk1pARTlQF|c zM^j1k#j-*BQ+yXx`_no;qvNwYb_nkf-XXk0ctLnUctLnUc&G4A;hn-e9Z!RlZ-43+ z(hY0&k*s|C)7cZxWKTRBpU|V&`HpNx9odXJvKe(`GUDqD3fYVb*^CO=j0!awSOK|@# zViqM>3%F|-(KE!V1V%~9usR`*hOpKln3iC5f=yl?A{u02L&R{6q&ezXQFWlGI#5&{ zD5AD6d3)*-qf3k~F}lE*c1E`t-C}f$(Va4S#OM*DM~ogZLR~9nBU`MA3|s*XEebw} zail1+zsPrGWxKMn&^?vq!vbCGEqt<@@6Kk>oy`EIr5SLL>sDt46AkfRDc@5gSFP*T zTd`obk$j2cYE2D796WM}1COdfu+7tm0*}IB3DXQeYZd}wk$#X-VI5F{b-9M&V7u zn}jzBZ*n|T-nQq`k>&a9BTjA4g<-&&Z)mK|%Dq{+H!JsMmEK}_a&_4vQj16}BDJDu zUIeuoBcZp7)+!pbu!v|FfRdDHGg>0kma(e+Id$`(vw?N!OhP*_C za>y@JXWO5LZ>(F2kpJjm?l2@-c|#$opub@wSCAkIOY8A}s&Os~3q23Y3D&pMW?t^K z%RkV-t1-$oCSS7H5i>i)gfhlpMTZ`iVC5~u^n&QnFl1;xVa@F%m)Cxs8OIk9(>ZrT z5dt&8F5Go0T6KcUy8IX_6V8WnimVDWv1mnEewB`Ob~ou% zyIb^b(R)buOL>Yf~m1`#81a#-u(@EwMq? zkSDx*P~yob(H~Do`zgbjXg_7RiL#$Ed{^XvX1@cZb0_Bj>D(eU;2MQS2^*}bapnXY zEWx=IY_P-w-pXN!k9II5Fkpx!*w4TaOZXg=GJPhKJlF+my!V13M&qR#46y{J0ZBQq z#$LBL#`1FG1>cTx}HHs4C zN&*5mOgi?BRqGguVajj`eONLhlwsgTD8opMsE&+K8M~|sVSyoTGHl?{Oh>8E+>cP2 zN8!+yY2i@Syu;NLs2U~cL?Lt{*IA%ycvudqT7uWtP}Qf|P}MTBCwD_D7^Q`*YooD+ zqq1<67V^Qnv6wy<)5k;~lk3J~=6K8;7js<9iI_PNGbfB$bC1~`k))x#(tyThD3`XN zu^S4F?Ide9G`0i>TxcBV(AeqFI542LC0N6uwk24fp|&NMD|EL6$7LAc89{d*$z+ie zbe|1x!RwQ;$&<0klXA|~V_c=0^3|$PwIFE)b4~@xD8W*x(j-ge0FAuNjan5#j&Uk& zD%CnZG&x04_KVXLMFYToS8tcoaZfp&N~3m9E1om4aWgcIZEPlPV>2x0M}lK8OY6mo z1FuYau`;25Seej2%%WEtpF+Vt9b366baL@m9KG`949!b}X0oHLW2-hCL`=6F@;;a}Q9Eiu!Rm%8+vGv+VG|&N@cG{I$ban(}zgs4Jhd2bf_ZhP05%( z9Mhqx(Gas_B^o;083t}7790tJp^c8p)NxcqZlWJYMdbV9#!(UZZvrUyp-()+UFQ?e z+A+8W`KZ|B9SEazGs8SeMHuE$D#E+RV`vNHj8TA`F$!=)$e0Ph5916Ly*y6(u@8{r zq@OXt{F#`b40D~JjPK*lPdxK;={lHgN#KK<6VKM3wf!)`U6ZLJCgq4pcLZK1k|I+= zri4tV$h44YAu~Yq_E0!ouQP+wYqzRTDnyg9@{={nV-at%MsGN4iiYz}_f#x9m6e^! z%1*1u=}0UG?afVeD@?G2?NG!!y;#%_Ghs4z(n4ZKM&rjF?A5%hS2NFE&SqHmdo`Qs z;|z$qF#0(8VPoneowwEcMDJIg{bVw)e$A@-MH*n9%w~X0W-}n>fS7}15*)Od8jKhe zX;7pgEjkPd9}+$!d|3Ff@L}P@!bgOU2p8|S zed0Ma(C2l0LB|&}4VGqd9yuyCMy1B6)Epy;E*%qTOr$X*p|Y~gJr`Au@#o_NlvKNf zlIO6EDUme1A9wvEZpm**z({CuR4f+l}TRd`kF~@G0Tb!l#8# z3!fG~1OKaq!vFdQ)ASFf)wJ6DWY)yVtcjBu6LV12+7x@IsjU1|R(>ig4^vY49PF{g zM|nBJfUvc~GDFmkDRarDYvHygtJXQg=Eg2$=0$?Nnv3>oF50WPXs_m~eZu>M_X+P4 z-Y>jgc)##|;RC`4gbxTG5I!h;Q23zmLE%HfhlCFa9}+$+d|3Ff@L}O2!bgOU2p@4g zRJgI1lEydoa{BL;^xvz=8^^|9Vgnq1S;tp&1cUDzn-F6{j0rIXG|=O;x>h5IR0|&iQM=rURQj_VQ&0Y)PQQ8e=;XOk(Hgu z%EF9L9VaLoec%vRXp=I0Qie~;@X6FQQ^rVM8Zl)we{V#!?*H(5J!JyP`y-}OscD3U zuARZZns^rZ;V#G`-Ozph()B@>4Y;)wR?)%*OMs zS;Qg`Mk|7U!sqh#Zo$3OkQ=c zL~p=%aq}6#1$6CeY`6aeBDc4;o5-^%Dilo0Xq|%w)z%!NCuM6K0z9D==_=e-b`_RL;4nq$fpV0A1Z#X{k<)Dm% zP=-g5piJmbpphkh)wdBtEoVY49%CkS^OMkxN0FeL14XJ9mRZiMceA|w=gabEb@@N5%l}zk{`2wLIn68PM4IEY?Oon7<}}Nje=6!` z=AVue&v+v4b0o7s8NctFTQC`zzd*YBoM+RFLc3VO**wa z<0*Cl8&B(~IqC-Hp_Wr{&8mX8O5^^-gSIN;R%P58Rc0X86y`ON){IoG`w8#5)}M;1 z(E8JHf}UpOT35!k>@qf-6ymxmqc&yKri|K@QD_=sl*A~BQA!zQG0I|; z#VDtYiWn6!Dq>XL;5z8zN<$xOHb3jV7L>9CuQN938rB#n=X5A%jJG=-8d~BGcV0`)V2*;U0CSp1%o!bGb0W=aqA@RgUiiG>$^Ghu=h7C7w)}jy=`ToTL3u8c z&g+Op(%JMEUx+eVd@)KaQ6_H26l5+-CJ42cOa{m0=lm-qZ}RuMF4G_E50>cPf1sz}H zv6M1OVwA)viBSgQ7SE&n{FQUdqx^!7FY;JP85J=qVpNt(+AuXNApF7GKE(o6Bl+uo z{)Ufz;QBv)-=BQ+Eg#YAMi4&F1mQEeTPVj&ZudkbE)%hII${~<2&g4UpXEf1`>kg= z2V>tb%Q=`&kNC*i?5jR$^Q#kHx6j$EFPXv3#Z$jIN@JdrOcLfO?R&{6J+C?Ad@ML0 zP5t=P+dQX!xax;X7g!=+BDwIgcXe+M)0>4?{EScac!zhvq#?NYk|Vmhp&(oRB4yN- zzUnk=1xVhaxnzRKV98|cXmmRo6USxY%fgqXf(LzBq!p1?L|P%qoxzT1g;tt=q3v&A zcCWq=OECI}B^dp~dO)Usv~>Y5cmsU>1u8X;zGU2Cnk=9@J$KkvW{*cFX!{}*8NE!8$CR43bf*!I&@X+Q< z>I+}i@f96kRo^JQB)lZNB)t5R`oWiVd_~7s)h`OK2(JjQIG*;3`OTLyo!sWj-pYj4ISB2yy_>tr^uHxBKh*maf!ZO>{F$(q|21{E+Sva zNatWrC=f=mo1n<8=t!cT&K8DDcayNAk&`(W46?)r{k`-hE+O6N{g7J(x*Lv#L;`uHk;rX5k=%3I!s1BHnssiAPuA?i zj=Def8N(KzIKmm^yJgQW;XZ@Gdcx11WI~g1cw0{?ZR>HTZPN9GpPqTz5ot%H9g^ZF z{KVW9b63n=GWCR?NP8mfiL_@VsJbV7U--W8ec}7U4}>2GKM;N({80Fz@I&E;!jFU> z2|p5kB>Y(TvG8Nz$HGs9p9nt@ej@x-_^I$y;itmSgr5mN6MiQAT==>0bK&R0FN9wR zzYu;Q{8IR(@Jr#Bjz_f$b4;#=Y+;V6f9XBVElh}NR$+5noF?*f>RXy7ZgEP-PIHSB zBHpmuj_KQ+fAW_7Hf2Pk3~$}-Q08^N&A&sLZ!tj`-qqUC(;~Zinq-$uitUog40pxc z6LU|`o$TqklRe@4!uN&m3*Q%hApAi1f$#(2hr$nq9|}JdekA-z_>u4<;m5*{g&zw) z7Jef9MEHsD6XB=APlcZfKNWr^{7m?n@H64(!q0`D3qKcrA^bx4h42gEm%=ZFUkbl; zTy+jt{#&?Ez16SUx1PUpVOQ;2FX;FpkJ~9@Ta0Znw#C>1<2KJ@2Q#Ki{=AxfW zcT>i$7`tNZIzyV{LT0Px>e;(bzR_U;FB$YYmPoY)9kb zXln9GA8J}wzwOHq(A5(5VkvN#FB7IUulk|2X92Bw^eltF_fq@zWZ$0b+mi?OQ^vj+ z`(o^iagZ_fS+K#W5$SPKr*T5y=wg2UQc0AHF`H~6&~v~sj*^vs`MF^?TE3py5>8_X*`sBei|z4pKcOWfv(Lv1TK z)UEnM-Ksy-{rn^0N5YSU9|=Deek}Z0__6R4;U~gRgr5jM6@DuGRQRdzGvQ~#&xD@| zKNo&3{9O3C@C)G=!Y_nh2)`75Dg09SrQ=c8@sc`^yy}sOvuP?WBeYvH}zinRBsY9>=*iWDU-ofA2GzHB{itb3! z9Vxo2^Se5~>*u51fJNNRHm@s8*Tb(xlN$~-8d-}M6aKJD$HiATCDctQWC=BTTFIC4 zKs7`3b|y`QpeNhCGKQXkwCkj(GmuexGHOr8?(6)%&hP8|fzBW3{DICNrqMf0qj#w2 z1sCl}HI&AtP1Sr|CATO2QM7|7+VuXS_jM>g7_rZ389z-AjSOL9g<6(SGo}o`qIjU& z`G>mQf2iC2hq~Q=DEvtHk?9M&XUZn}jzBZxY@lyjgg&@Mhu7!dry52yYSIBD__2tMFFgt-{-cw+U|(-X^?V zc)Rd+;qAgZgm(z<5Z)oYAiN;FAiN;FQ+TKFPT`%xi^7Y-i^7Y-yM%WM?-Jf6yjys; z@NVJV!h3}G2=5WzBfS1~#rqpNepAOkQ~ZTD2yYPHAiPm{qwq%Ijl!FRHwkYN-Xy$P zc(d?k;myKZgtrK95#A!aRd}oLR^hF}+l03XZxh}oyj^&^@OI(t!aIa_2=5TyA-o{G zAiN;FAiPs}r|?eUox+R4i^7Y-i^98vcM0zj-X*+Sc(?Fw;oZV}g!c&V5#A%b{#%Oo zpX>M+I{u~NFT6o`gYX97jlvs+HwteQ-Xy$9c$4rZ;myLEg*OXt7TzMfMR<$w7U8YJ zTZOj@Zx!AqyiItU@HXM?!rO(n3vUptZ1>ptZox(eXcM9(mUKCyw zUKCyw-X*+Cc$e@l;oZW!g?9_@7TzPgM|h9$9^v(WrFdVvmi(zEfBsrkK$M1r(r`_b zhHIiUiqe=+8WT!mLTM7EDWNnal%|BzEJ|}iX-+8338h7pmW0xhP+Ag7t0=7rr8S|n zCX_Z&+7e1zLTO7V?V_|Nl=g(uo=`eO=}0IY38f>U6htW`ltMx&B$Q53IulA~Lg`E> zMNx_grI=8P38hPvu7uK+P`VOIwH+tsAzUB#T6dx0F{53zxN0P@$x4oTsMuT)N zu8oU6VRV2KWOB7`f=n*1O^7+kjJXClNjmeIl(LgzPAShRlK3>qRIKcjnA6OYA2pg* zo<4n_j+LDjeTH=2gqtCqn{{SLXHhd>kNncBk*ZfCRj)>>UX4_J!uy2x3GWl$FT7uP zzwmzH1HuP{4+tL+J}7)p_@MAX;X}fQgbxWH5>UX4_J!uy2x3GWl$FT7uPzwmzH1HuP{4+tL+J}7)p z_@MAX;X}fQgbxWH52 z#uLhTLYWX{BB4wqlnGM8GSU=Iy!|`z2!Y?>oCYsW{q>u^4ZC*ylpg0rj)5?9t(p)p zp3Pi~=(eFEEFVoup~*y{$wZ;aMCU0{rV`3jLP=Eo0jen8bizw?795l?l|-JA+T{Fa z)lM|V-qD4xV?_^la)S-C90~cN+gXl;+>APFBOywgk zbexpaT|;9Gb|UP;cSyx@$**L)j>cF26y6Uk2GWS`%n6kDWXpN3-C31fnGqzpIGEPc&0 zh&R%FJ$q(Zg3Bg|43g1sgawRFSGSJQuA zOaH+mwVP;GBOgJDH74Xt&6$AGPQw*%tT#Ox#Wh+2E&=zTJxH=(tK6N zuj%-8ZLJYr5?&Hs5?+2yTWP+kik7nh8Lg=b&yhenprMikMZmYvl064JQ`u1vaiqljLP=@rs>(-8VvM!dBErCqrS zVs4s*aQ`i8-|4qJTy5Cq8r!i}S>9fQBfSjQ6Qr_{l5avtPcmIkFkD4DlF2VYz_0F= zpr!SRuUG5v=`U~>zLEa>X8P~X_%B#B`?^;fPL^hMu{f)X#aUe}&XL4v&74ScBF%|3 zPZE39`ENwo&WkxO<^q{NVU7J>?{OALr|iNvU6xa|g+EI(3N>i)b+`N#zj?9v4Nq|F zTl}Ucw5R&dkbozaNax#pmPqHDC6-9nb5No$lg_sXEt9SU>D*?xEc%MkQOe3Uu8jK8 zNS1aycp6Ikm;ilwG&8|lq=j@YRLy)f9Qry9s#^PIx3EMR`V0D+47+LQYl*0vUjAk_ zOK5IcaZ(HQO@JPQ4VLhchkL+QC>?Dhgk2_W`i6+C$_cCHE*P~+PcbN~=80+@{l7-< z5?P~ng;0i(S+i1*(K_ke`?)S(tjl%l%$RS0+8~o|*kHyO?vTfZ=o_Tl-q;WpXl^E_ z;|z4C6B3h?p%Ny!MzAKaAA^a43PT)RW0dGxw*k*Q`7#3xF)@A5*XT(E+?tf8HUeur zLl_cDgCV|I42IwsyuT^$Z_;WyYg77e%9ACLN-?Ps+Nd@Yxzq`{OZD2-a%tT}$mr`Za1nHIomZvvs&rnJO;F15HQ{T**My^J$Jd3g3ttxw9UR{fz9D=gR1v8XTm4^QR(Y*jt^4;Ln%Qe;W#8>z^_au#__xpob90zL|Bj(= zz7BKsHyQt0;6s%B8qsTsXw^iS$9vK|-i$o{DrAA&k}Uql-2N)b?An#zW@esi=rJs_ zpS14c|B(^bl0^fLKoSPnnVg48&^JjXmmhP;=SMw@xoc4tenG)V!YM(BO$Y7?r+8s7 z&wLVn=Htvb9?i%3u*J;B`RHkpRG)>A1yo-M(w>SGs9hB?$NUdjpSbO*8MU53%AD|h zStr=}nnJBo*fI^Dtu%uv6WI!x8Z-F?MVRS=e9Cem75rN)Ol1^xRNLiHZI?r}T}i6# zO19cAhidDhtaCM@sf*2H+R#@&l?;a?zM0KuIo}8 zu@JOw)$y$!`E{$wKcT;%^6v)9_&@#RM(j#WHs0|2D382U=I=><~Ca?_+MrwWe`)(>qQ^-5!S>eb4GBi zshyUC;JOUI`}(&UXV2h5mLc5?p-9HC%}{6(zs*o`(p8kwEJ`5@s8tHmo=Sb2WM57N z%h3C0teuo=WXhp1U=`4R%41lOnQvE$w*gqwD++<7LicE9GX9a7$TPCgQ>l(fnO#5R zCHx)jf{dBOa$r&tu;eMSzs)!n_Wv?=-%*kr`GMc3OlJ9^-bu$Jk9Xo4*u^fq*@b=Y zo}SK;I+8NvNvEUS9l5*k7+_!qGXu;F1_QI+(e2VoXCxB97yvUc-BsOHUG2U1-h1C& z)%r#JzFAdWSf2FHWJP>_5g8E~X)CA8>zq#Szme02*)89qt2)0-syIPS=XB1LbWUy( z%#p(W^Udfln7!WfR3DXQj*R1a`zo(DaZ#7RQ z_3NGY^cnk2wmI+V!}~95LZ7iqtgQK>7QERMWUy8uKQj-oE^3+EHB8IgSq!^WQt(mz z9~u%nK`d_e&aUUM`VCfla*6dp7U$9?tu`+VVbcpnQOTfPa90fPaX8h<}KG zh+oC8;#cvj_%-|*eht5de}sR8e}sR8e~f>Oe~f>Oe}aF4e}aF4e~N#Ke~N!AU$^$w4p-qFWT`qb(jebA^+t==W_U45>lf1LDP zp5DD%`bhFUeXhlf)o^C4)~*jH-_tVsB;?{clU2iSv>W|m>9eqlJT<$dwaa|+zNovT zW$Y}}*>Xt>YSou0cv%bD*{ETcwV;Mw=3(At!msfD$`!tCxPo87-^bs_-^bs_Kfpi0 zKfpi0Kg2)8Kg2)8ui{tntN2y?8h#DGhF`-!!au@4!au@4#y`eC#y`eC!9T%2!9T%2 z#XrSA#Xq%PG9%@gC9_W+p_u>oTr&H31o93~H{Rjt#ydO$c^7{de;0oje-D2Te-D2T zzldMNFX9*ROZX-H5`GE4j9%HG2>%%W82=dm82<$S1pfs81pgHO6#o?e6n}>| zFW!+0OZzPB9l7k{1FsstOUS!k$h%(1yK=>8W8Wj>Jul=vFXTNhn^+K)@a?K05=7n7ILOvqoBQNA5FXSUHo~p2l$92*EoIFnF=5YRvicIy zGS{q_hUg!kOYy02a6DR3@f#(73`zdRM@40MM)GimQ=xV1e;A}a`akDrPjhK|5G~U} zd-PkVWTpL<;w}4SPcmr}Z`j`$NTxI*RpI^V9vPRE2{%gzme< z=`)$M7DTk5ejl8s;~5Rtr@I*)Cv8cF-e<~sUsLF5w(zM|K3zR@H(8_g>dI=gUU@-5 zI#ZL>pMS40^?J7j*$N%%EJL47${$md;A~3%DxxfFD_B-W+JD*mo42*eNWUcXFD}-m z^sge;Rmf8RDq`NL(-rHVXR6Z;uYZNLE@i)_F8NDm^)#Vg%gEnW@-F=AlZRsSuKuZ| zdaXjANHtKpq4H3yG`&Gf6Q*Un@4|%}E6NKrY86_bQA?K=&~ekh@YJNGO{voGl!mi2Rh8^5Ra)kOV#BHm z1-)IRN=xewQLUv(l$6$|m(^NY8&;jMJ<!m1zyvhNTPP-X_RUCR17_W6H=F zWD4Pus3EeZG_po(*WJ3NG(1~XzJ;<{uw)Bmwe){&hDQ67HC4#UYPGcXt5yr@cBxI< zvDN<6>bU7)sZPsCTf7-irv+{OI-GS{S`v9z|4dn3ap;SO(#C4}gQRxyHm1Gber(Km zpPEGfNT+sMe?ze;W0r-pic_ymDcL>h&D55T)u&m~(FR7f0dFwwP0a`v zNU|VFB#ER7l0s5eiX&E0W82K$p0%fP`*PgTirU{QwAp-FaI~WCaE4P^DC^DYO0A&P zjLp%?@(L=;D@d9OWaA_Y>&?4cAyYP4UN%`?HdRxyucxv$q3cbRmrWfmTO91F!thiv zJXH)_bwR3;Y9w8dG?GR#1<4?pvcay>Hrrj|XjQg2oaWay=k$-pAFV2He%0aT%lTi& zUC+zaI(GW!9jnXBR+pDeGvw*QkjrSN%ZsJUi)D0``Z8{&bSr1d%VrLjEsl0gVSH*B zpBhFkEAq#7f6CV8`PHjzoqj4#)f$>!leJUU&QeFJGEcH~ZqEV~TChPl3SyM)4 zTDF#DovlOPLa!~IO0_zb^w3sYTQbRN>ukcl7TM}_)M7wMy$bvdk3`!Gi)+p8f%J&FEe+&brpa5pix(;f48Vn>)?>7LvIh6N1IA2 zGB+Pbo3h24HKX=$h9liI_Z#Lmpg4vV^&Bi4ST?e3@_uBul88zoDv78pL?nqw5|Jb# zS%^pxks=~RL`ow5ve=%G94qUKJGUM@8xChU6^(YT>U+%md$Q096CUup!nTxKYsq(U^^0LxZQ`R0~os^a}+q1X{su-Lq z2B(U_scO=H!Ei-+we?9VORny@O zbF{j=e05p*qt%V2MeLE=d=qxGy1c4%c~$AMsvPMgP2rDf9Zi>4k}0nwQ(lRTM6o}a zL*?~X%!(VShC!@h5NjC38U{IAUpW3Y6h1W;KFK^cze!jaY)9%dtJa%ssnjI&iyx`k zB=pW(sxL~m-cDF2+rYBXPJTU|Wpy??wFK3gAbW==?^@b>^jZzq|GitQGf@+D^(9B^ zx`yJ>nj1bXsQ-_*P79huadaA`_kUt{swVkl_Gr0#l5ybpE91Z?83*&JI1cr?YB{=_ zYfW9X9Ob<~=3C!bY>ckIUe|BG(ONV2minKn8nhk`%36QA$_nu1bWtKO!| zb*%R|pjB6a&41%Cwb~(J9iVWpop@X&HS$&`z25LK#_UyN28Cr*EFOYZ3U+8gy%XxtGWryu(>yrvZj3v1Wx8i{nle(h(}c^iOCPDog^IV8yY%sn zUb=MY;~jmksf#k*T1Kx(x;0$qY`2E%?OnG%LDI54+9GX4kCxFn)1zgywjM20GJ$$| z2GXm&(5sJL?YVxf+pA@yHu(t@J9qkYWy*b85YdA2-LCY#FJ-<^EAF^M^1w-d$YMwy zdGaY)_#}@!5C5u1zph3q^LC2TLES#Nvw7P7hg zwzv84ZLc&-E71L=rEtsa-3GRlSI|;kK`XcDty)%ZgIddrwU!ra)Ae#ZC~b3FdD*tZ zWs8H|UKpNshNqo@>nKPE(t&h}=$%fdJmB~XJDEGNF3Hiy8(n3?+^(&a|48fY>g|Q0 zlIxE4@+P+*Zn7M2v;zJAwH+m89ciFB-sn7&R+>v|{TYx>Mz&MK^=WOVhU;aZG~9%L zXkQNLDjm@-Eu*`9mzL4{HR+mM))xn{yU^)whM}jB(?d>gA*Yv|zCuo4*-&-U>Tc5l zIZ||&u3T1Ozv1Z7Cw&~>i0motnylJJ_G+Zv7xi2I-s+Nzj9z`LWskw*c4JBlZNWY* zsPCopX&HMW*IN2Yo`-2c>Bymh=`VD#pH2@Hat6p5EaVK5GgQbKDjS&oDh_PAY4jb0 z{z9MG;~ZI;ZU2A?_g>r_C@nKkI-=4N8$PI8-@dn?Cx<~TbBj4r>AOCIr3IzIw%`!k zYe>WGfz7;Xn|Fo^Z8M3o(P`#hxS4z5X6}WXd7{vQx8N;!3*L&i;;ncq-iEi~ZFt+^ zd+npm<@c}6<@c}6<@c}BM6=iE-A7A#`IhqXE#>8<3&rxS<>gz;%eR)7mo65|x0RP~ zD=Y6v&+SfbXSa0UB(5Dg>9jLe?Tl4BW7W=BcHkX&2i}2q;+=RW-ideNU3eGXRkkhK z5B0_S{pR*V9m>1r_nYO)O?y>gdnFAuz4|9sq_rlY$3SUvNkYq*&dU*1%luRGI^<3E zzC^lVVz1g-q%S6MO%q!1s@WCvFDFaa?Ch7W>C%KttzV_4Ce)`(aSPb9;_y9hD58{LPAU=o>;Y0WkK2$cA(vD*Dr7^|kON)xlmuA`C zO0!I6!j6Zu%_ROaO=$S<+QE@l8qq(xD^0a!rK!3!k+LbIsdgz%)g>-X)pXgAHS_48 znMViBJUVFR(LoE|g16u;cq`tDx8kjM8{USu;ceF04dlOGNIOjDR%}nDF}AVN7+p5$ zQW|5^(imOh(ioeT#^@54#%LOs#+a4pQ9xQmLVs*3??~H9*p$)O;?CMmZ`$ciJH2VA zw;gx~-hp@Eop>kSiFe{%co*J+wl=j+!(q5Cf=>1xxwA-ZT?f=k|#@iriyeR+ zX_f>@4>YY$M$DnPIGo*dx0~*E)7@@{qX+N7d*oToU)iH*56`UfPA@UN#PmwcPi;&e z(uedR#YrdYH{IIux+(22X*tYE52P{X8k&SQzfxCDAEj9)TrZn@G+f^??a^?3X;fNh z!hX|?`M2%HlD3*0ePr9K1@)@BR}1QYrIrquu-`HTJ41T7l+KtueMsD=MRhfOTGYH5 zS{&hi#-N`u=r4@U05X6K6l4$?LFr*ViOb{_a#6%%t zQXV1c@6=3I7Ct2lpCs6#rwE#^kRPm+8m24trK@QZ zWYYKy?^VrI+E=L_v}>42>QBP?QOGBy$841(h&JSe74l6)?@GF^*5bIsb@^<(eWSC(%O(Q zEu-g~F?~r!e{5-7%jnRLYZ-kmHLhj!kUy?v^#9UK@Q`3a!|nft&}Rx0T1Joi6B*rb zlY~!dxK=os1__$dc4!NyG*YXXB65nzX^qrsrmMA@X@aH+n$aNruO%~Do&ML786szh zoTYWM8l)v=wIBLNnP#=`T6~VR+yA^YXUfPFozrlA9b-UHOw8N7iy9%iLd zlh6aO)NB&^s)AHslo8%I8PS&+^bzPtt-gCYQm2Q(QG!N!VPdpa-&Gye_Z;*XI#&8J z%2-xw7^~G9#`LWUXIz8WPqJFgIInAr6EvYgdQ_Uo>TiHg)ar44Lf5I6mXjK(yU?U| zLF=BR&Pff|!|POy&g?0T)M8Wm=7w&kDGk>;rU{?UvP0Ce)F3TBL(mLCGX%}lY49vQ zi_hY-_?&h@b_?%&nmKJ9`v&22gwLCBnGy3Eq?2%-pm~B8v`+2Pf=23&zd+;yk&Co$ zk=8BZi}(`0gfHPs)=N$YTnrC$F+5zjOdhE(Ox?oQ?IWT;lZjop%5#SGvX`l=*Qt7i zFszroIvT@V(T?!d`3T?hj%bh`A4c@rUk@~+M2-?U+87x%XY$b|olax;n11`Kzwkb$ z)#>qftn~I}T)#2aFM`IYb6mr9w;wOwdE}i5YM&r-LL+snP0*Q1f+n>ITFsQ^tLc{-_CQP_jc8w;iX}FeM({Q~KT+{HsZ_BO|zD|qR zY4JKu*uXdN4SWON#5eIxd=uZox9}}|3*W}K@ojt?-@$kA9efAh#dq;td>7xt_wYS@ z58uc4@qK(B&*ccn<%r1T2+8HVn7kL0_hRy1Ox}w*@L~?Um;*27fS6@Hv+G%CS3uiI=+st3uiI=+st3f*P zOW=xTxw4nPvti#1sA#c?TQt$CXN`(BT~=!34Hhd~$Rwzepri(A4M~EM1SJVdX^`F* znr7D6%aD`_^Paw>Y;3V#Rn7Xcb5#q=R+iGEpW5QpL{t+|O+8r}}u25CW;S8s$wd5&fm3IATb$PMs@?z3#^Pp0{n5d>$y1ZEW zP_ew*Z=c*thfG<0q@1Q~=1^I_r$3r0t6;!ZApI?_JX5v_Y8adv2B(I>sVNL!Rwkc5 zFv!YQ<&#W4K1tl+`!A`{tWNL0q*{~E7pA0clhD6zQ)3$9y~vo=$#bjS=2@MvcWR>5 zKHSrXG1)eKE2mb&?d!ny(VISNsBM8w8M!g5CA?0> zJDUo7T~lGNYbxw@P15nQx0;)6rTToYUe};cmg=<=`u`8>&1$@Ng&MSJ_93D^M(r|=Y>!mIEq zyb7Z z+nKH!q$O%ikXNFX$T}kHIi&1ldv`uw3$Cyt$l`m$Ohv&Y6Z zYC+w@8?~U`95rb{=AtPhRX1t4R^6oGdWTfet<$Zdhh;CzK3!Jgm3Spyi6`+Sp2U-Q z3QyrFJcU=`Rd^L%g;(R%cr{*)r|~qN#?yEP&)^w6gV*3Scnw~IXYnka#j|)VUW?b_ zwRjy~hu7hCczw4w8)Y-q6I4%7JwXi`q+6_kpay~(2x??ajd&y8h&O2^`U`taJteDc zBC?6dihkz60Lwv^L(B=h60gK7@g$zalXwzO;VC?Yr|>Gg3a`Se@M^poug0tKG@i!O zcpA^(89ak$@EW`Zufc2ZES|-)cowh4Yw=pV7O%tW@H)H>ugB~0db}QQz#H%eya8{- z8}UZG5pTkq@Fu*;dhs~e%wd~7TL74$W;0} z_7YHY5z)0=jB(@t;N>1_wzfp_2?cqiV8cjBFRmt0xt`$k=b zOUf>}veHY+F1fNYpNgX)J>N*%O+qhYr2{6R-+)Okiqh3=e=SL$^>($`g!~sKS#OuQ zk}^xZh%zUv;s|%k2Km-=k$TthZT@fxX<(^>RnotM?dAAKr)e;eB{N-jDa={rCVrfDhmU_#i%r z58{LP5I%$t;X~GoW7*uU`*5>d;pmvQkkeAgX(gw%kkdv^8#%?_$QBbROIg1O(wn$ez5LP5*xIQtm$Yh~`Z9tvu2^TA zhU=>zZJqKRrg!Jk)@A>ok9qN+I2`SCti90H4stpQIi2Km7IM1eLXq81@(xc(sj=Aj zc5RK`<+SUl=v_{`_CUWMm30<-(4l2yMtkp-bZ9}D*7BFM?F~)`BPC5U1@#-dPA#Z& zyi?2QwC>cE>3-3r7m7N^yR?kX@h+~lyEI&%z;|8iT zi}&KacrV_E_u+kbAKs7mbI(`eYTk1QM75e zzADp3c$*2AVr?3!hr)K+(XQ>#FHYOFVr@^mhHE!E2=CBvt)oN3b*6M^xXzSLZHI<+ zY8iy_=%j;P8mVjP;u5uspe};CHArjdCa9aBZi0Ha&h5c_@E*Ju@5OuZUc3+Q!~5_) zydUq!`|*B!03W~y@Bw@fAH)aoL3{`w!iVr7yqT-BX0FbfxjJj+>a7KD!CUYaytTh% zezg+RN>D37Z5pH_-9}IwL2U%Jv!-^u9dE}w@D98K@4!3pPP`NE#Jlh=ybJHbyYX(k z8}G(@@E*Je@4_3-}_wh%e%c_!7Q^FX2o0FsG?uPE*62riM99jo>5r z2tI<3;-mN|K8lawWB3?8hL7Xp_&7d}Pv8^y1U`XJ;* zJU)*v;0yQyzJM>{i})hGh%ezw_!7Q^FLQoa=KQeC`C*y!!wSBFuiz{AD!z)Z;;Z-? zzJ{;iYxp|8j<4hE_y)d#Z{QpFCccSp;+yyuzJ+h$TlhA3uiI=+st3uiI=+st+pKK9MeNJcXz5D!dA>!mIFVyc(~@tMN3R#?yEj&)^w6gJyaunqYw#?d#j|)8 zuf=QeTD%so!|U)mybiC&>+yQL9&f-K@CLjAZ^Rq%M!XSk!kh3Wya}(EW_!=DoMk!3 z_QxynO1u(J;z>M-C-D@X!c%w(ufnVFD!dA>#;fsayc$pAX*`Xm@eH29Gk6BC!E5ju zyavzWSv-qp@mjnVuf=QeI=l|A!|U*RydJN|>+yMeNJcXz5D!dA>!mIFVyc(~@tMN3R#?yEj&)^w6gJyaunqYw#?d#j|)8uf=Qe zTD%so!|U)mybiC&>+yQL9&hIO+RX8_nd56S$JZ9T1#iJy@K(GPZ^c{jHoOgQ!`tw7 zyd7`H+wl&(1Mk2)@J_rF@5DRtF1!ox!n^Qpyc_SvyYU{p2k*gq@Ls$Z@5OuZKD-a_ z!~5`lydUq!`|$yM03W~y@IibKAH)aoA$$lQ!iVr?j<3xeUz<6;HgkM!!CUYayajK? zTk%%B6>r1a@HV^+Z^zs5cDx<$z&r2`yaVsVJMm7u6Ys*i@GiUy@5a0FZoC`s!F%u? zya(^ad+}bp7w^OS@IJf`@5lS`e!L$azz6UFd;lNB2k}9C5Ff&a@F9E%A7*|GGe3rz zAH&R#5qtz6!AI~>d=wwWNAWRy3?IYC@Ns+`AIHb>348*dz$frYd=j6;C-EtK3ZKHK z@M(M+pT?*08GHtx!DsMUd={U@XYo0F4xhv4@OgY5pU3C%1$+Ttz!&gEd=X#77x5*0 z317mO@L}f1F!N)W`7zA=7{N#I5qtz6#Ygc`d=wwU$M7+H3?IkG@o{_{pTH;Z348*d z#3%7dd=j6+r|>C!3ZKTO@o9X9{2B6R$e$&Dmi$@r=g6NUe~$cl^5@B)Cx3zb1@afj zUnGB#{6+GY$X_CViTq`@?=stWneDsG_FchO@D+RoU&UAPReTj+!`JXNd<|d6*YS0H z9pAt=@C|$e-^4fZO?(sI!ng1(d<);kxAAR!8{ffq@Ev>y-^F+FU3?ec!}st#d=KBp z_wjvvAJ63o$mNL0MeNJcXz5D!dA>!mIFVyc(~@tMN3R#?yEj&)^w6gJ zyaunqYw#?d#j|)8uf=QeTD%so!|U)mybiC&>+yQL9+pKK9#}3O~mV0a;yb`a(EAb?r#FKauPvI#%g{SZ; zyb7P8n4Ey@id;s(|8)s;2AuFXYd-l2Cu+m|f z4zI`S@p`--uh?gMct6Kn;r$$Qh4+iim4sA!A(dW8r5BPUB9NSzl_=Y`aHA@zjRdm;5+NWF%b{~_Q%azy?n z^pTfzbNX2NSq4}JSvo9TmLZm5mJyawmNAxbmI;%Tu%i_u)R=hx>6q z?#KOj01w~+Jb(xBARfenxPv>mgFCp3ySR(HcnA;SAv}bK@h~37!*~Rb;1N86NAV~g z#iMu(kKr*qhR5+Z9>?Q&0#D!xJb@oM&GveQ<*O{uu>Ei!?!$e!ANS*a+>ZzF03N^t zcn}ZbK|F{%xPv>mgS)tkySR&o@DLutLwFbu<6%6ENAL(9!6SGSkK$20ipTI69>ZgJ z9FOC1JdP*u1fIYX_>r@0uX8M4V|kwKhx>3J?!*1KANS*aJb(xA03N`Dcn}ZbLEOO| z+`%2(#a-ORT|9(`@DLut!+01E<6%64NAL(9!J~K-kK$20hR5(29>e2!9FOC1Jb@?h z1fIZ;yw3Kz!14{2Z?gSxAMV3_xF7f9e%y}-@Bkjb19%V*;z2x!JGg^8xP!a6i@Ugs zhwu;{!b5l%5947xj7RVY9>F7c6p!LjJc`Hg7#_o8cpQ)8aXgME@C2T~6Znz0*j{h5 z{1(e^v;A-%?!$e!ANS*a+>ZzF03N^tcn}ZbK|F{%xPv>mgS)tkySR&o@DLutLwFbu z<6%6ENAL(9!6SGSkK$20ipTI69>ZgJ9FOC1JdP*u1fIYX_>u3hy}rxxdn~`t_QQR+ z5BK4I+>iTlKOVpXcmNOJK|F{D@gVNt4({L%?&2=);w~P-LwE=e;bAZgJ43FV)JdVflIG(^0cmhx0M}ENe`XS37vHUUH5BK3d+=u&dKkmo< zcmNOJ0X%>Q@gN?=gSdk`xPv>mi@UgsyLbo>;UPSPhw(5T#>037kKhqJf=BTv9>t@0 z43FV4Jch^dI3CC2cmhx02|R%x`3c+W4_N*o%RgfK;Xd4l`*1(*$Njh;58wejfCum( z9>jxq5O;6~cW?)HaTj-S7Z2efJcNhvFdoLkco>i15j=uN@F*U|qj(gL;W0dh$M85F z$K!Y$Pv8kWfhX`If6Vs!6P7<^`7^d3?!$e!5BKAK+>iV503N^tcmNOLK|F{DaR+yB z2X}B6cX1bY@em%uLwE=e<6%6Ehw%s=!6SGCkK$20ibwGn9>ZgJ43Fb+JdVfl1fIYX zcmhB2r);l3WBKPS|AOs@`*0ub!~M7)_v3y%fCumZ9>9Zm5D(%(+`%2(!5!SiUEIZ8 zJcNhv5FWzAco+}kVLXCI@CY8kqj(gL;!!+?$M6^)!{c}ykK=JXfhX_;p1_a%CEM$- zSpJ`~%)9y*q>ubxHlcqs+DD|1NS}$6|A+50L9+DYe%z1y@cF7c1drlTJc>v07#_o8cnpu@aXgO4@dTd0 z6L4>501x6pJctKz2X}A>cW@VX zaTj;-5FWxqcnA;UVLXh7@dzHlBX|Ul;!!+`NAVaQ!((_1kK=JXj>qu?p1>1$0zdM9 zv%UTwmVeFiZ`gjg5BK3d+>iTlKkml^cmNOJ0X&EY@gN?=9o)ek+`(Pk#a-ORLwE=e z;UPSXhw(5T#v^zHkKhqJibwG%9>rsL43FV4JdVflI3C9ncmhx03H-=AY_E4&zQ^(+ z+Yk5QKHP`&9X z1dremJc38@C?3V5cnpu>F+7IH@i-pG<9Gs3;0Zi|AGyT#y3Fzl%lFxSxDWT?KHQJ{ zaX;?I19$)r-~l{{2k{^t#2wth9o)fP+{Ino#Y1=q58)v^jEC_s9>ybh1dremJc>v0 zC?3UQcnpu>F+7gP@i-pG6LT+T7!TuNJc38?2p++scodJ~Q9Opn@E9J$ z<9Hm8<8eHJC-4NGz>j>y_WGFRCoDf@`{6#^hx>3p?#KPO9}nOGJb(xAARfenco27R z2X}A>cX1bYaTgEaAv}bK@Gu_6!+02v;1N86NAM^f#iMu>kKr*qhR5(Y9>?Q&98cg0 zJb@?hBNc3~N|s5MDYhT(!+p39_v3!tkNfce9>4>501x6pJctKz2X}A>cW@VXaTj;- z5FWxqcnA;UVLXh7@dzHlBX|Ul;!!+`NAVaQ!((_1kK=JXj>qu?p1>1$0zXp4_Nr!? zW|?98;Xd4l`*1(*$Njh;58wejfCum(9>jxq5O;6~cW?)HaTj-S7Z2efJcNhvFdoLk zco>i15j=uN@F*U|qj(gL;W0dh$M85F$K!Y$Pv8kWfhX`IHEgde%UYIoY(LzG`*0ub z$Njh;_u~OPfCumZ9>jxq5D(%G?%)pY;4bdsF7DzXJcNhv5FW6q?#KOj01w~+ zJb(xBARfenxPv>mgFCp3ySR(HcnA;SAv}bK@h~37!*~Rb;1N86NAV~g#iMu(kKr*q zhR5+Z9>?Q&0#D!xJb@o+W_z`;Y-QQT_QQR+5BK4I+>iTlKOVpXcmNOJK|F{D@gVNt z4({L%?&2=);w~P-LwE=e;bAZgJ43FV)JdVflIG(^0 zcmhx0N7~t59V|OpcCr0%AMV3_xF7f9e%y}-@Bkjb19%V*;z2x!JGg^8xP!a6i@Ugs zhwu;{!b5l%5947xj7RVY9>F7c6p!LjJc`Hg7#_o8cpQ)8aXgME@C2T~6ZnyCwpS0! zUY31qKir4=a3Ai+{kR|Z;{iN?2k-zM#DjPc58@8);12HKF7Dzk?&2Xlgop4D9>&9X z7!TtSJc38?2p+|wcodJ~F+7IH@E9J)<9Hm8;|V;0C-4M*q@V3Iz;ck~5Ze#;;Xd4l z`*A<+$NhK!58wejfCup)9>jyVgFCo`JGhIxxQn}Z2oK>QJcNhwFdoLkcm$8&5j=uN z@hBd}qj(ID;W0dh$MHBG$K!YcPv8kWfgc%WdyTLhWjV(7!+p39_u+orkNa^y9>4>5 z01x0nJctMJAnxD}?%)pY;x6vuE*`=|cnA;SVLXh7@h~32BX|Ul;88q^NAV~g!((_1 zkKu7Vj>qvhp1>1$0#D#a#@SvIEGOA+2 z-yz>6-zDEAKSX|r{1EwJ^26kZ$&Zj9AwNQXl>8|9QSxKt$HZy-l%`{RvpJjZ`0*tq#q;wIO)epKSBBl(od3plJrxgpCbJ<>8D9QL;4xg z$4DO|{VeHcNk2#WInu{TA1D1h>E}tmK>7vJFOq(d^h=~)B7K7N3DPfw{sjI6{sjK$-K_5(miMx}kM-k^ z;g8{u;g92w|e?{x=V^B>itm|69_(_n`Xk zJ;W0KJ^c6a-^YI+|9$-H_}B5T<6pdHi|&dHi|&1^fm41^fm4 zMf^qlMf^qlCHy7)CHy7)1bzZPfuF!%#$U!?#$U!y;wSNw_(}W}ehNQ@pTeKyc=061 zizhi=JjwCmDf}t?Df}t?Y5ZyYY5ZyY8T=Xi8T=Xi7=8>th9AS9#h=BW#h=BW!=J;S z!=J;Cd)5EFaM2Q}|Q(Q}|Q()A-Z))A-Z) zGx#(3Gx#(3G5i>Q3_pfHi$9A$i$9A$hd+luhd+lO$B*O3@#FaO`1APl`1AM+_zU<8 z_zU=p_>1_9_>1^U_)GXp_)GW+`~-diKY_oDzl^_(zl@*6PvR%>llUq86n+Xng+KWq z+xsDw53_uP?T7Fa(JV(y)2fcE~AM(l_m#AG^?zoL&Ev~NRSAyPJK4(9fwR}N8 zm8?Z`O4p+0%v!XZSo{&A^X@O(h~pam4>eIbDl+!Z%NXi7KmU-9^Yah0WJF#l zM7%)63q-s?#EXT97m0Y0h!=@?sSxoJ5ib$(QfYf+E1CBEecMuL&yNFn_g6LXa~k#L zFUrq)>9+b$wM=WXEz7&T_J;~>^YU(=CQ904=8^+!2=PkSL(ov26`yTrdH6{= z(CG1j*6NOITPqv>6vu;891l*F9yGM=dFM1Yzo)tReTDQZq+ccdD(N$%&yYS#`Yh>l zq|cFljr41z&yzk+`gPK;lfFRu0_oFSoSx?5^fVWzr@1(N1%Cy91%Cy96@L|f6@L{! zgP+0A;Ail&_*wiceilE6pTp1L=kV9?*YMZy*YNZBdHg(n9)BHw9e*8v9lwBIz%Sqz z@Y7t}o#x{1G#7WLxwv};e+7R9e+7RPe-(cfe-%H2pTW=IXYjN5S^O-17C(od!_VR8 z@YnFy@YnFy?$ev~^Z0rEJboU39e*8v9e*9afM38b;1}>WSiZ^fEtYSyJk7<^X)c~l zbMbVVi>FucSMXQxSMXQySMgWzSMf9W8T<@>20x3R#n0kr@pJe&{2YD`f9W@Hg-`@Hg-`@i*}|@i+0e@VD@{ z@VD@{@wf4}@wf4B;ori)g?|hGHu>Ks|Jyddcu{(~aGE;JiR+a@&MSJ_FjuO@vzIBL zr;pMCnnM9|(kWggp5`!qn#1_%(#yI+9R$5X&?}|Kda2f2e(0&LTlXNy<4 z8b5=d!O!4l@U!??{49PJKZl>g&*A6r*Ld3W8c&;E<7v}tJZ(CUpU2PR=keF^*YVf! z*YOMZ1^fbj0e=I31Aha51Ah~L6Mqwb6MqYT3x5lL3x6Ab8-E*r8~+ymE&N;fxA1S{ z-^RaQ|8@8aLZzl(nt{~rE5{CoKK@bBZ_ z$G?w%AO8XV1N;a05AYx2Kg55C{}BHX{v-TH_>b@(<3GlKjQ<$_3H}rOC-_hBKfwP0 z{{#FF@IS==5dTB`5Ai=D|BuN3Bl7>4{68lDkIDZN^8bYVKOz68j(1g7Zc_J|8L~e7vxj%knyLK{Zq>RDRurC`F}?KpOOFP+3IBrcJ3f0W_0Ij?sJtViqGjHdJTpFd=iLWvfk({`t^Y7$ za^5T0dk;i@ER|XNkH2W_V!xKBHuH$8!;7|C3Rl> zOyouCyhxoFsq>OXYHu!SW6#?5T`FztB`UsLx}h$cI=xMJS;HBY&u*wIr6sSFZsIGY zoA`>B)M^HQHFDf^Yv@-a&--L6*e^Vf=A9|>r%Ur?7wP@H#`k_9@~kP>`$b*$eNOZH zJ|B6>m9@g@j-m(bCKsv{X?IR zoUluL2=BZ>+t+!cwzungO?Ta(^>*E;?drZ>+u3~sY4Urn)A*k2HNFS$!F#XM{NC%e zTrb{>_g$y?eRv<)LXT661{A}^XP*mkpS ziMB6Aj+t~j>Gm&0PTKS>8rN}?mg~4#Th#F-9qEo+G_La|&F{Qf^E>g*TQuEu6HB}c z@4~zBZoC`s#(VG{ya(^Wd+}bp7w^UU@IJf`@5B4?e!L&=#|Q8Md;lN72k}9C5Ff;c z@F9E%AF^IDGj7uHmi5{3m-X2rfUM7Mub;CXyWOOI-7{}y`K8DUr87bLWgY~X$smV^ zFWH2viCan%12=gwvSL#pVq;|O_7S!8)-DGn%x^{dt2snSe{EXYQ!Fl>Hi={Qa4bF7 z2pd^E#ddsId)o08?P=$inMq&Knb}3U>nl1kUAJmnH~HOP(fICL)w^%g`g-slyysSp z>$y#ry?8I)i}&Jvcpu(}_gSa?vaj`i*&DjvujE}F+umEfPko|6J7Q9g?Z4Dx28GPq z@@`nTI2`?~wx8AZv)X>M+PpjaOJ+k35Hvv006_z{MgCU_nzTWK1Pu~2NYJ3xG;M>1 zz8sMwha54!!jYYzAybprEIDIIAw84bW{w-h-b-_f+gsXfyC`keWaPa|K@gWU+}(JyG+=oigS zLwV_>l(U_ba(3q%;j}yQdD36dgI%E>POGDwHl<#BT9$h4a_n=uo-t0FWB3?8{&{WJ zIQiq`PtcDE`Y}QNB<-1`J(IL&5})EUK1F+`sCNpV<}^M{d#3See1`VSkUvBIEbW=4 zJ+tJ`F^+Sk<0vPaVNQp`oDPRMjSX|U9KlEM5qtz6y-v5^C_Z{ax}9-phuxW^9mRPihlpW09qax+Qus7l z_%z0+;yfPbGHIOp#%+ChXX1M8_rwh)2YH#d`wtrI2&f^JO%3GK6ww)6< z>iA7wr|HS-^&~QB>(4urH|qMPuG9LauGjjeZqP&UH1$qX?=3LUcn@=u9_A!H z%t?Cq79E!neB@@*U(#_Jxy5V;nU^CmzEc6lIEL))-+1w zD3Q`9gL-}&(?}9Tj+LF*$8Rc|rCj1n*wK;MWnFHDZZSt)MoadVajjf;m~ko}rx_DB zd%M!am%P?Y+@ha~13t~w?(~;*z^8A~1HlYevol=H&ak0pxLTX#YIc^1FpJOPb4-Le zd=8((=ka-b9-n6-EpWBAaI@}s3;4n8IZ`H!1x9Kb%BY*5xEj)Hx zw{iBP{D>`I+-}31rKNFpZb>;^auy%KrI9)(aj8e=A};OJG%oG5=`mc|Yi6Qe(n_OC zrlpBIG#BJ#G*0)%>E8Hl+RX{_C&-^5f0F!3@+WVL{HA;%?@W-gGvJJ(-{LGd3(npa`5nJ>evbS(^5;w; zZ;f&Wn&w(l)@(Z?Yu4p$+W8q=R&MiU<+`*N(|KnWm-X8-ldRv~*2wzp1BW?W+F@^d zWc|g>_&w*+ce_{etWp@m9ZY*h}Pc zg`ixH^0^!p#MrJG^>I%$g;^6OdN!16}h z#@FrOZ4k6U&;~&p8uSAjv`Nq=L7N0^YS0gLGnk;f`#nvV$h`Y~O_*?RP`0$pn^v}| zc$CQH*+om}?#=UK|W7fEIZyVSW$oqPBmzK8FXb#Ys|+D^Y zw9a&^-(I{(6N`x*iPb(zBTY65Ex;Vr0(y$vl~L2-*_GkZ)$Gdfgbq()nz~O@_i5@r zUCHGL$mNL00f0O)8 z@;AxfB7ckgEt_9Fh^%wPBkMEsOV5)VWXj5lnVV$Fnu?iQWXfv1%#uB*@RV*-`ls_{ zQ=ZbXtBC5X*b+6fBJXU8@<}ebOkA<0+pKE4xT?IfV=}$nVy9Gg3EwTv+#_?Zm|1MD zte`mEWi2HMbLCXnEA;wacM(~?5uNd}ev@Fw&@v>nj3#yq9p9DivWsYdj>ldhd+#%& zuul*6i#^CYxg5E<+@b8emz_V9ec)vu6tfuynR#t@>NR!SU3wqaey3hjx8EhblWo*N zx`TA*9U9+xr(RoklHYZQmg~Ax%XN|8eY;+hcjMi7H{Nr*UcUC+q4o9PJ$Uc!T3_!S zEb(5v5AVbK@ILFs&E0qVXC4EalJ;?L-u(wh&3x>?!#>+xw2y83@3e`J?4@o0T{iKd zd2P7E9xVrMw>kP8c;Jr6|0Y$<=#%3+ZQ`nl{S{m9z+I8|&Hc@wu2CNg51Iz&-35J+ zTqtA02p_uLR`l!oY@hxO-5L4crP#-|Swp6idACv$#XUzFWABm%?$nW&wc1A_vO@jF z>rTDEm+I{n9xB#s@1mqqxr-`}T>tHMr_;M5>9*MeN!(!*=IOdTFuzj=*ml#N%4LPR zywis1O_i+GX3AQ1S;XR`Y`I&z&~lGAx{XVU)1Mk2)?$z=g_c59APQ3G8t*7%oU3TGJco*JIr^2T?6-;>E)v^Qkm&R(rHd}D;tM)jg(`Qi2 z*jiq-b7)Wt5=*m&OtbBj%eyz)2{c3phUmbMiOjqI&|=$z?9~tItmt@g06@3c|kYjdnn&a`QC?gR`out_4H9s-$Pno-^03|zDG2!|3Nzl@}$Fz z;zKr}9qWHsP68(L5nT?LnhIwKQ(fNuZ*-g=wuyhD2@{@of6eTF>_`vFpD%az6;7ASd^8U6Q7>MWX(2H)@(BMc1Tul68g2ijDd}iHh9H~ zr?>IjBmdeQ9>*EL@jImhdAH_2H`{C6Tx!U2f>E5fBl6oOc*0zW<=w0enqX5*5;S>- z4*BGrIwX^9imBTpzhMeb;Zt1gO_gnml9t$qg3<^(q_R?d;%ejN3x)Ff?=tHv4yp9a zbXPBsrBZJ|ihZA^#nX30{wLG2X|6A)bsEa$mhAp^`pw*K_q5!>-!;QBbB9gUnyG)U zJy6YX1v;Z8<$6oPX0_0*wuae~GPh|CQ(^WlyCcb@&%6JXJ=n~df>ObpmT9#6&fJ}L zrLCGUWxT$c2eMpONl)xkXKAmUX3|nS{iJ=Gf0vG-G|jrSN|#)RNn7kRmKNB2X@M^9 z)OzMhugA=TSnZ-7py%(1{7cj9d2OjQJMZd|ef}=ncKdv`&URoy3)X1D^iv8hXhA!! z?RJb7bUgLIw5Vm|$S$#qIyQPBT-1VkAY5e9F42c2y^_|0{E`{;ynEIRp0iXoiqac9 z)1)J|*U}MP-lYe1X|#RDzko}7txGHIC`$wF=t`FVIK9UvuBn)d zwY*E>J}aMC8RfEW)PzeLN4cyU)u12zlC1xS_C8^Z$T7XPo3q8o2p_Xofi&N|YjL+u zKxvYG=Xtjthh;T(VwO}^{8~gN*tm9K)>b}#Pvn0wZ5qGVp6~TWX`JiL2^}Ck#Z8z< zdsdV13EDKFV{@M!ut^Qqv)iP0;UC+XHF>YDW%52)TOCRTsmS$Ra$<^rg7Uq zacP^~ndc}!htJ(-u5pSx^~~MgPCavvcQ%^2*ZVXpsyDE*9{U_rhQ#ceWZvfm&J_X= ztMVLLI*X?u}0U)ZX7mmJ+<^32i8Stt}O8ECr_JJ8;oSd=h* zQYoWsHoDd)4K7J&c}&WHM@Mw`Z2TD>?GjnPzh@ zHp*Swm{##)Jps|8vHzF3w+^>0InO(#*ymVRV;?6ovg5>wtt2ueu`{tejs+I9TGZV4 zFf+qG%%o;!W@cuAA>(916F)Q0%=|U*JeI}W%y;*^^}W9Zd*9pbRw66?JY`>N`# zs&7@*s%*SA zzV#8UlDhiBg}(JsO<2{&f3Ha4_G(nja%xH(tf>KBTR!BtbjBZeN*vNUi74K6OKZ!AZ2#?44@370UT8cj8fdw|xABwjc>G$^df^fN&WB^l zDeWu1^N~o=CL=sVxxeE(aUXwv*F~I|iU?Qw4X-=)JZ$WvK70DEb9?#w-iN|LJ;Qy^ zdAwWy-bZ{+^u3R!u;^j6FnT~Oj61z=GjP$AB<{oR0m+V@4}D7iF81HWUNy(Wq)Kac zINzuI@B0d{nC~Oz`yoa%t({2@U2T*Xs`ww++?nxoO@uL{TqGzPsQ|^cD%E}i$sC?>)(lo-kz+ZkW z26iii{pH6avh2tw?3G{T^y8~OuGkFt)sMU5zxoNbj+{^Ym5&*IEr$QEI4Ak5bWT!j z{Da8(H7fqv$2>cI&1Vt68atA&eIgbY_vY7qtP=g}o0+d$!u!JZ>(2PgjyPkii~k@} zzTw=ZmFpWmnNd;I#whfcZ20l;zQ5~;%kk~ZH);7dow2m&ev@;ZZ+?O+5zBAcvQ+7} zK3>0Ee#=$FbQZH*dR|ZrpehZQ{nj6)o-GFK+f?yw_kpjeZ@UkCO?^9wsWy7Q`<>Ly z*-7troX1z&cRn7sC}*a^8@`hcf8yx2+UU9PyMGke2g2X|n6JO@raUtL?k57iH%9V% z$x?~m!{~b$ea}WeA4cDIalS41ezMg4XLR-VG5@~JKNyKWu(@v-eqeJQgQ=DuVEzNQ z(l=8-Oq~+9Ow~r;HT=*`jXmwjk@1J_qeyX4KlCY?@2!61BAok2A3N~p>E*p2IgbnY zk!$>Xl=d{Yqfe(BSXn;(L0_q^mh`$1no z&;I^_*3_BLas2Vz?;SWP;ve+E&2zt>!op2de_ZFPKV~r1A2Xuri*(f&2Q?};)}rS) zs8KmudkVLDp8B5W0OK2N?E{^fy|6n_E!0(xHfeQ()bB>#&#XGnH0!e6Al zFTr0T|4ZihJ-G9e)R%rQ|0L=s zsV{Tj@NzmH$P@J_%2{5Pq4#aCVE78>2(NHX@Ct%nO+4F~z3S3zg|v0bV!ZxdEuc$GI1G)4%OIHW#b|bRSTPHjm;Z{%^PKbhu$d6 z3O9I~{r=PJL!V|R{WNzI&v0A&Oxnq3JELc~eR$>%V+Q^8nDn3J7Vp`#XU~o!pQX5G z(}6|ua_-Zf^Je)2aqT~c(Q{E};TqBwi@dbi)eqMecMH-ME+kpL6woe3u>^)9RbCPk zb`s-|wwH9&+%!I|-MV`{dMVq)M_0vtK%U}#yAOJw$#Y4cbseTJD_05&PYRh?k?J_S zFH3n&<=Ri^M7wcGe9BOJUiCb;*Uxi~^!&f_a6C^>z3@MIbMb<8oDehk3&}{wHjezE zZyR33A6{fkUQBJOHXf%bfG0OEGSn~O4=-^K_Y$ptDe5dnS=yp^q|G7sg3=bFD{YY; z+R}u?z2FOf=+TuTPZZ=2MQK8!ZPF3mAstbUbnGWrtkI!~<$3k1@AFGatjJ?v_(E=0 z5@dm8vlnINOISS>xJFl2mBa-siHmcG8gbg$zswEw%XIt8bmq%6@)gQ?g-hWT_$%;N zNq?2}SL0ly+VI*Rc(#4*UwPGdEjqmz9Mv10r+PzYsouCWRd3AJsy7CqRBznbsNM~^ zhhNH7{IwIn) zC7xYa>Xj7nEH6c+Nc3mXKd%GkpD=PN1l{^j+lm61vDp~@(c_m9la zDPeqmDJ;gPmd4a`sv)cDjhd?7$fUec*T=8B5A5f!>r{+S_G|aXp+jXOH8SfaH7v_; zs9Nq!H8i^v4!u#6`3(TwDK~$b+pnj&Eqa=pou@f*c!raWXFlxt>zR*uR(d8KYUnp> zyb$@I#QWT5Dd|~CdX|!&^Np*|JD$rABh+or`5eN>70-ovG2SJC;olMz*MS7ZWc4g( z2G8mT9e5M{LnhBDCHX?((ocUV>$|5voPAPx;f~KJ#l8P*ZuP9vYL673_x*F+uKVSq z=iL(Bv8W}_yCps~dESR9Zq^G_^McLew&mwzEq=jyeB1Sc^SFQ)>H8N`-|MC!%wM#* zZ<}7Ux%0k66)!OWFEIcw*(fP2y7hTFN3%P(tx^_uLoeh+^+(sJ{^|%;lvs1*| zs#kJvPYOK%YIkAqDz0B`cr_m(N%PE|IwreXUK38kTMWYM?x*)fKfUgL^0!hB9dhhF zfeuw2ONK=szE1mJ|FCCul@`mB3QE(p*Q`T_usU>zZaQ=byHrQXu$mcGGs9|TsOn3G zZ)ApVWQK2KhHn&xMej>z>I8eX#756ZY|(|Ut7o$*T!ZgTV-j5COR6JdQtc1bd}Izu zX~|S_Q_OF_9(toNdm|s9LsFXA6yx!bK zXKd-a^=Fc$Cb(yOgRb{r6nPfQXKm>Up7s6wv9`!xl1bA%=Fj=y$`7@lOXdlFBCRWh z8$7F@BJ!p2tbU4!Px>h$K7Atlq(Z|zRH#FK(p2RYyBV!>&r|vHsdCl#ybp_f{Pn!g ziQ=TnbHoci($b-kY+vwE6BD^J#tD@lM8DwNK7@JkkNg#~7oCTR+wQS(#MD=qZCiYm3l(Tqh(U=kQPdG#3j`s`~|pVMYPAp{qlYEcAw4XW5tmAbaA|WKE^QH)EQfH(inwH@iOiRVWR)Vx zGAGI0kz!P(E%my`Ljp??4}k|-QWN2AdyOm0h(?_=9Sv&wRE*N=sk79Huglt}n6JB& z+{dq{PRdR~4;^yPx-$)tGo={S>P-t(8=dkxoux`0r?XO3 zk)T?#516FljZ`upGMj2_%ghS&zf;=NOky6MlveJ^~+wkHSacqwq2K7<>#q1|Nry z!^h#{@CoB3qAAyg+N8lsy zQTQl)6g~*b!KdI;@M-uod>TFt zpMlT7XW%pNS@B5Q&Cl==_y~LiJ_;X&kHSacWAHKf7<>#q4j+e)!^hzh@CoHSUKG?n zj#xS>`}ZqLpUVEp!_v{&r{zy(bXj)(mn_Sl^6qH)=oA*+kV^7DM@c35Uo)hV{3(@W zno9ad+ACHYj#!d5G7F#dk6tevMffSgqa7~77vYQWMfeiDtVlVt-`HMCW?7_G*FVX0 zrGGU+7U)GHSUd-qud)S^vX)~ivB0eC!M|uUxly2*WhdLHTW8Q9lj1< zhp*T5dQ#%vEGhYh-n?k|i7Kf!F8|8UULSX`p(yLVBg)bcCRy%3$tpTCda2qt5$DD~ zQGsgk5xYuXD=p3l|Cau>i+t1nrf4Jg@=c}r-{)-7`&)U+7Af0#%J$I*)W7(`&$vW; zb}I4lbV)R%5+C)aqYpfgP8?E+{)a#+@rsm^N_2`!yyD%}#_2-n&(;_FYvGuwSZ4{L z&Qe!)+_}`0U0kK^9dkJ1#u0G` zIud=Pvyw22j?yi(BhEv_c^nC`S)S;>EDxQCS&@e}$hCLm9d7ats(160U1uo$vjcf< z%3ya=hVaRh;VW`Zrs`ce^b>Mp>X28~WC>SGSzHKpr12s{`o~0!+YY|4Q}}}Z+YfLT zxLX5NxyODFvpvlAl9>i)k7n+}_u>2S{orWPLj5LM+-Egf@(!>d&t$1L_>V)W!qSu& z!mvhBOj?j^|5J|7TMx%LJktAB* z&1jIP{Cb=X)^ty1_BYZ;*>zIWQE4lYUk@Ak^{|m&4;%UQut~>n((#*g{3Zjl1>b^i z!MEVs@NM`ue7ihwX(J*%-hL!KtfA7wC+YDg>50i+dK`Y1P!^A+$^Qq@<&qUxvK(Tz zRcj`7fu+tNX4_>mygY4dBoaY?oyeHu|3*74?T74To?15SWkgapXOqrG>T5@e35>jH zQB^_COT>AJIB#k-dDDi2L5*xFJA)b(DIQLf?=f69dkmLN#AUPLa9h|`I1dr$A>usj zB+7Zh!}1MBIZybS32jIl4%)Djs*>g%+OU%AyBBuL{j=+`A=Z;3l%4h_xRuHd zw^G>-%Wjpb@D4?va(CLzeSTNQrEX9Am$xU`9*SflmQ2KwOJ&QIk`}v8trpVIQf=5n z!yetXN4M?KZTs+j_&$6;c;SAjzN~NaPx<_lwf!elU)rE}IN64HIElD#h`4XqpwO1+ z%H$HXWtX-du`jX-_s8lG4ervh*}dJ;aao5fE6p3c<>Qi9(tT+=^ZCqX>6rYJthL|B zqbkM&zWjN+^zt!;$11F{@vSLY`^;3b2R)U{CzV{@&ZuOABbKb5swp&WXFjiehSC>~ zAv~7wIERbyMff6o5x$go)|{nelt+?fCTA&`XAx;D?O={6M~*E=j>{u-uLff!@A#GI zcx_sYMU4e<)2wu0SC zdDIG7F_hkBt|rT@V^(eM|G8V}D~-G)Ci8UXj?QYM|6OSzHY^t7i(oZ+tBoXmOG)dAQX?K+S(r)g8ySWSQ+UH7sNY1nYI6pE&PgzL!tP zd$esYnQ46Xu-MNn_L+r2fhdhGLvl zW(-jZ>C|FiTz6>8X41pQ=4}W}yQ|}~S#{}z{L_iqr{&{a%JK<>Co1%ORt#<`!b6&h z;FF5*r^Aczgh_sl$wK~oZ886p$}G*dsbF?oDwt0){$hNK{O~G5W%3PbNjoi`pDpnG zY=P%z3;9Df?PKC#ZV}5xEEi9V$8f(L&q)?-{#)r=us$?jIzBL>PAEs5@dL>=(=4nZ zmEzJC?1XaU#B#)yeO}uP8j`kPjL8aPvQmtRpgP6Ayo%W>W~<3eRj-~Do%2`mt50hf ztzoo=(VC6E7DnqBtz)$A3ccxDw`Hoc@Q62&=!dMYmK<<{25;aG8)XOF1a1O1Yhdo3TbONOwuR$u**Uy?Zez5K z(Kbfgg;C*ip(^W-kd{X*L93f?1LF$ch!$zTrOCv-x#=R^n_H5pB?JSxy z7mh@QzS^kJPECjW`rD|`8EAFXIT;FRVx*L+O`E18^}bZ*ZAv`fG^L))Bkl(_O>Xde zqP7@~Y|j%zl1H3}O_TE+utL|v4$AcqDd(XfDGyzjwkxz@hk@O34$mk%uEqC{yIAgG zxob=N#~yGGxK{&nU)#rQAG7^rrXJgOMQJ81+-OHG;n`zH1?Uk`<^{X?Di>O^QD=YKbKM^Pw-lIZ(?Ur%W%6QZ!>jExNg?}~)Y3YICHMacDJ;!#(UqCf zwCzbPl$F(;T67M+Z zcn9enq<50uNqQ&gT_@YV3*H6qf_KBa;ob0Vcn`b>-UIJ}_riPOz3^UmAG{CV2k(RT z!~5a=@P7CJd;mTGAAlch_eSMlhr{M}x3{^&VGHpV;;pStZ*3!lx5C@tZSXdD8@wIf z4sVCI!#m&|@D6wfyc6CD?}T^4yWm~$E_fHb8{Q4?hIhkz;63mjcn`c6-V5)A_rm+& zeegbbAG{yl5ATQf!w294@B#P${Nx@T@aZ}@*y;X1*hSb)*yFGn-VASsH^W=tE$|k2 z3%nKH3U7tC!rS0&@HTiGydB;SZ-=+TJK!Dg4tNK=6W$5$gm=Qb;9c-8co)1I-VN`D zcf)(&J@6iQ54;!N3-5*Z!u#NT@IH7SydT~V?}zup2jBzn0r&v?;hpeK+TR85f_KqBUGQ#rH@q9(4ex>X(7!$K9(XUjm;Al(UU(n858emwgZIPx z;r;M__yBwWJ^&wp4>E5LGH(wuZx1qW55b4vL+~N^Fnkz33?GJ%z(?RC@DcbZd=x$k zABB&>$KYe|G59!q96k;ohflyK;1lo(_#}K1KG}TWLuuZdgipbz;8WzEf=|Pz;nVPG z_zZjoJ_DZ#UTps7POdgy8}9_q9n3#9=bu`#PnDAoT>UfBS2;-7OxWUZ9zGABhtCJ6 zK5eLG(KU;%S#-^!doJDF+rxg2RJB2AT(EAM*OyME?p^e1+#O_hVS2@RFz z*v7?{wW-olOPl8F^Uv4iZ<=o|XVL%VOwUW2=IaV-7-Vr9WN{m0aT{cD8-fqPhu}l- zVfZk77(NUifsepP;3M!+_$Yi7J_;X$kHN>_WAJhKID8yF4xfNez$f4n!3(FF>d0O> zpVlY8az4YS!WAYdZIaR^DQ%M4rr=ZXDfm?4+0D%~M$>J+ex@;+wvlgAW-yweq#2B6 z(x$N5SbF7LHz%{XC@+$nxPzTYW-% zZ9+q(qjc@e*XN(F&p%(EU# zmoZaY294J#&AhrZeFKIRJ;q5X>GYu(kXbr%mX4gIBWLO8IrtoW4n7C3z$@?yyaJzx z&%@{8bxMA&+B93=^4a>9&(^nmwzlQfrn&ku=IYCst1n}&zKlwJ8I}4nD)nVl>dI(P ztMl`lrumxu)yAoK=|CNkn`m50kwLbQgKQxO*+LGog&cwp!H3{O@L~8cd>B3qAAyg+ zN7#aoutgt*kHSacqwq2K7<>#q1|Nry!^h#{@CodQa%R~!E{ z7N6-}W`sUMkv|SH1DS!$Kqy(8vB}=Jy#HBT_>;((M8+gCr)=aC<*8(p&kk5l*|KT6 zxAr1#nC>f?n`N+?h45*MVv1V z)4lOptk1l3a&*vfF$pfE8y$7wlJ9YRpR!b+WjSS$)v|Akj)|_L_pGB;he8Z1b%Vgce1+1Ht-uuwiwPFiD3`mvvQ&cA1)Z?e8 zQI8VWT#yr~VmVqy73te8#RZuf;|@>d^GRdv2k0xhU$sa4*mX|7Nu8P|)vRJqy;8m> zT}hpU#`VIumg@Sz=4+I>W=oH9bc_n7Pi(o4<+?3X z1;tFafs~E%5N@caJboMMDSz6;a5EXIip}J^DNMeka0|W#--2(!x8d9HZTNQZV&v)s z`8~uW$b*;!`BRM`{Rm6kzD8o3(uI=Sxg z4G(3exa1Z!qW`9PQ48K)elhRm7xR7^foh{K=KW&43uoA2jCL5K9mZ&f?%9R!IC_v*rp5X~7V8UH ztS?0E$j=;_mg@5_)#qQT&#!jo`5V;OY*DQ?E!X7DZerB_Wa3?Uun0Ehd}| zHJ9bZz30uvRynd=M2hJlWn>vJ_7y@#N61*3SJHg#b}=<4jncY-yp52jHLYCcCPte+ zGV=wu>8|~E(FxT0XEBRxwZ*Xh&nE5Vi1Yk;5X61^SJQB8r#vZ4<|#~;*(htnv{Q6} z-Vbo3w53X~N|Y62;aP20e)K2lb*6^BR^~UQ=o@lM*(YV6l)|ql?_WogcHAV7_>P-I z#7$D9+UOlfUES{AU3Z-)u3b7=a?*_S7w^kmXOEuVv-w4y=X^M6Ka$`Wuzfq13*2Y) z_H9|(-)JFR&=HO|_>1|crTo(}p9+sm_TFE&)~}>GtBs$G*=&W1SFH0>PP9%%s*Qh?{vel|>C&!*Z=`xrXI>*>WArbz2@4Wv*M%XHwOkS2xOeHpsJ)@~F0rQb*h2H*M}Y zV-xdD%r|YW@mJohWSK&YwlLaCMoQm?ZzrCq*|u0;%-b%GE87(lah}pFzmpDmRqBq9 zcDeRDx!lunu;8vwg1PLIr4sir+Vh#Q7nnVY+rwx-8D&$-KE>_3IQBEH_&*o>nRKyp zq_V(~%0j;nyA}od1IzPIL#`!C}(d!etK zszaEC{`!Q0x`f5Lg1#CBE!G#bSYOa$eL+h#1*z_(`ZAX4%UG%}W4XSJ<@z#~>&sZK zEhC>c)k6nLJ(T)FFCf%BoYmOXCFnf1#zLc6mr!4Z#<$L*z6_0ct%ZiZE}^~*IYX_5 zyrM3lz6|+EokeXK^ovgM7JA=PvdmAN`f5}CbY!8woSOP@pe|vtrZibB))bTy>dRQH zFJq~`jHUWAmg>t`sxM=?zKrGiGM4MhSgtLD9!d)e*We1*;0o8^3fJhWj&8m9uIlKP zPdZxFr)uNBd|#R>R|gLK*84KHCic6Lyf$#)x3w8*SkF_|BPGACQ{8F*?(<*?C`J0q zk%3ZVRi*u#tlIeUcdF4TsxeYhjZRUGO3AO2AG1d9YEzA|acNRROWvfWrK)2EZ&KAI zRBenYDw?jWd^4m;YHEzq^ppE)(w1%nqSB_7(uk(isb6V|EvAMiH)_xr<+n%;t0{XL zjXuImb{e1bZ4uuiuelcPN|J4U?~!LvV^Rh$_v_A}#VU#q zHWHgwzezZb(Ktrq7>(OVUm%x)35+H%n!sqnM*lR7CNY}CXcD7I7EPs69lKQkDz{m$deP(=J5)C`t0VaaAu_owrN`D;Z(`KRIh)5u}38p&QalzI*l zPc`S2iMNcDi8qgwsrG%NR0-^_x7XR}Y-)!3bk;3MFGnddTx#2_d(~~5bE|!DH<$9r zd@h-%(3alpRw%gQf_<7-!LlOV-p$UZZqA61qoQ{C&lnd$Sz^97CE=x9mcsMY>X4Bx2LdMrmL!pwd;<~%kV zOL;VGW63;)$x`8XGD;zQ96k=8a3Q}NbxpY9tYyksG%k-6eKo0GxGj2Ua{R!J>85mY z!r@d-rXW+0>6}bMrXe#TcSdP5;|Fd@rOiyFu;}$vpYOO;U%KONYgBjt+I!#o?({#f zQ{^eKH65NdBn7se8vmJh>v%&fCELc&ACnf_OqXpWD3xQIq{=okWt*9aywGB(|LgSK z`)2yGAMki9k#Up#wsgwbCOzh!f0JmxZlz95cgTL3LY-Merpg&o&-!xL*v9RxIyJ)S z(d_0p^{j7>HOgk#%*e*MXU0r4+F_y5R#!{xZX+*k<5Q(#$FUs8aw7Fr-h@PE(05U+GR}8vJVG@5Lrm zEl4T4KC6u%jFg#j?wP4l%`?+kq}upQtgtgPSxU*7)Y+-V|I$W!4J@nvuV~~yIjoT% z_&K%IzlQa8TB?cl@J6Qfb29pkg>tGKaUVn6y^c(!JZS@x%u`q%JjLZ@k**`Aj_Pnyj=NzIxqoG59_JxY;sp1E?Kxjav`u|Hg~QqEW@XRMSnR!SLrBBNT9 zt+iSJ?023g)vgkm$s@(Q`BQ13y6DGlobnv0UFn8HB<~PCo^nd5SN^rxIG@%x^y(U%n9Wz`x%^Wl z|1^KtiYTG8%R)UzSRtIRTexTA>aV-moQp)?Y))3PNO@_Wk3=uNQ?sS1O@hi=dNMEQuywvD zRSO(Syrh@aXDv1<=lQ9&I4$I}nrQMn-nnw5QjW|=#NTV1N_jL1Xb>GH%M?#W3gvYU z;qp4i;qtnO&%kHk8WN=!-am_X&f=Z3c;{?*rzYQ77VbI13gJ8pIlKa|z$@^1_&j_b zJ|7$%X(2Cr)NI+KW^+eSvn8wW!nr(8wXr=G+POSqo|5OTHV)aQTtp==LWNiI+$mDZ zC|^w}<&0`q65@G9jVncbl|q~+-J2Fyz>n8f-;5V+m6D>FDUu6Kk+R0O*EnypEAzz# z=xdx7vY!7KElf)~-Y2gLFO*j~#5?6x5ubz4!6iS^D{y(0<8Tc@#HGz4TwdjP*}Kz{ zKFbn5M_3`8FD>a>N*4xswR4DYm~ezwJ>iS+Mff6o3BCkhf-k|B;mh!4_;T?4Drj05 ztb1j1VW>V~xHh3-VWhYsnilKxFV^Q@tk16|19McV5)gtb3sp<$_e%TP)nOMa5d&l8o`=`2KvG6(0JC+ zCJk&|LS6nQ^+U-*uLb#)wI&UHDP2vG57Z^p71eNfm!x+&2kTzPTo|fP7_Lpox+Gp? zlWWvcbxD0feg4G}-hz`~)Y&f8XI`q$yp(5NuFt$&pLw}H^D>$72wkBoTtO>bK`UHA zD_mi#gQd4HSBJ`x;c{f86j>YeE1GLVgu{d*yt)Zrhp)regXdRm(w1D0md~PZI@$#mmI~*n)iFX2d!xJM34x=@U*2+ff7)f;)Nwvem zD8JA$>AZDWepuJ&q^`oGp(?W#+t$)CMf zs^(@>Mzu-Jt<9r$*Co^y60f~CXb{tLr*ys39a^^-N!mJ8Zg_1kWKR~=1!-&N3~GLg z_?hGCu$LsYIF=?g)?qTz=xgj8!qtw5OTI(6bShq42pc%%22QzwQ*Pk2n|hd=UNEgT zY!2n0^zfH7U08LmpC)(lOQ(7n6rWL{r9?ZH3Z! zi(}=;csVjriY%o(@|mS%p2B3A!epdy*=63At_OduZP^7YC1Dp##095FafPTg(T8e- zL!N(0dBi2!A$$?O2$#U%66g>vff1KLhj1x!yzcp$>WkAml^db*N4Q*m?v^$b`N*v5 zlfEXX#}zUCtSS00;?p{xiXmI^kol@v@o2fXR?=vxqgK*DDO~k~OJ8iOwp6xi<8Q|h zu6l5PKzHlg0Zu0OTFR|b*8HZ#KMlC%JW#o*s&a68(Kjz^VJp2~U zx-+JB6eFm{MUSdo4(VYvDdK99L%7=DI9#G5F3}F*YjA0cbjflEml(y-p_?|<7bCVx zsQeL^okHw1f*bijZR*oM*N_zhwc&nnYd72v7sNaM8@!pa!J8?Yw){^4H0J)N9z5`}U$JW6OEcZzE_iah@0<79MBRk`xzOwedG%D7QVhE^j-u z6(gh8M31Q<4(U0yA>wL-L%8HS4wv$XZ^5P4akx}Qd>bw?j@ONh>Wh(3w?wF>Mku|- z$fQ-utxn&@&?@D~sgAhyb>Gv#+jk7(R~Y;ijUD$&be|XT9eb|(Y1d=t1!&i~=TgV| z0^N0P_xr9hdRddev{Uo9H1_NsEMp!@l_KeT&J%kRzA{cv?Wdl~3fOl&ld0y0cx_=n zWz5_$Ens9*3!`JzE{6<&8WM4}!696tBOZE_Aow0!0)wlM9m1u^@wx$2@5O+r+z6FF zLg_6AFfDX?SkI!Shx>fe!+k#K;ePs5Ty6`Kp7j={w3_J6w$u}^zpt_3U7sFXm|sa< zxiB5XU%XnFiAZ)02ro`Xik37z_l>?^bWwLY;@ozDMd$V^xs)=hfTd)f!ep7kWTbG} z1+#Fu%)6q@Wh|G&vbfmPmgqaR#UYQ2Bs$`f>JYvNm#j#aB8PA(avUy25nqN&hvRjR zqogCoMmikwI7&JqE*%cx(&0EL|i%?!llFU^5d=p>dBu(Iof`%Bp<0Z zJ{z6Ck~@mVXoZnjVa!%NX7`5WYS|A~v0TM+&6e^v6|;s5tRO)t|iNAV{=5- zbMtJIvre9M=kdR9Tz4M*W9`hv)2$)HCgm|)677&-mFS2|uS2+`MqE-I!llx2xU@xF ziX6fvMseITt&R+bbi{B;heL){IwCF|4&l-fap`afmk!6_(h+g#a0r(U#nBe`R6A9^a!DULZDY8Pk=wahsTLV!oB$9g=Tt+0qqkVYG$O zwqG#v3b9?b+{SV{EQ>itqGOOH)ggl|l@XUphj7Vq94=WAmn?^HDT=rhIfP4x<8{s? z9Wlt#;gG?Wj)+T#L%4J}4wsIIONT?abVOV_9Kxl;@j7REN9ahm$o7s{@P9S-WjiGo z@rtvw8yUt_R*v-x5G`m>tV!3C_ZzorES$k#ky=1QQz^HeRJo_n+%Gxhm?o;r7 zom)wM46{T#WY{G&;*#nRE`g52B{1TW)hFLlsGVIb3ap`af zmk!6_(h+g#a0r)CFp1>7@%k>7|Rh?bK{|f;YJf ze*fVM@eFdouQQzDh~IzkytC*${3MU_coV&t^2mi2lexkrTi%}B&^xN7xmcQA-;%3% zJnmnXT*U{&Cztb#DrMPuT;DQPEEkH3$!>v%b!td>i`w9jhj~&S@kO}wIu4iKh)bnI zxU@xFvK+!C#__s`fzlCPA{`ES7`PA*1Jyrp=?E?z4&l-fap`afmyU=_heNn@I9`6( zm^M^DiE`40syrMk9uDoUvO!&`lm>N$v0rtbyS=rV3pcyp5u4i~R_7zHZ*|vF9*y{# z&7F6x5_x?FwKiXhtYf}z^LwK7b(?D&UgaD6>&_D?rxZ66X{W__s!1`9YKTL|TWyH= zDtr|#(UC5_4&jpOI9viFE`bi=Qlxlkr^Pr)M~tI%IApw~BjVEG5H1}Nmkx(;>2Mq_ z9TArfhj8goytLDLC+J8!E&GlKbivZ*5H7ur!zDH1+wg7pcAfi4Ot^)_IK(|9CgSQ^ zhj4YM<8bMSxH``vTwNA%>2L^_4#(@2T;?nA@PqJ!@PqJXcr&~i-VASn zx4>KAE$~)&E4&rn3U7nA!Q0?%@OF4RydB;S?|^r}JK!DgPIxE06W$5$f_K5Y;9c-; zcsINo-VN`8_rQDLJ@8(5FT5At3-5#X!TaES@P2qdydT~VAAk?Q2jBznlUEq;Rl+sG zb;cil5PlGT5Z(-LhBw2T;VtkMcniD*-U@Gpx58WDZSXdD8@vtP4sVCI!`tB<@D6wf zyaV0|?}T^4JK-V5)A_riPOeegbbAG{CV5ATQf z!~5X_@B#P$d;os(2IIX+xJ9_l_`?sv55f<^o8isyW_UBa1>OR0fw#b0;jQpicq_aO z-Ue@jx53-t?eKPZJG=wl0q=l!z&qic@J@IqybIn1?}B&1yW!pNZg@Am2i^nkf%m|B z;l1!)crUyU-Usi4_rd$&{qTNxKYRc_03U!4z)#*`ymtxr2=^I(_(AwV_(6CxycymM zZ-%$PTi`A57I-VX72XPOg}1@m;BD|Wcsslu-VSevcfdQ~9qRGvYHICnhe2*;6v~s_%M7J zJ`5j*k1X(ApGCqY!exh}@KN|Ed=x$gAA^s<$Kc}&(HMWLb{wN|jK-G(`nxz2w)9tP zC$OAYDqGk*eHW(~#wlm>qu*&q;OY?nRy{=Wzj3DUakxb?l&}wYck> zUdr-JyJ+W86T((aaJW>;Gh@qC6|K+$R7P2DyS|#g={mQ?X^~zJK1Od*J3R%m24#K8=;Cns=|JH){&tP-~+9)ewhi z9IU(;Ln)6$iFSyQ^cF_a8&G;3VkD^%mq3Sb$#Oh+G0svH{Vqig83*-c#3jZdTs`PG zTsk5y9S-5r5pn5o2$v4W%j2xm#&?7c-DeH54;y43Hpo6~kbT$?dygOxnat&Y z6Sm|#ifg4ZCTy2nBIKP!%oG;_TZbjZi@c4zJTaoUk)KJGvUxdR#G4rGu! zkRkXGd(@lzz1~F?`_Y__JP0DR#8vE^e=9--6<{+$q(0+ ze1}_v+YY5X(xu!XTzVrey$<1$>NtD`E^U!6Sq_7vBTavA$1Y8a8`)$yx0!$1%6_V< zvf)XvvPmd%d;IIpdB{9u9)bcn_bg*HOQ+Ae1HBQP-9GS}>dn-5Nkfc)XSq3dI+1do zxl*2{%0_vXQz4R=W;@@ERm%A)<$Uw?`P6`v?*nd?^LYz8zkT4V8e}~usvl%EEjnFI zbI2g5RS{Q%9Kz?|YC)u{TOGnB*KxQyH{#Of5H3ZEm*&BDf{ru~z7urFnFsf?)q5!a zG@O4L;ZxzFgWT8;a$`Tpjs0Nx#(oH+A&iDF8Y&wNV>FD>Fh;{=qmdn^-(A8z!omn` zYRhP@do2Gno`0G+tSzGyK1$)EC>SjY*RNt>G=|X_Mq_28ag4?>8pmk7Y&3z<1V$5k zaSgM@Ei8)$P3GdK^r`e8nlx8TQraY?O}aFGAh0M+zjClss%L7qG<8ookMHuQQl2!^ z+uVuMHuwH@I+-U6EN6CVr*uuByN9Lh9hS0Rn?iHb-TaQPT{S7LVKvF&zC$%6;%b9K zxRgg+${oU`*KxS?MqDZ#!e@e`BP|4P$1Y9RJEa+UcDEeaD@XQAk-2i7xpJPla-O+z zo=Q1SrJScy&QmGpncu0MiFK>LdsxceVJZ8yDaF{Rt5aHlI)u-c$0qG#-w8U>{QXYQp*e4X6QKp4?s(I-;FCCS$rgOX<1N{u zPlWtvf00w9MVtEuc+uuh#x`S#(wAKNl}@y|3tX~!dg(~7vAJ}MSf=!4oBPOe+2(O; zmIqA}u$Ok{C9LaL6#K z{xpo|W%UPCtts)Wtk#l+gCt}wpcnH{YO)mKHXK=wNZi3}M~ajdMM`Npl34PW zBb;&Bk(4oW_4KU+Mqh1+QCCMg#5>e|5tn?2@Kw0}lyzYr(=h5tx;_}-?S+{aa$b~oo$d%{9Z zahO~WugoRq;5NF>$(wrsvG1^k@kWrV^h)bnIxCA;5m#m0OmP5D{MO}7HS`#jy z);Po!)P{&lxkI@0Iu4h>h)bYDxCBOgA1+yr!zD)XVr7($7452?M7E##-(3!IEEmhoba7r{%$Uip@CyaZlWO1cTu zMoZ`M>HjAd{-KJCNo1jXSiO)RR%ib^aG~bJTE$6Lk$JJ4c`?tN{v&05KIu*Qyyz~K z^DUL~HAsJ28Gj?rix<4IyK@_?+`A{j>Dxg z;u7c(E?E(mB8PD4a6C9VG_k!Sbm-RO9iby_#XORJp0eVR^g+SO+JVQ?NUnI`-yiSr zuG-vVy_(Erv1&i@v+~tsuEe#K*l2m@xQ6B0S|s`~d2K!0iphN4=6>aPeYH%S$6pUy zPkBmiCHXPTQtpspm*|K~uS2+`Iu4i0h)bYDxU|L8AX$+vMGn_UUq3LLPZ@Hnzd;?Y z_i!CfFT1zg>K&m&Znfdz^ww&_!|By|!^7!sIc(Sk*n!!%{9gK|^YE=y=V4oEfApHW z+kbmv@5UTRJBwi@Da?Z8x8%MdSu?4eZMeh)mzdgJ+2K0EPC6p|3FvUWhwD%}{UI+qLVx`9 zb)?;4{v2DKw_AE(ZCBeXrTD&Kzb=I)$zfGgv3l;y}7VfRP9Epj1 z5))#jV{9K3px1_^*xZcz=5tj~!TnFkD$Klcuap`afmyU=_heNn@I3Aq-NE=Na zCNA(WaX}9g|0MQ?3)|U4$wh1yv02>W2UxcqF2R@JOYkN5vL19k<^3iW%UCRjMScl1 zE!00El&s>>;)0(Y`}yR8pB+b}X|XO})8c0N8Ks((UgKz5+$yK0gzegd1~nzs=PJ~m z6#4HX?^1oFZEh^q|_;Tn^OORPhk~LcY0k_e9~1_d&jksyN#wT6a^?o8NdbY1NrM%dh(Ry=VE=E&k{F zw!^iJ*wAV-Ty69OzqSe43UYKz<7?XyNz=G?#LnXhetok{oaYlZbDjq^zZDLwW`#qk zMGm(-v8tPNr>h-1T%u#bmEM>*SGPT@OK;doWu&iddBT;zn0Tel;if0rb@=-BfiL6} zZQ2$euETn`5K1~CT{;}Xr6c0f;Seqz5tj~!aOrTod?BT6@jF3B+7^3E*%o_DiFizj zcuc*E*|e8=zq`pxC7WqB)%Gf+h zWfUfXu~={6d(t+p!ha{WC(;%UC~c|6C*JdmQK!_&{}3iWldu?f33G@aNK?e6$st^t z9EVF&#HGn0T$&;-O%CCb` z@~G%N%=djA>awXedbU+#Qbt{J)kaOM`hBd(xKGCY$VdzI^hYgB{`kA$q&u6QHPxEf zE2|+fJE|cL;Zp86o-WbeQ!|T7WpJsCP%0g!%T4MfrV`uI`Dr#5m-llbDE0j6=A@I364wX_vi|?WGrX^G|!(tA2|+?5B4LMf{7l zOOPeVQcjj3%aCOVb?ORO@cTFHmi_(>5#GO9@Y_9c+R?PQQ@0gf+>PCb-|AcR8$SGY zjo;g8TB^??nQ6n~EKAg~&v#?y{@82-pPcBH)v#e&#ppPr0q~CtvU(zY0l_xC3ig zu@8FMUa=20tlG;pIcs)WOlW+I z$hq)tR*H&TC(rtRB*ro!fA!K*IIWr#Q@qJ~|55CK|aA2v6 znx)Pm4pvfRWpF8SyigQzDRKyxn21Y^L%75^9vmHMgZxg=AqU>t*X_G^esWGeDecvw z*U~mRIfce=!~S6B*w_m{@GmrN*eSe8*t8F^olbd_cQcu%(3U=r-m<0lBU@N*x#0g0 zvbJpQ|J1#0bJwzs`L@lmutWd4?iUM}l>G1r$&dA4${p_8?W8yOCT=LHK4#vxJ4$78 z<+EZdES2GlQW>{w66mvJ3X!bvS;>k*w>9$R&4a{*M@WoAJVZShafxvVml((45)*Oh za0r)4_XJ^QKu zz0cvQvEEPC*)K@#mvtSvHED0WV|P|-!sFDMm`!(Z-`$z>%|94I-(ru2%dDL(|>h}!$L>I zrNiMK+w`bMHwffcrv#UdxYd>pO(5vduaSS@llreDD*p>d5q^sBXosgHUTu6aX1r4{ zIt8OsFgo><*?&?zHCbflr($+0W~UvMnVptMW_DV_5pha!0lrOf{s%K}B`*HUQv>2` zek5@=L!AF-Fyd0AxJcioE|(Cxs*OYr>$&)mg?|u-h|-sy@c940guX0W^k5b_?UVlT zjnj@Q<(23nuS6G-(=k6inQNp^{}kcT4$pv}0Y3wN2K>xV`YRe|9!2;m!lNCY1wRXZ z7W^#u+3>UBXT#5ip94P!eh&N`__^?N;pf87g`WpM4}Ko}Jox$W^Wo>i&xcn^*uqf-A6)pM0AXm_OC zmzhO$Xm(LWujUc!vAF2pjHr`~D@$!CMLt`Oe6AEZ{nK1vXkVCAJNPCMXG zp5`oS3B4))R#KB>_^C%yDV}$n1-dg3NW=omQ^H{awOxK&0`>21Jqy%!DD#AiwMFR7 zuJ!2JJYqc-7xFW5+UHBxy4p~h5{@lLjw?k@|9o8fzZ;kS>BmIGXL_d}>!Xv?kBh|r z(UXJCnJH}k8z#wIr!$wxD*x}v9pgm zaP-et8~-S-;&YBIM-tJ9o_kCw@!VvQ5`pLGdiz-9JujJwoQK7EVNvX?&PLGL2s#_V z2>2BXz~~%|5CqHwVT3Tl!-c7x)yBW~*K57q+E2IQ;N2qpA=Uc|9)0Qiu?md>g%Cx7tjH!E&Xhhjq@rox)7ra=}HMG zj4#6IB8)D==%R3t(u{e$=b|s1KzJhINijElEskbS!RQo>PDw`D(akBg^jvf*mZxHQ zsxAHB*iTC^pO%tE9J1Srj}Z!YZ{X}|j>s=N(B6_@pqRe4TD6|<975wWV$!A_bv zkEDwK7~Aj#_r6>BsH5MW0doVdyAW#lRVo=bZOc=p&U7}97*Q-ADx<-5Urj*{rGC*qv@8m;q(*S z1*e}#NSB-;i?4*m87Js5fy4qbe8Ns8W-GIvipcX$&OF5OP`AFI{o-;@~t-h zr+Bt01x&})#{a`9Cq{~9dY-y8obE>WS3S-+-V^Z|ClK14nckL~&(*jyu{;yYGf#Am zGf#4O*6}fW{9W`bvD)}Q(1da%nVZm%pV4frrX4y|+~R7Bl#)&^TC3|UrJPixsOQf< z-jn&+ClH=Uc#^|&;OD^4fu93E7k)1MT==>0^Wf*f&x4@;555EC^1N;W~4e%S`H^Og(-w3}6eiQsA z_)YMOxo5kWd$x2b~Dg09SrSQw(m%%TCUk1M%emVSd_~q~` z;8(z}_^dox^YIm*b9g2EO8AxVE8$ncuYz9%zY2c!r(Mp~pCN=_4Zj9{4g4DTHSlZU z*TS!bUkkquejWTe_;v8>;n%~jhhGoB0e%Dg2KWu|8{s#?Z-n0nzX^U5{3iHK@Qc}j zUd#^kVs@YxbBl5b{1W&j+@f3(wUk$$+el`4R_%-lr;Mc&ffnN*17Je=K zTKIMF>)_YHuM1vmqi#4Z9`XAY@5W=xkwoP2H|g%icX2l*oKk`m8=~u}>w4W<8yf~_;R+jSL7*IkaA_7awREODaBjgtJL(g^({v0a;m?a>My4uD&I34{0jIL zaMc$4O8AxVD=q&&hI|PKqpL8w3Ztti@-_Vd}P!*y)xud5rb ztEu2>D!7^ou13H$@N3}Lz^{Q{3%?eAZK|i*=za9Hsj_SzeI1s_vO^wGmbTP4sXcXD z>XcNwrA{feTk7kNFE#mk?q{y2>DM!;HzXrnemB5xfZqVW5q=~5M)-~Jo8ULWZ*r5< zep|v!A$`{lqQsTj00AZ-L(m zzZHHf{8spF@Y~?G!Eb}#4!<3KJN$O|9q>Egcfjv}-wD4Hekc4+_+9Y3;CI3Ag5M3l z8-6$ZZumW<-$VL6q~A;Wy`-cfjv}-vPe^ekc4+ z_?_@O;djCBg5L$d3w}5JZus5syW#i1?}6U~zXyIV{9gFI@O$C+!S93L2fr_PF^Knm zHXE&bKbMWxeM<9K$~v9y{Fa8Z7`po@`hJSOpQ7)l>IdKtz#o7=0DlnvApAl2gYbvo z55XUTKU63E{#>SN&SX{_9{6cwdetv{zmwuL|mC>;0I;(fk=^z_?->ONQIE7 z5|i0OGIK_$Nf?K>EhLJhf3#`2S-UNtHs8!{>t^;ox9BYSsF~$`C8w04cW-8Y zbhGzIe?QDsP&!loRY$VYu)Kxs)-CLhZ&8`HycLUEvAC68+pX;CZiC+jzYTsH{C4>5 z@Y~_H!|#CK0lx!&2mDU>o$x#1cf#+2-vz%5ei!_1_}%ck;djIDf!_na2YwIyUiiK6 zd*S!O?}Oh5zYl(2@X|%1=La76do{Lv(pd6I{q0XG?<&oM*T=6csL(Vi+MiT%;h`$N z_$3#u&i5nZeq`K_jQf%K0Q>>?1Mmmn55gaWKL~#i{t)~j_(SlA>Y6DXacxLPYKE0d zOrczgii<$9;u?{x$R=4ugQYE5`U;UiUv#P9bn%rfrP7xg#YtrtOJ!2A>ZRnklvujP z*a+RsM(AcXN;i8C^vwGj8{c*8{L%H^kCOfv>5q~ASly=f;qq4Y;iIxqRka~o z)rP!BK1q0#(^b9Gk9PdgRE(~7$%u5xaGd;)kzaBmU2?)saw7h?W!Uu|PlaTj_BbUy zPDxLc1wSFd-nl)I%(6UBq*AlUlg^NQu$Z?VX3zC7d#;Dsb3M$S>=F1Q@JHZ}z#oM_ z3V#&-DEu+_WAMk|kJatzq$4SH8zd$=MPiDDMT*?hdY?;{?|$FUekfkSl(OhKDNE@` z#j+uJ?)GxA(&#a9v93!(JpeA&iBjv)ahW8Rw|lx=AFpe|T|nWDNU zZ0rf)iBw%SOi$PxLOY(!cdbvR?Wi2=$?~rCNlJYxH+w3XWko(!-nBkOkzdSr(O*ot zGutoa$zM{kib(eW)rK$U$zLv#OK!wY<6(9h53|#Fn4QQY@JHZ}z#oA>3V#&-DEv|Q zWAMk|kHH_S+sQo4hC@1{o24W5>{_%zVj@vu9J1k%nACpF)cT@r{6d;!*?lNZvLc6M zrQ*Cbk+w*bHir}^ZK*idDS;}f=%~kO$m2BRaT@Y?T|;CS3S<|B%Z^JS)2JLlo}l_C zsQ!ua_UQ>4@+8GQNpVk>`{hYWdWw>sqNJy6Wbc0}m6SQj7b)(G$ue6gzi4y&%NJE< zz770RD!AHsV{DJVoaiz#oA>0)GVlDEv|Qqwq)JkHH^4C)kN-SYW3dLKvcC)ji{UIHZE*+6B9gdS*w)7U?O{|Wq0;C}-DQ~004{}leG@IU+inS0M*&64Cg3}(N#crf2mf(QYUNGt?L zVHW_!Kl&ksBtn1~1W0MU#hRI(p6;3H9zWCL=XvkF_uhMN0FV?x0TJ@S2oZ#c2vMX+ zDJU8!C@J`bPUbncZr$qFjX~@Jm_M&x*2%0om6^A0-MZ!zel7f3_;u_od-(TL{zTc| z_ea)hKJ5Y@JgX(bN7QPGB+r_e1|}7m@yRfm@nJBTB@crY&G^7p&1blaU-Ma(Y6j0w zRjTfN%ubVPi;qgxO1)OlAs8b4j#;^k+ur@b0KoBh`$ohIotOK#3&*zk^2Gi-_%yXc>9 z)rcqhcfRP~OZ3lDc%$$};pp3O^vzOulW=gc>t#L3WlBLX%^;XMh1@qSpTYw{7<pgqnmpZ5cu@Bi0)L6u)*i7spw z;3M+2)Z|BsYNc6gnw2Vk2ps*Jz@N*hqdFg+uQPRAf%{XGb+lotU}y**wydX(W}PqK zaKe#OPj%A}eLM)+i~;dhb@Lapm^Od05Kx<#H2j9(kjBqt6HX)QF5HpLsQY=}kGEDk z_#ax;e`wYI%Uy?7EsQmO@@n`zSvH7m!xt$xnv%_V!W)G*3U3nLB)my@lkjGCqB+Nu z!Zc%=LHMHQv8m2|Q?H_lWT6PR| zn{Ij-b;R*|G()#!bRJDIQhGE`SNijwE~3g~QB;412&op2O{WTvKb9r%#}Wko7;e!N z&cjl(HV;b)b`)nbea)9J#i7lbFZ)!-A0(*xiYmX#veuNc$J1-6$+wYerCDp5l`4Kl zu1;;%QIlVztCMD(+N@`roOSD|sae-FD^)bBclCVwX#P@`4b5N1cVFP_{7^Pb^CGZM z<`|BqhA(l-YhX{D(i_+lyKUeQ_(|zTsW*O^W{uR;WB1ggS(7xIq}lWpS~O9K=bPp) zdDdvcG)=ti;EB>K^=7IUPb4j7hTyb(+0B2+Em~FC%1X|ZZK`aGE8A7so>%71O4|O} zo7&cFR%{!UxO4PT_`Q^LK=t;oRI2V2d>?gX1OHL)szn2D`6~St4a=1NH2FxWRZ7t? zbEP&ZwMi*rl`FNIQl;u?Z?@ev^Q7v~2s?5|;V4vPXI$B(%C5Mw+bXlP>?ZPyxd&`v zS?qw0N;-)An)E_-^`iNOz5HC>3vxW_B_DW)ywf4?M7`!pol@$QQbZtE>XK5Ilt9Qp zO)5<4kGp@nrP&?(`Ma=M`g-=VSN%5%@4(`DxY~f#3d7ak*E0P&pmwE=x^M6byZ%=* ztR7m#>c8$6>w1CQ6!G<@!dolWi(c$PFM6vB_N=pB?83a*i<*NLG4$f^jnzAoJd0|+ z=0Ae4ogd3sk_mi~ zv5a83vPFKdGa$g{>YvQBGF4bRw; zQ_uzZT&Io(k#_^9U{Fs3=peK{{u^D5OgGA}`7brW>7;rCWompd%GAAyGT*)Sw8O{H z{7a9Pdo&Pb>UH8gE8;X8OuLAS|tvzI8FXz<(4s>zca*EC3fcHc~xn_$iHF@U`@W9gqF zDmAc49%7;@fy0B4rMZ#*=axO?#;<*}@ExUcyXb_olrW6XR?ZxAlP4s~hxl&id<|AoJdP#Q;@n}U za`X+{Y>5^E`GLLa3q{8813T7jNJk0t2m1C`-G=ME=C4H!JUpI4zTmKJ2}gAc6CygQ zal9Bz{(>zB^Ha)0uu;ut?iOS)-%)7!I(A^P(5flu;!51VoYfi>TTKzMY5fMvwt%z= zX%o^Okai*Mj;MPCtc8BzqPT^Afs?ZD_=1zIv`M|~Ym&dNQh2-YcHv;|c3_S&cU(spTZhKhp;31Rq*F+zkgkAq3F#8j z9guDz-HE3<#H&NR(5v%8uU>Wv?=&30iRf=gfB-=$bxEmqTH%J%ezKOO#q^XyHVN@@7BcJm=W zqQJ&kf(=Wt1{;^bhNW<@p)4G1Tm~DK!okM!>c4f@3`=QPO2bkD85rfR8F6xz(x}QY z425?&#xNB9hoR^{X^qPv<8sKj95S9{!H8Ilh{cFlpjW3pDtuJAN|oO5V*uP4aGzhCZR8PiW{9%7sbclfoy3PYRzBJ|%og z_>}Nz;nTvWg->&g#n;^@{v^pnS=5J(Jx{3hx!(E4){DANQ{Lf?c1K`lQq+rG7O6GM;Q8!_x8m z5E&5Z0g)aM=>aE=cYuWt3Lg|cD11oxknkblL&AqY$D5MFaGhohxK97Ub@pHG8nAJe zV8c?Z!Nz580E=+}*Db-AenrW}eeL5lUOvpPEv3I;5FqxFnq?9IOrO>OCrld3#D+MC-4_*oU zmGAsl|LQya_j9LZFdZ8dcp@-2@&vWVQZ(wBPi|rdyg&?cfsDzCzxuFqtO*G_$NCTZ znELOl*?;|CLhNDR|7De5QTbIX^CPiGo5DTX6z3~cdQmMM-`_a=5EBS2B zTaAiY9*h4f)ZNmDp;ropMskpy;ACx5(v2x2zl8jypNagd`bh1 z2ucZzTm~bX)%3LoDS;Ni{7gR}`U9drAo>Fu-k|V7;e)~lg%1fI5Zs5u-i zJa!ijOJ_K75x6*UaA7IV;Nmj4uoMn1l!b$f%iv-I=h+X0SSh7pDGgJJTP0wWp8!A$ z7IKkI>==R;k{E*i!@ir{JjQe>2aL-B<2JZTmECL2Ck$&sGccOae`w;9k89#*^CpnL zBQWHLHb!7bmyUcR`|l6i0iJuxTerzwUKx>VN945;d2LkqsPIwYqr%6`3)$Q~X08YV zUCX=GV^Y7tY36_4*&R>(KOqlK$iox#FlYTqfs+C!V{l5~l)$MNoEA7Oa5@L`z&)aG zUM4)EZ-S5Lo8Tk*7Ko@uH^6N!zu5zSzpHO6P@JX&TDWw~HlPgV0 z=>{L=O4Fvq+wu8aHlr72zwwSA?$!UlqP8d{y|W@HOFU!qX3lCVRsYD>;Pat+ zp4I;t)oK|B!VLEp@Vb5JJHGJ5%TM^*_A}aGn29$SLeJV5n3d9OtQ5#dX--OWv6AzH zN9Uz9FQxfd$=PN^ZRU`2fg2imEG^)VWd!`OWQ?S{HxI=E{K=;&7wAv>8ffm%1)~|J z4;xqzI66MwC|g_Z$QDC{Z8Uw@hFO?PY}YIZwsn>Xi`rjX)c)F{_SY7*zqTZNN%)fR zCE?4$mxV72UlzV1d`0+*@D<^!!dHc_3SSkzCVWl!n(#H@>%!NCuM1yyT)x4?vW~rO zzDvE1&CBAef*WdMLv3uRjScm&DST7-rtnSSTf(=5ZwcQLzAb!P__pxvgyRM{@{Ps~ zaOAsL=$kXb=W&FGO5_kH5OmE6;qz460zyM{%BjE%k%#Ei<|2XEtc`qtmH14!JfGp^ z`IAU3RO{+}Ho&vG*qs$J7mzt2b3*0=GB0F4x!9l41?)_G)$RSqT}x>;xrBd+i0M_T z{tutE=HeC~B1X;slk;1wc_UGq`e zWfr#~CD`^^HUXmFTv*gb+oCqw7PZl~sExKI;Y-4ogf9tS7QQTeS@^Q>72zwwSA?$! zUlqP8d{y|W@HOFU!qMUwkXHoc)@Fn3(!k2_E3ttw#EPPq`itrWTE5cWVuL@rk zzAAiG_?qxF;cLRzgs%%<7rrihUHFFZ4dENYH-v8r-xR(nd{g+A@Gaq6!ncHP3*Q#L zEqq(}jxNP^bSbu@OR*hYs_hEj6}~HcSNNXrJ>h%8_k`~Y-xt0wd_TF=0}o$bfrl@* zz=N``(7?m%!NcppgR-vhz{Bgo!|TC=vh2ab>%qh8!Gm&qg@^P!5Z?pwJrLgmjptDK zq3}cDhr*A99|=DaekA-@__6R~;m3(Tz{7chhw}sv${G)Ncs+P{J$O)-KfuH5!Ncpp zgR<%oI^;*S&YJrUm%@jcOaPKBQeKNWr|{7m?n@H63O!q0`D3qKcrp7;Yi zoF{nL`kj{`Ad`J#p;7{)TmUT}4ro~=cR#=-w*?^+_!$(i$OKK8NQDbgyAZVtQM(ZJ zOW~KoFNI$UzY=~W{7U$h@N41M!mov23*XV@&Wu4<;m5*{g&zw)PR0lx&Kx{4l9duVH-U!)7?@B@krOq4 zqUKN3{E50h6@DuGRQRdzGvQ~#&xD@|KNo&3{9O3C@C)G=!Y_nh2)`75Dg09SrSL1^ zSHiD^UkSe!el7f3__gpIE#h{xh}+R3Zbyr_UE#aJcZKf?-xIzkd{6kE@O|O?!uN&m z3qKHkApAi1f$&4&hr$nq9|}JbekA-z_>u5q;m5*{g&zw)5q={4MEHsDQ{kt=PlcZf zKNEf?{7m?n@N?nk!q0`D3%?M4A^bx4h44$^m%=ZFUkbkxekJ@$_?7T$;n%{ig=aH?>_ zAIuUzE8{QDDck-asuh1T_h$b-inP5@%A{p3H&Tc`OJ*;R(!*?{!O|M^8!T{E-ik6C z=H?Z?U)(6oMrk(2noT;Ans}7xbx|h6zvR24ROh?d%~EfsIyIW99+CY>rQGn%_zllW z)iXa__Q#H#cXseN`5>?(CtYqVnA5;jM!PiHrO_^p_J@Ld(*Wo*F!Qg;_^WR%w_JGBKOF#u`IDQVX#zh-(@GnE zmlcm+;!!~#cB@%qkRa}lB!W0gs`C-hHg>^=FaX!U09Zb$l1jz5F*`Kc4vn@$qwNU( z=#)mMG&-fx88o`2(It&8X>sqNTm#h1(>8W|%80J}>HNLlF4}z3VURUlz|aL_6NJ!^U+d{Mo9vLlbXxP2uR{Ke zzsg#3+l6=e@eM=JR&Am3>FVFIX&cvQ)VvF;s+aiXiaXW+<9FfgW;JVGQ!7J|%MEL3__Ci-YiaX}3!v+(9MEO? zbYxaX&2?-2MQxw??(!>#U`G{`ho23RwlRT-?LR2NtRYoCC68O)P0^acwKJXGjC?rV> zgTjyYv{JCxLmI`bBEol0w{fkEc7+*K`4`8@T8A9XFaW8rBR-X)UL#@W(rkYBC0 zt51=M{NiOR)n9XUbwx+Fu?v2YqRqO1Z5!2ja%;O2_1jK$&YbOJ#839Nn+npZddPsH^14|K_b8H?*Vp@2$4<_kw7B7uGUE#A$O{e z&>BD7*`+SJG|Vn)cDq^Eo$5Vk9=XuX)^$#hdbdW{os1B?d_g!|2-T2doKI09(tpW)<07{V$)*%vnrpnQu%;+$Xe7~h!*+jojSCr|Ip&k`p*DZ zi~e=xa{V*kU;GMA{j+JsbA^gLTWjw6S>-;0TD9njQ}c}1EKztC0=U*Z$Fi2{I4z;F z)-wbw> z?$1JiPwJjyiNTn`FFJ_GoA8$?m}aHwXWW(bre3M~^AI!uJ8|7*%3%PWdNt3ge2ygs z;Pr?DOW}xv%OJ~AIJ{3;IJmhCS}cWw3}o@B{*ds%q1F%%p#~j74LXDx{1C!vH)u3U zqfr`-(r5}AP10zRMw2v}gGRG7nx)a49HLJXsXd&K_upKw=eL^RnnEMG z3@R48@ciR1 zfATO+^DWWQRzjl|1D@j1awIT1Jmt622GV!+&p@O8uh-*ll< z>YcHAr`DdG)THCP?p7Zo%XwAQ71S$Lf5RK{`I+RfVQ zewM7eExOJP>p|O(`b|v6`2>o=xsx$CmI?-je)DPU??PKsP z#REfjxm!4fM>*zU*E#pHE1A0c{$CKE5C6S@Z^QY#SAS2JHuqINuM!#;g;~&=)rAe3 z)qiMF|GnVT6?R=JRrNMqli@`XYpMP^1@`yrmNU=!Bv(g6PU3Ym@7CvNRf{(I}0^pwT3aCTTQDqbX=KOQTsD&C+O257cr)df+uA2j1`bfd`tF z6@EEWYT$w3JE-Nxw1LJ#1IP~yz-)QYHqy4%kNT$6wkfs6&~n=*+S-|d)|8rQfHcDZ?ZC8kk_|BF zvN^TY0BPGy=hz-|K2ads{18R9v7}3QsA_DMa1J8j%wN3!so_vl!zm&0ToZ{$%~F@& z_R}D!?1Mnqk%Okh`4EX131q=V7kJy4Z4XzxF>8RgAI;#+Qb#o?y9~-Kg@Y|+;UMZV zh_Vz8ZZ3lxOX2V&X5HH=s|ADcS^I94%ha+G&^6YzJxg|yY5vVK;Y(mm#HI8T?~zG1h8RV1#~m! z{}JyDv-SVfR_$!9`+2`FKxW+@{XCqY@9qYDmU1_kyA0ASg?9=EU)1N}_}1FN=+qBJ z>-}N0EX4z3bh%qN21PmMfg8>}x~1QvTlzh^rQdTa`#H8=Y4l2?_sQxpTfy}PjXr7g zNuy6~^aYK6Y4l5@UmE?=kYA7<;A00g?@ob~hZ3ah(hrwnm8y1s7^CM_bTJ5qW~0w& z@lHP*09P~Q^>{CBxTXhZf1&`Kb6XHM^}lKT_yZjLLR23OagCVe7{=q)7!ON6QGmx9 z!Z)|##|q%*0eN6R9vF}Z27-?Ur7GdII-kY9{hgUuq!G2aMK7L zh1K`1o(TUv8UDjC?5FMWbQw|OBWip^osX#VQQ@P)M}?0H9}_+%d`$S5@Nwbe!pDV= z3!e}^A$&skgz!n>lfoy3PYRzBJ|%og_>}Nz;nTvWg-;9b(GAWX-Vf#c*7J0BxZp7D z;lmub0JUTGDMuvSjs@EI1_yR9OqYh7CHrW|ZIV73N}wV3Oz^o&H|)DpeaD`$=;tj} zo+$fCkiVMP&)cxbuS(Tlben$O>Ggx|KlYjdS~CX+Xw47$4?N988lVji`@yGJIe1&8 zlm_om9uhtzd`S3^@L}P@!iR+q3m*|aB78*ni11P2qryjpj|v|XJ|=uj_!tMlCz{5k zG%lraDUHV?osiOmlqRG!p`Ip%PYRzDJ{k8kC8a4kmfcS=EPP^Wit4=YKh0q9v8`#d zL42omcYNA3A@^{@3@Ky+H_SNM_HdJpY1b3>@v?;Jr6Dt|mxeys;-rkGdgCp+URpcM z)akp+bnAP@XY&7$IjD`fKHqjlF5dNLBam8Q3V){h3@42Pzx&^B>e-`u{qa_yQZh(8nJVy-}ZwzyQ9NK-;Y=9ezd@p>E z>PmDDleUAj;Y<8r@{tl68oH~LrzV3oM9oil>tOhiQl;uuze*pL`f#j19IJ!Ass9?( zqnSF4Zbai94K<^x84ERIsu>S8t9wPJyE_ zf6y_TFWHUy-}&grY#CM>qbYkBd!~BdLNFHh1j=Uh&%CE`s?+P^))Umn<4!OhQ~&1$ zCryOmPRIe1sNtQV$>Logq{T!$oC$R@5pzN-)(O*MG9JRD)F-LVCv+yCt3ENJQ>JO9 zl%`C{WE~yT!l#8#vk|VRr=`?$kH=XL7tB1)dbAwx@ntfORc>znHMCyEdA#?2c9yAB z{UtJT3t5xU?dM`1-}m}~@A%x^+aLdh7oK?a z?hCKIa@!i}*R?=D+u%B|-^`;-9tgYH1KiDq;Xv5P9>7&FpX3?T_4J^wrw4UCJ@^7Q zw1Gm~KxXEJQ!OomOINu)2fb1GFEzF#s0aoPWy)_-~4p(EqNEJJURK&H${ z^lK#jcUmO!+@xlPNviX^0#o<=W*jxAq&XE|s83Oy?=(-d;Z8qCPfLB8 z>byLk4(gSv?}HCD8r)K!!Bqp#+B3Ln;1N8FI-a#>Z}FI&3pI19nGZGdUX!P|wOIDo z^sL1n^#82IKW+b&XK#IXc!JHo0Q0*ZcWeKjI6havaadVLG@UO(c|>Nk`k&G2f5uk- zISCj3HS&<=tTbn-`7u%Zu>!TOR*+=;jhOmOKDk3 z%dryN1EoCsS1=g5dqu8X4K=H(Sqn95s#y;;>xsLqJ^F2>=p1|zHG@iMIFE$2PK&i$ zpSU3s))(EqUcj?KU_G>=5wB>(EAfaiuH1pE(p;71YNT0Sz4cRt{+a%3GFg+!T5PhG zm@xgd5P@ePe}Lu1^C-Lg>}BRZ7!rnEOB?Sz9gx1_lh zYX(|U+LqFGtOTYuB>OSiJkMwF;sWRPncLw%JYui9JK2A;PjSAVy{+&Diine5P?H6e~)zPdjIw%kF&q;e)w6mo_qYJ81*~wSlvdxug+4yvy@x zBlBq^h`JgH=et5nnJaMAFDPLalrRfQm<1)&qVPrGi^3O$F9}}~z9f7}__FY2;mg98 zlY|5hqfEzxOeAnHLYNo-x~~@&pDLa@nVgHa<5~o2@#$hk3EZuDCf8D-5vVVv_DiWf z#+KVJr}oRK{c>VoUcM6(N|Lj@kcM|54KHG3{WC`vijl?XoSx+IyI8Qr65Ee-1koD! zAqpG~k9>sFCtie;rDk&?t)<~3AD+>FH!oH%guP7~6g|7-<6}~Eb zP57GdHQ{T**M+YOUl+b!Osn!r8l9CiIx9(ZN}w2`TLMoLn0z1`3G|^5+AuBGo+_>* zn3ij|<67j|+SA2~62=xfF0U6Ff%7_tSBLPX3KnBz4Pxz>bkxtuceKwrHvrMc_V9uMr`z{{d#JT z49M-*i}v|>ctd%!p}g5p-fSqZHid5r-xR(nd`tM2@Gaq6!ncKQ3*Q#Lo#f?48s3dG zyccw5;PWZ=UQ&VY~p10VedKKu`S zJSelzsC?Ev#dF#0bKeymdk8Jqxi;9bjJ4-dZE$rhaAg^5&lj~rI)J&al75nlDchON z=F1sw&vJ^K;hh2(%CmPrULabYeWqAZf}isgntkp^i|TW!{ak8~IOO(osr`IvKcCto zPO=Y6sA41eoU))0FDS$d3h{zcU{Uy@@I~Q^!k2_E311SvBz#%;vhZc$%Sj4>hldS3 ztSm1)8)pO02dFK^5$gq+fu5U#o{gLtwU}yytZRWR%UF9U)dqFf0(F*=b_x8gBg=@8 zP346&G7D*B;NCp43&o&du~)Fq<`W-Vb_i*JTK$j&53U;rhr%!NQi~tYk4IV5b_m@|m`+P>M=Rxx)ZZJ4zf163 z&H?W8W}Il2JI*!;RM)yg>faW&w&V!+?`FdNyP0tRZU*-`_#(=zZluiWM#`*iq|8FY zUPQrZT^e)Jn3Kj_(3qFTyfo&eF)t0}nk_uQO8Y-xrR4@#=|AZEeVFp>z4&Z`Lt^%R zT!DL1zV!~Wxx;qfxx|_~ml#NHZKI*}d}=*ku!cu4zTD6TX@>dyF~b`)T8Z#h zr4}r_OCu!QrLpndOBNlo_v4Caz_#MqNB(lbTbhkqnM+%lOIis7Pgd-&r0$nxr6p5` zrVr3cX(CpkVQMb3VS8XDPox!vcts&zQHWPU60AyNRT`_(SPdF$(pZzmnl#pe#=11t zrLiuJb!p^@v~n+Ae!%f7_v4D}Xa&s>+wy8^vzplO*aY$1W-YZ@E7-u17=@|-6T9Xv zuczwksXB&~wI0%_1n+v-;9Zsv1!YCygO^9wluaMN{)>*3L9w@z%Yvf7BV*U?3f3`NT8bU^OeBRJp+a;<2FFfD?yHB4(4Ho zpB#jrSVlg!I~KSp7e7Meyin9z#fY^|e7AtZ71j-* zy%e(kcrfl4p*#*dx}UeB`*}O@{XC4#5`fe0t~7V0xf^MgcW?b<#M)R%dr#VXvG!i7 zy)W&3Y3~Q^Yztz)V8S^q561xp%5WUW)rXMBRyY! zEc{sbvG8NzC&EvJp9nt@ek%M__^I$y;b+3ngr5mN6MioIT==>0bI0=-UBc`9EbtY) zssB*N?+0H$k=0!TRh@mSHHwn8z^_zU3;GXrKkoB|wZJ-S!RyM`xBjh=mU&ZnZs9_t zFGTu6q%Sn6OW~KoFNI$kp7nDjr7J02nNk3$$v54urFm_dnZ`BMT?0&i`o|Fq)zyW)CooP^4{%YMJaJYV!vM?kN)>l z``ig(+PxDu^3b%a+&NJ094L1Vlv{_w4}~8JKNNn1M=|&!>k%Hs(0_OoL;vAXjKVZs zJ}88-QpJ6(gEW?)k;n3&5X&&3N7-BgJ=5n_063b!bqc@mY(S@4- zNq|$l2=KC81bAg50y-1ujgTX~HgcrbMvmflPmZN@ETv;99qZkb6X7SqPlTTcKNWr| z{8ad<@H63O!q0@C2|pKpF8o~hx#Rii-~zGdN#Fvp=jW|25PSWH*z>T(qzfFj_o|7%jdzZM5_sXz@Rz z#dFN%?f4v%eRS;d>0&KE6@N*}{z85E>Zw=-$KzF@C95bjVik81lk2pN>$HvQw2kXx z8zHYT^yuVtVd2Z(^1rYc2EhWE3753tIqdRwd=AS!QLHU^!oP6ceL5OD|}b@ zuJB#qd&2jG?+M=%zAt=V_`dM{;%0Su=Wcwy#H{HHBCyIPFHq(7E|>D)7m)I9!5Dtp zeI{Z zPT3|c&ZK!J&9i4|aYiM+ws$V2b19um>6}W|tRjFp@e+B%Gxrt0XQ}ful8DvU`fBDi zKi+4sta17^Yo1_b&2P_K!P=|K*LRC&z>=Dw-Zc_by&ysDzmOWG3ytDJqqxv0E}r9% zFAWdpH7Q+6=~7B!#ZU8GnNntPCCw{pf@tvsl@X=swP|Oj*JeUNbnP6Z551uio{RLM zH?+@lk#LNkvT!s8IgiJVwhDK&Rk)+A!kw_0xGRlaY3xd4H)!lhV^11;(%1_c`_kB# z#=bQ6AKH-Ixfh>5!nulDjXFI=TaD%2g1*hn_u~fa^px)};z}`9-%Hg&-eweT_w1+Y z`>8rcV(Pw)Ro|VWE&o=m+rM_i?%y1dKvs69u!DY0}GXAy+ zW{U&m&4KdfKzVZzGV4$phtfEd#$nJn3UAjPg}3XD!rOI6(2G8iWM`^E%)^-~4LqoX zJSiEUU4JPLe5{(83Q{KM>_h{d9D;!npy{#x3v&{v6r)!@NJAnP49NjQ!nsaFbe6h- z2ovrwwmFJl13co*VBQ@$vb!O6mWtJnO&yiT)YLgin#Z|jo*#&##~HL)Di7c&mk~Xd zss{&`(LGDmqeqvIRgcafs~?;vPEuEvPQ>-(e)Seop6@T7@E*@^`P+#nB+Lo;l%5!N zKx~(siX2N_SqRd!FRXM=ydZ67cg zvy>x1&1Fzysd^A`85~%u9zD8@&R8anyT-wyGb9cc{fC1^|G}-vS(C%z(&2)`;n*&o z_g`@saSvY51svPuJ)FxPCXKaKfbKk8t0Y z4NG9dCgi>aZTAHjvy`vE&t*_!sd^A{8H8902N##Yg{AOo;n#_;YhGjq)x3l<`*cOk z%P#nb5=%9&WZ!r8Pba}=wJ&;&|G)!3#!>rH^l8)DmtDYTQQaW>taKf%&}gL!KlV_m z`p5o~ejQEy`_uf?ZXNCMsm1J*OZ7DM8klBo!yY;4^DCq;seD=GEB>jsW}gnhm!Bq; z5(wIN;CcwY;$!8f-@wr|K`Q@J9Qc^PFU8eS{eg1_ue_mpdXNp((}Vm{Ts>{bLC7RWq>bIApNA@k+xC&kDaM~s{?{N{||viqj@7)DP8MmXO#FxvWE zq!9XyGiFw3nBV!cR*e+=!W#5_Bh|m#Cx%9*MwJU7nx|7!Y|})W zk5KIuKcRoxUihE-L7sInh&)G|W7Fo?6oaz0z-P?#*7tnu`{RVf06kGKHkL{lj0oZJ z5qUiZ!%{eg;Nb>)uLp6K!r^C^L6N0!5TopPo{*2>;r+m~Zg&F@uYU{=KhPe-BTqvl zSxZQT7G+9nsA*MATc~MMO?#+mPZFrbX4*=Xev^qI;wK_o;t-*;5Tg6v^D)ohR+{od zDy?RUc(l^$pL$M!kXikH0=A}(PuZZM_80ltns##H`LR7#$53#K zE1$bC6nAxtLWu!TR;a<(Wl&`)94uW1L6*Y7jk0hsav6+R3I`9!d8#~yho{OP3lF49 zM+ka{(x5ZcbgHH+)O00@(ZO)>^AsH~;R{m;N=NK}wCWF_bW)QM>SVZ%eQnbjtD{Sc z?mzYDcE!oiMH~L!Raa~SQfBi#o=x3m0}|aY@xwpe)Z~h?`xQ%UUz@S{lpjZmWcpz&$2xiTzzI^e;hJA!gh%Gi= zjV9Te*ZgqiDWK+c7r1b&c|h;gzUq}c;A^SQ1HP8(Jf3T%UT5lXdL1RNoF0hb=aYN2TiLeNh22zJ7r7Na{iL3nJq( z$dFu4hU*Ls24reP#`QtQagbr>50e3DIpuquj3)wkuzX!5Nc)dFzN%65ake!uk@zvY1||~ou#vgUbxI>`B+%xc`NYvk8!cyE z<*Ws&Hnn`krz7z7sjTS%%VsHouPJ@el|b5+K)P_=0cnpcNPF6Wudgsc^;ydVu;dcq zHOcZgXu0Q`6cI3@EF5H91{s#Z!Gm%fF{Ckg+y~hs{yetQ6l6U8K*o;^kg>HgmEbbZ z6KgI=Vp1W%$U44O%{IxCg_|6B@LfF5JUX^ufqw zFk&ejWGH_--D4{!;cdd(gtrNA7v3(sU3hzvC$mB|3#7ZfFk%9cu7igLAHBVJ=b4_sb65Lpt5>gV}SSG0qZti<< zW2socM=paFOW|NdSvVNE3^FW*gNMuD!BRMQPp$t9m>5<;hn-eg?9??65b`eOL$k3o9M^$0{yU5o}eF>(GN@E=!dd! z^kYfL^%^=e!M~+;=o2xzwHkly_=q)Tm>G0y_V0epzx&4z8g^q^!50C!3DfNh4&@1M z^?t?OSUHt336CA$$86C`<9c$Xv$ASPI8@Am?-Cx6C8! zFK*XXk8f0}-t%vZ*SuL>gCA}$)x1@ys`Z*$V0HZO%}P}R{{z~H|HRBznQ!<4?zbIA zBjHoxe%1cpv-&NsM!>Cux9vle*rcH$R+W{iD(=v{$t7hi)%jJ-T2uFAflVE4ctKG2 zCcb}fm2YV!N=;@*{Tuk4J-~Wv6Ti{pKVeoc&H7w3PZ#Ao*34Nr;gT*Z*te6(KNFkLTilGo8Isl>wA6r zYGT?Q`qgfeT5pQgLCz*Psy9>p&sN(3(k%7nTs_ZHFn3RZIZJsE%v}a)mcqf8vT(3; z83b7h2RD~Ni=}Xoq3pPL*qTyD3wZb<5FTXtHZur3xPp9BrEs{+GYvd^mlZs|2{#;g zZo^S-1TwA!GJZh8J(QK(@Qf`>d-I18a7zAAc*d5K%DR@2hpnn<4K;15X$v*&s%d{H zSJ7UcC@oABT)9`O{vV%PTR2(r2m4x>GX6b#{?Z@%Hfw*!>ey4W^-Vu-@=i!At?^mo zN)TYr8SJY(xmrENz@ zXu(sUbo^Xfd1`?(tPK%B_Eh;56ou^XgoA@alsCQDGi-k_A`^u7E z%|_>D^L<=Ys*5houw7KwdNQm>&}ugm)qc*dSjJ?KUJ)6`L5AfA z8Wfhj-wq!5F|V5Uv(FjV;y=EQRu}%O*MABI?!}otA2m09KGb3Q{Ll35Kk3`@_dR^n zn!9`N`;+f5&TiaW3;ZE2YeB!GssGf1Eq~wfuz}LVocO6LkEC;?0f~wDwU};M0-U6t)5~x}wca%WYrn*1l z&48*;bzsTz{bw7zDi=xYk!5;Ko(>yUbP4^2Gyad3&JcISg8UV-1Y% zp>wY5LCYHfBUb_$WA;ZQMJw*89OvwTnG`|Jj^ydExNDIBzTv5?bp z9JEO9eV!g7MvlWheyAgem>+0AOy@L({l;=y2;etGiU z?67OZ0m6@%nLhr@RGOf|?wa1?H_e*fXW1N(W+Bavh=kpnjx)4Dx!l0i=aQo#PUyy1 zy^#scgS?S8JgORL!?y#Q%mz_u;t}ZwVY%tOXTu}rE4aKVH@#o1C^gHhnZ3}gIW=o8 znrUG6Ta528HW-tqCdTBchvE2T7e?YS#2`F|;O}!hsC(*ywc}vTQn>=xx(s?Og~P#= z9TyK;<7N)H)lFKdc=9y;O8TV~JqTG^ZR!cFnaM%1D&hSRje^IvFn%cCc zHf^#o^3342)TS-9X_rlVl=vl#EdTLJ3@;P%gvS^?RxOGxJWE-z1AUiUg~Pv;g@dZg zZNlMY%ECd*Wzb?N9AqHp2@f8gqu^m@m;cgdYLM|nMVS5Q0vQ80J-flkvlWbd#{&`f z9S<<_dNA@s28<}{&;cWt!HA`BFhXZyWDE2UgJ*1kjwIkK`W=|c`KCz+rt;`5P|V03 z3V#Q~&l|%XoLza_x5H*vOsJjAYW$2%wyNkf8$-0Z=#*v`HMI=qJj`Fe>58ZNE~$5G z>g}c`&AQ+71r@JVyWe-g+Q>5y%sn%}oTV}Zd|d`#mcqf3vhXg|gOk^Tj+dapQuXK@ za-MPM*YgAYdLF=8o^0sXlMVg))DG|XslfWhDW?SQupjY4|6IAhN4wC!@W=A7mHM|j z{+tJSrGEGzJdi;>dIqCM&+qiuGZ+|ej~+1V72f-j2a($qy)Q@M?R_N@`d-Y0N)>;Y zuaAblwV_|-It&dG`U!tE)@YnhJU&5vb==Af^5dMr)zjMoDiZQqkFb0-- zm|O$WOZs zzQ`5V;7clBRtXJ$erre?L(&+M#!%20md3C&hNUqq4S5F<9^jKXzEz~(Wa8sF`pG7G zR)V4qhW-;lV94@i|9u_(a+{Pu(tdPDzs+Qtm8zHhZYAiNpr3D(IvD5bpq;COy9sQ4 zI0-K3yR!%6`~f+CK*0f3$3c~)@Im2V>Gg0WOW{MpLCfpGh^27wfGjs+1wMvA@>hw* z;Nnf+s2)e1e^0D5{z~u#836@ECI{BkHYZTwUO~XC(xaq9Ncn&+Ng8v3b;E1BL0-jwf5ayiq-Cj2L4na6$hpb`ezXU`~ENgJH){LKR2 zf=6|~?uPeEy`PUX@yfaXL3UT1ZzCus$bF2}a~{KFsawq$lFJwpOW_y;W#QoKGFY+{ z4wf#17E9rvMLE9Zj1^1hFKai(G35>5`7-_RD4s9VIS-GP=|4PL#{UMP#~p~l*L2={ zUF8FoLqTIm8bi_;lE!e*7?#GcG=^Ox&*%X@QN~|F8{iXV-|w5s19ZRtR-xCF2OfMZ z>@(OS3a1|_+oeHikZt%Q@a4hRawU6IqCEI|S`jtl4^s@q_Cu-tP-;Jv+7Ac&?0xs) zw2|Sok>O$^c{;%%9$xr@rNTX+uwt-YkKnKrJ}4Z6@p^=YrErACWsqel9D$)6r;D9W z5kgMKV@MbMhjiKTGwT@AWsB$7S2+idzozo_>UEo&#~)O$7S6aKB}SAIBT9)8CCzBi z7?sATG)ARioPNaTYU9M|Yz&)b2O6H8 zl@)3@+(Qh9vrIxgp%6|egcAzkgohC465*4=CxuT6pAtSLd`kF~@M+=G!l#8#Cn;h) zEL_IJON9+QTs9u?E1h6uApE+|6<}n-Klrz8Ish#be!~&tW^EZaFV!ZvxeRVP1w#o0 zb>cPQ^%!{&kUV`p5MLvtzMZAX>iYw5vw`rGYRc zciQm0-bWh=w2?p?Kbz~v-Nmh6-1>DFvH$Jri||5%otJg53}f&yVhk*GudP=&_ z@Lu74!okh)KH+`B`-OvtW%YURi)Q_Ft@$8Py<+vfN|L|Cu{(C$7Z}1JB z&EHh{mddwvb{9S*d`S3^@L}P@!iR+qJDwjM1HA9+*R}j4$pCNu>hx}5%O=Tzw{xL9 z_(podezRCn8hp#=)E}@Tygc~!p9-r2Kj9Ch_Cu*XhGcUx?T1qP;naROwa1WUA5Qqi zM&t!#A-v#Ofw8kxmSDs#BN{A)V`!9x4+%#!ydKn93P&_tMl@IoM>HUdhn>!m9Xw%= zAv^RRvSXVk@i**bjT7dZDuJv4$+K%jQ6Ev%M-=rDWyPrQQQ@P)M}=GeIT{Ic6Db9(Z)#9bGiAfr0);D6xZ_Ta(tEtPM36Z!?`H?mVQUQy?z@|)Rt8E8|@(Z)9mu$|A9 zAn=MO0HVWE34vx^Ms!#T$G9QqiH1=*KB*GXrGBhu=Y8H=;6885kzyc6wb6xQ_9Pec^3ONQ2#u(iZJ=&hfpj^ffSgIbJ zT?RRps_#>Mzv|JqmS*l@JIG5{E__&e=HAMjQo-E$ftvWce3Ti0B)3N z2@;-p7$1OrOm0VOX+?a4Z*`a#trC|-|Xuv9&Q;WC22QuPP|QW4f_CrW?y+Omg0w9)G9$5K}SV z1R0m+IOlNP6)^Q|beKZnMUN`H$Wqw=YA(ZjELD&0T}JmTRgcbGMrSOO>_ODL{*nC& ze`LSHAKAa~$MV2RdJ@s+s(A|k*(w>Qcm0=7fTqP2)+BNScQ`VmNhAt)(JiuZ&E&;}AMU?w>y z>XrxNt#uC5;}d@415C386?oC+Cv0i_y}p4p_1<^ek0$#n?FGLRF!g>`!vW;h)3pAp z-tKfTG_~ADqd}Ts6drzzf~C?1#9f|L{iN!_)ayZ#rEoaYWjK?iaCnh&l6LT-*FUm< z;eVt&kajcS-qsB6!B{%X=-$>0?!1^h8#HF6F)NK(Y0L$UIcdyEV@?|LL1SJT^U|1i zjXc3;;u~5<$u_4z$u`jZUe+)AN*v^Df6Z=V8ACA5?vxoxvjIudaGPJE8t;G6W-hid z+GYdVp`X9xHW)zO&wOlS12G#6B=qy2yA6h9trTyu*|>Zt`H(1M{7zSRM4$D-jjvNQ zdz=aUC@?oe;ELaT4S~1KjH1F%F2tc2jKU_2J`6$NJn&Bgax zOp|SzW?*jWxj?d<<5%&p!h)a4*6`nAzVid;ti&8YXnVBbYkI4?S-r}e)m&$+@|gy* zT%$T)(_7=sYA!a`Xv6nK*7Qunx~XIM>+v&B>+v&B>-jTJdCG2MXunn%+D3R93axE~ z2cb-0YrKZsh#!C2qz!kcH)$h*HoVB)6xS_r-QuV?_$?b1dfB2AIMi)Afg0QL*|s#d zsTqCY)J_SyuQG#M1C|amy00>$`!2J>XNAuSpA|kQd`|eB@Hyf0!smt03!hK!+knTg zWixmTdHK5J2b96X>%qhIz=N{gRDm6Mu#`P`P?kM-xITDzJ$O)ddj%MoYW@%pmbz5} zf4FS^Fx>p%rEvJeW%EZkn9V6pPD*f!ft=6HGX|Pxyp$3=16ho)XaOT%Jis$7b-x9S zTm~bS!r>Xp!okR8kYOntWLyRhmcro~%JKaiJ9~hypEDH61x0c}kzN$OD11@)qVOf* zOTw3gF9}~3zASuM_;Qjd;NcNmP^7_wvLX#09$E14dhnpEOaTwg{ct|WXvR0eTJq`N z1Zyr6jGM*CxOu5qfm}QBFdi=z-IXM| zrUwu49{h?8K09Zwm9*$p!~b zFKn7`QMt)SA9(iLq&j`PY3k6}VifoU6+JnyO)~bd zKpp@L#2tb$u#|(q++~nvDI9z$3kOw~!IGtL5Of&?Sqk444l<}04?Dr@=F5z3zRc+6 z%ZzT$%nF|sJ}Z1y_?+-L;d8?0gwG3~7d|h1KDoIA9yYo1mJ7((5ebQ=-_acd03ta(!uqwz|NhNYA+nyiaF zLop;*!f05=V?auQz8NtCLEi*jjGAUZ*fw|fK;PQ}X_iHnx)^;tOB+g_uwdy41ePq7 zQ1GR(7`Y5aEQP~wF2iLkg(Cqd$BBr94gF>9X7;fj5_UxiwW5StQ9`XG z2?ag#p%ygsE>{k)Fhl~q%bVM)Z)o9y2Og5830q?j&~?+y36Sct+8?r)H0DgVP?E@R9r zg|7++Ew2YHmcqe^vT%@b8Dv-r2M@@~GsHLam$jSO$9lGKf$z0Q!9xf74-XwA%a!=4 zwT<|xwGBRj#ueNKpFpFqNgM79Y{pNmZN^WnZPJD-p)J~Qk+>y)Z^hx;Vq~~ovdsrh zv|wQ%CD2Af5~B_I5Cd@^Vhk+hBQSRvq*)3FRhPk%rEn0WEF834-WI+sd^`3%Vgz@& zUT8P7kM(SA6K$_>%DDcdF0O$WMDW%k1-KC`fbJH8mpJj8Mct&?5$J zEEQ4Eav6+R3I`*X!HA`BFrq9Rj9dm8mcqdUa-I<%3LdtGD8}42%6UJPC*2kL^yl2C zE4-gd2d^jutMBj=@2jSX<8bvo$dxJ{oU8dkSt+gQM(!Hh<=A@7Y3XgRbXOa+|c~KF6k6Be0t^#P%tnC zuLpgW!oi%faFBKxd|3(yL6^aerEt)qoCNzr!2@fG9o!N6b>HCH4gc+h|MqbgjXxc; z|5Er5uYjr!diakG$i>K?9o_HQiSJ_V=q}C<@8a-7LA$))!>_FG+I#esD!yQ`i(kwM z4?XVUhkhV<+QAS%=k`%Vw6v#w_GrkhvOP02kT(7ytjK1cJ@T_Y`!w{YFjd2DU-;pV zO!z#q&sO}^iytlQkAb?!6|7n6bzg9G8RS?B2QSLPLBwTnU@06uLY6Zz#~#4j_8W%y zkKV?Ahp5xf+vz7YjzZ0mYK}wAar`42i1@*a{BHiiODbPRnSI;LI^p8_P-=%#JEWR_ z#i@Mw%8TKJ1HXVilJ=3bk7DhkRQp)k$I?EIwT}zh{9Woixxn653cqE)+wgpLVlej`E{b;o+!8{QE*Eq%C9`2P`9D-_=8O(m@{xGkR6QZ$Yg$n z>y!s_x9@PC#xuaFW`I*WcHz^r7k&2NymCfO-itVs<{6Lj|LLP2vm>u`P6N&-=QP08 zE`B7)zx{L0IRa;@O4aXWCSjh4LWPmJ>o6#mawW#$GKRoXIEYgg4&p9@GfUy1>oRz< z6b@pP<51y%zs4l49}L7KuKy4|{fCGoKO)HS^Spe)@%s$;Wgq{AX7UT&-oIod{%eoK zWjyC!O7oI)K7WPjit1b9mQvM)sey z>?HhZ^@ZI5+<8sq>nb0x+!eknd{_9c@IAw`^Z1^W_N25YrTtiG-;}aRf8R8-d4J#3 zDdYz!2>QGPf-H4QaYwflLD1_#kfm@CbQuI$3I{jJ!okgD&|)bZWFY5Z1rK*Oc(Bx6 zBk*t;JXi{cqbLgp50~L1mcrp9m%)RjaCnAta#!jAw*?u@0|eODKs+HIysne#1D1#S z5ro6nBnv+jekA-z_>u4<;m5*{g&zw)c0A7@BgFHz5&Cg|EAZg;;z_!E@F1x;e6?6n z0!^D@b&^l*4^#W2#J+s=THIiz>Yjg#?kH{OC~fGt&`_o7@A!EfgU;C;r;QvJ8_ARP zz>>*-(ydZe?XSuoup$0v2|{G>m$_811glj2S#wwk!7|}@f5-);;-OLz5i^jB353n_ z6k%g2C4|kCaQ_3%A$AV>Fie&bVkf0a)&GZuK%U-U?&$`;ER}u-N>32Do5*cpFA>ii)?ACi9j@^{@3zULrw!#pi9LQfa?o~6`rEqu}a-NnKA!ROqHElTb>)7Txw!z@C2J+Oy2t6e*B$i4I1l(nefu(Se zrYszMT?SQ_!Vzqj!HuPGFru8K-ac-pY4wKNY5Y;TecVnv^-J@8yu*7|T+zLORTL(g zoi|m!#d25myQ<&K>d^vk((OrOPa1pD*u%FSti^r9v)ORpl!9QIL2&gvRd#eU8-zSV zcX+>*$+^RuZ+yfU6kP)pS;m^X)a3aRR9yp9S;m^+>y_ZkQcC+&`iK6Vl6_ao!wr`1 zez0VzJ8U56GPto64);+O4n{765li79<1)yw6b>?!<2!S>gBSXX>uom%KmB7tIKWS~ zc=;B880G-sLn>sy4s$5AL#Z7~?eJ}?9|=DaekA-z__6R~;m5*{9hbYRD&>PW;^h`T z1aa_Yv7&T9Z~KC-bYMB51Sub;wuh-LSlcY6#o*g7L_0o}s=sH`LHQ_c_d<&&MMaSa;@v?@fj-a_3g2s~STpzyXO850yPHv!3Asrr52 z<$m0lN{9f5P#j7L5wK>iJ?Rk!61|!DuelOp0i}E<24Bw=P-Usy0YR5Ri=}Yn1ZClH zuFG&POW`2nGQ7xAIC6q=lA9OGoeSm8g>vUYxpgW0Quw9tOU%gpsje$&T$u)zNWPM~ zGPP`hbw%Awyv7-xzua+cZbV*P$Iiii4M=%CNU@Akpaf)dW1qFpIqU4b?>Xn*bMd`S3i&ybB$11) zi*F>jf*&1RRIQ6;B=|SLMa$)46bUX5N(5`* z0;{P1`Af)`k*`Gl1^8cp{{{G87_TRbKh5!V{weBDU&`kO|1<}jc`W;7T_s zbIYIQP*h4agZx>J$$@iL8%2DbmEs$zcYljfYwSqa757GfS*EHzRwuZ-%s zL-g%VF^}X7RHUtoZzQ+~Mh6!y>!KA2E=JZxCK6n3jNUk7{VVfm_WP)Q%C1o9ksE=} zD?RwD{1`&c?UWw*>kBVt`}c*HkS`-&iTon?7s0;>{zdRFfqx17OWBO94&(bI7;sxK6=bmEF-Bnc*#*2OIn{0rdX z=5jHL1XsdD2NxOZA`=NNGScCp+nvuh`)&5qZ1+*^l;?&d=$A>*FO#5OCV{^K{uS`A zfPV%2tKeS+|0?)b!M_InHSn*2f9>c6@bs6PdHCfcX{7A_@{v4Ld_?gu1)u&(v;J3_ z^}o`ruOZa+zuK(-)n@&#HtTDMb^Wh3>wm3T|7*?qazRzUz6unNQs_(Rgo09({933Y zQ{TO?msVYWJT*WAoEN6hpas|qu0u_l7qll5BWl+(IeEF$6#nuxvDo zaTP%I+@XYPLMsjhVdEtt8nHgh?)!(rCJy+9*zlLw4NA5c zVe^;#AlhH@%Vd9<&4_=%EU)MNrC-O>Rx-PowDFDXVo*{CC9Ow!_4Lbbl9-Em9dm`z z$!QlVImOqBA-)*3yLRkIF}J1oT8ghNMYV438yI~9BN4QbqFoy)gZF^bo&N6}5*Kne6nahJ?(3iW^vM&f zw0oh9IK*c(ur zd;xp`d;xp`d=Y#Rd=Y%H;X3i~_m0HlrIID$;cQX=&K~t2{S8mOy=0I2eY553pZZlx zvPk`X>4&R;t5aWq>hv?EQFW?}uLr*bxh2RgL2e21%izo4%izo4E8r{OE8r{OtKh5P ztKh4R!HY*B{Wlytkuh13d7+RNnNJoeG9{)?ccp`FJIkez923D8bip~^f77jqs{JXd zj=QKH*6uI0WvfQTynMSXUvU@xqWqzsylD(>Kw~I{KVRznQTaUdzcs8Om2byOX?Oj* zl)OPRg%vl^?)tw|>~?CO&;B&m&d(s0{8;> zBKRWsBKRWs68IAM68IAMGWat1GWat13it~63it~6D)=h+D)_4LdM;7FNrN{%^(lw{ zr;$ICIad9C!sTyDoYb%5rG69eHzs}5Z{i59extt;X}lvo|2^^eS>*qK{5j$SJ_kMr zJ_kMzJ`X+*J`cVCz5u=ez5u=mz6ibuzG%GmkJ`6?)V^0SwP$~*J&#}QmAw6@z8chM zCV-!fQ`D%PphhL@!7tJI5}hy6`4Z%o!I#08!I!~Tz*oRmz*oRm!B@do!B-oD6OUZ` zKO3InVZP#FDISsF;t?HOJgkdHB)E837mrAA@reF&9bX&N)5F=HC-}dB{6*xONe1va z@Hy}~@Okig@Okig@CEP%@CEP%@I~-N@I~-N<8?C3{d_)dD<1yZqIi6$Tpa4(AH!Au z9-sR6*Yeb_KVYhUU9NupwMO;pdg|BZ>NoNik#8<`^!v0{p*N>r@p`WGCthl|BcI^E z1pg)YFEPGl@MZ92@MZ87@D=bC@D=b?@Kx|t@YRNYmKcwCcs$|}`Q}LRur3~vzxdQE zo?O7i!(Sm34}Wz~JZvW(mg3>B6N-oZEgrTPkNRW6@|SqnPvYS?iHGALf27}^&sPh@ z!~Mt~zjK7gF9cWr{tBY{x1IXY*A8pHud(p1vGA_3@UF2iuY<3HuY<3HZ-8%rZ-8%r zZ-Q@vZ-Q?&7IyI{lhB*I){2Y=DKe3)up(2I(x0=D7(FZH`@MA*EnA9~rD*w+xS|#P z4cRP-){DhWqUCbY@+L~OylE0G&5d>1ZoEz>oqj&K@%qER-f;Mrwvj>AUb(gW-)yQz zee0t}BY&#M+UNK1$vu2>51-t_XItP~;9KBZ;M?Ha;M?Ha;5*UMWM8-QsktkqxRboFoQjgow}{zN z#OkC}@{3sQO|i2V#V*p`G`reJ>T~lF1`sAm&ne$D-kMn5`3|;g;s0>B980LCviFa`E z=8wuyyz72jA|G7j`NM|woVdnje~r!l8k_w!ZU)xD*TL7p*TFZyH^4W*H^4W+H^Dc- zHybw-;^BosJR;fri-&dbhy)jp=-}dET|6Se#lyOIM1qS)^v;_LO@SLfU1jLT&m8{s z#_BJ%VmE3uk#Cf2cm1Dr;%ZBJk0IY<$oClXJzTN{z6HJoz6HJwz74(&z74(uz5~7k zzSD52czDef4-Zv5B5{d$lnd-9JUfYuL6M2XNMyYK5gC_@Oyrxge-at*Z$!rBA`|(h zqx(>ivByLvvi6*o6hM(MC=!)hHSq(Afk82-L3Qs!bst%Sb&l`iuw5Lsi^Fzt=zZ|} z;P=7rgYSXwf$xFuf$xLwgYSdyHv%Fa-g}CNeIy={_*y*tMQri#-cda4Tk)`+`Zcb8 zU9R@kUdQB)Gt|DFq4sM}sc{#o@yJe4BXprg7}*JGc%_*?%p_hcs>?wGEy^DpN&g3U z;{o1yfHxlCtyT+zRtt+(3zJr>vT0W~?aHQI*|aO0gUaThvN@=14jP_U-$yn&;^=XT zV`S%e3qh9_g8Xp=7r7LR9Df|aMJ&Z4#ve6ar{x;A+H2fuuW_rr#;y1|_&WGH_&WFo z_y+g}_y+hU_$K%!_-5l)UOb#$;t|QMws`nfR6JZR9=>G~52vSi_?AjMjEjeFQN_cz zsOsP4>fiO$zwGPr--F*h_}zowJ@{{dZ-H-tZ-H-vZ-Z}xZ-eiE?||=s?=;3Q9_A?? z9L1lB$7^66O>JumDlsGvy;^f#APM%{6Cy%xz2A(xr zVsIq?)_63r#-oWf9!;$A=wcmw9ef>p9ee|P1AGH~1AG&F6MPeVv+?Ld{hOfrKf>=G z_3u&t9`*0h{ucNa_!jsU_%`@9_%`@9_zw6E_zw6E_%8S^_%8S^_u58+s%|T^zP}v+* zHV4?8{3T8}{}u9=k-x%;CwMn_H+VOA4|orF4|osw<$smOm6v}R`74pVl=o8JOZgSb zuTXx4@;=J@DDR{EcZ2tU_kj0+_kf@J*Wr8WSCMZ){#xW| z@YCR@!B2yq0Y3wN2K)^8S@5&qXTi^cp94P!eh&N`_<8X2;OD{5gI@r@0Db}d0{BJn zi{KZ*FM?kJzXW~>{1W(O@XO$r!7qdNg7<>=g7<=70lxx%1^fzlA9x>lA9x@5$+r^k zUq`+T`5VL^yc@h5yc@g+ya&7oya)W$TZ#9tBj1Mn4dM@e8vHc)Y49`PXTZ;Zp8-D$ zeir;J_*w9C;OD^4fu93E4}Ko}JotI=3*Z;PFMwYFzX*O2{37^8@JryAz%PMc0>2D? z8T>N%W$<3`UhrPEyp8-Dueg^yu_*w9?;Ag?lf}aCF2YwFxoN@MQ zddWuZ`^l%;k9=pO+O@8BBPmyV*4195%j>=0dHOw1zvt=qJiIP|UjV-VegXU<_(kxG z;1|I!fnNf@1b(S8F7OT_x>essZjig-tSyz9Nl&c@>>L;=>-pla044=#JxeVW4 z@Lup<@LupM;8(z}fL{Uc1MdUx1Mh2$SN)rx`j3Ra`n9fpBPmyZ*41AmQo3P(DHV z1m%;IPf|Wf`Bm1ntE_8RS=X+zuJwcWgZG2?gI@!`27V3v8u$SC0Qdm-0QhzA>)_YH zuY(VQ4}uSZ4}#wSzX5&&{08_C_z?II_z?I_@SET_!Eb^OgAaobgAap`fRBKWfRBKW zf{%iaf{%iafscWYfscWYgO7ucgO7txfKPx=fKPx=f=_}^f=_~9WnH_1y22>2-YDEKJ&DEJuo82A|Y82C8&IQTgD zIQRtk1o#B_1o$NQB={uwB=}XJ_J4lJ_J707_a&_KlL98fAwo!{YFx*{;aFNNXpfZb@db381GH^+=S0f z_}ql=F!(U|F!(U|2>1y22>1y2DEKJ&DEMe&yz1Zl)PE%W)vtB+8%eqPv#$OkDOW$% z)lX#Sc)zcG;-U7N?KazcR69Cj8e?2zjBAW>jWO(urd>(urd;xp` zd;xp`d=Y#Rd=Y%nIQ~>TWanqkva6pxm)F)PSNnd3Ej!0Uc7A59_G)`w4@>m7M1M>4 zw*;SM@MZ92@MZ87@D=bC@D=b?@Kx|t@YTk6#KSzr!%v^pub)$^Uq7E#KQ32$^|Rjc z+F4yM^?&)WzID}){N;>ayzMu&=W_9m{AJ{?M5-S@FBfm);vH$cBd^(q-C`ehi+$KF z_F+@tQ{YqJQ{dC!)8NzK)8MzkZ-d_kzYTr|{0{gX@H^ml!S90K1-}bE13m*j13m*j z3qA`z3qA`z2R;Wr2R;Wr4?Yh*4?YjR0KNde0KNde2)+ou2)+ou1il2m1il2m489D$ z489D$0=@#i0=@#i3cd=y3chNbJWw90A75{3&)1pSv#$Edn_n$?*^xKw-)^ygyT$(P z7W=m;@G0;q@G0ut)US2*8%eqPv#$Ok8~dAw+G*B5QcwRVhAv)e5C3RA6)&G9sy)y9@{9c`zu5oc z=XFE;90&QuxcuUMk9bCB|0BOxmtWkj{Ni@if65#CqdWNT4*t7?|L)-5yWn@h?}Fcb zr+<#4eBz{iVg{objAk&JiP7J;(JV%@7|mid8>8|i4&0(`w`kbyqG73SBhl4^b@gDW z#v&W;)v)XawdZ4JwP$yzJ-b)Kuv^94y4sD78%5S`R{M6J$V%4fJ;$Kt7}Oktn!|nb z;Pc?~;Pc=M;0xdj;0xf3;EUjk;ETpCQEFv1{yN*Vx6bvCCZtUk6_YUkBd+-vHkL-vHkP-vr+T z-|XBaYkcD2@Qa5pBk^z=h=+NrU#ElmwXS|_uYPQ&_Jr4Ia*uxR(eFL_y$7!?@GbBy z@GbCd@NMvI@NMuN@E!0S@SVoE)Sr2(zT>L;o^4gny6Sm4P(6*S=D$n(yR^Sc`@8gi zAN)S}eenC>d*FNEd*FNE`{4WF`{4Tx{_5X;SO50A`nOcS9+&#H|JAR{)sOAfkMDBS zuIL1lA* z%^Ex5HFm;l?1b0YDX)XCgRg_HgKvOufNy|rfNz3tf^ULvHg?+LVIPWzuVL|Uu84=P zdGRnV9+7WB{#xwC!{y=;2`(Pi#Um12Jfa(~bMGE}@4@#TeD5)yE$}VyE$}VyZSZaI zZSZaI9q=9S9q^roKg7eiB_8G}9+8YkJe+Id;d1ea1Q!oqTjJqz@o-Lxhuakomy1W_ zTQZl5$C0^Y7rwjj-G%Qi*uruRZ#EKz|SD?*V*TEeu*MELts0TCK{aUD>pMwPT;wYQM!= zzERd{|5~SZP*pidmF(JD2c?Skc&&qCtv}Yd!&u`EV~snEHSR#x!Pmjp!Pmhzz&F4* z-ewlrNpAc`2c=CcH?iD|Wr%IYvV8Y}Yon}FZ;h|TE$h_Vl1t*|*od32S#irX`?lDN zn{!UwY$tBM#>LHcqUBr`Bga;Z+`bq&#v*ehC-1@h9?b7`GQU@t%aL0cZDF((qkNKX zE5rLyZ=JWX+{SVnmu}Ft~Ua7mrA83dKWuJ%#PUcNf09@ZHUzlM44Sy8r7P`Efr!N&?@Hc|Os$ zhxuO2lO%gF&kx`2;rD$k_hT7<@5l1LvcLCjS*MlwIz7bK;}YLU#x1J3#7PUWw7m#A zT|~pU7#zLSVe$Y!JP3(!e*oQ9>#ZFQXtlzcOSqL9aY3t9RcWUR#qCtdCsJDNQl(tb z?aKO~So;z$hW?|nIlyL(jrQ8xJGiX9!%C>EWnUg;9rN{=XF%)4T&=IiGC|q+O}CNn z_-({I5!p!ViO5E&qq>_h&nNmeW1cQIW1b;z+MEFDc2e}cpBMc|_V*%fU3??KMKwCO zSXvjsNN~}zE?SY`A``vy{-MlI?DxgP2_z3@FY!+1BXIf6xOhaWopK5y9@fPp5?nmo zj(E6So{0pPKcv?e;JxBTwRf+0ut-94FQLhIbGG8^1Yirxt++WKHQpjB+p$dCwiA_9 z+os{|H2lwhKqOA=kR3a*Om^&~;c0*NXQvTA@wGq2HxmDfuXRz41Q*Nb;38;U+#tv_`;Uoe4&en%f%y7?f4&qxowuCY$qPZ3y*@Ue@_hR-|fjG z^#t+V?GtNadsyxlOM&}oF5rGF)3!z|i+XwxbB9}`BMHB#S{KVmaB+(cE^gLE zD-v9ctcy`3xX46rq*vil(n9?mNrMN!S%v(8;+K#=c!xv&^oj5H|5!gr2F&`?9<7!t z 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 !Jo@9&ACLaT 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

)33>b^Q5-5!Y!3qNe=|Yoo4XWYl$nxX$*V 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%0AeNh38t&Ca;9UvxS^GMGlE+q33$qrK9JPRH{gE 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 z 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
  • d_>7g9Y~`0CpZ-$hJ(D80Qsf6qksl;Q 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�M8O0&vFgRo!7akt9eLog>)V}># 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 -S!(jhmM9k=41bf4Tq_<`K4 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 p=XFc_MG)_OqclB)88vO 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 M`(smjwbi%(4;b<_l>h($ literal 0 HcmV?d00001 diff --git a/schema.sql b/schema.sql new file mode 100644 index 0000000000..00124b040a --- /dev/null +++ b/schema.sql @@ -0,0 +1,431 @@ +CREATE TABLE IF NOT EXISTS `accounts` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(32) NOT NULL, + `password` varchar(255) NOT NULL DEFAULT '', + `type` int(11) NOT NULL DEFAULT '1', + `premdays` int(11) NOT NULL DEFAULT '0', + `lastday` int(10) unsigned NOT NULL DEFAULT '0', + `key` varchar(20) NOT NULL DEFAULT '0', + `email` varchar(255) NOT NULL, + `warnings` int(11) NOT NULL DEFAULT '0', + `group_id` int(11) NOT NULL DEFAULT '1', + `points` int(11) NOT NULL DEFAULT '0' COMMENT 'used with donation system', + `real_name` varchar(30) NOT NULL, + `location` varchar(30) NOT NULL, + `creation` int(11) NOT NULL DEFAULT '0', + `purchased_coins` int(11) NOT NULL DEFAULT '0', + `got_mount` tinyint(4) NOT NULL DEFAULT '0', + `hash` varchar(40) NOT NULL DEFAULT '0', + `obtained_mounts` tinyint(3) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`), + KEY `group_id` (`group_id`) +) ENGINE=InnoDB; + +DROP TRIGGER IF EXISTS `ondelete_accounts`; +DELIMITER // +CREATE TRIGGER `ondelete_accounts` BEFORE DELETE ON `accounts` + FOR EACH ROW BEGIN + DELETE FROM `bans` WHERE `account` = OLD.`id`; +END +// +DELIMITER ; + +CREATE TABLE IF NOT EXISTS `account_viplist` ( + `account_id` int(11) NOT NULL COMMENT 'id of account whose viplist entry it is', + `player_id` int(11) NOT NULL COMMENT 'id of target player of viplist entry', + `description` varchar(128) NOT NULL DEFAULT '', + `icon` tinyint(2) unsigned NOT NULL DEFAULT '0', + `notify` tinyint(1) NOT NULL DEFAULT '0', + UNIQUE KEY `account_player_index` (`account_id`,`player_id`), + KEY `account_id` (`account_id`), + KEY `player_id` (`player_id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `bans` ( + `type` int(11) NOT NULL COMMENT 'this field defines if its ip, accountban or namelock', + `ip` int(10) unsigned NOT NULL DEFAULT '0', + `mask` int(10) unsigned NOT NULL DEFAULT '4294967295', + `player` int(10) unsigned NOT NULL DEFAULT '0', + `account` int(10) unsigned NOT NULL DEFAULT '0', + `time` int(10) unsigned NOT NULL DEFAULT '0', + `reason_id` int(11) NOT NULL DEFAULT '0', + `action_id` int(11) NOT NULL DEFAULT '0', + `comment` varchar(60) NOT NULL DEFAULT '', + `banned_by` int(10) unsigned NOT NULL DEFAULT '0', + KEY `player` (`player`), + KEY `type` (`type`), + KEY `account` (`account`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `groups` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL COMMENT 'group name', + `flags` bigint(20) unsigned NOT NULL DEFAULT '0', + `access` int(11) NOT NULL, + `maxdepotitems` int(11) NOT NULL, + `maxviplist` int(11) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +INSERT INTO `groups` (`id`, `name`, `flags`, `access`, `maxdepotitems`, `maxviplist`) VALUES +(1, 'player', 0, 0, 0, 200), +(2, 'a gamemaster', 137438953471, 1, 0, 0), +(3, 'a god', 127540697997304, 1, 0, 0); + +CREATE TABLE IF NOT EXISTS `guilds` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `ownerid` int(11) NOT NULL, + `creationdata` int(11) NOT NULL, + `motd` varchar(255) NOT NULL, + `description` varchar(255) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`), + UNIQUE KEY `ownerid` (`ownerid`) +) ENGINE=InnoDB; + +DROP TRIGGER IF EXISTS `oncreate_guilds`; +DELIMITER // +CREATE TRIGGER `oncreate_guilds` AFTER INSERT ON `guilds` + FOR EACH ROW BEGIN + INSERT INTO `guild_ranks` (`name`, `level`, `guild_id`) VALUES ('the Leader', 3, NEW.`id`); + INSERT INTO `guild_ranks` (`name`, `level`, `guild_id`) VALUES ('a Vice-Leader', 2, NEW.`id`); + INSERT INTO `guild_ranks` (`name`, `level`, `guild_id`) VALUES ('a Member', 1, NEW.`id`); +END +// +DELIMITER ; + +CREATE TABLE IF NOT EXISTS `guildwar_kills` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `killer` varchar(50) NOT NULL, + `target` varchar(50) NOT NULL, + `killerguild` int(11) NOT NULL DEFAULT '0', + `targetguild` int(11) NOT NULL DEFAULT '0', + `warid` int(11) NOT NULL DEFAULT '0', + `time` bigint(15) NOT NULL, + PRIMARY KEY (`id`), + KEY `warid` (`warid`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `guild_invites` ( + `player_id` int(11) NOT NULL DEFAULT '0', + `guild_id` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`player_id`,`guild_id`), + KEY `player_id` (`player_id`), + KEY `guild_id` (`guild_id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `guild_membership` ( + `player_id` int(11) NOT NULL, + `guild_id` int(11) NOT NULL, + `rank_id` int(11) NOT NULL, + `nick` varchar(15) NOT NULL DEFAULT '', + PRIMARY KEY (`player_id`), + KEY `guild_id` (`guild_id`), + KEY `rank_id` (`rank_id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `guild_ranks` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `guild_id` int(11) NOT NULL COMMENT 'guild', + `name` varchar(255) NOT NULL COMMENT 'rank name', + `level` int(11) NOT NULL COMMENT 'rank level - leader, vice, member, maybe something else', + PRIMARY KEY (`id`), + KEY `guild_id` (`guild_id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `guild_wars` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `guild1` int(11) NOT NULL DEFAULT '0', + `guild2` int(11) NOT NULL DEFAULT '0', + `name1` varchar(255) NOT NULL, + `name2` varchar(255) NOT NULL, + `status` tinyint(2) NOT NULL DEFAULT '0', + `started` bigint(15) NOT NULL DEFAULT '0', + `ended` bigint(15) NOT NULL DEFAULT '0', + `kills` int(10) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `guild1` (`guild1`), + KEY `guild2` (`guild2`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `houses` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `owner` int(11) NOT NULL, + `paid` int(10) unsigned NOT NULL DEFAULT '0', + `warnings` int(11) NOT NULL DEFAULT '0', + `name` varchar(255) NOT NULL, + `rent` int(11) NOT NULL DEFAULT '0', + `town_id` int(11) NOT NULL DEFAULT '0', + `bid` int(11) NOT NULL DEFAULT '0', + `bid_end` int(11) NOT NULL DEFAULT '0', + `last_bid` int(11) NOT NULL DEFAULT '0', + `highest_bidder` int(11) NOT NULL DEFAULT '0', + `size` int(11) NOT NULL DEFAULT '0', + `beds` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `owner` (`owner`), + KEY `town_id` (`town_id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `house_lists` ( + `house_id` int(11) NOT NULL, + `listid` int(11) NOT NULL, + `list` text NOT NULL, + KEY `house_id` (`house_id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `market_history` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `player_id` int(11) NOT NULL, + `sale` tinyint(1) NOT NULL DEFAULT '0', + `itemtype` int(10) unsigned NOT NULL, + `amount` smallint(5) unsigned NOT NULL, + `price` int(10) unsigned NOT NULL DEFAULT '0', + `expires_at` bigint(20) unsigned NOT NULL, + `inserted` bigint(20) unsigned NOT NULL, + `state` tinyint(1) unsigned NOT NULL, + PRIMARY KEY (`id`), + KEY `player_id` (`player_id`,`sale`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `market_offers` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `player_id` int(11) NOT NULL, + `sale` tinyint(1) NOT NULL DEFAULT '0', + `itemtype` int(10) unsigned NOT NULL, + `amount` smallint(5) unsigned NOT NULL, + `created` bigint(20) unsigned NOT NULL, + `anonymous` tinyint(1) NOT NULL DEFAULT '0', + `price` int(10) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `sale` (`sale`,`itemtype`), + KEY `created` (`created`), + KEY `player_id` (`player_id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `players` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `group_id` int(11) NOT NULL DEFAULT '1', + `account_id` int(11) NOT NULL DEFAULT '0', + `level` int(11) NOT NULL DEFAULT '1', + `vocation` int(11) NOT NULL DEFAULT '0', + `health` int(11) NOT NULL DEFAULT '150', + `healthmax` int(11) NOT NULL DEFAULT '150', + `experience` bigint(20) NOT NULL DEFAULT '0', + `lookbody` int(11) NOT NULL DEFAULT '0', + `lookfeet` int(11) NOT NULL DEFAULT '0', + `lookhead` int(11) NOT NULL DEFAULT '0', + `looklegs` int(11) NOT NULL DEFAULT '0', + `looktype` int(11) NOT NULL DEFAULT '136', + `lookaddons` int(11) NOT NULL DEFAULT '0', + `maglevel` int(11) NOT NULL DEFAULT '0', + `mana` int(11) NOT NULL DEFAULT '0', + `manamax` int(11) NOT NULL DEFAULT '0', + `manaspent` int(11) unsigned NOT NULL DEFAULT '0', + `soul` int(10) unsigned NOT NULL DEFAULT '0', + `town_id` int(11) NOT NULL DEFAULT '0', + `posx` int(11) NOT NULL DEFAULT '0', + `posy` int(11) NOT NULL DEFAULT '0', + `posz` int(11) NOT NULL DEFAULT '0', + `conditions` blob NOT NULL, + `cap` int(11) NOT NULL DEFAULT '0', + `sex` int(11) NOT NULL DEFAULT '0', + `lastlogin` bigint(20) unsigned NOT NULL DEFAULT '0', + `lastip` int(10) unsigned NOT NULL DEFAULT '0', + `save` tinyint(1) NOT NULL DEFAULT '1', + `skull` tinyint(1) NOT NULL DEFAULT '0', + `skulltime` int(11) NOT NULL DEFAULT '0', + `lastlogout` bigint(20) unsigned NOT NULL DEFAULT '0', + `blessings` tinyint(2) NOT NULL DEFAULT '0', + `onlinetime` int(11) NOT NULL DEFAULT '0', + `deletion` bigint(15) NOT NULL DEFAULT '0', + `balance` bigint(20) unsigned NOT NULL DEFAULT '0', + `offlinetraining_time` smallint(5) unsigned NOT NULL DEFAULT '43200', + `offlinetraining_skill` int(11) NOT NULL DEFAULT '-1', + `stamina` smallint(5) unsigned NOT NULL DEFAULT '2520', + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`), + KEY `account_id` (`account_id`), + KEY `group_id` (`group_id`), + KEY `vocation` (`vocation`) +) ENGINE=InnoDB; + +DROP TRIGGER IF EXISTS `oncreate_players`; +DELIMITER // +CREATE TRIGGER `oncreate_players` AFTER INSERT ON `players` + FOR EACH ROW BEGIN + INSERT INTO `player_skills` (`player_id`, `skillid`, `value`) VALUES (NEW.`id`, 0, 10); + INSERT INTO `player_skills` (`player_id`, `skillid`, `value`) VALUES (NEW.`id`, 1, 10); + INSERT INTO `player_skills` (`player_id`, `skillid`, `value`) VALUES (NEW.`id`, 2, 10); + INSERT INTO `player_skills` (`player_id`, `skillid`, `value`) VALUES (NEW.`id`, 3, 10); + INSERT INTO `player_skills` (`player_id`, `skillid`, `value`) VALUES (NEW.`id`, 4, 10); + INSERT INTO `player_skills` (`player_id`, `skillid`, `value`) VALUES (NEW.`id`, 5, 10); + INSERT INTO `player_skills` (`player_id`, `skillid`, `value`) VALUES (NEW.`id`, 6, 10); +END +// +DELIMITER ; +DROP TRIGGER IF EXISTS `ondelete_players`; +DELIMITER // +CREATE TRIGGER `ondelete_players` BEFORE DELETE ON `players` + FOR EACH ROW BEGIN + DELETE FROM `bans` WHERE `type` = 2 AND `player` = OLD.`id`; + UPDATE `houses` SET `owner` = 0 WHERE `owner` = OLD.`id`; +END +// +DELIMITER ; + +CREATE TABLE IF NOT EXISTS `players_online` ( + `player_id` int(11) NOT NULL, + PRIMARY KEY (`player_id`) +) ENGINE=MEMORY; + +CREATE TABLE IF NOT EXISTS `player_deaths` ( + `player_id` int(11) NOT NULL, + `time` bigint(20) unsigned NOT NULL DEFAULT '0', + `level` int(11) NOT NULL DEFAULT '1', + `killed_by` varchar(255) NOT NULL, + `is_player` tinyint(1) NOT NULL DEFAULT '1', + `mostdamage_by` varchar(100) NOT NULL, + `mostdamage_is_player` tinyint(1) NOT NULL DEFAULT '0', + `unjustified` tinyint(1) NOT NULL DEFAULT '0', + `mostdamage_unjustified` tinyint(1) NOT NULL DEFAULT '0', + KEY `player_id` (`player_id`), + KEY `killed_by` (`killed_by`), + KEY `mostdamage_by` (`mostdamage_by`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `player_depotitems` ( + `player_id` int(11) NOT NULL, + `sid` int(11) NOT NULL COMMENT 'any given range eg 0-100 will be reserved for depot lockers and all > 100 will be then normal items inside depots', + `pid` int(11) NOT NULL DEFAULT '0', + `itemtype` smallint(6) NOT NULL, + `count` smallint(5) NOT NULL DEFAULT '0', + `attributes` blob NOT NULL, + UNIQUE KEY `player_id_2` (`player_id`,`sid`), + KEY `player_id` (`player_id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `player_inboxitems` ( + `player_id` int(11) NOT NULL, + `sid` int(11) NOT NULL, + `pid` int(11) NOT NULL DEFAULT '0', + `itemtype` smallint(6) NOT NULL, + `count` smallint(5) NOT NULL DEFAULT '0', + `attributes` blob NOT NULL, + UNIQUE KEY `player_id_2` (`player_id`,`sid`), + KEY `player_id` (`player_id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `player_items` ( + `player_id` int(11) NOT NULL DEFAULT '0', + `pid` int(11) NOT NULL DEFAULT '0', + `sid` int(11) NOT NULL DEFAULT '0', + `itemtype` smallint(6) NOT NULL DEFAULT '0', + `count` smallint(5) NOT NULL DEFAULT '0', + `attributes` blob NOT NULL, + KEY `player_id` (`player_id`), + KEY `sid` (`sid`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `player_skills` ( + `player_id` int(11) NOT NULL DEFAULT '0', + `skillid` tinyint(4) NOT NULL DEFAULT '0', + `value` int(10) unsigned NOT NULL DEFAULT '0', + `count` int(10) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`player_id`,`skillid`), + KEY `player_id` (`player_id`), + KEY `value_count_index` (`value`,`count`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `player_spells` ( + `player_id` int(11) NOT NULL, + `name` varchar(255) NOT NULL, + KEY `player_id` (`player_id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `player_storage` ( + `player_id` int(11) NOT NULL DEFAULT '0', + `key` int(10) unsigned NOT NULL DEFAULT '0', + `value` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`player_id`,`key`), + KEY `player_id` (`player_id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `server_config` ( + `config` varchar(50) NOT NULL, + `value` varchar(256) NOT NULL DEFAULT '', + UNIQUE KEY `config` (`config`) +) ENGINE=InnoDB; + +INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '11'); + +CREATE TABLE IF NOT EXISTS `tile_store` ( + `house_id` int(11) NOT NULL, + `data` longblob NOT NULL, + KEY `house_id` (`house_id`) +) ENGINE=InnoDB; + +ALTER TABLE `accounts` + ADD CONSTRAINT `accounts_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`); + +ALTER TABLE `account_viplist` + ADD CONSTRAINT `account_viplist_ibfk_1` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE, + ADD CONSTRAINT `account_viplist_ibfk_2` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE; + +ALTER TABLE `guilds` + ADD CONSTRAINT `guilds_ibfk_2` FOREIGN KEY (`ownerid`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +ALTER TABLE `guildwar_kills` + ADD CONSTRAINT `guildwar_kills_ibfk_1` FOREIGN KEY (`warid`) REFERENCES `guild_wars` (`id`) ON DELETE CASCADE; + +ALTER TABLE `guild_invites` + ADD CONSTRAINT `guild_invites_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE, + ADD CONSTRAINT `guild_invites_ibfk_2` FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`id`) ON DELETE CASCADE; + +ALTER TABLE `guild_membership` + ADD CONSTRAINT `guild_membership_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT `guild_membership_ibfk_2` FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT `guild_membership_ibfk_3` FOREIGN KEY (`rank_id`) REFERENCES `guild_ranks` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +ALTER TABLE `guild_ranks` + ADD CONSTRAINT `guild_ranks_ibfk_1` FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`id`) ON DELETE CASCADE; + +ALTER TABLE `house_lists` + ADD CONSTRAINT `house_lists_ibfk_1` FOREIGN KEY (`house_id`) REFERENCES `houses` (`id`) ON DELETE CASCADE; + +ALTER TABLE `market_history` + ADD CONSTRAINT `market_history_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE; + +ALTER TABLE `market_offers` + ADD CONSTRAINT `market_offers_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE; + +ALTER TABLE `players` + ADD CONSTRAINT `players_ibfk_1` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE, + ADD CONSTRAINT `players_ibfk_2` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`); + +ALTER TABLE `player_deaths` + ADD CONSTRAINT `player_deaths_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE; + +ALTER TABLE `player_depotitems` + ADD CONSTRAINT `player_depotitems_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE; + +ALTER TABLE `player_inboxitems` + ADD CONSTRAINT `player_inboxitems_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE; + +ALTER TABLE `player_items` + ADD CONSTRAINT `player_items_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE; + +ALTER TABLE `player_skills` + ADD CONSTRAINT `player_skills_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE; + +ALTER TABLE `player_spells` + ADD CONSTRAINT `player_spells_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE; + +ALTER TABLE `player_storage` + ADD CONSTRAINT `player_storage_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE; + +ALTER TABLE `tile_store` + ADD CONSTRAINT `tile_store_ibfk_1` FOREIGN KEY (`house_id`) REFERENCES `houses` (`id`) ON DELETE CASCADE; diff --git a/src/account.h b/src/account.h new file mode 100644 index 0000000000..0c260d7f52 --- /dev/null +++ b/src/account.h @@ -0,0 +1,39 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __ACCOUNT__ +#define __ACCOUNT__ + +#include +#include +#include "definitions.h" +#include "enums.h" + +struct Account { + uint32_t id, lastDay, premiumDays, warnings; + AccountType_t accountType; + std::string name, password; + std::list charList; + + Account() { + id = 0; + accountType = ACCOUNT_TYPE_NORMAL; + } +}; + +#endif diff --git a/src/actions.cpp b/src/actions.cpp new file mode 100644 index 0000000000..ca2386d9e1 --- /dev/null +++ b/src/actions.cpp @@ -0,0 +1,592 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "const.h" +#include "player.h" +#include "monster.h" +#include "npc.h" +#include "game.h" +#include "item.h" +#include "container.h" +#include "combat.h" +#include "house.h" +#include "tasks.h" +#include "tools.h" +#include "spells.h" +#include "configmanager.h" +#include "beds.h" + +#include +#include + +#include "actions.h" + +extern Game g_game; +extern Spells* g_spells; +extern Actions* g_actions; +extern ConfigManager g_config; + +Actions::Actions() : + m_scriptInterface("Action Interface") +{ + m_scriptInterface.initState(); +} + +Actions::~Actions() +{ + clear(); +} + +inline void Actions::clearMap(ActionUseMap& map) +{ + for (ActionUseMap::const_iterator it = map.begin(), end = map.end(); it != end; ++it) { + delete it->second; + } + + map.clear(); +} + +void Actions::clear() +{ + clearMap(useItemMap); + clearMap(uniqueItemMap); + clearMap(actionItemMap); + + m_scriptInterface.reInitState(); +} + +LuaScriptInterface& Actions::getScriptInterface() +{ + return m_scriptInterface; +} + +std::string Actions::getScriptBaseName() +{ + return "actions"; +} + +Event* Actions::getEvent(const std::string& nodeName) +{ + if (asLowerCaseString(nodeName) == "action") { + return new Action(&m_scriptInterface); + } else { + return NULL; + } +} + +bool Actions::registerEvent(Event* event, xmlNodePtr p) +{ + Action* action = dynamic_cast(event); + + if (!action) { + return false; + } + + int32_t id, id2, from; + bool success = true; + + if (readXMLInteger(p, "itemid", id)) { + if (useItemMap.find(id) != useItemMap.end()) { + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with id: " << id << std::endl; + return false; + } + + useItemMap[id] = action; + } else if (readXMLInteger(p, "fromid", id) && readXMLInteger(p, "toid", id2)) { + from = id; + + if (useItemMap.find(id) != useItemMap.end()) { + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with id: " << id << " in fromid: " << from << ", toid: " << id2 << std::endl; + success = false; + } else { + useItemMap[id] = action; + } + + while (id < id2) { + id++; + + if (useItemMap.find(id) != useItemMap.end()) { + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with id: " << id << " in fromid: " << from << ", toid: " << id2 << std::endl; + continue; + } + + useItemMap[id] = new Action(action); + } + } else if (readXMLInteger(p, "uniqueid", id)) { + if (uniqueItemMap.find(id) != uniqueItemMap.end()) { + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with uniqueid: " << id << std::endl; + return false; + } + + uniqueItemMap[id] = action; + } else if (readXMLInteger(p, "fromuid", id) && readXMLInteger(p, "touid", id2)) { + from = id; + + if (uniqueItemMap.find(id) != uniqueItemMap.end()) { + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with uniqueid: " << id << " in fromuid: " << from << ", touid: " << id2 << std::endl; + success = false; + } else { + uniqueItemMap[id] = action; + } + + while (id < id2) { + id++; + + if (uniqueItemMap.find(id) != uniqueItemMap.end()) { + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with uniqueid: " << id << " in fromuid: " << from << ", touid: " << id2 << std::endl; + continue; + } + + uniqueItemMap[id] = new Action(action); + } + } else if (readXMLInteger(p, "actionid", id) || readXMLInteger(p, "aid", id)) { + if (actionItemMap.find(id) != actionItemMap.end()) { + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with actionid: " << id << std::endl; + return false; + } + + actionItemMap[id] = action; + } else if (readXMLInteger(p, "fromaid", id) && readXMLInteger(p, "toaid", id2)) { + from = id; + + if (actionItemMap.find(id) != actionItemMap.end()) { + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with actionid: " << id << " in fromaid: " << from << ", toaid: " << id2 << std::endl; + success = false; + } else { + actionItemMap[id] = action; + } + + while (id < id2) { + id++; + + if (actionItemMap.find(id) != actionItemMap.end()) { + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with actionid: " << id << " in fromaid: " << from << ", toaid: " << id2 << std::endl; + continue; + } + + actionItemMap[id] = new Action(action); + } + } else { + success = false; + } + + return success; +} + +ReturnValue Actions::canUse(const Player* player, const Position& pos) +{ + if (pos.x != 0xFFFF) { + const Position& playerPos = player->getPosition(); + + if (playerPos.z > pos.z) { + return RET_FIRSTGOUPSTAIRS; + } else if (playerPos.z < pos.z) { + return RET_FIRSTGODOWNSTAIRS; + } else if (!Position::areInRange<1, 1, 0>(playerPos, pos)) { + return RET_TOOFARAWAY; + } + } + + return RET_NOERROR; +} + +ReturnValue Actions::canUse(const Player* player, const Position& pos, const Item* item) +{ + Action* action = getAction(item); + + if (action) { + return action->canExecuteAction(player, pos); + } + + return RET_NOERROR; +} + +ReturnValue Actions::canUseFar(const Creature* creature, const Position& toPos, bool checkLineOfSight) +{ + if (toPos.x == 0xFFFF) { + return RET_NOERROR; + } + + const Position& creaturePos = creature->getPosition(); + + if (creaturePos.z > toPos.z) { + return RET_FIRSTGOUPSTAIRS; + } else if (creaturePos.z < toPos.z) { + return RET_FIRSTGODOWNSTAIRS; + } else if (!Position::areInRange<7, 5, 0>(toPos, creaturePos)) { + return RET_TOOFARAWAY; + } + + if (checkLineOfSight && !g_game.canThrowObjectTo(creaturePos, toPos)) { + return RET_CANNOTTHROW; + } + + return RET_NOERROR; +} + +Action* Actions::getAction(const Item* item) +{ + if (item->getUniqueId() != 0) { + ActionUseMap::const_iterator it = uniqueItemMap.find(item->getUniqueId()); + + if (it != uniqueItemMap.end()) { + return it->second; + } + } + + if (item->getActionId() != 0) { + ActionUseMap::const_iterator it = actionItemMap.find(item->getActionId()); + + if (it != actionItemMap.end()) { + return it->second; + } + } + + ActionUseMap::const_iterator it = useItemMap.find(item->getID()); + + if (it != useItemMap.end()) { + return it->second; + } + + //rune items + Action* runeSpell = g_spells->getRuneSpell(item->getID()); + + if (runeSpell) { + return runeSpell; + } + + return NULL; +} + +ReturnValue Actions::internalUseItem(Player* player, const Position& pos, + uint8_t index, Item* item, uint32_t creatureId) +{ + if (Door* door = item->getDoor()) { + if (!door->canUse(player)) { + return RET_CANNOTUSETHISOBJECT; + } + } + + Action* action = getAction(item); + + if (action) { + int32_t stack = item->getParent()->__getIndexOfThing(item); + PositionEx posEx(pos, stack); + + if (action->isScripted()) { + if (action->executeUse(player, item, posEx, posEx, false, creatureId)) { + return RET_NOERROR; + } + } else if (action->function) { + if (action->function(player, item, posEx, posEx, false, creatureId)) { + return RET_NOERROR; + } + } + } + + if (BedItem* bed = item->getBed()) { + if (!bed->canUse(player)) { + return RET_CANNOTUSETHISOBJECT; + } + + if (bed->trySleep(player)) { + player->setBedItem(bed); + g_game.sendOfflineTrainingDialog(player); + } + + return RET_NOERROR; + } + + if (Container* container = item->getContainer()) { + Container* openContainer = NULL; + + //depot container + if (DepotLocker* depot = container->getDepotLocker()) { + DepotLocker* myDepotLocker = player->getDepotLocker(depot->getDepotId()); + myDepotLocker->setParent(depot->getParent()->getTile()); + openContainer = myDepotLocker; + player->setDepotChange(true); + player->setLastDepotId(depot->getDepotId()); + } else { + openContainer = container; + } + + if (container->getCorpseOwner() != 0 && !player->canOpenCorpse(container->getCorpseOwner())) { + return RET_YOUARENOTTHEOWNER; + } + + //open/close container + int32_t oldcid = player->getContainerID(openContainer); + + if (oldcid != -1) { + player->onCloseContainer(openContainer); + player->closeContainer(oldcid); + } else { + player->addContainer(index, openContainer); + player->onSendContainer(openContainer); + } + + return RET_NOERROR; + } + + if (item->isReadable()) { + if (item->canWriteText()) { + player->setWriteItem(item, item->getMaxWriteLength()); + player->sendTextWindow(item, item->getMaxWriteLength(), true); + } else { + player->setWriteItem(NULL); + player->sendTextWindow(item, 0, false); + } + + return RET_NOERROR; + } + + return RET_CANNOTUSETHISOBJECT; +} + +bool Actions::useItem(Player* player, const Position& pos, uint8_t index, Item* item, bool isHotkey) +{ + if (!player->canDoAction()) { + return false; + } + + player->setNextActionTask(NULL); + player->setNextAction(OTSYS_TIME() + g_config.getNumber(ConfigManager::ACTIONS_DELAY_INTERVAL)); + player->stopWalk(); + + if (isHotkey) { + showUseHotkeyMessage(player, item->getID(), player->__getItemTypeCount(item->getID(), -1)); + } + + ReturnValue ret = internalUseItem(player, pos, index, item, 0); + + if (ret != RET_NOERROR) { + player->sendCancelMessage(ret); + return false; + } + + return true; +} + +bool Actions::useItemEx(Player* player, const Position& fromPos, const Position& toPos, + uint8_t toStackPos, Item* item, bool isHotkey, uint32_t creatureId /* = 0*/) +{ + if (!player->canDoAction()) { + return false; + } + + player->setNextActionTask(NULL); + player->setNextAction(OTSYS_TIME() + g_config.getNumber(ConfigManager::EX_ACTIONS_DELAY_INTERVAL)); + player->stopWalk(); + + Action* action = getAction(item); + + if (!action) { + player->sendCancelMessage(RET_CANNOTUSETHISOBJECT); + return false; + } + + ReturnValue ret = action->canExecuteAction(player, toPos); + + if (ret != RET_NOERROR) { + player->sendCancelMessage(ret); + return false; + } + + if (isHotkey) { + showUseHotkeyMessage(player, item->getID(), player->__getItemTypeCount(item->getID(), -1)); + } + + int32_t fromStackPos = item->getParent()->__getIndexOfThing(item); + PositionEx fromPosEx(fromPos, fromStackPos); + PositionEx toPosEx(toPos, toStackPos); + + if (!action->executeUse(player, item, fromPosEx, toPosEx, true, creatureId)) { + if (!action->hasOwnErrorHandler()) { + player->sendCancelMessage(RET_CANNOTUSETHISOBJECT); + } + + return false; + } + + return true; +} + +void Actions::showUseHotkeyMessage(Player* player, int32_t id, uint32_t count) +{ + const ItemType& it = Item::items[id]; + std::ostringstream ss; + + if (!it.showCount) { + ss << "Using one of " << it.name << "..."; + } else if (count == 1) { + ss << "Using the last " << it.name << "..."; + } else { + ss << "Using one of " << count << " " << it.getPluralName() << "..."; + } + + player->sendTextMessage(MSG_INFO_DESCR, ss.str()); +} + +bool Actions::hasAction(const Item* item) +{ + return getAction(item) != NULL; +} + +Action::Action(LuaScriptInterface* _interface) : + Event(_interface) +{ + allowFarUse = false; + checkLineOfSight = true; + function = NULL; +} + +Action::Action(const Action* copy) : + Event(copy) +{ + allowFarUse = copy->allowFarUse; + checkLineOfSight = copy->checkLineOfSight; + function = copy->function; +} + +Action::~Action() +{ + // +} + +bool Action::configureEvent(xmlNodePtr p) +{ + int32_t intValue; + + if (readXMLInteger(p, "allowfaruse", intValue)) { + if (intValue != 0) { + setAllowFarUse(true); + } + } + + if (readXMLInteger(p, "blockwalls", intValue)) { + if (intValue == 0) { + setCheckLineOfSight(false); + } + } + + return true; +} + +bool Action::loadFunction(const std::string& functionName) +{ + const std::string& tmpFunctionName = asLowerCaseString(functionName); + + if (tmpFunctionName == "increaseitemid") { + function = increaseItemId; + } else if (tmpFunctionName == "decreaseitemid") { + function = decreaseItemId; + } else if (tmpFunctionName == "market") { + function = enterMarket; + } else { + std::cout << "[Warning - Action::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl; + return false; + } + + m_scripted = false; + return true; +} + +bool Action::increaseItemId(Player* player, Item* item, const PositionEx& posFrom, const PositionEx& posTo, bool extendedUse, uint32_t creatureId) +{ + Item* newItem = g_game.transformItem(item, item->getID() + 1); + g_game.startDecay(newItem); + return true; +} + +bool Action::decreaseItemId(Player* player, Item* item, const PositionEx& posFrom, const PositionEx& posTo, bool extendedUse, uint32_t creatureId) +{ + Item* newItem = g_game.transformItem(item, item->getID() - 1); + g_game.startDecay(newItem); + return true; +} + +bool Action::enterMarket(Player* player, Item* item, const PositionEx& posFrom, const PositionEx& posTo, bool extendedUse, uint32_t creatureId) +{ + if (!g_config.getBoolean(ConfigManager::MARKET_ENABLED)) { + player->sendTextMessage(MSG_INFO_DESCR, "The market is disabled."); + return false; + } + + if (player->getLastDepotId() == -1) { + return false; + } + + player->sendMarketEnter(player->getLastDepotId()); + return true; +} + +std::string Action::getScriptEventName() +{ + return "onUse"; +} + +ReturnValue Action::canExecuteAction(const Player* player, const Position& toPos) +{ + if (!getAllowFarUse()) { + return g_actions->canUse(player, toPos); + } else { + return g_actions->canUseFar(player, toPos, getCheckLineOfSight()); + } +} + +bool Action::executeUse(Player* player, Item* item, const PositionEx& fromPos, const PositionEx& toPos, bool extendedUse, uint32_t creatureId) +{ + //onUse(cid, item, fromPosition, itemEx, toPosition) + if (!m_scriptInterface->reserveScriptEnv()) { + std::cout << "[Error] Call stack overflow. Action::executeUse" << std::endl; + return false; + } + + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + env->setScriptId(m_scriptId, m_scriptInterface); + env->setRealPos(player->getPosition()); + + uint32_t cid = env->addThing(player); + uint32_t itemid1 = env->addThing(item); + + lua_State* L = m_scriptInterface->getLuaState(); + + m_scriptInterface->pushFunction(m_scriptId); + lua_pushnumber(L, cid); + LuaScriptInterface::pushThing(L, item, itemid1); + LuaScriptInterface::pushPosition(L, fromPos, fromPos.stackpos); + Thing* thing = g_game.internalGetThing(player, toPos, toPos.stackpos); + + if (thing && (!extendedUse || thing != item)) { + uint32_t thingId2 = env->addThing(thing); + LuaScriptInterface::pushThing(L, thing, thingId2); + LuaScriptInterface::pushPosition(L, toPos, toPos.stackpos); + } else { + LuaScriptInterface::pushThing(L, NULL, 0); + Position posEx; + LuaScriptInterface::pushPosition(L, posEx, 0); + } + + bool result = m_scriptInterface->callFunction(5); + m_scriptInterface->releaseScriptEnv(); + return result; +} diff --git a/src/actions.h b/src/actions.h new file mode 100644 index 0000000000..ee1d66c787 --- /dev/null +++ b/src/actions.h @@ -0,0 +1,122 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __ACTIONS__ +#define __ACTIONS__ + +#include "position.h" + +#include +#include "luascript.h" +#include "baseevents.h" +#include "thing.h" + +class Action; + +class Actions : public BaseEvents +{ + public: + Actions(); + virtual ~Actions(); + + bool useItem(Player* player, const Position& pos, uint8_t index, Item* item, bool isHotkey); + bool useItemEx(Player* player, const Position& fromPos, const Position& toPos, + uint8_t toStackPos, Item* item, bool isHotkey, uint32_t creatureId = 0); + + ReturnValue canUse(const Player* player, const Position& pos); + ReturnValue canUse(const Player* player, const Position& pos, const Item* item); + ReturnValue canUseFar(const Creature* creature, const Position& toPos, bool checkLineOfSight); + + bool hasAction(const Item* item); + + protected: + ReturnValue internalUseItem(Player* player, const Position& pos, + uint8_t index, Item* item, uint32_t creatureId); + void showUseHotkeyMessage(Player* player, int32_t id, uint32_t count); + + virtual void clear(); + virtual LuaScriptInterface& getScriptInterface(); + virtual std::string getScriptBaseName(); + virtual Event* getEvent(const std::string& nodeName); + virtual bool registerEvent(Event* event, xmlNodePtr p); + + void registerItemID(int32_t itemId, Event* event); + void registerActionID(int32_t actionId, Event* event); + void registerUniqueID(int32_t uniqueId, Event* event); + + typedef std::map ActionUseMap; + ActionUseMap useItemMap; + ActionUseMap uniqueItemMap; + ActionUseMap actionItemMap; + + Action* getAction(const Item* item); + void clearMap(ActionUseMap& map); + + LuaScriptInterface m_scriptInterface; +}; + +typedef bool (ActionFunction)(Player* player, Item* item, const PositionEx& posFrom, const PositionEx& posTo, bool extendedUse, uint32_t creatureId); + +class Action : public Event +{ + public: + Action(const Action* copy); + Action(LuaScriptInterface* _interface); + virtual ~Action(); + + virtual bool configureEvent(xmlNodePtr p); + virtual bool loadFunction(const std::string& functionName); + + //scripting + virtual bool executeUse(Player* player, Item* item, const PositionEx& posFrom, + const PositionEx& posTo, bool extendedUse, uint32_t creatureId); + // + + bool getAllowFarUse() const { + return allowFarUse; + } + void setAllowFarUse(bool v) { + allowFarUse = v; + } + + bool getCheckLineOfSight() const { + return checkLineOfSight; + } + void setCheckLineOfSight(bool v) { + checkLineOfSight = v; + } + + virtual ReturnValue canExecuteAction(const Player* player, const Position& toPos); + virtual bool hasOwnErrorHandler() { + return false; + } + + ActionFunction* function; + + protected: + virtual std::string getScriptEventName(); + + static ActionFunction increaseItemId; + static ActionFunction decreaseItemId; + static ActionFunction enterMarket; + + bool allowFarUse; + bool checkLineOfSight; +}; + +#endif diff --git a/src/admin.cpp b/src/admin.cpp new file mode 100644 index 0000000000..1e8afc2794 --- /dev/null +++ b/src/admin.cpp @@ -0,0 +1,719 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "admin.h" +#include "game.h" +#include "connection.h" +#include "outputmessage.h" +#include "networkmessage.h" +#include "configmanager.h" +#include "house.h" +#include "iologindata.h" +#include "ban.h" +#include "tools.h" +#include "rsa.h" + +#include "logger.h" + +static void addLogLine(ProtocolAdmin* conn, eLogType type, int level, const std::string& message); + +extern Game g_game; +extern ConfigManager g_config; +extern Ban g_bans; + +AdminProtocolConfig* g_adminConfig = NULL; + +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ +uint32_t ProtocolAdmin::protocolAdminCount = 0; +#endif + +ProtocolAdmin::ProtocolAdmin(Connection_ptr connection) : + Protocol(connection) +{ + m_state = NO_CONNECTED; + m_loginTries = 0; + m_lastCommand = 0; + m_startTime = time(NULL); +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + protocolAdminCount++; +#endif +} + +ProtocolAdmin::~ProtocolAdmin() +{ +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + protocolAdminCount--; +#endif +} + +void ProtocolAdmin::onRecvFirstMessage(NetworkMessage& msg) +{ + //is the remote admin protocol enabled? + if (!g_adminConfig->isEnabled()) { + getConnection()->closeConnection(); + return; + } + + m_state = NO_CONNECTED; + + //is allowed this ip? + if (!g_adminConfig->allowIP(getIP())) { + addLogLine(this, LOGTYPE_EVENT, 1, "ip not allowed"); + getConnection()->closeConnection(); + return; + } + + //max connections limit + if (!g_adminConfig->addConnection()) { + addLogLine(this, LOGTYPE_EVENT, 1, "cannot add new connection"); + getConnection()->closeConnection(); + return; + } + + addLogLine(this, LOGTYPE_EVENT, 1, "sending HELLO"); + //send hello + OutputMessage_ptr output = OutputMessagePool::getInstance()->getOutputMessage(this, false); + + if (output) { + output->AddByte(AP_MSG_HELLO); + output->AddU32(1); //version + output->AddString("OTADMIN"); + output->AddU16(g_adminConfig->getProtocolPolicy()); //security policy + output->AddU32(g_adminConfig->getProtocolOptions()); //protocol options(encryption, ...) + OutputMessagePool::getInstance()->send(output); + } + + m_lastCommand = time(NULL); + m_state = ENCRYPTION_NO_SET; +} + +void ProtocolAdmin::deleteProtocolTask() +{ + addLogLine(NULL, LOGTYPE_EVENT, 1, "end connection"); + g_adminConfig->removeConnection(); + Protocol::deleteProtocolTask(); +} + +void ProtocolAdmin::parsePacket(NetworkMessage& msg) +{ + if (g_game.getGameState() == GAME_STATE_SHUTDOWN) { + getConnection()->closeConnection(); + return; + } + + uint8_t recvbyte = msg.GetByte(); + + OutputMessagePool* outputPool = OutputMessagePool::getInstance(); + + OutputMessage_ptr output = outputPool->getOutputMessage(this, false); + + if (!output) { + return; + } + + switch (m_state) { + case ENCRYPTION_NO_SET: { + if (g_adminConfig->requireEncryption()) { + if ((time(NULL) - m_startTime) > 30000) { + getConnection()->closeConnection(); + addLogLine(this, LOGTYPE_WARNING, 1, "encryption timeout"); + return; + } + + if (recvbyte != AP_MSG_ENCRYPTION && recvbyte != AP_MSG_KEY_EXCHANGE) { + output->AddByte(AP_MSG_ERROR); + output->AddString("encryption needed"); + outputPool->send(output); + getConnection()->closeConnection(); + addLogLine(this, LOGTYPE_WARNING, 1, "wrong command while ENCRYPTION_NO_SET"); + return; + } + + break; + } else { + m_state = NO_LOGGED_IN; + } + } + + case NO_LOGGED_IN: { + if (g_adminConfig->requireLogin()) { + if ((time(NULL) - m_startTime) > 30000) { + //login timeout + getConnection()->closeConnection(); + addLogLine(this, LOGTYPE_WARNING, 1, "login timeout"); + return; + } + + if (m_loginTries > 3) { + output->AddByte(AP_MSG_ERROR); + output->AddString("too many login tries"); + outputPool->send(output); + getConnection()->closeConnection(); + addLogLine(this, LOGTYPE_WARNING, 1, "too many login tries"); + return; + } + + if (recvbyte != AP_MSG_LOGIN) { + output->AddByte(AP_MSG_ERROR); + output->AddString("you are not logged in"); + outputPool->send(output); + getConnection()->closeConnection(); + addLogLine(this, LOGTYPE_WARNING, 1, "wrong command while NO_LOGGED_IN"); + return; + } + + break; + } else { + m_state = LOGGED_IN; + } + } + + case LOGGED_IN: { + //can execute commands + break; + } + + default: { + getConnection()->closeConnection(); + return; + } + } + + m_lastCommand = time(NULL); + + switch (recvbyte) { + case AP_MSG_LOGIN: { + if (m_state == NO_LOGGED_IN && g_adminConfig->requireLogin()) { + std::string password = msg.GetString(); + + if (g_adminConfig->passwordMatch(password)) { + m_state = LOGGED_IN; + output->AddByte(AP_MSG_LOGIN_OK); + addLogLine(this, LOGTYPE_EVENT, 1, "login ok"); + } else { + m_loginTries++; + output->AddByte(AP_MSG_LOGIN_FAILED); + output->AddString("wrong password"); + addLogLine(this, LOGTYPE_WARNING, 1, "login failed.(" + password + ")"); + } + } else { + output->AddByte(AP_MSG_LOGIN_FAILED); + output->AddString("can not login"); + addLogLine(this, LOGTYPE_WARNING, 1, "wrong state at login"); + } + + break; + } + + case AP_MSG_ENCRYPTION: { + if (m_state == ENCRYPTION_NO_SET && g_adminConfig->requireEncryption()) { + uint8_t keyType = msg.GetByte(); + + if (keyType == ENCRYPTION_RSA1024XTEA) { + RSA* rsa = g_adminConfig->getRSAKey(ENCRYPTION_RSA1024XTEA); + + if (!rsa) { + output->AddByte(AP_MSG_ENCRYPTION_FAILED); + addLogLine(this, LOGTYPE_WARNING, 1, "no valid server key type"); + break; + } + + if (RSA_decrypt(rsa, msg)) { + m_state = NO_LOGGED_IN; + uint32_t k[4]; + k[0] = msg.GetU32(); + k[1] = msg.GetU32(); + k[2] = msg.GetU32(); + k[3] = msg.GetU32(); + + //use for in/out the new key we have + enableXTEAEncryption(); + setXTEAKey(k); + + output->AddByte(AP_MSG_ENCRYPTION_OK); + addLogLine(this, LOGTYPE_EVENT, 1, "encryption ok"); + } else { + output->AddByte(AP_MSG_ENCRYPTION_FAILED); + output->AddString("wrong encrypted packet"); + addLogLine(this, LOGTYPE_WARNING, 1, "wrong encrypted packet"); + } + } else { + output->AddByte(AP_MSG_ENCRYPTION_FAILED); + output->AddString("no valid key type"); + addLogLine(this, LOGTYPE_WARNING, 1, "no valid client key type"); + } + } else { + output->AddByte(AP_MSG_ENCRYPTION_FAILED); + output->AddString("can not set encryption"); + addLogLine(this, LOGTYPE_EVENT, 1, "can not set encryption"); + } + + break; + } + + case AP_MSG_KEY_EXCHANGE: { + if (m_state == ENCRYPTION_NO_SET && g_adminConfig->requireEncryption()) { + uint8_t keyType = msg.GetByte(); + + if (keyType == ENCRYPTION_RSA1024XTEA) { + RSA* rsa = g_adminConfig->getRSAKey(ENCRYPTION_RSA1024XTEA); + + if (!rsa) { + output->AddByte(AP_MSG_KEY_EXCHANGE_FAILED); + addLogLine(this, LOGTYPE_WARNING, 1, "no valid server key type"); + break; + } + + output->AddByte(AP_MSG_KEY_EXCHANGE_OK); + output->AddByte(ENCRYPTION_RSA1024XTEA); + char RSAPublicKey[128]; + rsa->getPublicKey(RSAPublicKey); + output->AddBytes(RSAPublicKey, 128); + } else { + output->AddByte(AP_MSG_KEY_EXCHANGE_FAILED); + addLogLine(this, LOGTYPE_WARNING, 1, "no valid client key type"); + } + } else { + output->AddByte(AP_MSG_KEY_EXCHANGE_FAILED); + output->AddString("can not get public key"); + addLogLine(this, LOGTYPE_WARNING, 1, "can not get public key"); + } + + break; + } + + case AP_MSG_COMMAND: { + if (m_state != LOGGED_IN) { + addLogLine(this, LOGTYPE_ERROR, 1, "recvbyte == AP_MSG_COMMAND && m_state != LOGGED_IN !!!"); + // We should never reach this point + break; + } + + uint8_t command = msg.GetByte(); + + switch (command) { + case CMD_BROADCAST: { + const std::string message = msg.GetString(); + addLogLine(this, LOGTYPE_EVENT, 1, "broadcast: " + message); + g_dispatcher.addTask(createTask(boost::bind(&Game::broadcastMessage, &g_game, message, MSG_STATUS_WARNING))); + output->AddByte(AP_MSG_COMMAND_OK); + break; + } + + case CMD_CLOSE_SERVER: { + g_dispatcher.addTask(createTask(boost::bind(&ProtocolAdmin::adminCommandCloseServer, this))); + break; + } + + case CMD_PAY_HOUSES: { + g_dispatcher.addTask(createTask(boost::bind(&ProtocolAdmin::adminCommandPayHouses, this))); + break; + } + + case CMD_OPEN_SERVER: { + g_dispatcher.addTask(createTask(boost::bind(&ProtocolAdmin::adminCommandOpenServer, this))); + break; + } + + case CMD_SHUTDOWN_SERVER: { + g_dispatcher.addTask(createTask(boost::bind(&ProtocolAdmin::adminCommandShutdownServer, this))); + getConnection()->closeConnection(); + return; + } + + case CMD_KICK: { + const std::string name = msg.GetString(); + g_dispatcher.addTask(createTask(boost::bind(&ProtocolAdmin::adminCommandKickPlayer, this, name))); + break; + } + + case CMD_SETOWNER: { + const std::string param = msg.GetString(); + g_dispatcher.addTask(createTask(boost::bind(&ProtocolAdmin::adminCommandSetOwner, this, param))); + break; + } + + default: { + output->AddByte(AP_MSG_COMMAND_FAILED); + output->AddString("not known server command"); + addLogLine(this, LOGTYPE_WARNING, 1, "not known server command"); + break; + } + } + + break; + } + + case AP_MSG_PING: { + output->AddByte(AP_MSG_PING_OK); + break; + } + + default: { + output->AddByte(AP_MSG_ERROR); + output->AddString("not known command byte"); + addLogLine(this, LOGTYPE_WARNING, 1, "not known command byte"); + break; + } + } + + if (output->getMessageLength() > 0) { + outputPool->send(output); + } +} + +void ProtocolAdmin::adminCommandCloseServer() +{ + g_game.setGameState(GAME_STATE_CLOSED); + + addLogLine(this, LOGTYPE_EVENT, 1, "close server ok"); + + OutputMessage_ptr output = OutputMessagePool::getInstance()->getOutputMessage(this, false); + + if (output) { + output->AddByte(AP_MSG_COMMAND_OK); + OutputMessagePool::getInstance()->send(output); + } +} + +void ProtocolAdmin::adminCommandPayHouses() +{ + OutputMessage_ptr output = OutputMessagePool::getInstance()->getOutputMessage(this, false); + + if (output) { + if (Houses::getInstance().payHouses()) { + addLogLine(this, LOGTYPE_EVENT, 1, "pay houses ok"); + output->AddByte(AP_MSG_COMMAND_OK); + } else { + addLogLine(this, LOGTYPE_WARNING, 1, "pay houses fail"); + output->AddByte(AP_MSG_COMMAND_FAILED); + output->AddString(" "); + } + + OutputMessagePool::getInstance()->send(output); + } +} + +void ProtocolAdmin::adminCommandOpenServer() +{ + g_game.setGameState(GAME_STATE_NORMAL); + + addLogLine(this, LOGTYPE_EVENT, 1, "open server ok"); + + OutputMessage_ptr output = OutputMessagePool::getInstance()->getOutputMessage(this, false); + + if (output) { + output->AddByte(AP_MSG_COMMAND_OK); + OutputMessagePool::getInstance()->send(output); + } +} + +void ProtocolAdmin::adminCommandShutdownServer() +{ + g_game.setGameState(GAME_STATE_SHUTDOWN); + + addLogLine(this, LOGTYPE_EVENT, 1, "starting server shutdown"); + + OutputMessage_ptr output = OutputMessagePool::getInstance()->getOutputMessage(this, false); + + if (output) { + output->AddByte(AP_MSG_COMMAND_OK); + OutputMessagePool::getInstance()->send(output); + } +} + +void ProtocolAdmin::adminCommandKickPlayer(const std::string& name) +{ + OutputMessage_ptr output = OutputMessagePool::getInstance()->getOutputMessage(this, false); + + if (output) { + Player* player = g_game.getPlayerByName(name); + + if (player) { + player->kickPlayer(false); + addLogLine(this, LOGTYPE_EVENT, 1, "kicked player " + name); + + output->AddByte(AP_MSG_COMMAND_OK); + } else { + addLogLine(this, LOGTYPE_WARNING, 1, "Could not kick player (not online): " + name); + + output->AddByte(AP_MSG_COMMAND_FAILED); + output->AddString("player is not online"); + } + + OutputMessagePool::getInstance()->send(output); + } +} + +void ProtocolAdmin::adminCommandSetOwner(const std::string& param) +{ + OutputMessage_ptr output = OutputMessagePool::getInstance()->getOutputMessage(this, false); + + if (output) { + boost::char_separator sep(", "); + tokenizer cmdtokens(param, sep); + tokenizer::iterator cmdit = cmdtokens.begin(); + std::string _house, name; + _house = parseParams(cmdit, cmdtokens.end()); + name = parseParams(cmdit, cmdtokens.end()); + trimString(_house); + trimString(name); + + if (House* house = Houses::getInstance().getHouse(atoi(_house.c_str()))) { + uint32_t _guid; + + if (IOLoginData::getInstance()->getGuidByName(_guid, name)) { + house->setHouseOwner(_guid); + addLogLine(this, LOGTYPE_EVENT, 1, "set " + name + " as new owner of house with id " + _house); + output->AddByte(AP_MSG_COMMAND_OK); + } else { + addLogLine(this, LOGTYPE_WARNING, 1, "Could not find player with name: " + name); + output->AddByte(AP_MSG_COMMAND_FAILED); + output->AddString("such player does not exists"); + } + } else { + addLogLine(this, LOGTYPE_WARNING, 1, "Could not find house with id: " + _house); + output->AddByte(AP_MSG_COMMAND_FAILED); + output->AddString("such house does not exists"); + } + + OutputMessagePool::getInstance()->send(output); + } +} + +///////////////////////////////////////////// + +AdminProtocolConfig::AdminProtocolConfig() +{ + m_enabled = true; + m_onlyLocalHost = true; + m_maxConnections = 1; + m_currrentConnections = 0; + m_password = ""; + m_key_RSA1024XTEA = NULL; + m_requireLogin = true; + m_requireEncryption = false; +} + +AdminProtocolConfig::~AdminProtocolConfig() +{ + delete m_key_RSA1024XTEA; +} + +bool AdminProtocolConfig::loadXMLConfig() +{ + xmlDocPtr doc = xmlParseFile("data/XML/admin.xml"); + + if (!doc) { + return false; + } + + xmlNodePtr root, p, q; + root = xmlDocGetRootElement(doc); + + if (!xmlStrEqual(root->name, (const xmlChar*)"otadmin")) { + xmlFreeDoc(doc); + return false; + } + + int enabled; + + if (readXMLInteger(root, "enabled", enabled)) { + m_enabled = enabled != 0; + } + + int value; + + p = root->children; + + while (p) { + if (xmlStrEqual(p->name, (const xmlChar*)"security")) { + if (readXMLInteger(p, "onlylocalhost", value)) { + m_onlyLocalHost = value != 0; + } + + if (readXMLInteger(p, "maxconnections", value) && value > 0) { + m_maxConnections = value; + } + + if (readXMLInteger(p, "loginrequired", value)) { + m_requireLogin = value != 0; + } + + std::string password; + + if (readXMLString(p, "loginpassword", password)) { + m_password = password; + } else if (m_requireLogin) { + std::cout << "Security warning: require login but use default password." << std::endl; + } + } else if (xmlStrEqual(p->name, (const xmlChar*)"encryption")) { + if (readXMLInteger(p, "required", value)) { + m_requireEncryption = value != 0; + } + + q = p->children; + + while (q) { + if (xmlStrEqual(q->name, (const xmlChar*)"key")) { + std::string str; + + if (readXMLString(q, "type", str)) { + if (asLowerCaseString(str) == "rsa1024xtea") { + if (readXMLString(q, "file", str)) { + m_key_RSA1024XTEA = new RSA(); + + if (!m_key_RSA1024XTEA->setKey("data/XML/" + str)) { + delete m_key_RSA1024XTEA; + m_key_RSA1024XTEA = NULL; + std::cout << "Can not load key from data/XML/" << str << std::endl; + } + } else { + std::cout << "Missing file for RSA1024XTEA key." << std::endl; + } + } else { + std::cout << str << " is not a valid key type." << std::endl; + } + } + } + + q = q->next; + } + } + + p = p->next; + } + + xmlFreeDoc(doc); + return true; +} + +bool AdminProtocolConfig::isEnabled() const +{ + return m_enabled; +} + +bool AdminProtocolConfig::addConnection() +{ + if (m_currrentConnections >= m_maxConnections) { + return false; + } + + m_currrentConnections++; + return true; +} + +void AdminProtocolConfig::removeConnection() +{ + if (m_currrentConnections > 0) { + m_currrentConnections--; + } +} + +bool AdminProtocolConfig::passwordMatch(std::string& password) +{ + //prevent empty password login + if (m_password == "") { + return false; + } + + return password == m_password; +} + +bool AdminProtocolConfig::allowIP(uint32_t ip) +{ + if (m_onlyLocalHost) { + if (ip != 0x0100007F) { //127.0.0.1 + addLogLine(NULL, LOGTYPE_WARNING, 1, std::string("forbidden connection try from ") + convertIPToString(ip)); + return false; + } else { + return true; + } + } + + return !g_bans.isIpDisabled(ip); +} + +bool AdminProtocolConfig::requireLogin() const +{ + return m_requireLogin; +} + +bool AdminProtocolConfig::requireEncryption() const +{ + return m_requireEncryption; +} + +uint16_t AdminProtocolConfig::getProtocolPolicy() +{ + uint16_t policy = 0; + + if (requireLogin()) { + policy = policy | REQUIRE_LOGIN; + } + + if (requireEncryption()) { + policy = policy | REQUIRE_ENCRYPTION; + } + + return policy; +} + +uint32_t AdminProtocolConfig::getProtocolOptions() +{ + uint32_t ret = 0; + + if (requireEncryption()) { + if (m_key_RSA1024XTEA) { + ret = ret | ENCRYPTION_RSA1024XTEA; + } + } + + return ret; +} + +RSA* AdminProtocolConfig::getRSAKey(uint8_t type) +{ + switch (type) { + case ENCRYPTION_RSA1024XTEA: + return m_key_RSA1024XTEA; + + default: + return NULL; + } +} + +///////////////////////////////////////////// + +static void addLogLine(ProtocolAdmin* conn, eLogType type, int level, const std::string& message) +{ + if (!g_config.getBoolean(ConfigManager::ADMIN_LOGS_ENABLED)) { + return; + } + + std::string logMsg; + + if (conn) { + logMsg = "[" + convertIPToString(conn->getIP()) + "] - "; + } + + logMsg += message; + LOG_MESSAGE("OTADMIN", type, level, logMsg); +} diff --git a/src/admin.h b/src/admin.h new file mode 100644 index 0000000000..55c8a6d61a --- /dev/null +++ b/src/admin.h @@ -0,0 +1,221 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_ADMIN_H__ +#define __OTSERV_ADMIN_H__ + +// -> server +// command(1 byte) | size(2 bytes) | parameters(size bytes) +// commands: +// login +// password(string) +// encryption +// encryption type(1 byte) +// RSA1024+XTEA +// :128 bytes encrypted using 1024 bytes public key +// 16 bytes XTEA key +// key-exchange +// public_key_type(1 byte) +// RSA1024+XTEA +// command +// command + paramters(string) +// no_operation/ping +// nothing +// +// <- server +// ret-code(1 byte)| size(2 bytes) | parameters(size bytes) +// ret-codes: +// hello +// server_version(4 bytes) +// server_string(string) +// security_policy(2 bytes flags) +// required_login +// required_encryption +// accepted_encryptions(4 bytes flags) +// RSA1024+XTEA +// key-exchange-ok +// public_key_type(1 byte) +// RSA1024+XTEA +// :128 bytes public key modulus +// key-exchange-failed +// reason(string) +// login-ok +// nothing +// login-failed +// reason(string) +// command-ok +// command result(string) +// command-failed +// reason(string) +// encryption-ok +// nothing +// encryption-failed +// reason(string) +// no_operation-ok +// nothing +// message +// message(string) +// error +// message(string) +// + +#include "player.h" +#include "logger.h" +#include + +class NetworkMessage; +class RSA; + +enum { + // + AP_MSG_LOGIN = 1, + AP_MSG_ENCRYPTION = 2, + AP_MSG_KEY_EXCHANGE = 3, + AP_MSG_COMMAND = 4, + AP_MSG_PING = 5, + // + AP_MSG_HELLO = 1, + AP_MSG_KEY_EXCHANGE_OK = 2, + AP_MSG_KEY_EXCHANGE_FAILED = 3, + AP_MSG_LOGIN_OK = 4, + AP_MSG_LOGIN_FAILED = 5, + AP_MSG_COMMAND_OK = 6, + AP_MSG_COMMAND_FAILED = 7, + AP_MSG_ENCRYPTION_OK = 8, + AP_MSG_ENCRYPTION_FAILED = 9, + AP_MSG_PING_OK = 10, + AP_MSG_MESSAGE = 11, + AP_MSG_ERROR = 12 +}; + +enum { + CMD_BROADCAST = 1, + CMD_CLOSE_SERVER = 2, + CMD_PAY_HOUSES = 3, + CMD_OPEN_SERVER = 4, + CMD_SHUTDOWN_SERVER = 5, + //CMD_RELOAD_SCRIPTS = 6, + //CMD_PLAYER_INFO = 7, + //CMD_GETONLINE = 8, + CMD_KICK = 9, + //CMD_BAN_MANAGER = 10, + //CMD_SERVER_INFO = 11, + //CMD_GETHOUSE = 12, + CMD_SETOWNER = 13 +}; + + +enum { + REQUIRE_LOGIN = 1, + REQUIRE_ENCRYPTION = 2 +}; + +enum { + ENCRYPTION_RSA1024XTEA = 1 +}; + +class AdminProtocolConfig +{ + public: + AdminProtocolConfig(); + ~AdminProtocolConfig(); + + bool loadXMLConfig(); + + bool isEnabled() const; + + bool addConnection(); + void removeConnection(); + + bool requireLogin() const; + bool requireEncryption() const; + + uint16_t getProtocolPolicy(); + uint32_t getProtocolOptions(); + + bool allowIP(uint32_t ip); + + bool passwordMatch(std::string& password); + + RSA* getRSAKey(uint8_t type); + + protected: + bool m_enabled; + bool m_onlyLocalHost; + int32_t m_maxConnections; + int32_t m_currrentConnections; + + std::string m_password; + + bool m_requireLogin; + bool m_requireEncryption; + + RSA* m_key_RSA1024XTEA; +}; + +class ProtocolAdmin : public Protocol +{ + public: + // static protocol information + enum {server_sends_first = false}; + enum {protocol_identifier = 0xFE}; // Not required as we send first + enum {use_checksum = false}; + static const char* protocol_name() { + return "admin protocol"; + } + +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + static uint32_t protocolAdminCount; +#endif + ProtocolAdmin(Connection_ptr connection); + virtual ~ProtocolAdmin(); + + virtual int32_t getProtocolId() { + return 0xFE; + } + + virtual void parsePacket(NetworkMessage& msg); + + virtual void onRecvFirstMessage(NetworkMessage& msg); + + protected: + virtual void deleteProtocolTask(); + + void adminCommandCloseServer(); + void adminCommandPayHouses(); + void adminCommandOpenServer(); + void adminCommandShutdownServer(); + void adminCommandKickPlayer(const std::string& name); + void adminCommandSetOwner(const std::string& param); + + enum ConnectionState_t { + NO_CONNECTED, + ENCRYPTION_NO_SET, + ENCRYPTION_OK, + NO_LOGGED_IN, + LOGGED_IN, + }; + + private: + int32_t m_loginTries; + ConnectionState_t m_state; + time_t m_lastCommand; + time_t m_startTime; +}; + +#endif diff --git a/src/ban.cpp b/src/ban.cpp new file mode 100644 index 0000000000..336cc091bc --- /dev/null +++ b/src/ban.cpp @@ -0,0 +1,364 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "definitions.h" + +#include "ban.h" +#include "iologindata.h" +#include "configmanager.h" +#include "tools.h" +#include "database.h" + +extern ConfigManager g_config; + +bool Ban::acceptConnection(uint32_t clientip) +{ + boost::recursive_mutex::scoped_lock lockClass(banLock); + + uint64_t currentTime = OTSYS_TIME(); + + IpConnectMap::iterator it = ipConnectMap.find(clientip); + + if (it == ipConnectMap.end()) { + ConnectBlock cb; + cb.lastAttempt = currentTime; + cb.blockTime = 0; + cb.count = 1; + + ipConnectMap[clientip] = cb; + return true; + } + + ConnectBlock& connectBlock = it->second; + + if (connectBlock.blockTime > currentTime) { + connectBlock.blockTime += 225; + return false; + } + + connectBlock.lastAttempt = currentTime; + + int64_t timeDiff = currentTime - connectBlock.lastAttempt; + + if (timeDiff <= 8000) { + if (++connectBlock.count > 3) { + connectBlock.count = 0; + + if (timeDiff <= 800) { + connectBlock.blockTime = currentTime + 3000; + return false; + } + } + } else { + connectBlock.count = 1; + } + + return true; +} + +void Ban::addLoginAttempt(uint32_t clientip, bool isSuccess) +{ + if (clientip == 0) { + return; + } + + boost::recursive_mutex::scoped_lock lockClass(banLock); + + time_t currentTime = time(NULL); + + IpLoginMap::iterator it = ipLoginMap.find(clientip); + + if (it == ipLoginMap.end()) { + LoginBlock lb; + lb.lastLoginTime = 0; + lb.numberOfLogins = 0; + + ipLoginMap[clientip] = lb; + it = ipLoginMap.find(clientip); + } + + LoginBlock& loginBlock = it->second; + + if (loginBlock.numberOfLogins >= (uint32_t)g_config.getNumber(ConfigManager::LOGIN_TRIES)) { + loginBlock.numberOfLogins = 0; + } + + uint32_t retryTimeout = (uint32_t)g_config.getNumber(ConfigManager::RETRY_TIMEOUT) / 1000; + + if (!isSuccess || ((uint32_t)currentTime < (uint32_t)loginBlock.lastLoginTime + retryTimeout)) { + ++loginBlock.numberOfLogins; + } else { + loginBlock.numberOfLogins = 0; + } + + loginBlock.lastLoginTime = currentTime; +} + +bool Ban::isIpDisabled(uint32_t clientip) +{ + uint32_t maxLoginTries = g_config.getNumber(ConfigManager::LOGIN_TRIES); + + if (maxLoginTries == 0 || clientip == 0) { + return false; + } + + boost::recursive_mutex::scoped_lock lockClass(banLock); + + time_t currentTime = time(NULL); + + IpLoginMap::iterator it = ipLoginMap.find(clientip); + + if (it != ipLoginMap.end()) { + uint32_t loginTimeout = (uint32_t)g_config.getNumber(ConfigManager::LOGIN_TIMEOUT) / 1000; + + if ((it->second.numberOfLogins >= maxLoginTries) && ((uint32_t)currentTime < (uint32_t)it->second.lastLoginTime + loginTimeout)) { + return true; + } + } + + return false; +} + +bool IOBan::isIpBanished(uint32_t clientip) +{ + if (clientip == 0) { + return false; + } + + Database* db = Database::getInstance(); + + DBQuery query; + query << "SELECT `ip`, `mask`, `time` FROM `bans` WHERE `type` = " << BAN_IPADDRESS; + + DBResult* result = db->storeQuery(query.str()); + + if (!result) { + return false; + } + + uint32_t currentTime = time(NULL); + + do { + uint32_t ip = result->getDataInt("ip"); + uint32_t mask = result->getDataInt("mask"); + + if ((ip & mask) == (clientip & mask)) { + uint32_t time = result->getDataInt("time"); + + if (time == 0 || currentTime < time) { + db->freeResult(result); + return true; + } + } + } while (result->next()); + + db->freeResult(result); + return false; +} + +bool IOBan::isPlayerNamelocked(const std::string& name) +{ + uint32_t playerId; + std::string playerName = name; + + if (!IOLoginData::getInstance()->getGuidByName(playerId, playerName)) { + return true; + } + + Database* db = Database::getInstance(); + + DBQuery query; + query << "SELECT COUNT(*) AS `count` FROM `bans` WHERE `type` = " << NAMELOCK_PLAYER << " AND `player` = " << playerId << " LIMIT 1"; + + DBResult* result = db->storeQuery(query.str()); + + if (!result) { + return false; + } + + int32_t numRows = result->getDataInt("count"); + db->freeResult(result); + return numRows > 0; +} + +bool IOBan::isAccountBanned(uint32_t account) +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "SELECT COUNT(*) as `count` FROM `bans` WHERE `type` = " << BAN_ACCOUNT << " AND `account` = " << account << " AND `time` > " << time(NULL) << " LIMIT 1"; + + DBResult* result = db->storeQuery(query.str()); + + if (!result) { + return false; + } + + int32_t numRows = result->getDataInt("count"); + db->freeResult(result); + return numRows > 0; +} + +bool IOBan::getBanInformation(uint32_t account, uint32_t& bannedBy, uint32_t& banTime, int32_t& reason, int32_t& action, std::string& comment, bool& deletion) +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "SELECT `banned_by`, `time`, `reason_id`, `action_id`, `comment` FROM `bans` WHERE `type` = " << BAN_ACCOUNT << " AND `account` = " << account << " AND `time` > " << time(NULL) << " LIMIT 1"; + + DBResult* result = db->storeQuery(query.str()); + + if (result) { + bannedBy = result->getDataInt("banned_by"); + banTime = result->getDataInt("time"); + reason = result->getDataInt("reason_id"); + action = result->getDataInt("action_id"); + comment = result->getDataString("comment"); + db->freeResult(result); + deletion = false; + return true; + } + + query.str(""); + query << "SELECT `banned_by`, `time`, `reason_id`, `action_id`, `comment` FROM `bans` WHERE `type` = " << DELETE_ACCOUNT << " AND `account` = " << account << " LIMIT 1"; + + result = db->storeQuery(query.str()); + + if (result) { + bannedBy = result->getDataInt("banned_by"); + banTime = result->getDataInt("time"); + reason = result->getDataInt("reason_id"); + action = result->getDataInt("action_id"); + comment = result->getDataString("comment"); + db->freeResult(result); + deletion = true; + return true; + } + + return false; +} + +int32_t IOBan::getNotationsCount(uint32_t account) +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "SELECT COUNT(*) AS `count` FROM `bans` WHERE `type` = " << NOTATION_ACCOUNT << " AND `account` = " << account; + + DBResult* result = db->storeQuery(query.str()); + + if (!result) { + return 0; + } + + int32_t numRows = result->getDataInt("count"); + db->freeResult(result); + return numRows > 0; +} + +void IOBan::addIpBan(uint32_t ip, uint32_t mask, uint64_t time) +{ + DBQuery query; + query << "INSERT INTO `bans` (`type`, `ip`, `mask`, `time`) VALUES (" << BAN_IPADDRESS << ", " << ip << ", " << mask << ", " << time << ")"; + Database::getInstance()->executeQuery(query.str()); +} + +void IOBan::addPlayerNamelock(uint32_t playerId, uint32_t time, uint32_t reasonId, uint32_t actionId, const std::string& comment, uint32_t bannedBy) +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "INSERT INTO `bans` (`type`, `player`, `time`, `reason_id`, `action_id`, `comment`, `banned_by`)" + " VALUES (" << NAMELOCK_PLAYER << ", " << playerId << ", " << time << ", " << reasonId << ", " << actionId << ", " << db->escapeString(comment) << ", " << bannedBy << ")"; + db->executeQuery(query.str()); +} + +void IOBan::addAccountNotation(uint32_t account, uint64_t time, uint32_t reasonId, uint32_t actionId, const std::string& comment, uint32_t bannedBy) +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "INSERT INTO `bans` (`type`, `account`, `time`, `reason_id`, `action_id`, `comment`, `banned_by`)" + " VALUES (" << NOTATION_ACCOUNT << ", " << account << ", " << time << ", " << reasonId << ", " << actionId << ", " << db->escapeString(comment) << ", " << bannedBy << ")"; + db->executeQuery(query.str()); +} + +void IOBan::addAccountDeletion(uint32_t account, uint64_t time, int32_t reasonId, int32_t actionId, const std::string& comment, uint32_t bannedBy) +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "INSERT INTO `bans` (`type`, `account`, `time`, `reason_id`, `action_id`, `comment`, `banned_by`)" + " VALUES (" << DELETE_ACCOUNT << ", " << account << ", " << time << ", " << reasonId << ", " << actionId << ", " << db->escapeString(comment) << ", " << bannedBy << ")"; + db->executeQuery(query.str()); +} + +void IOBan::addAccountBan(uint32_t account, uint64_t time, int32_t reasonId, int32_t actionId, const std::string& comment, uint32_t bannedBy) +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "INSERT INTO `bans` (`type`, `account`, `time`, `reason_id`, `action_id`, `comment`, `banned_by`)" + " VALUES (" << BAN_ACCOUNT << ", " << account << ", " << time << ", " << reasonId << ", " << actionId << ", " << db->escapeString(comment) << ", " << bannedBy << ")"; + db->executeQuery(query.str()); +} + +bool IOBan::removePlayerNamelock(uint32_t guid) +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "DELETE FROM `bans` WHERE `type` = " << NAMELOCK_PLAYER << " AND `player` = " << guid << " LIMIT 1"; + return db->executeQuery(query.str()); +} + +bool IOBan::removeAccountNotations(uint32_t account) +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "DELETE FROM `bans` WHERE `type` = " << NOTATION_ACCOUNT << " AND `account` = " << account << " LIMIT 1"; + return db->executeQuery(query.str()); +} + +bool IOBan::removeIPBan(uint32_t ip) +{ + DBQuery query; + query << "DELETE FROM `bans` WHERE `type` = " << BAN_IPADDRESS << " AND `ip` = " << ip; + return Database::getInstance()->executeQuery(query.str()); +} + +bool IOBan::removeAccountBan(uint32_t account) +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "UPDATE `bans` SET `time` = " << time(NULL) << " WHERE `type` = " << BAN_ACCOUNT << " AND `account` = " << account << " AND `time` > " << time(NULL) << " LIMIT 1"; + return db->executeQuery(query.str()); +} + +bool IOBan::removeAccountDeletion(uint32_t account) +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "DELETE FROM `bans` WHERE `type` = " << DELETE_ACCOUNT << " AND `account` = " << account << " LIMIT 1"; + return db->executeQuery(query.str()); +} diff --git a/src/ban.h b/src/ban.h new file mode 100644 index 0000000000..2f3a546381 --- /dev/null +++ b/src/ban.h @@ -0,0 +1,160 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_BAN_H__ +#define __OTSERV_BAN_H__ + +#include +#include +#include "player.h" + +struct idBan { + uint32_t id; + uint32_t time; + int32_t reasonId; + int32_t actionId; + std::string comment; + uint32_t bannedBy; + idBan(uint32_t _id, uint32_t _time, int32_t _reasonId, int32_t _actionId, const std::string& _comment, uint32_t _bannedBy) { + id = _id; + time = _time; + reasonId = _reasonId; + actionId = _actionId; + comment = _comment; + bannedBy = _bannedBy; + } +}; +typedef idBan AccountBanStruct; +typedef idBan AccountDeletionStruct; + +struct idNamelock { + uint32_t id; + idNamelock(uint32_t _id) { + id = _id; + } +}; +typedef idNamelock PlayerNamelockStruct; + +struct idNotation { + uint32_t id; + uint32_t time; + std::string comment; + uint32_t bannedBy; + idNotation(uint32_t _id, uint32_t _time, const std::string& _comment, uint32_t _bannedBy) { + id = _id; + time = _time; + comment = _comment; + bannedBy = _bannedBy; + } +}; +typedef idNotation AccountNotationStruct; + +struct IpBanStruct { + uint32_t ip; + uint32_t mask; + uint32_t time; + IpBanStruct(uint32_t _ip, uint32_t _mask, uint32_t _time) { + ip = _ip; + mask = _mask; + time = _time; + } +}; + +struct LoginBlock { + uint32_t lastLoginTime; + uint32_t numberOfLogins; +}; + +struct ConnectBlock { + uint64_t lastAttempt; + uint64_t blockTime; + uint32_t count; +}; + +typedef std::list< IpBanStruct > IpBanList; +typedef std::list< PlayerNamelockStruct > PlayerNamelockList; +typedef std::list< AccountNotationStruct > AccountNotationList; +typedef std::list< AccountBanStruct > AccountBanList; +typedef std::list< AccountDeletionStruct > AccountDeletionList; + +typedef std::map IpLoginMap; +typedef std::map IpConnectMap; + +enum BanType_t { + BAN_IPADDRESS = 1, + NAMELOCK_PLAYER = 2, + BAN_ACCOUNT = 3, + NOTATION_ACCOUNT = 4, + DELETE_ACCOUNT = 5 +}; + +class Ban +{ + public: + Ban() {} + virtual ~Ban() {} + + bool acceptConnection(uint32_t clientip); + void addLoginAttempt(uint32_t clientip, bool isSuccess); + bool isIpDisabled(uint32_t clientip); + + protected: + IpLoginMap ipLoginMap; + IpConnectMap ipConnectMap; + + uint32_t loginTimeout; + uint32_t maxLoginTries; + uint32_t retryTimeout; + + mutable boost::recursive_mutex banLock; +}; + +class IOBan +{ + public: + static IOBan* getInstance() { + static IOBan instance; + return &instance; + } + + bool isIpBanished(uint32_t clientip); + bool isPlayerNamelocked(const std::string& name); + bool isAccountBanned(uint32_t account); + + void addIpBan(uint32_t ip, uint32_t mask, uint64_t time); + + void addPlayerNamelock(uint32_t playerId, uint32_t time, uint32_t reasonId, uint32_t actionId, const std::string& comment, uint32_t bannedBy); + void addAccountBan(uint32_t account, uint64_t time, int32_t reasonId, int32_t actionId, const std::string& comment, uint32_t bannedBy); + void addAccountDeletion(uint32_t account, uint64_t time, int32_t reasonId, int32_t actionId, const std::string& comment, uint32_t bannedBy); + void addAccountNotation(uint32_t account, uint64_t time, uint32_t reasonId, uint32_t actionId, const std::string& comment, uint32_t bannedBy); + + bool getBanInformation(uint32_t account, uint32_t& bannedBy, uint32_t& banTime, int32_t& reason, int32_t& action, std::string& comment, bool& deletion); + int32_t getNotationsCount(uint32_t account); + + bool removeAccountBan(uint32_t account); + bool removeAccountNotations(uint32_t account); + bool removeAccountDeletion(uint32_t account); + bool removeIPBan(uint32_t ip); + bool removePlayerNamelock(uint32_t guid); + + protected: + IOBan() {} + virtual ~IOBan() {} +}; + +#endif diff --git a/src/baseevents.cpp b/src/baseevents.cpp new file mode 100644 index 0000000000..fe6ffa3356 --- /dev/null +++ b/src/baseevents.cpp @@ -0,0 +1,235 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include +#include "tools.h" + +#include "baseevents.h" + +BaseEvents::BaseEvents() +{ + m_loaded = false; +} + +BaseEvents::~BaseEvents() +{ + // +} + +bool BaseEvents::loadFromXml() +{ + if (m_loaded) { + std::cout << "[Error - BaseEvents::loadFromXml] It's already loaded." << std::endl; + return false; + } + + Event* event = NULL; + std::string scriptsName = getScriptBaseName(); + + if (getScriptInterface().loadFile(std::string("data/" + scriptsName + "/lib/" + scriptsName + ".lua")) == -1) { + std::cout << "[Warning - BaseEvents::loadFromXml] Can not load " << scriptsName << " lib/" << scriptsName << ".lua" << std::endl; + } + + std::string filename = "data/" + scriptsName + "/" + scriptsName + ".xml"; + xmlDocPtr doc = xmlParseFile(filename.c_str()); + + if (doc) { + m_loaded = true; + xmlNodePtr root, p; + root = xmlDocGetRootElement(doc); + + if (xmlStrcmp(root->name, (const xmlChar*)scriptsName.c_str())) { + std::cout << "[Error - BaseEvents::loadFromXml] Malformed " << scriptsName << " file." + << std::endl; + xmlFreeDoc(doc); + return false; + } + + p = root->children; + + while (p) { + if (p->name) { + std::string nodeName = (const char*)p->name; + + if ((event = getEvent(nodeName))) { + if (event->configureEvent(p)) { + bool success = true; + std::string scriptfile; + + if (readXMLString(p, "script", scriptfile)) { + if (!event->checkScript("data/", scriptsName, "/scripts/" + scriptfile) || + !event->loadScript("data/" + scriptsName + "/scripts/" + scriptfile)) { + success = false; + } + } else if (readXMLString(p, "function", scriptfile)) { + if (!event->loadFunction(scriptfile)) { + success = false; + } + } else { + success = false; + } + + if (success) { + if (!registerEvent(event, p)) { + success = false; + delete event; + } + } else { + delete event; + } + } else { + std::cout << "Warning: [BaseEvents::loadFromXml] Can not configure event" << std::endl; + delete event; + } + + event = NULL; + } + } + + p = p->next; + } + + xmlFreeDoc(doc); + } else { + std::cout << "Warning: [BaseEvents::loadFromXml] Can not open " << scriptsName << ".xml" << std::endl; + } + + return m_loaded; +} + +bool BaseEvents::reload() +{ + m_loaded = false; + clear(); + return loadFromXml(); +} + +Event::Event(LuaScriptInterface* _interface) +{ + m_scriptInterface = _interface; + m_scriptId = 0; + m_scripted = false; +} + +Event::Event(const Event* copy) +{ + m_scriptInterface = copy->m_scriptInterface; + m_scriptId = copy->m_scriptId; + m_scripted = copy->m_scripted; +} + +Event::~Event() +{ + // +} + +bool Event::checkScript(const std::string& datadir, const std::string& scriptsName, const std::string& scriptFile) +{ + LuaScriptInterface testInterface("Test Interface"); + testInterface.initState(); + + if (testInterface.loadFile(std::string(datadir + scriptsName + "/lib/" + scriptsName + ".lua")) == -1) { + std::cout << "Warning: [Event::checkScript] Can not load " << scriptsName << " lib/" << scriptsName << ".lua" << std::endl; + } + + if (m_scriptId != 0) { + std::cout << "Failure: [Event::checkScript] scriptid = " << m_scriptId << std::endl; + return false; + } + + if (testInterface.loadFile(datadir + scriptsName + scriptFile) == -1) { + std::cout << "Warning: [Event::checkScript] Can not load script. " << scriptFile << std::endl; + std::cout << testInterface.getLastLuaError() << std::endl; + return false; + } + + int32_t id = testInterface.getEvent(getScriptEventName()); + + if (id == -1) { + std::cout << "Warning: [Event::checkScript] Event " << getScriptEventName() << " not found. " << scriptFile << std::endl; + return false; + } + + return true; +} + +bool Event::loadScript(const std::string& scriptFile) +{ + if (!m_scriptInterface || m_scriptId != 0) { + std::cout << "Failure: [Event::loadScript] m_scriptInterface == NULL. scriptid = " << m_scriptId << std::endl; + return false; + } + + if (m_scriptInterface->loadFile(scriptFile) == -1) { + std::cout << "Warning: [Event::loadScript] Can not load script. " << scriptFile << std::endl; + std::cout << m_scriptInterface->getLastLuaError() << std::endl; + return false; + } + + int32_t id = m_scriptInterface->getEvent(getScriptEventName()); + + if (id == -1) { + std::cout << "Warning: [Event::loadScript] Event " << getScriptEventName() << " not found. " << scriptFile << std::endl; + return false; + } + + m_scripted = true; + m_scriptId = id; + return true; +} + +bool Event::loadFunction(const std::string& functionName) +{ + return false; +} + +CallBack::CallBack() +{ + m_scriptId = 0; + m_scriptInterface = NULL; + m_loaded = false; +} + +CallBack::~CallBack() +{ + // +} + +bool CallBack::loadCallBack(LuaScriptInterface* _interface, const std::string& name) +{ + if (!_interface) { + std::cout << "Failure: [CallBack::loadCallBack] m_scriptInterface == NULL" << std::endl; + return false; + } + + m_scriptInterface = _interface; + + int32_t id = m_scriptInterface->getEvent(name); + + if (id == -1) { + std::cout << "Warning: [CallBack::loadCallBack] Event " << name << " not found." << std::endl; + return false; + } + + m_callbackName = name; + m_scriptId = id; + m_loaded = true; + return true; +} diff --git a/src/baseevents.h b/src/baseevents.h new file mode 100644 index 0000000000..9a32a2f7b9 --- /dev/null +++ b/src/baseevents.h @@ -0,0 +1,92 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __BASEEVENTS_H__ +#define __BASEEVENTS_H__ + +#include "luascript.h" +#include + +class Event; + +class BaseEvents +{ + public: + BaseEvents(); + virtual ~BaseEvents(); + + bool loadFromXml(); + bool reload(); + bool isLoaded() const { + return m_loaded; + } + + protected: + virtual LuaScriptInterface& getScriptInterface() = 0; + virtual std::string getScriptBaseName() = 0; + virtual Event* getEvent(const std::string& nodeName) = 0; + virtual bool registerEvent(Event* event, xmlNodePtr p) = 0; + virtual void clear() = 0; + + bool m_loaded; +}; + +class Event +{ + public: + Event(LuaScriptInterface* _interface); + Event(const Event* copy); + virtual ~Event(); + + virtual bool configureEvent(xmlNodePtr p) = 0; + + bool checkScript(const std::string& datadir, const std::string& scriptsName, const std::string& scriptFile); + bool loadScript(const std::string& scriptFile); + virtual bool loadFunction(const std::string& functionName); + + virtual bool isScripted() { + return m_scripted; + } + + protected: + virtual std::string getScriptEventName() = 0; + + bool m_scripted; + int32_t m_scriptId; + LuaScriptInterface* m_scriptInterface; +}; + + +class CallBack +{ + public: + CallBack(); + virtual ~CallBack(); + + bool loadCallBack(LuaScriptInterface* _interface, const std::string& name); + + protected: + int32_t m_scriptId; + LuaScriptInterface* m_scriptInterface; + + bool m_loaded; + + std::string m_callbackName; +}; + +#endif diff --git a/src/beds.cpp b/src/beds.cpp new file mode 100644 index 0000000000..dba76e15e3 --- /dev/null +++ b/src/beds.cpp @@ -0,0 +1,330 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "beds.h" + +#include "house.h" +#include "iologindata.h" +#include "game.h" +#include "player.h" + +extern Game g_game; + +BedItem::BedItem(uint16_t _id) : Item(_id) +{ + house = NULL; + internalRemoveSleeper(); +} + +Attr_ReadValue BedItem::readAttr(AttrTypes_t attr, PropStream& propStream) +{ + switch (attr) { + case ATTR_SLEEPERGUID: { + uint32_t _guid; + + if (!propStream.GET_ULONG(_guid)) { + return ATTR_READ_ERROR; + } + + if (_guid != 0) { + std::string name; + + if (IOLoginData::getInstance()->getNameByGuid(_guid, name)) { + setSpecialDescription(name + " is sleeping there."); + Beds::getInstance().setBedSleeper(this, _guid); + } + } + + sleeperGUID = _guid; + return ATTR_READ_CONTINUE; + } + + case ATTR_SLEEPSTART: { + uint32_t sleep_start; + + if (!propStream.GET_ULONG(sleep_start)) { + return ATTR_READ_ERROR; + } + + sleepStart = (uint64_t)sleep_start; + return ATTR_READ_CONTINUE; + } + + default: + break; + } + + return Item::readAttr(attr, propStream); +} + +bool BedItem::serializeAttr(PropWriteStream& propWriteStream) const +{ + if (sleeperGUID != 0) { + propWriteStream.ADD_UCHAR(ATTR_SLEEPERGUID); + propWriteStream.ADD_ULONG(sleeperGUID); + } + + if (sleepStart != 0) { + propWriteStream.ADD_UCHAR(ATTR_SLEEPSTART); + propWriteStream.ADD_ULONG((int32_t)sleepStart); + } + + return true; +} + +BedItem* BedItem::getNextBedItem() +{ + Direction dir = Item::items[getID()].bedPartnerDir; + Position targetPos = getNextPosition(dir, getPosition()); + + Tile* tile = g_game.getMap()->getTile(targetPos); + + if (tile) { + return tile->getBedItem(); + } + + return NULL; +} + +bool BedItem::canUse(Player* player) +{ + if (!player || !house || !player->isPremium()) { + return false; + } + + if (sleeperGUID == 0) { + return isBed(); + } + + if (house->getHouseAccessLevel(player) != HOUSE_OWNER) { + std::string name; + + if (IOLoginData::getInstance()->getNameByGuid(sleeperGUID, name)) { + Player* sleeper = new Player(name, NULL); + + if (IOLoginData::getInstance()->loadPlayer(sleeper, name)) { + if (house->getHouseAccessLevel(sleeper) <= house->getHouseAccessLevel(player)) { + delete sleeper; + sleeper = NULL; + + return isBed(); + } + } + + delete sleeper; + sleeper = NULL; + } + + return false; + } + + return true; +} + +bool BedItem::trySleep(Player* player) +{ + if (!house || !player || player->isRemoved()) { + return false; + } + + if (sleeperGUID != 0) { + if (Item::items[getID()].transformToFree != 0 && house->getHouseOwner() == player->getGUID()) { + wakeUp(NULL); + } + + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + return true; +} + +bool BedItem::sleep(Player* player) +{ + if (!house || !player || player->isRemoved()) { + return false; + } + + if (sleeperGUID != 0) { + return false; + } + + BedItem* nextBedItem = getNextBedItem(); + + internalSetSleeper(player); + + if (nextBedItem) { + nextBedItem->internalSetSleeper(player); + } + + // update the BedSleepersMap + Beds::getInstance().setBedSleeper(this, player->getGUID()); + + // make the player walk onto the bed + player->getTile()->moveCreature(player, getTile()); + + // display 'Zzzz'/sleep effect + g_game.addMagicEffect(player->getPosition(), NM_ME_SLEEP); + + // kick player after he sees himself walk onto the bed and it change id + uint32_t playerId = player->getID(); + g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, boost::bind(&Game::kickPlayer, &g_game, playerId, false))); + + // change self and partner's appearance + updateAppearance(player); + + if (nextBedItem) { + nextBedItem->updateAppearance(player); + } + + return true; +} + +void BedItem::wakeUp(Player* player) +{ + // avoid crashes + if (!house) { + return; + } + + if (sleeperGUID != 0) { + if (!player) { + std::string name; + + if (IOLoginData::getInstance()->getNameByGuid(sleeperGUID, name)) { + Player* _player = new Player(name, NULL); + + if (IOLoginData::getInstance()->loadPlayer(_player, name)) { + regeneratePlayer(_player); + IOLoginData::getInstance()->savePlayer(_player); + } + + delete _player; + _player = NULL; + } + } else { + regeneratePlayer(player); + g_game.addCreatureHealth(player); + } + } + + // update the BedSleepersMap + Beds::getInstance().setBedSleeper(NULL, sleeperGUID); + + BedItem* nextBedItem = getNextBedItem(); + + // unset sleep info + internalRemoveSleeper(); + + if (nextBedItem) { + nextBedItem->internalRemoveSleeper(); + } + + // change self and partner's appearance + updateAppearance(NULL); + + if (nextBedItem) { + nextBedItem->updateAppearance(NULL); + } +} + +void BedItem::regeneratePlayer(Player* player) const +{ + int32_t sleptTime = int32_t(time(NULL) - sleepStart); + + Condition* condition = player->getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT); + + if (condition) { + int32_t regen = 0; + + if (condition->getTicks() != -1) { + regen = std::min((condition->getTicks() / 1000), sleptTime) / 30; + int32_t newRegenTicks = condition->getTicks() - (regen * 30000); + + if (newRegenTicks <= 0) { + player->removeCondition(condition); + condition = NULL; + } else { + condition->setTicks(newRegenTicks); + } + } else { + regen = sleptTime / 30; + } + + player->changeHealth(regen, false); + player->changeMana(regen); + } + + int32_t soulRegen = sleptTime / (60 * 15); + player->changeSoul(soulRegen); +} + +void BedItem::updateAppearance(const Player* player) +{ + const ItemType& it = Item::items[getID()]; + + if (it.type == ITEM_TYPE_BED) { + if (player && it.transformToOnUse[player->getSex()] != 0) { + const ItemType& newType = Item::items[it.transformToOnUse[player->getSex()]]; + + if (newType.type == ITEM_TYPE_BED) { + g_game.transformItem(this, it.transformToOnUse[player->getSex()]); + } + } else if (it.transformToFree != 0) { + const ItemType& newType = Item::items[it.transformToFree]; + + if (newType.type == ITEM_TYPE_BED) { + g_game.transformItem(this, it.transformToFree); + } + } + } +} + +void BedItem::internalSetSleeper(const Player* player) +{ + std::string desc_str = player->getName() + " is sleeping there."; + + setSleeper(player->getGUID()); + setSleepStart(time(NULL)); + setSpecialDescription(desc_str); +} + +void BedItem::internalRemoveSleeper() +{ + setSleeper(0); + setSleepStart(0); + setSpecialDescription("Nobody is sleeping there."); +} + +BedItem* Beds::getBedBySleeper(uint32_t guid) +{ + std::map::iterator it = BedSleepersMap.find(guid); + + if (it != BedSleepersMap.end()) { + return it->second; + } + + return NULL; +} + +void Beds::setBedSleeper(BedItem* bed, uint32_t guid) +{ + BedSleepersMap[guid] = bed; +} diff --git a/src/beds.h b/src/beds.h new file mode 100644 index 0000000000..7c392dd765 --- /dev/null +++ b/src/beds.h @@ -0,0 +1,113 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTS_BEDS_H__ +#define __OTS_BEDS_H__ + +#include "item.h" +#include "position.h" +#include "definitions.h" + +#include +#include + +class House; +class Player; + +class BedItem : public Item +{ + public: + BedItem(uint16_t id); + virtual ~BedItem() {}; + + virtual BedItem* getBed() { + return this; + } + virtual const BedItem* getBed() const { + return this; + } + + virtual Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream); + virtual bool serializeAttr(PropWriteStream& propWriteStream) const; + + virtual bool canRemove() const { + return (house == NULL); + } + + uint32_t getSleeper() const { + return sleeperGUID; + } + void setSleeper(uint32_t guid) { + sleeperGUID = guid; + } + + uint64_t getSleepStart() const { + return sleepStart; + } + void setSleepStart(uint64_t now) { + sleepStart = now; + } + + House* getHouse() const { + return house; + } + void setHouse(House* h) { + house = h; + } + + bool canUse(Player* player); + + bool trySleep(Player* player); + bool sleep(Player* player); + void wakeUp(Player* player); + + BedItem* getNextBedItem(); + + protected: + void updateAppearance(const Player* player); + void regeneratePlayer(Player* player) const; + void internalSetSleeper(const Player* player); + void internalRemoveSleeper(); + + uint32_t sleeperGUID; + uint64_t sleepStart; + House* house; +}; + +class Beds +{ + public: + virtual ~Beds() {} + + static Beds& getInstance() { + static Beds instance; + return instance; + } + + BedItem* getBedBySleeper(uint32_t guid); + void setBedSleeper(BedItem* bed, uint32_t guid); + + protected: + Beds() { + BedSleepersMap.clear(); + } + + std::map BedSleepersMap; +}; + +#endif diff --git a/src/chat.cpp b/src/chat.cpp new file mode 100644 index 0000000000..9590cfe817 --- /dev/null +++ b/src/chat.cpp @@ -0,0 +1,677 @@ +////////////////////////////////////////////////////////////////////// +// OpenTibia - an opensource roleplaying game +////////////////////////////////////////////////////////////////////// +// +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// +#include "otpch.h" + +#include "chat.h" +#include "configmanager.h" +#include "player.h" +#include "game.h" +#include "iologindata.h" + +extern ConfigManager g_config; +extern Game g_game; +extern Chat g_chat; + +PrivateChatChannel::PrivateChatChannel(uint16_t channelId, const std::string& channelName) : + ChatChannel(channelId, channelName) +{ + m_owner = 0; +} + +bool PrivateChatChannel::isInvited(const Player* player) +{ + if (!player) { + return false; + } + + if (player->getGUID() == getOwner()) { + return true; + } + + InvitedMap::const_iterator it = m_invites.find(player->getGUID()); + return it != m_invites.end(); +} + +bool PrivateChatChannel::addInvited(Player* player) +{ + InvitedMap::const_iterator it = m_invites.find(player->getGUID()); + + if (it != m_invites.end()) { + return false; + } + + m_invites[player->getGUID()] = player; + return true; +} + +bool PrivateChatChannel::removeInvited(Player* player) +{ + InvitedMap::iterator it = m_invites.find(player->getGUID()); + + if (it == m_invites.end()) { + return false; + } + + m_invites.erase(it); + return true; +} + +void PrivateChatChannel::invitePlayer(Player* player, Player* invitePlayer) +{ + if (player != invitePlayer && addInvited(invitePlayer)) { + std::string msg = player->getName(); + msg += " invites you to "; + msg += (player->getSex() == PLAYERSEX_FEMALE ? "her" : "his"); + msg += " private chat channel."; + invitePlayer->sendTextMessage(MSG_INFO_DESCR, msg.c_str()); + + msg = invitePlayer->getName(); + msg += " has been invited."; + player->sendTextMessage(MSG_INFO_DESCR, msg.c_str()); + + UsersMap::iterator cit; + + for (cit = m_users.begin(); cit != m_users.end(); ++cit) { + Player* tmpPlayer = cit->second->getPlayer(); + + if (tmpPlayer) { + tmpPlayer->sendChannelEvent(m_id, invitePlayer->getName(), CHANNELEVENT_INVITE); + } + } + } +} + +void PrivateChatChannel::excludePlayer(Player* player, Player* excludePlayer) +{ + if (player != excludePlayer && removeInvited(excludePlayer)) { + removeUser(excludePlayer); + + std::string msg = excludePlayer->getName(); + msg += " has been excluded."; + player->sendTextMessage(MSG_INFO_DESCR, msg.c_str()); + + excludePlayer->sendClosePrivate(getId()); + + UsersMap::iterator cit; + + for (cit = m_users.begin(); cit != m_users.end(); ++cit) { + Player* tmpPlayer = cit->second->getPlayer(); + + if (tmpPlayer) { + tmpPlayer->sendChannelEvent(m_id, excludePlayer->getName(), CHANNELEVENT_EXCLUDE); + } + } + } +} + +void PrivateChatChannel::closeChannel() +{ + UsersMap::iterator cit; + + for (cit = m_users.begin(); cit != m_users.end(); ++cit) { + Player* toPlayer = cit->second->getPlayer(); + + if (toPlayer) { + toPlayer->sendClosePrivate(getId()); + } + } +} + +ChatChannel::ChatChannel(uint16_t channelId, const std::string& channelName) +{ + m_id = channelId; + m_name = channelName; +} + +bool ChatChannel::addUser(Player* player) +{ + if (m_users.find(player->getID()) != m_users.end()) { + return false; + } + + if (m_id == CHANNEL_GUILD) { + Guild* guild = player->getGuild(); + + if (guild && !guild->getMotd().empty()) { + g_scheduler.addEvent(createSchedulerTask(150, boost::bind( + &Game::sendGuildMotd, &g_game, player->getID(), guild->getId()))); + } + } + + if (m_id == CHANNEL_GUILD || m_id == CHANNEL_PARTY || m_id == CHANNEL_PRIVATE) { + for (UsersMap::const_iterator cit = m_users.begin(); cit != m_users.end(); ++cit) { + Player* tmpPlayer = cit->second->getPlayer(); + if (tmpPlayer) { + tmpPlayer->sendChannelEvent(m_id, player->getName(), CHANNELEVENT_JOIN); + } + } + } + + m_users[player->getID()] = player; + return true; +} + +bool ChatChannel::removeUser(Player* player) +{ + UsersMap::iterator it = m_users.find(player->getID()); + + if (it == m_users.end()) { + return false; + } + + m_users.erase(it); + + if (m_id == CHANNEL_GUILD || m_id == CHANNEL_PARTY || m_id == CHANNEL_PRIVATE) { + UsersMap::iterator cit; + + for (cit = m_users.begin(); cit != m_users.end(); ++cit) { + Player* tmpPlayer = cit->second->getPlayer(); + + if (tmpPlayer) { + tmpPlayer->sendChannelEvent(m_id, player->getName(), CHANNELEVENT_LEAVE); + } + } + } + + return true; +} + +void ChatChannel::sendToAll(const std::string& message, SpeakClasses type) +{ + for (UsersMap::iterator it = m_users.begin(), end = m_users.end(); it != end; ++it) { + it->second->sendChannelMessage("", message, type, m_id); + } +} + +bool ChatChannel::talk(Player* fromPlayer, SpeakClasses type, const std::string& text) +{ + if (m_users.find(fromPlayer->getID()) == m_users.end()) { + return false; + } + + if (!fromPlayer->hasFlag(PlayerFlag_CannotBeMuted) && (m_id == CHANNEL_ADVERTISING || m_id == CHANNEL_ADVERTISINGROOKGAARD)) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_CHANNELMUTEDTICKS, 120000, 0, false, m_id); + fromPlayer->addCondition(condition); + } + + for (UsersMap::iterator it = m_users.begin(); it != m_users.end(); ++it) { + it->second->sendToChannel(fromPlayer, type, text, getId()); + } + + return true; +} + +Chat::Chat() +{ + // Create the default channels + m_normalChannels[CHANNEL_GAMEMASTER] = new ChatChannel(CHANNEL_GAMEMASTER, "Gamemaster"); + m_normalChannels[CHANNEL_TUTOR] = new ChatChannel(CHANNEL_TUTOR, "Tutor"); + m_normalChannels[CHANNEL_WORLDCHAT] = new ChatChannel(CHANNEL_WORLDCHAT, "World Chat"); + m_normalChannels[CHANNEL_ENGLISHCHAT] = new ChatChannel(CHANNEL_ENGLISHCHAT, "English Chat"); + m_normalChannels[CHANNEL_ADVERTISING] = new ChatChannel(CHANNEL_ADVERTISING, "Advertising"); + m_normalChannels[CHANNEL_ADVERTISINGROOKGAARD] = new ChatChannel(CHANNEL_ADVERTISINGROOKGAARD, "Advertising-Rookgaard"); + m_normalChannels[CHANNEL_HELP] = new ChatChannel(CHANNEL_HELP, "Help"); + + dummyPrivate = new PrivateChatChannel(CHANNEL_PRIVATE, "Private Chat Channel"); +} + +Chat::~Chat() +{ + for (NormalChannelMap::iterator it = m_normalChannels.begin(); it != m_normalChannels.end(); ++it) { + delete it->second; + } + + m_normalChannels.clear(); + + for (GuildChannelMap::iterator it = m_guildChannels.begin(); it != m_guildChannels.end(); ++it) { + delete it->second; + } + + m_guildChannels.clear(); + + for (PartyChannelMap::iterator it = m_partyChannels.begin(); it != m_partyChannels.end(); ++it) { + delete it->second; + } + + m_partyChannels.clear(); + + for (PrivateChannelMap::iterator it = m_privateChannels.begin(); it != m_privateChannels.end(); ++it) { + delete it->second; + } + + m_privateChannels.clear(); + delete dummyPrivate; +} + +ChatChannel* Chat::createChannel(Player* player, uint16_t channelId) +{ + if (getChannel(player, channelId)) { + return NULL; + } + + switch (channelId) { + case CHANNEL_GUILD: { + Guild* guild = player->getGuild(); + + if (guild) { + ChatChannel* newChannel = new ChatChannel(channelId, guild->getName()); + + if (newChannel) { + m_guildChannels[guild->getId()] = newChannel; + } + + return newChannel; + } + } + + case CHANNEL_PARTY: { + ChatChannel* newChannel = NULL; + + if (player->getParty() && (newChannel = new ChatChannel(channelId, "Party"))) { + m_partyChannels[player->getParty()] = newChannel; + } + + return newChannel; + } + + case CHANNEL_PRIVATE: { + //only 1 private channel for each premium player + if (!player->isPremium() || getPrivateChannel(player)) { + return NULL; + } + + //find a free private channel slot + for (uint16_t i = 100; i < 10000; ++i) { + if (m_privateChannels.find(i) == m_privateChannels.end()) { + PrivateChatChannel* newChannel = NULL; + + if ((newChannel = new PrivateChatChannel(i, player->getName() + "'s Channel"))) { + newChannel->setOwner(player->getGUID()); + m_privateChannels[i] = newChannel; + } + + return newChannel; + } + } + } + + default: + break; + } + + return NULL; +} + +bool Chat::deleteChannel(Player* player, uint16_t channelId) +{ + switch (channelId) { + case CHANNEL_GUILD: { + Guild* guild = player->getGuild(); + + if (!guild) { + return false; + } + + GuildChannelMap::iterator it = m_guildChannels.find(guild->getId()); + + if (it == m_guildChannels.end()) { + return false; + } + + delete it->second; + m_guildChannels.erase(it); + return true; + } + + case CHANNEL_PARTY: { + PartyChannelMap::iterator it = m_partyChannels.find(player->getParty()); + + if (it == m_partyChannels.end()) { + return false; + } + + delete it->second; + m_partyChannels.erase(it); + return true; + } + + default: { + PrivateChannelMap::iterator it = m_privateChannels.find(channelId); + + if (it == m_privateChannels.end()) { + return false; + } + + it->second->closeChannel(); + + delete it->second; + m_privateChannels.erase(it); + return true; + } + } + + return false; +} + +ChatChannel* Chat::addUserToChannel(Player* player, uint16_t channelId) +{ + ChatChannel* channel = getChannel(player, channelId); + + if (channel && channel->addUser(player)) { + return channel; + } + + return NULL; +} + +bool Chat::removeUserFromChannel(Player* player, uint16_t channelId) +{ + ChatChannel* channel = getChannel(player, channelId); + + if (channel && channel->removeUser(player)) { + if (channel->getOwner() == player->getGUID()) { + deleteChannel(player, channelId); + } + + return true; + } + + return false; +} + +void Chat::removeUserFromAllChannels(Player* player) +{ + for (NormalChannelMap::iterator it = m_normalChannels.begin(), end = m_normalChannels.end(); it != end; ++it) { + it->second->removeUser(player); + } + + for (PartyChannelMap::iterator it = m_partyChannels.begin(), end = m_partyChannels.end(); it != end; ++it) { + it->second->removeUser(player); + } + + for (GuildChannelMap::iterator it = m_guildChannels.begin(), end = m_guildChannels.end(); it != end; ++it) { + it->second->removeUser(player); + } + + for (PrivateChannelMap::iterator it = m_privateChannels.begin(), end = m_privateChannels.end(); it != end; ++it) { + PrivateChatChannel* channel = it->second; + channel->removeUser(player); + + if (channel->getOwner() == player->getGUID()) { + deleteChannel(player, channel->getId()); + } + } +} + +bool Chat::talkToChannel(Player* player, SpeakClasses type, const std::string& text, uint16_t channelId) +{ + ChatChannel* channel = getChannel(player, channelId); + + if (!channel || !player) { + return false; + } + + if (player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER) { + if (player->getLevel() < 2 && channelId < CHANNEL_PARTY && channelId != CHANNEL_ADVERTISINGROOKGAARD) { + player->sendCancel("You may not speak into channels as long as you are on level 1."); + return false; + } else if ((channelId == CHANNEL_ADVERTISING || channelId == CHANNEL_ADVERTISINGROOKGAARD) && player->hasCondition(CONDITION_CHANNELMUTEDTICKS, channelId)) { + player->sendCancel("You may only place one offer in two minutes."); + return false; + } else if (channelId == CHANNEL_HELP && player->hasCondition(CONDITION_CHANNELMUTEDTICKS, CHANNEL_HELP)) { + player->sendCancel("You are muted from the Help channel for using it inappropriately."); + return false; + } + } + + if (channelId == CHANNEL_HELP && player->getAccountType() >= ACCOUNT_TYPE_TUTOR && text.length() > 6) { + if (text.length() > 6 && text.substr(0, 6) == "!mute ") { + std::string param = text.substr(6); + trimString(param); + Player* paramPlayer = g_game.getPlayerByName(param); + + if (paramPlayer && paramPlayer->getAccountType() < player->getAccountType()) { + if (!paramPlayer->hasCondition(CONDITION_CHANNELMUTEDTICKS, CHANNEL_HELP)) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_CHANNELMUTEDTICKS, 3600000, 0, false, CHANNEL_HELP); + paramPlayer->addCondition(condition); + + std::ostringstream ss; + ss << paramPlayer->getName() << " has been muted by " << player->getName() << " for using Help Channel inappropriately."; + channel->sendToAll(ss.str(), SPEAK_CHANNEL_R1); + } else { + player->sendCancel("That player is already muted."); + } + } else { + player->sendCancel("A player with that name is not online."); + } + + return true; + } else if (text.length() > 8 && text.substr(0, 8) == "!unmute ") { + std::string param = text.substr(8); + trimString(param); + Player* paramPlayer = g_game.getPlayerByName(param); + + if (paramPlayer && paramPlayer->getAccountType() < player->getAccountType()) { + Condition* condition = paramPlayer->getCondition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT, CHANNEL_HELP); + + if (condition && condition->getTicks() > 0) { + paramPlayer->removeCondition(condition); + + std::ostringstream ss; + ss << paramPlayer->getName() << " has been unmuted by " << player->getName() << "."; + channel->sendToAll(ss.str(), SPEAK_CHANNEL_R1); + } else { + player->sendCancel("That player is not muted."); + } + } else { + player->sendCancel("A player with that name is not online."); + } + + return true; + } + } + + if (channelId == CHANNEL_GUILD && player->getGuildLevel() > 1) { + type = SPEAK_CHANNEL_O; + } + + return channel->talk(player, type, text); +} + +std::string Chat::getChannelName(Player* player, uint16_t channelId) +{ + ChatChannel* channel = getChannel(player, channelId); + + if (!channel) { + return ""; + } + + return channel->getName(); +} + +ChannelList Chat::getChannelList(Player* player) +{ + ChannelList list; + + if (player->getGuild()) { + ChatChannel* channel = getChannel(player, CHANNEL_GUILD); + + if (channel) { + list.push_back(channel); + } else if ((channel = createChannel(player, CHANNEL_GUILD))) { + list.push_back(channel); + } + } + + if (player->getParty()) { + ChatChannel* channel = getChannel(player, CHANNEL_PARTY); + + if (channel) { + list.push_back(channel); + } else if ((channel = createChannel(player, CHANNEL_PARTY))) { + list.push_back(channel); + } + } + + for (NormalChannelMap::iterator it = m_normalChannels.begin(); it != m_normalChannels.end(); ++it) { + ChatChannel* channel = getChannel(player, it->first); + + if (channel) { + list.push_back(it->second); + } + } + + bool hasPrivate = false; + + for (PrivateChannelMap::iterator pit = m_privateChannels.begin(); pit != m_privateChannels.end(); ++pit) { + if (PrivateChatChannel* channel = pit->second) { + if (channel->isInvited(player)) { + list.push_back(channel); + } + + if (channel->getOwner() == player->getGUID()) { + hasPrivate = true; + } + } + } + + if (!hasPrivate && player->isPremium()) { + list.push_front(dummyPrivate); + } + + return list; +} + +ChatChannel* Chat::getChannel(Player* player, uint16_t channelId) +{ + if (channelId == CHANNEL_GUILD) { + Guild* guild = player->getGuild(); + + if (guild) { + GuildChannelMap::iterator git = m_guildChannels.find(guild->getId()); + + if (git != m_guildChannels.end()) { + return git->second; + } + } + + return NULL; + } + + if (channelId == CHANNEL_PARTY) { + if (!player->getParty()) { + return NULL; + } + + PartyChannelMap::iterator it = m_partyChannels.find(player->getParty()); + + if (it != m_partyChannels.end()) { + return it->second; + } + + return NULL; + } + + NormalChannelMap::iterator nit = m_normalChannels.find(channelId); + + if (nit != m_normalChannels.end()) { + switch (channelId) { + case CHANNEL_GAMEMASTER: { + if (player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER) { + return NULL; + } + + break; + } + + case CHANNEL_TUTOR: { + if (player->getAccountType() < ACCOUNT_TYPE_TUTOR) { + return NULL; + } + + break; + } + + case CHANNEL_ADVERTISING: { + if (player->getAccountType() < ACCOUNT_TYPE_SENIORTUTOR && player->getVocationId() == VOCATION_NONE) { + return NULL; + } + + break; + } + + case CHANNEL_ADVERTISINGROOKGAARD: { + if (player->getAccountType() < ACCOUNT_TYPE_SENIORTUTOR && player->getVocationId() != VOCATION_NONE) { + return NULL; + } + + break; + } + + default: + break; + } + + return nit->second; + } + + PrivateChannelMap::iterator pit = m_privateChannels.find(channelId); + + if (pit != m_privateChannels.end() && pit->second->isInvited(player)) { + return pit->second; + } + + return NULL; +} + +ChatChannel* Chat::getGuildChannelById(uint32_t guildId) +{ + GuildChannelMap::iterator it = m_guildChannels.find(guildId); + + if (it == m_guildChannels.end()) { + return NULL; + } + + return it->second; +} + +ChatChannel* Chat::getChannelById(uint16_t channelId) +{ + NormalChannelMap::iterator it = m_normalChannels.find(channelId); + + if (it == m_normalChannels.end()) { + return NULL; + } + + return it->second; +} + +PrivateChatChannel* Chat::getPrivateChannel(Player* player) +{ + PrivateChatChannel* channel = NULL; + + for (PrivateChannelMap::iterator it = m_privateChannels.begin(); it != m_privateChannels.end(); ++it) { + if ((channel = it->second) && channel->getOwner() == player->getGUID()) { + return channel; + } + } + + return NULL; +} diff --git a/src/chat.h b/src/chat.h new file mode 100644 index 0000000000..0a420296c3 --- /dev/null +++ b/src/chat.h @@ -0,0 +1,136 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_CHAT_H__ +#define __OTSERV_CHAT_H__ + +#include +#include +#include + +#include "const.h" +#include "definitions.h" +#include "party.h" + +class Player; + +typedef std::map UsersMap; +typedef std::map InvitedMap; + +class ChatChannel +{ + public: + ChatChannel(uint16_t channelId, const std::string& channelName); + virtual ~ChatChannel() {} + + bool addUser(Player* player); + bool removeUser(Player* player); + + bool talk(Player* fromPlayer, SpeakClasses type, const std::string& text); + void sendToAll(const std::string& message, SpeakClasses type); + + const std::string& getName() const { + return m_name; + } + uint16_t getId() const { + return m_id; + } + const UsersMap& getUsers() const { + return m_users; + } + + virtual uint32_t getOwner() { + return 0; + } + + protected: + UsersMap m_users; + std::string m_name; + uint16_t m_id; +}; + +class PrivateChatChannel : public ChatChannel +{ + public: + PrivateChatChannel(uint16_t channelId, const std::string& channelName); + virtual ~PrivateChatChannel() {} + + virtual uint32_t getOwner() { + return m_owner; + } + void setOwner(uint32_t id) { + m_owner = id; + } + + bool isInvited(const Player* player); + + void invitePlayer(Player* player, Player* invitePlayer); + void excludePlayer(Player* player, Player* excludePlayer); + + bool addInvited(Player* player); + bool removeInvited(Player* player); + + void closeChannel(); + + const InvitedMap& getInvitedUsers() { + return m_invites; + } + + protected: + InvitedMap m_invites; + uint32_t m_owner; +}; + +typedef std::list ChannelList; + +class Chat +{ + public: + Chat(); + virtual ~Chat(); + ChatChannel* createChannel(Player* player, uint16_t channelId); + bool deleteChannel(Player* player, uint16_t channelId); + + ChatChannel* addUserToChannel(Player* player, uint16_t channelId); + bool removeUserFromChannel(Player* player, uint16_t channelId); + void removeUserFromAllChannels(Player* player); + + bool talkToChannel(Player* player, SpeakClasses type, const std::string& text, uint16_t channelId); + + std::string getChannelName(Player* player, uint16_t channelId); + ChannelList getChannelList(Player* player); + + ChatChannel* getChannel(Player* player, uint16_t channelId); + ChatChannel* getChannelById(uint16_t channelId); + ChatChannel* getGuildChannelById(uint32_t guildId); + PrivateChatChannel* getPrivateChannel(Player* player); + + private: + typedef std::map NormalChannelMap; + typedef std::map PrivateChannelMap; + typedef std::map PartyChannelMap; + typedef std::map GuildChannelMap; + NormalChannelMap m_normalChannels; + PrivateChannelMap m_privateChannels; + PartyChannelMap m_partyChannels; + GuildChannelMap m_guildChannels; + + ChatChannel* dummyPrivate; +}; + +#endif diff --git a/src/combat.cpp b/src/combat.cpp new file mode 100644 index 0000000000..e949fd6035 --- /dev/null +++ b/src/combat.cpp @@ -0,0 +1,1535 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "combat.h" + +#include "game.h" +#include "creature.h" +#include "player.h" +#include "const.h" +#include "tools.h" +#include "weapons.h" +#include "configmanager.h" + +extern Game g_game; +extern Weapons* g_weapons; +extern ConfigManager g_config; + +Combat::Combat() +{ + params.valueCallback = NULL; + params.tileCallback = NULL; + params.targetCallback = NULL; + area = NULL; + + formulaType = FORMULA_UNDEFINED; + mina = 0.0; + minb = 0.0; + maxa = 0.0; + maxb = 0.0; +} + +Combat::~Combat() +{ + for (std::list::iterator it = params.conditionList.begin(); it != params.conditionList.end(); ++it) { + delete (*it); + } + + params.conditionList.clear(); + delete params.valueCallback; + delete params.tileCallback; + delete params.targetCallback; + delete area; +} + +bool Combat::getMinMaxValues(Creature* creature, Creature* target, int32_t& min, int32_t& max) const +{ + if (!creature) { + return false; + } + + if (creature->getCombatValues(min, max)) { + return true; + } else if (Player* player = creature->getPlayer()) { + if (params.valueCallback) { + params.valueCallback->getMinMaxValues(player, min, max, params.useCharges); + return true; + } else { + switch (formulaType) { + case FORMULA_LEVELMAGIC: { + max = (int32_t)((player->getLevel() * 2 + player->getMagicLevel() * 3) * 1. * mina + minb); + min = (int32_t)((player->getLevel() * 2 + player->getMagicLevel() * 3) * 1. * maxa + maxb); + return true; + } + + case FORMULA_SKILL: { + Item* tool = player->getWeapon(); + const Weapon* weapon = g_weapons->getWeapon(tool); + + min = (int32_t)minb; + + if (weapon) { + max = (int32_t)(weapon->getWeaponDamage(player, target, tool, true) * maxa + maxb); + + if (params.useCharges && tool->hasCharges()) { + int32_t newCharge = std::max(0, tool->getCharges() - 1); + g_game.transformItem(tool, tool->getID(), newCharge); + } + } else { + max = (int32_t)maxb; + } + + return true; + } + + case FORMULA_VALUE: { + min = (int32_t)mina; + max = (int32_t)maxa; + return true; + } + + default: { + min = 0; + max = 0; + return false; + } + } + } + } else if (formulaType == FORMULA_VALUE) { + min = (int32_t)mina; + max = (int32_t)maxa; + return true; + } + + return false; +} + +void Combat::getCombatArea(const Position& centerPos, const Position& targetPos, const AreaCombat* area, + std::list& list) +{ + if (area) { + area->getList(centerPos, targetPos, list); + } else if (targetPos.x >= 0 && targetPos.x < 0xFFFF && + targetPos.y >= 0 && targetPos.y < 0xFFFF && + targetPos.z >= 0 && targetPos.z < MAP_MAX_LAYERS) { + Tile* tile = g_game.getTile(targetPos.x, targetPos.y, targetPos.z); + + if (!tile) { + tile = new StaticTile(targetPos.x, targetPos.y, targetPos.z); + g_game.setTile(tile); + } + + list.push_back(tile); + } +} + +CombatType_t Combat::ConditionToDamageType(ConditionType_t type) +{ + switch (type) { + case CONDITION_FIRE: + return COMBAT_FIREDAMAGE; + + case CONDITION_ENERGY: + return COMBAT_ENERGYDAMAGE; + + case CONDITION_BLEEDING: + return COMBAT_PHYSICALDAMAGE; + + case CONDITION_DROWN: + return COMBAT_DROWNDAMAGE; + + case CONDITION_POISON: + return COMBAT_EARTHDAMAGE; + + case CONDITION_FREEZING: + return COMBAT_ICEDAMAGE; + + case CONDITION_DAZZLED: + return COMBAT_HOLYDAMAGE; + + case CONDITION_CURSED: + return COMBAT_DEATHDAMAGE; + + default: + break; + } + + return COMBAT_NONE; +} + +ConditionType_t Combat::DamageToConditionType(CombatType_t type) +{ + switch (type) { + case COMBAT_FIREDAMAGE: + return CONDITION_FIRE; + + case COMBAT_ENERGYDAMAGE: + return CONDITION_ENERGY; + + case COMBAT_DROWNDAMAGE: + return CONDITION_DROWN; + + case COMBAT_EARTHDAMAGE: + return CONDITION_POISON; + + case COMBAT_ICEDAMAGE: + return CONDITION_FREEZING; + + case COMBAT_HOLYDAMAGE: + return CONDITION_DAZZLED; + + case COMBAT_DEATHDAMAGE: + return CONDITION_CURSED; + + case COMBAT_PHYSICALDAMAGE: + return CONDITION_BLEEDING; + + default: + return CONDITION_NONE; + } +} + +bool Combat::isPlayerCombat(const Creature* target) +{ + if (target->getPlayer()) { + return true; + } + + if (target->isSummon() && target->getMaster()->getPlayer()) { + return true; + } + + return false; +} + +ReturnValue Combat::canTargetCreature(const Player* player, const Creature* target) +{ + if (player == target) { + return RET_YOUMAYNOTATTACKTHISPLAYER; + } + + if (!player->hasFlag(PlayerFlag_IgnoreProtectionZone)) { + //pz-zone + if (player->getZone() == ZONE_PROTECTION) { + return RET_YOUMAYNOTATTACKAPERSONWHILEINPROTECTIONZONE; + } + + if (target->getZone() == ZONE_PROTECTION) { + return RET_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE; + } + + //nopvp-zone + if (isPlayerCombat(target)) { + if (player->getZone() == ZONE_NOPVP) { + return RET_ACTIONNOTPERMITTEDINANOPVPZONE; + } + + if (target->getZone() == ZONE_NOPVP) { + return RET_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE; + } + } + } + + if (player->hasFlag(PlayerFlag_CannotUseCombat) || !target->isAttackable()) { + if (target->getPlayer()) { + return RET_YOUMAYNOTATTACKTHISPLAYER; + } else { + return RET_YOUMAYNOTATTACKTHISCREATURE; + } + } + + if (target->getPlayer()) { + if (isProtected(player, target->getPlayer())) { + return RET_YOUMAYNOTATTACKTHISPLAYER; + } + + if (player->getSecureMode() == SECUREMODE_ON && !Combat::isInPvpZone(player, target) && + player->getSkullClient(target->getPlayer()) == SKULL_NONE) { + return RET_TURNSECUREMODETOATTACKUNMARKEDPLAYERS; + } + } + + return Combat::canDoCombat(player, target); +} + +ReturnValue Combat::canDoCombat(const Creature* caster, const Tile* tile, bool isAggressive) +{ + if (tile->hasProperty(BLOCKPROJECTILE)) { + return RET_NOTENOUGHROOM; + } + + if (tile->floorChange()) { + return RET_NOTENOUGHROOM; + } + + if (tile->getTeleportItem()) { + return RET_NOTENOUGHROOM; + } + + if (caster) { + const Position& casterPosition = caster->getPosition(); + const Position& tilePosition = tile->getPosition(); + + if (casterPosition.z < tilePosition.z) { + return RET_FIRSTGODOWNSTAIRS; + } else if (casterPosition.z > tilePosition.z) { + return RET_FIRSTGOUPSTAIRS; + } + + if (const Player* player = caster->getPlayer()) { + if (player->hasFlag(PlayerFlag_IgnoreProtectionZone)) { + return RET_NOERROR; + } + } + } + + //pz-zone + if (isAggressive && tile->hasFlag(TILESTATE_PROTECTIONZONE)) { + return RET_ACTIONNOTPERMITTEDINPROTECTIONZONE; + } + + return RET_NOERROR; +} + +bool Combat::isInPvpZone(const Creature* attacker, const Creature* target) +{ + if (attacker->getZone() != ZONE_PVP) { + return false; + } + + if (target->getZone() != ZONE_PVP) { + return false; + } + + return true; +} + +bool Combat::isProtected(const Player* attacker, const Player* target) +{ + uint32_t protectionLevel = g_config.getNumber(ConfigManager::PROTECTION_LEVEL); + + if (target->getLevel() < protectionLevel || attacker->getLevel() < protectionLevel) { + return true; + } + + if (attacker->getVocationId() == VOCATION_NONE || target->getVocationId() == VOCATION_NONE) { + return true; + } + + if (attacker->getSkull() == SKULL_BLACK && attacker->getSkullClient(target) == SKULL_NONE) { + return true; + } + + return false; +} + +ReturnValue Combat::canDoCombat(const Creature* attacker, const Creature* target) +{ + if (attacker) { + if (const Player* targetPlayer = target->getPlayer()) { + if (targetPlayer->hasFlag(PlayerFlag_CannotBeAttacked)) { + return RET_YOUMAYNOTATTACKTHISPLAYER; + } + + if (const Player* attackerPlayer = attacker->getPlayer()) { + if (attackerPlayer->hasFlag(PlayerFlag_CannotAttackPlayer)) { + return RET_YOUMAYNOTATTACKTHISPLAYER; + } + + if (isProtected(attackerPlayer, targetPlayer)) { + return RET_YOUMAYNOTATTACKTHISPLAYER; + } + + //nopvp-zone + const Tile* targetPlayerTile = targetPlayer->getTile(); + + if (targetPlayerTile->hasFlag(TILESTATE_NOPVPZONE)) { + return RET_ACTIONNOTPERMITTEDINANOPVPZONE; + } else if (attackerPlayer->getTile()->hasFlag(TILESTATE_NOPVPZONE) && !targetPlayerTile->hasFlag(TILESTATE_NOPVPZONE) && !targetPlayerTile->hasFlag(TILESTATE_PROTECTIONZONE)) { + return RET_ACTIONNOTPERMITTEDINANOPVPZONE; + } + } + + if (attacker->isSummon()) { + if (const Player* masterAttackerPlayer = attacker->getMaster()->getPlayer()) { + if (masterAttackerPlayer->hasFlag(PlayerFlag_CannotAttackPlayer)) { + return RET_YOUMAYNOTATTACKTHISPLAYER; + } + + if (targetPlayer->getTile()->hasFlag(TILESTATE_NOPVPZONE)) { + return RET_ACTIONNOTPERMITTEDINANOPVPZONE; + } + + if (isProtected(masterAttackerPlayer, targetPlayer)) { + return RET_YOUMAYNOTATTACKTHISPLAYER; + } + } + } + } else if (target->getMonster()) { + if (const Player* attackerPlayer = attacker->getPlayer()) { + if (attackerPlayer->hasFlag(PlayerFlag_CannotAttackMonster)) { + return RET_YOUMAYNOTATTACKTHISCREATURE; + } + + if (target->isSummon() && target->getMaster()->getPlayer() && target->getZone() == ZONE_NOPVP) { + return RET_ACTIONNOTPERMITTEDINANOPVPZONE; + } + } else if (attacker->getMonster()) { + const Creature* targetMaster = target->getMaster(); + + if (!targetMaster || !targetMaster->getPlayer()) { + const Creature* attackerMaster = attacker->getMaster(); + + if (!attackerMaster || !attackerMaster->getPlayer()) { + return RET_YOUMAYNOTATTACKTHISCREATURE; + } + } + } + } + + if (g_game.getWorldType() == WORLD_TYPE_NO_PVP) { + if (attacker->getPlayer() || (attacker->isSummon() && attacker->getMaster()->getPlayer())) { + if (target->getPlayer()) { + if (!isInPvpZone(attacker, target)) { + return RET_YOUMAYNOTATTACKTHISPLAYER; + } + } + + if (target->isSummon() && target->getMaster()->getPlayer()) { + if (!isInPvpZone(attacker, target)) { + return RET_YOUMAYNOTATTACKTHISCREATURE; + } + } + } + } + } + + return RET_NOERROR; +} + +void Combat::setPlayerCombatValues(formulaType_t _type, double _mina, double _minb, double _maxa, double _maxb) +{ + formulaType = _type; + mina = _mina; + minb = _minb; + maxa = _maxa; + maxb = _maxb; +} + +bool Combat::setParam(CombatParam_t param, uint32_t value) +{ + switch (param) { + case COMBATPARAM_COMBATTYPE: { + params.combatType = (CombatType_t)value; + return true; + } + + case COMBATPARAM_EFFECT: { + params.impactEffect = (uint8_t)value; + return true; + } + + case COMBATPARAM_DISTANCEEFFECT: { + params.distanceEffect = (uint8_t)value; + return true; + } + + case COMBATPARAM_BLOCKEDBYARMOR: { + params.blockedByArmor = (value != 0); + return true; + } + + case COMBATPARAM_BLOCKEDBYSHIELD: { + params.blockedByShield = (value != 0); + return true; + } + + case COMBATPARAM_TARGETCASTERORTOPMOST: { + params.targetCasterOrTopMost = (value != 0); + return true; + } + + case COMBATPARAM_CREATEITEM: { + params.itemId = value; + return true; + } + + case COMBATPARAM_AGGRESSIVE: { + params.isAggressive = (value != 0); + return true; + } + + case COMBATPARAM_DISPEL: { + params.dispelType = (ConditionType_t)value; + return true; + } + + case COMBATPARAM_USECHARGES: { + params.useCharges = (value != 0); + return true; + } + + default: { + break; + } + } + + return false; +} + +bool Combat::setCallback(CallBackParam_t key) +{ + switch (key) { + case CALLBACKPARAM_LEVELMAGICVALUE: { + delete params.valueCallback; + params.valueCallback = new ValueCallback(FORMULA_LEVELMAGIC); + return true; + break; + } + + case CALLBACKPARAM_SKILLVALUE: { + delete params.valueCallback; + params.valueCallback = new ValueCallback(FORMULA_SKILL); + return true; + break; + } + + case CALLBACKPARAM_TARGETTILECALLBACK: { + delete params.tileCallback; + params.tileCallback = new TileCallback(); + break; + } + + case CALLBACKPARAM_TARGETCREATURECALLBACK: { + delete params.targetCallback; + params.targetCallback = new TargetCallback(); + break; + } + + default: { + std::cout << "Combat::setCallback - Unknown callback type: " << (uint32_t)key << std::endl; + break; + } + } + + return false; +} + +CallBack* Combat::getCallback(CallBackParam_t key) +{ + switch (key) { + case CALLBACKPARAM_LEVELMAGICVALUE: + case CALLBACKPARAM_SKILLVALUE: { + return params.valueCallback; + break; + } + + case CALLBACKPARAM_TARGETTILECALLBACK: { + return params.tileCallback; + break; + } + + case CALLBACKPARAM_TARGETCREATURECALLBACK: { + return params.targetCallback; + break; + } + } + + return NULL; +} + +bool Combat::CombatHealthFunc(Creature* caster, Creature* target, const CombatParams& params, void* data) +{ + Combat2Var* var = (Combat2Var*)data; + int32_t healthChange = var->change; + + if (g_game.combatBlockHit(params.combatType, caster, target, healthChange, params.blockedByShield, params.blockedByArmor)) { + return false; + } + + if (healthChange < 0) { + if (caster) { + Player* targetPlayer = target->getPlayer(); + + if (targetPlayer && caster->getPlayer() && targetPlayer->getSkull() != SKULL_BLACK) { + healthChange = healthChange / 2; + } + } + } + + bool result = g_game.combatChangeHealth(params.combatType, caster, target, healthChange); + + if (result) { + CombatConditionFunc(caster, target, params, NULL); + CombatDispelFunc(caster, target, params, NULL); + } + + return result; +} + +bool Combat::CombatManaFunc(Creature* caster, Creature* target, const CombatParams& params, void* data) +{ + Combat2Var* var = (Combat2Var*)data; + int32_t manaChange = var->change; + + if (manaChange < 0) { + if (caster && caster->getPlayer() && target->getPlayer()) { + manaChange = manaChange / 2; + } + } + + bool result = g_game.combatChangeMana(caster, target, manaChange); + + if (result) { + CombatConditionFunc(caster, target, params, NULL); + CombatDispelFunc(caster, target, params, NULL); + } + + return result; +} + +bool Combat::CombatConditionFunc(Creature* caster, Creature* target, const CombatParams& params, void* data) +{ + bool result = false; + + if (!params.conditionList.empty()) { + for (std::list::const_iterator it = params.conditionList.begin(); it != params.conditionList.end(); ++it) { + const Condition* condition = *it; + + if (caster == target || !target->isImmune(condition->getType())) { + Condition* conditionCopy = condition->clone(); + + if (caster) { + conditionCopy->setParam(CONDITIONPARAM_OWNER, caster->getID()); + } + + //TODO: infight condition until all aggressive conditions has ended + result = target->addCombatCondition(conditionCopy); + } + } + } + + return result; +} + +bool Combat::CombatDispelFunc(Creature* caster, Creature* target, const CombatParams& params, void* data) +{ + if (!target->hasCondition(params.dispelType)) { + return false; + } + + target->removeCondition(caster, params.dispelType); + return true; +} + +bool Combat::CombatNullFunc(Creature* caster, Creature* target, const CombatParams& params, void* data) +{ + CombatConditionFunc(caster, target, params, NULL); + CombatDispelFunc(caster, target, params, NULL); + return true; +} + +void Combat::combatTileEffects(const SpectatorVec& list, Creature* caster, Tile* tile, const CombatParams& params) +{ + if (params.itemId != 0) { + uint32_t itemId = params.itemId; + Player* _caster = NULL; + + if (caster) { + if (caster->getPlayer()) { + _caster = caster->getPlayer(); + } else if (caster->isSummon()) { + _caster = caster->getMaster()->getPlayer(); + } + } + + switch (itemId) { + case ITEM_FIREFIELD_PERSISTENT_FULL: + itemId = ITEM_FIREFIELD_PVP_FULL; + break; + + case ITEM_FIREFIELD_PERSISTENT_MEDIUM: + itemId = ITEM_FIREFIELD_PVP_MEDIUM; + break; + + case ITEM_FIREFIELD_PERSISTENT_SMALL: + itemId = ITEM_FIREFIELD_PVP_SMALL; + break; + + case ITEM_ENERGYFIELD_PERSISTENT: + itemId = ITEM_ENERGYFIELD_PVP; + break; + + case ITEM_POISONFIELD_PERSISTENT: + itemId = ITEM_POISONFIELD_PVP; + break; + + case ITEM_MAGICWALL_PERSISTENT: + itemId = ITEM_MAGICWALL; + break; + + case ITEM_WILDGROWTH_PERSISTENT: + itemId = ITEM_WILDGROWTH; + break; + + default: + break; + } + + if (_caster) { + if (g_game.getWorldType() == WORLD_TYPE_NO_PVP || tile->hasFlag(TILESTATE_NOPVPZONE)) { + if (itemId == ITEM_FIREFIELD_PVP_FULL) { + itemId = ITEM_FIREFIELD_NOPVP; + } else if (itemId == ITEM_POISONFIELD_PVP) { + itemId = ITEM_POISONFIELD_NOPVP; + } else if (itemId == ITEM_ENERGYFIELD_PVP) { + itemId = ITEM_ENERGYFIELD_NOPVP; + } + } else if (itemId == ITEM_FIREFIELD_PVP_FULL || itemId == ITEM_POISONFIELD_PVP || itemId == ITEM_ENERGYFIELD_PVP) { + _caster->addInFightTicks(true); + } + } + + Item* item = Item::CreateItem(itemId); + + if (caster) { + item->setOwner(caster->getID()); + } + + ReturnValue ret = g_game.internalAddItem(tile, item); + + if (ret == RET_NOERROR) { + g_game.startDecay(item); + } else { + delete item; + } + } + + if (params.tileCallback) { + params.tileCallback->onTileCombat(caster, tile); + } + + if (params.impactEffect != NM_ME_NONE) { + g_game.addMagicEffect(list, tile->getPosition(), params.impactEffect); + } +} + +void Combat::postCombatEffects(Creature* caster, const Position& pos, const CombatParams& params) +{ + if (caster && params.distanceEffect != NM_ME_NONE) { + addDistanceEffect(caster, caster->getPosition(), pos, params.distanceEffect); + } +} + +void Combat::addDistanceEffect(Creature* caster, const Position& fromPos, const Position& toPos, + uint8_t effect) +{ + uint8_t distanceEffect = effect; + + if (caster && distanceEffect == NM_SHOOT_WEAPONTYPE) { + switch (caster->getWeaponType()) { + case WEAPON_AXE: + distanceEffect = NM_SHOOT_WHIRLWINDAXE; + break; + case WEAPON_SWORD: + distanceEffect = NM_SHOOT_WHIRLWINDSWORD; + break; + case WEAPON_CLUB: + distanceEffect = NM_SHOOT_WHIRLWINDCLUB; + break; + default: + distanceEffect = NM_ME_NONE; + break; + } + } + + if (distanceEffect != NM_ME_NONE) { + g_game.addDistanceEffect(fromPos, toPos, distanceEffect); + } +} + +void Combat::CombatFunc(Creature* caster, const Position& pos, + const AreaCombat* area, const CombatParams& params, COMBATFUNC func, void* data) +{ + std::list tileList; + + if (caster) { + getCombatArea(caster->getPosition(), pos, area, tileList); + } else { + getCombatArea(pos, pos, area, tileList); + } + + SpectatorVec list; + uint32_t maxX = 0; + uint32_t maxY = 0; + uint32_t diff; + + //calculate the max viewable range + for (std::list::iterator it = tileList.begin(); it != tileList.end(); ++it) { + const Position& tilePos = (*it)->getPosition(); + diff = std::abs(tilePos.x - pos.x); + + if (diff > maxX) { + maxX = diff; + } + + diff = std::abs(tilePos.y - pos.y); + + if (diff > maxY) { + maxY = diff; + } + } + + g_game.getSpectators(list, pos, true, true, maxX + Map::maxViewportX, maxX + Map::maxViewportX, maxY + Map::maxViewportY, maxY + Map::maxViewportY); + + for (std::list::iterator it = tileList.begin(); it != tileList.end(); ++it) { + Tile* iter_tile = *it; + bool bContinue = true; + + if (canDoCombat(caster, iter_tile, params.isAggressive) == RET_NOERROR) { + if (CreatureVector* creatures = iter_tile->getCreatures()) { + for (CreatureVector::iterator cit = creatures->begin(), cend = creatures->end(); bContinue && cit != cend; ++cit) { + if (params.targetCasterOrTopMost) { + if (caster && caster->getTile() == iter_tile) { + if (*cit == caster) { + bContinue = false; + } + } else if (*cit == iter_tile->getTopCreature()) { + bContinue = false; + } + + if (bContinue) { + continue; + } + } + + if (!params.isAggressive || (caster != *cit && Combat::canDoCombat(caster, *cit) == RET_NOERROR)) { + func(caster, *cit, params, data); + + if (params.targetCallback) { + params.targetCallback->onTargetCombat(caster, *cit); + } + } + } + } + + combatTileEffects(list, caster, iter_tile, params); + } + } + + postCombatEffects(caster, pos, params); +} + +void Combat::doCombat(Creature* caster, Creature* target) const +{ + //target combat callback function + if (params.combatType != COMBAT_NONE) { + int32_t minChange = 0; + int32_t maxChange = 0; + getMinMaxValues(caster, target, minChange, maxChange); + + if (params.combatType != COMBAT_MANADRAIN) { + doCombatHealth(caster, target, minChange, maxChange, params); + } else { + doCombatMana(caster, target, minChange, maxChange, params); + } + } else { + doCombatDefault(caster, target, params); + } +} + +void Combat::doCombat(Creature* caster, const Position& pos) const +{ + //area combat callback function + if (params.combatType != COMBAT_NONE) { + int32_t minChange = 0; + int32_t maxChange = 0; + getMinMaxValues(caster, NULL, minChange, maxChange); + + if (params.combatType != COMBAT_MANADRAIN) { + doCombatHealth(caster, pos, area, minChange, maxChange, params); + } else { + doCombatMana(caster, pos, area, minChange, maxChange, params); + } + } else { + CombatFunc(caster, pos, area, params, CombatNullFunc, NULL); + } +} + +void Combat::doCombatHealth(Creature* caster, Creature* target, + int32_t minChange, int32_t maxChange, const CombatParams& params) +{ + if (!params.isAggressive || (caster != target && Combat::canDoCombat(caster, target) == RET_NOERROR)) { + Combat2Var var; + var.change = random_range(minChange, maxChange, DISTRO_NORMAL); + CombatHealthFunc(caster, target, params, (void*)&var); + + if (params.impactEffect != NM_ME_NONE) { + g_game.addMagicEffect(target->getPosition(), params.impactEffect); + } + + if (caster && params.distanceEffect != NM_ME_NONE) { + addDistanceEffect(caster, caster->getPosition(), target->getPosition(), params.distanceEffect); + } + } +} + +void Combat::doCombatHealth(Creature* caster, const Position& pos, + const AreaCombat* area, int32_t minChange, int32_t maxChange, const CombatParams& params) +{ + Combat2Var var; + var.change = random_range(minChange, maxChange, DISTRO_NORMAL); + CombatFunc(caster, pos, area, params, CombatHealthFunc, (void*)&var); +} + +void Combat::doCombatMana(Creature* caster, Creature* target, + int32_t minChange, int32_t maxChange, const CombatParams& params) +{ + if (!params.isAggressive || (caster != target && Combat::canDoCombat(caster, target) == RET_NOERROR)) { + Combat2Var var; + var.change = random_range(minChange, maxChange, DISTRO_NORMAL); + CombatManaFunc(caster, target, params, (void*)&var); + + if (params.targetCallback) { + params.targetCallback->onTargetCombat(caster, target); + } + + if (params.impactEffect != NM_ME_NONE) { + g_game.addMagicEffect(target->getPosition(), params.impactEffect); + } + + if (caster && params.distanceEffect != NM_ME_NONE) { + addDistanceEffect(caster, caster->getPosition(), target->getPosition(), params.distanceEffect); + } + } +} + +void Combat::doCombatMana(Creature* caster, const Position& pos, + const AreaCombat* area, int32_t minChange, int32_t maxChange, const CombatParams& params) +{ + Combat2Var var; + var.change = random_range(minChange, maxChange, DISTRO_NORMAL); + CombatFunc(caster, pos, area, params, CombatManaFunc, (void*)&var); +} + +void Combat::doCombatCondition(Creature* caster, const Position& pos, const AreaCombat* area, + const CombatParams& params) +{ + CombatFunc(caster, pos, area, params, CombatConditionFunc, NULL); +} + +void Combat::doCombatCondition(Creature* caster, Creature* target, const CombatParams& params) +{ + if (!params.isAggressive || (caster != target && Combat::canDoCombat(caster, target) == RET_NOERROR)) { + CombatConditionFunc(caster, target, params, NULL); + + if (params.targetCallback) { + params.targetCallback->onTargetCombat(caster, target); + } + + if (params.impactEffect != NM_ME_NONE) { + g_game.addMagicEffect(target->getPosition(), params.impactEffect); + } + + if (caster && params.distanceEffect != NM_ME_NONE) { + addDistanceEffect(caster, caster->getPosition(), target->getPosition(), params.distanceEffect); + } + } +} + +void Combat::doCombatDispel(Creature* caster, const Position& pos, const AreaCombat* area, + const CombatParams& params) +{ + CombatFunc(caster, pos, area, params, CombatDispelFunc, NULL); +} + +void Combat::doCombatDispel(Creature* caster, Creature* target, const CombatParams& params) +{ + if (!params.isAggressive || (caster != target && Combat::canDoCombat(caster, target) == RET_NOERROR)) { + CombatDispelFunc(caster, target, params, NULL); + + if (params.targetCallback) { + params.targetCallback->onTargetCombat(caster, target); + } + + if (params.impactEffect != NM_ME_NONE) { + g_game.addMagicEffect(target->getPosition(), params.impactEffect); + } + + if (caster && params.distanceEffect != NM_ME_NONE) { + addDistanceEffect(caster, caster->getPosition(), target->getPosition(), params.distanceEffect); + } + } +} + +void Combat::doCombatDefault(Creature* caster, Creature* target, const CombatParams& params) +{ + if (!params.isAggressive || (caster != target && Combat::canDoCombat(caster, target) == RET_NOERROR)) { + SpectatorVec list; + g_game.getSpectators(list, target->getPosition(), true, true); + + CombatNullFunc(caster, target, params, NULL); + combatTileEffects(list, caster, target->getTile(), params); + + if (params.targetCallback) { + params.targetCallback->onTargetCombat(caster, target); + } + + //if(params.impactEffect != NM_ME_NONE) + // g_game.addMagicEffect(target->getPosition(), params.impactEffect); + + if (caster && params.distanceEffect != NM_ME_NONE) { + addDistanceEffect(caster, caster->getPosition(), target->getPosition(), params.distanceEffect); + } + } +} + +//**********************************************************// + +void ValueCallback::getMinMaxValues(Player* player, int32_t& min, int32_t& max, bool useCharges) const +{ + //"onGetPlayerMinMaxValues"(...) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + lua_State* L = m_scriptInterface->getLuaState(); + + if (!env->setCallbackId(m_scriptId, m_scriptInterface)) { + return; + } + + uint32_t cid = env->addThing(player); + + m_scriptInterface->pushFunction(m_scriptId); + lua_pushnumber(L, cid); + + int32_t parameters = 1; + + switch (type) { + case FORMULA_LEVELMAGIC: { + //"onGetPlayerMinMaxValues"(cid, level, maglevel) + lua_pushnumber(L, player->getLevel()); + lua_pushnumber(L, player->getMagicLevel()); + parameters += 2; + break; + } + + case FORMULA_SKILL: { + //"onGetPlayerMinMaxValues"(cid, attackSkill, attackValue, attackFactor) + Item* tool = player->getWeapon(); + int32_t attackSkill = player->getWeaponSkill(tool); + int32_t attackValue = 7; + + if (tool) { + attackValue = tool->getAttack(); + + if (useCharges && tool->hasCharges()) { + int32_t newCharge = std::max(0, tool->getCharges() - 1); + g_game.transformItem(tool, tool->getID(), newCharge); + } + } + + float attackFactor = player->getAttackFactor(); + + lua_pushnumber(L, attackSkill); + lua_pushnumber(L, attackValue); + lua_pushnumber(L, attackFactor); + parameters += 3; + break; + } + + default: { + std::cout << "ValueCallback::getMinMaxValues - unknown callback type" << std::endl; + return; + break; + } + } + + int32_t size0 = lua_gettop(L); + + if (lua_pcall(L, parameters, 2 /*nReturnValues*/, 0) != 0) { + LuaScriptInterface::reportError(NULL, LuaScriptInterface::popString(L)); + } else { + max = LuaScriptInterface::popNumber(L); + min = LuaScriptInterface::popNumber(L); + } + + if ((lua_gettop(L) + parameters /*nParams*/ + 1) != size0) { + LuaScriptInterface::reportError(NULL, "Stack size changed!"); + } + + env->resetCallback(); + m_scriptInterface->releaseScriptEnv(); + } else { + std::cout << "[Error] Call stack overflow. ValueCallback::getMinMaxValues" << std::endl; + return; + } +} + +//**********************************************************// + +void TileCallback::onTileCombat(Creature* creature, Tile* tile) const +{ + //"onTileCombat"(cid, pos) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + lua_State* L = m_scriptInterface->getLuaState(); + + if (!env->setCallbackId(m_scriptId, m_scriptInterface)) { + return; + } + + uint32_t cid = 0; + + if (creature) { + cid = env->addThing(creature); + } + + m_scriptInterface->pushFunction(m_scriptId); + lua_pushnumber(L, cid); + m_scriptInterface->pushPosition(L, tile->getPosition(), 0); + + m_scriptInterface->callFunction(2); + + env->resetCallback(); + m_scriptInterface->releaseScriptEnv(); + } else { + std::cout << "[Error] Call stack overflow. TileCallback::onTileCombat" << std::endl; + return; + } +} + +//**********************************************************// + +void TargetCallback::onTargetCombat(Creature* creature, Creature* target) const +{ + //"onTargetCombat"(cid, target) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + lua_State* L = m_scriptInterface->getLuaState(); + + if (!env->setCallbackId(m_scriptId, m_scriptInterface)) { + return; + } + + uint32_t cid = 0; + + if (creature) { + cid = env->addThing(creature); + } + + uint32_t targetCid = env->addThing(target); + + m_scriptInterface->pushFunction(m_scriptId); + lua_pushnumber(L, cid); + lua_pushnumber(L, targetCid); + + int32_t size0 = lua_gettop(L); + + if (lua_pcall(L, 2, 0 /*nReturnValues*/, 0) != 0) { + LuaScriptInterface::reportError(NULL, LuaScriptInterface::popString(L)); + } + + if ((lua_gettop(L) + 2 /*nParams*/ + 1) != size0) { + LuaScriptInterface::reportError(NULL, "Stack size changed!"); + } + + env->resetCallback(); + m_scriptInterface->releaseScriptEnv(); + } else { + std::cout << "[Error] Call stack overflow. TargetCallback::onTargetCombat" << std::endl; + return; + } +} + +//**********************************************************// + +void AreaCombat::clear() +{ + for (AreaCombatMap::iterator it = areas.begin(); it != areas.end(); ++it) { + delete it->second; + } + + areas.clear(); +} + +AreaCombat::AreaCombat(const AreaCombat& rhs) +{ + hasExtArea = rhs.hasExtArea; + + for (AreaCombatMap::const_iterator it = rhs.areas.begin(); it != rhs.areas.end(); ++it) { + areas[it->first] = new MatrixArea(*it->second); + } +} + +bool AreaCombat::getList(const Position& centerPos, const Position& targetPos, std::list& list) const +{ + Tile* tile = g_game.getTile(targetPos.x, targetPos.y, targetPos.z); + + const MatrixArea* area = getArea(centerPos, targetPos); + + if (!area) { + return false; + } + + int32_t tmpPosX = targetPos.x; + int32_t tmpPosY = targetPos.y; + int32_t tmpPosZ = targetPos.z; + + size_t cols = area->getCols(); + size_t rows = area->getRows(); + + uint32_t centerY, centerX; + area->getCenter(centerY, centerX); + + tmpPosX -= centerX; + tmpPosY -= centerY; + + for (size_t y = 0; y < rows; ++y) { + for (size_t x = 0; x < cols; ++x) { + if (area->getValue(y, x) != 0) { + if (tmpPosX >= 0 && tmpPosX < 0xFFFF && + tmpPosY >= 0 && tmpPosY < 0xFFFF && + tmpPosZ >= 0 && tmpPosZ < MAP_MAX_LAYERS) { + if (g_game.isSightClear(targetPos, Position(tmpPosX, tmpPosY, tmpPosZ), true)) { + tile = g_game.getTile(tmpPosX, tmpPosY, tmpPosZ); + + if (!tile) { + tile = new StaticTile(tmpPosX, tmpPosY, tmpPosZ); + g_game.setTile(tile); + } + + list.push_back(tile); + } + } + } + + tmpPosX++; + } + + tmpPosX -= cols; + tmpPosY++; + } + + return true; +} + +int32_t round(float v) +{ + int32_t t = (long)std::floor(v); + + if ((v - t) > 0.5) { + return t + 1; + } else { + return t; + } +} + +void AreaCombat::copyArea(const MatrixArea* input, MatrixArea* output, MatrixOperation_t op) const +{ + uint32_t centerY, centerX; + input->getCenter(centerY, centerX); + + if (op == MATRIXOPERATION_COPY) { + for (uint32_t y = 0; y < input->getRows(); ++y) { + for (uint32_t x = 0; x < input->getCols(); ++x) { + (*output)[y][x] = (*input)[y][x]; + } + } + + output->setCenter(centerY, centerX); + } else if (op == MATRIXOPERATION_MIRROR) { + for (uint32_t y = 0; y < input->getRows(); ++y) { + int32_t rx = 0; + + for (int32_t x = input->getCols() - 1; x >= 0; --x) { + (*output)[y][rx++] = (*input)[y][x]; + } + } + + output->setCenter(centerY, (input->getRows() - 1) - centerX); + } else if (op == MATRIXOPERATION_FLIP) { + for (uint32_t x = 0; x < input->getCols(); ++x) { + int32_t ry = 0; + + for (int32_t y = input->getRows() - 1; y >= 0; --y) { + (*output)[ry++][x] = (*input)[y][x]; + } + } + + output->setCenter((input->getCols() - 1) - centerY, centerX); + } + //rotation + else { + uint32_t centerX, centerY; + input->getCenter(centerY, centerX); + + int32_t rotateCenterX = (output->getCols() / 2) - 1; + int32_t rotateCenterY = (output->getRows() / 2) - 1; + int32_t angle = 0; + + switch (op) { + case MATRIXOPERATION_ROTATE90: + angle = 90; + break; + + case MATRIXOPERATION_ROTATE180: + angle = 180; + break; + + case MATRIXOPERATION_ROTATE270: + angle = 270; + break; + + default: + angle = 0; + break; + } + + double angleRad = M_PI * angle / 180.0; + + double a = std::cos(angleRad); + double b = -std::sin(angleRad); + double c = std::sin(angleRad); + double d = std::cos(angleRad); + + for (int32_t x = 0; x < (long)input->getCols(); ++x) { + for (int32_t y = 0; y < (long)input->getRows(); ++y) { + //calculate new coordinates using rotation center + int32_t newX = x - centerX; + int32_t newY = y - centerY; + + //perform rotation + int32_t rotatedX = round(newX * a + newY * b); + int32_t rotatedY = round(newX * c + newY * d); + + //write in the output matrix using rotated coordinates + (*output)[rotatedY + rotateCenterY][rotatedX + rotateCenterX] = (*input)[y][x]; + } + } + + output->setCenter(rotateCenterY, rotateCenterX); + } +} + +MatrixArea* AreaCombat::createArea(const std::list& list, uint32_t rows) +{ + uint32_t cols = list.size() / rows; + MatrixArea* area = new MatrixArea(rows, cols); + + uint32_t x = 0; + uint32_t y = 0; + + for (std::list::const_iterator it = list.begin(); it != list.end(); ++it) { + if (*it == 1 || *it == 3) { + area->setValue(y, x, true); + } + + if (*it == 2 || *it == 3) { + area->setCenter(y, x); + } + + ++x; + + if (cols == x) { + x = 0; + ++y; + } + } + + return area; +} + +void AreaCombat::setupArea(const std::list& list, uint32_t rows) +{ + MatrixArea* area = createArea(list, rows); + + //NORTH + areas[NORTH] = area; + + uint32_t maxOutput = std::max(area->getCols(), area->getRows()) * 2; + + //SOUTH + MatrixArea* southArea = new MatrixArea(maxOutput, maxOutput); + copyArea(area, southArea, MATRIXOPERATION_ROTATE180); + areas[SOUTH] = southArea; + + //EAST + MatrixArea* eastArea = new MatrixArea(maxOutput, maxOutput); + copyArea(area, eastArea, MATRIXOPERATION_ROTATE90); + areas[EAST] = eastArea; + + //WEST + MatrixArea* westArea = new MatrixArea(maxOutput, maxOutput); + copyArea(area, westArea, MATRIXOPERATION_ROTATE270); + areas[WEST] = westArea; +} + +void AreaCombat::setupArea(int32_t length, int32_t spread) +{ + std::list list; + + uint32_t rows = length; + int32_t cols = 1; + + if (spread != 0) { + cols = ((length - length % spread) / spread) * 2 + 1; + } + + int32_t colSpread = cols; + + for (uint32_t y = 1; y <= rows; ++y) { + int32_t mincol = cols - colSpread + 1; + int32_t maxcol = cols - (cols - colSpread); + + for (int32_t x = 1; x <= cols; ++x) { + if (y == rows && x == ((cols - cols % 2) / 2) + 1) { + list.push_back(3); + } else if (x >= mincol && x <= maxcol) { + list.push_back(1); + } else { + list.push_back(0); + } + } + + if (spread > 0 && y % spread == 0) { + --colSpread; + } + } + + setupArea(list, rows); +} + +void AreaCombat::setupArea(int32_t radius) +{ + int32_t area[13][13] = { + {0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 8, 8, 7, 8, 8, 0, 0, 0, 0}, + {0, 0, 0, 8, 7, 6, 6, 6, 7, 8, 0, 0, 0}, + {0, 0, 8, 7, 6, 5, 5, 5, 6, 7, 8, 0, 0}, + {0, 8, 7, 6, 5, 4, 4, 4, 5, 6, 7, 8, 0}, + {0, 8, 6, 5, 4, 3, 2, 3, 4, 5, 6, 8, 0}, + {8, 7, 6, 5, 4, 2, 1, 2, 4, 5, 6, 7, 8}, + {0, 8, 6, 5, 4, 3, 2, 3, 4, 5, 6, 8, 0}, + {0, 8, 7, 6, 5, 4, 4, 4, 5, 6, 7, 8, 0}, + {0, 0, 8, 7, 6, 5, 5, 5, 6, 7, 8, 0, 0}, + {0, 0, 0, 8, 7, 6, 6, 6, 7, 8, 0, 0, 0}, + {0, 0, 0, 0, 8, 8, 7, 8, 8, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0} + }; + + std::list list; + + for (int32_t y = 0; y < 13; ++y) { + for (int32_t x = 0; x < 13; ++x) { + if (area[y][x] == 1) { + list.push_back(3); + } else if (area[y][x] > 0 && area[y][x] <= radius) { + list.push_back(1); + } else { + list.push_back(0); + } + } + } + + setupArea(list, 13); +} + +void AreaCombat::setupExtArea(const std::list& list, uint32_t rows) +{ + if (list.empty()) { + return; + } + + hasExtArea = true; + MatrixArea* area = createArea(list, rows); + + //NORTH-WEST + areas[NORTHWEST] = area; + + uint32_t maxOutput = std::max(area->getCols(), area->getRows()) * 2; + + //NORTH-EAST + MatrixArea* neArea = new MatrixArea(maxOutput, maxOutput); + copyArea(area, neArea, MATRIXOPERATION_MIRROR); + areas[NORTHEAST] = neArea; + + //SOUTH-WEST + MatrixArea* swArea = new MatrixArea(maxOutput, maxOutput); + copyArea(area, swArea, MATRIXOPERATION_FLIP); + areas[SOUTHWEST] = swArea; + + //SOUTH-EAST + MatrixArea* seArea = new MatrixArea(maxOutput, maxOutput); + copyArea(swArea, seArea, MATRIXOPERATION_MIRROR); + areas[SOUTHEAST] = seArea; +} + +//**********************************************************// + +void MagicField::onStepInField(Creature* creature) +{ + //remove magic walls/wild growth + if (id == ITEM_MAGICWALL || id == ITEM_WILDGROWTH || id == ITEM_MAGICWALL_SAFE || id == ITEM_WILDGROWTH_SAFE || isBlocking()) { + if (!creature->isInGhostMode()) { + g_game.internalRemoveItem(this, 1); + } + + return; + } + + const ItemType& it = items[getID()]; + + if (it.condition) { + Condition* conditionCopy = it.condition->clone(); + uint32_t ownerId = getOwner(); + + if (ownerId) { + bool harmfulField = true; + + if (g_game.getWorldType() == WORLD_TYPE_NO_PVP || getTile()->hasFlag(TILESTATE_NOPVPZONE)) { + Creature* owner = g_game.getCreatureByID(ownerId); + + if (owner) { + if (owner->getPlayer() || (owner->isSummon() && owner->getMaster()->getPlayer())) { + harmfulField = false; + } + } + } + + Player* targetPlayer = creature->getPlayer(); + + if (targetPlayer) { + Player* attackerPlayer = g_game.getPlayerByID(ownerId); + + if (attackerPlayer) { + if (Combat::isProtected(attackerPlayer, targetPlayer)) { + harmfulField = false; + } + } + } + + if (!harmfulField || (OTSYS_TIME() - createTime <= 5000) || creature->hasBeenAttacked(ownerId)) { + conditionCopy->setParam(CONDITIONPARAM_OWNER, ownerId); + } + } + + creature->addCondition(conditionCopy); + } +} diff --git a/src/combat.h b/src/combat.h new file mode 100644 index 0000000000..898d2efab3 --- /dev/null +++ b/src/combat.h @@ -0,0 +1,390 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_COMBAT_H__ +#define __OTSERV_COMBAT_H__ + +#include "thing.h" +#include "condition.h" +#include "map.h" +#include "baseevents.h" +#include "otsystem.h" + +#include + +class Condition; +class Creature; +class Position; +class Item; + +//for luascript callback +class ValueCallback : public CallBack +{ + public: + ValueCallback(formulaType_t _type) { + type = _type; + } + void getMinMaxValues(Player* player, int32_t& min, int32_t& max, bool useCharges) const; + + protected: + formulaType_t type; +}; + +class TileCallback : public CallBack +{ + public: + TileCallback() {} + void onTileCombat(Creature* creature, Tile* tile) const; + + protected: + formulaType_t type; +}; + +class TargetCallback : public CallBack +{ + public: + TargetCallback() {} + void onTargetCombat(Creature* creature, Creature* target) const; + + protected: + formulaType_t type; +}; + +struct CombatParams { + CombatParams() { + combatType = COMBAT_NONE; + blockedByArmor = false; + blockedByShield = false; + targetCasterOrTopMost = false; + isAggressive = true; + itemId = 0; + impactEffect = NM_ME_NONE; + distanceEffect = NM_ME_NONE; + dispelType = CONDITION_NONE; + useCharges = false; + + valueCallback = NULL; + tileCallback = NULL; + targetCallback = NULL; + } + + std::list conditionList; + ConditionType_t dispelType; + CombatType_t combatType; + bool blockedByArmor; + bool blockedByShield; + bool targetCasterOrTopMost; + bool isAggressive; + uint32_t itemId; + uint8_t impactEffect; + uint8_t distanceEffect; + bool useCharges; + + ValueCallback* valueCallback; + TileCallback* tileCallback; + TargetCallback* targetCallback; +}; + +typedef bool (*COMBATFUNC)(Creature*, Creature*, const CombatParams&, void*); + +struct Combat2Var { + int32_t change; +}; + +class MatrixArea +{ + public: + MatrixArea(uint32_t _rows, uint32_t _cols) { + centerX = 0; + centerY = 0; + + rows = _rows; + cols = _cols; + + data_ = new bool*[rows]; + + for (uint32_t row = 0; row < rows; ++row) { + data_[row] = new bool[cols]; + + for (uint32_t col = 0; col < cols; ++col) { + data_[row][col] = 0; + } + } + } + + MatrixArea(const MatrixArea& rhs) { + centerX = rhs.centerX; + centerY = rhs.centerY; + rows = rhs.rows; + cols = rhs.cols; + + data_ = new bool*[rows]; + + for (uint32_t row = 0; row < rows; ++row) { + data_[row] = new bool[cols]; + + for (uint32_t col = 0; col < cols; ++col) { + data_[row][col] = rhs.data_[row][col]; + } + } + } + + ~MatrixArea() { + for (uint32_t row = 0; row < rows; ++row) { + delete[] data_[row]; + } + + delete[] data_; + } + + void setValue(uint32_t row, uint32_t col, bool value) const { + data_[row][col] = value; + } + bool getValue(uint32_t row, uint32_t col) const { + return data_[row][col]; + } + + void setCenter(uint32_t y, uint32_t x) { + centerX = x; + centerY = y; + } + void getCenter(uint32_t& y, uint32_t& x) const { + x = centerX; + y = centerY; + } + + size_t getRows() const { + return rows; + } + size_t getCols() const { + return cols; + } + + inline const bool* operator[](uint32_t i) const { + return data_[i]; + } + inline bool* operator[](uint32_t i) { + return data_[i]; + } + + protected: + uint32_t centerX; + uint32_t centerY; + + uint32_t rows; + uint32_t cols; + bool** data_; +}; + +typedef std::map AreaCombatMap; + +class AreaCombat +{ + public: + AreaCombat() { + hasExtArea = false; + } + ~AreaCombat() { + clear(); + } + + AreaCombat(const AreaCombat& rhs); + + ReturnValue doCombat(Creature* attacker, const Position& pos, const Combat& combat) const; + bool getList(const Position& centerPos, const Position& targetPos, std::list& list) const; + + void setupArea(const std::list& list, uint32_t rows); + void setupArea(int32_t length, int32_t spread); + void setupArea(int32_t radius); + void setupExtArea(const std::list& list, uint32_t rows); + void clear(); + + protected: + enum MatrixOperation_t { + MATRIXOPERATION_COPY, + MATRIXOPERATION_MIRROR, + MATRIXOPERATION_FLIP, + MATRIXOPERATION_ROTATE90, + MATRIXOPERATION_ROTATE180, + MATRIXOPERATION_ROTATE270, + }; + + MatrixArea* createArea(const std::list& list, uint32_t rows); + void copyArea(const MatrixArea* input, MatrixArea* output, MatrixOperation_t op) const; + + MatrixArea* getArea(const Position& centerPos, const Position& targetPos) const { + int32_t dx = targetPos.x - centerPos.x; + int32_t dy = targetPos.y - centerPos.y; + + Direction dir = NORTH; + + if (dx < 0) { + dir = WEST; + } else if (dx > 0) { + dir = EAST; + } else if (dy < 0) { + dir = NORTH; + } else { + dir = SOUTH; + } + + if (hasExtArea) { + if (dx < 0 && dy < 0) { + dir = NORTHWEST; + } else if (dx > 0 && dy < 0) { + dir = NORTHEAST; + } else if (dx < 0 && dy > 0) { + dir = SOUTHWEST; + } else if (dx > 0 && dy > 0) { + dir = SOUTHEAST; + } + } + + AreaCombatMap::const_iterator it = areas.find(dir); + + if (it != areas.end()) { + return it->second; + } + + return NULL; + } + + AreaCombatMap areas; + bool hasExtArea; +}; + +class Combat +{ + public: + Combat(); + ~Combat(); + + static void doCombatHealth(Creature* caster, Creature* target, + int32_t minChange, int32_t maxChange, const CombatParams& params); + static void doCombatHealth(Creature* caster, const Position& pos, + const AreaCombat* area, int32_t minChange, int32_t maxChange, const CombatParams& params); + + static void doCombatMana(Creature* caster, Creature* target, + int32_t minChange, int32_t maxChange, const CombatParams& params); + static void doCombatMana(Creature* caster, const Position& pos, + const AreaCombat* area, int32_t minChange, int32_t maxChange, const CombatParams& params); + + static void doCombatCondition(Creature* caster, Creature* target, + const CombatParams& params); + static void doCombatCondition(Creature* caster, const Position& pos, + const AreaCombat* area, const CombatParams& params); + + static void doCombatDispel(Creature* caster, Creature* target, + const CombatParams& params); + static void doCombatDispel(Creature* caster, const Position& pos, + const AreaCombat* area, const CombatParams& params); + + static void getCombatArea(const Position& centerPos, const Position& targetPos, + const AreaCombat* area, std::list& list); + + static bool isInPvpZone(const Creature* attacker, const Creature* target); + static bool isProtected(const Player* attacker, const Player* target); + static bool isPlayerCombat(const Creature* target); + static CombatType_t ConditionToDamageType(ConditionType_t type); + static ConditionType_t DamageToConditionType(CombatType_t type); + static ReturnValue canTargetCreature(const Player* attacker, const Creature* target); + static ReturnValue canDoCombat(const Creature* caster, const Tile* tile, bool isAggressive); + static ReturnValue canDoCombat(const Creature* attacker, const Creature* target); + static void postCombatEffects(Creature* caster, const Position& pos, const CombatParams& params); + + static void addDistanceEffect(Creature* caster, const Position& fromPos, const Position& toPos, uint8_t effect); + + void doCombat(Creature* caster, Creature* target) const; + void doCombat(Creature* caster, const Position& pos) const; + + bool setCallback(CallBackParam_t key); + CallBack* getCallback(CallBackParam_t key); + + bool setParam(CombatParam_t param, uint32_t value); + void setArea(AreaCombat* _area) { + delete area; + area = _area; + } + bool hasArea() const { + return area != NULL; + } + void setCondition(const Condition* _condition) { + params.conditionList.push_back(_condition); + } + void setPlayerCombatValues(formulaType_t _type, double _mina, double _minb, double _maxa, double _maxb); + void postCombatEffects(Creature* caster, const Position& pos) const { + Combat::postCombatEffects(caster, pos, params); + } + + protected: + static void doCombatDefault(Creature* caster, Creature* target, const CombatParams& params); + + static void CombatFunc(Creature* caster, const Position& pos, + const AreaCombat* area, const CombatParams& params, COMBATFUNC func, void* data); + + static bool CombatHealthFunc(Creature* caster, Creature* target, const CombatParams& params, void* data); + static bool CombatManaFunc(Creature* caster, Creature* target, const CombatParams& params, void* data); + static bool CombatConditionFunc(Creature* caster, Creature* target, const CombatParams& params, void* data); + static bool CombatDispelFunc(Creature* caster, Creature* target, const CombatParams& params, void* data); + static bool CombatNullFunc(Creature* caster, Creature* target, const CombatParams& params, void* data); + + static void combatTileEffects(const SpectatorVec& list, Creature* caster, Tile* tile, const CombatParams& params); + bool getMinMaxValues(Creature* creature, Creature* target, int32_t& min, int32_t& max) const; + + //configureable + CombatParams params; + + //formula variables + formulaType_t formulaType; + double mina; + double minb; + double maxa; + double maxb; + + AreaCombat* area; +}; + +class MagicField : public Item +{ + public: + MagicField(uint16_t _type) : Item(_type) { + createTime = OTSYS_TIME(); + } + ~MagicField() {} + + virtual MagicField* getMagicField() { + return this; + } + virtual const MagicField* getMagicField() const { + return this; + } + + bool isReplaceable() const { + return Item::items[getID()].replaceable; + } + CombatType_t getCombatType() const { + const ItemType& it = items[getID()]; + return it.combatType; + } + void onStepInField(Creature* creature); + + private: + uint64_t createTime; +}; + +#endif diff --git a/src/commands.cpp b/src/commands.cpp new file mode 100644 index 0000000000..04fafe684f --- /dev/null +++ b/src/commands.cpp @@ -0,0 +1,1505 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include +#include +#include +#include +#include + +#include "commands.h" +#include "player.h" +#include "npc.h" +#include "monsters.h" +#include "game.h" +#include "actions.h" +#include "house.h" +#include "iologindata.h" +#include "tools.h" +#include "ban.h" +#include "configmanager.h" +#include "town.h" +#include "spells.h" +#include "talkaction.h" +#include "movement.h" +#include "spells.h" +#include "weapons.h" +#include "raids.h" +#include "chat.h" +#include "quests.h" +#include "mounts.h" +#include "globalevent.h" +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ +#include "outputmessage.h" +#include "connection.h" +#include "admin.h" +#include "status.h" +#include "protocollogin.h" +#endif + +#include +#include + +extern ConfigManager g_config; +extern Actions* g_actions; +extern Monsters g_monsters; +extern Npcs g_npcs; +extern TalkActions* g_talkActions; +extern MoveEvents* g_moveEvents; +extern Spells* g_spells; +extern Weapons* g_weapons; +extern Game g_game; +extern Chat g_chat; +extern CreatureEvents* g_creatureEvents; +extern GlobalEvents* g_globalEvents; + +s_defcommands Commands::defined_commands[] = { + //admin commands + {"/s", &Commands::placeNpc}, + {"/m", &Commands::placeMonster}, + {"/summon", &Commands::placeSummon}, + {"/B", &Commands::broadcastMessage}, + {"/b", &Commands::banPlayer}, + {"/t", &Commands::teleportMasterPos}, + {"/c", &Commands::teleportHere}, + {"/i", &Commands::createItemById}, + {"/n", &Commands::createItemByName}, + {"/q", &Commands::subtractMoney}, + {"/reload", &Commands::reloadInfo}, + {"/goto", &Commands::teleportTo}, + {"/info", &Commands::getInfo}, + {"/closeserver", &Commands::closeServer}, + {"/openserver", &Commands::openServer}, + {"/a", &Commands::teleportNTiles}, + {"/kick", &Commands::kickPlayer}, + {"/owner", &Commands::setHouseOwner}, + {"/gethouse", &Commands::getHouse}, + {"/town", &Commands::teleportToTown}, + {"/up", &Commands::changeFloor}, + {"/down", &Commands::changeFloor}, + {"/pos", &Commands::showPosition}, + {"/r", &Commands::removeThing}, + {"/newtype", &Commands::newType}, + {"/newitem", &Commands::newItem}, + {"/hide", &Commands::hide}, + {"/raid", &Commands::forceRaid}, + {"/addskill", &Commands::addSkill}, + {"/ban", &Commands::ban}, + {"/unban", &Commands::unban}, + {"/ghost", &Commands::ghost}, + {"/clean", &Commands::clean}, + {"/mccheck", &Commands::multiClientCheck}, + {"/addtutor", &Commands::addTutor}, + {"/removetutor", &Commands::removeTutor}, + {"/serverdiag", &Commands::serverDiag}, + + // player commands - TODO: make them talkactions + {"!online", &Commands::whoIsOnline}, + {"!buyhouse", &Commands::buyHouse}, + {"!sellhouse", &Commands::sellHouse}, + {"!serverinfo", &Commands::serverInfo}, + {"!kills", &Commands::playerKills} +}; + +Commands::Commands() +{ + loaded = false; + + //setup command map + for (uint32_t i = 0; i < sizeof(defined_commands) / sizeof(defined_commands[0]); i++) { + Command* cmd = new Command; + cmd->loadedGroupId = false; + cmd->loadedAccountType = false; + cmd->loadedLogging = false; + cmd->logged = true; + cmd->groupId = 1; + cmd->f = defined_commands[i].f; + std::string key = defined_commands[i].name; + commandMap[key] = cmd; + } +} + +Commands::~Commands() +{ + for (CommandMap::iterator it = commandMap.begin(); it != commandMap.end(); ++it) { + delete it->second; + } +} + +bool Commands::loadFromXml() +{ + std::string filename = "data/XML/commands.xml"; + + xmlDocPtr doc = xmlParseFile(filename.c_str()); + + if (doc) { + loaded = true; + xmlNodePtr root, p; + root = xmlDocGetRootElement(doc); + + if (xmlStrcmp(root->name, (const xmlChar*)"commands")) { + std::cout << "[Error - Commands::loadFromXml] Malformed commands file." << std::endl; + xmlFreeDoc(doc); + return false; + } + + std::string strCmd; + p = root->children; + + while (p) { + if (xmlStrcmp(p->name, (const xmlChar*)"command") == 0) { + if (readXMLString(p, "cmd", strCmd)) { + CommandMap::iterator it = commandMap.find(strCmd); + int32_t gId; + int32_t aTypeLevel; + std::string strValue; + + if (it != commandMap.end()) { + Command* command = it->second; + + if (readXMLInteger(p, "group", gId)) { + if (!command->loadedGroupId) { + command->groupId = gId; + command->loadedGroupId = true; + } else { + std::cout << "Duplicated command " << strCmd << std::endl; + } + } else { + std::cout << "missing group tag for " << strCmd << std::endl; + } + + if (readXMLInteger(p, "acctype", aTypeLevel)) { + if (!command->loadedAccountType) { + command->accountType = (AccountType_t)aTypeLevel; + command->loadedAccountType = true; + } else { + std::cout << "Duplicated command " << strCmd << std::endl; + } + } else { + std::cout << "missing acctype tag for " << strCmd << std::endl; + } + + if (readXMLString(p, "log", strValue)) { + if (!command->loadedLogging) { + command->logged = booleanString(strValue); + command->loadedLogging = true; + } else { + std::cout << "Duplicated log tag for " << strCmd << std::endl; + } + } else { + std::cout << "Missing log tag for " << strCmd << std::endl; + } + } else { + std::cout << "Unknown command " << strCmd << std::endl; + } + } else { + std::cout << "missing cmd." << std::endl; + } + } + + p = p->next; + } + + xmlFreeDoc(doc); + } + + for (CommandMap::iterator it = commandMap.begin(), end = commandMap.end(); it != end; ++it) { + Command* command = it->second; + + if (!command->loadedGroupId) { + std::cout << "Warning: Missing group id for command " << it->first << std::endl; + } + + if (!command->loadedAccountType) { + std::cout << "Warning: Missing acctype level for command " << it->first << std::endl; + } + + if (!command->loadedLogging) { + std::cout << "Warning: Missing log command " << it->first << std::endl; + } + + g_game.addCommandTag(it->first.substr(0, 1)); + } + + return loaded; +} + +bool Commands::reload() +{ + loaded = false; + + for (CommandMap::iterator it = commandMap.begin(), end = commandMap.end(); it != end; ++it) { + Command* command = it->second; + command->groupId = 1; + command->accountType = ACCOUNT_TYPE_GOD; + command->loadedGroupId = false; + command->loadedAccountType = false; + command->logged = true; + command->loadedLogging = false; + } + + g_game.resetCommandTag(); + return loadFromXml(); +} + +bool Commands::exeCommand(Player* player, const std::string& cmd) +{ + std::string str_command; + std::string str_param; + + std::string::size_type loc = cmd.find(' ', 0); + + if (loc != std::string::npos) { + str_command = std::string(cmd, 0, loc); + str_param = std::string(cmd, (loc + 1), cmd.size() - loc - 1); + } else { + str_command = cmd; + str_param = std::string(""); + } + + //find command + CommandMap::iterator it = commandMap.find(str_command); + + if (it == commandMap.end()) { + return false; + } + + Command* command = it->second; + + if (command->groupId > player->groupId || command->accountType > player->accountType) { + if (player->accessLevel) { + player->sendTextMessage(MSG_STATUS_SMALL, "You can not execute this command."); + } + + return false; + } + + //execute command + CommandFunc cfunc = command->f; + (this->*cfunc)(player, str_command, str_param); + + if (command->logged) { + player->sendTextMessage(MSG_STATUS_CONSOLE_RED, cmd.c_str()); + + if (dirExists("data/logs") || createDir("data/logs")) { + std::ostringstream ss; + ss << "data/logs/" << player->getName() << " commands.log"; + + std::ofstream out(ss.str().c_str(), std::ios::app); + + time_t ticks = time(NULL); + const tm* now = localtime(&ticks); + char buf[32]; + strftime(buf, sizeof(buf), "%d/%m/%Y %H:%M", now); + + out << "[" << buf << "] " << cmd << std::endl; + + out.close(); + } else { + std::cout << "[Warning - Commands::exeCommand] Cannot access \"data/logs\" for writing: " << strerror(errno) << "." << std::endl; + } + } + + return true; +} + +void Commands::placeNpc(Player* player, const std::string& cmd, const std::string& param) +{ + Npc* npc = Npc::createNpc(param); + + if (!npc) { + return; + } + + // Place the npc + if (g_game.placeCreature(npc, player->getPosition())) { + g_game.addMagicEffect(player->getPosition(), NM_ME_MAGIC_BLOOD); + npc->setMasterPos(npc->getPosition()); + } else { + delete npc; + player->sendCancelMessage(RET_NOTENOUGHROOM); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + } +} + +void Commands::placeMonster(Player* player, const std::string& cmd, const std::string& param) +{ + Monster* monster = Monster::createMonster(param); + + if (!monster) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return; + } + + // Place the monster + if (g_game.placeCreature(monster, player->getPosition())) { + g_game.addMagicEffect(monster->getPosition(), NM_ME_TELEPORT); + g_game.addMagicEffect(player->getPosition(), NM_ME_MAGIC_BLOOD); + } else { + delete monster; + player->sendCancelMessage(RET_NOTENOUGHROOM); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + } +} + +void Commands::placeSummon(Player* player, const std::string& cmd, const std::string& param) +{ + Monster* monster = Monster::createMonster(param); + + if (!monster) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return; + } + + // Place the monster + player->addSummon(monster); + + if (!g_game.placeCreature(monster, player->getPosition())) { + player->removeSummon(monster); + player->sendCancelMessage(RET_NOTENOUGHROOM); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + } else { + g_game.addMagicEffect(monster->getPosition(), NM_ME_TELEPORT); + } +} + +void Commands::broadcastMessage(Player* player, const std::string& cmd, const std::string& param) +{ + g_game.playerBroadcastMessage(player, param); +} + +void Commands::banPlayer(Player* player, const std::string& cmd, const std::string& param) +{ + Player* playerBan = g_game.getPlayerByName(param); + + if (playerBan) { + if (playerBan->hasFlag(PlayerFlag_CannotBeBanned)) { + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "You cannot ban this player."); + return; + } + + playerBan->sendTextMessage(MSG_STATUS_CONSOLE_RED, "You have been banned."); + uint32_t ip = playerBan->lastIP; + + if (ip > 0) { + IOBan::getInstance()->addIpBan(ip, 0xFFFFFFFF, (time(NULL) + 86400)); + } + + playerBan->kickPlayer(true); + } +} + +void Commands::teleportMasterPos(Player* player, const std::string& cmd, const std::string& param) +{ + Position oldPosition = player->getPosition(); + Position destPos = player->masterPos; + Position newPosition = g_game.getClosestFreeTile(player, 0, destPos, true); + + if (player->getPosition() != destPos) { + if (newPosition.x == 0) { + player->sendCancel("You can not teleport there."); + } else if (g_game.internalTeleport(player, newPosition) == RET_NOERROR) { + g_game.addMagicEffect(oldPosition, NM_ME_POFF, player->isInGhostMode()); + g_game.addMagicEffect(newPosition, NM_ME_TELEPORT, player->isInGhostMode()); + } + } +} + +void Commands::teleportHere(Player* player, const std::string& cmd, const std::string& param) +{ + Creature* paramCreature = g_game.getCreatureByName(param); + + if (paramCreature) { + Position oldPosition = paramCreature->getPosition(); + Position destPos = paramCreature->getPosition(); + Position newPosition = g_game.getClosestFreeTile(player, paramCreature, player->getPosition(), false); + + if (newPosition.x == 0) { + std::ostringstream ss; + ss << "You can not teleport " << paramCreature->getName() << std::endl; + player->sendCancel(ss.str()); + } else if (g_game.internalTeleport(paramCreature, newPosition) == RET_NOERROR) { + g_game.addMagicEffect(oldPosition, NM_ME_POFF, paramCreature->isInGhostMode()); + g_game.addMagicEffect(newPosition, NM_ME_TELEPORT, paramCreature->isInGhostMode()); + } + } else { + player->sendCancel("A creature with that name could not be found."); + } +} + +void Commands::createItemById(Player* player, const std::string& cmd, const std::string& param) +{ + std::string tmp = param; + + std::string::size_type pos = tmp.find(' ', 0); + + if (pos == std::string::npos) { + pos = tmp.size(); + } + + int32_t type = atoi(tmp.substr(0, pos).c_str()); + int32_t count = 1; + + if (pos < tmp.size()) { + tmp.erase(0, pos + 1); + count = std::max(1, std::min(atoi(tmp.c_str()), 100)); + } + + Item* newItem = Item::CreateItem(type, count); + + if (!newItem) { + return; + } + + ReturnValue ret = g_game.internalAddItem(player, newItem); + + if (ret != RET_NOERROR) { + ret = g_game.internalAddItem(player->getTile(), newItem, INDEX_WHEREEVER, FLAG_NOLIMIT); + + if (ret != RET_NOERROR) { + delete newItem; + return; + } + } + + g_game.startDecay(newItem); + g_game.addMagicEffect(player->getPosition(), NM_ME_MAGIC_POISON); +} + +void Commands::createItemByName(Player* player, const std::string& cmd, const std::string& param) +{ + std::string::size_type pos1 = param.find("\""); + pos1 = (std::string::npos == pos1 ? 0 : pos1 + 1); + + std::string::size_type pos2 = param.rfind("\""); + + if (pos2 == pos1 || pos2 == std::string::npos) { + pos2 = param.rfind(' '); + + if (pos2 == std::string::npos) { + pos2 = param.size(); + } + } + + std::string itemName = param.substr(pos1, pos2 - pos1); + + int32_t count = 1; + + if (pos2 < param.size()) { + std::string itemCount = param.substr(pos2 + 1, param.size() - (pos2 + 1)); + count = std::min(atoi(itemCount.c_str()), 100); + } + + int32_t itemId = Item::items.getItemIdByName(itemName); + + if (itemId == -1) { + player->sendTextMessage(MSG_STATUS_CONSOLE_RED, "Item could not be summoned."); + return; + } + + Item* newItem = Item::CreateItem(itemId, count); + + if (!newItem) { + return; + } + + ReturnValue ret = g_game.internalAddItem(player, newItem); + + if (ret != RET_NOERROR) { + ret = g_game.internalAddItem(player->getTile(), newItem, INDEX_WHEREEVER, FLAG_NOLIMIT); + + if (ret != RET_NOERROR) { + delete newItem; + return; + } + } + + g_game.startDecay(newItem); + g_game.addMagicEffect(player->getPosition(), NM_ME_MAGIC_POISON); +} + +void Commands::subtractMoney(Player* player, const std::string& cmd, const std::string& param) +{ + std::ostringstream ss; + ss << "You have " << g_game.getMoney(player) << " gold."; + player->sendCancel(ss.str()); +} + +void Commands::reloadInfo(Player* player, const std::string& cmd, const std::string& param) +{ + std::string tmpParam = asLowerCaseString(param); + + if (tmpParam == "action" || tmpParam == "actions") { + g_actions->reload(); + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded actions."); + } else if (tmpParam == "config" || tmpParam == "configuration") { + g_config.reload(); + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded config."); + } else if (tmpParam == "command" || tmpParam == "commands") { + reload(); + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded commands."); + } else if (tmpParam == "creaturescript" || tmpParam == "creaturescripts") { + g_creatureEvents->reload(); + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded creature scripts."); + } else if (tmpParam == "monster" || tmpParam == "monsters") { + g_monsters.reload(); + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded monsters."); + } else if (tmpParam == "move" || tmpParam == "movement" || tmpParam == "movements" + || tmpParam == "moveevents" || tmpParam == "moveevent") { + g_moveEvents->reload(); + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded movements."); + } else if (tmpParam == "npc" || tmpParam == "npcs") { + g_npcs.reload(); + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded npcs."); + } else if (tmpParam == "raid" || tmpParam == "raids") { + Raids::getInstance()->reload(); + Raids::getInstance()->startup(); + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded raids."); + } else if (tmpParam == "spell" || tmpParam == "spells") { + g_spells->reload(); + g_monsters.reload(); + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded spells."); + } else if (tmpParam == "talk" || tmpParam == "talkaction" || tmpParam == "talkactions") { + g_talkActions->reload(); + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded talk actions."); + } else if (tmpParam == "items") { + Item::items.reload(); + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded items."); + } else if (tmpParam == "weapon" || tmpParam == "weapons") { + g_weapons->reload(); + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded weapons."); + } else if (tmpParam == "quest" || tmpParam == "quests") { + Quests::getInstance()->reload(); + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded quests."); + } else if (tmpParam == "mount" || tmpParam == "mounts") { + Mounts::getInstance()->reload(); + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded mounts."); + } else if (tmpParam == "globalevents" || tmpParam == "globalevent") { + g_globalEvents->reload(); + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded globalevents."); + } else { + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reload type not found."); + } +} + +void Commands::teleportToTown(Player* player, const std::string& cmd, const std::string& param) +{ + std::string tmp = param; + Town* town = Towns::getInstance().getTown(tmp); + + if (town) { + Position oldPosition = player->getPosition(); + Position newPosition = g_game.getClosestFreeTile(player, 0, town->getTemplePosition(), true); + + if (player->getPosition() != town->getTemplePosition()) { + if (newPosition.x == 0) { + player->sendCancel("You can not teleport there."); + } else if (g_game.internalTeleport(player, newPosition) == RET_NOERROR) { + g_game.addMagicEffect(oldPosition, NM_ME_POFF, player->isInGhostMode()); + g_game.addMagicEffect(newPosition, NM_ME_TELEPORT, player->isInGhostMode()); + } + } + } else { + player->sendCancel("Could not find the town."); + } +} + +void Commands::teleportTo(Player* player, const std::string& cmd, const std::string& param) +{ + Creature* paramCreature = g_game.getCreatureByName(param); + + if (paramCreature) { + Position oldPosition = player->getPosition(); + Position newPosition = g_game.getClosestFreeTile(player, 0, paramCreature->getPosition(), true); + + if (newPosition.x > 0) { + if (g_game.internalTeleport(player, newPosition) == RET_NOERROR) { + bool ghostMode = false; + + if (player->isInGhostMode() || paramCreature->isInGhostMode()) { + ghostMode = true; + } + + g_game.addMagicEffect(oldPosition, NM_ME_POFF, ghostMode); + g_game.addMagicEffect(player->getPosition(), NM_ME_TELEPORT, ghostMode); + } + } else { + std::ostringstream ss; + ss << "You can not teleport to " << paramCreature->getName() << "."; + player->sendCancel(ss.str()); + } + } +} + +void Commands::getInfo(Player* player, const std::string& cmd, const std::string& param) +{ + Player* paramPlayer = g_game.getPlayerByName(param); + + if (!paramPlayer) { + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Player not found."); + return; + } + + if (player->getAccountType() < paramPlayer->getAccountType()) { + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "You can not get info about this player."); + return; + } + + uint32_t playerIp = paramPlayer->getIP(); + Account account = IOLoginData::getInstance()->loadAccount(paramPlayer->getAccount()); + + std::ostringstream info; + info << "Name: " << paramPlayer->name << std::endl << + "Access: " << paramPlayer->accessLevel << std::endl << + "Level: " << paramPlayer->level << std::endl << + "Magic Level: " << paramPlayer->magLevel << std::endl << + "Speed: " << paramPlayer->getSpeed() << std::endl << + "Position: " << paramPlayer->getPosition() << std::endl << + "Notations: " << IOBan::getInstance()->getNotationsCount(paramPlayer->getAccount()) << std::endl << + "Warnings: " << account.warnings << std::endl << + "IP: " << convertIPToString(playerIp) << std::endl; + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, info.str()); + + if (playerIp != 0) { + PlayerVector vec; + + for (AutoList::listiterator it = Player::listPlayer.list.begin(), end = Player::listPlayer.list.end(); it != end; ++it) { + if (it->second->getIP() != playerIp || it->second == paramPlayer) { + continue; + } + + vec.push_back(it->second); + } + + if (!vec.empty()) { + std::ostringstream ss; + + Player* tmpPlayer = vec[0]; + ss << "Other players on same IP: " << tmpPlayer->getName() << " [" << tmpPlayer->getLevel() << "]"; + + for (PlayerVector::size_type i = 1, size = vec.size(); i < size; ++i) { + tmpPlayer = vec[i]; + ss << ", " << tmpPlayer->getName() << " [" << tmpPlayer->getLevel() << "]"; + } + + ss << "."; + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, ss.str()); + } + } +} + +void Commands::closeServer(Player* player, const std::string& cmd, const std::string& param) +{ + if (param == "shutdown") { + g_game.setGameState(GAME_STATE_SHUTDOWN); + } else { + g_game.setGameState(GAME_STATE_CLOSED); + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Server is now closed."); + } +} + +void Commands::openServer(Player* player, const std::string& cmd, const std::string& param) +{ + g_game.setGameState(GAME_STATE_NORMAL); + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Server is now open."); +} + +void Commands::teleportNTiles(Player* player, const std::string& cmd, const std::string& param) +{ + int32_t ntiles = atoi(param.c_str()); + + if (ntiles != 0) { + Position oldPosition = player->getPosition(); + Position newPos = player->getPosition(); + + switch (player->direction) { + case NORTH: + newPos.y -= ntiles; + break; + case SOUTH: + newPos.y += ntiles; + break; + case EAST: + newPos.x += ntiles; + break; + case WEST: + newPos.x -= ntiles; + break; + default: + break; + } + + Position newPosition = g_game.getClosestFreeTile(player, 0, newPos, true); + + if (newPosition.x == 0) { + player->sendCancel("You can not teleport there."); + } else if (g_game.internalTeleport(player, newPosition) == RET_NOERROR) { + if (ntiles != 1) { + g_game.addMagicEffect(oldPosition, NM_ME_POFF, player->isInGhostMode()); + g_game.addMagicEffect(newPosition, NM_ME_TELEPORT, player->isInGhostMode()); + } + } + } +} + +void Commands::kickPlayer(Player* player, const std::string& cmd, const std::string& param) +{ + Player* playerKick = g_game.getPlayerByName(param); + + if (playerKick) { + if (playerKick->accessLevel) { + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "You cannot kick this player."); + return; + } + + playerKick->kickPlayer(true); + } +} + +void Commands::setHouseOwner(Player* player, const std::string& cmd, const std::string& param) +{ + if (player->getTile()->hasFlag(TILESTATE_HOUSE)) { + HouseTile* houseTile = dynamic_cast(player->getTile()); + + if (houseTile) { + uint32_t guid; + std::string name = param; + + if (name == "none") { + houseTile->getHouse()->setHouseOwner(0); + } else if (IOLoginData::getInstance()->getGuidByName(guid, name)) { + houseTile->getHouse()->setHouseOwner(guid); + } else { + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Player not found."); + } + } + } +} + +void Commands::sellHouse(Player* player, const std::string& cmd, const std::string& param) +{ + House* house = Houses::getInstance().getHouseByPlayerId(player->guid); + + if (!house) { + player->sendCancel("You do not own any house."); + return; + } + + Player* tradePartner = g_game.getPlayerByName(param); + + if (!(tradePartner && tradePartner != player)) { + player->sendCancel("Trade player not found."); + return; + } + + if (tradePartner->level < 1) { + player->sendCancel("Trade player level is too low."); + return; + } + + if (!Position::areInRange<2, 2, 0>(tradePartner->getPosition(), player->getPosition())) { + player->sendCancel("Trade player is too far away."); + return; + } + + if (!tradePartner->isPremium()) { + player->sendCancel("Trade player does not have a premium account."); + return; + } + + if (Houses::getInstance().getHouseByPlayerId(tradePartner->guid)) { + player->sendCancel("Trade player already owns a house."); + return; + } + + if (IOLoginData::getInstance()->hasBiddedOnHouse(tradePartner->guid)) { + player->sendCancel("Trade player is currently the highest bidder of an auctioned house."); + return; + } + + Item* transferItem = house->getTransferItem(); + + if (!transferItem) { + player->sendCancel("You can not trade this house."); + return; + } + + transferItem->getParent()->setParent(player); + + if (!g_game.internalStartTrade(player, tradePartner, transferItem)) { + house->resetTransferItem(); + } +} + +void Commands::getHouse(Player* player, const std::string& cmd, const std::string& param) +{ + std::string name = param; + uint32_t guid; + + if (!IOLoginData::getInstance()->getGuidByName(guid, name)) { + return; + } + + std::ostringstream str; + str << name; + + House* house = Houses::getInstance().getHouseByPlayerId(guid); + + if (house) { + str << " owns house: " << house->getName() << "."; + } else { + str << " does not own any house."; + } + + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, str.str().c_str()); +} + +void Commands::serverInfo(Player* player, const std::string& cmd, const std::string& param) +{ + std::ostringstream text; + text << "Server Info:"; + text << "\nExp Rate: " << g_game.getExperienceStage(player->level); + text << "\nSkill Rate: " << g_config.getNumber(ConfigManager::RATE_SKILL); + text << "\nMagic Rate: " << g_config.getNumber(ConfigManager::RATE_MAGIC); + text << "\nLoot Rate: " << g_config.getNumber(ConfigManager::RATE_LOOT); + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, text.str().c_str()); +} + +void Commands::buyHouse(Player* player, const std::string& cmd, const std::string& param) +{ + if (!player->isPremium()) { + player->sendCancelMessage(RET_YOUNEEDPREMIUMACCOUNT); + return; + } + + Position pos = player->getPosition(); + pos = getNextPosition(player->direction, pos); + + Tile* tile = g_game.getTile(pos.x, pos.y, pos.z); + + if (!tile) { + player->sendCancel("You have to be looking at the door of the house you would like to buy."); + return; + } + + HouseTile* houseTile = dynamic_cast(tile); + + if (!houseTile) { + player->sendCancel("You have to be looking at the door of the house you would like to buy."); + return; + } + + House* house = houseTile->getHouse(); + + if (!house || !house->getDoorByPosition(pos)) { + player->sendCancel("You have to be looking at the door of the house you would like to buy."); + return; + } + + if (house->getHouseOwner()) { + player->sendCancel("This house alreadly has an owner."); + return; + } + + for (HouseMap::iterator it = Houses::getInstance().getHouseBegin(), end = Houses::getInstance().getHouseEnd(); it != end; ++it) { + if (it->second->getHouseOwner() == player->guid) { + player->sendCancel("You are already the owner of a house."); + return; + } + } + + uint64_t price = 0; + + for (HouseTileList::iterator it = house->getHouseTileBegin(), end = house->getHouseTileEnd(); it != end; ++it) { + price += g_config.getNumber(ConfigManager::HOUSE_PRICE); + } + + if (!g_game.removeMoney(player, price)) { + player->sendCancel("You do not have enough money."); + return; + } + + house->setHouseOwner(player->guid); + player->sendTextMessage(MSG_INFO_DESCR, "You have successfully bought this house, be sure to have the money for the rent in your depot of this city."); +} + +void Commands::whoIsOnline(Player* player, const std::string& cmd, const std::string& param) +{ + std::ostringstream ss; + ss << Player::listPlayer.list.size() << " players online."; + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, ss.str()); + ss.str(""); + + uint32_t i = 0; + AutoList::listiterator it = Player::listPlayer.list.begin(); + if(!g_config.getBoolean(ConfigManager::SHOW_GAMEMASTERS_ONLINE)) + { + while(it != Player::listPlayer.list.end()) + { + if(!it->second->isAccessPlayer() || player->isAccessPlayer()) + { + ss << (i > 0 ? ", " : "") << it->second->name << " [" << it->second->level << "]"; + ++i; + } + ++it; + + if(i == 10) + { + ss << (it != Player::listPlayer.list.end() ? "," : "."); + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, ss.str()); + ss.str(""); + i = 0; + } + } + } + else + { + while(it != Player::listPlayer.list.end()) + { + ss << (i > 0 ? ", " : "") << it->second->name << " [" << it->second->level << "]"; + ++it; + ++i; + + if(i == 10) + { + ss << (it != Player::listPlayer.list.end() ? "," : "."); + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, ss.str()); + ss.str(""); + i = 0; + } + } + } + + if(i > 0) + { + ss << "."; + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, ss.str()); + } +} + +void Commands::changeFloor(Player* player, const std::string& cmd, const std::string& param) +{ + Position newPos = player->getPosition(); + + if (cmd[1] == 'u') { + newPos.z--; + } else { + newPos.z++; + } + + Position newPosition = g_game.getClosestFreeTile(player, 0, newPos, true); + + if (newPosition.x != 0) { + Position oldPosition = player->getPosition(); + + if (g_game.internalTeleport(player, newPosition) == RET_NOERROR) { + g_game.addMagicEffect(oldPosition, NM_ME_POFF, player->isInGhostMode()); + g_game.addMagicEffect(player->getPosition(), NM_ME_TELEPORT, player->isInGhostMode()); + return; + } + } + + player->sendCancel("You can not teleport there."); +} + +void Commands::showPosition(Player* player, const std::string& cmd, const std::string& param) +{ + if (param != "" && player->isAccessPlayer()) { + StringVec exploded = explodeString(param, ", ", 2); + + if (!exploded.size() || exploded.size() < 3) { + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Not enough params."); + return; + } + + uint16_t x = atoi(exploded[0].c_str()); + uint16_t y = atoi(exploded[1].c_str()); + uint8_t z = atoi(exploded[2].c_str()); + + Position oldPosition = player->getPosition(); + + if (g_game.internalTeleport(player, Position(x, y, z)) == RET_NOERROR) { + g_game.addMagicEffect(oldPosition, NM_ME_POFF, player->isInGhostMode()); + g_game.addMagicEffect(player->getPosition(), NM_ME_TELEPORT, player->isInGhostMode()); + return; + } + } + + const Position& pos = player->getPosition(); + + std::ostringstream ss; + + ss << "Your current position is [X: " << pos.x << " | Y: " << pos.y << " | Z: " << pos.z << "]."; + + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, ss.str()); +} + +void Commands::removeThing(Player* player, const std::string& cmd, const std::string& param) +{ + Position pos = player->getPosition(); + pos = getNextPosition(player->direction, pos); + Tile* removeTile = g_game.getMap()->getTile(pos); + + if (!removeTile) { + player->sendTextMessage(MSG_STATUS_SMALL, "Tile not found."); + g_game.addMagicEffect(pos, NM_ME_POFF); + return; + } + + Thing* thing = removeTile->getTopVisibleThing(player); + + if (!thing) { + player->sendTextMessage(MSG_STATUS_SMALL, "Object not found."); + g_game.addMagicEffect(pos, NM_ME_POFF); + return; + } + + if (Creature* creature = thing->getCreature()) { + g_game.removeCreature(creature, true); + } else { + Item* item = thing->getItem(); + + if (item) { + if (item->isGroundTile()) { + player->sendTextMessage(MSG_STATUS_SMALL, "You may not remove a ground tile."); + g_game.addMagicEffect(pos, NM_ME_POFF); + return; + } + + g_game.internalRemoveItem(item, std::max(1, std::min(atoi(param.c_str()), item->getItemCount()))); + g_game.addMagicEffect(pos, NM_ME_MAGIC_BLOOD); + } + } +} + +void Commands::newType(Player* player, const std::string& cmd, const std::string& param) +{ + int32_t lookType = atoi(param.c_str()); + + if (lookType >= 0 && lookType != 1 && lookType != 135 && lookType != 411 && lookType != 415 && lookType != 424 && (lookType <= 160 || lookType >= 192) && lookType != 439 && lookType != 440 && lookType != 468 && lookType != 469 && lookType <= 573 && (lookType < 474 || lookType > 485) && lookType != 518 && lookType != 519 && lookType != 520 && lookType != 524 && lookType != 525 && lookType != 536 && lookType != 543 && lookType != 549) { + Outfit_t newOutfit = player->getDefaultOutfit(); + newOutfit.lookType = lookType; + g_game.internalCreatureChangeOutfit(player, newOutfit); + } else { + player->sendTextMessage(MSG_STATUS_SMALL, "This looktype does not exist."); + } +} + +void Commands::hide(Player* player, const std::string& cmd, const std::string& param) +{ + player->setHiddenHealth(!player->isHealthHidden()); + g_game.addCreatureHealth(player); +} + +void Commands::newItem(Player* player, const std::string& cmd, const std::string& param) +{ + int32_t itemId = atoi(param.c_str()); + + for (uint32_t i = 0; i < param.length(); i++) { + if (!isNumber(param[i])) { + itemId = Item::items.getItemIdByName(param); + break; + } + } + + if (itemId <= 0) { + return; + } + + const ItemType& it = Item::items[itemId]; + + if (it.id == 0) { + return; + } + + Outfit_t outfit; + outfit.lookTypeEx = itemId; + + ConditionOutfit* outfitCondition = new ConditionOutfit(CONDITIONID_COMBAT, CONDITION_OUTFIT, -1); + + if (!outfitCondition) { + return; + } + + outfitCondition->addOutfit(outfit); + player->addCondition(outfitCondition); +} + +void Commands::forceRaid(Player* player, const std::string& cmd, const std::string& param) +{ + Raid* raid = Raids::getInstance()->getRaidByName(param); + + if (!raid || !raid->isLoaded()) { + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "No such raid exists."); + return; + } + + if (Raids::getInstance()->getRunning()) { + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Another raid is already being executed."); + return; + } + + Raids::getInstance()->setRunning(raid); + + RaidEvent* event = raid->getNextRaidEvent(); + + if (!event) { + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "The raid does not contain any data."); + return; + } + + raid->setState(RAIDSTATE_EXECUTING); + + uint32_t ticks = event->getDelay(); + + if (ticks > 0) { + g_scheduler.addEvent(createSchedulerTask(ticks, + boost::bind(&Raid::executeRaidEvent, raid, event))); + } else { + g_dispatcher.addTask(createTask( + boost::bind(&Raid::executeRaidEvent, raid, event))); + } + + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Raid started."); +} + +void Commands::addSkill(Player* player, const std::string& cmd, const std::string& param) +{ + boost::char_separator sep(","); + tokenizer cmdtokens(param, sep); + tokenizer::iterator cmdit = cmdtokens.begin(); + std::string param1, param2; + param1 = parseParams(cmdit, cmdtokens.end()); + param2 = parseParams(cmdit, cmdtokens.end()); + trimString(param1); + trimString(param2); + toLowerCaseString(param2); + + Player* paramPlayer = g_game.getPlayerByName(param1); + + if (!paramPlayer) { + player->sendTextMessage(MSG_STATUS_SMALL, "Couldn't find target."); + return; + } + + if (param2[0] == 'l' || param2[0] == 'e') { + paramPlayer->addExperience(Player::getExpForLevel(paramPlayer->getLevel() + 1) - paramPlayer->experience, false, false); + } else if (param2[0] == 'm') { + paramPlayer->addManaSpent(paramPlayer->vocation->getReqMana(paramPlayer->getBaseMagicLevel() + 1) - paramPlayer->manaSpent, false); + } else { + skills_t skillId = getSkillId(param2); + paramPlayer->addSkillAdvance(skillId, paramPlayer->vocation->getReqSkillTries(skillId, paramPlayer->getSkill(skillId, SKILL_LEVEL) + 1)); + } +} + +void Commands::ban(Player* player, const std::string& cmd, const std::string& param) +{ + StringVec exploded = explodeString(param, ", ", 4); + + if (!exploded.size() || exploded.size() < 5) { + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Not enough params."); + return; + } + + std::string targetName = exploded[0]; + + int32_t action = actionStringToInt(exploded[1]); + + if (action == -1) { + action = (int32_t)atoi(exploded[1].c_str()); + } + + int32_t reason = reasonStringToInt(exploded[2]); + + if (reason == -1) { + reason = (int32_t)atoi(exploded[2].c_str()); + } + + bool ipBan = (atoi(exploded[3].c_str()) != 0); + + std::string comment = exploded[4]; + g_game.violationWindow(player, targetName, reason, action, comment, ipBan); +} + +void Commands::unban(Player* player, const std::string& cmd, const std::string& param) +{ + uint32_t accountNumber = atoi(param.c_str()); + bool removedIPBan = false; + std::string name = param; + bool playerExists = false; + + if (IOLoginData::getInstance()->playerExists(name)) { + playerExists = true; + accountNumber = IOLoginData::getInstance()->getAccountNumberByName(name); + + uint32_t lastIP = IOLoginData::getInstance()->getLastIPByName(name); + + if (lastIP != 0 && IOBan::getInstance()->isIpBanished(lastIP)) { + removedIPBan = IOBan::getInstance()->removeIPBan(lastIP); + } + } + + bool banned = false; + bool deleted = false; + uint32_t bannedBy = 0, banTime = 0; + int32_t reason = 0, action = 0; + std::string comment = ""; + + if (IOBan::getInstance()->getBanInformation(accountNumber, bannedBy, banTime, reason, action, comment, deleted)) { + if (!deleted) { + banned = true; + } + } + + if (banned) { + if (IOBan::getInstance()->removeAccountBan(accountNumber)) { + std::ostringstream ss; + ss << name << " has been unbanned."; + player->sendTextMessage(MSG_INFO_DESCR, ss.str()); + } + } else if (deleted) { + if (IOBan::getInstance()->removeAccountDeletion(accountNumber)) { + std::ostringstream ss; + ss << name << " has been undeleted."; + player->sendTextMessage(MSG_INFO_DESCR, ss.str()); + } + } else if (removedIPBan) { + std::ostringstream ss; + ss << "The IP banishment on " << name << " has been lifted."; + player->sendTextMessage(MSG_INFO_DESCR, ss.str()); + } else { + bool removedNamelock = false; + + if (playerExists) { + uint32_t guid = 0; + + if (IOLoginData::getInstance()->getGuidByName(guid, name) && + IOBan::getInstance()->isPlayerNamelocked(name) && + IOBan::getInstance()->removePlayerNamelock(guid)) { + std::ostringstream ss; + ss << "Namelock on " << name << " has been lifted."; + player->sendTextMessage(MSG_INFO_DESCR, ss.str()); + removedNamelock = true; + } + } + + if (!removedNamelock) { + player->sendCancel("That player or account is not banished or deleted."); + } + } +} + +void Commands::playerKills(Player* player, const std::string& cmd, const std::string& param) +{ + int32_t fragTime = g_config.getNumber(ConfigManager::FRAG_TIME); + + if (player->skullTicks && fragTime > 0) { + int32_t frags = (int32_t)ceil(player->skullTicks / (double)fragTime); + int32_t remainingTime = (player->skullTicks % fragTime) / 1000; + int32_t hours = remainingTime / 3600; + int32_t minutes = (remainingTime % 3600) / 60; + + std::ostringstream ss; + ss << "You have " << frags << " unjustified kill" << (frags > 1 ? "s" : "") << ". The amount of unjustified kills will decrease after: " << hours << " hour" << (hours != 1 ? "s" : "") << " and " << minutes << " minute" << (minutes != 1 ? "s" : "") << "."; + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, ss.str()); + } else { + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "You do not have any unjustified frag."); + } +} + +void Commands::clean(Player* player, const std::string& cmd, const std::string& param) +{ + uint32_t count = g_game.getMap()->clean(); + + if (count != 1) { + std::ostringstream ss; + ss << "Cleaned " << count << " items from the map."; + g_game.broadcastMessage(ss.str(), MSG_STATUS_WARNING); + } else { + g_game.broadcastMessage("Cleaned 1 item from the map.", MSG_STATUS_WARNING); + } +} + +void Commands::serverDiag(Player* player, const std::string& cmd, const std::string& param) +{ +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + std::ostringstream text; + text << "Server diagonostic:\n"; + text << "World:" << "\n"; + text << "Player: " << g_game.getPlayersOnline() << " (" << Player::playerCount << ")\n"; + text << "Npc: " << g_game.getNpcsOnline() << " (" << Npc::npcCount << ")\n"; + text << "Monster: " << g_game.getMonstersOnline() << " (" << Monster::monsterCount << ")\n"; + + text << "\nProtocols:" << "\n"; + text << "--------------------\n"; + text << "ProtocolGame: " << ProtocolGame::protocolGameCount << "\n"; + text << "ProtocolLogin: " << ProtocolLogin::protocolLoginCount << "\n"; + text << "ProtocolAdmin: " << ProtocolAdmin::protocolAdminCount << "\n"; + text << "ProtocolStatus: " << ProtocolStatus::protocolStatusCount << "\n\n"; + + text << "\nConnections:\n"; + text << "--------------------\n"; + text << "Active connections: " << Connection::connectionCount << "\n"; + text << "Total message pool: " << OutputMessagePool::getInstance()->getTotalMessageCount() << "\n"; + text << "Auto message pool: " << OutputMessagePool::getInstance()->getAutoMessageCount() << "\n"; + text << "Free message pool: " << OutputMessagePool::getInstance()->getAvailableMessageCount() << "\n"; + + text << "\nLibraries:\n"; + text << "--------------------\n"; + text << "asio: " << BOOST_ASIO_VERSION << "\n"; + text << "libxml: " << XML_DEFAULT_VERSION << "\n"; + text << "lua: " << LUA_VERSION << "\n"; + + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, text.str().c_str()); +#else + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "This command requires the server to be compiled with the __ENABLE_SERVER_DIAGNOSTIC__ flag."); +#endif +} + +void Commands::ghost(Player* player, const std::string& cmd, const std::string& param) +{ + player->switchGhostMode(); + + SpectatorVec list; + g_game.getSpectators(list, player->getPosition(), true, true); + + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + Player* tmpPlayer = (*it)->getPlayer(); + tmpPlayer->sendCreatureChangeVisible(player, !player->isInGhostMode()); + + if (tmpPlayer != player && !tmpPlayer->isAccessPlayer()) { + if (player->isInGhostMode()) { + tmpPlayer->sendCreatureDisappear(player, player->getTile()->getClientIndexOfThing(tmpPlayer, player), true); + } else { + tmpPlayer->sendCreatureAppear(player, player->getPosition(), true); + } + + tmpPlayer->sendUpdateTile(player->getTile(), player->getPosition()); + } + } + + if (player->isInGhostMode()) { + for (AutoList::listiterator it = Player::listPlayer.list.begin(); it != Player::listPlayer.list.end(); ++it) { + if (!it->second->isAccessPlayer()) { + it->second->notifyStatusChange(player, VIPSTATUS_OFFLINE); + } + } + + IOLoginData::getInstance()->updateOnlineStatus(player->getGUID(), false); + player->sendTextMessage(MSG_INFO_DESCR, "You are now invisible."); + g_game.addMagicEffect(list, player->getPosition(), NM_ME_YALAHARIGHOST); + } else { + for (AutoList::listiterator it = Player::listPlayer.list.begin(); it != Player::listPlayer.list.end(); ++it) { + if (!it->second->isAccessPlayer()) { + it->second->notifyStatusChange(player, VIPSTATUS_ONLINE); + } + } + + IOLoginData::getInstance()->updateOnlineStatus(player->getGUID(), true); + player->sendTextMessage(MSG_INFO_DESCR, "You are visible again."); + Position pos = player->getPosition(); + pos.x += 1; + g_game.addMagicEffect(list, pos, NM_ME_SMOKE); + } +} + +void Commands::multiClientCheck(Player* player, const std::string& cmd, const std::string& param) +{ + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Multiclient Check List:"); + std::map< uint32_t, std::vector > ipMap; + + for (AutoList::listiterator it = Player::listPlayer.list.begin(); it != Player::listPlayer.list.end(); ++it) { + if (it->second->isRemoved() || it->second->getIP() == 0) { + continue; + } + + ipMap[it->second->getIP()].push_back(it->second); + } + + for (std::map< uint32_t, std::vector >::const_iterator it = ipMap.begin(), end = ipMap.end(); it != end; ++it) { + if (it->second.size() < 2) { + continue; + } + + Player* tmpPlayer = it->second[0]; + std::ostringstream ss; + ss << convertIPToString(it->first) << ": " << tmpPlayer->getName() << " [" << tmpPlayer->getLevel() << "]"; + + for (std::vector::size_type i = 1, size = it->second.size(); i < size; ++i) { + tmpPlayer = it->second[i]; + ss << ", " << tmpPlayer->getName() << " [" << tmpPlayer->getLevel() << "]"; + } + + ss << "."; + player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, ss.str()); + } +} + +void Commands::addTutor(Player* player, const std::string& cmd, const std::string& param) +{ + uint32_t accountId = 0; + std::string characterName = param; + + Player* targetPlayer = g_game.getPlayerByName(characterName); + + if (targetPlayer) { + targetPlayer->accountType = ACCOUNT_TYPE_TUTOR; + accountId = targetPlayer->getAccount(); + characterName = targetPlayer->getName(); + } else { + accountId = IOLoginData::getInstance()->getAccountNumberByName(characterName); + uint32_t guid; + IOLoginData::getInstance()->getGuidByName(guid, characterName); + } + + if (accountId != 0 && IOLoginData::getInstance()->getAccountType(accountId) == ACCOUNT_TYPE_NORMAL) { + IOLoginData::getInstance()->setAccountType(accountId, ACCOUNT_TYPE_TUTOR); + player->sendTextMessage(MSG_INFO_DESCR, characterName + (" is now a tutor.")); + } else { + player->sendTextMessage(MSG_INFO_DESCR, "A character with that name does not exist, or is already a tutor."); + } +} + +void Commands::removeTutor(Player* player, const std::string& cmd, const std::string& param) +{ + uint32_t accountId = 0; + std::string characterName = param; + + Player* targetPlayer = g_game.getPlayerByName(characterName); + + if (targetPlayer) { + targetPlayer->accountType = ACCOUNT_TYPE_NORMAL; + accountId = targetPlayer->getAccount(); + characterName = targetPlayer->getName(); + } else { + accountId = IOLoginData::getInstance()->getAccountNumberByName(characterName); + uint32_t guid; + IOLoginData::getInstance()->getGuidByName(guid, characterName); + } + + if (accountId != 0 && IOLoginData::getInstance()->getAccountType(accountId) == ACCOUNT_TYPE_TUTOR) { + IOLoginData::getInstance()->setAccountType(accountId, ACCOUNT_TYPE_NORMAL); + player->sendTextMessage(MSG_INFO_DESCR, characterName + (" is no longer a tutor.")); + } else { + player->sendTextMessage(MSG_INFO_DESCR, "A character with that name does not exist, or is not a tutor."); + } +} diff --git a/src/commands.h b/src/commands.h new file mode 100644 index 0000000000..9e014c8dc0 --- /dev/null +++ b/src/commands.h @@ -0,0 +1,110 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_COMMANDS_H__ +#define __OTSERV_COMMANDS_H__ + +#include +#include +#include "creature.h" + +struct Command; +struct s_defcommands; + +class Commands +{ + public: + Commands(); + ~Commands(); + + bool loadFromXml(); + bool reload(); + + bool exeCommand(Player* player, const std::string& cmd); + + protected: + bool loaded; + + //commands + void placeNpc(Player* player, const std::string& cmd, const std::string& param); + void placeMonster(Player* player, const std::string& cmd, const std::string& param); + void placeSummon(Player* player, const std::string& cmd, const std::string& param); + void broadcastMessage(Player* player, const std::string& cmd, const std::string& param); + void banPlayer(Player* player, const std::string& cmd, const std::string& param); + void teleportMasterPos(Player* player, const std::string& cmd, const std::string& param); + void teleportHere(Player* player, const std::string& cmd, const std::string& param); + void teleportToTown(Player* player, const std::string& cmd, const std::string& param); + void teleportTo(Player* player, const std::string& cmd, const std::string& param); + void createItemById(Player* player, const std::string& cmd, const std::string& param); + void createItemByName(Player* player, const std::string& cmd, const std::string& param); + void subtractMoney(Player* player, const std::string& cmd, const std::string& param); + void reloadInfo(Player* player, const std::string& cmd, const std::string& param); + void getInfo(Player* player, const std::string& cmd, const std::string& param); + void closeServer(Player* player, const std::string& cmd, const std::string& param); + void openServer(Player* player, const std::string& cmd, const std::string& param); + void teleportNTiles(Player* player, const std::string& cmd, const std::string& param); + void kickPlayer(Player* player, const std::string& cmd, const std::string& param); + void setHouseOwner(Player* player, const std::string& cmd, const std::string& param); + void sellHouse(Player* player, const std::string& cmd, const std::string& param); + void getHouse(Player* player, const std::string& cmd, const std::string& param); + void serverInfo(Player* player, const std::string& cmd, const std::string& param); + void changeFloor(Player* player, const std::string& cmd, const std::string& param); + void whoIsOnline(Player* player, const std::string& cmd, const std::string& param); + void showPosition(Player* player, const std::string& cmd, const std::string& param); + void removeThing(Player* player, const std::string& cmd, const std::string& param); + void buyHouse(Player* player, const std::string& cmd, const std::string& param); + void newType(Player* player, const std::string& cmd, const std::string& param); + void forceRaid(Player* player, const std::string& cmd, const std::string& param); + void addSkill(Player* player, const std::string& cmd, const std::string& param); + void playerKills(Player* player, const std::string& cmd, const std::string& param); + void ban(Player* player, const std::string& cmd, const std::string& param); + void unban(Player* player, const std::string& cmd, const std::string& param); + void clean(Player* player, const std::string& cmd, const std::string& param); + void serverDiag(Player* player, const std::string& cmd, const std::string& param); + void ghost(Player* player, const std::string& cmd, const std::string& param); + void multiClientCheck(Player* player, const std::string& cmd, const std::string& param); + void newItem(Player* player, const std::string& cmd, const std::string& param); + void hide(Player* player, const std::string& cmd, const std::string& param); + void addTutor(Player* player, const std::string& cmd, const std::string& param); + void removeTutor(Player* player, const std::string& cmd, const std::string& param); + + //table of commands + static s_defcommands defined_commands[]; + + typedef std::map CommandMap; + CommandMap commandMap; +}; + +typedef void (Commands::*CommandFunc)(Player*, const std::string&, const std::string&); + +struct Command { + CommandFunc f; + int32_t groupId; + AccountType_t accountType; + bool loadedGroupId; + bool loadedAccountType; + bool logged; + bool loadedLogging; +}; + +struct s_defcommands { + const char* name; + CommandFunc f; +}; + +#endif diff --git a/src/condition.cpp b/src/condition.cpp new file mode 100644 index 0000000000..3bec3d4efa --- /dev/null +++ b/src/condition.cpp @@ -0,0 +1,1988 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "condition.h" +#include "game.h" +#include "creature.h" +#include "tools.h" +#include "combat.h" + +#include + +extern Game g_game; + +Condition::Condition(ConditionId_t _id, ConditionType_t _type, int32_t _ticks, bool _buff, uint32_t _subId) : + id(_id), + subId(_subId), + ticks(_ticks), + endTime(0), + conditionType(_type), + isBuff(_buff) +{ + // +} + +bool Condition::setParam(ConditionParam_t param, int32_t value) +{ + switch (param) { + case CONDITIONPARAM_TICKS: { + ticks = value; + return true; + } + + case CONDITIONPARAM_BUFF_SPELL: { + isBuff = (value != 0); + return true; + } + + case CONDITIONPARAM_SUBID: { + subId = value; + return true; + } + + default: { + return false; + } + } + + return false; +} + +bool Condition::unserialize(PropStream& propStream) +{ + uint8_t attr_type; + + while (propStream.GET_UCHAR(attr_type) && attr_type != CONDITIONATTR_END) { + if (!unserializeProp((ConditionAttr_t)attr_type, propStream)) { + return false; + } + } + + return true; +} + +bool Condition::unserializeProp(ConditionAttr_t attr, PropStream& propStream) +{ + switch (attr) { + case CONDITIONATTR_TYPE: { + int32_t value = 0; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + conditionType = (ConditionType_t)value; + return true; + } + + case CONDITIONATTR_ID: { + int32_t value = 0; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + id = (ConditionId_t)value; + return true; + } + + case CONDITIONATTR_TICKS: { + int32_t value = 0; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + ticks = value; + return true; + } + + case CONDITIONATTR_ISBUFF: { + int8_t value = 0; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + isBuff = value != 0; + return true; + } + + case CONDITIONATTR_SUBID: { + int32_t value = 0; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + subId = value; + return true; + } + + case CONDITIONATTR_END: + return true; + + default: + return false; + } +} + +bool Condition::serialize(PropWriteStream& propWriteStream) +{ + propWriteStream.ADD_UCHAR(CONDITIONATTR_TYPE); + propWriteStream.ADD_VALUE((int32_t)conditionType); + + propWriteStream.ADD_UCHAR(CONDITIONATTR_ID); + propWriteStream.ADD_VALUE((int32_t)id); + + propWriteStream.ADD_UCHAR(CONDITIONATTR_TICKS); + propWriteStream.ADD_VALUE((int32_t)ticks); + + propWriteStream.ADD_UCHAR(CONDITIONATTR_ISBUFF); + propWriteStream.ADD_VALUE((int8_t)isBuff); + + propWriteStream.ADD_UCHAR(CONDITIONATTR_SUBID); + propWriteStream.ADD_VALUE((int32_t)subId); + return true; +} + +void Condition::setTicks(int32_t newTicks) +{ + ticks = newTicks; + endTime = ticks + OTSYS_TIME(); +} + +bool Condition::executeCondition(Creature* creature, int32_t interval) +{ + if (ticks == -1) { + return true; + } + + int32_t newTicks = std::max(0, getTicks() - interval); + //Not using set ticks here since it would reset endTime + ticks = newTicks; + return getEndTime() >= OTSYS_TIME(); +} + +Condition* Condition::createCondition(ConditionId_t _id, ConditionType_t _type, int32_t _ticks, int32_t param/* = 0*/, bool _buff/* = false*/, uint32_t _subId/* = 0*/) +{ + switch ((int32_t)_type) { + case CONDITION_POISON: + case CONDITION_FIRE: + case CONDITION_ENERGY: + case CONDITION_DROWN: + case CONDITION_FREEZING: + case CONDITION_DAZZLED: + case CONDITION_CURSED: + case CONDITION_BLEEDING: + return new ConditionDamage(_id, _type, _buff, _subId); + + case CONDITION_HASTE: + case CONDITION_PARALYZE: + return new ConditionSpeed(_id, _type, _ticks, _buff, _subId, param); + + case CONDITION_INVISIBLE: + return new ConditionInvisible(_id, _type, _ticks, _buff, _subId); + + case CONDITION_OUTFIT: + return new ConditionOutfit(_id, _type, _ticks, _buff, _subId); + + case CONDITION_LIGHT: + return new ConditionLight(_id, _type, _ticks, _buff, _subId, param & 0xFF, (param & 0xFF00) >> 8); + + case CONDITION_REGENERATION: + return new ConditionRegeneration(_id, _type, _ticks, _buff, _subId); + + case CONDITION_SOUL: + return new ConditionSoul(_id, _type, _ticks, _buff, _subId); + + case CONDITION_MANASHIELD: + return new ConditionManaShield(_id, _type, _ticks, _buff, _subId); + + case CONDITION_ATTRIBUTES: + return new ConditionAttributes(_id, _type, _ticks, _buff, _subId); + + case CONDITION_SPELLCOOLDOWN: + return new ConditionSpellCooldown(_id, _type, _ticks, _buff, _subId); + + case CONDITION_SPELLGROUPCOOLDOWN: + return new ConditionSpellGroupCooldown(_id, _type, _ticks, _buff, _subId); + + case CONDITION_INFIGHT: + case CONDITION_DRUNK: + case CONDITION_EXHAUST_WEAPON: + case CONDITION_EXHAUST_COMBAT: + case CONDITION_EXHAUST_HEAL: + case CONDITION_MUTED: + case CONDITION_CHANNELMUTEDTICKS: + case CONDITION_YELLTICKS: + case CONDITION_PACIFIED: + return new ConditionGeneric(_id, _type, _ticks, _buff, _subId); + + default: + return NULL; + } +} + + +Condition* Condition::createCondition(PropStream& propStream) +{ + uint8_t attr; + + if (!propStream.GET_UCHAR(attr) || attr != CONDITIONATTR_TYPE) { + return NULL; + } + + uint32_t _type = 0; + + if (!propStream.GET_ULONG(_type)) { + return NULL; + } + + if (!propStream.GET_UCHAR(attr) || attr != CONDITIONATTR_ID) { + return NULL; + } + + uint32_t _id = 0; + + if (!propStream.GET_ULONG(_id)) { + return NULL; + } + + if (!propStream.GET_UCHAR(attr) || attr != CONDITIONATTR_TICKS) { + return NULL; + } + + uint32_t _ticks = 0; + + if (!propStream.GET_ULONG(_ticks)) { + return NULL; + } + + if (!propStream.GET_UCHAR(attr) || attr != CONDITIONATTR_ISBUFF) { + return NULL; + } + + uint8_t _buff = 0; + + if (!propStream.GET_UCHAR(_buff)) { + return NULL; + } + + if (!propStream.GET_UCHAR(attr) || attr != CONDITIONATTR_SUBID) { + return NULL; + } + + uint32_t _subId = 0; + + if (!propStream.GET_ULONG(_subId)) { + return NULL; + } + + return createCondition((ConditionId_t)_id, (ConditionType_t)_type, _ticks, 0, _buff != 0, _subId); +} + +bool Condition::startCondition(Creature* creature) +{ + if (getTicks() > 0) { + endTime = getTicks() + OTSYS_TIME(); + } + + return true; +} + +bool Condition::isPersistent() const +{ + if (ticks == -1) { + return false; + } + + if (!(id == CONDITIONID_DEFAULT || id == CONDITIONID_COMBAT)) { + return false; + } + + return true; +} + +uint32_t Condition::getIcons() const +{ + return isBuff ? ICON_PARTY_BUFF : 0; +} + +bool Condition::updateCondition(const Condition* addCondition) +{ + if (conditionType != addCondition->getType()) { + return false; + } + + if (getTicks() == -1 && addCondition->getTicks() > 0) { + return false; + } + + if (addCondition->getTicks() >= 0 && getEndTime() > (OTSYS_TIME() + addCondition->getTicks())) { + return false; + } + + return true; +} + +ConditionGeneric::ConditionGeneric(ConditionId_t _id, ConditionType_t _type, int32_t _ticks, bool _buff, uint32_t _subId) : + Condition(_id, _type, _ticks, _buff, _subId) +{ + // +} + +bool ConditionGeneric::startCondition(Creature* creature) +{ + return Condition::startCondition(creature); +} + +bool ConditionGeneric::executeCondition(Creature* creature, int32_t interval) +{ + return Condition::executeCondition(creature, interval); +} + +void ConditionGeneric::endCondition(Creature* creature, ConditionEnd_t reason) +{ + // +} + +void ConditionGeneric::addCondition(Creature* creature, const Condition* addCondition) +{ + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); + } +} + +uint32_t ConditionGeneric::getIcons() const +{ + uint32_t icons = Condition::getIcons(); + + switch (conditionType) { + case CONDITION_MANASHIELD: + icons |= ICON_MANASHIELD; + break; + + case CONDITION_INFIGHT: + icons |= ICON_SWORDS; + break; + + case CONDITION_DRUNK: + icons |= ICON_DRUNK; + break; + + default: + break; + } + + return icons; +} + +ConditionAttributes::ConditionAttributes(ConditionId_t _id, ConditionType_t _type, int32_t _ticks, bool _buff, uint32_t _subId) : + ConditionGeneric(_id, _type, _ticks, _buff, _subId) +{ + currentSkill = 0; + currentStat = 0; + memset(skills, 0, sizeof(skills)); + memset(skillsPercent, 0, sizeof(skillsPercent)); + memset(stats, 0, sizeof(stats)); + memset(statsPercent, 0, sizeof(statsPercent)); +} + +void ConditionAttributes::addCondition(Creature* creature, const Condition* addCondition) +{ + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); + + const ConditionAttributes& conditionAttrs = static_cast(*addCondition); + //Remove the old condition + endCondition(creature, CONDITIONEND_ABORT); + + //Apply the new one + memcpy(skills, conditionAttrs.skills, sizeof(skills)); + memcpy(skillsPercent, conditionAttrs.skillsPercent, sizeof(skillsPercent)); + memcpy(stats, conditionAttrs.stats, sizeof(stats)); + memcpy(statsPercent, conditionAttrs.statsPercent, sizeof(statsPercent)); + + if (Player* player = creature->getPlayer()) { + updatePercentSkills(player); + updateSkills(player); + updatePercentStats(player); + updateStats(player); + } + } +} + +bool ConditionAttributes::unserializeProp(ConditionAttr_t attr, PropStream& propStream) +{ + if (attr == CONDITIONATTR_SKILLS) { + int32_t value = 0; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + skills[currentSkill] = value; + ++currentSkill; + return true; + } else if (attr == CONDITIONATTR_STATS) { + int32_t value = 0; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + stats[currentStat] = value; + ++currentStat; + return true; + } + + return Condition::unserializeProp(attr, propStream); +} + +bool ConditionAttributes::serialize(PropWriteStream& propWriteStream) +{ + if (!Condition::serialize(propWriteStream)) { + return false; + } + + for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + propWriteStream.ADD_UCHAR(CONDITIONATTR_SKILLS); + propWriteStream.ADD_VALUE(skills[i]); + } + + for (int32_t i = STAT_FIRST; i <= STAT_LAST; ++i) { + propWriteStream.ADD_UCHAR(CONDITIONATTR_STATS); + propWriteStream.ADD_VALUE(stats[i]); + } + + return true; +} + +bool ConditionAttributes::startCondition(Creature* creature) +{ + if (!Condition::startCondition(creature)) { + return false; + } + + if (Player* player = creature->getPlayer()) { + updatePercentSkills(player); + updateSkills(player); + updatePercentStats(player); + updateStats(player); + } + + return true; +} + +void ConditionAttributes::updatePercentStats(Player* player) +{ + for (int32_t i = STAT_FIRST; i <= STAT_LAST; ++i) { + if (statsPercent[i] == 0) { + continue; + } + + switch (i) { + case STAT_MAXHITPOINTS: + stats[i] = (int32_t)(player->getMaxHealth() * ((statsPercent[i] - 100) / 100.f)); + break; + + case STAT_MAXMANAPOINTS: + stats[i] = (int32_t)(player->getMaxMana() * ((statsPercent[i] - 100) / 100.f)); + break; + + case STAT_SOULPOINTS: + stats[i] = (int32_t)(player->getPlayerInfo(PLAYERINFO_SOUL) * ((statsPercent[i] - 100) / 100.f)); + break; + + case STAT_MAGICPOINTS: + stats[i] = (int32_t)(player->getMagicLevel() * ((statsPercent[i] - 100) / 100.f)); + break; + } + } +} + +void ConditionAttributes::updateStats(Player* player) +{ + bool needUpdateStats = false; + + for (int32_t i = STAT_FIRST; i <= STAT_LAST; ++i) { + if (stats[i]) { + needUpdateStats = true; + player->setVarStats((stats_t)i, stats[i]); + } + } + + if (needUpdateStats) { + player->sendStats(); + } +} + +void ConditionAttributes::updatePercentSkills(Player* player) +{ + for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + if (skillsPercent[i] == 0) { + continue; + } + + int32_t currSkill = player->getSkill((skills_t)i, SKILL_LEVEL); + skills[i] = (int32_t)(currSkill * ((skillsPercent[i] - 100) / 100.f)); + } +} + +void ConditionAttributes::updateSkills(Player* player) +{ + bool needUpdateSkills = false; + + for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + if (skills[i]) { + needUpdateSkills = true; + player->setVarSkill((skills_t)i, skills[i]); + } + } + + if (needUpdateSkills) { + player->sendSkills(); + } +} + +bool ConditionAttributes::executeCondition(Creature* creature, int32_t interval) +{ + return ConditionGeneric::executeCondition(creature, interval); +} + +void ConditionAttributes::endCondition(Creature* creature, ConditionEnd_t reason) +{ + Player* player = creature->getPlayer(); + + if (player) { + bool needUpdateSkills = false; + + for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + if (skills[i] || skillsPercent[i]) { + needUpdateSkills = true; + const int new_skill = skills[i]; + player->setVarSkill((skills_t)i, -new_skill); + } + } + + if (needUpdateSkills) { + player->sendSkills(); + } + + bool needUpdateStats = false; + + for (int32_t i = STAT_FIRST; i <= STAT_LAST; ++i) { + if (stats[i]) { + needUpdateStats = true; + player->setVarStats((stats_t)i, -stats[i]); + } + } + + if (needUpdateStats) { + player->sendStats(); + } + } +} + +bool ConditionAttributes::setParam(ConditionParam_t param, int32_t value) +{ + bool ret = ConditionGeneric::setParam(param, value); + + switch (param) { + case CONDITIONPARAM_SKILL_MELEE: { + skills[SKILL_CLUB] = value; + skills[SKILL_AXE] = value; + skills[SKILL_SWORD] = value; + return true; + } + + case CONDITIONPARAM_SKILL_MELEEPERCENT: { + skillsPercent[SKILL_CLUB] = value; + skillsPercent[SKILL_AXE] = value; + skillsPercent[SKILL_SWORD] = value; + return true; + } + + case CONDITIONPARAM_SKILL_FIST: { + skills[SKILL_FIST] = value; + return true; + } + + case CONDITIONPARAM_SKILL_FISTPERCENT: { + skillsPercent[SKILL_FIST] = value; + return true; + } + + case CONDITIONPARAM_SKILL_CLUB: { + skills[SKILL_CLUB] = value; + return true; + } + + case CONDITIONPARAM_SKILL_CLUBPERCENT: { + skillsPercent[SKILL_CLUB] = value; + return true; + } + + case CONDITIONPARAM_SKILL_SWORD: { + skills[SKILL_SWORD] = value; + return true; + } + + case CONDITIONPARAM_SKILL_SWORDPERCENT: { + skillsPercent[SKILL_SWORD] = value; + return true; + } + + case CONDITIONPARAM_SKILL_AXE: { + skills[SKILL_AXE] = value; + return true; + } + + case CONDITIONPARAM_SKILL_AXEPERCENT: { + skillsPercent[SKILL_AXE] = value; + return true; + } + + case CONDITIONPARAM_SKILL_DISTANCE: { + skills[SKILL_DIST] = value; + return true; + } + + case CONDITIONPARAM_SKILL_DISTANCEPERCENT: { + skillsPercent[SKILL_DIST] = value; + return true; + } + + case CONDITIONPARAM_SKILL_SHIELD: { + skills[SKILL_SHIELD] = value; + return true; + } + + case CONDITIONPARAM_SKILL_SHIELDPERCENT: { + skillsPercent[SKILL_SHIELD] = value; + return true; + } + + case CONDITIONPARAM_SKILL_FISHING: { + skills[SKILL_FISH] = value; + return true; + } + + case CONDITIONPARAM_SKILL_FISHINGPERCENT: { + skillsPercent[SKILL_FISH] = value; + return true; + } + + case CONDITIONPARAM_STAT_MAXHITPOINTS: { + stats[STAT_MAXHITPOINTS] = value; + return true; + } + + case CONDITIONPARAM_STAT_MAXMANAPOINTS: { + stats[STAT_MAXMANAPOINTS] = value; + return true; + } + + case CONDITIONPARAM_STAT_SOULPOINTS: { + stats[STAT_SOULPOINTS] = value; + return true; + } + + case CONDITIONPARAM_STAT_MAGICPOINTS: { + stats[STAT_MAGICPOINTS] = value; + return true; + } + + case CONDITIONPARAM_STAT_MAXHITPOINTSPERCENT: { + if (value < 0) { + value = 0; + } + + statsPercent[STAT_MAXHITPOINTS] = value; + return true; + } + + case CONDITIONPARAM_STAT_MAXMANAPOINTSPERCENT: { + if (value < 0) { + value = 0; + } + + statsPercent[STAT_MAXMANAPOINTS] = value; + return true; + } + + case CONDITIONPARAM_STAT_SOULPOINTSPERCENT: { + if (value < 0) { + value = 0; + } + + statsPercent[STAT_SOULPOINTS] = value; + return true; + } + + case CONDITIONPARAM_STAT_MAGICPOINTSPERCENT: { + if (value < 0) { + value = 0; + } + + statsPercent[STAT_MAGICPOINTS] = value; + return true; + } + + default: + return false; + } + + return ret; +} + +ConditionRegeneration::ConditionRegeneration(ConditionId_t _id, ConditionType_t _type, int32_t _ticks, bool _buff, uint32_t _subId) : + ConditionGeneric(_id, _type, _ticks, _buff, _subId) +{ + internalHealthTicks = 0; + internalManaTicks = 0; + + healthTicks = 1000; + manaTicks = 1000; + + healthGain = 0; + manaGain = 0; +} + +void ConditionRegeneration::addCondition(Creature* creature, const Condition* addCondition) +{ + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); + + const ConditionRegeneration& conditionRegen = static_cast(*addCondition); + + healthTicks = conditionRegen.healthTicks; + manaTicks = conditionRegen.manaTicks; + + healthGain = conditionRegen.healthGain; + manaGain = conditionRegen.manaGain; + } +} + +bool ConditionRegeneration::unserializeProp(ConditionAttr_t attr, PropStream& propStream) +{ + if (attr == CONDITIONATTR_HEALTHTICKS) { + uint32_t value = 0; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + healthTicks = value; + return true; + } else if (attr == CONDITIONATTR_HEALTHGAIN) { + uint32_t value = 0; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + healthGain = value; + return true; + } else if (attr == CONDITIONATTR_MANATICKS) { + uint32_t value = 0; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + manaTicks = value; + return true; + } else if (attr == CONDITIONATTR_MANAGAIN) { + uint32_t value = 0; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + manaGain = value; + return true; + } + + return Condition::unserializeProp(attr, propStream); +} + +bool ConditionRegeneration::serialize(PropWriteStream& propWriteStream) +{ + if (!Condition::serialize(propWriteStream)) { + return false; + } + + propWriteStream.ADD_UCHAR(CONDITIONATTR_HEALTHTICKS); + propWriteStream.ADD_VALUE(healthTicks); + + propWriteStream.ADD_UCHAR(CONDITIONATTR_HEALTHGAIN); + propWriteStream.ADD_VALUE(healthGain); + + propWriteStream.ADD_UCHAR(CONDITIONATTR_MANATICKS); + propWriteStream.ADD_VALUE(manaTicks); + + propWriteStream.ADD_UCHAR(CONDITIONATTR_MANAGAIN); + propWriteStream.ADD_VALUE(manaGain); + return true; +} + +bool ConditionRegeneration::executeCondition(Creature* creature, int32_t interval) +{ + internalHealthTicks += interval; + internalManaTicks += interval; + + if (creature->getZone() != ZONE_PROTECTION) { + if (internalHealthTicks >= healthTicks) { + internalHealthTicks = 0; + + int32_t realHealthGain = creature->getHealth(); + creature->changeHealth(healthGain); + realHealthGain = creature->getHealth() - realHealthGain; + + if (isBuff && realHealthGain > 0) { + Player* player = creature->getPlayer(); + + if (player) { + std::ostringstream ss; + ss << ucfirst(player->getNameDescription()) << " was healed for " << realHealthGain << " hitpoint" << (realHealthGain != 1 ? "s." : "."); + std::string message = ss.str(); + + std::ostringstream tmpSs; + tmpSs << "You were healed for " << realHealthGain << " hitpoint" << (realHealthGain != 1 ? "s." : "."); + player->sendHealMessage(MSG_HEALED, tmpSs.str(), player->getPosition(), realHealthGain, TEXTCOLOR_MAYABLUE); + + SpectatorVec list; + g_game.getSpectators(list, player->getPosition(), false, true); + + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + Player* tmpPlayer = (*it)->getPlayer(); + + if (tmpPlayer != player) { + tmpPlayer->sendHealMessage(MSG_HEALED_OTHERS, message, player->getPosition(), realHealthGain, TEXTCOLOR_MAYABLUE); + } + } + } + } + } + + if (internalManaTicks >= manaTicks) { + internalManaTicks = 0; + creature->changeMana(manaGain); + } + } + + return ConditionGeneric::executeCondition(creature, interval); +} + +bool ConditionRegeneration::setParam(ConditionParam_t param, int32_t value) +{ + bool ret = ConditionGeneric::setParam(param, value); + + switch (param) { + case CONDITIONPARAM_HEALTHGAIN: + healthGain = value; + return true; + + case CONDITIONPARAM_HEALTHTICKS: + healthTicks = value; + return true; + + case CONDITIONPARAM_MANAGAIN: + manaGain = value; + return true; + + case CONDITIONPARAM_MANATICKS: + manaTicks = value; + return true; + + default: + return false; + } + + return ret; +} + +ConditionSoul::ConditionSoul(ConditionId_t _id, ConditionType_t _type, int32_t _ticks, bool _buff, uint32_t _subId) : + ConditionGeneric(_id, _type, _ticks, _buff, _subId) +{ + internalSoulTicks = 0; + soulTicks = 0; + soulGain = 0; +} + +void ConditionSoul::addCondition(Creature* creature, const Condition* addCondition) +{ + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); + + const ConditionSoul& conditionSoul = static_cast(*addCondition); + + soulTicks = conditionSoul.soulTicks; + soulGain = conditionSoul.soulGain; + } +} + +bool ConditionSoul::unserializeProp(ConditionAttr_t attr, PropStream& propStream) +{ + if (attr == CONDITIONATTR_SOULGAIN) { + uint32_t value = 0; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + soulGain = value; + return true; + } else if (attr == CONDITIONATTR_SOULTICKS) { + uint32_t value = 0; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + soulTicks = value; + return true; + } + + return Condition::unserializeProp(attr, propStream); +} + +bool ConditionSoul::serialize(PropWriteStream& propWriteStream) +{ + if (!Condition::serialize(propWriteStream)) { + return false; + } + + propWriteStream.ADD_UCHAR(CONDITIONATTR_SOULGAIN); + propWriteStream.ADD_VALUE(soulGain); + + propWriteStream.ADD_UCHAR(CONDITIONATTR_SOULTICKS); + propWriteStream.ADD_VALUE(soulTicks); + + return true; +} + +bool ConditionSoul::executeCondition(Creature* creature, int32_t interval) +{ + internalSoulTicks += interval; + + if (Player* player = creature->getPlayer()) { + if (player->getZone() != ZONE_PROTECTION) { + if (internalSoulTicks >= soulTicks) { + internalSoulTicks = 0; + player->changeSoul(soulGain); + } + } + } + + return ConditionGeneric::executeCondition(creature, interval); +} + +bool ConditionSoul::setParam(ConditionParam_t param, int32_t value) +{ + bool ret = ConditionGeneric::setParam(param, value); + + switch (param) { + case CONDITIONPARAM_SOULGAIN: + soulGain = value; + return true; + + case CONDITIONPARAM_SOULTICKS: + soulTicks = value; + return true; + + default: + return false; + } + + return ret; +} + +ConditionDamage::ConditionDamage(ConditionId_t _id, ConditionType_t _type, bool _buff, uint32_t _subId) : + Condition(_id, _type, 0, _buff, _subId) +{ + delayed = false; + forceUpdate = false; + owner = 0; + minDamage = 0; + maxDamage = 0; + startDamage = 0; + periodDamage = 0; + periodDamageTick = 0; + tickInterval = 2000; +} + +bool ConditionDamage::setParam(ConditionParam_t param, int32_t value) +{ + bool ret = Condition::setParam(param, value); + + switch (param) { + case CONDITIONPARAM_OWNER: + owner = value; + return true; + + case CONDITIONPARAM_FORCEUPDATE: + forceUpdate = (value != 0); + return true; + + case CONDITIONPARAM_DELAYED: + delayed = (value != 0); + return true; + + case CONDITIONPARAM_MAXVALUE: + maxDamage = std::abs(value); + break; + + case CONDITIONPARAM_MINVALUE: + minDamage = std::abs(value); + break; + + case CONDITIONPARAM_STARTVALUE: + startDamage = std::abs(value); + break; + + case CONDITIONPARAM_TICKINTERVAL: + tickInterval = std::abs(value); + break; + + case CONDITIONPARAM_PERIODICDAMAGE: + periodDamage = value; + break; + + default: + return false; + } + + return ret; +} + +bool ConditionDamage::unserializeProp(ConditionAttr_t attr, PropStream& propStream) +{ + if (attr == CONDITIONATTR_DELAYED) { + bool value = false; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + delayed = value; + return true; + } else if (attr == CONDITIONATTR_PERIODDAMAGE) { + int32_t value = 0; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + periodDamage = value; + return true; + } else if (attr == CONDITIONATTR_OWNER) { + uint32_t value = 0; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + // owner = value; + return true; + } else if (attr == CONDITIONATTR_INTERVALDATA) { + IntervalInfo damageInfo; + + if (!propStream.GET_VALUE(damageInfo)) { + return false; + } + + damageList.push_back(damageInfo); + + if (getTicks() != -1) { + setTicks(getTicks() + damageInfo.interval); + } + + return true; + } + + return Condition::unserializeProp(attr, propStream); +} + +bool ConditionDamage::serialize(PropWriteStream& propWriteStream) +{ + if (!Condition::serialize(propWriteStream)) { + return false; + } + + propWriteStream.ADD_UCHAR(CONDITIONATTR_DELAYED); + propWriteStream.ADD_VALUE(delayed); + + propWriteStream.ADD_UCHAR(CONDITIONATTR_PERIODDAMAGE); + propWriteStream.ADD_VALUE(periodDamage); + + // propWriteStream.ADD_UCHAR(CONDITIONATTR_OWNER); + // propWriteStream.ADD_VALUE(owner); + + for (DamageList::const_iterator it = damageList.begin(); it != damageList.end(); ++it) { + propWriteStream.ADD_UCHAR(CONDITIONATTR_INTERVALDATA); + propWriteStream.ADD_VALUE((*it)); + } + + return true; +} + +bool ConditionDamage::updateCondition(const ConditionDamage* addCondition) +{ + if (addCondition->doForceUpdate()) { + return true; + } + + if (getTicks() == -1 && addCondition->getTicks() > 0) { + return false; + } + + if (addCondition->getTicks() <= getTicks()) { + return false; + } + + if (addCondition->getTotalDamage() < getTotalDamage()) { + return false; + } + + if (addCondition->periodDamage < periodDamage) { + return false; + } + + return true; +} + +bool ConditionDamage::addDamage(int32_t rounds, int32_t time, int32_t value) +{ + if (rounds == -1) { + //periodic damage + periodDamage = value; + setParam(CONDITIONPARAM_TICKINTERVAL, time); + setParam(CONDITIONPARAM_TICKS, -1); + return true; + } + + if (periodDamage > 0) { + return false; + } + + //rounds, time, damage + for (int32_t i = 0; i < rounds; ++i) { + IntervalInfo damageInfo; + damageInfo.interval = time; + damageInfo.timeLeft = time; + damageInfo.value = value; + + damageList.push_back(damageInfo); + + if (getTicks() != -1) { + setTicks(getTicks() + damageInfo.interval); + } + } + + return true; +} + +bool ConditionDamage::init() +{ + if (periodDamage != 0) { + return true; + } + + if (damageList.empty()) { + setTicks(0); + + int32_t amount = random_range(minDamage, maxDamage); + + if (amount != 0) { + if (startDamage > maxDamage) { + startDamage = maxDamage; + } else if (startDamage == 0) { + startDamage = std::max(1, std::ceil(amount / 20.0)); + } + + std::list list; + ConditionDamage::generateDamageList(amount, startDamage, list); + + for (std::list::iterator it = list.begin(); it != list.end(); ++it) { + addDamage(1, tickInterval, -*it); + } + } + } + + return (!damageList.empty()); +} + +bool ConditionDamage::startCondition(Creature* creature) +{ + if (!Condition::startCondition(creature)) { + return false; + } + + if (!init()) { + return false; + } + + if (!delayed) { + int32_t damage = 0; + + if (getNextDamage(damage)) { + return doDamage(creature, damage); + } + } + + return true; +} + +bool ConditionDamage::executeCondition(Creature* creature, int32_t interval) +{ + if (periodDamage != 0) { + periodDamageTick += interval; + + if (periodDamageTick >= tickInterval) { + periodDamageTick = 0; + doDamage(creature, periodDamage); + } + } else if (!damageList.empty()) { + IntervalInfo& damageInfo = damageList.front(); + + bool bRemove = (getTicks() != -1); + creature->onTickCondition(getType(), bRemove); + damageInfo.timeLeft -= interval; + + if (damageInfo.timeLeft <= 0) { + int32_t damage = damageInfo.value; + + if (bRemove) { + damageList.pop_front(); + } else { + damageInfo.timeLeft = damageInfo.interval; + } + + doDamage(creature, damage); + } + + if (!bRemove) { + if (getTicks() > 0) { + endTime += interval; + } + + interval = 0; + } + } + + return Condition::executeCondition(creature, interval); +} + +bool ConditionDamage::getNextDamage(int32_t& damage) +{ + if (periodDamage != 0) { + damage = periodDamage; + return true; + } else if (!damageList.empty()) { + IntervalInfo& damageInfo = damageList.front(); + + damage = damageInfo.value; + + if (getTicks() != -1) { + damageList.pop_front(); + } + + return true; + } + + return false; +} + +bool ConditionDamage::doDamage(Creature* creature, int32_t damage) +{ + if (creature->isSuppress(getType())) { + return true; + } + + CombatType_t combatType = Combat::ConditionToDamageType(conditionType); + Creature* attacker = g_game.getCreatureByID(owner); + + if (g_game.combatBlockHit(combatType, attacker, creature, damage, false, false)) { + return false; + } + + return g_game.combatChangeHealth(combatType, attacker, creature, damage); +} + +void ConditionDamage::endCondition(Creature* creature, ConditionEnd_t reason) +{ + // +} + +void ConditionDamage::addCondition(Creature* creature, const Condition* addCondition) +{ + if (addCondition->getType() == conditionType) { + const ConditionDamage& conditionDamage = static_cast(*addCondition); + + if (updateCondition(&conditionDamage)) { + setTicks(addCondition->getTicks()); + owner = conditionDamage.owner; + maxDamage = conditionDamage.maxDamage; + minDamage = conditionDamage.minDamage; + startDamage = conditionDamage.startDamage; + tickInterval = conditionDamage.tickInterval; + periodDamage = conditionDamage.periodDamage; + int32_t nextTimeLeft = tickInterval; + + if (!damageList.empty()) { + //save previous timeLeft + IntervalInfo& damageInfo = damageList.front(); + nextTimeLeft = damageInfo.timeLeft; + damageList.clear(); + } + + damageList = conditionDamage.damageList; + + if (init()) { + if (!damageList.empty()) { + //restore last timeLeft + IntervalInfo& damageInfo = damageList.front(); + damageInfo.timeLeft = nextTimeLeft; + } + + if (!delayed) { + int32_t damage = 0; + + if (getNextDamage(damage)) { + doDamage(creature, damage); + } + } + } + } + } +} + +int32_t ConditionDamage::getTotalDamage() const +{ + int32_t result = 0; + + if (!damageList.empty()) { + for (DamageList::const_iterator it = damageList.begin(); it != damageList.end(); ++it) { + result += it->value; + } + } else { + result = maxDamage + (maxDamage - minDamage) / 2; + } + + return std::abs(result); +} + +uint32_t ConditionDamage::getIcons() const +{ + uint32_t icons = Condition::getIcons(); + + switch (conditionType) { + case CONDITION_FIRE: + icons |= ICON_BURN; + break; + + case CONDITION_ENERGY: + icons |= ICON_ENERGY; + break; + + case CONDITION_DROWN: + icons |= ICON_DROWNING; + break; + + case CONDITION_POISON: + icons |= ICON_POISON; + break; + + case CONDITION_FREEZING: + icons |= ICON_FREEZING; + break; + + case CONDITION_DAZZLED: + icons |= ICON_DAZZLED; + break; + + case CONDITION_CURSED: + icons |= ICON_CURSED; + break; + + case CONDITION_BLEEDING: + icons |= ICON_BLEEDING; + break; + + default: + break; + } + + return icons; +} + +void ConditionDamage::generateDamageList(int32_t amount, int32_t start, std::list& list) +{ + amount = std::abs(amount); + int32_t sum = 0; + int32_t med = 0; + double x1, x2; + + for (int32_t i = start; i > 0; --i) { + int32_t n = start + 1 - i; + med = (n * amount) / start; + + do { + sum += i; + list.push_back(i); + + x1 = std::fabs(1.0 - (((float)sum) + i) / med); + x2 = std::fabs(1.0 - (((float)sum) / med)); + } while (x1 < x2); + } +} + +ConditionSpeed::ConditionSpeed(ConditionId_t _id, ConditionType_t _type, int32_t _ticks, bool _buff, uint32_t _subId, int32_t changeSpeed) : + Condition(_id, _type, _ticks, _buff, _subId) +{ + speedDelta = changeSpeed; + mina = 0.0f; + minb = 0.0f; + maxa = 0.0f; + maxb = 0.0f; +} + +void ConditionSpeed::setFormulaVars(float _mina, float _minb, float _maxa, float _maxb) +{ + mina = _mina; + minb = _minb; + maxa = _maxa; + maxb = _maxb; +} + +void ConditionSpeed::getFormulaValues(int32_t var, int32_t& min, int32_t& max) const +{ + min = (int32_t)std::ceil(var * 1.f * mina + minb); + max = (int32_t)std::ceil(var * 1.f * maxa + maxb); +} + +bool ConditionSpeed::setParam(ConditionParam_t param, int32_t value) +{ + Condition::setParam(param, value); + + if (param == CONDITIONPARAM_SPEED) { + speedDelta = value; + + if (value > 0) { + conditionType = CONDITION_HASTE; + } else { + conditionType = CONDITION_PARALYZE; + } + + return true; + } + + return false; +} + +bool ConditionSpeed::unserializeProp(ConditionAttr_t attr, PropStream& propStream) +{ + if (attr == CONDITIONATTR_SPEEDDELTA) { + int32_t value = 0; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + speedDelta = value; + return true; + } else if (attr == CONDITIONATTR_FORMULA_MINA) { + float value = 0; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + mina = value; + return true; + } else if (attr == CONDITIONATTR_FORMULA_MINB) { + float value = 0; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + minb = value; + return true; + } else if (attr == CONDITIONATTR_FORMULA_MAXA) { + float value = 0; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + maxa = value; + return true; + } else if (attr == CONDITIONATTR_FORMULA_MAXB) { + float value = 0; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + maxb = value; + return true; + } + + return Condition::unserializeProp(attr, propStream); +} + +bool ConditionSpeed::serialize(PropWriteStream& propWriteStream) +{ + if (!Condition::serialize(propWriteStream)) { + return false; + } + + propWriteStream.ADD_UCHAR(CONDITIONATTR_SPEEDDELTA); + propWriteStream.ADD_VALUE(speedDelta); + + propWriteStream.ADD_UCHAR(CONDITIONATTR_FORMULA_MINA); + propWriteStream.ADD_VALUE(mina); + + propWriteStream.ADD_UCHAR(CONDITIONATTR_FORMULA_MINB); + propWriteStream.ADD_VALUE(minb); + + propWriteStream.ADD_UCHAR(CONDITIONATTR_FORMULA_MAXA); + propWriteStream.ADD_VALUE(maxa); + + propWriteStream.ADD_UCHAR(CONDITIONATTR_FORMULA_MAXB); + propWriteStream.ADD_VALUE(maxb); + return true; +} + +bool ConditionSpeed::startCondition(Creature* creature) +{ + if (!Condition::startCondition(creature)) { + return false; + } + + if (speedDelta == 0) { + int32_t min, max; + getFormulaValues(creature->getBaseSpeed(), min, max); + speedDelta = random_range(min, max); + } + + g_game.changeSpeed(creature, speedDelta); + return true; +} + +bool ConditionSpeed::executeCondition(Creature* creature, int32_t interval) +{ + return Condition::executeCondition(creature, interval); +} + +void ConditionSpeed::endCondition(Creature* creature, ConditionEnd_t reason) +{ + g_game.changeSpeed(creature, -speedDelta); +} + +void ConditionSpeed::addCondition(Creature* creature, const Condition* addCondition) +{ + if (conditionType != addCondition->getType()) { + return; + } + + if (getTicks() == -1 && addCondition->getTicks() > 0) { + return; + } + + setTicks(addCondition->getTicks()); + + const ConditionSpeed& conditionSpeed = static_cast(*addCondition); + int32_t oldSpeedDelta = speedDelta; + speedDelta = conditionSpeed.speedDelta; + mina = conditionSpeed.mina; + maxa = conditionSpeed.maxa; + minb = conditionSpeed.minb; + maxb = conditionSpeed.maxb; + + if (speedDelta == 0) { + int32_t min; + int32_t max; + getFormulaValues(creature->getBaseSpeed(), min, max); + speedDelta = random_range(min, max); + } + + int32_t newSpeedChange = (speedDelta - oldSpeedDelta); + + if (newSpeedChange != 0) { + g_game.changeSpeed(creature, newSpeedChange); + } +} + +uint32_t ConditionSpeed::getIcons() const +{ + uint32_t icons = Condition::getIcons(); + + switch (conditionType) { + case CONDITION_HASTE: + icons |= ICON_HASTE; + break; + + case CONDITION_PARALYZE: + icons |= ICON_PARALYZE; + break; + + default: + break; + } + + return icons; +} + +ConditionInvisible::ConditionInvisible(ConditionId_t _id, ConditionType_t _type, int32_t _ticks, bool _buff, uint32_t _subId) : + ConditionGeneric(_id, _type, _ticks, _buff, _subId) +{ + // +} + +bool ConditionInvisible::startCondition(Creature* creature) +{ + if (!Condition::startCondition(creature)) { + return false; + } + + g_game.internalCreatureChangeVisible(creature, false); + return true; +} + +void ConditionInvisible::endCondition(Creature* creature, ConditionEnd_t reason) +{ + if (!creature->isInvisible()) { + g_game.internalCreatureChangeVisible(creature, true); + } +} + +ConditionOutfit::ConditionOutfit(ConditionId_t _id, ConditionType_t _type, int32_t _ticks, bool _buff, uint32_t _subId) : + Condition(_id, _type, _ticks, _buff, _subId) +{ + // +} + +void ConditionOutfit::addOutfit(Outfit_t outfit) +{ + outfits.push_back(outfit); +} + +bool ConditionOutfit::unserializeProp(ConditionAttr_t attr, PropStream& propStream) +{ + if (attr == CONDITIONATTR_OUTFIT) { + Outfit_t outfit; + + if (!propStream.GET_VALUE(outfit)) { + return false; + } + + outfits.push_back(outfit); + return true; + } + + return Condition::unserializeProp(attr, propStream); +} + +bool ConditionOutfit::serialize(PropWriteStream& propWriteStream) +{ + if (!Condition::serialize(propWriteStream)) { + return false; + } + + for (std::vector::const_iterator it = outfits.begin(); it != outfits.end(); ++it) { + propWriteStream.ADD_UCHAR(CONDITIONATTR_OUTFIT); + propWriteStream.ADD_VALUE(*it); + } + + return true; +} + +bool ConditionOutfit::startCondition(Creature* creature) +{ + if (!Condition::startCondition(creature)) { + return false; + } + + changeOutfit(creature); + return true; +} + +bool ConditionOutfit::executeCondition(Creature* creature, int32_t interval) +{ + return Condition::executeCondition(creature, interval); +} + +void ConditionOutfit::changeOutfit(Creature* creature, int32_t index /*= -1*/) +{ + if (!outfits.empty()) { + if (index == -1) { + index = random_range(0, outfits.size() - 1); + } + + Outfit_t outfit = outfits[index]; + g_game.internalCreatureChangeOutfit(creature, outfit); + } +} + +void ConditionOutfit::endCondition(Creature* creature, ConditionEnd_t reason) +{ + g_game.internalCreatureChangeOutfit(creature, creature->getDefaultOutfit()); +} + +void ConditionOutfit::addCondition(Creature* creature, const Condition* addCondition) +{ + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); + + const ConditionOutfit& conditionOutfit = static_cast(*addCondition); + outfits = conditionOutfit.outfits; + + changeOutfit(creature); + } +} + +ConditionLight::ConditionLight(ConditionId_t _id, ConditionType_t _type, int32_t _ticks, bool _buff, uint32_t _subId, int32_t _lightlevel, int32_t _lightcolor) : + Condition(_id, _type, _ticks, _buff, _subId) +{ + lightInfo.level = _lightlevel; + lightInfo.color = _lightcolor; + internalLightTicks = 0; + lightChangeInterval = 0; +} + +bool ConditionLight::startCondition(Creature* creature) +{ + if (!Condition::startCondition(creature)) { + return false; + } + + internalLightTicks = 0; + lightChangeInterval = ticks / lightInfo.level; + creature->setCreatureLight(lightInfo); + g_game.changeLight(creature); + return true; +} + +bool ConditionLight::executeCondition(Creature* creature, int32_t interval) +{ + internalLightTicks += interval; + + if (internalLightTicks >= lightChangeInterval) { + internalLightTicks = 0; + LightInfo creatureLight; + creature->getCreatureLight(creatureLight); + + if (creatureLight.level > 0) { + --creatureLight.level; + creature->setCreatureLight(creatureLight); + g_game.changeLight(creature); + } + } + + return Condition::executeCondition(creature, interval); +} + +void ConditionLight::endCondition(Creature* creature, ConditionEnd_t reason) +{ + creature->setNormalCreatureLight(); + g_game.changeLight(creature); +} + +void ConditionLight::addCondition(Creature* creature, const Condition* addCondition) +{ + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); + + const ConditionLight& conditionLight = static_cast(*addCondition); + lightInfo.level = conditionLight.lightInfo.level; + lightInfo.color = conditionLight.lightInfo.color; + lightChangeInterval = getTicks() / lightInfo.level; + internalLightTicks = 0; + creature->setCreatureLight(lightInfo); + g_game.changeLight(creature); + } +} + +bool ConditionLight::setParam(ConditionParam_t param, int32_t value) +{ + bool ret = Condition::setParam(param, value); + + if (!ret) { + switch (param) { + case CONDITIONPARAM_LIGHT_LEVEL: + lightInfo.level = value; + return true; + + case CONDITIONPARAM_LIGHT_COLOR: + lightInfo.color = value; + return true; + + default: + return false; + } + } + + return false; +} + +bool ConditionLight::unserializeProp(ConditionAttr_t attr, PropStream& propStream) +{ + if (attr == CONDITIONATTR_LIGHTCOLOR) { + uint32_t value = 0; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + lightInfo.color = value; + return true; + } else if (attr == CONDITIONATTR_LIGHTLEVEL) { + uint32_t value = 0; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + lightInfo.level = value; + return true; + } else if (attr == CONDITIONATTR_LIGHTTICKS) { + uint32_t value = 0; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + internalLightTicks = value; + return true; + } else if (attr == CONDITIONATTR_LIGHTINTERVAL) { + uint32_t value = 0; + + if (!propStream.GET_VALUE(value)) { + return false; + } + + lightChangeInterval = value; + return true; + } + + return Condition::unserializeProp(attr, propStream); +} + +bool ConditionLight::serialize(PropWriteStream& propWriteStream) +{ + if (!Condition::serialize(propWriteStream)) { + return false; + } + + propWriteStream.ADD_UCHAR(CONDITIONATTR_LIGHTCOLOR); + propWriteStream.ADD_VALUE(lightInfo.color); + + propWriteStream.ADD_UCHAR(CONDITIONATTR_LIGHTLEVEL); + propWriteStream.ADD_VALUE(lightInfo.level); + + propWriteStream.ADD_UCHAR(CONDITIONATTR_LIGHTTICKS); + propWriteStream.ADD_VALUE(internalLightTicks); + + propWriteStream.ADD_UCHAR(CONDITIONATTR_LIGHTINTERVAL); + propWriteStream.ADD_VALUE(lightChangeInterval); + + return true; +} + +ConditionSpellCooldown::ConditionSpellCooldown(ConditionId_t _id, ConditionType_t _type, int32_t _ticks, bool _buff, uint32_t _subId) : + ConditionGeneric(_id, _type, _ticks, _buff, _subId) +{ + // +} + +void ConditionSpellCooldown::addCondition(Creature* creature, const Condition* addCondition) +{ + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); + + if (subId != 0 && ticks > 0) { + Player* player = creature->getPlayer(); + + if (player) { + player->sendSpellCooldown(subId, ticks); + } + } + } +} + +bool ConditionSpellCooldown::startCondition(Creature* creature) +{ + if (!Condition::startCondition(creature)) { + return false; + } + + if (subId != 0 && ticks > 0) { + Player* player = creature->getPlayer(); + + if (player) { + player->sendSpellCooldown(subId, ticks); + } + } + + return true; +} + +ConditionSpellGroupCooldown::ConditionSpellGroupCooldown(ConditionId_t _id, ConditionType_t _type, int32_t _ticks, bool _buff, uint32_t _subId) : + ConditionGeneric(_id, _type, _ticks, _buff, _subId) +{ + // +} + +void ConditionSpellGroupCooldown::addCondition(Creature* creature, const Condition* addCondition) +{ + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); + + if (subId != 0 && ticks > 0) { + Player* player = creature->getPlayer(); + + if (player) { + player->sendSpellGroupCooldown((SpellGroup_t)subId, ticks); + } + } + } +} + +bool ConditionSpellGroupCooldown::startCondition(Creature* creature) +{ + if (!Condition::startCondition(creature)) { + return false; + } + + if (subId != 0 && ticks > 0) { + Player* player = creature->getPlayer(); + + if (player) { + player->sendSpellGroupCooldown((SpellGroup_t)subId, ticks); + } + } + + return true; +} diff --git a/src/condition.h b/src/condition.h new file mode 100644 index 0000000000..8b08b434fd --- /dev/null +++ b/src/condition.h @@ -0,0 +1,465 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_CONDITION_H__ +#define __OTSERV_CONDITION_H__ + +#include "fileloader.h" +#include "enums.h" + +#include +#include + +class Creature; +class Player; +class PropStream; + +enum ConditionType_t { + CONDITION_NONE = 0, + CONDITION_POISON = 1, + CONDITION_FIRE = 2, + CONDITION_ENERGY = 4, + CONDITION_BLEEDING = 8, + CONDITION_HASTE = 16, + CONDITION_PARALYZE = 32, + CONDITION_OUTFIT = 64, + CONDITION_INVISIBLE = 128, + CONDITION_LIGHT = 256, + CONDITION_MANASHIELD = 512, + CONDITION_INFIGHT = 1024, + CONDITION_DRUNK = 2048, + CONDITION_EXHAUST_WEAPON = 4096, + CONDITION_REGENERATION = 8192, + CONDITION_SOUL = 16384, + CONDITION_DROWN = 32768, + CONDITION_MUTED = 65536, + CONDITION_CHANNELMUTEDTICKS = 131072, + CONDITION_YELLTICKS = 262144, + CONDITION_ATTRIBUTES = 524288, + CONDITION_FREEZING = 1048576, + CONDITION_DAZZLED = 2097152, + CONDITION_CURSED = 4194304, + CONDITION_EXHAUST_COMBAT = 8388608, + CONDITION_EXHAUST_HEAL = 16777216, + CONDITION_PACIFIED = 33554432, + CONDITION_SPELLCOOLDOWN = 67108864, + CONDITION_SPELLGROUPCOOLDOWN = 134217728 +}; + +enum ConditionEnd_t { + CONDITIONEND_CLEANUP, + CONDITIONEND_DEATH, + CONDITIONEND_TICKS, + CONDITIONEND_ABORT +}; + +enum ConditionAttr_t { + CONDITIONATTR_TYPE = 1, + CONDITIONATTR_ID = 2, + CONDITIONATTR_TICKS = 3, + CONDITIONATTR_HEALTHTICKS = 4, + CONDITIONATTR_HEALTHGAIN = 5, + CONDITIONATTR_MANATICKS = 6, + CONDITIONATTR_MANAGAIN = 7, + CONDITIONATTR_DELAYED = 8, + CONDITIONATTR_OWNER = 9, + CONDITIONATTR_INTERVALDATA = 10, + CONDITIONATTR_SPEEDDELTA = 11, + CONDITIONATTR_FORMULA_MINA = 12, + CONDITIONATTR_FORMULA_MINB = 13, + CONDITIONATTR_FORMULA_MAXA = 14, + CONDITIONATTR_FORMULA_MAXB = 15, + CONDITIONATTR_LIGHTCOLOR = 16, + CONDITIONATTR_LIGHTLEVEL = 17, + CONDITIONATTR_LIGHTTICKS = 18, + CONDITIONATTR_LIGHTINTERVAL = 19, + CONDITIONATTR_SOULTICKS = 20, + CONDITIONATTR_SOULGAIN = 21, + CONDITIONATTR_SKILLS = 22, + CONDITIONATTR_STATS = 23, + CONDITIONATTR_OUTFIT = 24, + CONDITIONATTR_PERIODDAMAGE = 25, + CONDITIONATTR_ISBUFF = 26, + CONDITIONATTR_SUBID = 27, + + //reserved for serialization + CONDITIONATTR_END = 254 +}; + +struct IntervalInfo { + int32_t timeLeft; + int32_t value; + int32_t interval; +}; + +class Condition +{ + public: + Condition(ConditionId_t _id, ConditionType_t _type, int32_t _ticks, bool _buff = false, uint32_t _subId = 0); + virtual ~Condition() {} + + virtual bool startCondition(Creature* creature); + virtual bool executeCondition(Creature* creature, int32_t interval); + virtual void endCondition(Creature* creature, ConditionEnd_t reason) = 0; + virtual void addCondition(Creature* creature, const Condition* condition) = 0; + virtual uint32_t getIcons() const; + ConditionId_t getId() const { + return id; + } + uint32_t getSubId() const { + return subId; + } + + virtual Condition* clone() const = 0; + + ConditionType_t getType() const { + return conditionType; + } + int64_t getEndTime() const { + return endTime; + } + int32_t getTicks() const { + return ticks; + } + void setTicks(int32_t newTicks); + + static Condition* createCondition(ConditionId_t _id, ConditionType_t _type, int32_t ticks, int32_t param = 0, bool _buff = false, uint32_t _subId = 0); + static Condition* createCondition(PropStream& propStream); + + virtual bool setParam(ConditionParam_t param, int32_t value); + + //serialization + bool unserialize(PropStream& propStream); + virtual bool serialize(PropWriteStream& propWriteStream); + virtual bool unserializeProp(ConditionAttr_t attr, PropStream& propStream); + + bool isPersistent() const; + + protected: + ConditionId_t id; + uint32_t subId; + int32_t ticks; + int64_t endTime; + ConditionType_t conditionType; + bool isBuff; + + virtual bool updateCondition(const Condition* addCondition); +}; + +class ConditionGeneric: public Condition +{ + public: + ConditionGeneric(ConditionId_t _id, ConditionType_t _type, int32_t _ticks, bool _buff = false, uint32_t _subId = 0); + virtual ~ConditionGeneric() {} + + virtual bool startCondition(Creature* creature); + virtual bool executeCondition(Creature* creature, int32_t interval); + virtual void endCondition(Creature* creature, ConditionEnd_t reason); + virtual void addCondition(Creature* creature, const Condition* condition); + virtual uint32_t getIcons() const; + + virtual ConditionGeneric* clone() const { + return new ConditionGeneric(*this); + } +}; + +class ConditionManaShield : public ConditionGeneric +{ + public: + ConditionManaShield(ConditionId_t _id, ConditionType_t _type, int32_t _ticks, bool _buff = false, uint32_t _subId = 0) : ConditionGeneric(_id, _type, _ticks, _buff, _subId) {} + virtual ~ConditionManaShield() {} + + virtual ConditionManaShield* clone() const { + return new ConditionManaShield(*this); + } +}; + +class ConditionAttributes : public ConditionGeneric +{ + public: + ConditionAttributes(ConditionId_t _id, ConditionType_t _type, int32_t _ticks, bool _buff = false, uint32_t _subId = 0); + virtual ~ConditionAttributes() {} + + virtual bool startCondition(Creature* creature); + virtual bool executeCondition(Creature* creature, int32_t interval); + virtual void endCondition(Creature* creature, ConditionEnd_t reason); + virtual void addCondition(Creature* creature, const Condition* condition); + + virtual bool setParam(ConditionParam_t param, int32_t value); + + virtual ConditionAttributes* clone() const { + return new ConditionAttributes(*this); + } + + //serialization + virtual bool serialize(PropWriteStream& propWriteStream); + virtual bool unserializeProp(ConditionAttr_t attr, PropStream& propStream); + + protected: + int32_t skills[SKILL_LAST + 1]; + int32_t skillsPercent[SKILL_LAST + 1]; + int32_t stats[STAT_LAST + 1]; + int32_t statsPercent[STAT_LAST + 1]; + int32_t currentSkill; + int32_t currentStat; + + void updatePercentStats(Player* player); + void updateStats(Player* player); + void updatePercentSkills(Player* player); + void updateSkills(Player* player); +}; + +class ConditionRegeneration : public ConditionGeneric +{ + public: + ConditionRegeneration(ConditionId_t _id, ConditionType_t _type, int32_t _ticks, bool _buff = false, uint32_t _subId = 0); + virtual ~ConditionRegeneration() {} + + virtual void addCondition(Creature* creature, const Condition* addCondition); + virtual bool executeCondition(Creature* creature, int32_t interval); + + virtual bool setParam(ConditionParam_t param, int32_t value); + + virtual ConditionRegeneration* clone() const { + return new ConditionRegeneration(*this); + } + + //serialization + virtual bool serialize(PropWriteStream& propWriteStream); + virtual bool unserializeProp(ConditionAttr_t attr, PropStream& propStream); + + protected: + uint32_t internalHealthTicks; + uint32_t internalManaTicks; + + uint32_t healthTicks; + uint32_t manaTicks; + uint32_t healthGain; + uint32_t manaGain; +}; + +class ConditionSoul : public ConditionGeneric +{ + public: + ConditionSoul(ConditionId_t _id, ConditionType_t _type, int32_t _ticks, bool _buff = false, uint32_t _subId = 0); + virtual ~ConditionSoul() {} + + virtual void addCondition(Creature* creature, const Condition* addCondition); + virtual bool executeCondition(Creature* creature, int32_t interval); + + virtual bool setParam(ConditionParam_t param, int32_t value); + + virtual ConditionSoul* clone() const { + return new ConditionSoul(*this); + } + + //serialization + virtual bool serialize(PropWriteStream& propWriteStream); + virtual bool unserializeProp(ConditionAttr_t attr, PropStream& propStream); + + protected: + uint32_t internalSoulTicks; + uint32_t soulTicks; + uint32_t soulGain; +}; + +class ConditionInvisible: public ConditionGeneric +{ + public: + ConditionInvisible(ConditionId_t _id, ConditionType_t _type, int32_t _ticks, bool _buff = false, uint32_t _subId = 0); + virtual ~ConditionInvisible() {} + + virtual bool startCondition(Creature* creature); + virtual void endCondition(Creature* creature, ConditionEnd_t reason); + + virtual ConditionInvisible* clone() const { + return new ConditionInvisible(*this); + } +}; + +class ConditionDamage: public Condition +{ + public: + ConditionDamage(ConditionId_t _id, ConditionType_t _type, bool _buff = false, uint32_t _subId = 0); + virtual ~ConditionDamage() {} + + static void generateDamageList(int32_t amount, int32_t start, std::list& list); + + virtual bool startCondition(Creature* creature); + virtual bool executeCondition(Creature* creature, int32_t interval); + virtual void endCondition(Creature* creature, ConditionEnd_t reason); + virtual void addCondition(Creature* creature, const Condition* condition); + virtual uint32_t getIcons() const; + + virtual ConditionDamage* clone() const { + return new ConditionDamage(*this); + } + + virtual bool setParam(ConditionParam_t param, int32_t value); + + bool addDamage(int32_t rounds, int32_t time, int32_t value); + bool doForceUpdate() const { + return forceUpdate; + } + int32_t getTotalDamage() const; + + //serialization + virtual bool serialize(PropWriteStream& propWriteStream); + virtual bool unserializeProp(ConditionAttr_t attr, PropStream& propStream); + + protected: + int32_t maxDamage; + int32_t minDamage; + int32_t startDamage; + int32_t periodDamage; + int32_t periodDamageTick; + int32_t tickInterval; + + bool forceUpdate; + bool delayed; + uint32_t owner; + + bool init(); + + typedef std::list DamageList; + DamageList damageList; + + bool getNextDamage(int32_t& damage); + bool doDamage(Creature* creature, int32_t damage); + bool updateCondition(const ConditionDamage* addCondition); +}; + +class ConditionSpeed: public Condition +{ + public: + ConditionSpeed(ConditionId_t _id, ConditionType_t _type, int32_t _ticks, bool _buff, uint32_t _subId, int32_t changeSpeed); + virtual ~ConditionSpeed() {} + + virtual bool startCondition(Creature* creature); + virtual bool executeCondition(Creature* creature, int32_t interval); + virtual void endCondition(Creature* creature, ConditionEnd_t reason); + virtual void addCondition(Creature* creature, const Condition* condition); + virtual uint32_t getIcons() const; + + virtual ConditionSpeed* clone() const { + return new ConditionSpeed(*this); + } + + virtual bool setParam(ConditionParam_t param, int32_t value); + + void setFormulaVars(float _mina, float _minb, float _maxa, float _maxb); + + //serialization + virtual bool serialize(PropWriteStream& propWriteStream); + virtual bool unserializeProp(ConditionAttr_t attr, PropStream& propStream); + + protected: + void getFormulaValues(int32_t var, int32_t& min, int32_t& max) const; + + int32_t speedDelta; + + //formula variables + float mina; + float minb; + float maxa; + float maxb; +}; + +class ConditionOutfit: public Condition +{ + public: + ConditionOutfit(ConditionId_t _id, ConditionType_t _type, int32_t _ticks, bool _buff = false, uint32_t _subId = 0); + virtual ~ConditionOutfit() {} + + virtual bool startCondition(Creature* creature); + virtual bool executeCondition(Creature* creature, int32_t interval); + virtual void endCondition(Creature* creature, ConditionEnd_t reason); + virtual void addCondition(Creature* creature, const Condition* condition); + + virtual ConditionOutfit* clone() const { + return new ConditionOutfit(*this); + } + + void addOutfit(Outfit_t outfit); + + //serialization + virtual bool serialize(PropWriteStream& propWriteStream); + virtual bool unserializeProp(ConditionAttr_t attr, PropStream& propStream); + + protected: + std::vector outfits; + + void changeOutfit(Creature* creature, int32_t index = -1); +}; + +class ConditionLight: public Condition +{ + public: + ConditionLight(ConditionId_t _id, ConditionType_t _type, int32_t _ticks, bool _buff, uint32_t _subId, int32_t _lightlevel, int32_t _lightcolor); + virtual ~ConditionLight() {} + + virtual bool startCondition(Creature* creature); + virtual bool executeCondition(Creature* creature, int32_t interval); + virtual void endCondition(Creature* creature, ConditionEnd_t reason); + virtual void addCondition(Creature* creature, const Condition* addCondition); + + virtual ConditionLight* clone() const { + return new ConditionLight(*this); + } + + virtual bool setParam(ConditionParam_t param, int32_t value); + + //serialization + virtual bool serialize(PropWriteStream& propWriteStream); + virtual bool unserializeProp(ConditionAttr_t attr, PropStream& propStream); + + protected: + LightInfo lightInfo; + uint32_t internalLightTicks; + uint32_t lightChangeInterval; +}; + +class ConditionSpellCooldown: public ConditionGeneric +{ + public: + ConditionSpellCooldown(ConditionId_t _id, ConditionType_t _type, int32_t _ticks, bool _buff = false, uint32_t _subId = 0); + virtual ~ConditionSpellCooldown() {} + + virtual bool startCondition(Creature* creature); + virtual void addCondition(Creature* creature, const Condition* condition); + + virtual ConditionSpellCooldown* clone() const { + return new ConditionSpellCooldown(*this); + } +}; + +class ConditionSpellGroupCooldown: public ConditionGeneric +{ + public: + ConditionSpellGroupCooldown(ConditionId_t _id, ConditionType_t _type, int32_t _ticks, bool _buff = false, uint32_t _subId = 0); + virtual ~ConditionSpellGroupCooldown() {} + + virtual bool startCondition(Creature* creature); + virtual void addCondition(Creature* creature, const Condition* condition); + + virtual ConditionSpellGroupCooldown* clone() const { + return new ConditionSpellGroupCooldown(*this); + } +}; + +#endif diff --git a/src/configmanager.cpp b/src/configmanager.cpp new file mode 100644 index 0000000000..b8724eab66 --- /dev/null +++ b/src/configmanager.cpp @@ -0,0 +1,247 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "definitions.h" +#include "configmanager.h" +#include "tools.h" + +#include +#include + +ConfigManager::ConfigManager() +{ + m_isLoaded = false; +} + +ConfigManager::~ConfigManager() +{ + // +} + +bool ConfigManager::loadFile(const std::string& _filename) +{ + lua_State* L = lua_open(); + + if (!L) { + throw std::runtime_error("Failed to allocate memory"); + } + + if (luaL_dofile(L, _filename.c_str())) { + std::cout << "[Error - ConfigManager::loadFile] With file = " << _filename << ", " << lua_tostring(L, -1) << std::endl; + lua_close(L); + return false; + } + + //parse config + if (!m_isLoaded) { //info that must be loaded one time (unless we reset the modules involved) + m_confBoolean[SERVERSAVE_ENABLED] = booleanString(getGlobalString(L, "serverSaveEnabled", "yes")); + m_confBoolean[SAVE_GLOBAL_STORAGE] = booleanString(getGlobalString(L, "saveGlobalStorage", "no")); + m_confBoolean[BIND_ONLY_GLOBAL_ADDRESS] = booleanString(getGlobalString(L, "bindOnlyGlobalAddress", "no")); + m_confBoolean[OPTIMIZE_DATABASE] = booleanString(getGlobalString(L, "startupDatabaseOptimization", "yes")); + + m_confString[CONFIG_FILE] = _filename; + m_confString[IP] = getGlobalString(L, "ip", "127.0.0.1"); + m_confString[MAP_NAME] = getGlobalString(L, "mapName", "forgotten"); + m_confString[MAP_AUTHOR] = getGlobalString(L, "mapAuthor", "Unknown"); + m_confString[HOUSE_RENT_PERIOD] = getGlobalString(L, "houseRentPeriod", "monthly"); + m_confString[MYSQL_HOST] = getGlobalString(L, "mysqlHost", "localhost"); + m_confString[MYSQL_USER] = getGlobalString(L, "mysqlUser", "root"); + m_confString[MYSQL_PASS] = getGlobalString(L, "mysqlPass", ""); + m_confString[MYSQL_DB] = getGlobalString(L, "mysqlDatabase", "theforgottenserver"); + m_confString[PASSWORDTYPE] = getGlobalString(L, "passwordType", "plain"); + + m_confInteger[SQL_PORT] = getGlobalNumber(L, "mysqlPort", 3306); + m_confInteger[PASSWORD_TYPE] = PASSWORD_TYPE_PLAIN; + m_confInteger[SERVERSAVE_H] = getGlobalNumber(L, "serverSaveHour", 3); + m_confInteger[ADMIN_PORT] = getGlobalNumber(L, "adminProtocolPort", 7171); + m_confInteger[GAME_PORT] = getGlobalNumber(L, "gameProtocolPort", 7172); + m_confInteger[LOGIN_PORT] = getGlobalNumber(L, "loginProtocolPort", 7171); + m_confInteger[STATUS_PORT] = getGlobalNumber(L, "statusProtocolPort", 7171); + + m_confInteger[MARKET_OFFER_DURATION] = getGlobalNumber(L, "marketOfferDuration", 30 * 24 * 60 * 60); + } + + m_confBoolean[ON_OR_OFF_CHARLIST] = booleanString(getGlobalString(L, "displayOnOrOffAtCharlist", "no")); + m_confBoolean[ALLOW_CHANGEOUTFIT] = booleanString(getGlobalString(L, "allowChangeOutfit", "yes")); + m_confBoolean[ONE_PLAYER_ON_ACCOUNT] = booleanString(getGlobalString(L, "onePlayerOnlinePerAccount", "yes")); + m_confBoolean[CANNOT_ATTACK_SAME_LOOKFEET] = booleanString(getGlobalString(L, "noDamageToSameLookfeet", "no")); + m_confBoolean[AIMBOT_HOTKEY_ENABLED] = booleanString(getGlobalString(L, "hotkeyAimbotEnabled", "yes")); + m_confBoolean[SHOW_GAMEMASTERS_ONLINE] = booleanString(getGlobalString(L, "displayGamemastersWithOnlineCommand", "no")); + m_confBoolean[REMOVE_AMMO] = booleanString(getGlobalString(L, "removeAmmoWhenUsingDistanceWeapon", "yes")); + m_confBoolean[REMOVE_RUNE_CHARGES] = booleanString(getGlobalString(L, "removeChargesFromRunes", "yes")); + m_confBoolean[REMOVE_WEAPON_CHARGES] = booleanString(getGlobalString(L, "removeChargesFromWeapons", "yes")); + m_confBoolean[EXPERIENCE_FROM_PLAYERS] = booleanString(getGlobalString(L, "experienceByKillingPlayers", "no")); + m_confBoolean[SHUTDOWN_AT_SERVERSAVE] = booleanString(getGlobalString(L, "shutdownAtServerSave", "no")); + m_confBoolean[CLEAN_MAP_AT_SERVERSAVE] = booleanString(getGlobalString(L, "cleanMapAtServerSave", "yes")); + m_confBoolean[FREE_PREMIUM] = booleanString(getGlobalString(L, "freePremium", "no")); + m_confBoolean[ADMIN_LOGS_ENABLED] = booleanString(getGlobalString(L, "adminLogsEnabled", "no")); + m_confBoolean[BROADCAST_BANISHMENTS] = booleanString(getGlobalString(L, "broadcastBanishments", "yes")); + m_confBoolean[REPLACE_KICK_ON_LOGIN] = booleanString(getGlobalString(L, "replaceKickOnLogin", "yes")); + m_confBoolean[OLD_CONDITION_ACCURACY] = booleanString(getGlobalString(L, "oldConditionAccuracy", "no")); + m_confBoolean[ALLOW_CLONES] = booleanString(getGlobalString(L, "allowClones", "no")); + m_confBoolean[MARKET_ENABLED] = booleanString(getGlobalString(L, "marketEnabled", "yes")); + m_confBoolean[MARKET_PREMIUM] = booleanString(getGlobalString(L, "premiumToCreateMarketOffer", "yes")); + m_confBoolean[STAMINA_SYSTEM] = booleanString(getGlobalString(L, "staminaSystem", "yes")); + + m_confString[DEFAULT_PRIORITY] = getGlobalString(L, "defaultPriority", "high"); + m_confString[MAP_STORAGE_TYPE] = getGlobalString(L, "mapStorageType", "relational"); + m_confString[LOGIN_MSG] = getGlobalString(L, "loginMessage", "Welcome to the Forgotten Server!"); + m_confString[SERVER_NAME] = getGlobalString(L, "serverName"); + m_confString[OWNER_NAME] = getGlobalString(L, "ownerName"); + m_confString[OWNER_EMAIL] = getGlobalString(L, "ownerEmail"); + m_confString[URL] = getGlobalString(L, "url"); + m_confString[LOCATION] = getGlobalString(L, "location"); + m_confString[MOTD] = getGlobalString(L, "motd"); + m_confString[WORLD_TYPE] = getGlobalString(L, "worldType", "pvp"); + + m_confInteger[LOGIN_TRIES] = getGlobalNumber(L, "loginTries", 3); + m_confInteger[RETRY_TIMEOUT] = getGlobalNumber(L, "retryTimeout", 30 * 1000); + m_confInteger[LOGIN_TIMEOUT] = getGlobalNumber(L, "loginTimeout", 5 * 1000); + m_confInteger[MAX_PLAYERS] = getGlobalNumber(L, "maxPlayers"); + m_confInteger[PZ_LOCKED] = getGlobalNumber(L, "pzLocked", 0); + m_confInteger[DEFAULT_DESPAWNRANGE] = getGlobalNumber(L, "deSpawnRange", 2); + m_confInteger[DEFAULT_DESPAWNRADIUS] = getGlobalNumber(L, "deSpawnRadius", 50); + m_confInteger[RATE_EXPERIENCE] = getGlobalNumber(L, "rateExp", 1); + m_confInteger[RATE_SKILL] = getGlobalNumber(L, "rateSkill", 1); + m_confInteger[RATE_LOOT] = getGlobalNumber(L, "rateLoot", 1); + m_confInteger[RATE_MAGIC] = getGlobalNumber(L, "rateMagic", 1); + m_confInteger[RATE_SPAWN] = getGlobalNumber(L, "rateSpawn", 1); + m_confInteger[HOUSE_PRICE] = getGlobalNumber(L, "housePriceEachSQM", 1000); + m_confInteger[KILLS_TO_RED] = getGlobalNumber(L, "killsToRedSkull", 3); + m_confInteger[KILLS_TO_BLACK] = getGlobalNumber(L, "killsToBlackSkull", 6); + m_confInteger[KILLS_TO_BAN] = getGlobalNumber(L, "killsToBan", 5); + m_confInteger[BAN_DAYS] = getGlobalNumber(L, "banDays", 7); + m_confInteger[FINAL_BAN_DAYS] = getGlobalNumber(L, "finalBanDays", 30); + m_confInteger[ACTIONS_DELAY_INTERVAL] = getGlobalNumber(L, "timeBetweenActions", 200); + m_confInteger[EX_ACTIONS_DELAY_INTERVAL] = getGlobalNumber(L, "timeBetweenExActions", 1000); + m_confInteger[MAX_MESSAGEBUFFER] = getGlobalNumber(L, "maxMessageBuffer", 4); + m_confInteger[CRITICAL_HIT_CHANCE] = getGlobalNumber(L, "criticalHitChance", 5); + m_confInteger[KICK_AFTER_MINUTES] = getGlobalNumber(L, "kickIdlePlayerAfterMinutes", 15); + m_confInteger[PROTECTION_LEVEL] = getGlobalNumber(L, "protectionLevel", 1); + m_confInteger[DEATH_LOSE_PERCENT] = getGlobalNumber(L, "deathLosePercent", -1); + m_confInteger[STATUSQUERY_TIMEOUT] = getGlobalNumber(L, "statusTimeout", 5 * 60 * 1000); + m_confInteger[FRAG_TIME] = getGlobalNumber(L, "timeToDecreaseFrags", 24 * 60 * 60 * 1000); + m_confInteger[WHITE_SKULL_TIME] = getGlobalNumber(L, "whiteSkullTime", 15 * 60 * 1000); + m_confInteger[AUTO_SAVE_EACH_MINUTES] = getGlobalNumber(L, "autoSaveEachMinutes", 0); + m_confInteger[STAIRHOP_DELAY] = getGlobalNumber(L, "stairJumpExhaustion", 2000); + m_confInteger[EXP_FROM_PLAYERS_LEVEL_RANGE] = getGlobalNumber(L, "expFromPlayersLevelRange", 75); + m_confInteger[CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES] = getGlobalNumber(L, "checkExpiredMarketOffersEachMinutes", 60); + m_confInteger[MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER] = getGlobalNumber(L, "maxMarketOffersAtATimePerPlayer", 100); + m_confInteger[MAX_PACKETS_PER_SECOND] = getGlobalNumber(L, "maxPacketsPerSecond", 25); + + m_isLoaded = true; + lua_close(L); + return true; +} + +bool ConfigManager::reload() +{ + if (!m_isLoaded) { + return false; + } + + return loadFile(m_confString[CONFIG_FILE]); +} + +const std::string& ConfigManager::getString(string_config_t _what) const +{ + if (m_isLoaded && _what < LAST_STRING_CONFIG) { + return m_confString[_what]; + } else { + std::cout << "Warning: [ConfigManager::getString] " << _what << std::endl; + return m_confString[DUMMY_STR]; + } +} + +int32_t ConfigManager::getNumber(integer_config_t _what) const +{ + if (m_isLoaded && _what < LAST_INTEGER_CONFIG) { + return m_confInteger[_what]; + } else { + std::cout << "Warning: [ConfigManager::getNumber] " << _what << std::endl; + return 0; + } +} + +bool ConfigManager::getBoolean(boolean_config_t _what) const +{ + if (m_isLoaded && _what < LAST_BOOLEAN_CONFIG) { + return m_confBoolean[_what]; + } else { + std::cout << "Warning: [ConfigManager::getBoolean] " << _what << std::endl; + return false; + } +} + +bool ConfigManager::setNumber(integer_config_t _what, int32_t _value) +{ + if (m_isLoaded && _what < LAST_INTEGER_CONFIG) { + m_confInteger[_what] = _value; + return true; + } else { + std::cout << "Warning: [ConfigManager::setNumber] " << _what << std::endl; + return false; + } +} + +std::string ConfigManager::getGlobalString(lua_State* _L, const std::string& _identifier, const std::string& _default) +{ + lua_getglobal(_L, _identifier.c_str()); + + if (!lua_isstring(_L, -1)) { + return _default; + } + + int32_t len = (int32_t)lua_strlen(_L, -1); + std::string ret(lua_tostring(_L, -1), len); + lua_pop(_L, 1); + + return ret; +} + +int32_t ConfigManager::getGlobalNumber(lua_State* _L, const std::string& _identifier, const int32_t _default) +{ + lua_getglobal(_L, _identifier.c_str()); + + if (!lua_isnumber(_L, -1)) { + return _default; + } + + int32_t val = (int32_t)lua_tonumber(_L, -1); + lua_pop(_L, 1); + + return val; +} + +std::string ConfigManager::getGlobalStringField (lua_State* _L, const std::string& _identifier, const int32_t _key, const std::string& _default) +{ + lua_getglobal(_L, _identifier.c_str()); + + lua_pushnumber(_L, _key); + lua_gettable(_L, -2); /* get table[key] */ + + if (!lua_isstring(_L, -1)) { + return _default; + } + + std::string result = lua_tostring(_L, -1); + lua_pop(_L, 2); /* remove number and key*/ + return result; +} diff --git a/src/configmanager.h b/src/configmanager.h new file mode 100644 index 0000000000..abbdfa8588 --- /dev/null +++ b/src/configmanager.h @@ -0,0 +1,158 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef _CONFIG_MANAGER_H +#define _CONFIG_MANAGER_H + +#include + +extern "C" +{ +#include +#include +#include +} + +class ConfigManager +{ + public: + ConfigManager(); + ~ConfigManager(); + + enum boolean_config_t { + ON_OR_OFF_CHARLIST = 0, + ALLOW_CHANGEOUTFIT, + CANNOT_ATTACK_SAME_LOOKFEET, + ONE_PLAYER_ON_ACCOUNT, + AIMBOT_HOTKEY_ENABLED, + SHOW_GAMEMASTERS_ONLINE, + REMOVE_AMMO, + REMOVE_RUNE_CHARGES, + REMOVE_WEAPON_CHARGES, + EXPERIENCE_FROM_PLAYERS, + SHUTDOWN_AT_SERVERSAVE, + CLEAN_MAP_AT_SERVERSAVE, + SERVERSAVE_ENABLED, + FREE_PREMIUM, + ADMIN_LOGS_ENABLED, + SAVE_GLOBAL_STORAGE, + REPLACE_KICK_ON_LOGIN, + OLD_CONDITION_ACCURACY, + BROADCAST_BANISHMENTS, + FREE_MEMORY_AT_SHUTDOWN, + ALLOW_CLONES, + BIND_ONLY_GLOBAL_ADDRESS, + OPTIMIZE_DATABASE, + MARKET_ENABLED, + MARKET_PREMIUM, + STAMINA_SYSTEM, + LAST_BOOLEAN_CONFIG /* this must be the last one */ + }; + + enum string_config_t { + DUMMY_STR = 0, + CONFIG_FILE, + MAP_NAME, + HOUSE_RENT_PERIOD, + LOGIN_MSG, + FIRST_MSG, + SERVER_NAME, + OWNER_NAME, + OWNER_EMAIL, + URL, + LOCATION, + IP, + MOTD, + WORLD_TYPE, + MYSQL_HOST, + MYSQL_USER, + MYSQL_PASS, + MYSQL_DB, + DEFAULT_PRIORITY, + PASSWORDTYPE, + MAP_AUTHOR, + MAP_STORAGE_TYPE, + LAST_STRING_CONFIG /* this must be the last one */ + }; + + enum integer_config_t { + LOGIN_TRIES = 0, + RETRY_TIMEOUT, + LOGIN_TIMEOUT, + SQL_PORT, + MAX_PLAYERS, + PZ_LOCKED, + DEFAULT_DESPAWNRANGE, + DEFAULT_DESPAWNRADIUS, + RATE_EXPERIENCE, + RATE_SKILL, + RATE_LOOT, + RATE_MAGIC, + RATE_SPAWN, + SERVERSAVE_H, + HOUSE_PRICE, + KILLS_TO_RED, + KILLS_TO_BLACK, + KILLS_TO_BAN, + BAN_DAYS, + FINAL_BAN_DAYS, + MAX_MESSAGEBUFFER, + ACTIONS_DELAY_INTERVAL, + EX_ACTIONS_DELAY_INTERVAL, + CRITICAL_HIT_CHANCE, + KICK_AFTER_MINUTES, + PROTECTION_LEVEL, + DEATH_LOSE_PERCENT, + PASSWORD_TYPE, + STATUSQUERY_TIMEOUT, + FRAG_TIME, + WHITE_SKULL_TIME, + AUTO_SAVE_EACH_MINUTES, + ADMIN_PORT, + GAME_PORT, + LOGIN_PORT, + STATUS_PORT, + STAIRHOP_DELAY, + MARKET_OFFER_DURATION, + CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES, + MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER, + EXP_FROM_PLAYERS_LEVEL_RANGE, + MAX_PACKETS_PER_SECOND, + LAST_INTEGER_CONFIG /* this must be the last one */ + }; + + bool loadFile(const std::string& _filename); + bool reload(); + + const std::string& getString(string_config_t _what) const; + int32_t getNumber(integer_config_t _what) const; + bool getBoolean(boolean_config_t _what) const; + bool setNumber(integer_config_t _what, int32_t _value); + + private: + std::string getGlobalString(lua_State* _L, const std::string& _identifier, const std::string& _default = ""); + int32_t getGlobalNumber(lua_State* _L, const std::string& _identifier, const int32_t _default = 0); + std::string getGlobalStringField(lua_State* _L, const std::string& _identifier, const int32_t _key, const std::string& _default = ""); + + bool m_isLoaded; + std::string m_confString[LAST_STRING_CONFIG]; + int32_t m_confInteger[LAST_INTEGER_CONFIG]; + bool m_confBoolean[LAST_BOOLEAN_CONFIG]; +}; + +#endif diff --git a/src/connection.cpp b/src/connection.cpp new file mode 100644 index 0000000000..fadd59e382 --- /dev/null +++ b/src/connection.cpp @@ -0,0 +1,527 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "protocol.h" +#include "outputmessage.h" +#include "tasks.h" +#include "scheduler.h" +#include "connection.h" +#include "tools.h" +#include "server.h" +#include "protocolgame.h" +#include "protocolold.h" +#include "admin.h" +#include "status.h" +#include "configmanager.h" + +#include + +bool Connection::m_logError = true; + +extern ConfigManager g_config; + +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ +uint32_t Connection::connectionCount = 0; +#endif + +Connection_ptr ConnectionManager::createConnection(boost::asio::ip::tcp::socket* socket, + boost::asio::io_service& io_service, ServicePort_ptr servicer) +{ + boost::recursive_mutex::scoped_lock lockClass(m_connectionManagerLock); + Connection_ptr connection = boost::shared_ptr(new Connection(socket, io_service, servicer)); + m_connections.push_back(connection); + return connection; +} + +void ConnectionManager::releaseConnection(Connection_ptr connection) +{ + boost::recursive_mutex::scoped_lock lockClass(m_connectionManagerLock); + std::list::iterator it = + std::find(m_connections.begin(), m_connections.end(), connection); + + if (it != m_connections.end()) { + m_connections.erase(it); + } else { + std::cout << "Error: [ConnectionManager::releaseConnection] Connection not found" << std::endl; + } +} + +void ConnectionManager::closeAll() +{ + boost::recursive_mutex::scoped_lock lockClass(m_connectionManagerLock); + + for (std::list::iterator it = m_connections.begin(); it != m_connections.end(); ++it) { + try { + boost::system::error_code error; + (*it)->m_socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, error); + (*it)->m_socket->close(error); + } catch (boost::system::system_error&) { + } + } + + m_connections.clear(); +} + +//***************** + +void Connection::closeConnection() +{ + //any thread + boost::recursive_mutex::scoped_lock lockClass(m_connectionLock); + + if (m_connectionState == CONNECTION_STATE_CLOSED || m_connectionState == CONNECTION_STATE_REQUEST_CLOSE) { + return; + } + + m_connectionState = CONNECTION_STATE_REQUEST_CLOSE; + + g_dispatcher.addTask( + createTask(boost::bind(&Connection::closeConnectionTask, this))); +} + +void Connection::closeConnectionTask() +{ + //dispatcher thread + m_connectionLock.lock(); + + if (m_connectionState != CONNECTION_STATE_REQUEST_CLOSE) { + std::cout << "Error: [Connection::closeConnectionTask] m_connectionState = " << m_connectionState << std::endl; + m_connectionLock.unlock(); + return; + } + + if (m_protocol) { + m_protocol->setConnection(Connection_ptr()); + m_protocol->releaseProtocol(); + m_protocol = NULL; + } + + m_connectionState = CONNECTION_STATE_CLOSING; + + if (m_pendingWrite == 0 || m_writeError) { + closeSocket(); + releaseConnection(); + m_connectionState = CONNECTION_STATE_CLOSED; + } else { + //will be closed by onWriteOperation/handleWriteTimeout/handleReadTimeout instead + } + + m_connectionLock.unlock(); +} + +void Connection::closeSocket() +{ + m_connectionLock.lock(); + + if (m_socket->is_open()) { + m_pendingRead = 0; + m_pendingWrite = 0; + + try { + boost::system::error_code error; + m_socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, error); + m_socket->close(error); + } catch (boost::system::system_error& e) { + if (m_logError) { + LOG_MESSAGE("NETWORK", LOGTYPE_ERROR, 1, e.what()); + m_logError = false; + } + } + } + + m_connectionLock.unlock(); +} + +void Connection::releaseConnection() +{ + if (m_refCount > 0) { + //Reschedule it and try again. + g_scheduler.addEvent( createSchedulerTask(SCHEDULER_MINTICKS, + boost::bind(&Connection::releaseConnection, this))); + } else { + deleteConnectionTask(); + } +} + +void Connection::onStopOperation() +{ + //io_service thread + m_connectionLock.lock(); + m_readTimer.cancel(); + m_writeTimer.cancel(); + + try { + if (m_socket->is_open()) { + boost::system::error_code error; + m_socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, error); + m_socket->close(); + } + } catch (boost::system::system_error&) { + // + } + + delete m_socket; + m_socket = NULL; + + m_connectionLock.unlock(); + ConnectionManager::getInstance()->releaseConnection(shared_from_this()); +} + +void Connection::deleteConnectionTask() +{ + //dispather thread + assert(m_refCount == 0); + + try { + m_io_service.dispatch(boost::bind(&Connection::onStopOperation, this)); + } catch (boost::system::system_error& e) { + if (m_logError) { + LOG_MESSAGE("NETWORK", LOGTYPE_ERROR, 1, e.what()); + m_logError = false; + } + } +} + +void Connection::acceptConnection(Protocol* protocol) +{ + m_protocol = protocol; + m_protocol->onConnect(); + + acceptConnection(); +} + +void Connection::acceptConnection() +{ + try { + ++m_pendingRead; + m_readTimer.expires_from_now(boost::posix_time::seconds(Connection::read_timeout)); + m_readTimer.async_wait(boost::bind(&Connection::handleReadTimeout, boost::weak_ptr(shared_from_this()), boost::asio::placeholders::error)); + + // Read size of the first packet + boost::asio::async_read(getHandle(), + boost::asio::buffer(m_msg.getBuffer(), NetworkMessage::header_length), + boost::bind(&Connection::parseHeader, shared_from_this(), boost::asio::placeholders::error)); + } catch (boost::system::system_error& e) { + if (m_logError) { + LOG_MESSAGE("NETWORK", LOGTYPE_ERROR, 1, e.what()); + m_logError = false; + } + + closeConnection(); + } +} + +void Connection::parseHeader(const boost::system::error_code& error) +{ + m_connectionLock.lock(); + m_readTimer.cancel(); + + int32_t size = m_msg.decodeHeader(); + + if (error || size <= 0 || size >= NETWORKMESSAGE_MAXSIZE - 16) { + handleReadError(error); + } + + if (m_connectionState != CONNECTION_STATE_OPEN || m_readError) { + closeConnection(); + m_connectionLock.unlock(); + return; + } + + --m_pendingRead; + + try { + ++m_pendingRead; + m_readTimer.expires_from_now(boost::posix_time::seconds(Connection::read_timeout)); + m_readTimer.async_wait( boost::bind(&Connection::handleReadTimeout, boost::weak_ptr(shared_from_this()), + boost::asio::placeholders::error)); + + // Read packet content + m_msg.setMessageLength(size + NetworkMessage::header_length); + boost::asio::async_read(getHandle(), boost::asio::buffer(m_msg.getBodyBuffer(), size), + boost::bind(&Connection::parsePacket, shared_from_this(), boost::asio::placeholders::error)); + } catch (boost::system::system_error& e) { + if (m_logError) { + LOG_MESSAGE("NETWORK", LOGTYPE_ERROR, 1, e.what()); + m_logError = false; + } + + closeConnection(); + } + + m_connectionLock.unlock(); +} + +void Connection::parsePacket(const boost::system::error_code& error) +{ + m_connectionLock.lock(); + m_readTimer.cancel(); + + if (error) { + handleReadError(error); + } + + if (m_connectionState != CONNECTION_STATE_OPEN || m_readError) { + closeConnection(); + m_connectionLock.unlock(); + return; + } + + uint32_t timePassed = std::max(1, (time(NULL) - m_timeConnected) + 1); + + if ((++m_packetsSent / timePassed) > (uint32_t)g_config.getNumber(ConfigManager::MAX_PACKETS_PER_SECOND)) { + std::cout << convertIPToString(getIP()) << " disconnected for exceeding packet per second limit." << std::endl; + closeConnection(); + m_connectionLock.unlock(); + return; + } + + if (timePassed > 2) { + m_timeConnected = time(NULL); + m_packetsSent = 0; + } + + --m_pendingRead; + + //Check packet checksum + uint32_t recvChecksum = m_msg.PeekU32(); + uint32_t checksum = 0; + int32_t len = m_msg.getMessageLength() - m_msg.getReadPos() - 4; + + if (len > 0) { + checksum = adlerChecksum((uint8_t*)(m_msg.getBuffer() + m_msg.getReadPos() + 4), len); + } + + if (recvChecksum == checksum) { + m_msg.GetU32(); // remove the checksum + } + + if (!m_receivedFirst) { + // First message received + m_receivedFirst = true; + + if (!m_protocol) { + // Game protocol has already been created at this point + m_protocol = m_service_port->make_protocol(recvChecksum == checksum, m_msg); + + if (!m_protocol) { + closeConnection(); + m_connectionLock.unlock(); + return; + } + + m_protocol->setConnection(shared_from_this()); + } else { + m_msg.GetByte(); // Skip protocol ID + } + + m_protocol->onRecvFirstMessage(m_msg); + } else { + m_protocol->onRecvMessage(m_msg); // Send the packet to the current protocol + } + + try { + ++m_pendingRead; + m_readTimer.expires_from_now(boost::posix_time::seconds(Connection::read_timeout)); + m_readTimer.async_wait( boost::bind(&Connection::handleReadTimeout, boost::weak_ptr(shared_from_this()), + boost::asio::placeholders::error)); + + // Wait to the next packet + boost::asio::async_read(getHandle(), + boost::asio::buffer(m_msg.getBuffer(), NetworkMessage::header_length), + boost::bind(&Connection::parseHeader, shared_from_this(), boost::asio::placeholders::error)); + } catch (boost::system::system_error& e) { + if (m_logError) { + LOG_MESSAGE("NETWORK", LOGTYPE_ERROR, 1, e.what()); + m_logError = false; + } + + closeConnection(); + } + + m_connectionLock.unlock(); +} + +bool Connection::send(OutputMessage_ptr msg) +{ + m_connectionLock.lock(); + + if (m_connectionState != CONNECTION_STATE_OPEN || m_writeError) { + m_connectionLock.unlock(); + return false; + } + + if (m_pendingWrite == 0) { + msg->getProtocol()->onSendMessage(msg); + internalSend(msg); + } else { + OutputMessagePool* outputPool = OutputMessagePool::getInstance(); + outputPool->addToAutoSend(msg); + } + + m_connectionLock.unlock(); + return true; +} + +void Connection::internalSend(OutputMessage_ptr msg) +{ + try { + ++m_pendingWrite; + m_writeTimer.expires_from_now(boost::posix_time::seconds(Connection::write_timeout)); + m_writeTimer.async_wait( boost::bind(&Connection::handleWriteTimeout, boost::weak_ptr(shared_from_this()), + boost::asio::placeholders::error)); + + boost::asio::async_write(getHandle(), + boost::asio::buffer(msg->getOutputBuffer(), msg->getMessageLength()), + boost::bind(&Connection::onWriteOperation, shared_from_this(), msg, boost::asio::placeholders::error)); + } catch (boost::system::system_error& e) { + if (m_logError) { + LOG_MESSAGE("NETWORK", LOGTYPE_ERROR, 1, e.what()); + m_logError = false; + } + } +} + +uint32_t Connection::getIP() const +{ + //Ip is expressed in network byte order + boost::system::error_code error; + const boost::asio::ip::tcp::endpoint endpoint = m_socket->remote_endpoint(error); + + if (error) { + return 0; + } + + return htonl(endpoint.address().to_v4().to_ulong()); +} + +void Connection::onWriteOperation(OutputMessage_ptr msg, const boost::system::error_code& error) +{ + m_connectionLock.lock(); + m_writeTimer.cancel(); + + msg.reset(); + + if (error) { + handleWriteError(error); + } + + if (m_connectionState != CONNECTION_STATE_OPEN || m_writeError) { + closeSocket(); + closeConnection(); + m_connectionLock.unlock(); + return; + } + + --m_pendingWrite; + m_connectionLock.unlock(); +} + +void Connection::handleReadError(const boost::system::error_code& error) +{ + boost::recursive_mutex::scoped_lock lockClass(m_connectionLock); + + if (error == boost::asio::error::operation_aborted) { + //Operation aborted because connection will be closed + //Do NOT call closeConnection() from here + } else if (error == boost::asio::error::eof) { + //No more to read + closeConnection(); + } else if (error == boost::asio::error::connection_reset || + error == boost::asio::error::connection_aborted) { + //Connection closed remotely + closeConnection(); + } else { + closeConnection(); + } + + m_readError = true; +} + +void Connection::onReadTimeout() +{ + boost::recursive_mutex::scoped_lock lockClass(m_connectionLock); + + if (m_pendingRead > 0 || m_readError) { + closeSocket(); + closeConnection(); + } +} + +void Connection::onWriteTimeout() +{ + boost::recursive_mutex::scoped_lock lockClass(m_connectionLock); + + if (m_pendingWrite > 0 || m_writeError) { + closeSocket(); + closeConnection(); + } +} + +void Connection::handleReadTimeout(boost::weak_ptr weak_conn, const boost::system::error_code& error) +{ + if (error == boost::asio::error::operation_aborted) { + return; + } + + if (weak_conn.expired()) { + return; + } + + if (shared_ptr connection = weak_conn.lock()) { + connection->onReadTimeout(); + } +} + +void Connection::handleWriteError(const boost::system::error_code& error) +{ + boost::recursive_mutex::scoped_lock lockClass(m_connectionLock); + + if (error == boost::asio::error::operation_aborted) { + //Operation aborted because connection will be closed + //Do NOT call closeConnection() from here + } else if (error == boost::asio::error::eof) { + //No more to read + closeConnection(); + } else if (error == boost::asio::error::connection_reset || + error == boost::asio::error::connection_aborted) { + //Connection closed remotely + closeConnection(); + } else { + closeConnection(); + } + + m_writeError = true; +} + +void Connection::handleWriteTimeout(boost::weak_ptr weak_conn, const boost::system::error_code& error) +{ + if (error == boost::asio::error::operation_aborted) { + return; + } + + if (weak_conn.expired()) { + return; + } + + if (shared_ptr connection = weak_conn.lock()) { + connection->onWriteTimeout(); + } +} diff --git a/src/connection.h b/src/connection.h new file mode 100644 index 0000000000..23b701782b --- /dev/null +++ b/src/connection.h @@ -0,0 +1,182 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_CONNECTION_H__ +#define __OTSERV_CONNECTION_H__ + +#include "definitions.h" +#include + +#include +#include +#include +#include + +#include "networkmessage.h" + +class Protocol; +class OutputMessage; +typedef boost::shared_ptr OutputMessage_ptr; +class Connection; +typedef boost::shared_ptr Connection_ptr; +class ServiceBase; +typedef boost::shared_ptr Service_ptr; +class ServicePort; +typedef boost::shared_ptr ServicePort_ptr; + +class ConnectionManager +{ + public: + ~ConnectionManager() { + closeAll(); + } + + static ConnectionManager* getInstance() { + static ConnectionManager instance; + return &instance; + } + + Connection_ptr createConnection(boost::asio::ip::tcp::socket* socket, + boost::asio::io_service& io_service, ServicePort_ptr servicers); + void releaseConnection(Connection_ptr connection); + void closeAll(); + + protected: + ConnectionManager() { + } + + std::list m_connections; + boost::recursive_mutex m_connectionManagerLock; +}; + +class Connection : public boost::enable_shared_from_this, boost::noncopyable +{ + public: +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + static uint32_t connectionCount; +#endif + + enum { write_timeout = 30 }; + enum { read_timeout = 30 }; + + enum ConnectionState_t { + CONNECTION_STATE_OPEN = 0, + CONNECTION_STATE_REQUEST_CLOSE = 1, + CONNECTION_STATE_CLOSING = 2, + CONNECTION_STATE_CLOSED = 3 + }; + + private: + Connection(boost::asio::ip::tcp::socket* socket, + boost::asio::io_service& io_service, + ServicePort_ptr service_port) : + m_socket(socket), + m_readTimer(io_service), + m_writeTimer(io_service), + m_io_service(io_service), + m_service_port(service_port) { + m_refCount = 0; + m_protocol = NULL; + m_pendingWrite = 0; + m_pendingRead = 0; + m_connectionState = CONNECTION_STATE_OPEN; + m_receivedFirst = false; + m_writeError = false; + m_readError = false; + m_packetsSent = 0; + m_timeConnected = time(NULL); + +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + connectionCount++; +#endif + } + friend class ConnectionManager; + + public: + ~Connection() { +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + connectionCount--; +#endif + } + + boost::asio::ip::tcp::socket& getHandle() { + return *m_socket; + } + + void closeConnection(); + // Used by protocols that require server to send first + void acceptConnection(Protocol* protocol); + void acceptConnection(); + + bool send(OutputMessage_ptr msg); + + uint32_t getIP() const; + + int32_t addRef() { + return ++m_refCount; + } + int32_t unRef() { + return --m_refCount; + } + + private: + void parseHeader(const boost::system::error_code& error); + void parsePacket(const boost::system::error_code& error); + + void onWriteOperation(OutputMessage_ptr msg, const boost::system::error_code& error); + + void onStopOperation(); + void handleReadError(const boost::system::error_code& error); + void handleWriteError(const boost::system::error_code& error); + + static void handleReadTimeout(boost::weak_ptr weak_conn, const boost::system::error_code& error); + static void handleWriteTimeout(boost::weak_ptr weak_conn, const boost::system::error_code& error); + + void closeConnectionTask(); + void deleteConnectionTask(); + void releaseConnection(); + void closeSocket(); + void onReadTimeout(); + void onWriteTimeout(); + + void internalSend(OutputMessage_ptr msg); + + NetworkMessage m_msg; + boost::asio::ip::tcp::socket* m_socket; + boost::asio::deadline_timer m_readTimer; + boost::asio::deadline_timer m_writeTimer; + boost::asio::io_service& m_io_service; + ServicePort_ptr m_service_port; + bool m_receivedFirst; + bool m_writeError; + bool m_readError; + + int32_t m_pendingWrite; + int32_t m_pendingRead; + ConnectionState_t m_connectionState; + uint32_t m_refCount; + static bool m_logError; + boost::recursive_mutex m_connectionLock; + + time_t m_timeConnected; + uint32_t m_packetsSent; + + Protocol* m_protocol; +}; + +#endif diff --git a/src/const.h b/src/const.h new file mode 100644 index 0000000000..17465f8b97 --- /dev/null +++ b/src/const.h @@ -0,0 +1,626 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_CONST_H__ +#define __OTSERV_CONST_H__ + +#include "definitions.h" + +#define NETWORKMESSAGE_MAXSIZE 24590 + +enum MagicEffectClasses { + NM_ME_FIRST = 0x00, + NM_ME_DRAW_BLOOD = NM_ME_FIRST, + NM_ME_LOSE_ENERGY = 0x01, + NM_ME_POFF = 0x02, + NM_ME_BLOCKHIT = 0x03, + NM_ME_EXPLOSION_AREA = 0x04, + NM_ME_EXPLOSION_DAMAGE = 0x05, + NM_ME_FIRE_AREA = 0x06, + NM_ME_YELLOW_RINGS = 0x07, + NM_ME_POISON_RINGS = 0x08, + NM_ME_HIT_AREA = 0x09, + NM_ME_TELEPORT = 0x0A, //10 + NM_ME_ENERGY_DAMAGE = 0x0B, //11 + NM_ME_MAGIC_ENERGY = 0x0C, //12 + NM_ME_MAGIC_BLOOD = 0x0D, //13 + NM_ME_MAGIC_POISON = 0x0E, //14 + NM_ME_HITBY_FIRE = 0x0F, //15 + NM_ME_POISON = 0x10, //16 + NM_ME_MORT_AREA = 0x11, //17 + NM_ME_SOUND_GREEN = 0x12, //18 + NM_ME_SOUND_RED = 0x13, //19 + NM_ME_POISON_AREA = 0x14, //20 + NM_ME_SOUND_YELLOW = 0x15, //21 + NM_ME_SOUND_PURPLE = 0x16, //22 + NM_ME_SOUND_BLUE = 0x17, //23 + NM_ME_SOUND_WHITE = 0x18, //24 + NM_ME_BUBBLES = 0x19, //25 + NM_ME_CRAPS = 0x1A, //26 + NM_ME_GIFT_WRAPS = 0x1B, //27 + NM_ME_FIREWORK_YELLOW = 0x1C, //28 + NM_ME_FIREWORK_RED = 0x1D, //29 + NM_ME_FIREWORK_BLUE = 0x1E, //30 + NM_ME_STUN = 0x1F, //31 + NM_ME_SLEEP = 0x20, //32 + NM_ME_WATERCREATURE = 0x21, //33 + NM_ME_GROUNDSHAKER = 0x22, //34 + NM_ME_HEARTS = 0x23, //35 + NM_ME_FIREATTACK = 0x24, //36 + NM_ME_ENERGY_AREA = 0x25, //37 + NM_ME_SMALLCLOUDS = 0x26, //38 + NM_ME_HOLYDAMAGE = 0x27, //39 + NM_ME_BIGCLOUDS = 0x28, //40 + NM_ME_ICEAREA = 0x29, //41 + NM_ME_ICETORNADO = 0x2A, //42 + NM_ME_ICEATTACK = 0x2B, //43 + NM_ME_STONES = 0x2C, //44 + NM_ME_SMALLPLANTS = 0x2D, //45 + NM_ME_CARNIPHILA = 0x2E, //46 + NM_ME_PURPLEENERGY = 0x2F, //47 + NM_ME_YELLOWENERGY = 0x30, //48 + NM_ME_HOLYAREA = 0x31, //49 + NM_ME_BIGPLANTS = 0x32, //50 + NM_ME_CAKE = 0x33, //51 + NM_ME_GIANTICE = 0x34, //52 + NM_ME_WATERSPLASH = 0x35, //53 + NM_ME_PLANTATTACK = 0x36, //54 + NM_ME_TUTORIALARROW = 0x37, //55 + NM_ME_TUTORIALSQUARE = 0x38, //56 + NM_ME_MIRRORHORIZONTAL = 0x39, //57 + NM_ME_MIRRORVERTICAL = 0x3A, //58 + NM_ME_SKULLHORIZONTAL = 0x3B, //59 + NM_ME_SKULLVERTICAL = 0x3C, //60 + NM_ME_ASSASSIN = 0x3D, //61 + NM_ME_STEPSHORIZONTAL = 0x3E, //62 + NM_ME_BLOODYSTEPS = 0x3F, //63 + NM_ME_STEPSVERTICAL = 0x40, //64 + NM_ME_YALAHARIGHOST = 0x41, //65 + NM_ME_BATS = 0x42, //66 + NM_ME_SMOKE = 0x43, //67 + NM_ME_INSECTS = 0x44, //68 + NM_ME_DRAGONHEAD = 0x45, //69 + NM_ME_ORCSHAMAN = 0x46, //70 + NM_ME_ORCSHAMAN_FIRE = 0x47, //71 + NM_ME_THUNDER = 0x48, //72 + NM_ME_FERUMBRAS = 0x49, //73 + NM_ME_CONFETTI_HORIZONTAL = 0x4A, //74 + NM_ME_CONFETTI_VERTICAL = 0x4B, //75 + // 76-156 are empty + NM_ME_BLACKSMOKE = 0x9D, //157 + NM_ME_LAST = NM_ME_BLACKSMOKE, + + //for internal use, dont send to client + NM_ME_NONE = 0xFF, + NM_ME_UNK = 0xFFFF +}; + +enum ShootType_t { + NM_SHOOT_FIRST = 0x00, + NM_SHOOT_SPEAR = NM_SHOOT_FIRST, + NM_SHOOT_BOLT = 0x01, + NM_SHOOT_ARROW = 0x02, + NM_SHOOT_FIRE = 0x03, + NM_SHOOT_ENERGY = 0x04, + NM_SHOOT_POISONARROW = 0x05, + NM_SHOOT_BURSTARROW = 0x06, + NM_SHOOT_THROWINGSTAR = 0x07, + NM_SHOOT_THROWINGKNIFE = 0x08, + NM_SHOOT_SMALLSTONE = 0x09, + NM_SHOOT_DEATH = 0x0A, //10 + NM_SHOOT_LARGEROCK = 0x0B, //11 + NM_SHOOT_SNOWBALL = 0x0C, //12 + NM_SHOOT_POWERBOLT = 0x0D, //13 + NM_SHOOT_POISONFIELD = 0x0E, //14 + NM_SHOOT_INFERNALBOLT = 0x0F, //15 + NM_SHOOT_HUNTINGSPEAR = 0x10, //16 + NM_SHOOT_ENCHANTEDSPEAR = 0x11, //17 + NM_SHOOT_REDSTAR = 0x12, //18 + NM_SHOOT_GREENSTAR = 0x13, //19 + NM_SHOOT_ROYALSPEAR = 0x14, //20 + NM_SHOOT_SNIPERARROW = 0x15, //21 + NM_SHOOT_ONYXARROW = 0x16, //22 + NM_SHOOT_PIERCINGBOLT = 0x17, //23 + NM_SHOOT_WHIRLWINDSWORD = 0x18, //24 + NM_SHOOT_WHIRLWINDAXE = 0x19, //25 + NM_SHOOT_WHIRLWINDCLUB = 0x1A, //26 + NM_SHOOT_ETHEREALSPEAR = 0x1B, //27 + NM_SHOOT_ICE = 0x1C, //28 + NM_SHOOT_EARTH = 0x1D, //29 + NM_SHOOT_HOLY = 0x1E, //30 + NM_SHOOT_SUDDENDEATH = 0x1F, //31 + NM_SHOOT_FLASHARROW = 0x20, //32 + NM_SHOOT_FLAMMINGARROW = 0x21, //33 + NM_SHOOT_SHIVERARROW = 0x22, //34 + NM_SHOOT_ENERGYBALL = 0x23, //35 + NM_SHOOT_SMALLICE = 0x24, //36 + NM_SHOOT_SMALLHOLY = 0x25, //37 + NM_SHOOT_SMALLEARTH = 0x26, //38 + NM_SHOOT_EARTHARROW = 0x27, //39 + NM_SHOOT_EXPLOSION = 0x28, //40 + NM_SHOOT_CAKE = 0x29, //41 + NM_SHOOT_UNK1 = 0x2A, //42 DEBUG! + NM_SHOOT_TARSALARROW = 0x2B, //43 + NM_SHOOT_VORTEXBOLT = 0x2C, //44 + NM_SHOOT_UNK2 = 0x2D, //45 DEBUG! + NM_SHOOT_UNK3 = 0x2E, //46 DEBUG! + NM_SHOOT_PRISMATICBOLT = 0x2F, //47 + NM_SHOOT_CRYSTALLINEARROW = 0x30, //48 + NM_SHOOT_DRILLBOLT = 0x31, //49 + NM_SHOOT_ENVENOMEDARROW = 0x32, //50 + NM_SHOOT_LAST = NM_SHOOT_ENVENOMEDARROW, + + //for internal use, dont send to client + NM_SHOOT_WEAPONTYPE = 0xFE, //254 + NM_SHOOT_NONE = 0xFF, + NM_SHOOT_UNK = 0xFFFF +}; + +enum SpeakClasses { + SPEAK_SAY = 0x01, + SPEAK_WHISPER = 0x02, + SPEAK_YELL = 0x03, + SPEAK_PRIVATE_FROM = 0x04, + SPEAK_PRIVATE_TO = 0x05, + SPEAK_CHANNEL_Y = 0x07, + SPEAK_CHANNEL_O = 0x08, + SPEAK_PRIVATE_NP = 0x0A, + SPEAK_PRIVATE_PN = 0x0B, + SPEAK_BROADCAST = 0x0C, + SPEAK_CHANNEL_R1 = 0x0D, //red - #c text + SPEAK_PRIVATE_RED_FROM = 0x0E, //@name@text + SPEAK_PRIVATE_RED_TO = 0x0F, //@name@text + SPEAK_MONSTER_SAY = 0x22, + SPEAK_MONSTER_YELL = 0x23, + + SPEAK_CHANNEL_R2 = 0xFF + 4, //#d + SPEAK_CHANNEL_W = 0xFF + 5 +}; + +enum MessageClasses { + MSG_STATUS_CONSOLE_BLUE = 0x04, /*FIXME Blue message in the console*/ + MSG_STATUS_CONSOLE_RED = 0x0C, /*Red message in the console*/ + MSG_STATUS_DEFAULT = 0x10, /*White message at the bottom of the game window and in the console*/ + MSG_STATUS_WARNING = 0x11, /*Red message in game window and in the console*/ + MSG_EVENT_ADVANCE = 0x12, /*White message in game window and in the console*/ + MSG_STATUS_SMALL = 0x13, /*White message at the bottom of the game window"*/ + MSG_INFO_DESCR = 0x14, /*Green message in game window and in the console*/ + MSG_DAMAGE_DEALT = 0x15, + MSG_DAMAGE_RECEIVED = 0x16, + MSG_HEALED = 0x17, + MSG_EXPERIENCE = 0x18, + MSG_DAMAGE_OTHERS = 0x19, + MSG_HEALED_OTHERS = 0x1A, + MSG_EXPERIENCE_OTHERS = 0x1B, + MSG_EVENT_DEFAULT = 0x1C, /*White message at the bottom of the game window and in the console*/ + MSG_LOOT = 0x1D, + MSG_EVENT_ORANGE = 0x22, /*Orange message in the console*/ + MSG_STATUS_CONSOLE_ORANGE = 0x23 /*Orange message in the console*/ +}; + +enum FluidColors_t { + FLUID_EMPTY = 0x00, + FLUID_BLUE = 0x01, + FLUID_RED = 0x02, + FLUID_BROWN = 0x03, + FLUID_GREEN = 0x04, + FLUID_YELLOW = 0x05, + FLUID_WHITE = 0x06, + FLUID_PURPLE = 0x07 +}; + +enum FluidTypes_t { + FLUID_NONE = FLUID_EMPTY, + FLUID_WATER = FLUID_BLUE, + FLUID_BLOOD = FLUID_RED, + FLUID_BEER = FLUID_BROWN, + FLUID_SLIME = FLUID_GREEN, + FLUID_LEMONADE = FLUID_YELLOW, + FLUID_MILK = FLUID_WHITE, + FLUID_MANA = FLUID_PURPLE, + + FLUID_LIFE = FLUID_RED + 8, + FLUID_OIL = FLUID_BROWN + 8, + FLUID_URINE = FLUID_YELLOW + 8, + FLUID_COCONUTMILK = FLUID_WHITE + 8, + FLUID_WINE = FLUID_PURPLE + 8, + + FLUID_MUD = FLUID_BROWN + 16, + FLUID_FRUITJUICE = FLUID_YELLOW + 16, + + FLUID_LAVA = FLUID_RED + 24, + FLUID_RUM = FLUID_BROWN + 24, + FLUID_SWAMP = FLUID_GREEN + 24, + + FLUID_TEA = FLUID_BROWN + 32, + FLUID_MEAD = FLUID_BROWN + 40 +}; + +const uint8_t reverseFluidMap[] = { + FLUID_EMPTY, + FLUID_WATER, + FLUID_MANA, + FLUID_BEER, + FLUID_EMPTY, + FLUID_BLOOD, + FLUID_SLIME, + FLUID_EMPTY, + FLUID_LEMONADE, + FLUID_MILK +}; + +const uint8_t clientToServerFluidMap[] = { + FLUID_EMPTY, + FLUID_WATER, + FLUID_MANA, + FLUID_BEER, + FLUID_MUD, + FLUID_BLOOD, + FLUID_SLIME, + FLUID_RUM, + FLUID_LEMONADE, + FLUID_MILK, + FLUID_WINE, + FLUID_LIFE, + FLUID_URINE, + FLUID_OIL, + FLUID_FRUITJUICE, + FLUID_COCONUTMILK, + FLUID_TEA, + FLUID_MEAD +}; + +enum ClientFluidTypes_t { + CLIENTFLUID_EMPTY = 0x00, + CLIENTFLUID_BLUE = 0x01, + CLIENTFLUID_PURPLE = 0x02, + CLIENTFLUID_BROWN_1 = 0x03, + CLIENTFLUID_BROWN_2 = 0x04, + CLIENTFLUID_RED = 0x05, + CLIENTFLUID_GREEN = 0x06, + CLIENTFLUID_BROWN = 0x07, + CLIENTFLUID_YELLOW = 0x08, + CLIENTFLUID_WHITE = 0x09 +}; + +const uint8_t fluidMap[] = { + CLIENTFLUID_EMPTY, + CLIENTFLUID_BLUE, + CLIENTFLUID_RED, + CLIENTFLUID_BROWN_1, + CLIENTFLUID_GREEN, + CLIENTFLUID_YELLOW, + CLIENTFLUID_WHITE, + CLIENTFLUID_PURPLE +}; + +enum SquareColor_t { + SQ_COLOR_NONE = 256, + SQ_COLOR_BLACK = 0 +}; + +enum TextColor_t { + TEXTCOLOR_BLUE = 5, + TEXTCOLOR_GREEN = 18, + TEXTCOLOR_LIGHTGREEN = 30, + TEXTCOLOR_LIGHTBLUE = 35, + TEXTCOLOR_TEAL = 65, + TEXTCOLOR_MAYABLUE = 95, + TEXTCOLOR_DARKRED = 108, + TEXTCOLOR_LIGHTGREY = 129, + TEXTCOLOR_SKYBLUE = 143, + TEXTCOLOR_PURPLE = 154, + TEXTCOLOR_RED = 180, + TEXTCOLOR_ORANGE = 198, + TEXTCOLOR_YELLOW = 210, + TEXTCOLOR_WHITE_EXP = 215, + TEXTCOLOR_NONE = 255 +}; + +enum Icons_t { + ICON_POISON = 1, + ICON_BURN = 2, + ICON_ENERGY = 4, + ICON_DRUNK = 8, + ICON_MANASHIELD = 16, + ICON_PARALYZE = 32, + ICON_HASTE = 64, + ICON_SWORDS = 128, + ICON_DROWNING = 256, + ICON_FREEZING = 512, + ICON_DAZZLED = 1024, + ICON_CURSED = 2048, + ICON_PARTY_BUFF = 4096, + ICON_REDSWORDS = 8192, + ICON_PIGEON = 16384, + ICON_BLEEDING = 32768, + ICON_HUNGRY = 65536 +}; + +enum WeaponType_t { + WEAPON_NONE = 0, + WEAPON_SWORD = 1, + WEAPON_CLUB = 2, + WEAPON_AXE = 3, + WEAPON_SHIELD = 4, + WEAPON_DIST = 5, + WEAPON_WAND = 6, + WEAPON_AMMO = 7 +}; + +enum Ammo_t { + AMMO_NONE = 0, + AMMO_BOLT = 1, + AMMO_ARROW = 2, + AMMO_SPEAR = 3, + AMMO_THROWINGSTAR = 4, + AMMO_THROWINGKNIFE = 5, + AMMO_STONE = 6, + AMMO_SNOWBALL = 7 +}; + +enum AmmoAction_t { + AMMOACTION_NONE, + AMMOACTION_REMOVECOUNT, + AMMOACTION_REMOVECHARGE, + AMMOACTION_MOVE, + AMMOACTION_MOVEBACK +}; + +enum WieldInfo_t { + WIELDINFO_LEVEL = 1, + WIELDINFO_MAGLV = 2, + WIELDINFO_VOCREQ = 4, + WIELDINFO_PREMIUM = 8 +}; + +enum Skulls_t { + SKULL_NONE = 0, + SKULL_YELLOW = 1, + SKULL_GREEN = 2, + SKULL_WHITE = 3, + SKULL_RED = 4, + SKULL_BLACK = 5, + SKULL_ORANGE = 6, + SKULL_LAST = SKULL_ORANGE +}; + +enum PartyShields_t { + SHIELD_NONE = 0, + SHIELD_WHITEYELLOW = 1, + SHIELD_WHITEBLUE = 2, + SHIELD_BLUE = 3, + SHIELD_YELLOW = 4, + SHIELD_BLUE_SHAREDEXP = 5, + SHIELD_YELLOW_SHAREDEXP = 6, + SHIELD_BLUE_NOSHAREDEXP_BLINK = 7, + SHIELD_YELLOW_NOSHAREDEXP_BLINK = 8, + SHIELD_BLUE_NOSHAREDEXP = 9, + SHIELD_YELLOW_NOSHAREDEXP = 10, + SHIELD_GRAY = 11 +}; + +enum GuildEmblems_t { + GUILDEMBLEM_NONE = 0, + GUILDEMBLEM_ALLY = 1, + GUILDEMBLEM_ENEMY = 2, + GUILDEMBLEM_NEUTRAL = 3, + GUILDEMBLEM_MEMBER = 4, + GUILDEMBLEM_OTHER = 5 +}; + +enum item_t { + ITEM_FIREFIELD_PVP_FULL = 1487, + ITEM_FIREFIELD_PVP_MEDIUM = 1488, + ITEM_FIREFIELD_PVP_SMALL = 1489, + ITEM_FIREFIELD_PERSISTENT_FULL = 1492, + ITEM_FIREFIELD_PERSISTENT_MEDIUM = 1493, + ITEM_FIREFIELD_PERSISTENT_SMALL = 1494, + ITEM_FIREFIELD_NOPVP = 1500, + + ITEM_POISONFIELD_PVP = 1490, + ITEM_POISONFIELD_PERSISTENT = 1496, + ITEM_POISONFIELD_NOPVP = 1503, + + ITEM_ENERGYFIELD_PVP = 1491, + ITEM_ENERGYFIELD_PERSISTENT = 1495, + ITEM_ENERGYFIELD_NOPVP = 1504, + + ITEM_MAGICWALL = 1497, + ITEM_MAGICWALL_PERSISTENT = 1498, + ITEM_MAGICWALL_SAFE = 11098, + + ITEM_WILDGROWTH = 1499, + ITEM_WILDGROWTH_PERSISTENT = 2721, + ITEM_WILDGROWTH_SAFE = 11099, + + ITEM_BAG = 1987, + + ITEM_COINS_GOLD = 2148, + ITEM_COINS_PLATINUM = 2152, + ITEM_COINS_CRYSTAL = 2160, + + ITEM_DEPOT = 2594, + ITEM_LOCKER1 = 2589, + ITEM_INBOX = 14404, + ITEM_MARKET = 14405, + + ITEM_MALE_CORPSE = 3058, + ITEM_FEMALE_CORPSE = 3065, + + ITEM_MEAT = 2666, + ITEM_HAM = 2671, + ITEM_GRAPE = 2681, + ITEM_APPLE = 2674, + ITEM_BREAD = 2689, + ITEM_ROLL = 2690, + ITEM_CHEESE = 2696, + + ITEM_FULLSPLASH = 2016, + ITEM_SMALLSPLASH = 2019, + + ITEM_PARCEL = 2595, + ITEM_PARCEL_STAMPED = 2596, + ITEM_LETTER = 2597, + ITEM_LETTER_STAMPED = 2598, + ITEM_LABEL = 2599, + + ITEM_AMULETOFLOSS = 2173, + + ITEM_DOCUMENT_RO = 1968 //read-only +}; + +enum PlayerBlessings { + PlayerBlessing_First = 0, + PlayerBlessing_Second, + PlayerBlessing_Third, + PlayerBlessing_Fourth, + PlayerBlessing_Fifth +}; + +enum PlayerFlags { + PlayerFlag_CannotUseCombat = 0, //2^0 = 1 + PlayerFlag_CannotAttackPlayer, //2^1 = 2 + PlayerFlag_CannotAttackMonster, //2^2 = 4 + PlayerFlag_CannotBeAttacked, //2^3 = 8 + PlayerFlag_CanConvinceAll, //2^4 = 16 + PlayerFlag_CanSummonAll, //2^5 = 32 + PlayerFlag_CanIllusionAll, //2^6 = 64 + PlayerFlag_CanSenseInvisibility, //2^7 = 128 + PlayerFlag_IgnoredByMonsters, //2^8 = 256 + PlayerFlag_NotGainInFight, //2^9 = 512 + PlayerFlag_HasInfiniteMana, //2^10 = 1024 + PlayerFlag_HasInfiniteSoul, //2^11 = 2048 + PlayerFlag_HasNoExhaustion, //2^12 = 4096 + PlayerFlag_CannotUseSpells, //2^13 = 8192 + PlayerFlag_CannotPickupItem, //2^14 = 16384 + PlayerFlag_CanAlwaysLogin, //2^15 = 32768 + PlayerFlag_CanBroadcast, //2^16 = 65536 + PlayerFlag_CanEditHouses, //2^17 = 131072 + PlayerFlag_CannotBeBanned, //2^18 = 262144 + PlayerFlag_CannotBePushed, //2^19 = 524288 + PlayerFlag_HasInfiniteCapacity, //2^20 = 1048576 + PlayerFlag_CanPushAllCreatures, //2^21 = 2097152 + PlayerFlag_CanTalkRedPrivate, //2^22 = 4194304 + PlayerFlag_CanTalkRedChannel, //2^23 = 8388608 + PlayerFlag_TalkOrangeHelpChannel, //2^24 = 16777216 + PlayerFlag_NotGainExperience, //2^25 = 33554432 + PlayerFlag_NotGainMana, //2^26 = 67108864 + PlayerFlag_NotGainHealth, //2^27 = 134217728 + PlayerFlag_NotGainSkill, //2^28 = 268435456 + PlayerFlag_SetMaxSpeed, //2^29 = 536870912 + PlayerFlag_SpecialVIP, //2^30 = 1073741824 + PlayerFlag_NotGenerateLoot, //2^31 = 2147483648 + PlayerFlag_CanTalkRedChannelAnonymous, //2^32 = 4294967296 + PlayerFlag_IgnoreProtectionZone, //2^33 = 8589934592 + PlayerFlag_IgnoreSpellCheck, //2^34 = 17179869184 + PlayerFlag_IgnoreWeaponCheck, //2^35 = 34359738368 + PlayerFlag_CannotBeMuted, //2^36 = 68719476736 + PlayerFlag_IsAlwaysPremium, //2^37 = 137438953472 + + //add new flags here + PlayerFlag_LastFlag +}; + +enum ViolationActions_t { + Action_None = 0, + Action_Notation = 1, + Action_Namelock = 2, + Action_Banishment = 4, + Action_NamelockBan = 8, + Action_BanFinalWarning = 16, + Action_NamelockBanFinalWarning = 32, + Action_StatementReport = 64, + Action_IpBan = 128 +}; + +const int violationActions[6] = { + //ignore this + Action_None, + + //player + Action_None, + + //tutor + Action_None, + + //senior tutor + Action_None, + + //gamemaster + Action_Notation | Action_Namelock | Action_Banishment | Action_NamelockBan | Action_StatementReport, + + //god + Action_Notation | Action_Namelock | Action_Banishment | Action_NamelockBan | Action_BanFinalWarning | Action_NamelockBanFinalWarning | Action_StatementReport | Action_IpBan +}; + +const int violationReasons[6] = { + //ignore this + 0, + + //player + 0, + + //tutor + 0, + + /* + * senior tutor + * all name reasons + */ + 4, + + /* + * gamemaster + * all name, statement & cheating reasons + */ + 18, + + /* + * god + * all reasons + */ + 20, +}; + +#define CHANNEL_GUILD 0x00 +#define CHANNEL_PARTY 0x01 +#define CHANNEL_TUTOR 0x02 +#define CHANNEL_WORLDCHAT 0x03 +#define CHANNEL_ENGLISHCHAT 0x04 +#define CHANNEL_ADVERTISING 0x05 +#define CHANNEL_ADVERTISINGROOKGAARD 0x06 +#define CHANNEL_HELP 0x07 +#define CHANNEL_GAMEMASTER 0x08 +#define CHANNEL_PRIVATE 0xFFFF + +//Reserved player storage key ranges +//[10000000 - 20000000] +#define PSTRG_RESERVED_RANGE_START 10000000 +#define PSTRG_RESERVED_RANGE_SIZE 10000000 +//[1000 - 1500] +#define PSTRG_OUTFITS_RANGE_START (PSTRG_RESERVED_RANGE_START + 1000) +#define PSTRG_OUTFITS_RANGE_SIZE 500 +//[2001 - 2011] +#define PSTRG_MOUNTS_RANGE_START (PSTRG_RESERVED_RANGE_START + 2001) +#define PSTRG_MOUNTS_RANGE_SIZE 10 +#define PSTRG_MOUNTS_CURRENTMOUNT (PSTRG_MOUNTS_RANGE_START + 10) + +#define IS_IN_KEYRANGE(key, range) (key >= PSTRG_##range##_START && ((key - PSTRG_##range##_START) <= PSTRG_##range##_SIZE)) + +#endif diff --git a/src/container.cpp b/src/container.cpp new file mode 100644 index 0000000000..a95529e891 --- /dev/null +++ b/src/container.cpp @@ -0,0 +1,931 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "container.h" +#include "iomap.h" +#include "game.h" +#include "player.h" + +extern Game g_game; + +Container::Container(uint16_t _type) : Item(_type) +{ + maxSize = items[_type].maxItems; + totalWeight = 0.0; + serializationCount = 0; + unlocked = true; + pages = false; +} + +Container::Container(Tile* tile) : Item(460) +{ + TileItemVector* itemVector = tile->getItemList(); + + if (itemVector) { + for (ItemVector::reverse_iterator rit = itemVector->rbegin(), end = itemVector->rend(); rit != end; ++rit) { + Item* item = *rit; + + if (item->getContainer() || item->hasProperty(MOVEABLE)) { + addItem(item); + } + } + } + + maxSize = 30; + totalWeight = 0.0; + serializationCount = 0; + unlocked = false; + pages = true; + setParent(tile); +} + +Container::~Container() +{ + //std::cout << "Container destructor " << this << std::endl; + if (getID() == 460) { + g_game.browseFields.erase(getTile()); + + for (ItemDeque::const_iterator cit = itemlist.begin(); cit != itemlist.end(); ++cit) { + (*cit)->setParent(getParent()); + } + } else { + for (ItemDeque::const_iterator cit = itemlist.begin(); cit != itemlist.end(); ++cit) { + (*cit)->setParent(NULL); + (*cit)->releaseThing2(); + } + + itemlist.clear(); + } +} + +Item* Container::clone() const +{ + Container* _item = static_cast(Item::clone()); + for (ItemDeque::const_iterator it = itemlist.begin(); it != itemlist.end(); ++it) { + _item->addItem((*it)->clone()); + } + return _item; +} + +Container* Container::getParentContainer() +{ + if (Thing* thing = getParent()) { + if (Item* item = thing->getItem()) { + return item->getContainer(); + } + } + return NULL; +} + +bool Container::hasParent(uint16_t version) const +{ + if (version > 975) { + return getID() != 460 && dynamic_cast(getParent()) == NULL; + } else { + const Container* parentContainer = dynamic_cast(getParent()); + return parentContainer && parentContainer->getID() != 460; + } +} + +void Container::addItem(Item* item) +{ + itemlist.push_back(item); + item->setParent(this); +} + +Attr_ReadValue Container::readAttr(AttrTypes_t attr, PropStream& propStream) +{ + switch (attr) { + case ATTR_CONTAINER_ITEMS: { + uint32_t count; + + if (!propStream.GET_ULONG(count)) { + return ATTR_READ_ERROR; + } + + serializationCount = count; + return ATTR_READ_END; + } + + default: + break; + } + return Item::readAttr(attr, propStream); +} + +bool Container::unserializeItemNode(FileLoader& f, NODE node, PropStream& propStream) +{ + bool ret = Item::unserializeItemNode(f, node, propStream); + if (!ret) { + return false; + } + + uint32_t type; + NODE nodeItem = f.getChildNode(node, type); + while (nodeItem) { + //load container items + if (type != OTBM_ITEM) { + // unknown type + return false; + } + + PropStream itemPropStream; + f.getProps(nodeItem, itemPropStream); + + Item* item = Item::CreateItem(itemPropStream); + if (!item) { + return false; + } + + if (!item->unserializeItemNode(f, nodeItem, itemPropStream)) { + return false; + } + + addItem(item); + totalWeight += item->getWeight(); + + if (Container* parent_container = getParentContainer()) { + parent_container->updateItemWeight(item->getWeight()); + } + + nodeItem = f.getNextNode(nodeItem, type); + } + return true; +} + +void Container::updateItemWeight(double diff) +{ + totalWeight += diff; + + if (Container* parentContainer = getParentContainer()) { + parentContainer->updateItemWeight(diff); + } +} + +double Container::getWeight() const +{ + return Item::getWeight() + totalWeight; +} + +std::string Container::getContentDescription() const +{ + std::ostringstream os; + return getContentDescription(os).str(); +} + +std::ostringstream& Container::getContentDescription(std::ostringstream& os) const +{ + bool firstitem = true; + Container* evil = const_cast(this); + + for (ContainerIterator cit = evil->begin(); cit != evil->end(); ++cit) { + Item* i = *cit; + Container* container = i->getContainer(); + + if (container && container->size() != 0) { + continue; + } + + if (firstitem) { + firstitem = false; + } else { + os << ", "; + } + + os << i->getNameDescription(); + } + + if (firstitem) { + os << "nothing"; + } + return os; +} + +Item* Container::getItem(uint32_t index) const +{ + if (index >= size()) { + return NULL; + } + return itemlist[index]; +} + +uint32_t Container::getItemHoldingCount() const +{ + uint32_t counter = 0; + for (ContainerIterator iter = begin(); iter != end(); ++iter) { + ++counter; + } + return counter; +} + +bool Container::isHoldingItem(const Item* item) const +{ + for (ContainerIterator cit = begin(); cit != end(); ++cit) { + if (*cit == item) { + return true; + } + } + return false; +} + +void Container::onAddContainerItem(Item* item) +{ + const Position& cylinderMapPos = getPosition(); + + SpectatorVec list; + g_game.getSpectators(list, cylinderMapPos, false, true, 2, 2, 2, 2); + + SpectatorVec::const_iterator end = list.end(); + + //send to client + for (SpectatorVec::const_iterator it = list.begin(); it != end; ++it) { + (*it)->getPlayer()->sendAddContainerItem(this, item); + } + + //event methods + for (SpectatorVec::const_iterator it = list.begin(); it != end; ++it) { + (*it)->getPlayer()->onAddContainerItem(this, item); + } +} + +void Container::onUpdateContainerItem(uint32_t index, Item* oldItem, const ItemType& oldType, + Item* newItem, const ItemType& newType) +{ + const Position& cylinderMapPos = getPosition(); + + SpectatorVec list; + g_game.getSpectators(list, cylinderMapPos, false, true, 2, 2, 2, 2); + + SpectatorVec::const_iterator end = list.end(); + + //send to client + for (SpectatorVec::const_iterator it = list.begin(); it != end; ++it) { + (*it)->getPlayer()->sendUpdateContainerItem(this, index, oldItem, newItem); + } + + //event methods + for (SpectatorVec::const_iterator it = list.begin(); it != end; ++it) { + (*it)->getPlayer()->onUpdateContainerItem(this, index, oldItem, oldType, newItem, newType); + } +} + +void Container::onRemoveContainerItem(uint32_t index, Item* item) +{ + const Position& cylinderMapPos = getPosition(); + + SpectatorVec list; + g_game.getSpectators(list, cylinderMapPos, false, true, 2, 2, 2, 2); + + SpectatorVec::const_iterator end = list.end(); + + //send change to client + Item* lastItem = getItem(maxSize); + + for (SpectatorVec::const_iterator it = list.begin(); it != end; ++it) { + (*it)->getPlayer()->sendRemoveContainerItem(this, index, lastItem); + } + + //event methods + for (SpectatorVec::const_iterator it = list.begin(); it != end; ++it) { + (*it)->getPlayer()->onRemoveContainerItem(this, index, item); + } +} + +ReturnValue Container::__queryAdd(int32_t index, const Thing* thing, uint32_t count, + uint32_t flags, Creature* actor/* = NULL*/) const +{ + bool childIsOwner = hasBitSet(FLAG_CHILDISOWNER, flags); + if (childIsOwner) { + //a child container is querying, since we are the top container (not carried by a player) + //just return with no error. + return RET_NOERROR; + } + + if (!unlocked) { + return RET_NOTPOSSIBLE; + } + + const Item* item = thing->getItem(); + if (item == NULL) { + return RET_NOTPOSSIBLE; + } + + if (!item->isPickupable()) { + return RET_CANNOTPICKUP; + } + + if (item == this) { + return RET_THISISIMPOSSIBLE; + } + + const Cylinder* cylinder = getParent(); + if (!hasBitSet(FLAG_NOLIMIT, flags)) { + while (cylinder) { + if (cylinder == thing) { + return RET_THISISIMPOSSIBLE; + } + + if (dynamic_cast(cylinder)) { + return RET_CONTAINERNOTENOUGHROOM; + } + + cylinder = cylinder->getParent(); + } + + if (index == INDEX_WHEREEVER && size() >= capacity()) { + return RET_CONTAINERNOTENOUGHROOM; + } + } else { + while (cylinder) { + if (cylinder == thing) { + return RET_THISISIMPOSSIBLE; + } + + cylinder = cylinder->getParent(); + } + } + + const Cylinder* topParent = getTopParent(); + if (topParent != this) { + return topParent->__queryAdd(INDEX_WHEREEVER, item, count, flags | FLAG_CHILDISOWNER, actor); + } else { + return RET_NOERROR; + } +} + +ReturnValue Container::__queryMaxCount(int32_t index, const Thing* thing, uint32_t count, + uint32_t& maxQueryCount, uint32_t flags) const +{ + const Item* item = thing->getItem(); + if (item == NULL) { + maxQueryCount = 0; + return RET_NOTPOSSIBLE; + } + + if (hasBitSet(FLAG_NOLIMIT, flags)) { + maxQueryCount = std::max(1, count); + return RET_NOERROR; + } + + int32_t freeSlots = std::max(capacity() - size(), 0); + + if (item->isStackable()) { + uint32_t n = 0; + + if (index == INDEX_WHEREEVER) { + //Iterate through every item and check how much free stackable slots there is. + uint32_t slotIndex = 0; + + for (ItemDeque::const_iterator cit = itemlist.begin(); cit != itemlist.end(); ++cit, ++slotIndex) { + if ((*cit) != item && (*cit)->getID() == item->getID() && (*cit)->getItemCount() < 100) { + uint32_t remainder = (100 - (*cit)->getItemCount()); + if (__queryAdd(slotIndex, item, remainder, flags) == RET_NOERROR) { + n += remainder; + } + } + } + } else { + const Item* destItem = getItem(index); + if (destItem && destItem->getID() == item->getID() && destItem->getItemCount() < 100) { + uint32_t remainder = 100 - destItem->getItemCount(); + + if (__queryAdd(index, item, remainder, flags) == RET_NOERROR) { + n = remainder; + } + } + } + + maxQueryCount = freeSlots * 100 + n; + if (maxQueryCount < count) { + return RET_CONTAINERNOTENOUGHROOM; + } + } else { + maxQueryCount = freeSlots; + if (maxQueryCount == 0) { + return RET_CONTAINERNOTENOUGHROOM; + } + } + return RET_NOERROR; +} + +ReturnValue Container::__queryRemove(const Thing* thing, uint32_t count, uint32_t flags) const +{ + int32_t index = __getIndexOfThing(thing); + if (index == -1) { + return RET_NOTPOSSIBLE; + } + + const Item* item = thing->getItem(); + if (item == NULL) { + return RET_NOTPOSSIBLE; + } + + if (count == 0 || (item->isStackable() && count > item->getItemCount())) { + return RET_NOTPOSSIBLE; + } + + if (item->isNotMoveable() && !hasBitSet(FLAG_IGNORENOTMOVEABLE, flags)) { + return RET_NOTMOVEABLE; + } + return RET_NOERROR; +} + +Cylinder* Container::__queryDestination(int32_t& index, const Thing* thing, Item** destItem, + uint32_t& flags) +{ + if (!unlocked) { + *destItem = NULL; + return this; + } + + if (index == 254 /*move up*/) { + index = INDEX_WHEREEVER; + *destItem = NULL; + + Container* parentContainer = dynamic_cast(getParent()); + if (parentContainer) { + return parentContainer; + } + return this; + } + + if (index == 255 /*add wherever*/) { + index = INDEX_WHEREEVER; + *destItem = NULL; + } else if (index >= (int32_t)capacity()) { + /* + if you have a container, maximize it to show all 20 slots + then you open a bag that is inside the container you will have a bag with 8 slots + and a "grey" area where the other 12 slots where from the container + if you drop the item on that grey area + the client calculates the slot position as if the bag has 20 slots + */ + index = INDEX_WHEREEVER; + *destItem = NULL; + } + + const Item* item = thing->getItem(); + if (!item) { + return this; + } + + bool autoStack = !hasBitSet(FLAG_IGNOREAUTOSTACK, flags); + if (autoStack && item->isStackable() && item->getParent() != this) { + //try find a suitable item to stack with + uint32_t n = 0; + + for (ItemDeque::iterator cit = itemlist.begin(); cit != itemlist.end(); ++cit) { + if ((*cit) != item && (*cit)->getID() == item->getID() && (*cit)->getItemCount() < 100) { + *destItem = (*cit); + index = n; + return this; + } + + ++n; + } + } + + if (index != INDEX_WHEREEVER) { + Item* itemFromIndex = getItem(index); + if (itemFromIndex) { + *destItem = itemFromIndex; + } + + Cylinder* subCylinder = dynamic_cast(*destItem); + if (subCylinder) { + index = INDEX_WHEREEVER; + *destItem = NULL; + return subCylinder; + } + } + return this; +} + +void Container::__addThing(Thing* thing) +{ + return __addThing(0, thing); +} + +void Container::__addThing(int32_t index, Thing* thing) +{ + if (index >= (int32_t)capacity()) { + return /*RET_NOTPOSSIBLE*/; + } + + Item* item = thing->getItem(); + if (item == NULL) { + return /*RET_NOTPOSSIBLE*/; + } + + item->setParent(this); + itemlist.push_front(item); + totalWeight += item->getWeight(); + + if (Container* parentContainer = getParentContainer()) { + parentContainer->updateItemWeight(item->getWeight()); + } + + //send change to client + if (getParent() && (getParent() != VirtualCylinder::virtualCylinder)) { + onAddContainerItem(item); + } +} + +void Container::__addThingBack(Thing* thing) +{ + Item* item = thing->getItem(); + if (item == NULL) { + return /*RET_NOTPOSSIBLE*/; + } + + item->setParent(this); + itemlist.push_back(item); + totalWeight += item->getWeight(); + + if (Container* parentContainer = getParentContainer()) { + parentContainer->updateItemWeight(item->getWeight()); + } + + //send change to client + if (getParent() && (getParent() != VirtualCylinder::virtualCylinder)) { + onAddContainerItem(item); + } +} + +void Container::__updateThing(Thing* thing, uint16_t itemId, uint32_t count) +{ + int32_t index = __getIndexOfThing(thing); + if (index == -1) { + return /*RET_NOTPOSSIBLE*/; + } + + Item* item = thing->getItem(); + if (item == NULL) { + return /*RET_NOTPOSSIBLE*/; + } + + const ItemType& oldType = Item::items[item->getID()]; + + const ItemType& newType = Item::items[itemId]; + + const double oldWeight = item->getWeight(); + + item->setID(itemId); + + item->setSubType(count); + + const double diffWeight = -oldWeight + item->getWeight(); + + totalWeight += diffWeight; + + if (Container* parentContainer = getParentContainer()) { + parentContainer->updateItemWeight(diffWeight); + } + + //send change to client + if (getParent()) { + onUpdateContainerItem(index, item, oldType, item, newType); + } +} + +void Container::__replaceThing(uint32_t index, Thing* thing) +{ + Item* item = thing->getItem(); + + if (!item) { + return /*RET_NOTPOSSIBLE*/; + } + + Item* replacedItem = getItem(index); + + if (!replacedItem) { + return /*RET_NOTPOSSIBLE*/; + } + + totalWeight -= replacedItem->getWeight(); + totalWeight += item->getWeight(); + + if (Container* parentContainer = getParentContainer()) { + parentContainer->updateItemWeight(-replacedItem->getWeight() + item->getWeight()); + } + + itemlist[index] = item; + item->setParent(this); + + //send change to client + if (getParent()) { + const ItemType& oldType = Item::items[replacedItem->getID()]; + const ItemType& newType = Item::items[item->getID()]; + onUpdateContainerItem(index, replacedItem, oldType, item, newType); + } + + replacedItem->setParent(NULL); +} + +void Container::__removeThing(Thing* thing, uint32_t count) +{ + Item* item = thing->getItem(); + + if (item == NULL) { + return /*RET_NOTPOSSIBLE*/; + } + + int32_t index = __getIndexOfThing(thing); + + if (index == -1) { + return /*RET_NOTPOSSIBLE*/; + } + + if (item->isStackable() && count != item->getItemCount()) { + uint8_t newCount = (uint8_t)std::max(0, item->getItemCount() - count); + const double oldWeight = -item->getWeight(); + item->setItemCount(newCount); + const double diffWeight = oldWeight + item->getWeight(); + totalWeight += diffWeight; + + //send change to client + if (getParent()) { + if (Container* parentContainer = getParentContainer()) { + parentContainer->updateItemWeight(diffWeight); + } + + const ItemType& it = Item::items[item->getID()]; + + onUpdateContainerItem(index, item, it, item, it); + } + } else { + //send change to client + if (getParent()) { + if (Container* parentContainer = getParentContainer()) { + parentContainer->updateItemWeight(-item->getWeight()); + } + + onRemoveContainerItem(index, item); + } + + totalWeight -= item->getWeight(); + item->setParent(NULL); + itemlist.erase(itemlist.begin() + index); + } +} + +int32_t Container::__getIndexOfThing(const Thing* thing) const +{ + uint32_t index = 0; + + for (ItemDeque::const_iterator cit = getItems(); cit != getEnd(); ++cit) { + if (*cit == thing) { + return index; + } else { + ++index; + } + } + + return -1; +} + +int32_t Container::__getFirstIndex() const +{ + return 0; +} + +int32_t Container::__getLastIndex() const +{ + return size(); +} + +uint32_t Container::__getItemTypeCount(uint16_t itemId, int32_t subType /*= -1*/) const +{ + uint32_t count = 0; + + for (ItemDeque::const_iterator it = itemlist.begin(); it != itemlist.end(); ++it) { + if ((*it)->getID() == itemId) { + count += countByType(*it, subType); + } + } + + return count; +} + +std::map& Container::__getAllItemTypeCount(std::map& countMap) const +{ + for (ItemDeque::const_iterator it = itemlist.begin(); it != itemlist.end(); ++it) { + countMap[(*it)->getID()] += (*it)->getItemCount(); + } + + return countMap; +} + +Thing* Container::__getThing(uint32_t index) const +{ + return getItem(index); +} + +void Container::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) +{ + Cylinder* topParent = getTopParent(); + + if (topParent->getCreature()) { + topParent->postAddNotification(thing, oldParent, index, LINK_TOPPARENT); + } else { + if (topParent == this) { + //let the tile class notify surrounding players + if (topParent->getParent()) { + topParent->getParent()->postAddNotification(thing, oldParent, index, LINK_NEAR); + } + } else { + topParent->postAddNotification(thing, oldParent, index, LINK_PARENT); + } + } +} + +void Container::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, bool isCompleteRemoval, cylinderlink_t link /*= LINK_OWNER*/) +{ + Cylinder* topParent = getTopParent(); + + if (topParent->getCreature()) { + topParent->postRemoveNotification(thing, newParent, index, isCompleteRemoval, LINK_TOPPARENT); + } else { + if (topParent == this) { + //let the tile class notify surrounding players + if (topParent->getParent()) { + topParent->getParent()->postRemoveNotification(thing, newParent, index, isCompleteRemoval, LINK_NEAR); + } + } else { + topParent->postRemoveNotification(thing, newParent, index, isCompleteRemoval, LINK_PARENT); + } + } +} + +void Container::__internalAddThing(Thing* thing) +{ + __internalAddThing(0, thing); +} + +void Container::__internalAddThing(uint32_t index, Thing* thing) +{ + Item* item = thing->getItem(); + + if (item == NULL) { + return; + } + + item->setParent(this); + itemlist.push_front(item); + totalWeight += item->getWeight(); + + if (Container* parentContainer = getParentContainer()) { + parentContainer->updateItemWeight(item->getWeight()); + } +} + +void Container::__startDecaying() +{ + for (ItemDeque::const_iterator it = itemlist.begin(); it != itemlist.end(); ++it) { + (*it)->__startDecaying(); + } +} + +ContainerIterator Container::begin() +{ + ContainerIterator cit(this); + + if (!itemlist.empty()) { + cit.over.push(this); + cit.cur = itemlist.begin(); + } + + return cit; +} + +ContainerIterator Container::end() +{ + ContainerIterator cit(this); + return cit; +} + +// Very evil constructors, look away if you are sensitive! +ContainerIterator Container::begin() const +{ + Container* evil = const_cast(this); + return evil->begin(); +} + +ContainerIterator Container::end() const +{ + Container* evil = const_cast(this); + return evil->end(); +} + +ContainerIterator::ContainerIterator(): + super(NULL) {} + +ContainerIterator::ContainerIterator(Container* super): + super(super) {} + +ContainerIterator::~ContainerIterator() {} + +ContainerIterator::ContainerIterator(const ContainerIterator& rhs): + super(rhs.super), over(rhs.over), cur(rhs.cur) {} + +bool ContainerIterator::operator==(const ContainerIterator& rhs) +{ + return !(*this != rhs); +} + +bool ContainerIterator::operator!=(const ContainerIterator& rhs) +{ + assert(super); + + if (super != rhs.super) { + return true; + } + + if (over.empty() && rhs.over.empty()) { + return false; + } + + if (over.empty()) { + return true; + } + + if (rhs.over.empty()) { + return true; + } + + if (over.front() != rhs.over.front()) { + return true; + } + + return cur != rhs.cur; +} + +ContainerIterator& ContainerIterator::operator=(const ContainerIterator& rhs) +{ + this->super = rhs.super; + this->cur = rhs.cur; + this->over = rhs.over; + return *this; +} + +Item* ContainerIterator::operator*() +{ + assert(super); + return *cur; +} + +Item* ContainerIterator::operator->() +{ + return *(*this); +} + +ContainerIterator& ContainerIterator::operator++() +{ + assert(super); + + if (Item* i = *cur) { + Container* c = i->getContainer(); + + if (c && !c->empty()) { + over.push(c); + } + } + + ++cur; + + if (cur == over.front()->itemlist.end()) { + over.pop(); + + if (over.empty()) { + return *this; + } + + cur = over.front()->itemlist.begin(); + } + + return *this; +} + +ContainerIterator ContainerIterator::operator++(int) +{ + ContainerIterator tmp(*this); + ++*this; + return tmp; +} diff --git a/src/container.h b/src/container.h new file mode 100644 index 0000000000..822e5be7dc --- /dev/null +++ b/src/container.h @@ -0,0 +1,190 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_CONTAINER_H__ +#define __OTSERV_CONTAINER_H__ + +#include + +#include "definitions.h" +#include "cylinder.h" +#include "item.h" + +class Container; +class DepotChest; +class DepotLocker; + +class ContainerIterator +{ + public: + ContainerIterator(); + ContainerIterator(const ContainerIterator& rhs); + ~ContainerIterator(); + + ContainerIterator& operator=(const ContainerIterator& rhs); + bool operator==(const ContainerIterator& rhs); + bool operator!=(const ContainerIterator& rhs); + ContainerIterator& operator++(); + ContainerIterator operator++(int); + Item* operator*(); + Item* operator->(); + + protected: + ContainerIterator(Container* super); + + Container* super; + std::queue over; + ItemDeque::iterator cur; + + friend class Container; +}; + +class Container : public Item, public Cylinder +{ + public: + Container(uint16_t _type); + Container(Tile* tile); + virtual ~Container(); + virtual Item* clone() const; + + virtual Container* getContainer() { + return this; + } + virtual const Container* getContainer() const { + return this; + } + + virtual DepotChest* getDepotChest() { + return NULL; + } + virtual const DepotChest* getDepotChest() const { + return NULL; + } + + virtual DepotLocker* getDepotLocker() { + return NULL; + } + virtual const DepotLocker* getDepotLocker() const { + return NULL; + } + + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream); + bool unserializeItemNode(FileLoader& f, NODE node, PropStream& propStream); + std::string getContentDescription() const; + + uint32_t size() const { + return (uint32_t)itemlist.size(); + } + bool empty() const { + return itemlist.empty(); + } + uint32_t capacity() const { + return maxSize; + } + + ContainerIterator begin(); + ContainerIterator end(); + ContainerIterator begin() const; + ContainerIterator end() const; + + ItemDeque::const_iterator getItems() const { + return itemlist.begin(); + } + ItemDeque::const_iterator getEnd() const { + return itemlist.end(); + } + + ItemDeque::const_reverse_iterator getReversedItems() const { + return itemlist.rbegin(); + } + ItemDeque::const_reverse_iterator getReversedEnd() const { + return itemlist.rend(); + } + + bool hasParent(uint16_t version) const; + void addItem(Item* item); + Item* getItem(uint32_t index) const; + bool isHoldingItem(const Item* item) const; + + uint32_t getItemHoldingCount() const; + virtual double getWeight() const; + + bool isUnlocked() const { + return unlocked; + } + bool hasPages() const { + return pages; + } + + //cylinder implementations + virtual ReturnValue __queryAdd(int32_t index, const Thing* thing, uint32_t count, + uint32_t flags, Creature* actor = NULL) const; + virtual ReturnValue __queryMaxCount(int32_t index, const Thing* thing, uint32_t count, uint32_t& maxQueryCount, + uint32_t flags) const; + virtual ReturnValue __queryRemove(const Thing* thing, uint32_t count, uint32_t flags) const; + virtual Cylinder* __queryDestination(int32_t& index, const Thing* thing, Item** destItem, + uint32_t& flags); + + virtual void __addThing(Thing* thing); + virtual void __addThing(int32_t index, Thing* thing); + virtual void __addThingBack(Thing* thing); + + virtual void __updateThing(Thing* thing, uint16_t itemId, uint32_t count); + virtual void __replaceThing(uint32_t index, Thing* thing); + + virtual void __removeThing(Thing* thing, uint32_t count); + + virtual int32_t __getIndexOfThing(const Thing* thing) const; + virtual int32_t __getFirstIndex() const; + virtual int32_t __getLastIndex() const; + virtual uint32_t __getItemTypeCount(uint16_t itemId, int32_t subType = -1) const; + virtual std::map& __getAllItemTypeCount(std::map& countMap) const; + virtual Thing* __getThing(uint32_t index) const; + + virtual void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER); + virtual void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, bool isCompleteRemoval, cylinderlink_t link = LINK_OWNER); + + virtual void __internalAddThing(Thing* thing); + virtual void __internalAddThing(uint32_t index, Thing* thing); + virtual void __startDecaying(); + + private: + void onAddContainerItem(Item* item); + void onUpdateContainerItem(uint32_t index, Item* oldItem, const ItemType& oldType, + Item* newItem, const ItemType& newType); + void onRemoveContainerItem(uint32_t index, Item* item); + + Container* getParentContainer(); + void updateItemWeight(double diff); + + protected: + std::ostringstream& getContentDescription(std::ostringstream& os) const; + + uint32_t maxSize; + double totalWeight; + ItemDeque itemlist; + uint32_t serializationCount; + + bool unlocked; + bool pages; + + friend class ContainerIterator; + friend class IOMapSerialize; +}; + +#endif diff --git a/src/creature.cpp b/src/creature.cpp new file mode 100644 index 0000000000..72fe124e56 --- /dev/null +++ b/src/creature.cpp @@ -0,0 +1,1765 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "creature.h" +#include "game.h" +#include "player.h" +#include "npc.h" +#include "monster.h" +#include "container.h" +#include "condition.h" +#include "combat.h" +#include "configmanager.h" + +#include +#include +#include + +OTSYS_THREAD_LOCKVAR AutoID::autoIDLock; +uint32_t AutoID::count = 1000; +AutoID::list_type AutoID::list; + +double Creature::speedA = 857.36; +double Creature::speedB = 261.29; +double Creature::speedC = -4795.01; + +extern Game g_game; +extern ConfigManager g_config; +extern CreatureEvents* g_creatureEvents; + +Creature::Creature() : + isInternalRemoved(false) +{ + id = 0; + _tile = NULL; + direction = SOUTH; + master = NULL; + lootDrop = true; + skillLoss = true; + + health = 1000; + healthMax = 1000; + mana = 0; + manaMax = 0; + + lastStep = 0; + lastStepCost = 1; + baseSpeed = 220; + varSpeed = 0; + + masterRadius = -1; + masterPos.x = 0; + masterPos.y = 0; + masterPos.z = 0; + + followCreature = NULL; + hasFollowPath = false; + eventWalk = 0; + cancelNextWalk = false; + forceUpdateFollowPath = false; + isMapLoaded = false; + isUpdatingPath = false; + memset(localMapCache, 0, sizeof(localMapCache)); + + attackedCreature = NULL; + _lastHitCreature = NULL; + _mostDamageCreature = NULL; + lastHitUnjustified = false; + mostDamageUnjustified = false; + + lastHitCreature = 0; + blockCount = 0; + blockTicks = 0; + walkUpdateTicks = 0; + checkCreatureVectorIndex = -1; + creatureCheck = false; + scriptEventsBitField = 0; + + hiddenHealth = false; + + onIdleStatus(); +} + +Creature::~Creature() +{ + std::list::iterator cit; + + for (cit = summons.begin(); cit != summons.end(); ++cit) { + (*cit)->setAttackedCreature(NULL); + (*cit)->setMaster(NULL); + (*cit)->releaseThing2(); + } + + summons.clear(); + + for (ConditionList::iterator it = conditions.begin(); it != conditions.end(); ++it) { + (*it)->endCondition(this, CONDITIONEND_CLEANUP); + delete *it; + } + + conditions.clear(); + + attackedCreature = NULL; + + //std::cout << "Creature destructor " << this->getID() << std::endl; +} + +bool Creature::canSee(const Position& myPos, const Position& pos, uint32_t viewRangeX, uint32_t viewRangeY) +{ + if (myPos.z <= 7) { + //we are on ground level or above (7 -> 0) + //view is from 7 -> 0 + if (pos.z > 7) { + return false; + } + } else if (myPos.z >= 8) { + //we are underground (8 -> 15) + //view is +/- 2 from the floor we stand on + if (std::abs(myPos.z - pos.z) > 2) { + return false; + } + } + + int32_t offsetz = myPos.z - pos.z; + + if (((uint32_t)pos.x >= myPos.x - viewRangeX + offsetz) && ((uint32_t)pos.x <= myPos.x + viewRangeX + offsetz) && + ((uint32_t)pos.y >= myPos.y - viewRangeY + offsetz) && ((uint32_t)pos.y <= myPos.y + viewRangeY + offsetz)) { + return true; + } + + return false; +} + +bool Creature::canSee(const Position& pos) const +{ + return canSee(getPosition(), pos, Map::maxViewportX, Map::maxViewportY); +} + +bool Creature::canSeeCreature(const Creature* creature) const +{ + if (!canSeeInvisibility() && creature->isInvisible()) { + return false; + } + + return true; +} + +int64_t Creature::getTimeSinceLastMove() const +{ + if (lastStep) { + return OTSYS_TIME() - lastStep; + } + + return 0x7FFFFFFFFFFFFFFFLL; +} + +int32_t Creature::getWalkDelay(Direction dir) const +{ + if (lastStep == 0) { + return 0; + } + + int64_t ct = OTSYS_TIME(); + int64_t stepDuration = getStepDuration(dir); + return stepDuration - (ct - lastStep); +} + +int32_t Creature::getWalkDelay() const +{ + //Used for auto-walking + if (lastStep == 0) { + return 0; + } + + int64_t ct = OTSYS_TIME(); + int64_t stepDuration = getStepDuration() * lastStepCost; + return stepDuration - (ct - lastStep); +} + +void Creature::onThink(uint32_t interval) +{ + if (!isMapLoaded && useCacheMap()) { + isMapLoaded = true; + updateMapCache(); + } + + if (followCreature && getMaster() != followCreature && !canSeeCreature(followCreature)) { + onCreatureDisappear(followCreature, false); + } + + if (attackedCreature && getMaster() != attackedCreature && !canSeeCreature(attackedCreature)) { + onCreatureDisappear(attackedCreature, false); + } + + blockTicks += interval; + + if (blockTicks >= 1000) { + blockCount = std::min(blockCount + 1, 2); + blockTicks = 0; + } + + if (followCreature) { + walkUpdateTicks += interval; + + if (forceUpdateFollowPath || walkUpdateTicks >= 2000) { + walkUpdateTicks = 0; + forceUpdateFollowPath = false; + isUpdatingPath = true; + } + } + + if (isUpdatingPath) { + isUpdatingPath = false; + goToFollowCreature(); + } + + //scripting event - onThink + CreatureEventList thinkEvents = getCreatureEvents(CREATURE_EVENT_THINK); + + for (CreatureEventList::const_iterator it = thinkEvents.begin(), end = thinkEvents.end(); it != end; ++it) { + (*it)->executeOnThink(this, interval); + } +} + +void Creature::onAttacking(uint32_t interval) +{ + if (!attackedCreature) { + return; + } + + onAttacked(); + attackedCreature->onAttacked(); + + if (g_game.isSightClear(getPosition(), attackedCreature->getPosition(), true)) { + doAttacking(interval); + } +} + +void Creature::onIdleStatus() +{ + if (getHealth() > 0) { + healMap.clear(); + damageMap.clear(); + } +} + +void Creature::onWalk() +{ + if (getWalkDelay() <= 0) { + Direction dir; + uint32_t flags = FLAG_IGNOREFIELDDAMAGE; + + if (getNextStep(dir, flags)) { + ReturnValue ret = g_game.internalMoveCreature(this, dir, flags); + + if (ret != RET_NOERROR) { + if (Player* player = getPlayer()) { + player->sendCancelMessage(ret); + player->sendCancelWalk(); + } + + forceUpdateFollowPath = true; + } + } else { + if (listWalkDir.empty()) { + onWalkComplete(); + } + + stopEventWalk(); + } + } + + if (cancelNextWalk) { + listWalkDir.clear(); + onWalkAborted(); + cancelNextWalk = false; + } + + if (eventWalk != 0) { + eventWalk = 0; + addEventWalk(); + } +} + +void Creature::onWalk(Direction& dir) +{ + if (hasCondition(CONDITION_DRUNK)) { + uint32_t r = random_range(0, 20); + + if (r <= 4) { + switch (r) { + case 0: + dir = NORTH; + break; + case 1: + dir = WEST; + break; + case 3: + dir = SOUTH; + break; + case 4: + dir = EAST; + break; + + default: + break; + } + + g_game.internalCreatureSay(this, SPEAK_MONSTER_SAY, "Hicks!", false); + } + } +} + +bool Creature::getNextStep(Direction& dir, uint32_t& flags) +{ + if (listWalkDir.empty()) { + return false; + } + + dir = listWalkDir.front(); + listWalkDir.pop_front(); + onWalk(dir); + return true; +} + +bool Creature::startAutoWalk(std::list& listDir) +{ + const Player* thisPlayer = getPlayer(); + + if (thisPlayer && thisPlayer->getNoMove()) { + thisPlayer->sendCancelWalk(); + return false; + } + + listWalkDir = listDir; + addEventWalk(listDir.size() == 1); + return true; +} + +void Creature::addEventWalk(bool firstStep) +{ + cancelNextWalk = false; + + if (getStepSpeed() <= 0) { + return; + } + + if (eventWalk != 0) { + return; + } + + int64_t ticks = getEventStepTicks(firstStep); + + if (ticks <= 0) { + return; + } + + // Take first step right away, but still queue the next + if (ticks == 1) { + g_game.checkCreatureWalk(getID()); + } + + eventWalk = g_scheduler.addEvent(createSchedulerTask( + std::max(SCHEDULER_MINTICKS, ticks), boost::bind(&Game::checkCreatureWalk, &g_game, getID()))); +} + +void Creature::stopEventWalk() +{ + if (eventWalk != 0) { + g_scheduler.stopEvent(eventWalk); + eventWalk = 0; + } +} + +void Creature::updateMapCache() +{ + Tile* tile; + const Position& myPos = getPosition(); + Position pos(0, 0, myPos.z); + + for (int32_t y = -((mapWalkHeight - 1) / 2); y <= ((mapWalkHeight - 1) / 2); ++y) { + for (int32_t x = -((mapWalkWidth - 1) / 2); x <= ((mapWalkWidth - 1) / 2); ++x) { + pos.x = myPos.x + x; + pos.y = myPos.y + y; + tile = g_game.getTile(pos.x, pos.y, myPos.z); + updateTileCache(tile, pos); + } + } +} + +void Creature::updateTileCache(const Tile* tile, int32_t dx, int32_t dy) +{ + if ((std::abs(dx) <= (mapWalkWidth - 1) / 2) && + (std::abs(dy) <= (mapWalkHeight - 1) / 2)) { + int32_t x = (mapWalkWidth - 1) / 2 + dx; + int32_t y = (mapWalkHeight - 1) / 2 + dy; + + localMapCache[y][x] = (tile && tile->__queryAdd(0, this, 1, + FLAG_PATHFINDING | FLAG_IGNOREFIELDDAMAGE) == RET_NOERROR); + } +} + +void Creature::updateTileCache(const Tile* tile, const Position& pos) +{ + const Position& myPos = getPosition(); + + if (pos.z == myPos.z) { + int32_t dx = pos.x - myPos.x; + int32_t dy = pos.y - myPos.y; + + updateTileCache(tile, dx, dy); + } +} + +int32_t Creature::getWalkCache(const Position& pos) const +{ + if (!useCacheMap()) { + return 2; + } + + const Position& myPos = getPosition(); + + if (myPos.z != pos.z) { + return 0; + } + + if (pos == myPos) { + return 1; + } + + int32_t dx = pos.x - myPos.x; + int32_t dy = pos.y - myPos.y; + + if ((std::abs(dx) <= (mapWalkWidth - 1) / 2) && + (std::abs(dy) <= (mapWalkHeight - 1) / 2)) { + int32_t x = (mapWalkWidth - 1) / 2 + dx; + int32_t y = (mapWalkHeight - 1) / 2 + dy; + + if (localMapCache[y][x]) { + return 1; + } else { + return 0; + } + } + + //out of range + return 2; +} + +void Creature::onAddTileItem(const Tile* tile, const Position& pos, const Item* item) +{ + if (isMapLoaded && pos.z == getPosition().z) { + updateTileCache(tile, pos); + } +} + +void Creature::onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem, + const ItemType& oldType, const Item* newItem, const ItemType& newType) +{ + if (!isMapLoaded) { + return; + } + + if (oldType.blockSolid || oldType.blockPathFind || newType.blockPathFind || newType.blockSolid) { + if (pos.z == getPosition().z) { + updateTileCache(tile, pos); + } + } +} + +void Creature::onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType, + const Item* item) +{ + if (!isMapLoaded) { + return; + } + + if (iType.blockSolid || iType.blockPathFind || iType.isGroundTile()) { + if (pos.z == getPosition().z) { + updateTileCache(tile, pos); + } + } +} + +void Creature::onCreatureAppear(const Creature* creature, bool isLogin) +{ + if (creature == this) { + if (useCacheMap()) { + isMapLoaded = true; + updateMapCache(); + } + + if (isLogin) { + setLastPosition(getPosition()); + } + } else if (isMapLoaded) { + if (creature->getPosition().z == getPosition().z) { + updateTileCache(creature->getTile(), creature->getPosition()); + } + } +} + +void Creature::onCreatureDisappear(const Creature* creature, uint32_t stackpos, bool isLogout) +{ + onCreatureDisappear(creature, true); + + if (creature == this) { + if (getMaster() && !getMaster()->isRemoved()) { + getMaster()->removeSummon(this); + } + } else if (isMapLoaded) { + if (creature->getPosition().z == getPosition().z) { + updateTileCache(creature->getTile(), creature->getPosition()); + } + } +} + +void Creature::onCreatureDisappear(const Creature* creature, bool isLogout) +{ + if (attackedCreature == creature) { + setAttackedCreature(NULL); + onAttackedCreatureDisappear(isLogout); + } + + if (followCreature == creature) { + setFollowCreature(NULL); + onFollowCreatureDisappear(isLogout); + } +} + +void Creature::onChangeZone(ZoneType_t zone) +{ + if (attackedCreature && zone == ZONE_PROTECTION) { + onCreatureDisappear(attackedCreature, false); + } +} + +void Creature::onAttackedCreatureChangeZone(ZoneType_t zone) +{ + if (zone == ZONE_PROTECTION) { + onCreatureDisappear(attackedCreature, false); + } +} + +void Creature::onCreatureMove(const Creature* creature, const Tile* newTile, const Position& newPos, + const Tile* oldTile, const Position& oldPos, bool teleport) +{ + if (creature == this) { + lastStep = OTSYS_TIME(); + lastStepCost = 1; + + if (!teleport) { + if (oldPos.z != newPos.z) { + //floor change extra cost + lastStepCost = 1; + } else if (std::abs(newPos.x - oldPos.x) >= 1 && std::abs(newPos.y - oldPos.y) >= 1) { + //diagonal extra cost + lastStepCost = 3; + } + } else { + stopEventWalk(); + } + + if (!summons.empty()) { + //check if any of our summons is out of range (+/- 2 floors or 30 tiles away) + std::list despawnList; + std::list::iterator cit; + + for (cit = summons.begin(); cit != summons.end(); ++cit) { + const Position pos = (*cit)->getPosition(); + + if ((std::abs(pos.z - newPos.z) > 2) || + (std::max(std::abs((newPos.x) - pos.x), std::abs((newPos.y - 1) - pos.y)) > 30)) { + despawnList.push_back((*cit)); + } + } + + for (cit = despawnList.begin(); cit != despawnList.end(); ++cit) { + g_game.removeCreature((*cit), true); + } + } + + if (newTile->getZone() != oldTile->getZone()) { + onChangeZone(getZone()); + } + + //update map cache + if (isMapLoaded) { + if (teleport || oldPos.z != newPos.z) { + updateMapCache(); + } else { + Tile* tile; + const Position& myPos = getPosition(); + Position pos; + + if (oldPos.y > newPos.y) { //north + //shift y south + for (int32_t y = mapWalkHeight - 1 - 1; y >= 0; --y) { + memcpy(localMapCache[y + 1], localMapCache[y], sizeof(localMapCache[y])); + } + + //update 0 + for (int32_t x = -((mapWalkWidth - 1) / 2); x <= ((mapWalkWidth - 1) / 2); ++x) { + tile = g_game.getTile(myPos.x + x, myPos.y - ((mapWalkHeight - 1) / 2), myPos.z); + updateTileCache(tile, x, -((mapWalkHeight - 1) / 2)); + } + } else if (oldPos.y < newPos.y) { // south + //shift y north + for (int32_t y = 0; y <= mapWalkHeight - 1 - 1; ++y) { + memcpy(localMapCache[y], localMapCache[y + 1], sizeof(localMapCache[y])); + } + + //update mapWalkHeight - 1 + for (int32_t x = -((mapWalkWidth - 1) / 2); x <= ((mapWalkWidth - 1) / 2); ++x) { + tile = g_game.getTile(myPos.x + x, myPos.y + ((mapWalkHeight - 1) / 2), myPos.z); + updateTileCache(tile, x, (mapWalkHeight - 1) / 2); + } + } + + if (oldPos.x < newPos.x) { // east + //shift y west + int32_t starty = 0; + int32_t endy = mapWalkHeight - 1; + int32_t dy = (oldPos.y - newPos.y); + + if (dy < 0) { + endy = endy + dy; + } else if (dy > 0) { + starty = starty + dy; + } + + for (int32_t y = starty; y <= endy; ++y) { + for (int32_t x = 0; x <= mapWalkWidth - 1 - 1; ++x) { + localMapCache[y][x] = localMapCache[y][x + 1]; + } + } + + //update mapWalkWidth - 1 + for (int32_t y = -((mapWalkHeight - 1) / 2); y <= ((mapWalkHeight - 1) / 2); ++y) { + tile = g_game.getTile(myPos.x + ((mapWalkWidth - 1) / 2), myPos.y + y, myPos.z); + updateTileCache(tile, (mapWalkWidth - 1) / 2, y); + } + } else if (oldPos.x > newPos.x) { // west + //shift y east + int32_t starty = 0; + int32_t endy = mapWalkHeight - 1; + int32_t dy = (oldPos.y - newPos.y); + + if (dy < 0) { + endy = endy + dy; + } else if (dy > 0) { + starty = starty + dy; + } + + for (int32_t y = starty; y <= endy; ++y) { + for (int32_t x = mapWalkWidth - 1 - 1; x >= 0; --x) { + localMapCache[y][x + 1] = localMapCache[y][x]; + } + } + + //update 0 + for (int32_t y = -((mapWalkHeight - 1) / 2); y <= ((mapWalkHeight - 1) / 2); ++y) { + tile = g_game.getTile(myPos.x - ((mapWalkWidth - 1) / 2), myPos.y + y, myPos.z); + updateTileCache(tile, -((mapWalkWidth - 1) / 2), y); + } + } + + updateTileCache(oldTile, oldPos); + } + } + } else { + if (isMapLoaded) { + const Position& myPos = getPosition(); + + if (newPos.z == myPos.z) { + updateTileCache(newTile, newPos); + } + + if (oldPos.z == myPos.z) { + updateTileCache(oldTile, oldPos); + } + } + } + + if (creature == followCreature || (creature == this && followCreature)) { + if (hasFollowPath) { + isUpdatingPath = true; + } + + if (newPos.z != oldPos.z || !canSee(followCreature->getPosition())) { + onCreatureDisappear(followCreature, false); + } + } + + if (creature == attackedCreature || (creature == this && attackedCreature)) { + if (newPos.z != oldPos.z || !canSee(attackedCreature->getPosition())) { + onCreatureDisappear(attackedCreature, false); + } else { + if (hasExtraSwing()) { + //our target is moving lets see if we can get in hit + g_dispatcher.addTask(createTask( + boost::bind(&Game::checkCreatureAttack, &g_game, getID()))); + } + + if (newTile->getZone() != oldTile->getZone()) { + onAttackedCreatureChangeZone(attackedCreature->getZone()); + } + } + } +} + +void Creature::onDeath() +{ + Creature* mostDamageCreatureMaster = NULL; + Creature* lastHitCreatureMaster = NULL; + + if (getKillers(&_lastHitCreature, &_mostDamageCreature)) { + if (_lastHitCreature) { + lastHitUnjustified = _lastHitCreature->onKilledCreature(this); + lastHitCreatureMaster = _lastHitCreature->getMaster(); + } + + if (_mostDamageCreature) { + mostDamageCreatureMaster = _mostDamageCreature->getMaster(); + bool isNotLastHitMaster = (_mostDamageCreature != lastHitCreatureMaster); + bool isNotMostDamageMaster = (_lastHitCreature != mostDamageCreatureMaster); + bool isNotSameMaster = lastHitCreatureMaster == NULL || (mostDamageCreatureMaster != lastHitCreatureMaster); + + if (_mostDamageCreature != _lastHitCreature && isNotLastHitMaster && isNotMostDamageMaster && isNotSameMaster) { + mostDamageUnjustified = _mostDamageCreature->onKilledCreature(this, false); + } + } + } + + for (CountMap::iterator it = damageMap.begin(), end = damageMap.end(); it != end; ++it) { + if (Creature* attacker = g_game.getCreatureByID(it->first)) { + attacker->onAttackedCreatureKilled(this); + } + } + + bool droppedCorpse = dropCorpse(); + death(); + + if (getMaster()) { + getMaster()->removeSummon(this); + } + + if (droppedCorpse) { + g_game.removeCreature(this, false); + } +} + +bool Creature::dropCorpse() +{ + if (!lootDrop && getMonster() && !(master && master->getPlayer())) { + if (master) { + //scripting event - onDeath + CreatureEventList deathEvents = getCreatureEvents(CREATURE_EVENT_DEATH); + + for (CreatureEventList::const_iterator it = deathEvents.begin(); it != deathEvents.end(); ++it) { + (*it)->executeOnDeath(this, NULL, _lastHitCreature, _mostDamageCreature, lastHitUnjustified, mostDamageUnjustified); + } + } + + g_game.addMagicEffect(getPosition(), NM_ME_POFF); + } else { + Item* splash = NULL; + + switch (getRace()) { + case RACE_VENOM: + splash = Item::CreateItem(ITEM_FULLSPLASH, FLUID_GREEN); + break; + + case RACE_BLOOD: + splash = Item::CreateItem(ITEM_FULLSPLASH, FLUID_BLOOD); + break; + + default: + break; + } + + Tile* tile = getTile(); + + if (splash) { + g_game.internalAddItem(tile, splash, INDEX_WHEREEVER, FLAG_NOLIMIT); + g_game.startDecay(splash); + } + + Item* corpse = getCorpse(); + + if (corpse) { + g_game.internalAddItem(tile, corpse, INDEX_WHEREEVER, FLAG_NOLIMIT); + g_game.startDecay(corpse); + } + + //scripting event - onDeath + CreatureEventList deathEvents = getCreatureEvents(CREATURE_EVENT_DEATH); + + for (CreatureEventList::const_iterator it = deathEvents.begin(); it != deathEvents.end(); ++it) { + (*it)->executeOnDeath(this, corpse, _lastHitCreature, _mostDamageCreature, lastHitUnjustified, mostDamageUnjustified); + } + + if (corpse) { + dropLoot(corpse->getContainer()); + } + } + + return true; +} + +bool Creature::getKillers(Creature** _lastHitCreature, Creature** _mostDamageCreature) +{ + *_lastHitCreature = g_game.getCreatureByID(lastHitCreature); + + int32_t mostDamage = 0; + + for (CountMap::const_iterator it = damageMap.begin(), end = damageMap.end(); it != end; ++it) { + CountBlock_t cb = it->second; + + if ((cb.total > mostDamage && (OTSYS_TIME() - cb.ticks <= g_game.getInFightTicks()))) { + if ((*_mostDamageCreature = g_game.getCreatureByID(it->first))) { + mostDamage = cb.total; + } + } + } + + return (*_lastHitCreature || *_mostDamageCreature); +} + +bool Creature::hasBeenAttacked(uint32_t attackerId) +{ + CountMap::iterator it = damageMap.find(attackerId); + + if (it != damageMap.end()) { + return (OTSYS_TIME() - it->second.ticks <= g_game.getInFightTicks()); + } + + return false; +} + +Item* Creature::getCorpse() +{ + Item* corpse = Item::CreateItem(getLookCorpse()); + return corpse; +} + +void Creature::changeHealth(int32_t healthChange, bool sendHealthChange/* = true*/) +{ + int32_t oldHealth = health; + + if (healthChange > 0) { + health += std::min(healthChange, getMaxHealth() - health); + } else { + health = std::max(0, health + healthChange); + } + + if (sendHealthChange && oldHealth != health) { + g_game.addCreatureHealth(this); + } +} + +void Creature::changeMana(int32_t manaChange) +{ + if (manaChange > 0) { + mana += std::min(manaChange, getMaxMana() - mana); + } else { + mana = std::max(0, mana + manaChange); + } +} + +void Creature::drainHealth(Creature* attacker, CombatType_t combatType, int32_t damage) +{ + changeHealth(-damage, false); + + if (attacker) { + attacker->onAttackedCreatureDrainHealth(this, damage); + } +} + +void Creature::drainMana(Creature* attacker, int32_t manaLoss) +{ + onAttacked(); + changeMana(-manaLoss); +} + +BlockType_t Creature::blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, + bool checkDefense /* = false */, bool checkArmor /* = false */) +{ + BlockType_t blockType = BLOCK_NONE; + + if (isImmune(combatType)) { + damage = 0; + blockType = BLOCK_IMMUNITY; + } else if (checkDefense || checkArmor) { + bool hasDefense = false; + + if (blockCount > 0) { + --blockCount; + hasDefense = true; + } + + if (checkDefense && hasDefense) { + int32_t maxDefense = getDefense(); + int32_t minDefense = maxDefense / 2; + damage -= random_range(minDefense, maxDefense); + + if (damage <= 0) { + damage = 0; + blockType = BLOCK_DEFENSE; + checkArmor = false; + } + } + + if (checkArmor) { + int32_t armorValue = getArmor(); + int32_t minArmorReduction = 0; + int32_t maxArmorReduction = 0; + + if (armorValue > 1) { + minArmorReduction = (int32_t)std::ceil(armorValue * 0.475); + maxArmorReduction = (int32_t)std::ceil(((armorValue * 0.475) - 1) + minArmorReduction); + } else if (armorValue == 1) { + minArmorReduction = 1; + maxArmorReduction = 1; + } + + damage -= random_range(minArmorReduction, maxArmorReduction); + + if (damage <= 0) { + damage = 0; + blockType = BLOCK_ARMOR; + } + } + + if (hasDefense && blockType != BLOCK_NONE) { + onBlockHit(blockType); + } + } + + if (attacker) { + attacker->onAttackedCreature(this); + attacker->onAttackedCreatureBlockHit(this, blockType); + } + + onAttacked(); + return blockType; +} + +bool Creature::setAttackedCreature(Creature* creature) +{ + if (creature) { + const Position& creaturePos = creature->getPosition(); + + if (creaturePos.z != getPosition().z || !canSee(creaturePos)) { + attackedCreature = NULL; + return false; + } + } + + attackedCreature = creature; + + if (attackedCreature) { + onAttackedCreature(attackedCreature); + attackedCreature->onAttacked(); + } + + for (std::list::iterator cit = summons.begin(), end = summons.end(); cit != end; ++cit) { + (*cit)->setAttackedCreature(creature); + } + + return true; +} + +void Creature::getPathSearchParams(const Creature* creature, FindPathParams& fpp) const +{ + fpp.fullPathSearch = !hasFollowPath; + fpp.clearSight = true; + fpp.maxSearchDist = 12; + fpp.minTargetDist = 1; + fpp.maxTargetDist = 1; +} + +void Creature::goToFollowCreature() +{ + if (followCreature) { + FindPathParams fpp; + getPathSearchParams(followCreature, fpp); + + Monster* monster = getMonster(); + + if (monster && !monster->getMaster() && (monster->isFleeing() || fpp.maxTargetDist > 1)) { + Direction dir = NODIR; + + if (monster->isFleeing()) { + monster->getDistanceStep(followCreature->getPosition(), dir, true); + } else { //maxTargetDist > 1 + if (!monster->getDistanceStep(followCreature->getPosition(), dir)) { + // if we can't get anything then let the A* calculate + if (g_game.getPathToEx(this, followCreature->getPosition(), listWalkDir, fpp)) { + hasFollowPath = true; + startAutoWalk(listWalkDir); + } else { + hasFollowPath = false; + } + + return; + } + } + + if (dir != NODIR) { + listWalkDir.clear(); + listWalkDir.push_back(dir); + + hasFollowPath = true; + startAutoWalk(listWalkDir); + } + } else { + if (g_game.getPathToEx(this, followCreature->getPosition(), listWalkDir, fpp)) { + hasFollowPath = true; + startAutoWalk(listWalkDir); + } else { + hasFollowPath = false; + } + } + } + + onFollowCreatureComplete(followCreature); +} + +bool Creature::setFollowCreature(Creature* creature, bool fullPathSearch /*= false*/) +{ + if (creature) { + if (followCreature == creature) { + return true; + } + + const Position& creaturePos = creature->getPosition(); + + if (creaturePos.z != getPosition().z || !canSee(creaturePos)) { + followCreature = NULL; + return false; + } + + if (!listWalkDir.empty()) { + listWalkDir.clear(); + onWalkAborted(); + } + + hasFollowPath = false; + forceUpdateFollowPath = false; + followCreature = creature; + isUpdatingPath = true; + } else { + isUpdatingPath = false; + followCreature = NULL; + } + + onFollowCreature(creature); + return true; +} + +double Creature::getDamageRatio(Creature* attacker) const +{ + int32_t totalDamage = 0; + int32_t attackerDamage = 0; + + CountBlock_t cb; + + for (CountMap::const_iterator it = damageMap.begin(), end = damageMap.end(); it != end; ++it) { + cb = it->second; + totalDamage += cb.total; + + if (it->first == attacker->getID()) { + attackerDamage += cb.total; + } + } + + return ((double)attackerDamage / totalDamage); +} + +uint64_t Creature::getGainedExperience(Creature* attacker) const +{ + return std::floor(getDamageRatio(attacker) * getLostExperience()); +} + +void Creature::addDamagePoints(Creature* attacker, int32_t damagePoints) +{ + if (damagePoints <= 0) { + return; + } + + uint32_t attackerId = (attacker ? attacker->getID() : 0); + + CountMap::iterator it = damageMap.find(attackerId); + + if (it == damageMap.end()) { + CountBlock_t cb; + cb.ticks = OTSYS_TIME(); + cb.total = damagePoints; + damageMap[attackerId] = cb; + } else { + it->second.total += damagePoints; + it->second.ticks = OTSYS_TIME(); + } + + lastHitCreature = attackerId; +} + +void Creature::addHealPoints(Creature* caster, int32_t healthPoints) +{ + if (healthPoints <= 0) { + return; + } + + uint32_t casterId = (caster ? caster->getID() : 0); + + CountMap::iterator it = healMap.find(casterId); + + if (it == healMap.end()) { + CountBlock_t cb; + cb.ticks = OTSYS_TIME(); + cb.total = healthPoints; + healMap[casterId] = cb; + } else { + it->second.total += healthPoints; + it->second.ticks = OTSYS_TIME(); + } +} + +void Creature::onAddCondition(ConditionType_t type) +{ + if (type == CONDITION_PARALYZE && hasCondition(CONDITION_HASTE)) { + removeCondition(CONDITION_HASTE); + } else if (type == CONDITION_HASTE && hasCondition(CONDITION_PARALYZE)) { + removeCondition(CONDITION_PARALYZE); + } +} + +void Creature::onAddCombatCondition(ConditionType_t type) +{ + // +} + +void Creature::onEndCondition(ConditionType_t type) +{ + // +} + +void Creature::onTickCondition(ConditionType_t type, bool& bRemove) +{ + const MagicField* field = getTile()->getFieldItem(); + + if (!field) { + return; + } + + switch (type) { + case CONDITION_FIRE: + bRemove = (field->getCombatType() != COMBAT_FIREDAMAGE); + break; + case CONDITION_ENERGY: + bRemove = (field->getCombatType() != COMBAT_ENERGYDAMAGE); + break; + case CONDITION_POISON: + bRemove = (field->getCombatType() != COMBAT_EARTHDAMAGE); + break; + case CONDITION_FREEZING: + bRemove = (field->getCombatType() != COMBAT_ICEDAMAGE); + break; + case CONDITION_DAZZLED: + bRemove = (field->getCombatType() != COMBAT_HOLYDAMAGE); + break; + case CONDITION_CURSED: + bRemove = (field->getCombatType() != COMBAT_DEATHDAMAGE); + break; + case CONDITION_DROWN: + bRemove = (field->getCombatType() != COMBAT_DROWNDAMAGE); + break; + case CONDITION_BLEEDING: + bRemove = (field->getCombatType() != COMBAT_PHYSICALDAMAGE); + break; + default: + break; + } +} + +void Creature::onCombatRemoveCondition(const Creature* attacker, Condition* condition) +{ + removeCondition(condition); +} + +void Creature::onAttackedCreature(Creature* target) +{ + // +} + +void Creature::onAttacked() +{ + // +} + +void Creature::onAttackedCreatureDrainHealth(Creature* target, int32_t points) +{ + target->addDamagePoints(this, points); + + Creature* masterCreature = getMaster(); + + if (!masterCreature) { + return; + } + + Player* masterPlayer = masterCreature->getPlayer(); + + if (masterPlayer) { + std::ostringstream ss; + ss << "Your " << asLowerCaseString(getName()) << " deals " << points << " to " << target->getNameDescription() << "."; + masterPlayer->sendTextMessage(MSG_EVENT_DEFAULT, ss.str()); + } +} + +void Creature::onTargetCreatureGainHealth(Creature* target, int32_t points) +{ + target->addHealPoints(this, points); +} + +void Creature::onAttackedCreatureKilled(Creature* target) +{ + if (target != this) { + uint64_t gainExp = target->getGainedExperience(this); + onGainExperience(gainExp, target); + } +} + +bool Creature::onKilledCreature(Creature* target, bool lastHit/* = true*/) +{ + if (getMaster()) { + getMaster()->onKilledCreature(target); + } + + //scripting event - onKill + CreatureEventList killEvents = getCreatureEvents(CREATURE_EVENT_KILL); + + for (CreatureEventList::const_iterator it = killEvents.begin(), end = killEvents.end(); it != end; ++it) { + (*it)->executeOnKill(this, target); + } + + return false; +} + +void Creature::onGainExperience(uint64_t gainExp, Creature* target) +{ + if (gainExp != 0 && getMaster()) { + gainExp = gainExp / 2; + getMaster()->onGainExperience(gainExp, target); + + const Position& targetPos = getPosition(); + + std::ostringstream ssExp; + ssExp << ucfirst(getNameDescription()) << " gained " << gainExp << " experience points."; + std::string strExp = ssExp.str(); + + SpectatorVec list; + g_game.getSpectators(list, targetPos, false, true); + + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + (*it)->getPlayer()->sendExperienceMessage(MSG_EXPERIENCE_OTHERS, strExp, targetPos, gainExp, TEXTCOLOR_WHITE_EXP); + } + } +} + +void Creature::onGainSharedExperience(uint64_t gainExp) +{ + // +} + +void Creature::onAttackedCreatureBlockHit(Creature* target, BlockType_t blockType) +{ + // +} + +void Creature::onBlockHit(BlockType_t blockType) +{ + // +} + +void Creature::addSummon(Creature* creature) +{ + //std::cout << "addSummon: " << this << " summon=" << creature << std::endl; + creature->setDropLoot(false); + creature->setLossSkill(false); + creature->setMaster(this); + creature->useThing2(); + summons.push_back(creature); +} + +void Creature::removeSummon(const Creature* creature) +{ + //std::cout << "removeSummon: " << this << " summon=" << creature << std::endl; + std::list::iterator cit = std::find(summons.begin(), summons.end(), creature); + + if (cit != summons.end()) { + (*cit)->setDropLoot(false); + (*cit)->setLossSkill(true); + (*cit)->setMaster(NULL); + (*cit)->releaseThing2(); + summons.erase(cit); + } +} + +bool Creature::addCondition(Condition* condition, bool force/* = false*/) +{ + if (condition == NULL) { + return false; + } + + if (!force && condition->getType() == CONDITION_HASTE && hasCondition(CONDITION_PARALYZE)) { + int64_t walkDelay = getWalkDelay(); + + if (walkDelay > 0) { + g_scheduler.addEvent(createSchedulerTask(walkDelay, boost::bind(&Game::forceAddCondition, &g_game, getID(), condition))); + return false; + } + } + + Condition* prevCond = getCondition(condition->getType(), condition->getId(), condition->getSubId()); + + if (prevCond) { + prevCond->addCondition(this, condition); + delete condition; + return true; + } + + if (condition->startCondition(this)) { + conditions.push_back(condition); + onAddCondition(condition->getType()); + return true; + } + + delete condition; + return false; +} + +bool Creature::addCombatCondition(Condition* condition) +{ + //Caution: condition variable could be deleted after the call to addCondition + ConditionType_t type = condition->getType(); + + if (!addCondition(condition)) { + return false; + } + + onAddCombatCondition(type); + return true; +} + +void Creature::removeCondition(ConditionType_t type, bool force/* = false*/) +{ + for (ConditionList::iterator it = conditions.begin(); it != conditions.end();) { + if ((*it)->getType() != type) { + ++it; + continue; + } + + Condition* condition = *it; + + if (!force && (*it)->getType() == CONDITION_PARALYZE) { + int64_t walkDelay = getWalkDelay(); + + if (walkDelay > 0) { + g_scheduler.addEvent(createSchedulerTask(walkDelay, boost::bind(&Game::forceRemoveCondition, &g_game, getID(), type))); + return; + } + } + + it = conditions.erase(it); + + condition->endCondition(this, CONDITIONEND_ABORT); + delete condition; + + onEndCondition(type); + } +} + +void Creature::removeCondition(ConditionType_t type, ConditionId_t id, bool force/* = false*/) +{ + for (ConditionList::iterator it = conditions.begin(); it != conditions.end();) { + if ((*it)->getType() != type || (*it)->getId() != id) { + ++it; + continue; + } + + Condition* condition = *it; + + if (!force && (*it)->getType() == CONDITION_PARALYZE) { + int64_t walkDelay = getWalkDelay(); + + if (walkDelay > 0) { + g_scheduler.addEvent(createSchedulerTask(walkDelay, boost::bind(&Game::forceRemoveCondition, &g_game, getID(), type))); + return; + } + } + + it = conditions.erase(it); + + condition->endCondition(this, CONDITIONEND_ABORT); + delete condition; + + onEndCondition(type); + } +} + +void Creature::removeCondition(const Creature* attacker, ConditionType_t type) +{ + ConditionList tmpList = conditions; + + for (ConditionList::iterator it = tmpList.begin(), end = tmpList.end(); it != end; ++it) { + if ((*it)->getType() == type) { + onCombatRemoveCondition(attacker, *it); + } + } +} + +void Creature::removeCondition(Condition* condition, bool force/* = false*/) +{ + ConditionList::iterator it = std::find(conditions.begin(), conditions.end(), condition); + + if (it == conditions.end()) { + return; + } + + if (!force && condition->getType() == CONDITION_PARALYZE) { + int64_t walkDelay = getWalkDelay(); + + if (walkDelay > 0) { + g_scheduler.addEvent(createSchedulerTask(walkDelay, boost::bind(&Game::forceRemoveCondition, &g_game, getID(), condition->getType()))); + return; + } + } + + conditions.erase(it); + + condition->endCondition(this, CONDITIONEND_ABORT); + onEndCondition(condition->getType()); + delete condition; +} + +Condition* Creature::getCondition(ConditionType_t type) const +{ + for (ConditionList::const_iterator it = conditions.begin(), end = conditions.end(); it != end; ++it) { + if ((*it)->getType() == type) { + return *it; + } + } + + return NULL; +} + +Condition* Creature::getCondition(ConditionType_t type, ConditionId_t id, uint32_t subId/* = 0*/) const +{ + for (ConditionList::const_iterator it = conditions.begin(), end = conditions.end(); it != end; ++it) { + if ((*it)->getType() == type && (*it)->getId() == id && (*it)->getSubId() == subId) { + return *it; + } + } + + return NULL; +} + +void Creature::executeConditions(uint32_t interval) +{ + for (ConditionList::iterator it = conditions.begin(); it != conditions.end();) { + if (!(*it)->executeCondition(this, interval)) { + ConditionType_t type = (*it)->getType(); + + Condition* condition = *it; + it = conditions.erase(it); + + condition->endCondition(this, CONDITIONEND_TICKS); + delete condition; + + onEndCondition(type); + } else { + ++it; + } + } +} + +bool Creature::hasCondition(ConditionType_t type, uint32_t subId/* = 0*/) const +{ + if (type == CONDITION_EXHAUST_COMBAT && g_game.getStateTime() == 0) { + return true; + } + + if (isSuppress(type)) { + return false; + } + + for (ConditionList::const_iterator it = conditions.begin(), end = conditions.end(); it != end; ++it) { + if ((*it)->getType() != type || (*it)->getSubId() != subId) { + continue; + } + + if (g_config.getBoolean(ConfigManager::OLD_CONDITION_ACCURACY)) { + return true; + } + + if ((*it)->getEndTime() == 0) { + return true; + } + + int64_t seekTime = g_game.getStateTime(); + + if (seekTime == 0) { + return true; + } + + if ((*it)->getEndTime() >= seekTime) { + seekTime = (*it)->getEndTime(); + } + + if (seekTime >= OTSYS_TIME()) { + return true; + } + } + + return false; +} + +bool Creature::isImmune(CombatType_t type) const +{ + return hasBitSet((uint32_t)type, getDamageImmunities()); +} + +bool Creature::isImmune(ConditionType_t type) const +{ + return hasBitSet((uint32_t)type, getConditionImmunities()); +} + +bool Creature::isSuppress(ConditionType_t type) const +{ + return hasBitSet((uint32_t)type, getConditionSuppressions()); +} + +std::string Creature::getDescription(int32_t lookDistance) const +{ + std::string str = "a creature"; + return str; +} + +int32_t Creature::getStepDuration(Direction dir) const +{ + int32_t stepDuration = getStepDuration(); + + if (dir == NORTHWEST || dir == NORTHEAST || dir == SOUTHWEST || dir == SOUTHEAST) { + stepDuration *= 3; + } + + return stepDuration; +} + +int32_t Creature::getStepDuration() const +{ + if (isRemoved()) { + return 0; + } + + uint32_t calculatedStepSpeed; + uint32_t groundSpeed; + + int32_t stepSpeed = getStepSpeed(); + + if (stepSpeed > -Creature::speedB) { + calculatedStepSpeed = floor((Creature::speedA * log((stepSpeed / 2) + Creature::speedB) + Creature::speedC) + 0.5); + + if (calculatedStepSpeed <= 0) { + calculatedStepSpeed = 1; + } + } else { + calculatedStepSpeed = 1; + } + + const Tile* tile = getTile(); + + if (tile && tile->ground) { + uint32_t groundId = tile->ground->getID(); + groundSpeed = Item::items[groundId].speed; + + if (groundSpeed == 0) { + groundSpeed = 150; + } + } else { + groundSpeed = 150; + } + + double duration = std::floor(1000 * groundSpeed / calculatedStepSpeed); + + int32_t stepDuration = std::ceil(duration / 50) * 50; + + const Monster* monster = getMonster(); + + if (monster && monster->isTargetNearby() && !monster->isFleeing() && !monster->getMaster()) { + stepDuration <<= 1; + } + + return stepDuration; +} + +int64_t Creature::getEventStepTicks(bool onlyDelay) const +{ + int64_t ret = getWalkDelay(); + + if (ret <= 0) { + int32_t stepDuration = getStepDuration(); + + if (onlyDelay && stepDuration > 0) { + ret = 1; + } else { + ret = stepDuration * lastStepCost; + } + } + + return ret; +} + +void Creature::getCreatureLight(LightInfo& light) const +{ + light = internalLight; +} + +void Creature::setNormalCreatureLight() +{ + internalLight.level = 0; + internalLight.color = 0; +} + +bool Creature::registerCreatureEvent(const std::string& name) +{ + CreatureEvent* event = g_creatureEvents->getEventByName(name); + + if (!event) { + return false; + } + + CreatureEventType_t type = event->getEventType(); + + if (hasEventRegistered(type)) { + //check for duplicates + for (CreatureEventList::const_iterator it = eventsList.begin(); it != eventsList.end(); ++it) { + if (*it == event) { + return false; + } + } + } else { + //set the bit + scriptEventsBitField = scriptEventsBitField | ((uint32_t)1 << type); + } + + eventsList.push_back(event); + return true; +} + +CreatureEventList Creature::getCreatureEvents(CreatureEventType_t type) +{ + CreatureEventList tmpEventList; + + if (!hasEventRegistered(type)) { + return tmpEventList; + } + + for (CreatureEventList::const_iterator it = eventsList.begin(), end = eventsList.end(); it != end; ++it) { + if ((*it)->getEventType() == type) { + tmpEventList.push_back(*it); + } + } + + return tmpEventList; +} + +FrozenPathingConditionCall::FrozenPathingConditionCall(const Position& _targetPos) +{ + targetPos = _targetPos; +} + +bool FrozenPathingConditionCall::isInRange(const Position& startPos, const Position& testPos, + const FindPathParams& fpp) const +{ + int32_t dxMax = ((fpp.fullPathSearch || (startPos.x - targetPos.x) >= 0) ? fpp.maxTargetDist : 0); + + if (testPos.x > targetPos.x + dxMax) { + return false; + } + + int32_t dxMin = ((fpp.fullPathSearch || (startPos.x - targetPos.x) <= 0) ? fpp.maxTargetDist : 0); + + if (testPos.x < targetPos.x - dxMin) { + return false; + } + + int32_t dyMax = ((fpp.fullPathSearch || (startPos.y - targetPos.y) >= 0) ? fpp.maxTargetDist : 0); + + if (testPos.y > targetPos.y + dyMax) { + return false; + } + + int32_t dyMin = ((fpp.fullPathSearch || (startPos.y - targetPos.y) <= 0) ? fpp.maxTargetDist : 0); + + if (testPos.y < targetPos.y - dyMin) { + return false; + } + + return true; +} + +bool FrozenPathingConditionCall::operator()(const Position& startPos, const Position& testPos, + const FindPathParams& fpp, int32_t& bestMatchDist) const +{ + if (!isInRange(startPos, testPos, fpp)) { + return false; + } + + if (fpp.clearSight && !g_game.isSightClear(testPos, targetPos, true)) { + return false; + } + + int32_t testDist = std::max(std::abs(targetPos.x - testPos.x), std::abs(targetPos.y - testPos.y)); + + if (fpp.maxTargetDist == 1) { + if (testDist < fpp.minTargetDist || testDist > fpp.maxTargetDist) { + return false; + } + + return true; + } else if (testDist <= fpp.maxTargetDist) { + if (testDist < fpp.minTargetDist) { + return false; + } + + if (testDist == fpp.maxTargetDist) { + bestMatchDist = 0; + return true; + } else if (testDist > bestMatchDist) { + //not quite what we want, but the best so far + bestMatchDist = testDist; + return true; + } + } + + return false; +} diff --git a/src/creature.h b/src/creature.h new file mode 100644 index 0000000000..5b70d795f3 --- /dev/null +++ b/src/creature.h @@ -0,0 +1,589 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_CREATURE_H__ +#define __OTSERV_CREATURE_H__ + +#include "definitions.h" + +#include "templates.h" +#include "map.h" +#include "position.h" +#include "condition.h" +#include "const.h" +#include "tile.h" +#include "enums.h" +#include "creatureevent.h" + +#include + +typedef std::list ConditionList; +typedef std::list CreatureEventList; + +enum slots_t { + SLOT_WHEREEVER = 0, + SLOT_FIRST = 1, + SLOT_HEAD = SLOT_FIRST, + SLOT_NECKLACE = 2, + SLOT_BACKPACK = 3, + SLOT_ARMOR = 4, + SLOT_RIGHT = 5, + SLOT_LEFT = 6, + SLOT_LEGS = 7, + SLOT_FEET = 8, + SLOT_RING = 9, + SLOT_AMMO = 10, + SLOT_DEPOT = 11, + SLOT_LAST = SLOT_DEPOT +}; + +struct FindPathParams { + bool fullPathSearch; + bool clearSight; + bool allowDiagonal; + bool keepDistance; + int32_t maxSearchDist; + int32_t minTargetDist; + int32_t maxTargetDist; + + FindPathParams() { + fullPathSearch = true; + clearSight = true; + allowDiagonal = true; + keepDistance = false; + maxSearchDist = -1; + minTargetDist = -1; + maxTargetDist = -1; + } +}; + +class Map; +class Thing; +class Container; +class Player; +class Monster; +class Npc; +class Item; +class Tile; + +#define EVENT_CREATURECOUNT 10 +#define EVENT_CREATURE_THINK_INTERVAL 1000 +#define EVENT_CHECK_CREATURE_INTERVAL (EVENT_CREATURE_THINK_INTERVAL / EVENT_CREATURECOUNT) + +class FrozenPathingConditionCall +{ + public: + FrozenPathingConditionCall(const Position& _targetPos); + ~FrozenPathingConditionCall() {} + + bool operator()(const Position& startPos, const Position& testPos, + const FindPathParams& fpp, int32_t& bestMatchDist) const; + + bool isInRange(const Position& startPos, const Position& testPos, + const FindPathParams& fpp) const; + + protected: + Position targetPos; +}; + +////////////////////////////////////////////////////////////////////// +// Defines the Base class for all creatures and base functions which +// every creature has + +class Creature : public AutoID, virtual public Thing +{ + protected: + Creature(); + + public: + static double speedA, speedB, speedC; + + virtual ~Creature(); + + virtual Creature* getCreature() { + return this; + } + virtual const Creature* getCreature()const { + return this; + } + virtual Player* getPlayer() { + return NULL; + } + virtual const Player* getPlayer() const { + return NULL; + } + virtual Npc* getNpc() { + return NULL; + } + virtual const Npc* getNpc() const { + return NULL; + } + virtual Monster* getMonster() { + return NULL; + } + virtual const Monster* getMonster() const { + return NULL; + } + + virtual const std::string& getName() const = 0; + virtual const std::string& getNameDescription() const = 0; + virtual std::string getDescription(int32_t lookDistance) const; + + virtual CreatureType_t getType() const = 0; + + void setID() { + /* + * 0x20000000 - Player + * 0x30000000 - NPC + * 0x40000000 - Monster + */ + this->id = auto_id | this->idRange(); + } + void setRemoved() { + isInternalRemoved = true; + } + + virtual uint32_t idRange() = 0; + uint32_t getID() const { + return id; + } + virtual void removeList() = 0; + virtual void addList() = 0; + + virtual bool canSee(const Position& pos) const; + virtual bool canSeeCreature(const Creature* creature) const; + + virtual RaceType_t getRace() const { + return RACE_NONE; + } + Direction getDirection() const { + return direction; + } + void setDirection(Direction dir) { + direction = dir; + } + + const Position& getMasterPos() const { + return masterPos; + } + void setMasterPos(const Position& pos, uint32_t radius = 1) { + masterPos = pos; + masterRadius = radius; + } + + bool isHealthHidden() const { + return hiddenHealth; + } + void setHiddenHealth(bool b) { + hiddenHealth = b; + } + + virtual int32_t getThrowRange() const { + return 1; + } + virtual bool isPushable() const { + return (getWalkDelay() <= 0); + } + virtual bool isRemoved() const { + return isInternalRemoved; + } + virtual bool canSeeInvisibility() const { + return false; + } + virtual bool isInGhostMode() const { + return false; + } + + int32_t getWalkDelay(Direction dir) const; + int32_t getWalkDelay() const; + int64_t getTimeSinceLastMove() const; + + int64_t getEventStepTicks(bool onlyDelay = false) const; + int32_t getStepDuration(Direction dir) const; + int32_t getStepDuration() const; + virtual int32_t getStepSpeed() const { + return getSpeed(); + } + int32_t getSpeed() const { + return baseSpeed + varSpeed; + } + void setSpeed(int32_t varSpeedDelta) { + int32_t oldSpeed = getSpeed(); + varSpeed = varSpeedDelta; + + if (getSpeed() <= 0) { + stopEventWalk(); + cancelNextWalk = true; + } else if (oldSpeed <= 0 && !listWalkDir.empty()) { + addEventWalk(); + } + } + + void setBaseSpeed(uint32_t newBaseSpeed) { + baseSpeed = newBaseSpeed; + } + int32_t getBaseSpeed() const { + return baseSpeed; + } + + virtual int32_t getHealth() const { + return health; + } + virtual int32_t getMaxHealth() const { + return healthMax; + } + virtual int32_t getMana() const { + return mana; + } + virtual int32_t getMaxMana() const { + return manaMax; + } + + const Outfit_t getCurrentOutfit() const { + return currentOutfit; + } + void setCurrentOutfit(Outfit_t outfit) { + currentOutfit = outfit; + } + const Outfit_t getDefaultOutfit() const { + return defaultOutfit; + } + bool isInvisible() const { + return hasCondition(CONDITION_INVISIBLE); + } + ZoneType_t getZone() const { + return getTile()->getZone(); + } + + //walk functions + bool startAutoWalk(std::list& listDir); + void addEventWalk(bool firstStep = false); + void stopEventWalk(); + virtual void goToFollowCreature(); + + //walk events + virtual void onWalk(Direction& dir); + virtual void onWalkAborted() {} + virtual void onWalkComplete() {} + + //follow functions + virtual Creature* getFollowCreature() const { + return followCreature; + } + virtual bool setFollowCreature(Creature* creature, bool fullPathSearch = false); + + //follow events + virtual void onFollowCreature(const Creature* creature) {} + virtual void onFollowCreatureComplete(const Creature* creature) {} + + //combat functions + Creature* getAttackedCreature() { + return attackedCreature; + } + virtual bool setAttackedCreature(Creature* creature); + virtual BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, + bool checkDefense = false, bool checkArmor = false); + + void setMaster(Creature* creature) { + master = creature; + } + Creature* getMaster() { + return master; + } + bool isSummon() const { + return master != NULL; + } + const Creature* getMaster() const { + return master; + } + + virtual void addSummon(Creature* creature); + virtual void removeSummon(const Creature* creature); + const std::list& getSummons() { + return summons; + } + + virtual int32_t getArmor() const { + return 0; + } + virtual int32_t getDefense() const { + return 0; + } + virtual float getAttackFactor() const { + return 1.0f; + } + virtual float getDefenseFactor() const { + return 1.0f; + } + + bool addCondition(Condition* condition, bool force = false); + bool addCombatCondition(Condition* condition); + void removeCondition(ConditionType_t type, ConditionId_t id, bool force = false); + void removeCondition(ConditionType_t type, bool force = false); + void removeCondition(Condition* condition, bool force = false); + void removeCondition(const Creature* attacker, ConditionType_t type); + Condition* getCondition(ConditionType_t type) const; + Condition* getCondition(ConditionType_t type, ConditionId_t id, uint32_t subId = 0) const; + void executeConditions(uint32_t interval); + bool hasCondition(ConditionType_t type, uint32_t subId = 0) const; + virtual bool isImmune(ConditionType_t type) const; + virtual bool isImmune(CombatType_t type) const; + virtual bool isSuppress(ConditionType_t type) const; + virtual uint32_t getDamageImmunities() const { + return 0; + } + virtual uint32_t getConditionImmunities() const { + return 0; + } + virtual uint32_t getConditionSuppressions() const { + return 0; + } + virtual bool isAttackable() const { + return true; + } + + virtual void changeHealth(int32_t healthChange, bool sendHealthChange = true); + virtual void changeMana(int32_t manaChange); + + virtual void drainHealth(Creature* attacker, CombatType_t combatType, int32_t damage); + virtual void drainMana(Creature* attacker, int32_t manaLoss); + + virtual bool challengeCreature(Creature* creature) { + return false; + } + virtual bool convinceCreature(Creature* creature) { + return false; + } + + virtual void onDeath(); + virtual uint64_t getGainedExperience(Creature* attacker) const; + void addDamagePoints(Creature* attacker, int32_t damagePoints); + void addHealPoints(Creature* caster, int32_t healthPoints); + bool hasBeenAttacked(uint32_t attackerId); + + //combat event functions + virtual void onAddCondition(ConditionType_t type); + virtual void onAddCombatCondition(ConditionType_t type); + virtual void onEndCondition(ConditionType_t type); + virtual void onTickCondition(ConditionType_t type, bool& bRemove); + virtual void onCombatRemoveCondition(const Creature* attacker, Condition* condition); + virtual void onAttackedCreature(Creature* target); + virtual void onAttacked(); + virtual void onAttackedCreatureDrainHealth(Creature* target, int32_t points); + virtual void onTargetCreatureGainHealth(Creature* target, int32_t points); + virtual void onAttackedCreatureKilled(Creature* target); + virtual bool onKilledCreature(Creature* target, bool lastHit = true); + virtual void onGainExperience(uint64_t gainExp, Creature* target); + virtual void onGainSharedExperience(uint64_t gainExp); + virtual void onAttackedCreatureBlockHit(Creature* target, BlockType_t blockType); + virtual void onBlockHit(BlockType_t blockType); + virtual void onChangeZone(ZoneType_t zone); + virtual void onAttackedCreatureChangeZone(ZoneType_t zone); + virtual void onIdleStatus(); + + virtual void getCreatureLight(LightInfo& light) const; + virtual void setNormalCreatureLight(); + void setCreatureLight(LightInfo& light) { + internalLight = light; + } + + virtual void onThink(uint32_t interval); + virtual void onAttacking(uint32_t interval); + virtual void onWalk(); + virtual bool getNextStep(Direction& dir, uint32_t& flags); + + virtual void onAddTileItem(const Tile* tile, const Position& pos, const Item* item); + virtual void onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem, + const ItemType& oldType, const Item* newItem, const ItemType& newType); + virtual void onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType, + const Item* item); + + virtual void onCreatureAppear(const Creature* creature, bool isLogin); + virtual void onCreatureDisappear(const Creature* creature, uint32_t stackpos, bool isLogout); + virtual void onCreatureMove(const Creature* creature, const Tile* newTile, const Position& newPos, + const Tile* oldTile, const Position& oldPos, bool teleport); + + virtual void onAttackedCreatureDisappear(bool isLogout) {} + virtual void onFollowCreatureDisappear(bool isLogout) {} + + virtual void onCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, + Position* pos = NULL) {} + + virtual void onCreatureConvinced(const Creature* convincer, const Creature* creature) {} + virtual void onPlacedCreature() {} + virtual void onRemovedCreature() {} + + virtual WeaponType_t getWeaponType() { + return WEAPON_NONE; + } + virtual bool getCombatValues(int32_t& min, int32_t& max) { + return false; + } + + size_t getSummonCount() const { + return summons.size(); + } + void setDropLoot(bool _lootDrop) { + lootDrop = _lootDrop; + } + void setLossSkill(bool _skillLoss) { + skillLoss = _skillLoss; + } + + //creature script events + bool registerCreatureEvent(const std::string& name); + + virtual void setParent(Cylinder* cylinder) { + _tile = dynamic_cast(cylinder); + _position = _tile->getTilePosition(); + Thing::setParent(cylinder); + } + + const Position& getPosition() const { + return _position; + } + + Tile* getTile() { + return _tile; + } + const Tile* getTile() const { + return _tile; + } + int32_t getWalkCache(const Position& pos) const; + + const Position& getLastPosition() { + return lastPosition; + } + void setLastPosition(Position newLastPos) { + lastPosition = newLastPos; + } + + static bool canSee(const Position& myPos, const Position& pos, uint32_t viewRangeX, uint32_t viewRangeY); + + virtual double getDamageRatio(Creature* attacker) const; + + protected: + static const int32_t mapWalkWidth = Map::maxViewportX * 2 + 1; + static const int32_t mapWalkHeight = Map::maxViewportY * 2 + 1; + bool localMapCache[mapWalkHeight][mapWalkWidth]; + + virtual bool useCacheMap() const { + return false; + } + + Tile* _tile; + Position _position; + uint32_t id; + bool isInternalRemoved; + bool isMapLoaded; + bool isUpdatingPath; + + // The creature onThink event vector this creature belongs to + // -1 represents that the creature isn't in any vector + int32_t checkCreatureVectorIndex; + bool creatureCheck; + + int32_t health, healthMax; + int32_t mana, manaMax; + + Outfit_t currentOutfit; + Outfit_t defaultOutfit; + + Position masterPos; + Position lastPosition; + int32_t masterRadius; + uint64_t lastStep; + uint32_t lastStepCost; + uint32_t baseSpeed; + int32_t varSpeed; + bool skillLoss; + bool lootDrop; + Direction direction; + ConditionList conditions; + LightInfo internalLight; + + //summon variables + Creature* master; + std::list summons; + + //follow variables + Creature* followCreature; + uint32_t eventWalk; + bool cancelNextWalk; + std::list listWalkDir; + uint32_t walkUpdateTicks; + bool hasFollowPath; + bool forceUpdateFollowPath; + bool hiddenHealth; + + //combat variables + Creature* attackedCreature; + Creature* _lastHitCreature; + Creature* _mostDamageCreature; + bool lastHitUnjustified; + bool mostDamageUnjustified; + + struct CountBlock_t { + int32_t total; + int64_t ticks; + }; + + typedef std::map CountMap; + CountMap damageMap; + CountMap healMap; + uint32_t lastHitCreature; + uint32_t blockCount; + uint32_t blockTicks; + + //creature script events + uint32_t scriptEventsBitField; + bool hasEventRegistered(CreatureEventType_t event) { + return (0 != (scriptEventsBitField & ((uint32_t)1 << event))); + } + CreatureEventList eventsList; + CreatureEventList getCreatureEvents(CreatureEventType_t type); + + void updateMapCache(); + void updateTileCache(const Tile* tile, int32_t dx, int32_t dy); + void updateTileCache(const Tile* tile, const Position& pos); + void onCreatureDisappear(const Creature* creature, bool isLogout); + virtual void doAttacking(uint32_t interval) {} + virtual bool hasExtraSwing() { + return false; + } + + virtual uint64_t getLostExperience() const { + return 0; + } + bool getKillers(Creature** lastHitCreature, Creature** mostDamageCreature); + virtual void dropLoot(Container* corpse) {} + virtual uint16_t getLookCorpse() const { + return 0; + } + virtual void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const; + virtual void death() {} + virtual bool dropCorpse(); + virtual Item* getCorpse(); + + friend class Game; + friend class Map; + friend class Commands; + friend class LuaScriptInterface; +}; + +#endif diff --git a/src/creatureevent.cpp b/src/creatureevent.cpp new file mode 100644 index 0000000000..74b1b2dae5 --- /dev/null +++ b/src/creatureevent.cpp @@ -0,0 +1,451 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "creatureevent.h" +#include "tools.h" +#include "player.h" + +CreatureEvents::CreatureEvents() : + m_scriptInterface("CreatureScript Interface") +{ + m_scriptInterface.initState(); +} + +CreatureEvents::~CreatureEvents() +{ + CreatureEventList::iterator it; + + for (it = m_creatureEvents.begin(); it != m_creatureEvents.end(); ++it) { + delete it->second; + } + + m_creatureEvents.clear(); +} + +void CreatureEvents::clear() +{ + //clear creature events + for (CreatureEventList::iterator it = m_creatureEvents.begin(); it != m_creatureEvents.end(); ++it) { + it->second->clearEvent(); + } + + //clear lua state + m_scriptInterface.reInitState(); +} + +LuaScriptInterface& CreatureEvents::getScriptInterface() +{ + return m_scriptInterface; +} + +std::string CreatureEvents::getScriptBaseName() +{ + return "creaturescripts"; +} + +Event* CreatureEvents::getEvent(const std::string& nodeName) +{ + if (asLowerCaseString(nodeName) == "event") { + return new CreatureEvent(&m_scriptInterface); + } + + return NULL; +} + +bool CreatureEvents::registerEvent(Event* event, xmlNodePtr p) +{ + CreatureEvent* creatureEvent = dynamic_cast(event); + + if (!creatureEvent) { + return false; + } + + if (creatureEvent->getEventType() == CREATURE_EVENT_NONE) { + std::cout << "Error: [CreatureEvents::registerEvent] Trying to register event without type!" << std::endl; + return false; + } + + CreatureEvent* oldEvent = getEventByName(creatureEvent->getName(), false); + + if (oldEvent) { + //if there was an event with the same that is not loaded + //(happens when realoading), it is reused + if (!oldEvent->isLoaded() && oldEvent->getEventType() == creatureEvent->getEventType()) { + oldEvent->copyEvent(creatureEvent); + } + + return false; + } else { + //if not, register it normally + m_creatureEvents[creatureEvent->getName()] = creatureEvent; + return true; + } +} + +CreatureEvent* CreatureEvents::getEventByName(const std::string& name, bool forceLoaded /*= true*/) +{ + CreatureEventList::iterator it = m_creatureEvents.find(name); + + if (it != m_creatureEvents.end()) { + if (!forceLoaded || it->second->isLoaded()) { + return it->second; + } + } + + return NULL; +} + +uint32_t CreatureEvents::playerLogin(Player* player) +{ + //fire global event if is registered + for (CreatureEventList::iterator it = m_creatureEvents.begin(); it != m_creatureEvents.end(); ++it) { + if (it->second->getEventType() == CREATURE_EVENT_LOGIN) { + if (!it->second->executeOnLogin(player)) { + return 0; + } + } + } + + return 1; +} + +uint32_t CreatureEvents::playerLogout(Player* player) +{ + //fire global event if is registered + for (CreatureEventList::iterator it = m_creatureEvents.begin(); it != m_creatureEvents.end(); ++it) { + if (it->second->getEventType() == CREATURE_EVENT_LOGOUT) { + if (!it->second->executeOnLogout(player)) { + return 0; + } + } + } + + return 1; +} + +uint32_t CreatureEvents::playerAdvance(Player* player, skills_t skill, uint32_t oldLevel, + uint32_t newLevel) +{ + for (CreatureEventList::iterator it = m_creatureEvents.begin(); it != m_creatureEvents.end(); ++it) { + if (it->second->getEventType() == CREATURE_EVENT_ADVANCE) { + if (!it->second->executeAdvance(dynamic_cast(player), skill, oldLevel, newLevel)) { + return 0; + } + } + } + + return 1; +} + +///////////////////////////////////// + +CreatureEvent::CreatureEvent(LuaScriptInterface* _interface) : + Event(_interface) +{ + m_type = CREATURE_EVENT_NONE; + m_isLoaded = false; +} + +bool CreatureEvent::configureEvent(xmlNodePtr p) +{ + std::string str; + + //Name that will be used in monster xml files and + // lua function to register events to reference this event + if (readXMLString(p, "name", str)) { + m_eventName = str; + } else { + std::cout << "Error: [CreatureEvent::configureEvent] No name for creature event." << std::endl; + return false; + } + + if (readXMLString(p, "type", str)) { + std::string tmpStr = asLowerCaseString(str); + + if (tmpStr == "login") { + m_type = CREATURE_EVENT_LOGIN; + } else if (tmpStr == "logout") { + m_type = CREATURE_EVENT_LOGOUT; + } else if (tmpStr == "think") { + m_type = CREATURE_EVENT_THINK; + } else if (tmpStr == "preparedeath") { + m_type = CREATURE_EVENT_PREPAREDEATH; + } else if (tmpStr == "death") { + m_type = CREATURE_EVENT_DEATH; + } else if (tmpStr == "kill") { + m_type = CREATURE_EVENT_KILL; + } else if (tmpStr == "advance") { + m_type = CREATURE_EVENT_ADVANCE; + } else { + std::cout << "[Error - CreatureEvent::configureEvent] No valid type for creature event." << str << std::endl; + return false; + } + } else { + std::cout << "[Error - CreatureEvent::configureEvent] No type for creature event." << std::endl; + return false; + } + + m_isLoaded = true; + return true; +} + +std::string CreatureEvent::getScriptEventName() +{ + //Depending on the type script event name is different + switch (m_type) { + case CREATURE_EVENT_LOGIN: + return "onLogin"; + + case CREATURE_EVENT_LOGOUT: + return "onLogout"; + + case CREATURE_EVENT_THINK: + return "onThink"; + + case CREATURE_EVENT_PREPAREDEATH: + return "onPrepareDeath"; + + case CREATURE_EVENT_DEATH: + return "onDeath"; + + case CREATURE_EVENT_KILL: + return "onKill"; + + case CREATURE_EVENT_ADVANCE: + return "onAdvance"; + + case CREATURE_EVENT_NONE: + default: + return ""; + } +} + +void CreatureEvent::copyEvent(CreatureEvent* creatureEvent) +{ + m_scriptId = creatureEvent->m_scriptId; + m_scriptInterface = creatureEvent->m_scriptInterface; + m_scripted = creatureEvent->m_scripted; + m_isLoaded = creatureEvent->m_isLoaded; +} + +void CreatureEvent::clearEvent() +{ + m_scriptId = 0; + m_scriptInterface = NULL; + m_scripted = false; + m_isLoaded = false; +} + +uint32_t CreatureEvent::executeOnLogin(Player* player) +{ + //onLogin(cid) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + env->setScriptId(m_scriptId, m_scriptInterface); + env->setRealPos(player->getPosition()); + + uint32_t cid = env->addThing(player); + + lua_State* L = m_scriptInterface->getLuaState(); + + m_scriptInterface->pushFunction(m_scriptId); + lua_pushnumber(L, cid); + + bool result = m_scriptInterface->callFunction(1); + m_scriptInterface->releaseScriptEnv(); + + return result; + } else { + std::cout << "[Error - CreatureEvent::executeOnLogin] Call stack overflow." << std::endl; + return 0; + } +} + +uint32_t CreatureEvent::executeOnLogout(Player* player) +{ + //onLogout(cid) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + env->setScriptId(m_scriptId, m_scriptInterface); + env->setRealPos(player->getPosition()); + + uint32_t cid = env->addThing(player); + + lua_State* L = m_scriptInterface->getLuaState(); + + m_scriptInterface->pushFunction(m_scriptId); + lua_pushnumber(L, cid); + + bool result = m_scriptInterface->callFunction(1); + m_scriptInterface->releaseScriptEnv(); + + return result; + } else { + std::cout << "[Error - CreatureEvent::executeOnLogout] Call stack overflow." << std::endl; + return 0; + } +} + +uint32_t CreatureEvent::executeOnThink(Creature* creature, uint32_t interval) +{ + //onThink(cid, interval) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + env->setScriptId(m_scriptId, m_scriptInterface); + env->setRealPos(creature->getPosition()); + + uint32_t cid = env->addThing(creature); + + lua_State* L = m_scriptInterface->getLuaState(); + + m_scriptInterface->pushFunction(m_scriptId); + lua_pushnumber(L, cid); + lua_pushnumber(L, interval); + + bool result = m_scriptInterface->callFunction(2); + m_scriptInterface->releaseScriptEnv(); + + return result; + } else { + std::cout << "[Error - CreatureEvent::executeOnThink] Call stack overflow." << std::endl; + return 0; + } +} + +uint32_t CreatureEvent::executeOnPrepareDeath(Player* player, Creature* killer) +{ + //onPrepareDeath(cid, killer) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + env->setScriptId(m_scriptId, m_scriptInterface); + env->setRealPos(player->getPosition()); + + uint32_t cid = env->addThing(player); + uint32_t killercid = env->addThing(killer); + + lua_State* L = m_scriptInterface->getLuaState(); + + m_scriptInterface->pushFunction(m_scriptId); + lua_pushnumber(L, cid); + lua_pushnumber(L, killercid); + + bool result = m_scriptInterface->callFunction(2); + m_scriptInterface->releaseScriptEnv(); + + return result; + } else { + std::cout << "[Error - CreatureEvent::executeOnPrepareDeath] Call stack overflow." << std::endl; + return 0; + } +} + +uint32_t CreatureEvent::executeOnDeath(Creature* creature, Item* corpse, Creature* killer, Creature* mostDamageKiller, bool lastHitUnjustified, bool mostDamageUnjustified) +{ + //onDeath(cid, corpse, lasthitkiller, mostdamagekiller, lasthitunjustified, mostdamageunjustified) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + env->setScriptId(m_scriptId, m_scriptInterface); + env->setRealPos(creature->getPosition()); + + uint32_t cid = env->addThing(creature); + uint32_t corpseid = env->addThing(corpse); + uint32_t killercid = env->addThing(killer); + uint32_t mostdamagekillercid = env->addThing(mostDamageKiller); + + lua_State* L = m_scriptInterface->getLuaState(); + + m_scriptInterface->pushFunction(m_scriptId); + lua_pushnumber(L, cid); + lua_pushnumber(L, corpseid); + lua_pushnumber(L, killercid); + lua_pushnumber(L, mostdamagekillercid); + lua_pushnumber(L, lastHitUnjustified); + lua_pushnumber(L, mostDamageUnjustified); + + bool result = m_scriptInterface->callFunction(6); + m_scriptInterface->releaseScriptEnv(); + + return result; + } else { + std::cout << "[Error - CreatureEvent::executeOnDeath] Call stack overflow." << std::endl; + return 0; + } +} + +uint32_t CreatureEvent::executeAdvance(Creature* creature, skills_t skill, uint32_t oldLevel, + uint32_t newLevel) +{ + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + env->setScriptId(m_scriptId, m_scriptInterface); + env->setRealPos(creature->getPosition()); + + uint32_t cid = env->addThing(creature); + + lua_State* L = m_scriptInterface->getLuaState(); + + m_scriptInterface->pushFunction(m_scriptId); + lua_pushnumber(L, cid); + lua_pushnumber(L, static_cast(skill)); + lua_pushnumber(L, oldLevel); + lua_pushnumber(L, newLevel); + + bool result = m_scriptInterface->callFunction(4); + m_scriptInterface->releaseScriptEnv(); + return result; + } else { + std::cout << "[Error - CreatureEvent::executeAdvance] Call stack overflow." << std::endl; + return 0; + } +} + +uint32_t CreatureEvent::executeOnKill(Creature* creature, Creature* target) +{ + //onKill(cid, target) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + env->setScriptId(m_scriptId, m_scriptInterface); + env->setRealPos(creature->getPosition()); + + uint32_t cid = env->addThing(creature); + uint32_t targetId = env->addThing(target); + + lua_State* L = m_scriptInterface->getLuaState(); + + m_scriptInterface->pushFunction(m_scriptId); + lua_pushnumber(L, cid); + lua_pushnumber(L, targetId); + + bool result = m_scriptInterface->callFunction(2); + m_scriptInterface->releaseScriptEnv(); + + return result; + } else { + std::cout << "[Error - CreatureEvent::executeOnKill] Call stack overflow." << std::endl; + return 0; + } +} diff --git a/src/creatureevent.h b/src/creatureevent.h new file mode 100644 index 0000000000..3583f807df --- /dev/null +++ b/src/creatureevent.h @@ -0,0 +1,105 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_CREATUREEVENT_H__ +#define __OTSERV_CREATUREEVENT_H__ + +#include "luascript.h" +#include "baseevents.h" +#include "enums.h" + +enum CreatureEventType_t { + CREATURE_EVENT_NONE, + CREATURE_EVENT_LOGIN, + CREATURE_EVENT_LOGOUT, + CREATURE_EVENT_THINK, + CREATURE_EVENT_PREPAREDEATH, + CREATURE_EVENT_DEATH, + CREATURE_EVENT_KILL, + CREATURE_EVENT_ADVANCE +}; + +class CreatureEvent; + +class CreatureEvents : public BaseEvents +{ + public: + CreatureEvents(); + virtual ~CreatureEvents(); + + // global events + uint32_t playerLogin(Player* player); + uint32_t playerLogout(Player* player); + uint32_t playerAdvance(Player* player, skills_t, uint32_t, uint32_t); + + CreatureEvent* getEventByName(const std::string& name, bool forceLoaded = true); + + protected: + virtual LuaScriptInterface& getScriptInterface(); + virtual std::string getScriptBaseName(); + virtual Event* getEvent(const std::string& nodeName); + virtual bool registerEvent(Event* event, xmlNodePtr p); + virtual void clear(); + + //creature events + typedef std::map CreatureEventList; + CreatureEventList m_creatureEvents; + + LuaScriptInterface m_scriptInterface; +}; + +class CreatureEvent : public Event +{ + public: + CreatureEvent(LuaScriptInterface* _interface); + virtual ~CreatureEvent() {} + + virtual bool configureEvent(xmlNodePtr p); + + CreatureEventType_t getEventType() const { + return m_type; + } + const std::string& getName() const { + return m_eventName; + } + bool isLoaded() const { + return m_isLoaded; + } + + void clearEvent(); + void copyEvent(CreatureEvent* creatureEvent); + + //scripting + uint32_t executeOnLogin(Player* player); + uint32_t executeOnLogout(Player* player); + uint32_t executeOnThink(Creature* creature, uint32_t interval); + uint32_t executeOnPrepareDeath(Player* player, Creature* killer); + uint32_t executeOnDeath(Creature* creature, Item* corpse, Creature* killer, Creature* mostDamageKiller, bool lastHitUnjustified, bool mostDamageUnjustified); + uint32_t executeOnKill(Creature* creature, Creature* target); + uint32_t executeAdvance(Creature*, skills_t, uint32_t, uint32_t); + // + + protected: + virtual std::string getScriptEventName(); + + std::string m_eventName; + CreatureEventType_t m_type; + bool m_isLoaded; +}; + +#endif diff --git a/src/cylinder.cpp b/src/cylinder.cpp new file mode 100644 index 0000000000..9b8d79886c --- /dev/null +++ b/src/cylinder.cpp @@ -0,0 +1,68 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "cylinder.h" + +VirtualCylinder* VirtualCylinder::virtualCylinder = new VirtualCylinder; + +int32_t Cylinder::__getIndexOfThing(const Thing* thing) const +{ + return -1; +} + +int32_t Cylinder::__getFirstIndex() const +{ + return -1; +} + +int32_t Cylinder::__getLastIndex() const +{ + return -1; +} + +uint32_t Cylinder::__getItemTypeCount(uint16_t itemId, int32_t subType /*= -1*/) const +{ + return 0; +} + +std::map& Cylinder::__getAllItemTypeCount(std::map& countMap) const +{ + return countMap; +} + +Thing* Cylinder::__getThing(uint32_t index) const +{ + return NULL; +} + +void Cylinder::__internalAddThing(Thing* thing) +{ + // +} + +void Cylinder::__internalAddThing(uint32_t index, Thing* thing) +{ + // +} + +void Cylinder::__startDecaying() +{ + // +} diff --git a/src/cylinder.h b/src/cylinder.h new file mode 100644 index 0000000000..28f7eaad6d --- /dev/null +++ b/src/cylinder.h @@ -0,0 +1,256 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_CYLINDER_H__ +#define __OTSERV_CYLINDER_H__ + +#include + +#include "definitions.h" +#include "thing.h" + +class Item; +class Creature; + +#define INDEX_WHEREEVER -1 + +enum cylinderflags_t { + FLAG_NOLIMIT = 1, //Bypass limits like capacity/container limits, blocking items/creatures etc. + FLAG_IGNOREBLOCKITEM = 2, //Bypass moveable blocking item checks + FLAG_IGNOREBLOCKCREATURE = 4, //Bypass creature checks + FLAG_CHILDISOWNER = 8, //Used by containers to query capacity of the carrier (player) + FLAG_PATHFINDING = 16, //An additional check is done for floor changing/teleport items + FLAG_IGNOREFIELDDAMAGE = 32, //Bypass field damage checks + FLAG_IGNORENOTMOVEABLE = 64, //Bypass check for movability + FLAG_IGNOREAUTOSTACK = 128 //__queryDestination will not try to stack items together +}; + +enum cylinderlink_t { + LINK_OWNER, + LINK_PARENT, + LINK_TOPPARENT, + LINK_NEAR +}; + +class Cylinder : virtual public Thing +{ + public: + /** + * Query if the cylinder can add an object + * \param index points to the destination index (inventory slot/container position) + * -1 is a internal value and means add to a empty position, with no destItem + * \param thing the object to move/add + * \param count is the amount that we want to move/add + * \param flags if FLAG_CHILDISOWNER if set the query is from a child-cylinder (check cap etc.) + * if FLAG_NOLIMIT is set blocking items/container limits is ignored + * \param actor the creature trying to add the thing + * \returns ReturnValue holds the return value + */ + virtual ReturnValue __queryAdd(int32_t index, const Thing* thing, uint32_t count, + uint32_t flags, Creature* actor = NULL) const = 0; + + /** + * Query the cylinder how much it can accept + * \param index points to the destination index (inventory slot/container position) + * -1 is a internal value and means add to a empty position, with no destItem + * \param item the object to move/add + * \param count is the amount that we want to move/add + * \param maxQueryCount is the max amount that the cylinder can accept + * \param flags optional flags to modifiy the default behaviour + * \returns ReturnValue holds the return value + */ + virtual ReturnValue __queryMaxCount(int32_t index, const Thing* thing, uint32_t count, uint32_t& maxQueryCount, + uint32_t flags) const = 0; + + /** + * Query if the cylinder can remove an object + * \param item the object to move/remove + * \param count is the amount that we want to remove + * \param flags optional flags to modifiy the default behaviour + * \returns ReturnValue holds the return value + */ + virtual ReturnValue __queryRemove(const Thing* thing, uint32_t count, uint32_t flags) const = 0; + + /** + * Query the destination cylinder + * \param index points to the destination index (inventory slot/container position), + * -1 is a internal value and means add to a empty position, with no destItem + * this method can change the index to point to the new cylinder index + * \param destItem is the destination object + * \param flags optional flags to modifiy the default behaviour + * this method can modifiy the flags + * \returns Cylinder returns the destination cylinder + */ + virtual Cylinder* __queryDestination(int32_t& index, const Thing* thing, Item** destItem, + uint32_t& flags) = 0; + + /** + * Add the object to the cylinder + * \param item is the object to add + */ + virtual void __addThing(Thing* thing) = 0; + + /** + * Add the object to the cylinder + * \param index points to the destination index (inventory slot/container position) + * \param item is the object to add + */ + virtual void __addThing(int32_t index, Thing* thing) = 0; + + /** + * Update the item count or type for an object + * \param thing is the object to update + * \param itemId is the new item id + * \param count is the new count value + */ + virtual void __updateThing(Thing* thing, uint16_t itemId, uint32_t count) = 0; + + /** + * Replace an object with a new + * \param index is the position to change (inventory slot/container position) + * \param thing is the object to update + */ + virtual void __replaceThing(uint32_t index, Thing* thing) = 0; + + /** + * Remove an object + * \param thing is the object to delete + * \param count is the new count value + */ + virtual void __removeThing(Thing* thing, uint32_t count) = 0; + + /** + * Is sent after an operation (move/add) to update internal values + * \param thing is the object that has been added + * \param index is the objects new index value + * \param link holds the relation the object has to the cylinder + */ + virtual void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) = 0; + + /** + * Is sent after an operation (move/remove) to update internal values + * \param thing is the object that has been removed + * \param index is the previous index of the removed object + * \param isCompleteRemoval indicates if the item was completely removed or just partially (stackables) + * \param link holds the relation the object has to the cylinder + */ + virtual void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, bool isCompleteRemoval, cylinderlink_t link = LINK_OWNER) = 0; + + /** + * Gets the index of an object + * \param thing the object to get the index value from + * \returns the index of the object, returns -1 if not found + */ + virtual int32_t __getIndexOfThing(const Thing* thing) const; + + /** + * Returns the first index + * \returns the first index, if not implemented -1 is returned + */ + virtual int32_t __getFirstIndex() const; + + /** + * Returns the last index + * \returns the last index, if not implemented -1 is returned + */ + virtual int32_t __getLastIndex() const; + + /** + * Gets the object based on index + * \returns the object, returns NULL if not found + */ + virtual Thing* __getThing(uint32_t index) const; + + /** + * Get the amount of items of a certain type + * \param itemId is the item type to the get the count of + * \param subType is the extra type an item can have such as charges/fluidtype, -1 means not used + * \param returns the amount of items of the asked item type + */ + virtual uint32_t __getItemTypeCount(uint16_t itemId, int32_t subType = -1) const; + + /** + * Get the amount of items of a all types + * \param countMap a map to put the itemID:count mapping in + * \param returns a map mapping item id to count (same as first argument) + */ + virtual std::map& __getAllItemTypeCount(std::map& countMap) const; + + /** + * Adds an object to the cylinder without sending to the client(s) + * \param thing is the object to add + */ + virtual void __internalAddThing(Thing* thing); + + /** + * Adds an object to the cylinder without sending to the client(s) + * \param thing is the object to add + * \param index points to the destination index (inventory slot/container position) + */ + virtual void __internalAddThing(uint32_t index, Thing* thing); + + virtual void __startDecaying(); +}; + +class VirtualCylinder : public Cylinder +{ + public: + static VirtualCylinder* virtualCylinder; + + virtual ReturnValue __queryAdd(int32_t index, const Thing* thing, uint32_t count, + uint32_t flags, Creature* actor = NULL) const { + return RET_NOTPOSSIBLE; + } + virtual ReturnValue __queryMaxCount(int32_t index, const Thing* thing, uint32_t count, + uint32_t& maxQueryCount, uint32_t flags) const { + return RET_NOTPOSSIBLE; + } + virtual ReturnValue __queryRemove(const Thing* thing, uint32_t count, uint32_t flags) const { + return RET_NOTPOSSIBLE; + } + virtual Cylinder* __queryDestination(int32_t& index, const Thing* thing, Item** destItem, + uint32_t& flags) { + return NULL; + } + + virtual void __addThing(Thing* thing) {} + virtual void __addThing(int32_t index, Thing* thing) {} + virtual void __updateThing(Thing* thing, uint16_t itemId, uint32_t count) {} + virtual void __replaceThing(uint32_t index, Thing* thing) {} + virtual void __removeThing(Thing* thing, uint32_t count) {} + + virtual void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) {} + virtual void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, bool isCompleteRemoval, + cylinderlink_t link = LINK_OWNER) {} + + virtual bool isPushable() const { + return false; + } + virtual int getThrowRange() const { + return 1; + } + virtual std::string getDescription(int32_t lookDistance) const { + return ""; + } + + virtual bool isRemoved() const { + return false; + } +}; + +#endif diff --git a/src/database.cpp b/src/database.cpp new file mode 100644 index 0000000000..17b206bc4f --- /dev/null +++ b/src/database.cpp @@ -0,0 +1,120 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "database.h" +#include + +#include "databasemysql.h" + +boost::recursive_mutex DBQuery::database_lock; + +Database* _Database::_instance = NULL; + +Database* _Database::getInstance() +{ + if (!_instance) { + _instance = new Database; + } + + return _instance; +} + +DBResult* _Database::verifyResult(DBResult* result) +{ + if (!result->next()) { + _instance->freeResult(result); + return NULL; + } + + return result; +} + +DBQuery::DBQuery() +{ + database_lock.lock(); +} + +DBQuery::~DBQuery() +{ + database_lock.unlock(); +} + +DBInsert::DBInsert(Database* db) +{ + m_db = db; + m_rows = 0; + + // checks if current database engine supports multiline INSERTs + m_multiLine = m_db->getParam(DBPARAM_MULTIINSERT) != 0; +} + +void DBInsert::setQuery(const std::string& query) +{ + m_query = query; + m_buf = ""; + m_rows = 0; +} + +bool DBInsert::addRow(const std::string& row) +{ + if (!m_multiLine) { + return m_db->executeQuery(m_query + "(" + row + ")" ); // executes INSERT for current row + } + + m_rows++; + + // adds new row to buffer + size_t size = m_buf.length(); + + if (size == 0) { + m_buf = "(" + row + ")"; + } else if (size > 8192) { + if (!execute()) { + return false; + } + + m_buf = "(" + row + ")"; + } else { + m_buf += ",(" + row + ")"; + } + + return true; +} + +bool DBInsert::addRow(std::ostringstream& row) +{ + bool ret = addRow(row.str()); + row.str(""); + return ret; +} + +bool DBInsert::execute() +{ + if (!m_multiLine || m_buf.length() == 0 || m_rows == 0) { + return true; + } + + m_rows = 0; + + // executes buffer + bool res = m_db->executeQuery(m_query + m_buf); + m_buf = ""; + return res; +} diff --git a/src/database.h b/src/database.h new file mode 100644 index 0000000000..e99bab1308 --- /dev/null +++ b/src/database.h @@ -0,0 +1,381 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_DATABASE_H__ +#define __OTSERV_DATABASE_H__ + +#include "definitions.h" +#include +#include +#include "enums.h" + +#define DATABASE_VIRTUAL +#define DATABASE_CLASS DatabaseMySQL +#define DBRES_CLASS MySQLResult +class DatabaseMySQL; +class MySQLResult; + +typedef DATABASE_CLASS Database; +typedef DBRES_CLASS DBResult; + +typedef std::map listNames_t; + +class DBQuery; + +enum DBParam_t { + DBPARAM_MULTIINSERT = 1 +}; + +class _Database +{ + public: + /** + * Singleton implementation. + * + * Retruns instance of database handler. Don't create database (or drivers) instances in your code - instead of it use Database::instance(). This method stores static instance of connection class internaly to make sure exacly one instance of connection is created for entire system. + * + * @return database connection handler singletor + */ + static Database* getInstance(); + + /** + * Database information. + * + * Returns currently used database attribute. + * + * @param DBParam_t parameter to get + * @return suitable for given parameter + */ + DATABASE_VIRTUAL bool getParam(DBParam_t param) { + return false; + } + + /** + * Database connected. + * + * Returns whether or not the database is connected. + * + * @return whether or not the database is connected. + */ + bool isConnected() const { + return m_connected; + } + + protected: + /** + * Transaction related methods. + * + * Methods for starting, commiting and rolling back transaction. Each of the returns boolean value. + * + * @return true on success, false on error + * @note + * If your database system doesn't support transactions you should return true - it's not feature test, code should work without transaction, just will lack integrity. + */ + friend class DBTransaction; + DATABASE_VIRTUAL bool beginTransaction() { + return 0; + } + DATABASE_VIRTUAL bool rollback() { + return 0; + } + DATABASE_VIRTUAL bool commit() { + return 0; + } + + public: + /** + * Executes command. + * + * Executes query which doesn't generates results (eg. INSERT, UPDATE, DELETE...). + * + * @param std::string query command + * @return true on success, false on error + */ + DATABASE_VIRTUAL bool executeQuery(const std::string& query) { + return 0; + } + + /** + * Returns ID of last inserted row + * + * @return id of last inserted row, 0 if last query did not result in any rows with auto_increment keys + */ + DATABASE_VIRTUAL uint64_t getLastInsertedRowID() { + return 0; + } + + /** + * Queries database. + * + * Executes query which generates results (mostly SELECT). + * + * @param std::string query + * @return results object (null on error) + */ + DATABASE_VIRTUAL DBResult* storeQuery(const std::string& query) { + return 0; + } + + /** + * Escapes string for query. + * + * Prepares string to fit SQL queries including quoting it. + * + * @param std::string string to be escaped + * @return quoted string + */ + DATABASE_VIRTUAL std::string escapeString(const std::string& s) { + return "''"; + } + + /** + * Escapes binary stream for query. + * + * Prepares binary stream to fit SQL queries. + * + * @param char* binary stream + * @param long stream length + * @return quoted string + */ + DATABASE_VIRTUAL std::string escapeBlob(const char* s, uint32_t length) { + return "''"; + } + + /** + * Resource freeing. + * + * @param DBResult* resource to be freed + */ + DATABASE_VIRTUAL void freeResult(DBResult* res) {} + + /** + * Retrieve id of last inserted row + * + * @return id on success, 0 if last query did not result on any rows with auto_increment keys + */ + DATABASE_VIRTUAL uint64_t getLastInsertId() { + return 0; + } + + /** + * Get database engine name + * + * @return the database engine name + */ + DATABASE_VIRTUAL std::string getClientName() { + return ""; + } + + /** + * Get database engine version + * + * @return the database engine version + */ + DATABASE_VIRTUAL const char* getClientVersion() { + return ""; + } + + /** + * Get database engine version + * + * @return the database engine version + */ + DATABASE_VIRTUAL uint64_t getClientVersionNumeric() { + return 0; + } + + protected: + _Database() : m_connected(false) {}; + DATABASE_VIRTUAL ~_Database() {}; + + DBResult* verifyResult(DBResult* result); + + bool m_connected; + + private: + static Database* _instance; +}; + +class _DBResult +{ + public: + /** Get the Integer value of a field in database + *\return The Integer value of the selected field and row + *\param s The name of the field + */ + DATABASE_VIRTUAL int32_t getDataInt(const std::string& s) { + return 0; + } + + /** Get the Long value of a field in database + *\return The Long value of the selected field and row + *\param s The name of the field + */ + DATABASE_VIRTUAL int64_t getDataLong(const std::string& s) { + return 0; + } + + /** Get the String of a field in database + *\return The String of the selected field and row + *\param s The name of the field + */ + DATABASE_VIRTUAL std::string getDataString(const std::string& s) { + return "''"; + } + + /** Get the blob of a field in database + *\return a PropStream that is initiated with the blob data field, if not exist it returns NULL. + *\param s The name of the field + */ + DATABASE_VIRTUAL const char* getDataStream(const std::string& s, unsigned long& size) { + return 0; + } + + /** + * Moves to next result in set. + * + * \return true if moved, false if there are no more results. + */ + DATABASE_VIRTUAL bool next() { + return false; + } + + listNames_t getListNames() const { + return m_listNames; + } + + protected: + _DBResult() {} + DATABASE_VIRTUAL ~_DBResult() {} + + listNames_t m_listNames; +}; + +/** + * Thread locking hack. + * + * By using this class for your queries you lock and unlock database for threads. +*/ +class DBQuery : public std::ostringstream +{ + friend class _Database; + + public: + DBQuery(); + virtual ~DBQuery(); + + protected: + static boost::recursive_mutex database_lock; +}; + +/** + * INSERT statement. + * + * Gives possibility to optimize multiple INSERTs on databases that support multiline INSERTs. + */ +class DBInsert +{ + public: + /** + * Associates with given database handler. + * + * @param Database* database wrapper + */ + DBInsert(Database* db); + ~DBInsert() {} + + /** + * Sets query prototype. + * + * @param std::string& INSERT query + */ + void setQuery(const std::string& query); + + /** + * Adds new row to INSERT statement. + * + * On databases that doesn't support multiline INSERTs it simply execute INSERT for each row. + * + * @param std::string& row data + */ + bool addRow(const std::string& row); + /** + * Allows to use addRow() with stringstream as parameter. + */ + bool addRow(std::ostringstream& row); + + /** + * Executes current buffer. + */ + bool execute(); + + /** + * Returns ID of the inserted column if it had a AUTO_INCREMENT key + */ + uint64_t getInsertID(); + + protected: + Database* m_db; + bool m_multiLine; + uint32_t m_rows; + std::string m_query; + std::string m_buf; +}; + +#include "databasemysql.h" + +class DBTransaction +{ + public: + DBTransaction(Database* database) { + m_database = database; + m_state = STATE_NO_START; + } + + ~DBTransaction() { + if (m_state == STATE_START) { + m_database->rollback(); + } + } + + bool begin() { + m_state = STATE_START; + return m_database->beginTransaction(); + } + + bool commit() { + if (m_state != STATE_START) { + return false; + } + + m_state = STEATE_COMMIT; + return m_database->commit(); + } + + private: + enum TransactionStates_t { + STATE_NO_START, + STATE_START, + STEATE_COMMIT + }; + + TransactionStates_t m_state; + Database* m_database; +}; + +#endif diff --git a/src/databasemanager.cpp b/src/databasemanager.cpp new file mode 100644 index 0000000000..a6c3b276be --- /dev/null +++ b/src/databasemanager.cpp @@ -0,0 +1,503 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" +#include "enums.h" +#include +#include + +#include "databasemanager.h" +#include "tools.h" + +#include "ban.h" + +#include "configmanager.h" +extern ConfigManager g_config; + +bool DatabaseManager::optimizeTables() +{ + Database* db = Database::getInstance(); + DBQuery query; + + query << "SELECT `TABLE_NAME` FROM `information_schema`.`TABLES` WHERE `TABLE_SCHEMA` = " << db->escapeString(g_config.getString(ConfigManager::MYSQL_DB)) << " AND `DATA_FREE` > 0"; + DBResult* result = db->storeQuery(query.str()); + + if (!result) { + return false; + } + + do { + std::string tableName = result->getDataString("TABLE_NAME"); + std::cout << "> Optimizing table " << tableName << "..." << std::flush; + + query.str(""); + query << "OPTIMIZE TABLE `" << tableName << "`"; + + if (db->executeQuery(query.str())) { + std::cout << " [success]" << std::endl; + } else { + std::cout << " [failed]" << std::endl; + } + } while (result->next()); + + db->freeResult(result); + return true; +} + +bool DatabaseManager::triggerExists(const std::string& triggerName) +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "SELECT `TRIGGER_NAME` FROM `information_schema`.`TRIGGERS` WHERE `TRIGGER_SCHEMA` = " << db->escapeString(g_config.getString(ConfigManager::MYSQL_DB)) << " AND `TRIGGER_NAME` = " << db->escapeString(triggerName); + + DBResult* result = db->storeQuery(query.str()); + + if (!result) { + return false; + } + + db->freeResult(result); + return true; +} + +bool DatabaseManager::tableExists(const std::string& tableName) +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "SELECT `TABLE_NAME` FROM `information_schema`.`tables` WHERE `TABLE_SCHEMA` = " << db->escapeString(g_config.getString(ConfigManager::MYSQL_DB)) << " AND `TABLE_NAME` = " << db->escapeString(tableName); + + DBResult* result = db->storeQuery(query.str()); + + if (!result) { + return false; + } + + db->freeResult(result); + return true; +} + +bool DatabaseManager::isDatabaseSetup() +{ + Database* db = Database::getInstance(); + DBQuery query; + query << "SELECT `TABLE_NAME` FROM `information_schema`.`tables` WHERE `TABLE_SCHEMA` = " << db->escapeString(g_config.getString(ConfigManager::MYSQL_DB)); + DBResult* result = db->storeQuery(query.str()); + + if (!result) { + return false; + } + + db->freeResult(result); + return true; +} + +int32_t DatabaseManager::getDatabaseVersion() +{ + if (!tableExists("server_config")) { + Database* db = Database::getInstance(); + db->executeQuery("CREATE TABLE `server_config` (`config` VARCHAR(50) NOT NULL, `value` VARCHAR(256) NOT NULL DEFAULT '', UNIQUE(`config`)) ENGINE = InnoDB"); + db->executeQuery("INSERT INTO `server_config` VALUES ('db_version', 0)"); + return 0; + } + + int32_t version = 0; + + if (getDatabaseConfig("db_version", version)) { + return version; + } + + return -1; +} + +uint32_t DatabaseManager::updateDatabase() +{ + Database* db = Database::getInstance(); + DBQuery query; + + int32_t databaseVersion = getDatabaseVersion(); + + if (databaseVersion < 0) { + return 0; + } + + switch (databaseVersion) { + case 0: { + std::cout << "> Updating database to version 1 (account names)" << std::endl; + db->executeQuery("ALTER TABLE `accounts` ADD `name` VARCHAR(32) NOT NULL AFTER `id`"); + db->executeQuery("UPDATE `accounts` SET `name` = `id`"); + db->executeQuery("ALTER TABLE `accounts` ADD UNIQUE (`name`)"); + registerDatabaseConfig("db_version", 1); + return 1; + } + + case 1: { + std::cout << "> Updating database to version 2 (market offers)" << std::endl; + db->executeQuery("CREATE TABLE `market_offers` (`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `player_id` INT NOT NULL, `sale` TINYINT(1) NOT NULL DEFAULT 0, `itemtype` INT UNSIGNED NOT NULL, `amount` SMALLINT UNSIGNED NOT NULL, `created` BIGINT UNSIGNED NOT NULL, `anonymous` TINYINT(1) NOT NULL DEFAULT 0, `price` INT UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (`id`), KEY(`sale`, `itemtype`), KEY(`created`), FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE) ENGINE = InnoDB"); + registerDatabaseConfig("db_version", 2); + return 2; + } + + case 2: { + std::cout << "> Updating database to version 3 (bank balance)" << std::endl; + db->executeQuery("ALTER TABLE `players` ADD `balance` BIGINT UNSIGNED NOT NULL DEFAULT 0"); + registerDatabaseConfig("db_version", 3); + return 3; + } + + case 3: { + std::cout << "> Updating database to version 4 (market history)" << std::endl; + db->executeQuery("CREATE TABLE `market_history` (`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `player_id` INT NOT NULL, `sale` TINYINT(1) NOT NULL DEFAULT 0, `itemtype` INT UNSIGNED NOT NULL, `amount` SMALLINT UNSIGNED NOT NULL, `price` INT UNSIGNED NOT NULL DEFAULT 0, `expires_at` BIGINT UNSIGNED NOT NULL, `inserted` BIGINT UNSIGNED NOT NULL, `state` TINYINT(1) UNSIGNED NOT NULL, PRIMARY KEY(`id`), KEY(`player_id`, `sale`), FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE) ENGINE = InnoDB"); + registerDatabaseConfig("db_version", 4); + return 4; + } + + case 4: { + std::cout << "> Updating database to version 5 (black skull & guild wars)" << std::endl; + db->executeQuery("ALTER TABLE `players` CHANGE `redskull` `skull` TINYINT(1) NOT NULL DEFAULT '0', CHANGE `redskulltime` `skulltime` INT(11) NOT NULL DEFAULT '0'"); + db->executeQuery("CREATE TABLE IF NOT EXISTS `guild_wars` ( `id` int(11) NOT NULL AUTO_INCREMENT, `guild1` int(11) NOT NULL DEFAULT '0', `guild2` int(11) NOT NULL DEFAULT '0', `name1` varchar(255) NOT NULL, `name2` varchar(255) NOT NULL, `status` tinyint(2) NOT NULL DEFAULT '0', `started` bigint(15) NOT NULL DEFAULT '0', `ended` bigint(15) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `guild1` (`guild1`), KEY `guild2` (`guild2`)) ENGINE=InnoDB"); + db->executeQuery("CREATE TABLE IF NOT EXISTS `guildwar_kills` (`id` int(11) NOT NULL AUTO_INCREMENT, `killer` varchar(50) NOT NULL, `target` varchar(50) NOT NULL, `killerguild` int(11) NOT NULL DEFAULT '0', `targetguild` int(11) NOT NULL DEFAULT '0', `warid` int(11) NOT NULL DEFAULT '0', `time` bigint(15) NOT NULL, PRIMARY KEY (`id`), KEY `warid` (`warid`), FOREIGN KEY (`warid`) REFERENCES `guild_wars`(`id`) ON DELETE CASCADE) ENGINE=InnoDB"); + registerDatabaseConfig("db_version", 5); + return 5; + } + + case 5: { + std::cout << "> Updating database to version 6 (market bug fix)" << std::endl; + db->executeQuery("DELETE FROM `market_offers` WHERE `amount` = 0"); + registerDatabaseConfig("db_version", 6); + return 6; + } + + case 6: { + std::cout << "> Updating database to version 7 (offline training)" << std::endl; + db->executeQuery("ALTER TABLE `players` ADD `offlinetraining_time` SMALLINT UNSIGNED NOT NULL DEFAULT 43200"); + db->executeQuery("ALTER TABLE `players` ADD `offlinetraining_skill` INT NOT NULL DEFAULT -1"); + registerDatabaseConfig("db_version", 7); + return 7; + } + + case 7: { + std::cout << "> Updating database to version 8 (account viplist with description, icon and notify server side)" << std::endl; + db->executeQuery("RENAME TABLE `player_viplist` TO `account_viplist`"); + db->executeQuery("ALTER TABLE `account_viplist` DROP FOREIGN KEY `account_viplist_ibfk_1`"); + db->executeQuery("UPDATE `account_viplist` SET `player_id` = (SELECT `account_id` FROM `players` WHERE `id` = `player_id`)"); + db->executeQuery("ALTER TABLE `account_viplist` CHANGE `player_id` `account_id` INT( 11 ) NOT NULL COMMENT 'id of account whose viplist entry it is'"); + db->executeQuery("ALTER TABLE `account_viplist` DROP FOREIGN KEY `account_viplist_ibfk_2`"); + db->executeQuery("ALTER TABLE `account_viplist` CHANGE `vip_id` `player_id` INT( 11 ) NOT NULL COMMENT 'id of target player of viplist entry'"); + db->executeQuery("ALTER TABLE `account_viplist` DROP INDEX `player_id`, ADD INDEX `account_id` (`account_id`)"); + db->executeQuery("ALTER TABLE `account_viplist` DROP INDEX `vip_id`, ADD INDEX `player_id` (`player_id`)"); + db->executeQuery("ALTER TABLE `account_viplist` ADD FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE"); + db->executeQuery("ALTER TABLE `account_viplist` ADD FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE"); + db->executeQuery("ALTER TABLE `account_viplist` ADD `description` VARCHAR(128) NOT NULL DEFAULT '', ADD `icon` TINYINT( 2 ) UNSIGNED NOT NULL DEFAULT '0', ADD `notify` TINYINT( 1 ) NOT NULL DEFAULT '0'"); + + // Remove duplicates + DBResult* result = db->storeQuery("SELECT `account_id`, `player_id`, COUNT(*) AS `count` FROM `account_viplist` GROUP BY `account_id`, `player_id` HAVING COUNT(*) > 1"); + + if (result) { + do { + query.str(""); + query << "DELETE FROM `account_viplist` WHERE `account_id` = " << result->getDataInt("account_id") << " AND `player_id` = " << result->getDataInt("player_id") << " LIMIT " << (result->getDataInt("count") - 1); + db->executeQuery(query.str()); + } while (result->next()); + + db->freeResult(result); + } + + // Remove if an account has over 200 entries + result = db->storeQuery("SELECT `account_id`, COUNT(*) AS `count` FROM `account_viplist` GROUP BY `account_id` HAVING COUNT(*) > 200"); + + if (result) { + do { + query.str(""); + query << "DELETE FROM `account_viplist` WHERE `account_id` = " << result->getDataInt("account_id") << " LIMIT " << (result->getDataInt("count") - 200); + db->executeQuery(query.str()); + } while (result->next()); + + db->freeResult(result); + } + + db->executeQuery("ALTER TABLE `account_viplist` ADD UNIQUE `account_player_index` (`account_id`, `player_id`)"); + + registerDatabaseConfig("db_version", 8); + return 8; + } + + case 8: { + std::cout << "> Updating database to version 9 (global inbox)" << std::endl; + db->executeQuery("CREATE TABLE IF NOT EXISTS `player_inboxitems` (`player_id` int(11) NOT NULL, `sid` int(11) NOT NULL, `pid` int(11) NOT NULL DEFAULT '0', `itemtype` smallint(6) NOT NULL, `count` smallint(5) NOT NULL DEFAULT '0', `attributes` blob NOT NULL, UNIQUE KEY `player_id_2` (`player_id`,`sid`), KEY `player_id` (`player_id`), FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=latin1"); + + // Delete "market" item + db->executeQuery("DELETE FROM `player_depotitems` WHERE `itemtype` = 14405"); + + // Move up items in depot chests + DBResult* result = db->storeQuery("SELECT `player_id`, `pid`, (SELECT `dp2`.`sid` FROM `player_depotitems` AS `dp2` WHERE `dp2`.`player_id` = `dp1`.`player_id` AND `dp2`.`pid` = `dp1`.`sid` AND `itemtype` = 2594) AS `sid` FROM `player_depotitems` AS `dp1` WHERE `itemtype` = 2589"); + + if (result) { + do { + query.str(""); + query << "UPDATE `player_depotitems` SET `pid` = " << result->getDataInt("pid") << " WHERE `player_id` = " << result->getDataInt("player_id") << " AND `pid` = " << result->getDataInt("sid"); + db->executeQuery(query.str()); + } while (result->next()); + + db->freeResult(result); + } + + // Delete the depot lockers + db->executeQuery("DELETE FROM `player_depotitems` WHERE `itemtype` = 2589"); + + // Delete the depot chests + db->executeQuery("DELETE FROM `player_depotitems` WHERE `itemtype` = 2594"); + + std::ostringstream ss2; + + result = db->storeQuery("SELECT DISTINCT `player_id` FROM `player_depotitems` WHERE `itemtype` = 14404"); + + if (result) { + do { + int32_t runningId = 100; + + DBInsert stmt(db); + stmt.setQuery("INSERT INTO `player_inboxitems` (`player_id`, `sid`, `pid`, `itemtype`, `count`, `attributes`) VALUES "); + + std::ostringstream sss; + sss << "SELECT `sid` FROM `player_depotitems` WHERE `player_id` = " << result->getDataInt("player_id") << " AND `itemtype` = 14404"; + DBResult* result2 = db->storeQuery(sss.str()); + + if (result2) { + do { + std::stack sids; + sids.push(result2->getDataInt("sid")); + + while (!sids.empty()) { + int32_t sid = sids.top(); + sids.pop(); + + std::ostringstream ss; + ss << "SELECT * FROM `player_depotitems` WHERE `player_id` = " << result->getDataInt("player_id") << " AND `pid` = " << sid; + DBResult* result3 = db->storeQuery(ss.str()); + + if (result3) { + do { + unsigned long attrSize = 0; + const char* attr = result3->getDataStream("attributes", attrSize); + ss2 << result->getDataInt("player_id") << "," << ++runningId << ",0," << result3->getDataInt("itemtype") << "," << result3->getDataInt("count") << "," << db->escapeBlob(attr, attrSize); + + if (!stmt.addRow(ss2)) { + std::cout << "Failed to add row!" << std::endl; + } + + sids.push(result3->getDataInt("sid")); + + std::ostringstream tmpss; + tmpss << "DELETE FROM `player_depotitems` WHERE `player_id` = " << result->getDataInt("player_id") << " AND `sid` = " << result3->getDataInt("sid"); + db->executeQuery(tmpss.str()); + } while (result3->next()); + + db->freeResult(result3); + } + } + } while (result2->next()); + + db->freeResult(result2); + } + + if (!stmt.execute()) { + std::cout << "Failed to execute statement!" << std::endl; + } + } while (result->next()); + + db->freeResult(result); + } + + // Delete the inboxes + db->executeQuery("DELETE FROM `player_depotitems` WHERE `itemtype` = 14404"); + + registerDatabaseConfig("db_version", 9); + return 9; + } + + case 9: { + std::cout << "> Updating database to version 10 (stamina)" << std::endl; + db->executeQuery("ALTER TABLE `players` ADD `stamina` SMALLINT UNSIGNED NOT NULL DEFAULT 2520"); + registerDatabaseConfig("db_version", 10); + return 10; + } + + case 10: { + std::cout << "> Updating database to version 11 (improved guild and players online structure)" << std::endl; + db->executeQuery("CREATE TABLE IF NOT EXISTS `guild_membership` (`player_id` int(11) NOT NULL, `guild_id` int(11) NOT NULL, `rank_id` int(11) NOT NULL, `nick` varchar(15) NOT NULL DEFAULT '', PRIMARY KEY (`player_id`), KEY `guild_id` (`guild_id`), KEY `rank_id` (`rank_id`)) ENGINE=InnoDB"); + db->executeQuery("ALTER TABLE `guild_membership` ADD CONSTRAINT `guild_membership_ibfk_3` FOREIGN KEY (`rank_id`) REFERENCES `guild_ranks` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, ADD CONSTRAINT `guild_membership_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, ADD CONSTRAINT `guild_membership_ibfk_2` FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`id`) ON DELETE CASCADE ON UPDATE CASCADE"); + db->executeQuery("ALTER TABLE `guild_invites` ADD PRIMARY KEY (`player_id`, `guild_id`)"); + db->executeQuery("ALTER TABLE `player_skills` ADD PRIMARY KEY (`player_id`, `skillid`)"); + db->executeQuery("ALTER TABLE `player_storage` ADD PRIMARY KEY (`player_id`, `key`)"); + + DBResult* result = db->storeQuery("SELECT `players`.`id` AS `player_id`, `players`.`rank_id` AS `rank_id`, `players`.`guildnick` AS `guild_nick`, `guild_ranks`.`guild_id` AS `guild_id` FROM `guild_ranks` INNER JOIN `players` ON `guild_ranks`.`id` = `players`.`rank_id`"); + + if (result) { + DBInsert stmt(db); + stmt.setQuery("INSERT INTO `guild_membership` (`player_id`, `guild_id`, `rank_id`, `nick`) VALUES "); + + std::ostringstream ss; + + do { + ss << result->getDataInt("player_id") << "," << result->getDataInt("guild_id") << "," << result->getDataInt("rank_id") << "," << db->escapeString(result->getDataString("guild_nick")); + + if (!stmt.addRow(ss)) { + std::cout << "Failed to add row!" << std::endl; + } + } while (result->next()); + + db->freeResult(result); + + if (!stmt.execute()) { + std::cout << "Failed to execute statement!" << std::endl; + } + } + + db->executeQuery("ALTER TABLE `players` DROP `rank_id`, DROP `guildnick`, DROP `direction`, DROP `loss_experience`, DROP `loss_mana`, DROP `loss_skills`, DROP `premend`, DROP `online`"); + db->executeQuery("DROP TRIGGER IF EXISTS `ondelete_guilds`"); + db->executeQuery("CREATE TABLE IF NOT EXISTS `players_online` (`player_id` int(11) NOT NULL, PRIMARY KEY (`player_id`)) ENGINE=MEMORY"); + registerDatabaseConfig("db_version", 11); + return 11; + } + + default: + break; + } + + return 0; +} + +bool DatabaseManager::getDatabaseConfig(const std::string& config, int32_t& value) +{ + Database* db = Database::getInstance(); + DBQuery query; + query << "SELECT `value` FROM `server_config` WHERE `config` = " << db->escapeString(config); + DBResult* result = db->storeQuery(query.str()); + + if (!result) { + return false; + } + + value = atoi(result->getDataString("value").c_str()); + db->freeResult(result); + return true; +} + +bool DatabaseManager::getDatabaseConfig(const std::string& config, std::string& value) +{ + Database* db = Database::getInstance(); + DBQuery query; + query << "SELECT `value` FROM `server_config` WHERE `config` = " << db->escapeString(config); + DBResult* result = db->storeQuery(query.str()); + + if (!result) { + return false; + } + + value = result->getDataString("value"); + db->freeResult(result); + return true; +} + +void DatabaseManager::registerDatabaseConfig(const std::string& config, int32_t value) +{ + Database* db = Database::getInstance(); + DBQuery query; + + int32_t tmp; + + if (!getDatabaseConfig(config, tmp)) { + query << "INSERT INTO `server_config` VALUES (" << db->escapeString(config) << ", '" << value << "')"; + } else { + query << "UPDATE `server_config` SET `value` = '" << value << "' WHERE `config` = " << db->escapeString(config); + } + + db->executeQuery(query.str()); +} + +void DatabaseManager::registerDatabaseConfig(const std::string& config, const std::string& value) +{ + Database* db = Database::getInstance(); + DBQuery query; + + std::string tmp; + + if (!getDatabaseConfig(config, tmp)) { + query << "INSERT INTO `server_config` VALUES (" << db->escapeString(config) << ", " << db->escapeString(value) << ")"; + } else { + query << "UPDATE `server_config` SET `value` = " << db->escapeString(value) << " WHERE `config` = " << db->escapeString(config); + } + + db->executeQuery(query.str()); +} + +void DatabaseManager::checkEncryption() +{ + int32_t currentValue = g_config.getNumber(ConfigManager::PASSWORD_TYPE); + int32_t oldValue = 0; + + if (getDatabaseConfig("encryption", oldValue)) { + if (currentValue == oldValue) { + return; + } + + if (oldValue != PASSWORD_TYPE_PLAIN) { + std::string oldName; + + if (oldValue == PASSWORD_TYPE_MD5) { + oldName = "md5"; + } else if (oldValue == PASSWORD_TYPE_SHA1) { + oldName = "sha1"; + } else { + oldName = "plain"; + } + + g_config.setNumber(ConfigManager::PASSWORD_TYPE, oldValue); + std::cout << "> WARNING: Unsupported password hashing switch! Change back passwordType in config.lua to \"" << oldName << "\"!" << std::endl; + return; + } + + switch (currentValue) { + case PASSWORD_TYPE_MD5: { + DBQuery query; // keep for locking + Database::getInstance()->executeQuery("UPDATE `accounts` SET `password` = MD5(`password`), `key` = MD5(`key`)"); + std::cout << "> Password type has been updated to MD5." << std::endl; + break; + } + + case PASSWORD_TYPE_SHA1: { + DBQuery query; // keep for locking + Database::getInstance()->executeQuery("UPDATE `accounts` SET `password` = SHA1(`password`), `key` = SHA1(`key`)"); + std::cout << "> Password type has been updated to SHA1." << std::endl; + break; + } + + default: + break; + } + } + + registerDatabaseConfig("encryption", currentValue); +} + +void DatabaseManager::checkTriggers() +{ + // +} diff --git a/src/databasemanager.h b/src/databasemanager.h new file mode 100644 index 0000000000..ce3aadf835 --- /dev/null +++ b/src/databasemanager.h @@ -0,0 +1,52 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __DATABASE_MANAGER__ +#define __DATABASE_MANAGER__ +#include "database.h" + +class DatabaseManager +{ + public: + DatabaseManager() {} + virtual ~DatabaseManager() {} + + static DatabaseManager* getInstance() { + static DatabaseManager instance; + return &instance; + } + + bool tableExists(const std::string& table); + bool triggerExists(const std::string& trigger); + + int32_t getDatabaseVersion(); + bool isDatabaseSetup(); + + bool optimizeTables(); + uint32_t updateDatabase(); + + bool getDatabaseConfig(const std::string& config, int32_t& value); + void registerDatabaseConfig(const std::string& config, int32_t value); + + bool getDatabaseConfig(const std::string& config, std::string& value); + void registerDatabaseConfig(const std::string& config, const std::string& value); + + void checkEncryption(); + void checkTriggers(); +}; +#endif diff --git a/src/databasemysql.cpp b/src/databasemysql.cpp new file mode 100644 index 0000000000..416d1cc69e --- /dev/null +++ b/src/databasemysql.cpp @@ -0,0 +1,322 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "database.h" +#include "databasemysql.h" +#if defined(WIN32) && !defined(_MSC_VER) +#include +#else +#include +#endif +#include "configmanager.h" +#include +#include + +extern ConfigManager g_config; + +/** DatabaseMySQL definitions */ + +DatabaseMySQL::DatabaseMySQL() +{ + m_connected = false; + + // connection handle initialization + if (!mysql_init(&m_handle)) { + std::cout << std::endl << "Failed to initialize MySQL connection handle." << std::endl; + return; + } + + // automatic reconnect + my_bool reconnect = true; + mysql_options(&m_handle, MYSQL_OPT_RECONNECT, &reconnect); + + // connects to database + if (!mysql_real_connect(&m_handle, g_config.getString(ConfigManager::MYSQL_HOST).c_str(), g_config.getString(ConfigManager::MYSQL_USER).c_str(), g_config.getString(ConfigManager::MYSQL_PASS).c_str(), g_config.getString(ConfigManager::MYSQL_DB).c_str(), g_config.getNumber(ConfigManager::SQL_PORT), NULL, 0)) { + std::cout << "Failed to connect to database. MYSQL ERROR: " << mysql_error(&m_handle) << std::endl; + return; + } + + if (MYSQL_VERSION_ID < 50019) { + //mySQL servers < 5.0.19 has a bug where MYSQL_OPT_RECONNECT is (incorrectly) reset by mysql_real_connect calls + //See http://dev.mysql.com/doc/refman/5.0/en/mysql-options.html for more information. + mysql_options(&m_handle, MYSQL_OPT_RECONNECT, &reconnect); + std::cout << std::endl << "[Warning] Outdated mySQL server detected. Consider upgrading to a newer version." << std::endl; + } + + m_connected = true; + + if (g_config.getString(ConfigManager::MAP_STORAGE_TYPE) == "binary") { + DBQuery query; + query << "SHOW variables LIKE 'max_allowed_packet'"; + + DBResult* result; + + if ((result = storeQuery(query.str()))) { + int32_t max_query = result->getDataInt("Value"); + freeResult(result); + + if (max_query < 16777216) { + std::cout << std::endl << "[Warning] max_allowed_packet might be set too low for binary map storage." << std::endl; + std::cout << "Use the following query to raise max_allow_packet: "; + std::cout << "SET GLOBAL max_allowed_packet = 16777216"; + } + } + } +} + +DatabaseMySQL::~DatabaseMySQL() +{ + mysql_close(&m_handle); +} + +bool DatabaseMySQL::getParam(DBParam_t param) +{ + return param == DBPARAM_MULTIINSERT; +} + +bool DatabaseMySQL::beginTransaction() +{ + return executeQuery("BEGIN"); +} + +bool DatabaseMySQL::rollback() +{ + if (!m_connected) { + return false; + } + + if (mysql_rollback(&m_handle) != 0) { + std::cout << "mysql_rollback(): MYSQL ERROR: " << mysql_error(&m_handle) << std::endl; + return false; + } + + return true; +} + +bool DatabaseMySQL::commit() +{ + if (!m_connected) { + return false; + } + + if (mysql_commit(&m_handle) != 0) { + std::cout << "mysql_commit(): MYSQL ERROR: " << mysql_error(&m_handle) << std::endl; + return false; + } + + return true; +} + +bool DatabaseMySQL::executeQuery(const std::string& query) +{ + if (!m_connected) { + return false; + } + + bool state = true; + + // executes the query + if (mysql_real_query(&m_handle, query.c_str(), query.length()) != 0) { + std::cout << "mysql_real_query(): " << query.substr(0, 256) << ": MYSQL ERROR: " << mysql_error(&m_handle) << std::endl; + int error = mysql_errno(&m_handle); + + if (error == CR_SERVER_LOST || error == CR_SERVER_GONE_ERROR) { + m_connected = false; + } + + state = false; + } + + // we should call that every time as someone would call executeQuery('SELECT...') + // as it is described in MySQL manual: "it doesn't hurt" :P + MYSQL_RES* m_res = mysql_store_result(&m_handle); + + if (m_res) { + mysql_free_result(m_res); + } + + return state; +} + +DBResult* DatabaseMySQL::storeQuery(const std::string& query) +{ + if (!m_connected) { + return NULL; + } + + // executes the query + if (mysql_real_query(&m_handle, query.c_str(), query.length()) != 0) { + std::cout << "mysql_real_query(): " << query << ": MYSQL ERROR: " << mysql_error(&m_handle) << std::endl; + int error = mysql_errno(&m_handle); + + if (error == CR_SERVER_LOST || error == CR_SERVER_GONE_ERROR) { + m_connected = false; + } + } + + // we should call that every time as someone would call executeQuery('SELECT...') + // as it is described in MySQL manual: "it doesn't hurt" :P + MYSQL_RES* m_res = mysql_store_result(&m_handle); + + // error occured + if (!m_res) { + std::cout << "mysql_store_result(): " << query.substr(0, 256) << ": MYSQL ERROR: " << mysql_error(&m_handle) << std::endl; + int error = mysql_errno(&m_handle); + + if (error == CR_SERVER_LOST || error == CR_SERVER_GONE_ERROR) { + m_connected = false; + } + + return NULL; + } + + // retriving results of query + DBResult* res = new MySQLResult(m_res); + return verifyResult(res); +} + +uint64_t DatabaseMySQL::getLastInsertedRowID() +{ + return (uint64_t)mysql_insert_id(&m_handle); +} + +std::string DatabaseMySQL::escapeString(const std::string& s) +{ + return escapeBlob(s.c_str(), s.length()); +} + +std::string DatabaseMySQL::escapeBlob(const char* s, uint32_t length) +{ + // remember about quoiting even an empty string! + if (!s) { + return std::string("''"); + } + + // the worst case is 2n + 1 + char* output = new char[length * 2 + 1]; + + // quotes escaped string and frees temporary buffer + mysql_real_escape_string(&m_handle, output, s, length); + std::string r = "'"; + r += output; + r += "'"; + delete[] output; + return r; +} + +void DatabaseMySQL::freeResult(DBResult* res) +{ + delete (MySQLResult*)res; +} + +/** MySQLResult definitions */ + +int32_t MySQLResult::getDataInt(const std::string& s) +{ + listNames_t::iterator it = m_listNames.find(s); + + if (it == m_listNames.end()) { + std::cout << "Error during getDataInt(" << s << ")." << std::endl; + return 0; + } + + if (m_row[it->second] == NULL) { + return 0; + } + + return atoi(m_row[it->second]); +} + +int64_t MySQLResult::getDataLong(const std::string& s) +{ + listNames_t::iterator it = m_listNames.find(s); + + if (it == m_listNames.end()) { + std::cout << "Error during getDataLong(" << s << ")." << std::endl; + return 0; + } + + if (m_row[it->second] == NULL) { + return 0; + } + + return ATOI64(m_row[it->second]); +} + +std::string MySQLResult::getDataString(const std::string& s) +{ + listNames_t::iterator it = m_listNames.find(s); + + if (it == m_listNames.end()) { + std::cout << "Error during getDataString(" << s << ")." << std::endl; + return std::string(""); + } + + if (m_row[it->second] == NULL) { + return std::string(""); + } + + return std::string(m_row[it->second]); +} + +const char* MySQLResult::getDataStream(const std::string& s, unsigned long& size) +{ + listNames_t::iterator it = m_listNames.find(s); + + if (it == m_listNames.end()) { + std::cout << "Error during getDataStream(" << s << ")." << std::endl; + size = 0; + return NULL; + } + + if (m_row[it->second] == NULL) { + size = 0; + return NULL; + } + + size = mysql_fetch_lengths(m_handle)[it->second]; + return m_row[it->second]; + +} + +bool MySQLResult::next() +{ + m_row = mysql_fetch_row(m_handle); + return m_row != NULL; +} + +MySQLResult::MySQLResult(MYSQL_RES* res) +{ + m_handle = res; + m_listNames.clear(); + + MYSQL_FIELD* field; + int32_t i = 0; + + while ((field = mysql_fetch_field(m_handle))) { + m_listNames[field->name] = i; + i++; + } +} + +MySQLResult::~MySQLResult() +{ + mysql_free_result(m_handle); +} diff --git a/src/databasemysql.h b/src/databasemysql.h new file mode 100644 index 0000000000..8f5c9997f7 --- /dev/null +++ b/src/databasemysql.h @@ -0,0 +1,92 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_DATABASEMYSQL_H__ +#define __OTSERV_DATABASEMYSQL_H__ + +#ifndef __OTSERV_DATABASE_H__ +#error "database.h should be included first." +#endif + +#include "definitions.h" +#include "otsystem.h" + +#if defined(WIN32) && !defined(_MSC_VER) +#include +#else +#include +#endif + +#include +#include + +class DatabaseMySQL : public _Database +{ + public: + DatabaseMySQL(); + DATABASE_VIRTUAL ~DatabaseMySQL(); + + DATABASE_VIRTUAL bool getParam(DBParam_t param); + + DATABASE_VIRTUAL bool beginTransaction(); + DATABASE_VIRTUAL bool rollback(); + DATABASE_VIRTUAL bool commit(); + + DATABASE_VIRTUAL bool executeQuery(const std::string& query); + DATABASE_VIRTUAL DBResult* storeQuery(const std::string& query); + + DATABASE_VIRTUAL uint64_t getLastInsertedRowID(); + + DATABASE_VIRTUAL std::string escapeString(const std::string& s); + DATABASE_VIRTUAL std::string escapeBlob(const char* s, uint32_t length); + + DATABASE_VIRTUAL void freeResult(DBResult* res); + + DATABASE_VIRTUAL uint64_t getLastInsertId() { + return (uint64_t)mysql_insert_id(&m_handle); + } + + DATABASE_VIRTUAL const char* getClientVersion() { + return mysql_get_client_info(); + } + + protected: + MYSQL m_handle; +}; + +class MySQLResult : public _DBResult +{ + friend class DatabaseMySQL; + + public: + DATABASE_VIRTUAL int32_t getDataInt(const std::string& s); + DATABASE_VIRTUAL int64_t getDataLong(const std::string& s); + DATABASE_VIRTUAL std::string getDataString(const std::string& s); + DATABASE_VIRTUAL const char* getDataStream(const std::string& s, unsigned long& size); + + DATABASE_VIRTUAL bool next(); + + protected: + MySQLResult(MYSQL_RES* res); + DATABASE_VIRTUAL ~MySQLResult(); + + MYSQL_RES* m_handle; + MYSQL_ROW m_row; +}; + +#endif diff --git a/src/definitions.h b/src/definitions.h new file mode 100644 index 0000000000..656774171a --- /dev/null +++ b/src/definitions.h @@ -0,0 +1,177 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_DEFINITIONS_H__ +#define __OTSERV_DEFINITIONS_H__ + +#define STATUS_SERVER_NAME "The Forgotten Server" +#define STATUS_SERVER_PROTOCOL "10.0" +#define STATUS_SERVER_VERSION "1.0" +#define STATUS_SERVER_DEVELOPERS "Mark Samman" + +#define CLIENT_VERSION_MIN 972 +#define CLIENT_VERSION_MAX 981 +#define CLIENT_VERSION_STR "10.0" + +enum passwordType_t { + PASSWORD_TYPE_PLAIN = 0, + PASSWORD_TYPE_MD5, + PASSWORD_TYPE_SHA1 +}; + +#ifdef __GNUC__ +#define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#ifdef __STRICT_ANSI__ +#undef __STRICT_ANSI__ +#endif + +#ifndef __FUNCTION__ +#define __FUNCTION__ __func__ +#endif + +#ifdef XML_GCC_FREE +#define xmlFreeOTSERV(s) free(s) +#else +#define xmlFreeOTSERV(s) xmlFree(s) +#endif + +#if defined(WIN32) || defined(_WIN32) +#if !defined(WINDOWS) +#define WINDOWS +#endif +#endif + +#include +#include +#include + +#ifdef WINDOWS +#include +#include +#include + +#if defined(_MSC_VER) && defined(NDEBUG) +#define _SECURE_SCL 0 +#define HAS_ITERATOR_DEBUGGING 0 +#endif + +#ifdef _WIN32_WINNT +#undef _WIN32_WINNT +#endif + +//Windows 2000 0x0500 +//Windows XP 0x0501 +//Windows 2003 0x0502 +//Windows Vista 0x0600 +#define _WIN32_WINNT 0x0501 + +#ifdef __GNUC__ +#if GCC_VERSION >= 40700 +#include +#include +#define OTSERV_HASH_MAP std::unordered_map +#define OTSERV_HASH_SET std::unordered_set +#elif GCC_VERSION >= 40000 +#include +#include +#define OTSERV_HASH_MAP std::tr1::unordered_map +#define OTSERV_HASH_SET std::tr1::unordered_set +#else +#include +#include +#define OTSERV_HASH_MAP __gnu_cxx::hash_map +#define OTSERV_HASH_SET __gnu_cxx::hash_set +#endif +#include + +#define ATOI64 atoll + +inline int strcasecmp(const char* s1, const char* s2) +{ + return ::_stricmp(s1, s2); +} + +inline int strncasecmp(const char* s1, const char* s2, size_t n) +{ + return ::_strnicmp(s1, s2, n); +} +#else +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#ifndef _USE_MATH_DEFINES +#define _USE_MATH_DEFINES +#endif + +#include +#include +#include + +inline int strcasecmp(const char* s1, const char* s2) +{ + return ::_stricmp(s1, s2); +} + +inline int strncasecmp(const char* s1, const char* s2, size_t n) +{ + return ::_strnicmp(s1, s2, n); +} + +#define OTSERV_HASH_MAP std::unordered_map +#define OTSERV_HASH_SET std::unordered_set +#define ATOI64 _atoi64 + +#pragma warning(disable:4786) // msvc too long debug names in stl +#pragma warning(disable:4250) // 'class1' : inherits 'class2::member' via dominance +#pragma warning(disable:4244) //'argument' : conversion from 'type1' to 'type2', possible loss of data +#pragma warning(disable:4267) //'var' : conversion from 'size_t' to 'type', possible loss of data +#endif +#else +#include +#include +#include +#include + +#ifdef __GXX_EXPERIMENTAL_CXX0X__ +#include +#include +#define OTSERV_HASH_MAP std::unordered_map +#define OTSERV_HASH_SET std::unordered_set +#elif GCC_VERSION >= 40000 +#include +#include +#define OTSERV_HASH_MAP std::tr1::unordered_map +#define OTSERV_HASH_SET std::tr1::unordered_set +#else +#include +#include +#define OTSERV_HASH_MAP __gnu_cxx::hash_map +#define OTSERV_HASH_SET __gnu_cxx::hash_set +#endif + +#define ATOI64 atoll +#endif + +#endif diff --git a/src/depotchest.cpp b/src/depotchest.cpp new file mode 100644 index 0000000000..89ac086007 --- /dev/null +++ b/src/depotchest.cpp @@ -0,0 +1,81 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "depotchest.h" +#include "tools.h" + +DepotChest::DepotChest(uint16_t _type) : + Container(_type) +{ + maxDepotLimit = 1500; +} + +DepotChest::~DepotChest() +{ + // +} + +ReturnValue DepotChest::__queryAdd(int32_t index, const Thing* thing, uint32_t count, + uint32_t flags, Creature* actor/* = NULL*/) const +{ + const Item* item = thing->getItem(); + + if (item == NULL) { + return RET_NOTPOSSIBLE; + } + + bool skipLimit = hasBitSet(FLAG_NOLIMIT, flags); + + if (!skipLimit) { + int32_t addCount = 0; + + if ((item->isStackable() && item->getItemCount() != count)) { + addCount = 1; + } + + if (item->getTopParent() != this) { + if (const Container* container = item->getContainer()) { + addCount = container->getItemHoldingCount() + 1; + } else { + addCount = 1; + } + } + + if (getItemHoldingCount() + addCount > maxDepotLimit) { + return RET_DEPOTISFULL; + } + } + + return Container::__queryAdd(index, thing, count, flags, actor); +} + +void DepotChest::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) +{ + if (getParent() != NULL) { + getParent()->postAddNotification(thing, oldParent, index, LINK_PARENT); + } +} + +void DepotChest::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, bool isCompleteRemoval, cylinderlink_t link /*= LINK_OWNER*/) +{ + if (getParent() != NULL) { + getParent()->postRemoveNotification(thing, newParent, index, isCompleteRemoval, LINK_PARENT); + } +} diff --git a/src/depotchest.h b/src/depotchest.h new file mode 100644 index 0000000000..240fd7ccee --- /dev/null +++ b/src/depotchest.h @@ -0,0 +1,59 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_DEPOTCHEST_H__ +#define __OTSERV_DEPOTCHEST_H__ + +#include "container.h" + +class DepotChest : public Container +{ + public: + DepotChest(uint16_t _type); + ~DepotChest(); + + DepotChest* getDepotChest() { + return this; + } + const DepotChest* getDepotChest() const { + return this; + } + + //serialization + void setMaxDepotLimit(uint32_t maxitems) { + maxDepotLimit = maxitems; + } + + //cylinder implementations + ReturnValue __queryAdd(int32_t index, const Thing* thing, uint32_t count, + uint32_t flags, Creature* actor = NULL) const; + + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER); + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, bool isCompleteRemoval, cylinderlink_t link = LINK_OWNER); + + //overrides + bool canRemove() const { + return false; + } + + private: + uint32_t maxDepotLimit; +}; + +#endif + diff --git a/src/depotlocker.cpp b/src/depotlocker.cpp new file mode 100644 index 0000000000..8a89c2206e --- /dev/null +++ b/src/depotlocker.cpp @@ -0,0 +1,79 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "depotlocker.h" +#include "tools.h" + +DepotLocker::DepotLocker(uint16_t _type) : + Container(_type) +{ + depotId = 0; + maxSize = 3; +} + +DepotLocker::~DepotLocker() +{ + // +} + +Attr_ReadValue DepotLocker::readAttr(AttrTypes_t attr, PropStream& propStream) +{ + if (attr == ATTR_DEPOT_ID) { + uint16_t _depotId; + + if (!propStream.GET_USHORT(_depotId)) { + return ATTR_READ_ERROR; + } + + setDepotId(_depotId); + return ATTR_READ_CONTINUE; + } else { + return Item::readAttr(attr, propStream); + } +} + +ReturnValue DepotLocker::__queryAdd(int32_t index, const Thing* thing, uint32_t count, + uint32_t flags, Creature* actor/* = NULL*/) const +{ + return Container::__queryAdd(index, thing, count, flags, actor); +} + +void DepotLocker::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) +{ + if (getParent() != NULL) { + getParent()->postAddNotification(thing, oldParent, index, LINK_PARENT); + } +} + +void DepotLocker::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, bool isCompleteRemoval, cylinderlink_t link /*= LINK_OWNER*/) +{ + if (getParent() != NULL) { + getParent()->postRemoveNotification(thing, newParent, index, isCompleteRemoval, LINK_PARENT); + } +} + +void DepotLocker::removeInbox(Inbox* inbox) +{ + ItemDeque::iterator cit = std::find(itemlist.begin(), itemlist.end(), inbox); + if (cit == itemlist.end()) { + return; + } + itemlist.erase(cit); +} diff --git a/src/depotlocker.h b/src/depotlocker.h new file mode 100644 index 0000000000..bdb2dabc74 --- /dev/null +++ b/src/depotlocker.h @@ -0,0 +1,67 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_DEPOTLOCKER_H__ +#define __OTSERV_DEPOTLOCKER_H__ + +#include "container.h" +#include "inbox.h" + +class DepotLocker : public Container +{ + public: + DepotLocker(uint16_t _type); + ~DepotLocker(); + + DepotLocker* getDepotLocker() { + return this; + } + const DepotLocker* getDepotLocker() const { + return this; + } + + void removeInbox(Inbox* inbox); + + //serialization + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream); + + uint32_t getDepotId() const { + return depotId; + } + void setDepotId(uint32_t id) { + depotId = id; + } + + //cylinder implementations + ReturnValue __queryAdd(int32_t index, const Thing* thing, uint32_t count, + uint32_t flags, Creature* actor = NULL) const; + + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER); + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, bool isCompleteRemoval, cylinderlink_t link = LINK_OWNER); + + //overrides + bool canRemove() const { + return false; + } + + private: + uint32_t depotId; +}; + +#endif + diff --git a/src/enums.h b/src/enums.h new file mode 100644 index 0000000000..a8c7daa769 --- /dev/null +++ b/src/enums.h @@ -0,0 +1,395 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_ENUMS_H__ +#define __OTSERV_ENUMS_H__ +#include +#include + +enum VipStatus_t { + VIPSTATUS_OFFLINE = 0, + VIPSTATUS_ONLINE = 1, + VIPSTATUS_PENDING = 2 +}; + +enum MarketAction_t { + MARKETACTION_BUY = 0, + MARKETACTION_SELL = 1 +}; + +enum MarketRequest_t { + MARKETREQUEST_OWN_OFFERS = 0xFFFE, + MARKETREQUEST_OWN_HISTORY = 0xFFFF +}; + +enum MarketOfferState_t { + OFFERSTATE_ACTIVE = 0, + OFFERSTATE_CANCELLED = 1, + OFFERSTATE_EXPIRED = 2, + OFFERSTATE_ACCEPTED = 3, + + OFFERSTATE_ACCEPTEDEX = 255 +}; + +enum ChannelEvent_t { + CHANNELEVENT_JOIN = 0, + CHANNELEVENT_LEAVE = 1, + CHANNELEVENT_INVITE = 2, + CHANNELEVENT_EXCLUDE = 3 +}; + +enum ReportType_t { + REPORTTYPE_NAME = 0, + REPORTTYPE_STATEMENT = 1, + REPORTTYPE_BOT = 2 +}; + +enum CreatureType_t { + CREATURETYPE_PLAYER = 0, + CREATURETYPE_MONSTER = 1, + CREATURETYPE_NPC = 2, + CREATURETYPE_SUMMON_OWN = 3, + CREATURETYPE_SUMMON_OTHERS = 4 +}; + +enum StorageValues_t { + STORAGEVALUE_PROMOTION = 30018 +}; + +enum OperatingSystem_t { + CLIENTOS_LINUX = 0x01, + CLIENTOS_WINDOWS = 0x02, + CLIENTOS_FLASH = 0x03 +}; + +enum SpellGroup_t { + SPELLGROUP_NONE = 0, + SPELLGROUP_ATTACK = 1, + SPELLGROUP_HEALING = 2, + SPELLGROUP_SUPPORT = 3, + SPELLGROUP_SPECIAL = 4 +}; + +enum AccountType_t { + ACCOUNT_TYPE_NORMAL = 0x01, + ACCOUNT_TYPE_TUTOR = 0x02, + ACCOUNT_TYPE_SENIORTUTOR = 0x03, + ACCOUNT_TYPE_GAMEMASTER = 0x04, + ACCOUNT_TYPE_GOD = 0x05 +}; + +enum RaceType_t { + RACE_NONE = 0, + RACE_VENOM = 1, + RACE_BLOOD = 2, + RACE_UNDEAD = 3, + RACE_FIRE = 4, + RACE_ENERGY = 5 +}; + +enum CombatType_t { + COMBAT_FIRST = 0, + COMBAT_NONE = COMBAT_FIRST, + COMBAT_PHYSICALDAMAGE = 1, + COMBAT_ENERGYDAMAGE = 2, + COMBAT_EARTHDAMAGE = 4, + COMBAT_FIREDAMAGE = 8, + COMBAT_UNDEFINEDDAMAGE = 16, + COMBAT_LIFEDRAIN = 32, + COMBAT_MANADRAIN = 64, + COMBAT_HEALING = 128, + COMBAT_DROWNDAMAGE = 256, + COMBAT_ICEDAMAGE = 512, + COMBAT_HOLYDAMAGE = 1024, + COMBAT_DEATHDAMAGE = 2048, + COMBAT_LAST = COMBAT_DEATHDAMAGE, + COMBAT_COUNT = 12 +}; + +enum CombatParam_t { + COMBATPARAM_COMBATTYPE = 1, + COMBATPARAM_EFFECT = 2, + COMBATPARAM_DISTANCEEFFECT = 3, + COMBATPARAM_BLOCKEDBYSHIELD = 4, + COMBATPARAM_BLOCKEDBYARMOR = 5, + COMBATPARAM_TARGETCASTERORTOPMOST = 6, + COMBATPARAM_CREATEITEM = 7, + COMBATPARAM_AGGRESSIVE = 8, + COMBATPARAM_DISPEL = 9, + COMBATPARAM_USECHARGES = 10 +}; + +enum CallBackParam_t { + CALLBACKPARAM_LEVELMAGICVALUE = 1, + CALLBACKPARAM_SKILLVALUE = 2, + CALLBACKPARAM_TARGETTILECALLBACK = 3, + CALLBACKPARAM_TARGETCREATURECALLBACK = 4 +}; + +enum ConditionParam_t { + CONDITIONPARAM_OWNER = 1, + CONDITIONPARAM_TICKS = 2, + //CONDITIONPARAM_OUTFIT = 3, + CONDITIONPARAM_HEALTHGAIN = 4, + CONDITIONPARAM_HEALTHTICKS = 5, + CONDITIONPARAM_MANAGAIN = 6, + CONDITIONPARAM_MANATICKS = 7, + CONDITIONPARAM_DELAYED = 8, + CONDITIONPARAM_SPEED = 9, + CONDITIONPARAM_LIGHT_LEVEL = 10, + CONDITIONPARAM_LIGHT_COLOR = 11, + CONDITIONPARAM_SOULGAIN = 12, + CONDITIONPARAM_SOULTICKS = 13, + CONDITIONPARAM_MINVALUE = 14, + CONDITIONPARAM_MAXVALUE = 15, + CONDITIONPARAM_STARTVALUE = 16, + CONDITIONPARAM_TICKINTERVAL = 17, + CONDITIONPARAM_FORCEUPDATE = 18, + CONDITIONPARAM_SKILL_MELEE = 19, + CONDITIONPARAM_SKILL_FIST = 20, + CONDITIONPARAM_SKILL_CLUB = 21, + CONDITIONPARAM_SKILL_SWORD = 22, + CONDITIONPARAM_SKILL_AXE = 23, + CONDITIONPARAM_SKILL_DISTANCE = 24, + CONDITIONPARAM_SKILL_SHIELD = 25, + CONDITIONPARAM_SKILL_FISHING = 26, + CONDITIONPARAM_STAT_MAXHITPOINTS = 27, + CONDITIONPARAM_STAT_MAXMANAPOINTS = 28, + CONDITIONPARAM_STAT_SOULPOINTS = 29, + CONDITIONPARAM_STAT_MAGICPOINTS = 30, + CONDITIONPARAM_STAT_MAXHITPOINTSPERCENT = 31, + CONDITIONPARAM_STAT_MAXMANAPOINTSPERCENT = 32, + CONDITIONPARAM_STAT_SOULPOINTSPERCENT = 33, + CONDITIONPARAM_STAT_MAGICPOINTSPERCENT = 34, + CONDITIONPARAM_PERIODICDAMAGE = 35, + CONDITIONPARAM_SKILL_MELEEPERCENT = 36, + CONDITIONPARAM_SKILL_FISTPERCENT = 37, + CONDITIONPARAM_SKILL_CLUBPERCENT = 38, + CONDITIONPARAM_SKILL_SWORDPERCENT = 39, + CONDITIONPARAM_SKILL_AXEPERCENT = 40, + CONDITIONPARAM_SKILL_DISTANCEPERCENT = 41, + CONDITIONPARAM_SKILL_SHIELDPERCENT = 42, + CONDITIONPARAM_SKILL_FISHINGPERCENT = 43, + CONDITIONPARAM_BUFF_SPELL = 44, + CONDITIONPARAM_SUBID = 45 +}; + +enum BlockType_t { + BLOCK_NONE = 0, + BLOCK_DEFENSE, + BLOCK_ARMOR, + BLOCK_IMMUNITY +}; + +enum skills_t { + SKILL_FIRST = 0, + SKILL_FIST = SKILL_FIRST, + SKILL_CLUB = 1, + SKILL_SWORD = 2, + SKILL_AXE = 3, + SKILL_DIST = 4, + SKILL_SHIELD = 5, + SKILL_FISH = 6, + SKILL__MAGLEVEL = 7, + SKILL__LEVEL = 8, + SKILL_LAST = SKILL_FISH, + SKILL__LAST = SKILL__LEVEL +}; + +enum stats_t { + STAT_FIRST = 0, + STAT_MAXHITPOINTS = STAT_FIRST, + STAT_MAXMANAPOINTS, + STAT_SOULPOINTS, + STAT_MAGICPOINTS, + STAT_LAST = STAT_MAGICPOINTS +}; + +enum formulaType_t { + FORMULA_UNDEFINED = 0, + FORMULA_LEVELMAGIC = 1, + FORMULA_SKILL = 2, + FORMULA_VALUE = 3 +}; + +enum ConditionId_t { + CONDITIONID_DEFAULT = -1, + CONDITIONID_COMBAT = 0, + CONDITIONID_HEAD = 1, + CONDITIONID_NECKLACE = 2, + CONDITIONID_BACKPACK = 3, + CONDITIONID_ARMOR = 4, + CONDITIONID_RIGHT = 5, + CONDITIONID_LEFT = 6, + CONDITIONID_LEGS = 7, + CONDITIONID_FEET = 8, + CONDITIONID_RING = 9, + CONDITIONID_AMMO = 10 +}; + +enum PlayerSex_t { + PLAYERSEX_FEMALE = 0, + PLAYERSEX_MALE = 1 +}; + +enum Vocation_t { + VOCATION_NONE = 0, + VOCATION_SORCERER = 1, + VOCATION_DRUID = 2, + VOCATION_PALADIN = 3, + VOCATION_KNIGHT = 4, + VOCATION_MASTERSORCERER = 5, + VOCATION_ELDERDRUID = 6, + VOCATION_ROYALPALADIN = 7, + VOCATION_ELITEKNIGHT = 8 +}; + +enum CharacterTypes_t { + PLAYER_MALE_1 = 0x80, + PLAYER_MALE_2 = 0x81, + PLAYER_MALE_3 = 0x82, + PLAYER_MALE_4 = 0x83, + PLAYER_MALE_5 = 0x84, + PLAYER_MALE_6 = 0x85, + PLAYER_MALE_7 = 0x86, + PLAYER_FEMALE_1 = 0x88, + PLAYER_FEMALE_2 = 0x89, + PLAYER_FEMALE_3 = 0x8A, + PLAYER_FEMALE_4 = 0x8B, + PLAYER_FEMALE_5 = 0x8C, + PLAYER_FEMALE_6 = 0x8D, + PLAYER_FEMALE_7 = 0x8E +}; + +struct Outfit_t { + Outfit_t() { + lookHead = 0; + lookBody = 0; + lookLegs = 0; + lookFeet = 0; + lookType = 0; + lookTypeEx = 0; + lookAddons = 0; + lookMount = 0; + } + + uint16_t lookType; + uint16_t lookTypeEx; + uint8_t lookHead; + uint8_t lookBody; + uint8_t lookLegs; + uint8_t lookFeet; + uint8_t lookAddons; + uint16_t lookMount; +}; + +struct LightInfo { + uint32_t level; + uint32_t color; + LightInfo() { + level = 0; + color = 0; + }; + LightInfo(uint32_t _level, uint32_t _color) { + level = _level; + color = _color; + } +}; + +struct ShopInfo { + uint32_t itemId; + int32_t subType; + uint32_t buyPrice; + uint32_t sellPrice; + std::string realName; + + ShopInfo() { + itemId = 0; + subType = 1; + buyPrice = 0; + sellPrice = 0; + realName = ""; + }; + + ShopInfo(uint32_t _itemId, int32_t _subType = 0, + uint32_t _buyPrice = 0, uint32_t _sellPrice = 0, + std::string _realName = "") { + itemId = _itemId; + subType = _subType; + buyPrice = _buyPrice; + sellPrice = _sellPrice; + realName = _realName; + }; +}; + +struct MarketOffer { + uint32_t price; + uint32_t timestamp; + uint16_t amount; + uint16_t counter; + uint16_t itemId; + std::string playerName; +}; + +struct MarketOfferEx { + uint32_t playerId; + uint32_t timestamp; + uint32_t price; + uint16_t amount; + uint16_t counter; + uint16_t itemId; + MarketAction_t type; + std::string playerName; +}; + +struct ExpiredMarketOffer { + uint32_t id; + uint32_t price; + uint16_t amount; + uint16_t itemId; + uint32_t playerId; +}; + +struct HistoryMarketOffer { + uint32_t timestamp; + uint32_t price; + uint16_t itemId; + uint16_t amount; + MarketOfferState_t state; +}; + +struct MarketStatistics { + MarketStatistics() { + numTransactions = 0; + highestPrice = 0; + totalPrice = 0; + lowestPrice = 0; + } + + uint32_t numTransactions; + uint32_t highestPrice; + uint64_t totalPrice; + uint32_t lowestPrice; +}; + +typedef std::list MarketOfferList; +typedef std::list ExpiredMarketOfferList; +typedef std::list HistoryMarketOfferList; +typedef std::list ShopInfoList; + +#endif diff --git a/src/fileloader.cpp b/src/fileloader.cpp new file mode 100644 index 0000000000..c1a95a0614 --- /dev/null +++ b/src/fileloader.cpp @@ -0,0 +1,536 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "fileloader.h" + +FileLoader::FileLoader() +{ + m_file = NULL; + m_root = NULL; + m_buffer = new uint8_t[1024]; + m_buffer_size = 1024; + m_lastError = ERROR_NONE; + + //cache + m_use_cache = false; + m_cache_size = 0; + m_cache_index = NO_VALID_CACHE; + m_cache_offset = NO_VALID_CACHE; + memset(m_cached_data, 0, sizeof(m_cached_data)); +} + +FileLoader::~FileLoader() +{ + if (m_file) { + fclose(m_file); + m_file = NULL; + } + + NodeStruct::clearNet(m_root); + delete[] m_buffer; + + for (int32_t i = 0; i < CACHE_BLOCKS; i++) { + delete[] m_cached_data[i].data; + } +} + +bool FileLoader::openFile(const char* filename, const char* accept_identifier, bool write, bool caching /*= false*/) +{ + if (write) { + m_file = fopen(filename, "wb"); + + if (!m_file) { + m_lastError = ERROR_CAN_NOT_CREATE; + return false; + } + + uint32_t version = 0; + writeData(&version, sizeof(version), false); + return true; + } + + m_file = fopen(filename, "rb"); + + if (!m_file) { + m_lastError = ERROR_CAN_NOT_OPEN; + return false; + } + + char identifier[4]; + + if (fread(identifier, 1, 4, m_file) < 4) { + fclose(m_file); + m_file = NULL; + m_lastError = ERROR_EOF; + return false; + } + + // The first four bytes must either match the accept identifier or be 0x00000000 (wildcard) + if (memcmp(identifier, accept_identifier, 4) != 0 && memcmp(identifier, "\0\0\0\0", 4) != 0) { + fclose(m_file); + m_file = NULL; + m_lastError = ERROR_INVALID_FILE_VERSION; + return false; + } + + if (caching) { + m_use_cache = true; + fseek(m_file, 0, SEEK_END); + int32_t file_size = ftell(m_file); + m_cache_size = std::min(32768, std::max(file_size / 20, 8192)) & ~0x1FFF; + } + + if (!safeSeek(4)) { + m_lastError = ERROR_INVALID_FORMAT; + return false; + } + + delete m_root; + m_root = new NodeStruct(); + m_root->start = 4; + + int32_t byte; + + if (safeSeek(4) && readByte(byte) && byte == NODE_START) { + return parseNode(m_root); + } + + return false; +} + +bool FileLoader::parseNode(NODE node) +{ + int32_t byte, pos; + NODE currentNode = node; + + while (true) { + //read node type + if (!readByte(byte)) { + return false; + } + + currentNode->type = byte; + bool setPropsSize = false; + + while (true) { + if (!readByte(byte)) { + return false; + } + + bool skipNode = false; + + switch (byte) { + case NODE_START: { + //child node start + if (!safeTell(pos)) { + return false; + } + + NODE childNode = new NodeStruct(); + childNode->start = pos; + currentNode->propsSize = pos - currentNode->start - 2; + currentNode->child = childNode; + + setPropsSize = true; + + if (!parseNode(childNode)) { + return false; + } + + break; + } + + case NODE_END: { + //current node end + if (!setPropsSize) { + if (!safeTell(pos)) { + return false; + } + + currentNode->propsSize = pos - currentNode->start - 2; + } + + if (!readByte(byte)) { + return true; + } + + switch (byte) { + case NODE_START: { + //starts next node + if (!safeTell(pos)) { + return false; + } + + skipNode = true; + NODE nextNode = new NodeStruct(); + nextNode->start = pos; + currentNode->next = nextNode; + currentNode = nextNode; + break; + } + + case NODE_END: + return safeTell(pos) && safeSeek(pos); + + default: + m_lastError = ERROR_INVALID_FORMAT; + return false; + } + + break; + } + + case ESCAPE_CHAR: { + if (!readByte(byte)) { + return false; + } + + break; + } + + default: + break; + } + + if (skipNode) { + break; + } + } + } + + return false; +} + +const uint8_t* FileLoader::getProps(const NODE node, uint32_t& size) +{ + if (node) { + if (node->propsSize >= m_buffer_size) { + delete[] m_buffer; + + while (node->propsSize >= m_buffer_size) { + m_buffer_size <<= 1; + } + + m_buffer = new uint8_t[m_buffer_size]; + } + + //get buffer + if (readBytes(m_buffer, node->propsSize, node->start + 2)) { + //unscape buffer + uint32_t j = 0; + bool escaped = false; + + for (uint32_t i = 0; i < node->propsSize; ++i, ++j) { + if (m_buffer[i] == ESCAPE_CHAR) { + //escape char found, skip it and write next + ++i; + m_buffer[j] = m_buffer[i]; + //is neede a displacement for next bytes + escaped = true; + } else if (escaped) { + //perform that displacement + m_buffer[j] = m_buffer[i]; + } + } + + size = j; + return m_buffer; + } + } + + return NULL; +} + +bool FileLoader::getProps(const NODE node, PropStream& props) +{ + uint32_t size; + + if (const uint8_t* a = getProps(node, size)) { + props.init((char*)a, size); + return true; + } + + props.init(NULL, 0); + return false; +} + +int32_t FileLoader::setProps(void* data, uint16_t size) +{ + //data + if (!writeData(data, size, true)) { + return getError(); + } + + return ERROR_NONE; +} + +void FileLoader::startNode(uint8_t type) +{ + uint8_t nodeBegin = NODE_START; + writeData(&nodeBegin, sizeof(nodeBegin), false); + writeData(&type, sizeof(type), true); +} + +void FileLoader::endNode() +{ + uint8_t nodeEnd = NODE_END; + writeData(&nodeEnd, sizeof(nodeEnd), false); +} + +NODE FileLoader::getChildNode(const NODE parent, uint32_t& type) +{ + if (parent) { + NODE child = parent->child; + + if (child) { + type = child->type; + } + + return child; + } + + type = m_root->type; + return m_root; +} + +NODE FileLoader::getNextNode(const NODE prev, uint32_t& type) +{ + if (prev) { + NODE next = prev->next; + + if (next) { + type = next->type; + } + + return next; + } + + return NO_NODE; +} + +inline bool FileLoader::readByte(int32_t& value) +{ + if (m_use_cache) { + if (m_cache_index == NO_VALID_CACHE) { + m_lastError = ERROR_CACHE_ERROR; + return false; + } + + if (m_cache_offset >= m_cached_data[m_cache_index].size) { + int32_t pos = m_cache_offset + m_cached_data[m_cache_index].base; + int32_t tmp = getCacheBlock(pos); + + if (tmp < 0) { + return false; + } + + m_cache_index = tmp; + m_cache_offset = pos - m_cached_data[m_cache_index].base; + + if (m_cache_offset >= m_cached_data[m_cache_index].size) { + return false; + } + } + + value = m_cached_data[m_cache_index].data[m_cache_offset]; + m_cache_offset++; + return true; + } + + value = fgetc(m_file); + + if (value != EOF) { + return true; + } + + m_lastError = ERROR_EOF; + return false; +} + +inline bool FileLoader::readBytes(uint8_t* buffer, uint32_t size, int32_t pos) +{ + if (m_use_cache) { + //seek at pos + uint32_t reading, remain = size, bufferPos = 0; + + do { + //prepare cache + uint32_t i = getCacheBlock(pos); + + if (i == NO_VALID_CACHE) { + return false; + } + + m_cache_index = i; + m_cache_offset = pos - m_cached_data[i].base; + + //get maximum read block size and calculate remaining bytes + reading = std::min(remain, m_cached_data[i].size - m_cache_offset); + remain = remain - reading; + + //read it + memcpy(buffer + bufferPos, m_cached_data[m_cache_index].data + m_cache_offset, reading); + + //update variables + m_cache_offset = m_cache_offset + reading; + bufferPos = bufferPos + reading; + pos = pos + reading; + } while (remain > 0); + + return true; + } + + if (fseek(m_file, pos, SEEK_SET)) { + m_lastError = ERROR_SEEK_ERROR; + return false; + } + + if (fread(buffer, 1, size, m_file) == (uint32_t)size) { + return true; + } + + m_lastError = ERROR_EOF; + return false; +} + +inline bool FileLoader::checks(const NODE node) +{ + if (!m_file) { + m_lastError = ERROR_NOT_OPEN; + return false; + } + + if (!node) { + m_lastError = ERROR_INVALID_NODE; + return false; + } + + return true; +} + +inline bool FileLoader::safeSeek(uint32_t pos) +{ + if (m_use_cache) { + uint32_t i = getCacheBlock(pos); + + if (i == NO_VALID_CACHE) { + return false; + } + + m_cache_index = i; + m_cache_offset = pos - m_cached_data[i].base; + } else if (fseek(m_file, pos, SEEK_SET)) { + m_lastError = ERROR_SEEK_ERROR; + return false; + } + + return true; +} + +inline bool FileLoader::safeTell(int32_t& pos) +{ + if (m_use_cache) { + if (m_cache_index == NO_VALID_CACHE) { + m_lastError = ERROR_CACHE_ERROR; + return false; + } + + pos = m_cached_data[m_cache_index].base + m_cache_offset - 1; + return true; + } + + pos = ftell(m_file); + + if (pos == -1) { + m_lastError = ERROR_TELL_ERROR; + return false; + } + + pos = pos - 1; + return true; +} + +inline uint32_t FileLoader::getCacheBlock(uint32_t pos) +{ + bool found = false; + uint32_t i, base_pos = pos & ~(m_cache_size - 1); + + for (i = 0; i < CACHE_BLOCKS; i++) { + if (m_cached_data[i].loaded) { + if (m_cached_data[i].base == base_pos) { + found = true; + break; + } + } + } + + if (!found) { + i = loadCacheBlock(pos); + } + + return i; +} + +int32_t FileLoader::loadCacheBlock(uint32_t pos) +{ + int32_t i, loading_cache = -1, base_pos = pos & ~(m_cache_size - 1); + + for (i = 0; i < CACHE_BLOCKS; i++) { + if (!m_cached_data[i].loaded) { + loading_cache = i; + break; + } + } + + if (loading_cache == -1) { + for (i = 0; i < CACHE_BLOCKS; i++) { + if ((long)(abs((long)m_cached_data[i].base - base_pos)) > (long)(2 * m_cache_size)) { + loading_cache = i; + break; + } + } + + if (loading_cache == -1) { + loading_cache = 0; + } + } + + if (m_cached_data[loading_cache].data == NULL) { + m_cached_data[loading_cache].data = new uint8_t[m_cache_size]; + } + + m_cached_data[loading_cache].base = base_pos; + + if (fseek(m_file, m_cached_data[loading_cache].base, SEEK_SET)) { + m_lastError = ERROR_SEEK_ERROR; + return -1; + } + + uint32_t size = fread(m_cached_data[loading_cache].data, 1, m_cache_size, m_file); + m_cached_data[loading_cache].size = size; + + if (size < (pos - m_cached_data[loading_cache].base)) { + m_lastError = ERROR_SEEK_ERROR; + return -1; + } + + m_cached_data[loading_cache].loaded = 1; + return loading_cache; +} diff --git a/src/fileloader.h b/src/fileloader.h new file mode 100644 index 0000000000..4bcb7968ec --- /dev/null +++ b/src/fileloader.h @@ -0,0 +1,404 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_FILELOADER_H__ +#define __OTSERV_FILELOADER_H__ + +#include +#include +#include + +struct NodeStruct; + +typedef NodeStruct* NODE; + +struct NodeStruct { + NodeStruct() { + start = propsSize = type = 0; + next = child = 0; + } + + virtual ~NodeStruct() {} + + uint32_t start; + uint32_t propsSize; + uint32_t type; + NodeStruct* next; + NodeStruct* child; + + static void clearNet(NodeStruct* root) { + if (root) { + clearChild(root); + } + } + + private: + static void clearNext(NodeStruct* node) { + NodeStruct* deleteNode = node; + NodeStruct* nextNode; + + while (deleteNode) { + if (deleteNode->child) { + clearChild(deleteNode->child); + } + + nextNode = deleteNode->next; + delete deleteNode; + deleteNode = nextNode; + } + } + + static void clearChild(NodeStruct* node) { + if (node->child) { + clearChild(node->child); + } + + if (node->next) { + clearNext(node->next); + } + + delete node; + } +}; + +#define NO_NODE 0 + +enum FILELOADER_ERRORS { + ERROR_NONE, + ERROR_INVALID_FILE_VERSION, + ERROR_CAN_NOT_OPEN, + ERROR_CAN_NOT_CREATE, + ERROR_EOF, + ERROR_SEEK_ERROR, + ERROR_NOT_OPEN, + ERROR_INVALID_NODE, + ERROR_INVALID_FORMAT, + ERROR_TELL_ERROR, + ERROR_COULDNOTWRITE, + ERROR_CACHE_ERROR, +}; + +class PropStream; + +class FileLoader +{ + public: + FileLoader(); + virtual ~FileLoader(); + + bool openFile(const char* filename, const char* identifier, bool write, bool caching = false); + const uint8_t* getProps(const NODE, uint32_t& size); + bool getProps(const NODE, PropStream& props); + NODE getChildNode(const NODE parent, uint32_t& type); + NODE getNextNode(const NODE prev, uint32_t& type); + + void startNode(uint8_t type); + void endNode(); + int32_t setProps(void* data, uint16_t size); + + int32_t getError() const { + return m_lastError; + } + void clearError() { + m_lastError = ERROR_NONE; + } + + protected: + enum SPECIAL_BYTES { + NODE_START = 0xFE, + NODE_END = 0xFF, + ESCAPE_CHAR = 0xFD, + }; + + bool parseNode(NODE node); + + inline bool readByte(int32_t& value); + inline bool readBytes(uint8_t* buffer, uint32_t size, int32_t pos); + inline bool checks(const NODE node); + inline bool safeSeek(uint32_t pos); + inline bool safeTell(int32_t& pos); + + public: + inline bool writeData(const void* data, int32_t size, bool unescape) { + for (int32_t i = 0; i < size; ++i) { + uint8_t c = *(((uint8_t*)data) + i); + + if (unescape && (c == NODE_START || c == NODE_END || c == ESCAPE_CHAR)) { + uint8_t escape = ESCAPE_CHAR; + size_t value = fwrite(&escape, 1, 1, m_file); + + if (value != 1) { + m_lastError = ERROR_COULDNOTWRITE; + return false; + } + } + + size_t value = fwrite(&c, 1, 1, m_file); + + if (value != 1) { + m_lastError = ERROR_COULDNOTWRITE; + return false; + } + } + + return true; + } + + protected: + FILE* m_file; + FILELOADER_ERRORS m_lastError; + NODE m_root; + uint32_t m_buffer_size; + uint8_t* m_buffer; + + bool m_use_cache; + struct _cache { + uint32_t loaded; + uint32_t base; + uint32_t size; + uint8_t* data; + }; + +#define CACHE_BLOCKS 3 + uint32_t m_cache_size; + _cache m_cached_data[CACHE_BLOCKS]; +#define NO_VALID_CACHE 0xFFFFFFFF + uint32_t m_cache_index; + uint32_t m_cache_offset; + inline uint32_t getCacheBlock(uint32_t pos); + int32_t loadCacheBlock(uint32_t pos); +}; + +class PropStream +{ + public: + PropStream() { + end = NULL; + p = NULL; + } + ~PropStream() {} + + void init(const char* a, uint32_t size) { + p = a; + end = a + size; + } + + uint64_t size() const { + return end - p; + } + + template + inline bool GET_STRUCT(T* &ret) { + if (size() < (long)sizeof(T)) { + ret = NULL; + return false; + } + + ret = (T*)p; + p += sizeof(T); + return true; + } + + template + inline bool GET_VALUE(T& ret) { + if (size() < (long)sizeof(T)) { + return false; + } + + ret = *((T*)p); + p += sizeof(T); + return true; + } + + inline bool GET_TIME(time_t& ret) { + return GET_VALUE(ret); + } + + inline bool GET_ULONG(uint32_t& ret) { + return GET_VALUE(ret); + } + + inline bool GET_USHORT(uint16_t& ret) { + return GET_VALUE(ret); + } + + inline bool GET_UCHAR(uint8_t& ret) { + return GET_VALUE(ret); + } + + inline bool GET_STRING(std::string& ret) { + uint16_t str_len; + + if (!GET_USHORT(str_len)) { + return false; + } + + return GET_STRING(ret, str_len); + } + + inline bool GET_STRING(std::string& ret, uint16_t str_len) { + if (size() < (int32_t)str_len) { + return false; + } + + char* str = new char[str_len + 1]; + memcpy(str, p, str_len); + str[str_len] = 0; + ret.assign(str, str_len); + delete[] str; + p += str_len; + return true; + } + + inline bool GET_LSTRING(std::string& ret) { + char* str; + uint32_t str_len; + + if (!GET_ULONG(str_len)) { + return false; + } + + if (size() < str_len) { + return false; + } + + str = new char[str_len + 1]; + memcpy(str, p, str_len); + str[str_len] = 0; + ret.assign(str, str_len); + delete[] str; + p += str_len; + return true; + } + + inline bool GET_NSTRING(unsigned short str_len, std::string& ret) { + if (size() < (int32_t)str_len) { + return false; + } + + char* str = new char[str_len + 1]; + memcpy(str, p, str_len); + str[str_len] = 0; + ret.assign(str, str_len); // String can contain 0s + delete[] str; + p += str_len; + return true; + } + + inline bool SKIP_N(uint32_t n) { + if (size() < n) { + return false; + } + + p += n; + return true; + } + + protected: + const char* p; + const char* end; +}; + +class PropWriteStream +{ + public: + PropWriteStream() { + buffer = (char*)malloc(32 * sizeof(char)); + buffer_size = 32; + size = 0; + memset(buffer, 0, 32 * sizeof(char)); + } + + ~PropWriteStream() { + free(buffer); + } + + const char* getStream(uint32_t& _size) const { + _size = size; + return buffer; + } + + //TODO: might need temp buffer and zero fill the memory chunk allocated by realloc + template + inline void ADD_TYPE(T* add) { + if ((buffer_size - size) < sizeof(T)) { + buffer_size = buffer_size + ((sizeof(T) + 0x1F) & 0xFFFFFFE0); + buffer = (char*)realloc(buffer, buffer_size); + } + + memcpy(&buffer[size], (char*)add, sizeof(T)); + size = size + sizeof(T); + } + + template + inline void ADD_VALUE(T add) { + if ((buffer_size - size) < sizeof(T)) { + buffer_size = buffer_size + ((sizeof(T) + 0x1F) & 0xFFFFFFE0); + buffer = (char*)realloc(buffer, buffer_size); + } + + memcpy(&buffer[size], &add, sizeof(T)); + size = size + sizeof(T); + } + + inline void ADD_ULONG(uint32_t ret) { + ADD_VALUE(ret); + } + + inline void ADD_USHORT(uint16_t ret) { + ADD_VALUE(ret); + } + + inline void ADD_UCHAR(uint8_t ret) { + ADD_VALUE(ret); + } + + inline void ADD_STRING(const std::string& add) { + uint16_t str_len = (uint16_t)add.size(); + ADD_USHORT(str_len); + + if ((buffer_size - size) < str_len) { + buffer_size += ((str_len + 0x1F) & 0xFFFFFFE0); + buffer = (char*)realloc(buffer, buffer_size); + } + + memcpy(&buffer[size], add.c_str(), str_len); + size = size + str_len; + } + + inline void ADD_LSTRING(const std::string& add) { + uint32_t str_len = (uint32_t)add.size(); + + ADD_ULONG(str_len); + + if ((buffer_size - size) < str_len) { + buffer_size = buffer_size + ((str_len + 0x1F) & 0xFFFFFFE0); + buffer = (char*)realloc(buffer, buffer_size); + } + + memcpy(&buffer[size], add.c_str(), str_len); + size = size + str_len; + } + + protected: + char* buffer; + uint32_t buffer_size; + uint32_t size; +}; + +#endif diff --git a/src/game.cpp b/src/game.cpp new file mode 100644 index 0000000000..c39a154f85 --- /dev/null +++ b/src/game.cpp @@ -0,0 +1,7025 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "definitions.h" + +#include +#include +#include + +#include +#include + +#include "otsystem.h" +#include "tasks.h" +#include "items.h" +#include "commands.h" +#include "creature.h" +#include "player.h" +#include "monster.h" +#include "game.h" +#include "tile.h" +#include "house.h" +#include "actions.h" +#include "combat.h" +#include "iologindata.h" +#include "iomarket.h" +#include "chat.h" +#include "luascript.h" +#include "talkaction.h" +#include "spells.h" +#include "configmanager.h" +#include "ban.h" +#include "raids.h" +#include "database.h" +#include "server.h" +#include "ioguild.h" +#include "quests.h" +#include "globalevent.h" +#include "mounts.h" +#include "beds.h" + +extern ConfigManager g_config; +extern Actions* g_actions; +extern Commands commands; +extern Chat g_chat; +extern TalkActions* g_talkActions; +extern Spells* g_spells; +extern Vocations g_vocations; +extern GlobalEvents* g_globalEvents; + +Game::Game() +{ + gameState = GAME_STATE_NORMAL; + worldType = WORLD_TYPE_PVP; + + checkLightEvent = 0; + checkCreatureEvent = 0; + checkDecayEvent = 0; + + map = NULL; + lastStageLevel = 0; + lastPlayersRecord = 0; + useLastStageLevel = false; + stagesEnabled = false; + stateTime = OTSYS_TIME(); + + for (int16_t i = 0; i < 3; i++) { + serverSaveMessage[i] = false; + } + + OTSYS_THREAD_LOCKVARINIT(AutoID::autoIDLock); + + lastBucket = 0; + + //(1440 minutes/day)/(3600 seconds/day)*10 seconds event interval + int32_t dayCycle = 3600; + lightHourDelta = 1440 * 10 / dayCycle; + lightHour = SUNRISE + (SUNSET - SUNRISE) / 2; + lightLevel = LIGHT_LEVEL_DAY; + lightState = LIGHT_STATE_DAY; + + offlineTrainingWindow = new ModalWindow(0xFFFFFFFF, "Choose a Skill", "Please choose a skill:"); + offlineTrainingWindow->addChoice(SKILL_SWORD, "Sword Fighting and Shielding"); + offlineTrainingWindow->addChoice(SKILL_AXE, "Axe Fighting and Shielding"); + offlineTrainingWindow->addChoice(SKILL_CLUB, "Club Fighting and Shielding"); + offlineTrainingWindow->addChoice(SKILL_DIST, "Distance Fighting and Shielding"); + offlineTrainingWindow->addChoice(SKILL__MAGLEVEL, "Magic Level and Shielding"); + offlineTrainingWindow->addButton(1, "Okay"); + offlineTrainingWindow->addButton(0, "Cancel"); + offlineTrainingWindow->setDefaultEnterButton(1); + offlineTrainingWindow->setDefaultEscapeButton(0); + offlineTrainingWindow->setPriority(true); + + wildcardTree = new WildcardTreeNode(false); +} + +Game::~Game() +{ + for (OTSERV_HASH_MAP::iterator it = guilds.begin(); it != guilds.end(); ++it) { + delete it->second; + } + + delete map; + delete offlineTrainingWindow; + delete wildcardTree; + + g_scheduler.stopEvent(checkLightEvent); + g_scheduler.stopEvent(checkCreatureEvent); + g_scheduler.stopEvent(checkDecayEvent); +} + +void Game::start(ServiceManager* servicer) +{ + services = servicer; + + checkLightEvent = g_scheduler.addEvent(createSchedulerTask(EVENT_LIGHTINTERVAL, + boost::bind(&Game::checkLight, this))); + + checkCreatureLastIndex = 0; + checkCreatureEvent = g_scheduler.addEvent(createSchedulerTask(EVENT_CREATURE_THINK_INTERVAL, + boost::bind(&Game::checkCreatures, this))); + + checkDecayEvent = g_scheduler.addEvent(createSchedulerTask(EVENT_DECAYINTERVAL, + boost::bind(&Game::checkDecay, this))); +} + +GameState_t Game::getGameState() const +{ + return gameState; +} + +void Game::setWorldType(WorldType_t type) +{ + worldType = type; +} + +void Game::setGameState(GameState_t newState) +{ + if (gameState == GAME_STATE_SHUTDOWN) { + return; //this cannot be stopped + } + + if (gameState != newState) { + gameState = newState; + + switch (newState) { + case GAME_STATE_INIT: { + Spawns::getInstance()->startup(); + + Raids::getInstance()->loadFromXml(); + Raids::getInstance()->startup(); + + Quests::getInstance()->loadFromXml(); + + Mounts::getInstance()->loadFromXml(); + + loadMotd(); + loadPlayersRecord(); + + loadGameState(); + g_globalEvents->startup(); + break; + } + + case GAME_STATE_SHUTDOWN: { + g_globalEvents->execute(GLOBALEVENT_SHUTDOWN); + //kick all players that are still online + AutoList::listiterator it = Player::listPlayer.list.begin(); + + while (it != Player::listPlayer.list.end()) { + it->second->kickPlayer(true); + it = Player::listPlayer.list.begin(); + } + + saveGameState(); + + g_dispatcher.addTask( + createTask(boost::bind(&Game::shutdown, this))); + + g_scheduler.stop(); + g_dispatcher.stop(); + break; + } + + case GAME_STATE_CLOSED: { + /* kick all players without the CanAlwaysLogin flag */ + AutoList::listiterator it = Player::listPlayer.list.begin(); + + while (it != Player::listPlayer.list.end()) { + if (!it->second->hasFlag(PlayerFlag_CanAlwaysLogin)) { + it->second->kickPlayer(true); + it = Player::listPlayer.list.begin(); + } else { + ++it; + } + } + + saveGameState(); + break; + } + + default: + break; + } + } +} + +void Game::saveGameState() +{ + if (gameState == GAME_STATE_NORMAL) { + setGameState(GAME_STATE_MAINTAIN); + } + + stateTime = 0; + std::cout << "Saving server..." << std::endl; + IOLoginData* ioLoginData = IOLoginData::getInstance(); + + for (AutoList::listiterator it = Player::listPlayer.list.begin(); it != Player::listPlayer.list.end(); ++it) { + it->second->loginPosition = it->second->getPosition(); + ioLoginData->savePlayer(it->second); + } + + map->saveMap(); + ScriptEnvironment::saveGameState(); + stateTime = OTSYS_TIME() + STATE_TIME; + + if (gameState == GAME_STATE_MAINTAIN) { + setGameState(GAME_STATE_NORMAL); + } +} + +void Game::loadGameState() +{ + ScriptEnvironment::loadGameState(); +} + +int32_t Game::loadMap(const std::string& filename) +{ + if (!map) { + map = new Map; + } + + inFightTicks = g_config.getNumber(ConfigManager::PZ_LOCKED); + Player::maxMessageBuffer = g_config.getNumber(ConfigManager::MAX_MESSAGEBUFFER); + Monster::despawnRange = g_config.getNumber(ConfigManager::DEFAULT_DESPAWNRANGE); + Monster::despawnRadius = g_config.getNumber(ConfigManager::DEFAULT_DESPAWNRADIUS); + return map->loadMap("data/world/" + filename + ".otbm"); +} + +void Game::refreshMap() +{ + Tile* tile; + Item* item; + + for (Map::TileMap::iterator it = map->refreshTileMap.begin(); it != map->refreshTileMap.end(); ++it) { + tile = it->first; + + if (TileItemVector* items = tile->getItemList()) { + //remove garbage + int32_t downItemSize = tile->getDownItemCount(); + + for (int32_t i = downItemSize - 1; i >= 0; --i) { + item = items->at(i); + + if (item) { + internalRemoveItem(item); + } + } + } + + cleanup(); + + //restore to original state + TileItemVector list = it->second.list; + + for (ItemVector::reverse_iterator it = list.rbegin(); it != list.rend(); ++it) { + Item* item = (*it)->clone(); + ReturnValue ret = internalAddItem(tile, item, INDEX_WHEREEVER, FLAG_NOLIMIT); + + if (ret == RET_NOERROR) { + if (item->getUniqueId() != 0) { + ScriptEnvironment::addUniqueThing(item); + } + + startDecay(item); + } else { + std::cout << "Could not refresh item: " << item->getID() << "pos: " << tile->getPosition() << std::endl; + delete item; + } + } + } +} + +Cylinder* Game::internalGetCylinder(Player* player, const Position& pos) +{ + if (pos.x != 0xFFFF) { + return getTile(pos.x, pos.y, pos.z); + } + + //container + if (pos.y & 0x40) { + uint8_t from_cid = pos.y & 0x0F; + return player->getContainer(from_cid); + } + + //inventory + return player; +} + +Thing* Game::internalGetThing(Player* player, const Position& pos, int32_t index, uint32_t spriteId /*= 0*/, stackPosType_t type /*= STACKPOS_NORMAL*/) +{ + if (pos.x != 0xFFFF) { + Tile* tile = getTile(pos.x, pos.y, pos.z); + + if (tile) { + /*look at*/ + if (type == STACKPOS_LOOK) { + return tile->getTopVisibleThing(player); + } + + Thing* thing = NULL; + + /*for move operations*/ + if (type == STACKPOS_MOVE) { + Item* item = tile->getTopDownItem(); + + if (item && !item->isNotMoveable()) { + thing = item; + } else { + thing = tile->getTopVisibleCreature(player); + } + } else if (type == STACKPOS_USEITEM) { + //First check items with topOrder 2 (ladders, signs, splashes) + Item* item = tile->getItemByTopOrder(2); + + if (item && g_actions->hasAction(item)) { + thing = item; + } else { + //then down items + thing = tile->getTopDownItem(); + + if (!thing) { + thing = tile->getTopTopItem(); //then last we check items with topOrder 3 (doors etc) + + if (!thing) { + thing = tile->ground; + } + } + } + } else if (type == STACKPOS_USE) { + thing = tile->getTopDownItem(); + } else { + thing = tile->__getThing(index); + } + + if (player) { + //do extra checks here if the thing is accessable + if (thing && thing->getItem()) { + if (tile->hasProperty(ISVERTICAL)) { + if (player->getPosition().x + 1 == tile->getPosition().x) { + thing = NULL; + } + } else if (tile->hasProperty(ISHORIZONTAL)) { + if (player->getPosition().y + 1 == tile->getPosition().y) { + thing = NULL; + } + } + } + } + + return thing; + } + } else { + //container + if (pos.y & 0x40) { + uint8_t fromCid = pos.y & 0x0F; + uint8_t slot = pos.z; + + Container* parentcontainer = player->getContainer(fromCid); + + if (!parentcontainer) { + return NULL; + } + + return parentcontainer->getItem(player->getContainerIndex(fromCid) + slot); + } else if (pos.y == 0 && pos.z == 0) { + const ItemType& it = Item::items.getItemIdByClientId(spriteId); + + if (it.id == 0) { + return NULL; + } + + int32_t subType = -1; + + if (it.isFluidContainer() && index < int32_t(sizeof(reverseFluidMap) / sizeof(int8_t))) { + subType = reverseFluidMap[index]; + } + + return findItemOfType(player, it.id, true, subType); + } + //inventory + else { + slots_t slot = (slots_t)static_cast(pos.y); + return player->getInventoryItem(slot); + } + } + + return NULL; +} + +void Game::internalGetPosition(Item* item, Position& pos, uint8_t& stackpos) +{ + pos.x = 0; + pos.y = 0; + pos.z = 0; + stackpos = 0; + + Cylinder* topParent = item->getTopParent(); + + if (topParent) { + if (Player* player = dynamic_cast(topParent)) { + pos.x = 0xFFFF; + + Container* container = dynamic_cast(item->getParent()); + + if (container) { + pos.y = ((uint16_t) ((uint16_t)0x40) | ((uint16_t)player->getContainerID(container)) ); + pos.z = container->__getIndexOfThing(item); + stackpos = pos.z; + } else { + pos.y = player->__getIndexOfThing(item); + stackpos = pos.y; + } + } else if (Tile* tile = topParent->getTile()) { + pos = tile->getPosition(); + stackpos = tile->__getIndexOfThing(item); + } + } +} + +void Game::setTile(Tile* newTile) +{ + return map->setTile(newTile->getPosition(), newTile); +} + +Tile* Game::getTile(int32_t x, int32_t y, int32_t z) +{ + return map->getTile(x, y, z); +} + +Tile* Game::getTile(const Position& pos) +{ + return map->getTile(pos); +} + +QTreeLeafNode* Game::getLeaf(uint32_t x, uint32_t y) +{ + return map->getLeaf(x, y); +} + +Creature* Game::getCreatureByID(uint32_t id) +{ + if (id == 0) { + return NULL; + } + + AutoList::listiterator it = listCreature.list.find(id); + + if (it != listCreature.list.end()) { + if (!it->second->isRemoved()) { + return it->second; + } + } + + return NULL; +} + +Player* Game::getPlayerByID(uint32_t id) +{ + if (id == 0) { + return NULL; + } + + AutoList::listiterator it = Player::listPlayer.list.find(id); + + if (it != Player::listPlayer.list.end()) { + if (!it->second->isRemoved()) { + return it->second; + } + } + + return NULL; +} + +Creature* Game::getCreatureByName(const std::string& s) +{ + if (s.empty()) { + return NULL; + } + + std::string txt1 = asUpperCaseString(s); + + for (AutoList::listiterator it = listCreature.list.begin(); it != listCreature.list.end(); ++it) { + if (!it->second->isRemoved() && txt1 == asUpperCaseString(it->second->getName())) { + return it->second; + } + } + + return NULL; //just in case the creature doesnt exist +} + +Player* Game::getPlayerByName(const std::string& s) +{ + if (s.empty()) { + return NULL; + } + + PlayerNameMap::const_iterator it = mappedPlayerNames.find(asLowerCaseString(s)); + + if (it == mappedPlayerNames.end()) { + return NULL; + } + + if (it->second->isRemoved()) { + return NULL; + } + + return it->second; +} + +Player* Game::getPlayerByGUID(const uint32_t& guid) +{ + if (guid == 0) { + return NULL; + } + + for (AutoList::listiterator it = Player::listPlayer.list.begin(); it != Player::listPlayer.list.end(); ++it) { + Player* player = it->second; + + if (!player->isRemoved() && guid == player->getGUID()) { + return player; + } + } + + return NULL; +} + +ReturnValue Game::getPlayerByNameWildcard(const std::string& s, Player*& player) +{ + size_t strlen = s.length(); + + if (strlen == 0 || strlen > 20) { + return RET_PLAYERWITHTHISNAMEISNOTONLINE; + } + + if ((*s.rbegin()) == '~') { + const std::string& query = asLowerCaseString(s.substr(0, strlen - 1)); + std::string result; + ReturnValue ret = wildcardTree->findOne(query, result); + + if (ret != RET_NOERROR) { + return ret; + } + + player = getPlayerByName(result); + } else { + player = getPlayerByName(s); + } + + if (!player) { + return RET_PLAYERWITHTHISNAMEISNOTONLINE; + } + + return RET_NOERROR; +} + +Player* Game::getPlayerByAccount(uint32_t acc) +{ + for (AutoList::listiterator it = Player::listPlayer.list.begin(); it != Player::listPlayer.list.end(); ++it) { + if (!it->second->isRemoved() && it->second->getAccount() == acc) { + return it->second; + } + } + + return NULL; +} + +PlayerVector Game::getPlayersByAccount(uint32_t acc) +{ + PlayerVector players; + + for (AutoList::listiterator it = Player::listPlayer.list.begin(); it != Player::listPlayer.list.end(); ++it) { + if (!it->second->isRemoved() && it->second->getAccount() == acc) { + players.push_back(it->second); + } + } + + return players; +} + +PlayerVector Game::getPlayersByIP(uint32_t ipadress, uint32_t mask) +{ + PlayerVector players; + + for (AutoList::listiterator it = Player::listPlayer.list.begin(); it != Player::listPlayer.list.end(); ++it) { + if (!it->second->isRemoved() && (it->second->getIP() & mask) == (ipadress & mask)) { + players.push_back(it->second); + } + } + + return players; +} + +bool Game::internalPlaceCreature(Creature* creature, const Position& pos, bool extendedPos /*=false*/, bool forced /*= false*/) +{ + if (creature->getParent() != NULL) { + return false; + } + + if (!map->placeCreature(pos, creature, extendedPos, forced)) { + return false; + } + + creature->useThing2(); + creature->setID(); + listCreature.addList(creature); + creature->addList(); + return true; +} + +bool Game::placeCreature(Creature* creature, const Position& pos, bool extendedPos /*=false*/, bool forced /*= false*/) +{ + if (!internalPlaceCreature(creature, pos, extendedPos, forced)) { + return false; + } + + SpectatorVec list; + getSpectators(list, creature->getPosition(), true); + + SpectatorVec::const_iterator end = list.end(); + + for (SpectatorVec::const_iterator it = list.begin(); it != end; ++it) { + if (Player* tmpPlayer = (*it)->getPlayer()) { + tmpPlayer->sendCreatureAppear(creature, creature->getPosition(), true); + } + } + + for (SpectatorVec::const_iterator it = list.begin(); it != end; ++it) { + (*it)->onCreatureAppear(creature, true); + } + + Cylinder* creatureParent = creature->getParent(); + int32_t newIndex = creatureParent->__getIndexOfThing(creature); + creatureParent->postAddNotification(creature, NULL, newIndex); + + // TODO: Move this code to Player::onCreatureAppear where creature == this. + Player* player = creature->getPlayer(); + + if (player) { + int32_t offlineTime; + + if (player->getLastLogout() != 0) { + // Not counting more than 21 days to prevent overflow when multiplying with 1000 (for milliseconds). + offlineTime = std::min(time(NULL) - player->getLastLogout(), 86400 * 21); + } else { + offlineTime = 0; + } + + Condition* conditionMuted = player->getCondition(CONDITION_MUTED, CONDITIONID_DEFAULT); + + if (conditionMuted && conditionMuted->getTicks() > 0) { + conditionMuted->setTicks(conditionMuted->getTicks() - (offlineTime * 1000)); + + if (conditionMuted->getTicks() <= 0) { + player->removeCondition(conditionMuted); + } else { + player->addCondition(conditionMuted->clone()); + } + } + + Condition* conditionTrade = player->getCondition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT, CHANNEL_ADVERTISING); + + if (conditionTrade && conditionTrade->getTicks() > 0) { + conditionTrade->setTicks(conditionTrade->getTicks() - (offlineTime * 1000)); + + if (conditionTrade->getTicks() <= 0) { + player->removeCondition(conditionTrade); + } else { + player->addCondition(conditionTrade->clone()); + } + } + + Condition* conditionTradeRook = player->getCondition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT, CHANNEL_ADVERTISINGROOKGAARD); + + if (conditionTradeRook && conditionTradeRook->getTicks() > 0) { + conditionTradeRook->setTicks(conditionTradeRook->getTicks() - (offlineTime * 1000)); + + if (conditionTradeRook->getTicks() <= 0) { + player->removeCondition(conditionTradeRook); + } else { + player->addCondition(conditionTradeRook->clone()); + } + } + + Condition* conditionHelp = player->getCondition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT, CHANNEL_HELP); + + if (conditionHelp && conditionHelp->getTicks() > 0) { + conditionHelp->setTicks(conditionHelp->getTicks() - (offlineTime * 1000)); + + if (conditionHelp->getTicks() <= 0) { + player->removeCondition(conditionHelp); + } else { + player->addCondition(conditionHelp->clone()); + } + } + + Condition* conditionYell = player->getCondition(CONDITION_YELLTICKS, CONDITIONID_DEFAULT); + + if (conditionYell && conditionYell->getTicks() > 0) { + conditionYell->setTicks(conditionYell->getTicks() - (offlineTime * 1000)); + + if (conditionYell->getTicks() <= 0) { + player->removeCondition(conditionYell); + } else { + player->addCondition(conditionYell->clone()); + } + } + + if (player->isPremium()) { + int32_t value; + player->getStorageValue(STORAGEVALUE_PROMOTION, value); + + if (player->isPromoted() && value != 1) { + player->addStorageValue(STORAGEVALUE_PROMOTION, 1); + } else if (!player->isPromoted() && value == 1) { + player->setVocation(g_vocations.getPromotedVocation(player->getVocationId())); + } + } else if (player->isPromoted()) { + player->setVocation(player->vocation->getFromVocation()); + } + + bool sentStats = false; + + int16_t oldStaminaMinutes = player->getStaminaMinutes(); + player->regenerateStamina(offlineTime); + + int32_t offlineTrainingSkill = player->getOfflineTrainingSkill(); + + if (offlineTrainingSkill != -1) { + player->setOfflineTrainingSkill(-1); + int32_t offlineTrainingTime = std::max(0, std::min(offlineTime, std::min(43200, player->getOfflineTrainingTime() / 1000))); + + if (offlineTime >= 600) { + player->removeOfflineTrainingTime(offlineTrainingTime * 1000); + + int32_t remainder = offlineTime - offlineTrainingTime; + + if (remainder > 0) { + player->addOfflineTrainingTime(remainder * 1000); + } + + if (offlineTrainingTime >= 60) { + std::ostringstream ss; + ss << "During your absence you trained for "; + int32_t hours = offlineTrainingTime / 3600; + + if (hours > 1) { + ss << hours << " hours"; + } else if (hours == 1) { + ss << "1 hour"; + } + + int32_t minutes = (offlineTrainingTime % 3600) / 60; + + if (minutes != 0) { + if (hours != 0) { + ss << " and "; + } + + if (minutes > 1) { + ss << minutes << " minutes"; + } else { + ss << "1 minute"; + } + } + + ss << "."; + player->sendTextMessage(MSG_EVENT_ADVANCE, ss.str()); + + Vocation* vocation; + + if (player->isPromoted()) { + vocation = player->getVocation(); + } else { + int32_t promotedVocationId = g_vocations.getPromotedVocation(player->getVocationId()); + vocation = g_vocations.getVocation(promotedVocationId); + } + + bool sendUpdateSkills = false; + + if (offlineTrainingSkill == SKILL_CLUB || offlineTrainingSkill == SKILL_SWORD || offlineTrainingSkill == SKILL_AXE) { + float modifier = vocation->getAttackSpeed() / 1000.f; + sendUpdateSkills = player->addOfflineTrainingTries((skills_t)offlineTrainingSkill, (offlineTrainingTime / modifier) / 2); + } else if (offlineTrainingSkill == SKILL_DIST) { + float modifier = vocation->getAttackSpeed() / 1000.f; + sendUpdateSkills = player->addOfflineTrainingTries((skills_t)offlineTrainingSkill, (offlineTrainingTime / modifier) / 4); + } else if (offlineTrainingSkill == SKILL__MAGLEVEL) { + int32_t gainTicks = vocation->getManaGainTicks() << 1; + + if (gainTicks == 0) { + gainTicks = 1; + } + + player->addOfflineTrainingTries(SKILL__MAGLEVEL, offlineTrainingTime * (vocation->getManaGainAmount() / gainTicks)); + } + + if (player->addOfflineTrainingTries(SKILL_SHIELD, offlineTrainingTime / 4) || sendUpdateSkills) { + player->sendSkills(); + } + + } + + player->sendStats(); + sentStats = true; + } else { + player->sendTextMessage(MSG_EVENT_ADVANCE, "You must be logged out for more than 10 minutes to start offline training."); + } + } else { + uint16_t oldMinutes = player->getOfflineTrainingTime() / 60 / 1000; + player->addOfflineTrainingTime(offlineTime * 1000); + uint16_t newMinutes = player->getOfflineTrainingTime() / 60 / 1000; + + if (oldMinutes != newMinutes) { + player->sendStats(); + sentStats = true; + } + } + + if (!sentStats && player->getStaminaMinutes() != oldStaminaMinutes) { + player->sendStats(); + } + } + + addCreatureCheck(creature); + creature->onPlacedCreature(); + return true; +} + +bool Game::removeCreature(Creature* creature, bool isLogout /*= true*/) +{ + if (creature->isRemoved()) { + return false; + } + + Tile* tile = creature->getTile(); + + SpectatorVec list; + getSpectators(list, tile->getPosition(), true); + + SpectatorVec::const_iterator end = list.end(); + + std::vector oldStackPosVector; + + for (SpectatorVec::const_iterator it = list.begin(); it != end; ++it) { + if (Player* player = (*it)->getPlayer()) { + if (!creature->isInGhostMode() || player->isAccessPlayer()) { + oldStackPosVector.push_back(tile->getClientIndexOfThing(player, creature)); + } + } + } + + int32_t index = tile->__getIndexOfThing(creature); + + if (!map->removeCreature(creature)) { + return false; + } + + //send to client + uint32_t i = 0; + + for (SpectatorVec::const_iterator it = list.begin(); it != end; ++it) { + if (Player* player = (*it)->getPlayer()) { + if (!creature->isInGhostMode() || player->isAccessPlayer()) { + player->sendCreatureDisappear(creature, oldStackPosVector[i], isLogout); + ++i; + } + } + } + + //event method + for (SpectatorVec::const_iterator it = list.begin(); it != end; ++it) { + (*it)->onCreatureDisappear(creature, index, isLogout); + } + + creature->getParent()->postRemoveNotification(creature, NULL, index, true); + + listCreature.removeList(creature->getID()); + creature->removeList(); + creature->setRemoved(); + FreeThing(creature); + + removeCreatureCheck(creature); + + for (std::list::iterator cit = creature->summons.begin(); cit != creature->summons.end(); ++cit) { + (*cit)->setLossSkill(false); + removeCreature(*cit); + } + + creature->onRemovedCreature(); + return true; +} + +bool Game::playerMoveThing(uint32_t playerId, const Position& fromPos, + uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + uint8_t fromIndex = 0; + + if (fromPos.x == 0xFFFF) { + if (fromPos.y & 0x40) { + fromIndex = static_cast(fromPos.z); + } else { + fromIndex = static_cast(fromPos.y); + } + } else { + fromIndex = fromStackPos; + } + + Thing* thing = internalGetThing(player, fromPos, fromIndex, spriteId, STACKPOS_MOVE); + + Cylinder* toCylinder = internalGetCylinder(player, toPos); + + if (!thing || !toCylinder) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + return false; + } + + if (Creature* movingCreature = thing->getCreature()) { + if (Position::areInRange<1, 1, 0>(movingCreature->getPosition(), player->getPosition())) { + SchedulerTask* task = createSchedulerTask(1000, + boost::bind(&Game::playerMoveCreature, this, player->getID(), + movingCreature->getID(), movingCreature->getPosition(), toCylinder->getPosition())); + player->setNextActionTask(task); + } else { + playerMoveCreature(playerId, movingCreature->getID(), movingCreature->getPosition(), toCylinder->getPosition()); + } + } else if (thing->getItem()) { + player->incrementMoveItemsBuffer(); + + if ((OTSYS_TIME() - player->getLastMoveItemTime()) < 105) { + player->incrementMoveItemsBuffer(3); + return false; + } + + player->updateLastMoveItemTime(); + + if (player->getMoveItemsBuffer() > 6) { + return false; + } + + playerMoveItem(playerId, fromPos, spriteId, fromStackPos, toPos, count); + } + + return true; +} + +bool Game::playerMoveCreature(uint32_t playerId, uint32_t movingCreatureId, + const Position& movingCreatureOrigPos, const Position& toPos) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + if (!player->canDoAction()) { + uint32_t delay = player->getNextActionTime(); + SchedulerTask* task = createSchedulerTask(delay, boost::bind(&Game::playerMoveCreature, + this, playerId, movingCreatureId, movingCreatureOrigPos, toPos)); + player->setNextActionTask(task); + return false; + } + + player->setNextActionTask(NULL); + + Creature* movingCreature = getCreatureByID(movingCreatureId); + + if (!movingCreature || movingCreature->isRemoved()) { + return false; + } + + if (movingCreature->getPlayer()) { + if (movingCreature->getPlayer()->getNoMove()) { + return false; + } + } + + if (!Position::areInRange<1, 1, 0>(movingCreatureOrigPos, player->getPosition())) { + //need to walk to the creature first before moving it + std::list listDir; + + if (getPathToEx(player, movingCreatureOrigPos, listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask(boost::bind(&Game::playerAutoWalk, + this, player->getID(), listDir))); + SchedulerTask* task = createSchedulerTask(1500, boost::bind(&Game::playerMoveCreature, this, + playerId, movingCreatureId, movingCreatureOrigPos, toPos)); + player->setNextWalkActionTask(task); + return true; + } else { + player->sendCancelMessage(RET_THEREISNOWAY); + return false; + } + } + + Tile* toTile = getTile(toPos); + + if (!toTile) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + return false; + } + + if ((!movingCreature->isPushable() && !player->hasFlag(PlayerFlag_CanPushAllCreatures)) || + (movingCreature->isInGhostMode() && !player->isAccessPlayer())) { + player->sendCancelMessage(RET_NOTMOVEABLE); + return false; + } + + //check throw distance + const Position& movingCreaturePos = movingCreature->getPosition(); + + if ((std::abs(movingCreaturePos.x - toPos.x) > movingCreature->getThrowRange()) || (std::abs(movingCreaturePos.y - toPos.y) > movingCreature->getThrowRange()) || (std::abs(movingCreaturePos.z - toPos.z) * 4 > movingCreature->getThrowRange())) { + player->sendCancelMessage(RET_DESTINATIONOUTOFREACH); + return false; + } + + Tile* movingCreatureTile = movingCreature->getTile(); + + if (!movingCreatureTile) { + player->sendCancelMessage(RET_NOTMOVEABLE); + return false; + } + + if (player != movingCreature) { + bool toTileIsSafe = false; + + if (toTile->hasFlag(TILESTATE_NOPVPZONE) || toTile->hasFlag(TILESTATE_PROTECTIONZONE)) { + toTileIsSafe = true; + } + + if (toTile->hasProperty(BLOCKPATH)) { + player->sendCancelMessage(RET_NOTENOUGHROOM); + return false; + } else if ((movingCreature->getZone() == ZONE_PROTECTION || movingCreature->getZone() == ZONE_NOPVP) && !toTileIsSafe) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + return false; + } else if (movingCreature->getNpc() && !Spawns::getInstance()->isInZone(movingCreature->masterPos, movingCreature->masterRadius, toPos)) { + player->sendCancelMessage(RET_NOTENOUGHROOM); + return false; + } else if (toTile->getCreatureCount() > 0) { + player->sendCancelMessage(RET_NOTENOUGHROOM); + return false; + } + } + + ReturnValue ret = internalMoveCreature(movingCreature, movingCreatureTile, toTile); + + if (ret != RET_NOERROR) { + player->sendCancelMessage(ret); + return false; + } + + return true; +} + +ReturnValue Game::internalMoveCreature(Creature* creature, Direction direction, uint32_t flags /*= 0*/) +{ + Cylinder* fromTile = creature->getTile(); + Cylinder* toTile = NULL; + + creature->setLastPosition(creature->getPosition()); + const Position& currentPos = creature->getPosition(); + Position destPos = currentPos; + bool diagonalMovement; + + switch (direction) { + case NORTHWEST: + case NORTHEAST: + case SOUTHWEST: + case SOUTHEAST: + diagonalMovement = true; + break; + + default: + diagonalMovement = false; + break; + } + + destPos = getNextPosition(direction, destPos); + + if (creature->getPlayer() && !diagonalMovement) { + //try go up + if (currentPos.z != 8 && creature->getTile()->hasHeight(3)) { + Tile* tmpTile = getTile(currentPos.x, currentPos.y, currentPos.z - 1); + + if (tmpTile == NULL || (tmpTile->ground == NULL && !tmpTile->hasProperty(BLOCKSOLID))) { + tmpTile = getTile(destPos.x, destPos.y, destPos.z - 1); + + if (tmpTile && tmpTile->ground && !tmpTile->hasProperty(BLOCKSOLID)) { + flags = flags | FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE; + + if (!tmpTile->floorChange()) { + destPos.z--; + } + } + } + } else { + //try go down + Tile* tmpTile = getTile(destPos); + + if (currentPos.z != 7 && (tmpTile == NULL || (tmpTile->ground == NULL && !tmpTile->hasProperty(BLOCKSOLID)))) { + tmpTile = getTile(destPos.x, destPos.y, destPos.z + 1); + + if (tmpTile && tmpTile->hasHeight(3)) { + flags = flags | FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE; + destPos.z++; + } + } + } + } + + toTile = getTile(destPos); + ReturnValue ret = RET_NOTPOSSIBLE; + + if (toTile != NULL) { + ret = internalMoveCreature(creature, fromTile, toTile, flags); + } + + return ret; +} + +ReturnValue Game::internalMoveCreature(Creature* creature, Cylinder* fromCylinder, Cylinder* toCylinder, uint32_t flags /*= 0*/) +{ + //check if we can move the creature to the destination + ReturnValue ret = toCylinder->__queryAdd(0, creature, 1, flags); + + if (ret != RET_NOERROR) { + return ret; + } + + fromCylinder->getTile()->moveCreature(creature, toCylinder); + + int32_t index = 0; + Item* toItem = NULL; + Cylinder* subCylinder = NULL; + + uint32_t n = 0; + + while ((subCylinder = toCylinder->__queryDestination(index, creature, &toItem, flags)) != toCylinder) { + toCylinder->getTile()->moveCreature(creature, subCylinder); + + if (creature->getParent() != subCylinder) { + //could happen if a script move the creature + break; + } + + toCylinder = subCylinder; + flags = 0; + + //to prevent infinite loop + if (++n >= MAP_MAX_LAYERS) { + break; + } + } + + return RET_NOERROR; +} + +bool Game::playerMoveItem(uint32_t playerId, const Position& fromPos, + uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + if (!player->canDoAction()) { + uint32_t delay = player->getNextActionTime(); + SchedulerTask* task = createSchedulerTask(delay, boost::bind(&Game::playerMoveItem, this, + playerId, fromPos, spriteId, fromStackPos, toPos, count)); + player->setNextActionTask(task); + return false; + } + + player->setNextActionTask(NULL); + + Cylinder* fromCylinder = internalGetCylinder(player, fromPos); + uint8_t fromIndex = 0; + + if (fromPos.x == 0xFFFF) { + if (fromPos.y & 0x40) { + fromIndex = static_cast(fromPos.z); + } else { + fromIndex = static_cast(fromPos.y); + } + } else { + fromIndex = fromStackPos; + } + + Thing* thing = internalGetThing(player, fromPos, fromIndex, spriteId, STACKPOS_MOVE); + + if (!thing || !thing->getItem()) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + return false; + } + + Item* item = thing->getItem(); + + Cylinder* toCylinder = internalGetCylinder(player, toPos); + uint8_t toIndex = 0; + + if (toPos.x == 0xFFFF) { + if (toPos.y & 0x40) { + toIndex = static_cast(toPos.z); + } else { + toIndex = static_cast(toPos.y); + } + } + + if (fromCylinder == NULL || toCylinder == NULL || item == NULL || item->getClientID() != spriteId) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + return false; + } + + if (!item->isPushable() || item->getUniqueId() != 0) { + player->sendCancelMessage(RET_NOTMOVEABLE); + return false; + } + + const Position& playerPos = player->getPosition(); + + const Position& mapFromPos = fromCylinder->getTile()->getPosition(); + + const Tile* toCylinderTile = toCylinder->getTile(); + + const Position& mapToPos = toCylinderTile->getPosition(); + + if (playerPos.z > mapFromPos.z) { + player->sendCancelMessage(RET_FIRSTGOUPSTAIRS); + return false; + } + + if (playerPos.z < mapFromPos.z) { + player->sendCancelMessage(RET_FIRSTGODOWNSTAIRS); + return false; + } + + if (!Position::areInRange<1, 1, 0>(playerPos, mapFromPos)) { + //need to walk to the item first before using it + std::list listDir; + + if (getPathToEx(player, item->getPosition(), listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask(boost::bind(&Game::playerAutoWalk, + this, player->getID(), listDir))); + + SchedulerTask* task = createSchedulerTask(400, boost::bind(&Game::playerMoveItem, this, + playerId, fromPos, spriteId, fromStackPos, toPos, count)); + player->setNextWalkActionTask(task); + return true; + } else { + player->sendCancelMessage(RET_THEREISNOWAY); + return false; + } + } + + //hangable item specific code + if (item->isHangable() && toCylinderTile->hasProperty(SUPPORTHANGABLE)) { + //destination supports hangable objects so need to move there first + if (toCylinderTile->hasProperty(ISVERTICAL)) { + if (player->getPosition().x + 1 == mapToPos.x) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + return false; + } + } else if (toCylinderTile->hasProperty(ISHORIZONTAL)) { + if (player->getPosition().y + 1 == mapToPos.y) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + return false; + } + } + + if (!Position::areInRange<1, 1, 0>(playerPos, mapToPos)) { + Position walkPos = mapToPos; + + if (toCylinderTile->hasProperty(ISVERTICAL)) { + walkPos.x -= -1; + } + + if (toCylinderTile->hasProperty(ISHORIZONTAL)) { + walkPos.y -= -1; + } + + Position itemPos = fromPos; + uint8_t itemStackPos = fromStackPos; + + if (fromPos.x != 0xFFFF && Position::areInRange<1, 1, 0>(mapFromPos, player->getPosition()) + && !Position::areInRange<1, 1, 0>(mapFromPos, walkPos)) { + //need to pickup the item first + Item* moveItem = NULL; + + ReturnValue ret = internalMoveItem(fromCylinder, player, INDEX_WHEREEVER, item, count, &moveItem); + + if (ret != RET_NOERROR) { + player->sendCancelMessage(ret); + return false; + } + + //changing the position since its now in the inventory of the player + internalGetPosition(moveItem, itemPos, itemStackPos); + } + + std::list listDir; + + if (map->getPathTo(player, walkPos, listDir)) { + g_dispatcher.addTask(createTask(boost::bind(&Game::playerAutoWalk, + this, player->getID(), listDir))); + + SchedulerTask* task = createSchedulerTask(400, boost::bind(&Game::playerMoveItem, this, + playerId, itemPos, spriteId, itemStackPos, toPos, count)); + player->setNextWalkActionTask(task); + return true; + } else { + player->sendCancelMessage(RET_THEREISNOWAY); + return false; + } + } + } + + if ((std::abs(playerPos.x - mapToPos.x) > item->getThrowRange()) || + (std::abs(playerPos.y - mapToPos.y) > item->getThrowRange()) || + (std::abs(mapFromPos.z - mapToPos.z) * 4 > item->getThrowRange())) { + player->sendCancelMessage(RET_DESTINATIONOUTOFREACH); + return false; + } + + if (!canThrowObjectTo(mapFromPos, mapToPos)) { + player->sendCancelMessage(RET_CANNOTTHROW); + return false; + } + + ReturnValue ret = internalMoveItem(fromCylinder, toCylinder, toIndex, item, count, NULL, 0, player); + + if (ret != RET_NOERROR) { + player->sendCancelMessage(ret); + return false; + } + + return true; +} + +ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, int32_t index, + Item* item, uint32_t count, Item** _moveItem, uint32_t flags /*= 0*/, Creature* actor/* = NULL*/) +{ + if (!toCylinder) { + return RET_NOTPOSSIBLE; + } + + Tile* fromTile = fromCylinder->getTile(); + + if (fromTile) { + BrowseFieldMap::const_iterator it = browseFields.find(fromTile); + + if (it != browseFields.end() && it->second == fromCylinder) { + fromCylinder = fromTile; + } + } + + Item* toItem = NULL; + + Cylinder* subCylinder; + int floorN = 0; + + while ((subCylinder = toCylinder->__queryDestination(index, item, &toItem, flags)) != toCylinder) { + toCylinder = subCylinder; + flags = 0; + + //to prevent infinite loop + if (++floorN >= MAP_MAX_LAYERS) { + break; + } + } + + //destination is the same as the source? + if (item == toItem) { + return RET_NOERROR; //silently ignore move + } + + //check if we can add this item + ReturnValue ret = toCylinder->__queryAdd(index, item, count, flags, actor); + + if (ret == RET_NEEDEXCHANGE) { + //check if we can add it to source cylinder + int32_t fromIndex = fromCylinder->__getIndexOfThing(item); + + ret = fromCylinder->__queryAdd(fromIndex, toItem, toItem->getItemCount(), 0); + + if (ret == RET_NOERROR) { + //check how much we can move + uint32_t maxExchangeQueryCount = 0; + ReturnValue retExchangeMaxCount = fromCylinder->__queryMaxCount(INDEX_WHEREEVER, toItem, toItem->getItemCount(), maxExchangeQueryCount, 0); + + if (retExchangeMaxCount != RET_NOERROR && maxExchangeQueryCount == 0) { + return retExchangeMaxCount; + } + + if (toCylinder->__queryRemove(toItem, toItem->getItemCount(), flags) == RET_NOERROR) { + int32_t oldToItemIndex = toCylinder->__getIndexOfThing(toItem); + toCylinder->__removeThing(toItem, toItem->getItemCount()); + fromCylinder->__addThing(toItem); + + if (oldToItemIndex != -1) { + toCylinder->postRemoveNotification(toItem, fromCylinder, oldToItemIndex, true); + } + + int32_t newToItemIndex = fromCylinder->__getIndexOfThing(toItem); + + if (newToItemIndex != -1) { + fromCylinder->postAddNotification(toItem, toCylinder, newToItemIndex); + } + + ret = toCylinder->__queryAdd(index, item, count, flags); + toItem = NULL; + } + } + } + + if (ret != RET_NOERROR) { + return ret; + } + + //check how much we can move + uint32_t maxQueryCount = 0; + ReturnValue retMaxCount = toCylinder->__queryMaxCount(index, item, count, maxQueryCount, flags); + + if (retMaxCount != RET_NOERROR && maxQueryCount == 0) { + return retMaxCount; + } + + uint32_t m = 0; + + if (item->isStackable()) { + m = std::min(count, maxQueryCount); + } else { + m = maxQueryCount; + } + + Item* moveItem = item; + + //check if we can remove this item + ret = fromCylinder->__queryRemove(item, m, flags); + + if (ret != RET_NOERROR) { + return ret; + } + + //remove the item + int32_t itemIndex = fromCylinder->__getIndexOfThing(item); + Item* updateItem = NULL; + fromCylinder->__removeThing(item, m); + bool isCompleteRemoval = item->isRemoved(); + + //update item(s) + if (item->isStackable()) { + uint32_t n; + + if (toItem && toItem->getID() == item->getID()) { + n = std::min(100 - toItem->getItemCount(), m); + toCylinder->__updateThing(toItem, toItem->getID(), toItem->getItemCount() + n); + updateItem = toItem; + } else { + n = 0; + } + + int32_t count = m - n; + + if (count > 0) { + moveItem = Item::CreateItem(item->getID(), count); + } else { + moveItem = NULL; + } + + if (item->isRemoved()) { + FreeThing(item); + } + } + + //add item + if (moveItem /*m - n > 0*/) { + toCylinder->__addThing(index, moveItem); + } + + if (itemIndex != -1) { + fromCylinder->postRemoveNotification(item, toCylinder, itemIndex, isCompleteRemoval); + } + + if (moveItem) { + int32_t moveItemIndex = toCylinder->__getIndexOfThing(moveItem); + + if (moveItemIndex != -1) { + toCylinder->postAddNotification(moveItem, fromCylinder, moveItemIndex); + } + } + + if (updateItem) { + int32_t updateItemIndex = toCylinder->__getIndexOfThing(updateItem); + + if (updateItemIndex != -1) { + toCylinder->postAddNotification(updateItem, fromCylinder, updateItemIndex); + } + } + + if (_moveItem) { + if (moveItem) { + *_moveItem = moveItem; + } else { + *_moveItem = item; + } + } + + //we could not move all, inform the player + if (item->isStackable() && maxQueryCount < count) { + return retMaxCount; + } + + return ret; +} + +ReturnValue Game::internalMoveTradeItem(Cylinder* fromCylinder, Cylinder* toCylinder, int32_t index, + Item* item, Item* tradeItem, uint32_t count, Item** _moveItem, uint32_t flags /*= 0*/, Creature* actor/* = NULL*/) +{ + if (!toCylinder) { + return RET_NOTPOSSIBLE; + } + + Tile* fromTile = fromCylinder->getTile(); + + if (fromTile) { + BrowseFieldMap::const_iterator it = browseFields.find(fromTile); + + if (it != browseFields.end() && it->second == fromCylinder) { + fromCylinder = fromTile; + } + } + + Item* toItem = NULL; + + Cylinder* subCylinder; + int floorN = 0; + + while ((subCylinder = toCylinder->__queryDestination(index, item, &toItem, flags)) != toCylinder) { + toCylinder = subCylinder; + flags = 0; + + //to prevent infinite loop + if (++floorN >= MAP_MAX_LAYERS) { + break; + } + } + + //destination is the same as the source? + if (item == toItem) { + return RET_NOERROR; //silently ignore move + } + + //check if we can add this item + ReturnValue ret = toCylinder->__queryAdd(index, item, count, flags, actor); + + if (ret == RET_NEEDEXCHANGE) { + //check if we can add it to source cylinder + int32_t fromIndex = fromCylinder->__getIndexOfThing(item); + + ret = fromCylinder->__queryAdd(fromIndex, toItem, toItem->getItemCount(), 0); + + if (ret == RET_NOERROR) { + //check how much we can move + uint32_t maxExchangeQueryCount = 0; + ReturnValue retExchangeMaxCount = fromCylinder->__queryMaxCount(INDEX_WHEREEVER, toItem, toItem->getItemCount(), maxExchangeQueryCount, 0); + + if (retExchangeMaxCount != RET_NOERROR && maxExchangeQueryCount == 0) { + return retExchangeMaxCount; + } + + if (toCylinder->__queryRemove(toItem, toItem->getItemCount(), flags) == RET_NOERROR) { + int32_t oldToItemIndex = toCylinder->__getIndexOfThing(toItem); + toCylinder->__removeThing(toItem, toItem->getItemCount()); + fromCylinder->__addThing(toItem); + + if (oldToItemIndex != -1) { + toCylinder->postRemoveNotification(toItem, fromCylinder, oldToItemIndex, true); + } + + int32_t newToItemIndex = fromCylinder->__getIndexOfThing(toItem); + + if (newToItemIndex != -1) { + fromCylinder->postAddNotification(toItem, toCylinder, newToItemIndex); + } + + ret = toCylinder->__queryAdd(index, item, count, flags); + toItem = NULL; + } + } + } + + if (ret != RET_NOERROR) { + return ret; + } + + //check how much we can move + uint32_t maxQueryCount = 0; + ReturnValue retMaxCount = toCylinder->__queryMaxCount(index, item, count, maxQueryCount, flags); + + if (retMaxCount != RET_NOERROR && maxQueryCount == 0) { + return retMaxCount; + } + + uint32_t m = 0; + + if (item->isStackable()) { + m = std::min(count, maxQueryCount); + } else { + m = maxQueryCount; + } + + Item* moveItem = item; + + //check if we can remove this item + ret = fromCylinder->__queryRemove(item, m, flags); + + if (ret != RET_NOERROR) { + return ret; + } + + if (toCylinder->getItem() == tradeItem) { + return RET_NOTENOUGHROOM; + } + + Cylinder* tmpCylinder = toCylinder->getParent(); + + while (tmpCylinder) { + if (tmpCylinder->getItem() == tradeItem) { + return RET_NOTENOUGHROOM; + } + + tmpCylinder = tmpCylinder->getParent(); + } + + //remove the item + int32_t itemIndex = fromCylinder->__getIndexOfThing(item); + Item* updateItem = NULL; + fromCylinder->__removeThing(item, m); + bool isCompleteRemoval = item->isRemoved(); + + //update item(s) + if (item->isStackable()) { + uint32_t n; + + if (toItem && toItem->getID() == item->getID()) { + n = std::min(100 - toItem->getItemCount(), m); + toCylinder->__updateThing(toItem, toItem->getID(), toItem->getItemCount() + n); + updateItem = toItem; + } else { + n = 0; + } + + int32_t count = m - n; + + if (count > 0) { + moveItem = Item::CreateItem(item->getID(), count); + } else { + moveItem = NULL; + } + + if (item->isRemoved()) { + FreeThing(item); + } + } + + //add item + if (moveItem /*m - n > 0*/) { + toCylinder->__addThing(index, moveItem); + } + + if (itemIndex != -1) { + fromCylinder->postRemoveNotification(item, toCylinder, itemIndex, isCompleteRemoval); + } + + if (moveItem) { + int32_t moveItemIndex = toCylinder->__getIndexOfThing(moveItem); + + if (moveItemIndex != -1) { + toCylinder->postAddNotification(moveItem, fromCylinder, moveItemIndex); + } + } + + if (updateItem) { + int32_t updateItemIndex = toCylinder->__getIndexOfThing(updateItem); + + if (updateItemIndex != -1) { + toCylinder->postAddNotification(updateItem, fromCylinder, updateItemIndex); + } + } + + if (_moveItem) { + if (moveItem) { + *_moveItem = moveItem; + } else { + *_moveItem = item; + } + } + + //we could not move all, inform the player + if (item->isStackable() && maxQueryCount < count) { + return retMaxCount; + } + + return ret; +} + +ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t index /*= INDEX_WHEREEVER*/, + uint32_t flags/* = 0*/, bool test/* = false*/) +{ + uint32_t remainderCount = 0; + return internalAddItem(toCylinder, item, index, flags, test, remainderCount); +} + +ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t index, + uint32_t flags, bool test, uint32_t& remainderCount) +{ + remainderCount = 0; + + if (toCylinder == NULL || item == NULL) { + return RET_NOTPOSSIBLE; + } + + Cylinder* destCylinder = toCylinder; + Item* toItem = NULL; + toCylinder = toCylinder->__queryDestination(index, item, &toItem, flags); + + //check if we can add this item + ReturnValue ret = toCylinder->__queryAdd(index, item, item->getItemCount(), flags); + + if (ret != RET_NOERROR) { + return ret; + } + + /* + Check if we can move add the whole amount, we do this by checking against the original cylinder, + since the queryDestination can return a cylinder that might only hold a part of the full amount. + */ + uint32_t maxQueryCount = 0; + ret = destCylinder->__queryMaxCount(INDEX_WHEREEVER, item, item->getItemCount(), maxQueryCount, flags); + + if (ret != RET_NOERROR) { + return ret; + } + + if (test) { + return RET_NOERROR; + } + + if (item->isStackable() && toItem && toItem->getID() == item->getID()) { + uint32_t m = std::min(item->getItemCount(), maxQueryCount); + uint32_t n = 0; + + if (toItem->getID() == item->getID()) { + n = std::min(100 - toItem->getItemCount(), m); + toCylinder->__updateThing(toItem, toItem->getID(), toItem->getItemCount() + n); + } + + int32_t count = m - n; + + if (count > 0) { + if (item->getItemCount() != count) { + Item* remainderItem = Item::CreateItem(item->getID(), count); + + if (internalAddItem(destCylinder, remainderItem, INDEX_WHEREEVER, flags, false) != RET_NOERROR) { + FreeThing(remainderItem); + remainderCount = count; + } + } else { + toCylinder->__addThing(index, item); + + int32_t itemIndex = toCylinder->__getIndexOfThing(item); + + if (itemIndex != -1) { + toCylinder->postAddNotification(item, NULL, itemIndex); + } + } + } else { + //fully merged with toItem, item will be destroyed + item->onRemoved(); + FreeThing(item); + + int32_t itemIndex = toCylinder->__getIndexOfThing(toItem); + + if (itemIndex != -1) { + toCylinder->postAddNotification(toItem, NULL, itemIndex); + } + } + } else { + toCylinder->__addThing(index, item); + + int32_t itemIndex = toCylinder->__getIndexOfThing(item); + + if (itemIndex != -1) { + toCylinder->postAddNotification(item, NULL, itemIndex); + } + } + + return RET_NOERROR; +} + +ReturnValue Game::internalRemoveItem(Item* item, int32_t count /*= -1*/, bool test /*= false*/, uint32_t flags /*= 0*/) +{ + Cylinder* cylinder = item->getParent(); + + if (cylinder == NULL) { + return RET_NOTPOSSIBLE; + } + + Tile* fromTile = cylinder->getTile(); + + if (fromTile) { + BrowseFieldMap::const_iterator it = browseFields.find(fromTile); + + if (it != browseFields.end() && it->second == cylinder) { + cylinder = fromTile; + } + } + + if (count == -1) { + count = item->getItemCount(); + } + + //check if we can remove this item + ReturnValue ret = cylinder->__queryRemove(item, count, flags | FLAG_IGNORENOTMOVEABLE); + + if (ret != RET_NOERROR) { + return ret; + } + + if (!item->canRemove()) { + return RET_NOTPOSSIBLE; + } + + if (!test) { + int32_t index = cylinder->__getIndexOfThing(item); + + //remove the item + cylinder->__removeThing(item, count); + bool isCompleteRemoval = false; + + if (item->isRemoved()) { + isCompleteRemoval = true; + FreeThing(item); + } + + cylinder->postRemoveNotification(item, NULL, index, isCompleteRemoval); + } + + item->onRemoved(); + return RET_NOERROR; +} + +ReturnValue Game::internalPlayerAddItem(Player* player, Item* item, bool dropOnMap /*= true*/, slots_t slot /*= SLOT_WHEREEVER*/) +{ + uint32_t remainderCount = 0; + ReturnValue ret = internalAddItem(player, item, (int32_t)slot, 0, false, remainderCount); + + if (remainderCount > 0) { + Item* remainderItem = Item::CreateItem(item->getID(), remainderCount); + ReturnValue remaindRet = internalAddItem(player->getTile(), remainderItem, INDEX_WHEREEVER, FLAG_NOLIMIT); + + if (remaindRet != RET_NOERROR) { + FreeThing(remainderItem); + } + } + + if (ret != RET_NOERROR && dropOnMap) { + ret = internalAddItem(player->getTile(), item, INDEX_WHEREEVER, FLAG_NOLIMIT); + } + + return ret; +} + +Item* Game::findItemOfType(Cylinder* cylinder, uint16_t itemId, + bool depthSearch /*= true*/, int32_t subType /*= -1*/) +{ + if (cylinder == NULL) { + return NULL; + } + + std::list listContainer; + Container* tmpContainer = NULL; + Thing* thing = NULL; + Item* item = NULL; + + for (int32_t i = cylinder->__getFirstIndex(); i < cylinder->__getLastIndex();) { + if ((thing = cylinder->__getThing(i)) && (item = thing->getItem())) { + if (item->getID() == itemId && (subType == -1 || subType == item->getSubType())) { + return item; + } else { + ++i; + + if (depthSearch && (tmpContainer = item->getContainer())) { + listContainer.push_back(tmpContainer); + } + } + } else { + ++i; + } + } + + while (!listContainer.empty()) { + Container* container = listContainer.front(); + listContainer.pop_front(); + + for (ItemDeque::const_iterator it = container->getItems(), end = container->getEnd(); it != end; ++it) { + Item* item = *it; + + if (item->getID() == itemId && (subType == -1 || subType == item->getSubType())) { + return item; + } + + if ((tmpContainer = item->getContainer())) { + listContainer.push_back(tmpContainer); + } + } + } + + return NULL; +} + +bool Game::removeItemOfType(Cylinder* cylinder, uint16_t itemId, int32_t count, int32_t subType /*= -1*/, bool onlySubContainers/* = false*/) +{ + if (cylinder == NULL || ((int32_t)cylinder->__getItemTypeCount(itemId, subType) < count)) { + return false; + } + + if (count <= 0) { + return true; + } + + std::list listContainer; + Container* tmpContainer = NULL; + Thing* thing = NULL; + Item* item = NULL; + + for (int32_t i = cylinder->__getFirstIndex(); i < cylinder->__getLastIndex() && count > 0;) { + if ((thing = cylinder->__getThing(i)) && (item = thing->getItem())) { + if (!onlySubContainers && item->getID() == itemId) { + if (item->isStackable()) { + if (item->getItemCount() > count) { + internalRemoveItem(item, count); + count = 0; + } else { + count -= item->getItemCount(); + internalRemoveItem(item); + } + } else if (subType == -1 || subType == item->getSubType()) { + --count; + internalRemoveItem(item); + } else { + ++i; + } + } else { + ++i; + + if ((tmpContainer = item->getContainer())) { + listContainer.push_back(tmpContainer); + } + } + } else { + ++i; + } + } + + while (!listContainer.empty() && count > 0) { + Container* container = listContainer.front(); + listContainer.pop_front(); + + for (int32_t i = 0; i < (int32_t)container->size() && count > 0;) { + Item* item = container->getItem(i); + + if (item->getID() == itemId) { + if (item->isStackable()) { + if (item->getItemCount() > count) { + internalRemoveItem(item, count); + count = 0; + } else { + count -= item->getItemCount(); + internalRemoveItem(item); + } + } else if (subType == -1 || subType == item->getSubType()) { + --count; + internalRemoveItem(item); + } else { + ++i; + } + } else { + ++i; + + if ((tmpContainer = item->getContainer())) { + listContainer.push_back(tmpContainer); + } + } + } + } + + return (count == 0); +} + +uint64_t Game::getMoney(const Cylinder* cylinder) +{ + if (cylinder == NULL) { + return 0; + } + + std::list listContainer; + ItemDeque::const_iterator it; + Container* tmpContainer; + + Thing* thing; + Item* item; + + uint64_t moneyCount = 0; + + for (int32_t i = cylinder->__getFirstIndex(); i < cylinder->__getLastIndex(); ++i) { + if (!(thing = cylinder->__getThing(i))) { + continue; + } + + if (!(item = thing->getItem())) { + continue; + } + + if ((tmpContainer = item->getContainer())) { + listContainer.push_back(tmpContainer); + } else if (item->getWorth() != 0) { + moneyCount += item->getWorth(); + } + } + + while (!listContainer.empty()) { + Container* container = listContainer.front(); + listContainer.pop_front(); + + for (it = container->getItems(); it != container->getEnd(); ++it) { + Item* item = *it; + + if ((tmpContainer = item->getContainer())) { + listContainer.push_back(tmpContainer); + } else if (item->getWorth() != 0) { + moneyCount += item->getWorth(); + } + } + } + + return moneyCount; +} + +bool Game::removeMoney(Cylinder* cylinder, uint64_t money, uint32_t flags /*= 0*/) +{ + if (cylinder == NULL) { + return false; + } + + if (money <= 0) { + return true; + } + + std::list listContainer; + Container* tmpContainer = NULL; + + typedef std::multimap > MoneyMap; + typedef MoneyMap::value_type moneymap_pair; + MoneyMap moneyMap; + Thing* thing; + Item* item; + uint64_t moneyCount = 0; + + for (int32_t i = cylinder->__getFirstIndex(); i < cylinder->__getLastIndex(); ++i) { + if (!(thing = cylinder->__getThing(i))) { + continue; + } + + if (!(item = thing->getItem())) { + continue; + } + + if ((tmpContainer = item->getContainer())) { + listContainer.push_back(tmpContainer); + } else if (item->getWorth() != 0) { + moneyCount += item->getWorth(); + moneyMap.insert(moneymap_pair(item->getWorth(), item)); + } + } + + while (!listContainer.empty()) { + Container* container = listContainer.front(); + listContainer.pop_front(); + + for (ItemDeque::const_iterator it = container->getItems(), end = container->getEnd(); it != end; ++it) { + Item* item = *it; + + if ((tmpContainer = item->getContainer())) { + listContainer.push_back(tmpContainer); + } else if (item->getWorth() != 0) { + moneyCount += item->getWorth(); + moneyMap.insert(moneymap_pair(item->getWorth(), item)); + } + } + } + + /*not enough money*/ + if (moneyCount < money) { + return false; + } + + MoneyMap::iterator mit, mend; + + for (mit = moneyMap.begin(), mend = moneyMap.end(); mit != mend && money > 0; ++mit) { + Item* item = mit->second; + internalRemoveItem(item); + + if (mit->first > money) { + /* Remove a monetary value from an item*/ + uint64_t remaind = item->getWorth() - money; + addMoney(cylinder, remaind, flags); + money = 0; + } else { + money -= mit->first; + } + } + + return money == 0; +} + +void Game::addMoney(Cylinder* cylinder, uint64_t money, uint32_t flags /*= 0*/) +{ + int32_t crys = money / 10000; + money -= crys * 10000; + int64_t plat = money / 100; + money -= plat * 100; + int64_t gold = money; + + while (crys > 0) { + Item* remaindItem = Item::CreateItem(ITEM_COINS_CRYSTAL, std::min(100, crys)); + + ReturnValue ret = internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags); + + if (ret != RET_NOERROR) { + internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER, FLAG_NOLIMIT); + } + + crys -= std::min(100, crys); + } + + if (plat != 0) { + Item* remaindItem = Item::CreateItem(ITEM_COINS_PLATINUM, plat); + + ReturnValue ret = internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags); + + if (ret != RET_NOERROR) { + internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER, FLAG_NOLIMIT); + } + } + + if (gold != 0) { + Item* remaindItem = Item::CreateItem(ITEM_COINS_GOLD, gold); + + ReturnValue ret = internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags); + + if (ret != RET_NOERROR) { + internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER, FLAG_NOLIMIT); + } + } +} + +Item* Game::transformItem(Item* item, uint16_t newId, int32_t newCount /*= -1*/) +{ + if (item->getID() == newId && (newCount == -1 || (newCount == item->getSubType() && newCount != 0))) { //chargeless item placed on map = infinite + return item; + } + + Cylinder* cylinder = item->getParent(); + + if (cylinder == NULL) { + return NULL; + } + + Tile* fromTile = cylinder->getTile(); + + if (fromTile) { + BrowseFieldMap::const_iterator it = browseFields.find(fromTile); + + if (it != browseFields.end() && it->second == cylinder) { + cylinder = fromTile; + } + } + + int32_t itemIndex = cylinder->__getIndexOfThing(item); + + if (itemIndex == -1) { + return item; + } + + if (!item->canTransform()) { + return item; + } + + const ItemType& curType = Item::items[item->getID()]; + + const ItemType& newType = Item::items[newId]; + + if (curType.alwaysOnTop != newType.alwaysOnTop) { + //This only occurs when you transform items on tiles from a downItem to a topItem (or vice versa) + //Remove the old, and add the new + ReturnValue ret = internalRemoveItem(item); + + if (ret != RET_NOERROR) { + return item; + } + + Item* newItem = NULL; + + if (newCount == -1) { + newItem = Item::CreateItem(newId); + } else { + newItem = Item::CreateItem(newId, newCount); + } + + if (newItem == NULL) { + return NULL; + } + + newItem->copyAttributes(item); + + ret = internalAddItem(cylinder, newItem, INDEX_WHEREEVER, FLAG_NOLIMIT); + + if (ret != RET_NOERROR) { + delete newItem; + return NULL; + } + + return newItem; + } + + if (curType.type == newType.type) { + //Both items has the same type so we can safely change id/subtype + if (newCount == 0 && (item->isStackable() || item->hasCharges())) { + if (item->isStackable()) { + internalRemoveItem(item); + return NULL; + } else { + int32_t newItemId = newId; + + if (curType.id == newType.id) { + newItemId = curType.decayTo; + } + + if (newItemId == -1) { + internalRemoveItem(item); + return NULL; + } else if (newItemId != newId) { + //Replacing the the old item with the new while maintaining the old position + Item* newItem = Item::CreateItem(newItemId, 1); + + if (newItem == NULL) { + return NULL; + } + + cylinder->__replaceThing(itemIndex, newItem); + cylinder->postAddNotification(newItem, cylinder, itemIndex); + + item->setParent(NULL); + cylinder->postRemoveNotification(item, cylinder, itemIndex, true); + FreeThing(item); + return newItem; + } else { + item = transformItem(item, newItemId); + return item; + } + } + } else { + cylinder->postRemoveNotification(item, cylinder, itemIndex, false); + uint16_t itemId = item->getID(); + int32_t count = item->getSubType(); + + if (curType.id != newType.id) { + if (newType.group != curType.group) { + item->setDefaultSubtype(); + } + + itemId = newId; + } + + if (newCount != -1 && newType.hasSubType()) { + count = newCount; + } + + cylinder->__updateThing(item, itemId, count); + cylinder->postAddNotification(item, cylinder, itemIndex); + return item; + } + } else { + //Replacing the the old item with the new while maintaining the old position + Item* newItem = NULL; + + if (newCount == -1) { + newItem = Item::CreateItem(newId); + } else { + newItem = Item::CreateItem(newId, newCount); + } + + if (newItem == NULL) { + return NULL; + } + + cylinder->__replaceThing(itemIndex, newItem); + cylinder->postAddNotification(newItem, cylinder, itemIndex); + + item->setParent(NULL); + cylinder->postRemoveNotification(item, cylinder, itemIndex, true); + FreeThing(item); + + return newItem; + } + + return NULL; +} + +ReturnValue Game::internalTeleport(Thing* thing, const Position& newPos, bool pushMove/* = true*/, uint32_t flags /*= 0*/) +{ + if (newPos == thing->getPosition()) { + return RET_NOERROR; + } else if (thing->isRemoved()) { + return RET_NOTPOSSIBLE; + } + + Tile* toTile = getTile(newPos.x, newPos.y, newPos.z); + + if (toTile) { + if (Creature* creature = thing->getCreature()) { + ReturnValue ret = toTile->__queryAdd(0, creature, 1, FLAG_NOLIMIT); + + if (ret != RET_NOERROR) { + return ret; + } + + creature->getTile()->moveCreature(creature, toTile, !pushMove); + return RET_NOERROR; + } else if (Item* item = thing->getItem()) { + return internalMoveItem(item->getParent(), toTile, INDEX_WHEREEVER, item, item->getItemCount(), NULL, flags); + } + } + + return RET_NOTPOSSIBLE; +} + +//Implementation of player invoked events +bool Game::playerMove(uint32_t playerId, Direction direction) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + player->resetIdleTime(); + player->setNextWalkActionTask(NULL); + + std::list dirs; + dirs.push_back(direction); + return player->startAutoWalk(dirs); +} + +bool Game::playerBroadcastMessage(Player* player, const std::string& text) +{ + if (!player->hasFlag(PlayerFlag_CanBroadcast)) { + return false; + } + + std::cout << "> " << player->getName() << " broadcasted: \"" << text << "\"." << std::endl; + + for (AutoList::listiterator it = Player::listPlayer.list.begin(); it != Player::listPlayer.list.end(); ++it) { + it->second->sendCreatureSay(player, SPEAK_BROADCAST, text); + } + + return true; +} + +bool Game::playerCreatePrivateChannel(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved() || !player->isPremium()) { + return false; + } + + ChatChannel* channel = g_chat.createChannel(player, CHANNEL_PRIVATE); + + if (!channel || !channel->addUser(player)) { + return false; + } + + player->sendCreatePrivateChannel(channel->getId(), channel->getName()); + return true; +} + +bool Game::playerChannelInvite(uint32_t playerId, const std::string& name) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + PrivateChatChannel* channel = g_chat.getPrivateChannel(player); + + if (!channel) { + return false; + } + + Player* invitePlayer = getPlayerByName(name); + + if (!invitePlayer) { + return false; + } + + channel->invitePlayer(player, invitePlayer); + return true; +} + +bool Game::playerChannelExclude(uint32_t playerId, const std::string& name) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + PrivateChatChannel* channel = g_chat.getPrivateChannel(player); + + if (!channel) { + return false; + } + + Player* excludePlayer = getPlayerByName(name); + + if (!excludePlayer) { + return false; + } + + channel->excludePlayer(player, excludePlayer); + return true; +} + +bool Game::playerRequestChannels(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + player->sendChannelsDialog(); + return true; +} + +bool Game::playerOpenChannel(uint32_t playerId, uint16_t channelId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + ChatChannel* channel = g_chat.addUserToChannel(player, channelId); + + if (!channel) { + return false; + } + + player->sendChannel(channel->getId(), channel->getName()); + return true; +} + +bool Game::playerCloseChannel(uint32_t playerId, uint16_t channelId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + g_chat.removeUserFromChannel(player, channelId); + return true; +} + +bool Game::playerOpenPrivateChannel(uint32_t playerId, std::string& receiver) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + if (!IOLoginData::getInstance()->playerExists(receiver)) { + player->sendCancel("A player with this name does not exist."); + return true; + } + + player->sendOpenPrivateChannel(receiver); + return true; +} + +bool Game::playerCloseNpcChannel(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + SpectatorVec list; + getSpectators(list, player->getPosition()); + + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + if (Npc* npc = (*it)->getNpc()) { + npc->onPlayerCloseChannel(player); + } + } + + return true; +} + +bool Game::playerReceivePing(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + player->receivePing(); + return true; +} + +bool Game::playerRemoveLagging(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + player->receivePing(); + player->setLagging(false); + + SpectatorVec list; + getSpectators(list, player->getPosition(), true); + + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + if (Monster* tmpMonster = (*it)->getMonster()) { + tmpMonster->onCreatureAppear(player, false); + } + } + + return true; +} + +bool Game::playerReceivePingBack(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + player->sendPingBack(); + return true; +} + +bool Game::playerAutoWalk(uint32_t playerId, std::list& listDir) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + player->resetIdleTime(); + player->setNextWalkTask(NULL); + return player->startAutoWalk(listDir); +} + +bool Game::playerStopAutoWalk(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + player->stopWalk(); + return true; +} + +bool Game::playerUseItemEx(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos, uint16_t fromSpriteId, + const Position& toPos, uint8_t toStackPos, uint16_t toSpriteId, bool isHotkey) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + if (isHotkey && !g_config.getBoolean(ConfigManager::AIMBOT_HOTKEY_ENABLED)) { + return false; + } + + Thing* thing = internalGetThing(player, fromPos, fromStackPos, fromSpriteId, STACKPOS_USEITEM); + + if (!thing) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + return false; + } + + Item* item = thing->getItem(); + + if (!item || !item->isUseable() || item->getClientID() != fromSpriteId) { + player->sendCancelMessage(RET_CANNOTUSETHISOBJECT); + return false; + } + + Position walkToPos = fromPos; + ReturnValue ret = g_actions->canUse(player, fromPos); + + if (ret == RET_NOERROR) { + ret = g_actions->canUse(player, toPos, item); + + if (ret == RET_TOOFARAWAY) { + walkToPos = toPos; + } + } + + if (ret != RET_NOERROR) { + if (ret == RET_TOOFARAWAY) { + Position itemPos = fromPos; + uint8_t itemStackPos = fromStackPos; + + if (fromPos.x != 0xFFFF && toPos.x != 0xFFFF && Position::areInRange<1, 1, 0>(fromPos, player->getPosition()) && + !Position::areInRange<1, 1, 0>(fromPos, toPos)) { + Item* moveItem = NULL; + + ReturnValue ret = internalMoveItem(item->getParent(), player, INDEX_WHEREEVER, + item, item->getItemCount(), &moveItem); + + if (ret != RET_NOERROR) { + player->sendCancelMessage(ret); + return false; + } + + //changing the position since its now in the inventory of the player + internalGetPosition(moveItem, itemPos, itemStackPos); + } + + std::list listDir; + + if (getPathToEx(player, walkToPos, listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask(boost::bind(&Game::playerAutoWalk, + this, player->getID(), listDir))); + + SchedulerTask* task = createSchedulerTask(400, boost::bind(&Game::playerUseItemEx, this, + playerId, itemPos, itemStackPos, fromSpriteId, toPos, toStackPos, toSpriteId, isHotkey)); + player->setNextWalkActionTask(task); + return true; + } else { + player->sendCancelMessage(RET_THEREISNOWAY); + return false; + } + } + + player->sendCancelMessage(ret); + return false; + } + + if (!player->canDoAction()) { + uint32_t delay = player->getNextActionTime(); + SchedulerTask* task = createSchedulerTask(delay, boost::bind(&Game::playerUseItemEx, this, + playerId, fromPos, fromStackPos, fromSpriteId, toPos, toStackPos, toSpriteId, isHotkey)); + player->setNextActionTask(task); + return false; + } + + player->resetIdleTime(); + player->setNextActionTask(NULL); + + return g_actions->useItemEx(player, fromPos, toPos, toStackPos, item, isHotkey); +} + +bool Game::playerUseItem(uint32_t playerId, const Position& pos, uint8_t stackPos, + uint8_t index, uint16_t spriteId, bool isHotkey) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + if (isHotkey && !g_config.getBoolean(ConfigManager::AIMBOT_HOTKEY_ENABLED)) { + return false; + } + + Thing* thing = internalGetThing(player, pos, stackPos, spriteId, STACKPOS_USEITEM); + + if (!thing) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + return false; + } + + Item* item = thing->getItem(); + + if (!item || item->isUseable() || item->getClientID() != spriteId) { + player->sendCancelMessage(RET_CANNOTUSETHISOBJECT); + return false; + } + + ReturnValue ret = g_actions->canUse(player, pos); + + if (ret != RET_NOERROR) { + if (ret == RET_TOOFARAWAY) { + std::list listDir; + + if (getPathToEx(player, pos, listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask(boost::bind(&Game::playerAutoWalk, + this, player->getID(), listDir))); + + SchedulerTask* task = createSchedulerTask(400, boost::bind(&Game::playerUseItem, this, + playerId, pos, stackPos, index, spriteId, isHotkey)); + player->setNextWalkActionTask(task); + return true; + } + + ret = RET_THEREISNOWAY; + } + + player->sendCancelMessage(ret); + return false; + } + + if (!player->canDoAction()) { + uint32_t delay = player->getNextActionTime(); + SchedulerTask* task = createSchedulerTask(delay, boost::bind(&Game::playerUseItem, this, + playerId, pos, stackPos, index, spriteId, isHotkey)); + player->setNextActionTask(task); + return false; + } + + player->resetIdleTime(); + player->setNextActionTask(NULL); + + g_actions->useItem(player, pos, index, item, isHotkey); + return true; +} + +bool Game::playerUseWithCreature(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos, + uint32_t creatureId, uint16_t spriteId, bool isHotkey) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + Creature* creature = getCreatureByID(creatureId); + + if (!creature) { + return false; + } + + if (!Position::areInRange<7, 5, 0>(creature->getPosition(), player->getPosition())) { + return false; + } + + if (!g_config.getBoolean(ConfigManager::AIMBOT_HOTKEY_ENABLED)) { + if (creature->getPlayer() || isHotkey) { + player->sendCancelMessage(RET_DIRECTPLAYERSHOOT); + return false; + } + } + + Thing* thing = internalGetThing(player, fromPos, fromStackPos, spriteId, STACKPOS_USEITEM); + + if (!thing) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + return false; + } + + Item* item = thing->getItem(); + + if (!item || !item->isUseable() || item->getClientID() != spriteId) { + player->sendCancelMessage(RET_CANNOTUSETHISOBJECT); + return false; + } + + Position toPos = creature->getPosition(); + Position walkToPos = fromPos; + ReturnValue ret = g_actions->canUse(player, fromPos); + + if (ret == RET_NOERROR) { + ret = g_actions->canUse(player, toPos, item); + + if (ret == RET_TOOFARAWAY) { + walkToPos = toPos; + } + } + + if (ret != RET_NOERROR) { + if (ret == RET_TOOFARAWAY) { + Position itemPos = fromPos; + uint8_t itemStackPos = fromStackPos; + + if (fromPos.x != 0xFFFF && Position::areInRange<1, 1, 0>(fromPos, player->getPosition()) && !Position::areInRange<1, 1, 0>(fromPos, toPos)) { + Item* moveItem = NULL; + + ReturnValue ret = internalMoveItem(item->getParent(), player, INDEX_WHEREEVER, + item, item->getItemCount(), &moveItem); + + if (ret != RET_NOERROR) { + player->sendCancelMessage(ret); + return false; + } + + //changing the position since its now in the inventory of the player + internalGetPosition(moveItem, itemPos, itemStackPos); + } + + std::list listDir; + + if (getPathToEx(player, walkToPos, listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask(boost::bind(&Game::playerAutoWalk, + this, player->getID(), listDir))); + + SchedulerTask* task = createSchedulerTask(400, boost::bind(&Game::playerUseWithCreature, this, + playerId, itemPos, itemStackPos, creatureId, spriteId, isHotkey)); + player->setNextWalkActionTask(task); + return true; + } else { + player->sendCancelMessage(RET_THEREISNOWAY); + return false; + } + } + + player->sendCancelMessage(ret); + return false; + } + + if (!player->canDoAction()) { + uint32_t delay = player->getNextActionTime(); + SchedulerTask* task = createSchedulerTask(delay, boost::bind(&Game::playerUseWithCreature, this, + playerId, fromPos, fromStackPos, creatureId, spriteId, isHotkey)); + player->setNextActionTask(task); + return false; + } + + player->resetIdleTime(); + player->setNextActionTask(NULL); + + return g_actions->useItemEx(player, fromPos, creature->getPosition(), creature->getParent()->__getIndexOfThing(creature), item, isHotkey, creatureId); +} + +bool Game::playerCloseContainer(uint32_t playerId, uint8_t cid) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + player->closeContainer(cid); + player->sendCloseContainer(cid); + return true; +} + +bool Game::playerMoveUpContainer(uint32_t playerId, uint8_t cid) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + Container* container = player->getContainer(cid); + + if (!container) { + return false; + } + + Container* parentContainer = dynamic_cast(container->getParent()); + + if (!parentContainer) { + Tile* tile = container->getTile(); + + if (!tile) { + return false; + } + + BrowseFieldMap::const_iterator it = browseFields.find(tile); + + if (it == browseFields.end()) { + parentContainer = new Container(tile); + parentContainer->useThing2(); + browseFields[tile] = parentContainer; + g_scheduler.addEvent(createSchedulerTask(30000, boost::bind(&Game::decreaseBrowseFieldRef, this, tile->getPosition()))); + } else { + parentContainer = it->second; + } + } + + player->addContainer(cid, parentContainer); + player->sendContainer(cid, parentContainer, parentContainer->hasParent(player->getProtocolVersion()), player->getContainerIndex(cid)); + return true; +} + +bool Game::playerUpdateTile(uint32_t playerId, const Position& pos) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + if (player->canSee(pos)) { + Tile* tile = getTile(pos.x, pos.y, pos.z); + player->sendUpdateTile(tile, pos); + return true; + } + + return false; +} + +bool Game::playerUpdateContainer(uint32_t playerId, uint8_t cid) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + Container* container = player->getContainer(cid); + + if (!container) { + return false; + } + + player->sendContainer(cid, container, container->hasParent(player->getProtocolVersion()), player->getContainerIndex(cid)); + return true; +} + +bool Game::playerRotateItem(uint32_t playerId, const Position& pos, uint8_t stackPos, const uint16_t spriteId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + Thing* thing = internalGetThing(player, pos, stackPos); + + if (!thing) { + return false; + } + + Item* item = thing->getItem(); + + if (!item || item->getClientID() != spriteId || !item->isRoteable() || item->getUniqueId() != 0) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + return false; + } + + if (pos.x != 0xFFFF && !Position::areInRange<1, 1, 0>(pos, player->getPosition())) { + std::list listDir; + + if (getPathToEx(player, pos, listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask(boost::bind(&Game::playerAutoWalk, + this, player->getID(), listDir))); + + SchedulerTask* task = createSchedulerTask(400, boost::bind(&Game::playerRotateItem, this, + playerId, pos, stackPos, spriteId)); + player->setNextWalkActionTask(task); + return true; + } else { + player->sendCancelMessage(RET_THEREISNOWAY); + return false; + } + } + + uint16_t newId = Item::items[item->getID()].rotateTo; + + if (newId != 0) { + transformItem(item, newId); + } + + return true; +} + +bool Game::playerWriteItem(uint32_t playerId, uint32_t windowTextId, const std::string& text) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + uint16_t maxTextLength = 0; + uint32_t internalWindowTextId = 0; + Item* writeItem = player->getWriteItem(internalWindowTextId, maxTextLength); + + if (text.length() > maxTextLength || windowTextId != internalWindowTextId) { + return false; + } + + if (!writeItem || writeItem->isRemoved()) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + return false; + } + + Cylinder* topParent = writeItem->getTopParent(); + Player* owner = dynamic_cast(topParent); + + if (owner && owner != player) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + return false; + } + + if (!Position::areInRange<1, 1, 0>(writeItem->getPosition(), player->getPosition())) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + return false; + } + + if (!text.empty()) { + if (writeItem->getText() != text) { + writeItem->setText(text); + writeItem->setWriter(player->getName()); + writeItem->setDate(std::time(NULL)); + } + } else { + writeItem->resetText(); + writeItem->resetWriter(); + writeItem->resetDate(); + } + + uint16_t newId = Item::items[writeItem->getID()].writeOnceItemId; + + if (newId != 0) { + transformItem(writeItem, newId); + } + + player->setWriteItem(NULL); + return true; +} + +bool Game::playerBrowseField(uint32_t playerId, const Position& pos) +{ + Player* player = getPlayerByID(playerId); + if (!player || player->isRemoved()) { + return false; + } + + const Position& playerPos = player->getPosition(); + if (playerPos.z > pos.z) { + player->sendCancelMessage(RET_FIRSTGOUPSTAIRS); + return false; + } + + if (playerPos.z < pos.z) { + player->sendCancelMessage(RET_FIRSTGODOWNSTAIRS); + return false; + } + + if (!Position::areInRange<1, 1, 0>(playerPos, pos)) { + std::list listDir; + if (getPathToEx(player, pos, listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask(boost::bind(&Game::playerAutoWalk, + this, player->getID(), listDir))); + SchedulerTask* task = createSchedulerTask(400, boost::bind( + &Game::playerBrowseField, this, playerId, pos + )); + player->setNextWalkActionTask(task); + return true; + } else { + player->sendCancelMessage(RET_THEREISNOWAY); + return false; + } + } + + Tile* tile = getTile(pos); + if (!tile) { + return false; + } + + Container* container; + + BrowseFieldMap::const_iterator it = browseFields.find(tile); + if (it == browseFields.end()) { + container = new Container(tile); + container->useThing2(); + browseFields[tile] = container; + g_scheduler.addEvent(createSchedulerTask(30000, boost::bind(&Game::decreaseBrowseFieldRef, this, tile->getPosition()))); + } else { + container = it->second; + } + + uint8_t dummyContainerId = 0xF - ((pos.x % 3) * 3 + (pos.y % 3)); + Container* openContainer = player->getContainer(dummyContainerId); + + if (openContainer) { + player->onCloseContainer(openContainer); + player->closeContainer(dummyContainerId); + } else { + player->addContainer(dummyContainerId, container); + player->sendContainer(dummyContainerId, container, false, 0); + } + + return true; +} + +bool Game::playerSeekInContainer(uint32_t playerId, uint8_t containerId, uint16_t index) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + Container* container = player->getContainer(containerId); + + if (!container) { + return false; + } + + player->setContainerIndex(containerId, index); + player->sendContainer(containerId, container, false, index); + return true; +} + +bool Game::playerUpdateHouseWindow(uint32_t playerId, uint8_t listId, uint32_t windowTextId, const std::string& text) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + uint32_t internalWindowTextId; + uint32_t internalListId; + House* house = player->getEditHouse(internalWindowTextId, internalListId); + + if (house && internalWindowTextId == windowTextId && listId == 0) { + house->setAccessList(internalListId, text); + player->setEditHouse(NULL); + } + + return true; +} + +bool Game::playerRequestTrade(uint32_t playerId, const Position& pos, uint8_t stackPos, + uint32_t tradePlayerId, uint16_t spriteId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + Player* tradePartner = getPlayerByID(tradePlayerId); + + if (!tradePartner || tradePartner == player) { + player->sendTextMessage(MSG_INFO_DESCR, "Sorry, not possible."); + return false; + } + + if (!Position::areInRange<2, 2, 0>(tradePartner->getPosition(), player->getPosition())) { + std::ostringstream ss; + ss << tradePartner->getName() << " tells you to move closer."; + player->sendTextMessage(MSG_INFO_DESCR, ss.str()); + return false; + } + + if (!canThrowObjectTo(tradePartner->getPosition(), player->getPosition())) { + player->sendCancelMessage(RET_CREATUREISNOTREACHABLE); + return false; + } + + Item* tradeItem = dynamic_cast(internalGetThing(player, pos, stackPos, spriteId, STACKPOS_USE)); + + if (!tradeItem || tradeItem->getClientID() != spriteId || !tradeItem->isPickupable() || tradeItem->getUniqueId() != 0) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + return false; + } + + const Position& playerPosition = player->getPosition(); + + const Position& tradeItemPosition = tradeItem->getPosition(); + + if (playerPosition.z > tradeItemPosition.z) { + player->sendCancelMessage(RET_FIRSTGOUPSTAIRS); + return false; + } else if (playerPosition.z < tradeItemPosition.z) { + player->sendCancelMessage(RET_FIRSTGODOWNSTAIRS); + return false; + } else if (!Position::areInRange<1, 1, 0>(tradeItemPosition, playerPosition)) { + std::list listDir; + + if (getPathToEx(player, pos, listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask(boost::bind(&Game::playerAutoWalk, + this, player->getID(), listDir))); + + SchedulerTask* task = createSchedulerTask(400, boost::bind(&Game::playerRequestTrade, this, + playerId, pos, stackPos, tradePlayerId, spriteId)); + player->setNextWalkActionTask(task); + return true; + } else { + player->sendCancelMessage(RET_THEREISNOWAY); + return false; + } + } + + const Container* container = NULL; + + std::map::const_iterator it; + + for (it = tradeItems.begin(); it != tradeItems.end(); ++it) { + if (tradeItem == it->first || + ((container = dynamic_cast(tradeItem)) && container->isHoldingItem(it->first)) || + ((container = dynamic_cast(it->first)) && container->isHoldingItem(tradeItem))) { + player->sendTextMessage(MSG_INFO_DESCR, "This item is already being traded."); + return false; + } + } + + Container* tradeContainer = tradeItem->getContainer(); + + if (tradeContainer && tradeContainer->getItemHoldingCount() + 1 > 100) { + player->sendTextMessage(MSG_INFO_DESCR, "You can not trade more than 100 items."); + return false; + } + + return internalStartTrade(player, tradePartner, tradeItem); +} + +bool Game::internalStartTrade(Player* player, Player* tradePartner, Item* tradeItem) +{ + if (player->tradeState != TRADE_NONE && !(player->tradeState == TRADE_ACKNOWLEDGE && player->tradePartner == tradePartner)) { + player->sendCancelMessage(RET_YOUAREALREADYTRADING); + return false; + } else if (tradePartner->tradeState != TRADE_NONE && tradePartner->tradePartner != player) { + player->sendCancelMessage(RET_THISPLAYERISALREADYTRADING); + return false; + } + + player->tradePartner = tradePartner; + player->tradeItem = tradeItem; + player->tradeState = TRADE_INITIATED; + tradeItem->useThing2(); + tradeItems[tradeItem] = player->getID(); + + player->sendTradeItemRequest(player, tradeItem, true); + + if (tradePartner->tradeState == TRADE_NONE) { + std::ostringstream ss; + ss << player->getName() << " wants to trade with you."; + tradePartner->sendTextMessage(MSG_EVENT_ADVANCE, ss.str()); + tradePartner->tradeState = TRADE_ACKNOWLEDGE; + tradePartner->tradePartner = player; + } else { + Item* counterOfferItem = tradePartner->tradeItem; + player->sendTradeItemRequest(tradePartner, counterOfferItem, false); + tradePartner->sendTradeItemRequest(player, tradeItem, false); + } + + return true; +} + +bool Game::playerAcceptTrade(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + if (!(player->getTradeState() == TRADE_ACKNOWLEDGE || player->getTradeState() == TRADE_INITIATED)) { + return false; + } + + Player* tradePartner = player->tradePartner; + + if (!tradePartner) { + return false; + } + + if (!canThrowObjectTo(tradePartner->getPosition(), player->getPosition())) { + player->sendCancelMessage(RET_CREATUREISNOTREACHABLE); + return false; + } + + player->setTradeState(TRADE_ACCEPT); + + if (tradePartner->getTradeState() == TRADE_ACCEPT) { + Item* tradeItem1 = player->tradeItem; + Item* tradeItem2 = tradePartner->tradeItem; + + player->setTradeState(TRADE_TRANSFER); + tradePartner->setTradeState(TRADE_TRANSFER); + + std::map::iterator it; + + it = tradeItems.find(tradeItem1); + + if (it != tradeItems.end()) { + FreeThing(it->first); + tradeItems.erase(it); + } + + it = tradeItems.find(tradeItem2); + + if (it != tradeItems.end()) { + FreeThing(it->first); + tradeItems.erase(it); + } + + bool isSuccess = false; + + ReturnValue ret1 = internalAddItem(tradePartner, tradeItem1, INDEX_WHEREEVER, 0, true); + ReturnValue ret2 = internalAddItem(player, tradeItem2, INDEX_WHEREEVER, 0, true); + + if (ret1 == RET_NOERROR && ret2 == RET_NOERROR) { + ret1 = internalRemoveItem(tradeItem1, tradeItem1->getItemCount(), true); + ret2 = internalRemoveItem(tradeItem2, tradeItem2->getItemCount(), true); + + if (ret1 == RET_NOERROR && ret2 == RET_NOERROR) { + Cylinder* cylinder1 = tradeItem1->getParent(); + Cylinder* cylinder2 = tradeItem2->getParent(); + + uint32_t count1 = tradeItem1->getItemCount(); + uint32_t count2 = tradeItem2->getItemCount(); + + ret1 = internalMoveTradeItem(cylinder1, tradePartner, INDEX_WHEREEVER, tradeItem1, tradeItem2, count1, NULL, FLAG_IGNOREAUTOSTACK); + + if (ret1 == RET_NOERROR) { + internalMoveItem(cylinder2, player, INDEX_WHEREEVER, tradeItem2, count2, NULL, FLAG_IGNOREAUTOSTACK); + + tradeItem1->onTradeEvent(ON_TRADE_TRANSFER, tradePartner); + tradeItem2->onTradeEvent(ON_TRADE_TRANSFER, player); + + isSuccess = true; + } + } + } + + if (!isSuccess) { + std::string errorDescription; + + if (tradePartner->tradeItem) { + errorDescription = getTradeErrorDescription(ret1, tradeItem1); + tradePartner->sendTextMessage(MSG_EVENT_ADVANCE, errorDescription); + tradePartner->tradeItem->onTradeEvent(ON_TRADE_CANCEL, tradePartner); + } + + if (player->tradeItem) { + errorDescription = getTradeErrorDescription(ret2, tradeItem2); + player->sendTextMessage(MSG_EVENT_ADVANCE, errorDescription); + player->tradeItem->onTradeEvent(ON_TRADE_CANCEL, player); + } + } + + player->setTradeState(TRADE_NONE); + player->tradeItem = NULL; + player->tradePartner = NULL; + player->sendTradeClose(); + + tradePartner->setTradeState(TRADE_NONE); + tradePartner->tradeItem = NULL; + tradePartner->tradePartner = NULL; + tradePartner->sendTradeClose(); + return isSuccess; + } + + return false; +} + +std::string Game::getTradeErrorDescription(ReturnValue ret, Item* item) +{ + if (item) { + if (ret == RET_NOTENOUGHCAPACITY) { + std::ostringstream ss; + ss << "You do not have enough capacity to carry"; + + if (item->isStackable() && item->getItemCount() > 1) { + ss << " these objects."; + } else { + ss << " this object." ; + } + + ss << std::endl << " " << item->getWeightDescription(); + return ss.str(); + } else if (ret == RET_NOTENOUGHROOM || ret == RET_CONTAINERNOTENOUGHROOM) { + std::ostringstream ss; + ss << "You do not have enough room to carry"; + + if (item->isStackable() && item->getItemCount() > 1) { + ss << " these objects."; + } else { + ss << " this object."; + } + + return ss.str(); + } + } + + return "Trade could not be completed."; +} + +bool Game::playerLookInTrade(uint32_t playerId, bool lookAtCounterOffer, int index) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + Player* tradePartner = player->tradePartner; + + if (!tradePartner) { + return false; + } + + Item* tradeItem = NULL; + + if (lookAtCounterOffer) { + tradeItem = tradePartner->getTradeItem(); + } else { + tradeItem = player->getTradeItem(); + } + + if (!tradeItem) { + return false; + } + + const Position& playerPosition = player->getPosition(); + + const Position& tradeItemPosition = tradeItem->getPosition(); + + int32_t lookDistance = std::max(std::abs(playerPosition.x - tradeItemPosition.x), + std::abs(playerPosition.y - tradeItemPosition.y)); + + std::ostringstream ss; + + if (index == 0) { + ss << "You see " << tradeItem->getDescription(lookDistance); + player->sendTextMessage(MSG_INFO_DESCR, ss.str()); + return false; + } + + Container* tradeContainer = tradeItem->getContainer(); + + if (!tradeContainer || index > (int32_t)tradeContainer->getItemHoldingCount()) { + return false; + } + + bool foundItem = false; + std::list listContainer; + ItemDeque::const_iterator it; + Container* tmpContainer = NULL; + + listContainer.push_back(tradeContainer); + + while (!foundItem && !listContainer.empty()) { + const Container* container = listContainer.front(); + listContainer.pop_front(); + + for (it = container->getItems(); it != container->getEnd(); ++it) { + if ((tmpContainer = (*it)->getContainer())) { + listContainer.push_back(tmpContainer); + } + + --index; + + if (index == 0) { + tradeItem = *it; + foundItem = true; + break; + } + } + } + + if (foundItem) { + ss << "You see " << tradeItem->getDescription(lookDistance); + player->sendTextMessage(MSG_INFO_DESCR, ss.str()); + } + + return foundItem; +} + +bool Game::playerCloseTrade(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + return internalCloseTrade(player); +} + +bool Game::internalCloseTrade(Player* player) +{ + Player* tradePartner = player->tradePartner; + + if ((tradePartner && tradePartner->getTradeState() == TRADE_TRANSFER) || player->getTradeState() == TRADE_TRANSFER) { + std::cout << "Warning: [Game::playerCloseTrade] TradeState == TRADE_TRANSFER. " << + player->getName() << " " << player->getTradeState() << " , " << + tradePartner->getName() << " " << tradePartner->getTradeState() << std::endl; + return true; + } + + if (player->getTradeItem()) { + std::map::iterator it = tradeItems.find(player->getTradeItem()); + + if (it != tradeItems.end()) { + FreeThing(it->first); + tradeItems.erase(it); + } + + player->tradeItem->onTradeEvent(ON_TRADE_CANCEL, player); + player->tradeItem = NULL; + } + + player->setTradeState(TRADE_NONE); + player->tradePartner = NULL; + + player->sendTextMessage(MSG_STATUS_SMALL, "Trade cancelled."); + player->sendTradeClose(); + + if (tradePartner) { + if (tradePartner->getTradeItem()) { + std::map::iterator it = tradeItems.find(tradePartner->getTradeItem()); + + if (it != tradeItems.end()) { + FreeThing(it->first); + tradeItems.erase(it); + } + + tradePartner->tradeItem->onTradeEvent(ON_TRADE_CANCEL, tradePartner); + tradePartner->tradeItem = NULL; + } + + tradePartner->setTradeState(TRADE_NONE); + tradePartner->tradePartner = NULL; + + tradePartner->sendTextMessage(MSG_STATUS_SMALL, "Trade cancelled."); + tradePartner->sendTradeClose(); + } + + return true; +} + +bool Game::playerPurchaseItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount, + bool ignoreCap/* = false*/, bool inBackpacks/* = false*/) +{ + if (amount == 0 || amount > 100) { + return false; + } + + Player* player = getPlayerByID(playerId); + + if (player == NULL || player->isRemoved()) { + return false; + } + + int32_t onBuy; + int32_t onSell; + + Npc* merchant = player->getShopOwner(onBuy, onSell); + + if (merchant == NULL) { + return false; + } + + /* + uint16_t itemId = 0; + uint8_t subType; + + const std::list itemTypes = Item::items.getItemIdsByClientId(spriteId); + for(std::list::const_iterator iter = itemTypes.begin(), end = itemTypes.end(); iter != end; ++iter) + { + ItemType* it = *iter; + if(it->id == 0) + continue; + + if(it->isSplash() || it->isFluidContainer()) + subType = clientFluidToServer(count); + else + subType = count; + + if(player->hasShopItemForSale(it->id, subType)) + { + itemId = it->id; + break; + } + } + + if(itemId == 0) + return false; + */ + + const ItemType& it = Item::items.getItemIdByClientId(spriteId); + + if (it.id == 0) { + return false; + } + + uint8_t subType; + + if (it.isSplash() || it.isFluidContainer()) { + subType = clientFluidToServer(count); + } else { + subType = count; + } + + if (!player->hasShopItemForSale(it.id, subType)) { + return false; + } + + merchant->onPlayerTrade(player, SHOPEVENT_BUY, onBuy, it.id, subType, amount, ignoreCap, inBackpacks); + return true; +} + +bool Game::playerSellItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount, bool ignoreEquipped) +{ + if (amount == 0 || amount > 100) { + return false; + } + + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + int32_t onBuy, onSell; + + Npc* merchant = player->getShopOwner(onBuy, onSell); + + if (!merchant) { + return false; + } + + const ItemType& it = Item::items.getItemIdByClientId(spriteId); + + if (it.id == 0) { + return false; + } + + uint8_t subType; + + if (it.isSplash() || it.isFluidContainer()) { + subType = clientFluidToServer(count); + } else { + subType = count; + } + + merchant->onPlayerTrade(player, SHOPEVENT_SELL, onSell, it.id, subType, amount, ignoreEquipped); + return true; +} + +bool Game::playerCloseShop(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + + if (player == NULL || player->isRemoved()) { + return false; + } + + player->closeShopWindow(); + return true; +} + +bool Game::playerLookInShop(uint32_t playerId, uint16_t spriteId, uint8_t count) +{ + Player* player = getPlayerByID(playerId); + + if (player == NULL || player->isRemoved()) { + return false; + } + + const ItemType& it = Item::items.getItemIdByClientId(spriteId); + + if (it.id == 0) { + return false; + } + + int32_t subType; + + if (it.isFluidContainer() || it.isSplash()) { + subType = clientFluidToServer(count); + } else { + subType = count; + } + + std::ostringstream ss; + ss << "You see " << Item::getDescription(it, 1, NULL, subType); + player->sendTextMessage(MSG_INFO_DESCR, ss.str()); + return true; +} + +bool Game::playerLookAt(uint32_t playerId, const Position& pos, uint16_t spriteId, uint8_t stackPos) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + Thing* thing = internalGetThing(player, pos, stackPos, spriteId, STACKPOS_LOOK); + + if (!thing) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + return false; + } + + Position thingPos = thing->getPosition(); + + if (!player->canSee(thingPos)) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + return false; + } + + Position playerPos = player->getPosition(); + + int32_t lookDistance = -1; + + if (thing != player) { + lookDistance = std::max(std::abs(playerPos.x - thingPos.x), std::abs(playerPos.y - thingPos.y)); + + if (playerPos.z != thingPos.z) { + lookDistance += 15; + } + } + + std::ostringstream ss; + ss << "You see " << thing->getDescription(lookDistance); + + if (player->isAccessPlayer()) { + Item* item = thing->getItem(); + + if (item) { + ss << std::endl << "ItemID: [" << item->getID() << "]"; + + if (item->getActionId() > 0) { + ss << ", ActionID: [" << item->getActionId() << "]"; + } + + if (item->getUniqueId() > 0) { + ss << ", UniqueID: [" << item->getUniqueId() << "]"; + } + + ss << "."; + const ItemType& it = Item::items[item->getID()]; + + if (it.transformEquipTo) { + ss << std::endl << "TransformTo: [" << it.transformEquipTo << "] (onEquip)."; + } else if (it.transformDeEquipTo) { + ss << std::endl << "TransformTo: [" << it.transformDeEquipTo << "] (onDeEquip)."; + } + + if (it.decayTo != -1) { + ss << std::endl << "DecayTo: [" << it.decayTo << "]."; + } + } + + if (const Creature* creature = thing->getCreature()) { + ss << std::endl << "Health: [" << creature->getHealth() << " / " << creature->getMaxHealth() << "]"; + + if (creature->getMaxMana() > 0) { + ss << ", Mana: [" << creature->getMana() << " / " << creature->getMaxMana() << "]"; + } + + ss << "."; + } + + ss << std::endl << "Position: [X: " << thingPos.x << "] [Y: " << thingPos.y << "] [Z: " << thingPos.z << "]."; + } + + player->sendTextMessage(MSG_INFO_DESCR, ss.str()); + return true; +} + +bool Game::playerLookInBattleList(uint32_t playerId, uint32_t creatureId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + Creature* creature = getCreatureByID(creatureId); + + if (!creature || creature->isRemoved()) { + return false; + } + + if (!player->canSeeCreature(creature)) { + return false; + } + + const Position& creaturePos = creature->getPosition(); + + if (!player->canSee(creaturePos)) { + return false; + } + + int32_t lookDistance; + + if (creature != player) { + const Position& playerPos = player->getPosition(); + lookDistance = std::max(std::abs(playerPos.x - creaturePos.x), std::abs(playerPos.y - creaturePos.y)); + + if (playerPos.z != creaturePos.z) { + lookDistance += 15; + } + } else { + lookDistance = -1; + } + + std::ostringstream ss; + ss << "You see " << creature->getDescription(lookDistance); + + if (player->isAccessPlayer()) { + ss << std::endl << "Health: [" << creature->getHealth() << " / " << creature->getMaxHealth() << "]"; + + if (creature->getMaxMana() > 0) { + ss << ", Mana: [" << creature->getMana() << " / " << creature->getMaxMana() << "]"; + } + + ss << "." << std::endl; + ss << "Position: [X: " << creaturePos.x << "] [Y: " << creaturePos.y << "] [Z: " << creaturePos.z << "]."; + } + + player->sendTextMessage(MSG_INFO_DESCR, ss.str()); + return true; +} + +bool Game::playerCancelAttackAndFollow(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + playerSetAttackedCreature(playerId, 0); + playerFollowCreature(playerId, 0); + player->stopWalk(); + return true; +} + +bool Game::playerSetAttackedCreature(uint32_t playerId, uint32_t creatureId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + if (player->getAttackedCreature() && creatureId == 0) { + player->setAttackedCreature(NULL); + player->sendCancelTarget(); + return true; + } + + Creature* attackCreature = getCreatureByID(creatureId); + + if (!attackCreature) { + player->setAttackedCreature(NULL); + player->sendCancelTarget(); + return false; + } + + ReturnValue ret = Combat::canTargetCreature(player, attackCreature); + + if (ret != RET_NOERROR) { + player->sendCancelMessage(ret); + player->sendCancelTarget(); + player->setAttackedCreature(NULL); + return false; + } + + player->setAttackedCreature(attackCreature); + g_dispatcher.addTask(createTask(boost::bind(&Game::updateCreatureWalk, this, player->getID()))); + return true; +} + +bool Game::playerFollowCreature(uint32_t playerId, uint32_t creatureId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + player->setAttackedCreature(NULL); + Creature* followCreature = NULL; + + if (creatureId != 0) { + followCreature = getCreatureByID(creatureId); + } + + g_dispatcher.addTask(createTask(boost::bind(&Game::updateCreatureWalk, this, player->getID()))); + return player->setFollowCreature(followCreature); +} + +bool Game::playerSetFightModes(uint32_t playerId, fightMode_t fightMode, chaseMode_t chaseMode, secureMode_t secureMode) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + player->setFightMode(fightMode); + player->setChaseMode(chaseMode); + player->setSecureMode(secureMode); + return true; +} + +bool Game::playerRequestAddVip(uint32_t playerId, const std::string& vip_name) +{ + if (vip_name.size() > 32) { + return false; + } + + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + std::string real_name; + real_name = vip_name; + uint32_t guid; + bool specialVip; + + if (!IOLoginData::getInstance()->getGuidByNameEx(guid, specialVip, real_name)) { + player->sendTextMessage(MSG_STATUS_SMALL, "A player with that name does not exist."); + return false; + } + + if (specialVip && !player->hasFlag(PlayerFlag_SpecialVIP)) { + player->sendTextMessage(MSG_STATUS_SMALL, "You can not add this player."); + return false; + } + + VipStatus_t status; + Player* vipPlayer = getPlayerByName(real_name); + + if (!vipPlayer) { + status = VIPSTATUS_OFFLINE; + } else if (player->isAccessPlayer() || !vipPlayer->isInGhostMode()) { + /* + if(vipPlayer->isPending) + status = VIPSTATUS_PENDING; + else + */ + status = VIPSTATUS_ONLINE; + } else { + status = VIPSTATUS_OFFLINE; + } + + return player->addVIP(guid, real_name, status); +} + +bool Game::playerRequestRemoveVip(uint32_t playerId, uint32_t guid) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + player->removeVIP(guid); + return true; +} + +bool Game::playerRequestEditVip(uint32_t playerId, uint32_t guid, const std::string& description, uint32_t icon, bool notify) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + player->editVIP(guid, description, icon, notify); + return true; +} + +bool Game::playerTurn(uint32_t playerId, Direction dir) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + player->resetIdleTime(); + return internalCreatureTurn(player, dir); +} + +bool Game::playerRequestOutfit(uint32_t playerId) +{ + if (!g_config.getBoolean(ConfigManager::ALLOW_CHANGEOUTFIT)) { + return false; + } + + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + player->sendOutfitWindow(); + return true; +} + +bool Game::playerToggleMount(uint32_t playerId, bool mount) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + return player->toggleMount(mount); +} + +bool Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit) +{ + if (!g_config.getBoolean(ConfigManager::ALLOW_CHANGEOUTFIT)) { + return false; + } + + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + if (outfit.lookMount != 0) { + Mount* mount = Mounts::getInstance()->getMountByClientID(outfit.lookMount); + + if (!mount || !mount->isTamed(player)) { + return false; + } + + if (player->isMounted()) { + Mount* prevMount = Mounts::getInstance()->getMountByID(player->getCurrentMount()); + + if (prevMount) { + changeSpeed(player, mount->getSpeed() - prevMount->getSpeed()); + } + + player->setCurrentMount(mount->getID()); + } else { + player->setCurrentMount(mount->getID()); + outfit.lookMount = 0; + } + } else if (player->isMounted()) { + player->dismount(); + } + + if (player->canWear(outfit.lookType, outfit.lookAddons) && player->hasRequestedOutfit()) { + player->hasRequestedOutfit(false); + player->defaultOutfit = outfit; + + if (player->hasCondition(CONDITION_OUTFIT)) { + return false; + } + + internalCreatureChangeOutfit(player, outfit); + } + + return true; +} + +bool Game::playerShowQuestLog(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + player->sendQuestLog(); + return true; +} + +bool Game::playerShowQuestLine(uint32_t playerId, uint16_t questId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + Quest* quest = Quests::getInstance()->getQuestByID(questId); + + if (!quest) { + return false; + } + + player->sendQuestLine(quest); + return true; +} + +bool Game::playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type, + const std::string& receiver, const std::string& text) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + player->resetIdleTime(); + + uint32_t muteTime = player->isMuted(); + + if (muteTime > 0) { + std::ostringstream ss; + ss << "You are still muted for " << muteTime << " seconds."; + player->sendTextMessage(MSG_STATUS_SMALL, ss.str()); + return false; + } + + if (playerSayCommand(player, type, text)) { + return true; + } + + if (text.length() >= 1 && text[0] == '/' && player->isAccessPlayer()) { + return true; + } + + if (playerSaySpell(player, type, text)) { + return true; + } + + if (type != SPEAK_PRIVATE_PN) { + player->removeMessageBuffer(); + } + + if (channelId == 0xDEAD && player->getCastingProtocol()) { + player->sendChannelMessage(player->getName(), text, SPEAK_CHANNEL_O, 0xDEAD); + return true; + } + + switch (type) { + case SPEAK_SAY: + return internalCreatureSay(player, SPEAK_SAY, text, false); + + case SPEAK_WHISPER: + return playerWhisper(player, text); + + case SPEAK_YELL: + return playerYell(player, text); + + case SPEAK_PRIVATE_TO: + case SPEAK_PRIVATE_RED_TO: + return playerSpeakTo(player, type, receiver, text); + + case SPEAK_CHANNEL_O: + case SPEAK_CHANNEL_Y: + case SPEAK_CHANNEL_R1: + return playerTalkToChannel(player, type, text, channelId); + + case SPEAK_PRIVATE_PN: + return playerSpeakToNpc(player, text); + + case SPEAK_BROADCAST: + return playerBroadcastMessage(player, text); + + default: + break; + } + + return false; +} + +bool Game::playerSayCommand(Player* player, SpeakClasses type, const std::string& text) +{ + //First, check if this was a command + for (uint32_t i = 0; i < commandTags.size(); i++) { + if (commandTags[i] == text.substr(0, 1)) { + if (commands.exeCommand(player, text)) { + return true; + } + } + } + + return false; +} + +bool Game::playerSaySpell(Player* player, SpeakClasses type, const std::string& text) +{ + std::string words = text; + + TalkActionResult_t result = g_talkActions->playerSaySpell(player, type, words); + + if (result == TALKACTION_BREAK) { + return true; + } + + result = g_spells->playerSaySpell(player, type, words); + + if (result == TALKACTION_BREAK) { + return internalCreatureSay(player, SPEAK_SAY, words, false); + } else if (result == TALKACTION_FAILED) { + return true; + } + + return false; +} + +bool Game::playerWhisper(Player* player, const std::string& text) +{ + SpectatorVec list; + getSpectators(list, player->getPosition(), false, false, + Map::maxClientViewportX, Map::maxClientViewportX, + Map::maxClientViewportY, Map::maxClientViewportY); + + SpectatorVec::const_iterator it; + SpectatorVec::const_iterator end = list.end(); + + //send to client + for (it = list.begin(); it != end; ++it) { + if (Player* tmpPlayer = (*it)->getPlayer()) { + if (!Position::areInRange<1, 1, 0>(player->getPosition(), (*it)->getPosition())) { + tmpPlayer->sendCreatureSay(player, SPEAK_WHISPER, "pspsps"); + } else { + tmpPlayer->sendCreatureSay(player, SPEAK_WHISPER, text); + } + } + } + + //event method + for (it = list.begin(); it != end; ++it) { + (*it)->onCreatureSay(player, SPEAK_WHISPER, text); + } + + return true; +} + +bool Game::playerYell(Player* player, const std::string& text) +{ + if (player->getLevel() == 1) { + player->sendTextMessage(MSG_STATUS_SMALL, "You may not yell as long as you are on level 1."); + return false; + } + + if (player->hasCondition(CONDITION_YELLTICKS)) { + player->sendCancelMessage(RET_YOUAREEXHAUSTED); + return false; + } + + if (player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_YELLTICKS, 30000, 0); + player->addCondition(condition); + } + + internalCreatureSay(player, SPEAK_YELL, asUpperCaseString(text), false); + return true; +} + +bool Game::playerSpeakTo(Player* player, SpeakClasses type, const std::string& receiver, + const std::string& text) +{ + Player* toPlayer = getPlayerByName(receiver); + + if (!toPlayer || toPlayer->isRemoved()) { + player->sendTextMessage(MSG_STATUS_SMALL, "A player with this name is not online."); + return false; + } + + if (type == SPEAK_PRIVATE_RED_TO && (player->hasFlag(PlayerFlag_CanTalkRedPrivate) || player->getAccountType() >= ACCOUNT_TYPE_GAMEMASTER)) { + type = SPEAK_PRIVATE_RED_FROM; + } else { + type = SPEAK_PRIVATE_FROM; + } + + toPlayer->sendCreatureSay(player, type, text); + toPlayer->onCreatureSay(player, type, text); + + if (toPlayer->isInGhostMode() && !player->isAccessPlayer()) { + player->sendTextMessage(MSG_STATUS_SMALL, "A player with this name is not online."); + } else { + std::ostringstream ss; + ss << "Message sent to " << toPlayer->getName() << "."; + player->sendTextMessage(MSG_STATUS_SMALL, ss.str()); + } + + return true; +} + +bool Game::playerTalkToChannel(Player* player, SpeakClasses type, const std::string& text, uint16_t channelId) +{ + switch (type) { + case SPEAK_CHANNEL_Y: { + if (player->isAccessPlayer() || (channelId == CHANNEL_HELP && (player->hasFlag(PlayerFlag_TalkOrangeHelpChannel) || player->getAccountType() > ACCOUNT_TYPE_NORMAL))) { + type = SPEAK_CHANNEL_O; + } + + break; + } + + case SPEAK_CHANNEL_O: { + if (channelId != CHANNEL_HELP || (!player->hasFlag(PlayerFlag_TalkOrangeHelpChannel) && player->getAccountType() == ACCOUNT_TYPE_NORMAL)) { + type = SPEAK_CHANNEL_Y; + } + + break; + } + + case SPEAK_CHANNEL_R1: { + if (!player->hasFlag(PlayerFlag_CanTalkRedChannel) && player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER) { + if (channelId == CHANNEL_HELP && (player->hasFlag(PlayerFlag_TalkOrangeHelpChannel) || player->getAccountType() >= ACCOUNT_TYPE_TUTOR)) { + type = SPEAK_CHANNEL_O; + } else { + type = SPEAK_CHANNEL_Y; + } + } + + break; + } + + default: + break; + } + + return g_chat.talkToChannel(player, type, text, channelId); +} + +bool Game::playerSpeakToNpc(Player* player, const std::string& text) +{ + SpectatorVec list; + getSpectators(list, player->getPosition()); + + //send to npcs only + for (SpectatorVec::iterator it = list.begin(), end = list.end(); it != end; ++it) { + if ((*it)->getNpc()) { + (*it)->onCreatureSay(player, SPEAK_PRIVATE_PN, text); + } + } + + return true; +} + +bool Game::npcSpeakToPlayer(Npc* npc, Player* player, const std::string& text, bool publicize) +{ + if (player != NULL) { + player->sendCreatureSay(npc, SPEAK_PRIVATE_NP, text); + player->onCreatureSay(npc, SPEAK_PRIVATE_NP, text); + } + + if (publicize) { + SpectatorVec list; + getSpectators(list, npc->getPosition()); + + SpectatorVec::const_iterator end = list.end(); + + //send to client + for (SpectatorVec::const_iterator it = list.begin(); it != end; ++it) { + if (*it != player) { + if (Player* tmpPlayer = (*it)->getPlayer()) { + tmpPlayer->sendCreatureSay(npc, SPEAK_SAY, text); + } + } + } + + //event method + for (SpectatorVec::const_iterator it = list.begin(); it != end; ++it) { + if (*it != player) { + (*it)->onCreatureSay(npc, SPEAK_SAY, text); + } + } + } + + return true; +} + +//-- +bool Game::canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight /*= true*/, + int32_t rangex /*= Map::maxClientViewportX*/, int32_t rangey /*= Map::maxClientViewportY*/) +{ + return map->canThrowObjectTo(fromPos, toPos, checkLineOfSight, rangex, rangey); +} + +bool Game::isSightClear(const Position& fromPos, const Position& toPos, bool floorCheck) +{ + return map->isSightClear(fromPos, toPos, floorCheck); +} + +bool Game::internalCreatureTurn(Creature* creature, Direction dir) +{ + if (creature->getDirection() == dir) { + return false; + } + + creature->setDirection(dir); + + SpectatorVec list; + getSpectators(list, creature->getPosition(), true, true); + + //send to client + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + (*it)->getPlayer()->sendCreatureTurn(creature); + } + + return true; +} + +bool Game::internalCreatureSay(Creature* creature, SpeakClasses type, const std::string& text, + bool ghostMode, SpectatorVec* listPtr/* = NULL*/, Position* pos/* = NULL*/) +{ + if (text.empty()) { + return false; + } + + Position destPos = creature->getPosition(); + + if (pos) { + destPos = (*pos); + } + + SpectatorVec list; + + if (!listPtr || listPtr->empty()) { + // This somewhat complex construct ensures that the cached SpectatorVec + // is used if available and if it can be used, else a local vector is + // used (hopefully the compiler will optimize away the construction of + // the temporary when it's not used). + if (type != SPEAK_YELL && type != SPEAK_MONSTER_YELL) { + getSpectators(list, destPos, false, false, + Map::maxClientViewportX, Map::maxClientViewportX, + Map::maxClientViewportY, Map::maxClientViewportY); + } else { + getSpectators(list, destPos, true, false, 18, 18, 14, 14); + } + } else { + list = (*listPtr); + } + + SpectatorVec::const_iterator end = list.end(); + + //send to client + for (SpectatorVec::const_iterator it = list.begin(); it != end; ++it) { + if (Player* tmpPlayer = (*it)->getPlayer()) { + if (!ghostMode || tmpPlayer->canSeeCreature(creature)) { + tmpPlayer->sendCreatureSay(creature, type, text, &destPos); + } + } + } + + //event method + for (SpectatorVec::const_iterator it = list.begin(); it != end; ++it) { + (*it)->onCreatureSay(creature, type, text, &destPos); + } + + return true; +} + +bool Game::getPathTo(const Creature* creature, const Position& destPos, + std::list& listDir, int32_t maxSearchDist /*= -1*/) +{ + return map->getPathTo(creature, destPos, listDir, maxSearchDist); +} + +bool Game::getPathToEx(const Creature* creature, const Position& targetPos, + std::list& dirList, const FindPathParams& fpp) +{ + return map->getPathMatching(creature, dirList, FrozenPathingConditionCall(targetPos), fpp); +} + +bool Game::getPathToEx(const Creature* creature, const Position& targetPos, std::list& dirList, + uint32_t minTargetDist, uint32_t maxTargetDist, bool fullPathSearch /*= true*/, + bool clearSight /*= true*/, int32_t maxSearchDist /*= -1*/) +{ + FindPathParams fpp; + fpp.fullPathSearch = fullPathSearch; + fpp.maxSearchDist = maxSearchDist; + fpp.clearSight = clearSight; + fpp.minTargetDist = minTargetDist; + fpp.maxTargetDist = maxTargetDist; + return getPathToEx(creature, targetPos, dirList, fpp); +} + +void Game::checkCreatureWalk(uint32_t creatureId) +{ + Creature* creature = getCreatureByID(creatureId); + + if (creature && creature->getHealth() > 0) { + creature->onWalk(); + cleanup(); + } +} + +void Game::updateCreatureWalk(uint32_t creatureId) +{ + Creature* creature = getCreatureByID(creatureId); + + if (creature && creature->getHealth() > 0) { + creature->goToFollowCreature(); + } +} + +void Game::checkCreatureAttack(uint32_t creatureId) +{ + Creature* creature = getCreatureByID(creatureId); + + if (creature && creature->getHealth() > 0) { + creature->onAttacking(0); + } +} + +void Game::addCreatureCheck(Creature* creature) +{ + if (creature->isRemoved()) { + return; + } + + creature->creatureCheck = true; + + if (creature->checkCreatureVectorIndex >= 0) { + return; //Already in a vector + } + + toAddCheckCreatureVector.push_back(creature); + creature->checkCreatureVectorIndex = random_range(0, EVENT_CREATURECOUNT - 1); + creature->useThing2(); +} + +void Game::removeCreatureCheck(Creature* creature) +{ + if (creature->checkCreatureVectorIndex == -1) { + return; + } + + creature->creatureCheck = false; +} + +void Game::checkCreatures() +{ + checkCreatureEvent = g_scheduler.addEvent(createSchedulerTask(EVENT_CHECK_CREATURE_INTERVAL, boost::bind(&Game::checkCreatures, this))); + + Creature* creature; + std::vector::iterator it; + + //add any new creatures + for (it = toAddCheckCreatureVector.begin(); it != toAddCheckCreatureVector.end(); ++it) { + creature = (*it); + checkCreatureVectors[creature->checkCreatureVectorIndex].push_back(creature); + } + + toAddCheckCreatureVector.clear(); + + checkCreatureLastIndex++; + + if (checkCreatureLastIndex == EVENT_CREATURECOUNT) { + checkCreatureLastIndex = 0; + } + + CreatureVector& checkCreatureVector = checkCreatureVectors[checkCreatureLastIndex]; + + for (it = checkCreatureVector.begin(); it != checkCreatureVector.end();) { + creature = (*it); + + if (creature->creatureCheck) { + if (creature->getHealth() > 0) { + creature->onThink(EVENT_CREATURE_THINK_INTERVAL); + creature->onAttacking(EVENT_CREATURE_THINK_INTERVAL); + creature->executeConditions(EVENT_CREATURE_THINK_INTERVAL); + } else { + creature->onDeath(); + } + + ++it; + } else { + creature->checkCreatureVectorIndex = -1; + it = checkCreatureVector.erase(it); + FreeThing(creature); + } + } + + cleanup(); +} + +void Game::changeSpeed(Creature* creature, int32_t varSpeedDelta) +{ + int32_t varSpeed = creature->getSpeed() - creature->getBaseSpeed(); + varSpeed += varSpeedDelta; + + creature->setSpeed(varSpeed); + + //send to clients + SpectatorVec list; + getSpectators(list, creature->getPosition(), false, true); + + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + (*it)->getPlayer()->sendChangeSpeed(creature, creature->getStepSpeed()); + } +} + +void Game::internalCreatureChangeOutfit(Creature* creature, const Outfit_t& outfit) +{ + creature->setCurrentOutfit(outfit); + + if (creature->isInvisible()) { + return; + } + + //send to clients + SpectatorVec list; + getSpectators(list, creature->getPosition(), true, true); + + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + (*it)->getPlayer()->sendCreatureChangeOutfit(creature, outfit); + } +} + +void Game::internalCreatureChangeVisible(Creature* creature, bool visible) +{ + //send to clients + SpectatorVec list; + getSpectators(list, creature->getPosition(), true, true); + + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + (*it)->getPlayer()->sendCreatureChangeVisible(creature, visible); + } +} + +void Game::changeLight(const Creature* creature) +{ + //send to clients + SpectatorVec list; + getSpectators(list, creature->getPosition(), true, true); + + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + (*it)->getPlayer()->sendCreatureLight(creature); + } +} + +bool Game::combatBlockHit(CombatType_t combatType, Creature* attacker, Creature* target, + int32_t& healthChange, bool checkDefense, bool checkArmor) +{ + if (target->getPlayer() && target->getPlayer()->isInGhostMode()) { + return true; + } + + if (healthChange > 0) { + return false; + } + + const Position& targetPos = target->getPosition(); + + SpectatorVec list; + + getSpectators(list, targetPos, false, true); + + if (!target->isAttackable() || Combat::canDoCombat(attacker, target) != RET_NOERROR) { + addMagicEffect(list, targetPos, NM_ME_POFF, target->isInGhostMode()); + return true; + } + + int32_t damage = -healthChange; + BlockType_t blockType = target->blockHit(attacker, combatType, damage, checkDefense, checkArmor); + healthChange = -damage; + + if (blockType == BLOCK_DEFENSE) { + addMagicEffect(list, targetPos, NM_ME_POFF); + return true; + } else if (blockType == BLOCK_ARMOR) { + addMagicEffect(list, targetPos, NM_ME_BLOCKHIT); + return true; + } else if (blockType == BLOCK_IMMUNITY) { + uint8_t hitEffect = 0; + + switch (combatType) { + case COMBAT_UNDEFINEDDAMAGE: + break; + + case COMBAT_ENERGYDAMAGE: + case COMBAT_FIREDAMAGE: + case COMBAT_PHYSICALDAMAGE: + case COMBAT_ICEDAMAGE: + case COMBAT_DEATHDAMAGE: { + hitEffect = NM_ME_BLOCKHIT; + break; + } + + case COMBAT_EARTHDAMAGE: { + hitEffect = NM_ME_POISON_RINGS; + break; + } + + case COMBAT_HOLYDAMAGE: { + hitEffect = NM_ME_HOLYDAMAGE; + break; + } + + default: { + hitEffect = NM_ME_POFF; + break; + } + } + + addMagicEffect(list, targetPos, hitEffect); + return true; + } + + return false; +} + +bool Game::combatChangeHealth(CombatType_t combatType, Creature* attacker, Creature* target, int32_t healthChange) +{ + const Position& targetPos = target->getPosition(); + + if (healthChange > 0) { + if (target->getHealth() <= 0) { + return false; + } + + Player* attackerPlayer; + + if (attacker) { + attackerPlayer = attacker->getPlayer(); + } else { + attackerPlayer = NULL; + } + + Player* targetPlayer = target->getPlayer(); + + if (attackerPlayer && targetPlayer) { + if (g_config.getBoolean(ConfigManager::CANNOT_ATTACK_SAME_LOOKFEET) && attackerPlayer->defaultOutfit.lookFeet == target->defaultOutfit.lookFeet && combatType != COMBAT_HEALING) { + return false; + } + + if (attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) { + return false; + } + } + + int32_t realHealthChange = target->getHealth(); + target->changeHealth(healthChange); + realHealthChange = target->getHealth() - realHealthChange; + + if (realHealthChange > 0 && !target->isInGhostMode()) { + std::ostringstream ss; + + if (!attacker) { + ss << ucfirst(target->getNameDescription()) << " was healed for " << realHealthChange << " hitpoint" << (realHealthChange != 1 ? "s." : "."); + } else if (attacker == target) { + ss << ucfirst(attacker->getNameDescription()) << " healed " << (targetPlayer ? (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "herself for " : "himself for ") : "itself for ") << realHealthChange << " hitpoint" << (realHealthChange != 1 ? "s." : "."); + } else { + ss << ucfirst(attacker->getNameDescription()) << " healed " << target->getNameDescription() << " for " << realHealthChange << " hitpoint" << (realHealthChange != 1 ? "s." : "."); + } + + std::string message = ss.str(); + + SpectatorVec list; + getSpectators(list, targetPos, false, true); + + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + Player* tmpPlayer = (*it)->getPlayer(); + + if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) { + std::ostringstream tmpSs; + tmpSs << "You heal " << target->getNameDescription() << " for " << realHealthChange << " hitpoint" << (realHealthChange != 1 ? "s." : "."); + attackerPlayer->sendHealMessage(MSG_HEALED, tmpSs.str(), targetPos, realHealthChange, TEXTCOLOR_MAYABLUE); + } else if (tmpPlayer == targetPlayer) { + std::ostringstream tmpSs; + + if (!attacker) { + tmpSs << "You were healed for " << realHealthChange << " hitpoint" << (realHealthChange != 1 ? "s." : "."); + } else if (targetPlayer == attackerPlayer) { + tmpSs << "You heal yourself for " << realHealthChange << " hitpoint" << (realHealthChange != 1 ? "s." : "."); + } else { + tmpSs << "You were healed by " << attacker->getNameDescription() << " for " << realHealthChange << " hitpoint" << (realHealthChange != 1 ? "s." : "."); + } + + targetPlayer->sendHealMessage(MSG_HEALED, tmpSs.str(), targetPos, realHealthChange, TEXTCOLOR_MAYABLUE); + } else { + tmpPlayer->sendHealMessage(MSG_HEALED_OTHERS, message, targetPos, realHealthChange, TEXTCOLOR_MAYABLUE); + } + } + } + } else { + SpectatorVec list; + getSpectators(list, targetPos, true, true); + + if (!target->isAttackable() || Combat::canDoCombat(attacker, target) != RET_NOERROR) { + addMagicEffect(list, targetPos, NM_ME_POFF); + return true; + } + + Player* attackerPlayer; + + if (attacker) { + attackerPlayer = attacker->getPlayer(); + } else { + attackerPlayer = NULL; + } + + Player* targetPlayer = target->getPlayer(); + + if (attackerPlayer && targetPlayer) { + if (g_config.getBoolean(ConfigManager::CANNOT_ATTACK_SAME_LOOKFEET) && attacker->defaultOutfit.lookFeet == target->defaultOutfit.lookFeet && combatType != COMBAT_HEALING) { + return false; + } + + if (attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) { + return false; + } + } + + int32_t damage = -healthChange; + + if (damage == 0) { + return true; + } + + if (target->hasCondition(CONDITION_MANASHIELD) && combatType != COMBAT_UNDEFINEDDAMAGE) { + int32_t manaDamage = std::min(target->getMana(), damage); + + if (manaDamage != 0) { + target->drainMana(attacker, manaDamage); + addMagicEffect(list, targetPos, NM_ME_LOSE_ENERGY); + + std::ostringstream ss; + + if (!attacker) { + ss << ucfirst(target->getNameDescription()) << " loses " << manaDamage << " mana."; + } else if (attacker == target) { + ss << ucfirst(target->getNameDescription()) << " loses " << manaDamage << " mana blocking an attack by " << (targetPlayer ? (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "herself." : "himself.") : "itself."); + } else { + ss << ucfirst(target->getNameDescription()) << " loses " << manaDamage << " mana blocking an attack by " << attacker->getNameDescription() << "."; + } + + std::string message = ss.str(); + + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + Player* tmpPlayer = (*it)->getPlayer(); + + if (tmpPlayer->getPosition().z == targetPos.z) { + if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) { + std::ostringstream tmpSs; + tmpSs << ucfirst(target->getNameDescription()) << " loses " << manaDamage << " mana blocking your attack."; + attackerPlayer->sendDamageMessage(MSG_DAMAGE_DEALT, tmpSs.str(), targetPos, manaDamage, TEXTCOLOR_BLUE); + } else if (tmpPlayer == targetPlayer) { + std::ostringstream tmpSs; + + if (!attacker) { + tmpSs << "You lose " << manaDamage << " mana."; + } else if (targetPlayer == attackerPlayer) { + tmpSs << "You lose " << manaDamage << " mana blocking an attack by yourself."; + } else { + tmpSs << "You lose " << manaDamage << " mana blocking an attack by " << attacker->getNameDescription() << "."; + } + + targetPlayer->sendDamageMessage(MSG_DAMAGE_RECEIVED, tmpSs.str(), targetPos, manaDamage, TEXTCOLOR_BLUE); + } else { + tmpPlayer->sendDamageMessage(MSG_DAMAGE_OTHERS, message, targetPos, manaDamage, TEXTCOLOR_BLUE); + } + } + } + + damage = std::max(0, damage - manaDamage); + } + } + + if (targetPlayer) { + if (damage >= targetPlayer->getHealth()) { + //scripting event - onPrepareDeath + CreatureEventList prepareDeathEvents = targetPlayer->getCreatureEvents(CREATURE_EVENT_PREPAREDEATH); + + for (CreatureEventList::const_iterator it = prepareDeathEvents.begin(); it != prepareDeathEvents.end(); ++it) { + (*it)->executeOnPrepareDeath(targetPlayer, attacker); + } + } + } + + damage = std::min(target->getHealth(), damage); + + if (damage > 0) { + target->drainHealth(attacker, combatType, damage); + addCreatureHealth(list, target); + + TextColor_t textColor = TEXTCOLOR_NONE; + uint8_t hitEffect = 0; + + switch (combatType) { + case COMBAT_PHYSICALDAMAGE: { + Item* splash = NULL; + + switch (target->getRace()) { + case RACE_VENOM: + textColor = TEXTCOLOR_LIGHTGREEN; + hitEffect = NM_ME_POISON; + splash = Item::CreateItem(ITEM_SMALLSPLASH, FLUID_GREEN); + break; + + case RACE_BLOOD: + textColor = TEXTCOLOR_RED; + hitEffect = NM_ME_DRAW_BLOOD; + splash = Item::CreateItem(ITEM_SMALLSPLASH, FLUID_BLOOD); + break; + + case RACE_UNDEAD: + textColor = TEXTCOLOR_LIGHTGREY; + hitEffect = NM_ME_HIT_AREA; + break; + + case RACE_FIRE: + textColor = TEXTCOLOR_ORANGE; + hitEffect = NM_ME_DRAW_BLOOD; + break; + + case RACE_ENERGY: + textColor = TEXTCOLOR_PURPLE; + hitEffect = NM_ME_ENERGY_DAMAGE; + break; + + default: + break; + } + + if (splash) { + internalAddItem(target->getTile(), splash, INDEX_WHEREEVER, FLAG_NOLIMIT); + startDecay(splash); + } + + break; + } + + case COMBAT_ENERGYDAMAGE: { + textColor = TEXTCOLOR_PURPLE; + hitEffect = NM_ME_ENERGY_DAMAGE; + break; + } + + case COMBAT_EARTHDAMAGE: { + textColor = TEXTCOLOR_LIGHTGREEN; + hitEffect = NM_ME_POISON_RINGS; + break; + } + + case COMBAT_DROWNDAMAGE: { + textColor = TEXTCOLOR_LIGHTBLUE; + hitEffect = NM_ME_LOSE_ENERGY; + break; + } + + case COMBAT_FIREDAMAGE: { + textColor = TEXTCOLOR_ORANGE; + hitEffect = NM_ME_HITBY_FIRE; + break; + } + + case COMBAT_ICEDAMAGE: { + textColor = TEXTCOLOR_SKYBLUE; + hitEffect = NM_ME_ICEATTACK; + break; + } + + case COMBAT_HOLYDAMAGE: { + textColor = TEXTCOLOR_YELLOW; + hitEffect = NM_ME_HOLYDAMAGE; + break; + } + + case COMBAT_DEATHDAMAGE: { + textColor = TEXTCOLOR_DARKRED; + hitEffect = NM_ME_SMALLCLOUDS; + break; + } + + case COMBAT_LIFEDRAIN: { + textColor = TEXTCOLOR_RED; + hitEffect = NM_ME_MAGIC_BLOOD; + break; + } + + default: + break; + } + + if (textColor != TEXTCOLOR_NONE) { + addMagicEffect(list, targetPos, hitEffect); + + std::ostringstream ss; + + if (!attacker) { + ss << ucfirst(target->getNameDescription()) << " loses " << damage << " hitpoint" << (damage != 1 ? "s." : "."); + } else if (attacker == target) { + ss << ucfirst(target->getNameDescription()) << " loses " << damage << " hitpoint" << (damage != 1 ? "s" : "") << " due to " << (targetPlayer ? (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her" : "his") : "its") << " own attack."; + } else { + ss << ucfirst(target->getNameDescription()) << " loses " << damage << " hitpoint" << (damage != 1 ? "s" : "") << " due to an attack by " << attacker->getNameDescription() << "."; + } + + std::string message = ss.str(); + + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + Player* tmpPlayer = (*it)->getPlayer(); + + if (tmpPlayer->getPosition().z == targetPos.z) { + if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) { + std::ostringstream tmpSs; + tmpSs << ucfirst(target->getNameDescription()) << " loses " << damage << " hitpoint" << (damage != 1 ? "s" : "") << " due to your attack."; + tmpPlayer->sendDamageMessage(MSG_DAMAGE_DEALT, tmpSs.str(), targetPos, damage, textColor); + } else if (tmpPlayer == targetPlayer) { + std::ostringstream tmpSs; + + if (!attacker) { + tmpSs << "You lose " << damage << " hitpoint" << (damage != 1 ? "s." : "."); + } else if (targetPlayer == attackerPlayer) { + tmpSs << "You lose " << damage << " hitpoint" << (damage != 1 ? "s" : "") << " due to your own attack."; + } else { + tmpSs << "You lose " << damage << " hitpoint" << (damage != 1 ? "s" : "") << " due to an attack by " << attacker->getNameDescription() << "."; + } + + tmpPlayer->sendDamageMessage(MSG_DAMAGE_RECEIVED, tmpSs.str(), targetPos, damage, textColor); + } else { + tmpPlayer->sendDamageMessage(MSG_DAMAGE_OTHERS, message, targetPos, damage, textColor); + } + } + } + } + } + } + + return true; +} + +bool Game::combatChangeMana(Creature* attacker, Creature* target, int32_t manaChange) +{ + if (manaChange > 0) { + if (attacker) { + Player* attackerPlayer = attacker->getPlayer(); + Player* targetPlayer = target->getPlayer(); + + if (attackerPlayer && targetPlayer) { + if (g_config.getBoolean(ConfigManager::CANNOT_ATTACK_SAME_LOOKFEET) && attacker->defaultOutfit.lookFeet == target->defaultOutfit.lookFeet) { + return false; + } + + if (attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) { + return false; + } + } + } + + target->changeMana(manaChange); + } else { + const Position& targetPos = target->getPosition(); + + if (!target->isAttackable() || Combat::canDoCombat(attacker, target) != RET_NOERROR) { + addMagicEffect(targetPos, NM_ME_POFF); + return false; + } + + Player* attackerPlayer = NULL; + + if (attacker) { + attackerPlayer = attacker->getPlayer(); + } + + Player* targetPlayer = target->getPlayer(); + + if (attackerPlayer && targetPlayer) { + if (g_config.getBoolean(ConfigManager::CANNOT_ATTACK_SAME_LOOKFEET) && attacker->defaultOutfit.lookFeet == target->defaultOutfit.lookFeet) { + return false; + } + + if (attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) { + return false; + } + } + + int32_t manaLoss = std::min(target->getMana(), -manaChange); + BlockType_t blockType = target->blockHit(attacker, COMBAT_MANADRAIN, manaLoss); + + if (blockType != BLOCK_NONE) { + addMagicEffect(targetPos, NM_ME_POFF); + return false; + } + + if (manaLoss <= 0) { + return true; + } + + target->drainMana(attacker, manaLoss); + + std::ostringstream ss; + + if (!attacker) { + ss << ucfirst(target->getNameDescription()) << " loses " << manaLoss << " mana."; + } else if (attacker == target) { + ss << ucfirst(target->getNameDescription()) << " loses " << manaLoss << " mana blocking an attack by " << (targetPlayer ? (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "herself." : "himself.") : "itself."); + } else { + ss << ucfirst(target->getNameDescription()) << " loses " << manaLoss << " mana blocking an attack by " << attacker->getNameDescription() << "."; + } + + std::string message = ss.str(); + + SpectatorVec list; + getSpectators(list, targetPos, false, true); + + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + Player* tmpPlayer = (*it)->getPlayer(); + + if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) { + std::ostringstream tmpSs; + tmpSs << ucfirst(target->getNameDescription()) << " loses " << manaLoss << " mana blocking your attack."; + tmpPlayer->sendDamageMessage(MSG_DAMAGE_DEALT, tmpSs.str(), targetPos, manaLoss, TEXTCOLOR_BLUE); + } else if (tmpPlayer == targetPlayer) { + std::ostringstream tmpSs; + + if (!attacker) { + tmpSs << "You lose " << manaLoss << " mana."; + } else if (targetPlayer == attackerPlayer) { + tmpSs << "You lose " << manaLoss << " mana blocking an attack by yourself."; + } else { + tmpSs << "You lose " << manaLoss << " mana blocking an attack by " << attacker->getNameDescription() << "."; + } + + tmpPlayer->sendDamageMessage(MSG_DAMAGE_RECEIVED, tmpSs.str(), targetPos, manaLoss, TEXTCOLOR_BLUE); + } else { + tmpPlayer->sendDamageMessage(MSG_DAMAGE_OTHERS, message, targetPos, manaLoss, TEXTCOLOR_BLUE); + } + } + } + + return true; +} + +void Game::addCreatureHealth(const Creature* target) +{ + SpectatorVec list; + getSpectators(list, target->getPosition(), true, true); + addCreatureHealth(list, target); +} + +void Game::addCreatureHealth(const SpectatorVec& list, const Creature* target) +{ + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + if (Player* tmpPlayer = (*it)->getPlayer()) { + tmpPlayer->sendCreatureHealth(target); + } + } +} + +void Game::addMagicEffect(const Position& pos, uint8_t effect, bool ghostMode /* = false */) +{ + if (ghostMode || effect > NM_ME_LAST) { + return; + } + + SpectatorVec list; + getSpectators(list, pos, true, true); + addMagicEffect(list, pos, effect); +} + +void Game::addMagicEffect(const SpectatorVec& list, const Position& pos, uint8_t effect, bool ghostMode/* = false */) +{ + if (ghostMode || effect > NM_ME_LAST) { + return; + } + + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + if (Player* tmpPlayer = (*it)->getPlayer()) { + tmpPlayer->sendMagicEffect(pos, effect); + } + } +} + +void Game::addDistanceEffect(const Position& fromPos, const Position& toPos, uint8_t effect) +{ + if (effect > NM_SHOOT_LAST || effect == NM_SHOOT_UNK1 || effect == NM_SHOOT_UNK2 || effect == NM_SHOOT_UNK3) { + return; + } + + SpectatorVec list; + getSpectators(list, fromPos, false, true); + getSpectators(list, toPos, false, true); + + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + if (Player* tmpPlayer = (*it)->getPlayer()) { + tmpPlayer->sendDistanceShoot(fromPos, toPos, effect); + } + } +} + +void Game::startDecay(Item* item) +{ + if (!item || !item->canDecay()) { + return; + } + + ItemDecayState_t decayState = item->getDecaying(); + + if (decayState == DECAYING_TRUE) { + return; + } + + if (item->getDuration() > 0) { + item->useThing2(); + item->setDecaying(DECAYING_TRUE); + toDecayItems.push_back(item); + } else { + internalDecayItem(item); + } +} + +void Game::internalDecayItem(Item* item) +{ + const ItemType& it = Item::items[item->getID()]; + + if (it.decayTo != 0) { + Item* newItem = transformItem(item, it.decayTo); + startDecay(newItem); + } else { + ReturnValue ret = internalRemoveItem(item); + + if (ret != RET_NOERROR) { + std::cout << "DEBUG, internalDecayItem failed, error code: " << (int32_t) ret << "item id: " << item->getID() << std::endl; + } + } +} + +void Game::checkDecay() +{ + checkDecayEvent = g_scheduler.addEvent(createSchedulerTask(EVENT_DECAYINTERVAL, + boost::bind(&Game::checkDecay, this))); + + size_t bucket = (lastBucket + 1) % EVENT_DECAY_BUCKETS; + + for (DecayList::iterator it = decayItems[bucket].begin(); it != decayItems[bucket].end();) { + Item* item = *it; + + if (!item->canDecay()) { + item->setDecaying(DECAYING_FALSE); + FreeThing(item); + it = decayItems[bucket].erase(it); + continue; + } + + int32_t decreaseTime = EVENT_DECAYINTERVAL * EVENT_DECAY_BUCKETS; + + if ((int32_t)item->getDuration() - decreaseTime < 0) { + decreaseTime = item->getDuration(); + } + + item->decreaseDuration(decreaseTime); + + int32_t dur = item->getDuration(); + + if (dur <= 0) { + it = decayItems[bucket].erase(it); + internalDecayItem(item); + FreeThing(item); + } else if (dur < EVENT_DECAYINTERVAL * EVENT_DECAY_BUCKETS) { + it = decayItems[bucket].erase(it); + size_t newBucket = (bucket + ((dur + EVENT_DECAYINTERVAL / 2) / 1000)) % EVENT_DECAY_BUCKETS; + + if (newBucket == bucket) { + internalDecayItem(item); + FreeThing(item); + } else { + decayItems[newBucket].push_back(item); + } + } else { + ++it; + } + } + + lastBucket = bucket; + cleanup(); +} + +void Game::checkLight() +{ + checkLightEvent = g_scheduler.addEvent(createSchedulerTask(EVENT_LIGHTINTERVAL, + boost::bind(&Game::checkLight, this))); + + lightHour += lightHourDelta; + + if (lightHour > 1440) { + lightHour -= 1440; + } + + if (std::abs(lightHour - SUNRISE) < 2 * lightHourDelta) { + lightState = LIGHT_STATE_SUNRISE; + } else if (std::abs(lightHour - SUNSET) < 2 * lightHourDelta) { + lightState = LIGHT_STATE_SUNSET; + } + + int32_t newLightLevel = lightLevel; + bool lightChange = false; + + switch (lightState) { + case LIGHT_STATE_SUNRISE: { + newLightLevel += (LIGHT_LEVEL_DAY - LIGHT_LEVEL_NIGHT) / 30; + lightChange = true; + break; + } + case LIGHT_STATE_SUNSET: { + newLightLevel -= (LIGHT_LEVEL_DAY - LIGHT_LEVEL_NIGHT) / 30; + lightChange = true; + break; + } + default: + break; + } + + if (newLightLevel <= LIGHT_LEVEL_NIGHT) { + lightLevel = LIGHT_LEVEL_NIGHT; + lightState = LIGHT_STATE_NIGHT; + } else if (newLightLevel >= LIGHT_LEVEL_DAY) { + lightLevel = LIGHT_LEVEL_DAY; + lightState = LIGHT_STATE_DAY; + } else { + lightLevel = newLightLevel; + } + + if (lightChange) { + LightInfo lightInfo; + getWorldLightInfo(lightInfo); + + for (AutoList::listiterator it = Player::listPlayer.list.begin(); it != Player::listPlayer.list.end(); ++it) { + it->second->sendWorldLight(lightInfo); + } + } +} + +void Game::getWorldLightInfo(LightInfo& lightInfo) const +{ + lightInfo.level = lightLevel; + lightInfo.color = 0xD7; +} + +void Game::addCommandTag(const std::string& tag) +{ + for (uint32_t i = 0, size = commandTags.size(); i < size; ++i) { + if (commandTags[i] == tag) { + return; + } + } + + commandTags.push_back(tag); +} + +void Game::resetCommandTag() +{ + commandTags.clear(); +} + +void Game::shutdown() +{ + std::cout << "Shutting down server..." << std::flush; + + g_scheduler.shutdown(); + g_dispatcher.shutdown(); + Spawns::getInstance()->clear(); + Raids::getInstance()->clear(); + + cleanup(); + + if (services) { + services->stop(); + } + + std::cout << " done!" << std::endl; +} + +void Game::cleanup() +{ + //free memory + for (std::vector::iterator it = ToReleaseThings.begin(); it != ToReleaseThings.end(); ++it) { + (*it)->releaseThing2(); + } + + ToReleaseThings.clear(); + + for (DecayList::iterator it = toDecayItems.begin(); it != toDecayItems.end(); ++it) { + int32_t dur = (*it)->getDuration(); + + if (dur >= EVENT_DECAYINTERVAL * EVENT_DECAY_BUCKETS) { + decayItems[lastBucket].push_back(*it); + } else { + decayItems[(lastBucket + 1 + (*it)->getDuration() / 1000) % EVENT_DECAY_BUCKETS].push_back(*it); + } + } + + toDecayItems.clear(); +} + +void Game::FreeThing(Thing* thing) +{ + ToReleaseThings.push_back(thing); +} + +bool Game::broadcastMessage(const std::string& text, MessageClasses type) +{ + std::cout << "> Broadcasted message: \"" << text << "\"." << std::endl; + + for (AutoList::listiterator it = Player::listPlayer.list.begin(); it != Player::listPlayer.list.end(); ++it) { + it->second->sendTextMessage(type, text); + } + + return true; +} + +void Game::updateCreatureWalkthrough(Creature* creature) +{ + //send to clients + SpectatorVec list; + getSpectators(list, creature->getPosition(), true, true); + + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + Player* tmpPlayer = (*it)->getPlayer(); + tmpPlayer->sendCreatureWalkthrough(creature, tmpPlayer->canWalkthroughEx(creature)); + } +} + +void Game::updatePlayerSkull(Player* player) +{ + if (getWorldType() != WORLD_TYPE_PVP) { + return; + } + + SpectatorVec list; + getSpectators(list, player->getPosition(), true, true); + + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + (*it)->getPlayer()->sendCreatureSkull(player); + } +} + +void Game::updatePlayerShield(Player* player) +{ + SpectatorVec list; + getSpectators(list, player->getPosition(), true, true); + + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + Player* spectatorPlayer = (*it)->getPlayer(); + spectatorPlayer->sendCreatureShield(player); + } +} + +void Game::updatePlayerHelpers(Player* player) +{ + uint32_t creatureId = player->getID(); + uint16_t helpers = player->getHelpers(); + + SpectatorVec list; + getSpectators(list, player->getPosition(), true, true); + + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + (*it)->getPlayer()->sendCreatureHelpers(creatureId, helpers); + } +} + +void Game::updateCreatureType(Creature* creature) +{ + const Player* masterPlayer = NULL; + + uint32_t creatureId = creature->getID(); + CreatureType_t creatureType = creature->getType(); + + if (creatureType == CREATURETYPE_MONSTER) { + const Creature* master = creature->getMaster(); + + if (master) { + masterPlayer = master->getPlayer(); + + if (masterPlayer) { + creatureType = CREATURETYPE_SUMMON_OTHERS; + } + } + } + + //send to clients + SpectatorVec list; + getSpectators(list, creature->getPosition(), true, true); + + if (creatureType == CREATURETYPE_SUMMON_OTHERS) { + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + Player* player = (*it)->getPlayer(); + + if (masterPlayer == player) { + player->sendCreatureType(creatureId, CREATURETYPE_SUMMON_OWN); + } else { + player->sendCreatureType(creatureId, creatureType); + } + } + } else { + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + (*it)->getPlayer()->sendCreatureType(creatureId, creatureType); + } + } +} + +void Game::updatePremium(Account& account) +{ + bool save = false; + time_t timeNow = time(NULL); + + if (account.premiumDays != 0 && account.premiumDays != 0xFFFF) { + if (account.lastDay == 0) { + account.lastDay = timeNow; + save = true; + } else { + uint32_t days = (timeNow - account.lastDay) / 86400; + + if (days > 0) { + if (days >= account.premiumDays) { + account.premiumDays = 0; + account.lastDay = 0; + } else { + account.premiumDays -= days; + uint32_t remainder = (timeNow - account.lastDay) % 86400; + account.lastDay = timeNow - remainder; + } + + save = true; + } + } + } else if (account.lastDay != 0) { + account.lastDay = 0; + save = true; + } + + if (save && !IOLoginData::getInstance()->saveAccount(account)) { + std::cout << "> ERROR: Failed to save account: " << account.name << "!" << std::endl; + } +} + +void Game::autoSave() +{ + int32_t autoSaveEachMinutes = g_config.getNumber(ConfigManager::AUTO_SAVE_EACH_MINUTES); + + if (autoSaveEachMinutes <= 0) { + return; + } + + g_dispatcher.addTask(createTask(boost::bind(&Game::saveGameState, this))); + g_scheduler.addEvent(createSchedulerTask(autoSaveEachMinutes * 1000 * 60, boost::bind(&Game::autoSave, this))); +} + +void Game::prepareServerSave() +{ + if (!serverSaveMessage[0]) { + serverSaveMessage[0] = true; + broadcastMessage("Server is saving game in 5 minutes. Please logout.", MSG_STATUS_WARNING); + g_scheduler.addEvent(createSchedulerTask(120000, boost::bind(&Game::prepareServerSave, this))); + } else if (!serverSaveMessage[1]) { + serverSaveMessage[1] = true; + broadcastMessage("Server is saving game in 3 minutes. Please logout.", MSG_STATUS_WARNING); + g_scheduler.addEvent(createSchedulerTask(120000, boost::bind(&Game::prepareServerSave, this))); + } else if (!serverSaveMessage[2]) { + serverSaveMessage[2] = true; + broadcastMessage("Server is saving game in one minute. Please logout.", MSG_STATUS_WARNING); + g_scheduler.addEvent(createSchedulerTask(60000, boost::bind(&Game::prepareServerSave, this))); + } else { + serverSave(); + } +} + +void Game::serverSave() +{ + if (g_config.getBoolean(ConfigManager::SHUTDOWN_AT_SERVERSAVE)) { + //shutdown server + setGameState(GAME_STATE_SHUTDOWN); + } else { + //close server + setGameState(GAME_STATE_CLOSED); + + //clean map if configured to + if (g_config.getBoolean(ConfigManager::CLEAN_MAP_AT_SERVERSAVE)) { + map->clean(); + } + + //reset variables + for (int16_t i = 0; i < 3; i++) { + setServerSaveMessage(i, false); + } + + //prepare for next serversave after 24 hours + g_scheduler.addEvent(createSchedulerTask(86100000, boost::bind(&Game::prepareServerSave, this))); + + //open server + setGameState(GAME_STATE_NORMAL); + } +} + +Position Game::getClosestFreeTile(Player* player, Creature* teleportedCreature, const Position& toPos, bool teleport) +{ + Tile* tile[9] = { + getTile(toPos.x, toPos.y, toPos.z), + getTile(toPos.x - 1, toPos.y - 1, toPos.z), getTile(toPos.x, toPos.y - 1, toPos.z), + getTile(toPos.x + 1, toPos.y - 1, toPos.z), getTile(toPos.x - 1, toPos.y, toPos.z), + getTile(toPos.x + 1, toPos.y, toPos.z), getTile(toPos.x - 1, toPos.y + 1, toPos.z), + getTile(toPos.x, toPos.y + 1, toPos.z), getTile(toPos.x + 1, toPos.y + 1, toPos.z), + }; + + if (teleport) { + if (player) { + for (int32_t i = 0; i < 9; i++) { + if (tile[i] && ((!tile[i]->hasProperty(IMMOVABLEBLOCKSOLID) && tile[i]->getCreatureCount() == 0) || player->getAccountType() == ACCOUNT_TYPE_GOD)) { + return tile[i]->getPosition(); + } + } + } + } else if (teleportedCreature) { + Player* teleportedPlayer = teleportedCreature->getPlayer(); + + if (teleportedPlayer) { + for (int32_t i = 0; i < 9; i++) { + if (tile[i] && ((!tile[i]->hasProperty(IMMOVABLEBLOCKSOLID) && tile[i]->getCreatureCount() == 0) || teleportedPlayer->getAccountType() == ACCOUNT_TYPE_GOD)) { + return tile[i]->getPosition(); + } + } + } else { + for (int32_t i = 0; i < 9; i++) { + if (tile[i] && tile[i]->getCreatureCount() == 0 && !tile[i]->hasProperty(IMMOVABLEBLOCKSOLID)) { + return tile[i]->getPosition(); + } + } + } + } + + return Position(0, 0, 0); +} + +int32_t Game::getMotdNum() +{ + if (lastMotdText != g_config.getString(ConfigManager::MOTD)) { + lastMotdNum++; + lastMotdText = g_config.getString(ConfigManager::MOTD); + + FILE* file = fopen("lastMotd.txt", "w"); + + if (file != NULL) { + fprintf(file, "%d", lastMotdNum); + fprintf(file, "\n%s", lastMotdText.c_str()); + fclose(file); + } + } + + return lastMotdNum; +} + +void Game::loadMotd() +{ + std::ifstream file("lastMotd.txt"); + + if (!file) { + std::cout << "> ERROR: Failed to load lastMotd.txt" << std::endl; + lastMotdNum = random_range(5, 500); + return; + } + + std::string tmpStr; + getline(file, tmpStr); + getline(file, lastMotdText); + lastMotdNum = atoi(tmpStr.c_str()); + file.close(); +} + +void Game::checkPlayersRecord() +{ + if (getPlayersOnline() > lastPlayersRecord) { + uint32_t tmplPlayersRecord = lastPlayersRecord; + lastPlayersRecord = getPlayersOnline(); + GlobalEventMap recordEvents = g_globalEvents->getEventMap(GLOBALEVENT_RECORD); + + for (GlobalEventMap::iterator it = recordEvents.begin(); it != recordEvents.end(); ++it) { + it->second->executeRecord(lastPlayersRecord, tmplPlayersRecord); + } + + savePlayersRecord(); + } +} + +void Game::savePlayersRecord() +{ + FILE* file = fopen("playersRecord.txt", "w"); + + if (file == NULL) { + std::cout << "> ERROR: Failed to save playersRecord.txt" << std::endl; + return; + } + + int32_t tmp = fprintf(file, "%u", lastPlayersRecord); + + if (tmp == EOF) { + std::cout << "> ERROR: Failed to save playersRecord.txt" << std::endl; + } + + fclose(file); +} + +void Game::loadPlayersRecord() +{ + FILE* file = fopen("playersRecord.txt", "r"); + + if (file == NULL) { + std::cout << "> ERROR: Failed to load playersRecord.txt" << std::endl; + lastPlayersRecord = 0; + return; + } + + int32_t tmp = fscanf(file, "%u", &lastPlayersRecord); + + if (tmp == EOF) { + std::cout << "> ERROR: Failed to read playersRecord.txt" << std::endl; + lastPlayersRecord = 0; + } + + fclose(file); +} + +uint64_t Game::getExperienceStage(uint32_t level) +{ + if (!stagesEnabled) { + return g_config.getNumber(ConfigManager::RATE_EXPERIENCE); + } + + if (useLastStageLevel && level >= lastStageLevel) { + return stages[lastStageLevel]; + } + + return stages[level]; +} + +bool Game::loadExperienceStages() +{ + std::string filename = "data/XML/stages.xml"; + xmlDocPtr doc = xmlParseFile(filename.c_str()); + + if (doc) { + xmlNodePtr root, p; + int32_t intVal, low, high, mult; + root = xmlDocGetRootElement(doc); + + if (xmlStrcmp(root->name, (const xmlChar*)"stages")) { + xmlFreeDoc(doc); + return false; + } + + p = root->children; + + while (p) { + if (!xmlStrcmp(p->name, (const xmlChar*)"config")) { + if (readXMLInteger(p, "enabled", intVal)) { + stagesEnabled = (intVal != 0); + } + } else if (!xmlStrcmp(p->name, (const xmlChar*)"stage")) { + if (readXMLInteger(p, "minlevel", intVal)) { + low = intVal; + } else { + low = 1; + } + + if (readXMLInteger(p, "maxlevel", intVal)) { + high = intVal; + } else { + high = 0; + lastStageLevel = low; + useLastStageLevel = true; + } + + if (readXMLInteger(p, "multiplier", intVal)) { + mult = intVal; + } else { + mult = 1; + } + + if (useLastStageLevel) { + stages[lastStageLevel] = mult; + } else { + for (int32_t iteratorValue = low; iteratorValue <= high; iteratorValue++) { + stages[iteratorValue] = mult; + } + } + } + + p = p->next; + } + + xmlFreeDoc(doc); + } + + return true; +} + +bool Game::playerInviteToParty(uint32_t playerId, uint32_t invitedId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + Player* invitedPlayer = getPlayerByID(invitedId); + + if (!invitedPlayer || invitedPlayer->isRemoved() || invitedPlayer->isInviting(player)) { + return false; + } + + if (invitedPlayer->getParty()) { + std::ostringstream ss; + ss << invitedPlayer->getName() << " is already in a party."; + player->sendTextMessage(MSG_INFO_DESCR, ss.str()); + return false; + } + + Party* party = player->getParty(); + + if (!party) { + party = new Party(player); + } else if (party->getLeader() != player) { + return false; + } + + return party->invitePlayer(invitedPlayer); +} + +bool Game::playerJoinParty(uint32_t playerId, uint32_t leaderId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + Player* leader = getPlayerByID(leaderId); + + if (!leader || leader->isRemoved() || !leader->isInviting(player)) { + return false; + } + + Party* party = leader->getParty(); + + if (!party || party->getLeader() != leader) { + return false; + } + + if (player->getParty()) { + player->sendTextMessage(MSG_INFO_DESCR, "You are already in a party."); + return false; + } + + return party->joinParty(player); +} + +bool Game::playerRevokePartyInvitation(uint32_t playerId, uint32_t invitedId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + Party* party = player->getParty(); + + if (!party || party->getLeader() != player) { + return false; + } + + Player* invitedPlayer = getPlayerByID(invitedId); + + if (!invitedPlayer || invitedPlayer->isRemoved() || !player->isInviting(invitedPlayer)) { + return false; + } + + party->revokeInvitation(invitedPlayer); + return true; +} + +bool Game::playerPassPartyLeadership(uint32_t playerId, uint32_t newLeaderId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + Party* party = player->getParty(); + + if (!party || party->getLeader() != player) { + return false; + } + + Player* newLeader = getPlayerByID(newLeaderId); + + if (!newLeader || newLeader->isRemoved() || !player->isPartner(newLeader)) { + return false; + } + + return party->passPartyLeadership(newLeader); +} + +bool Game::playerLeaveParty(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + Party* party = player->getParty(); + + if (!party || player->hasCondition(CONDITION_INFIGHT)) { + return false; + } + + return party->leaveParty(player); +} + +bool Game::playerEnableSharedPartyExperience(uint32_t playerId, bool sharedExpActive) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + Party* party = player->getParty(); + + if (!party || player->hasCondition(CONDITION_INFIGHT)) { + return false; + } + + return party->setSharedExperience(player, sharedExpActive); +} + +void Game::sendGuildMotd(uint32_t playerId, uint32_t guildId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return; + } + + Guild* guild = player->getGuild(); + + if (guild) { + player->sendChannelMessage("Message of the Day", guild->getMotd(), SPEAK_CHANNEL_R1, CHANNEL_GUILD); + } +} + +void Game::kickPlayer(uint32_t playerId, bool displayEffect) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return; + } + + player->kickPlayer(displayEffect); +} + +bool Game::playerReportBug(uint32_t playerId, const std::string& bug) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + if (player->getAccountType() == ACCOUNT_TYPE_NORMAL) { + return false; + } + + std::string fileName = "data/reports/" + player->getName() + " report.txt"; + FILE* file = fopen(fileName.c_str(), "a"); + + if (file) { + const Position& position = player->getPosition(); + fprintf(file, "------------------------------\nName: %s [Position X: %d Y: %d Z: %d]\nBug Report: %s\n", player->getName().c_str(), position.x, position.y, position.z, bug.c_str()); + fclose(file); + } + + player->sendTextMessage(MSG_EVENT_DEFAULT, "Your report has been sent to " + g_config.getString(ConfigManager::SERVER_NAME) + "."); + return true; +} + +bool Game::playerDebugAssert(uint32_t playerId, const std::string& assertLine, const std::string& date, const std::string& description, const std::string& comment) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + FILE* file = fopen("client_assertions.txt", "a"); + + if (file) { + fprintf(file, "----- %s - %s (%s) -----\n", formatDate(time(NULL)).c_str(), player->getName().c_str(), convertIPToString(player->getIP()).c_str()); + fprintf(file, "%s\n%s\n%s\n%s\n", assertLine.c_str(), date.c_str(), description.c_str(), comment.c_str()); + fclose(file); + } + + return true; +} + +bool Game::playerLeaveMarket(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + player->setMarketDepotId(-1); + return true; +} + +bool Game::playerBrowseMarket(uint32_t playerId, uint16_t spriteId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + if (player->getMarketDepotId() == -1) { + return false; + } + + const ItemType& it = Item::items.getItemIdByClientId(spriteId); + + if (it.id == 0) { + return false; + } + + if (!it.ware) { + return false; + } + + const MarketOfferList& buyOffers = IOMarket::getInstance()->getActiveOffers(MARKETACTION_BUY, it.id); + + const MarketOfferList& sellOffers = IOMarket::getInstance()->getActiveOffers(MARKETACTION_SELL, it.id); + + player->sendMarketBrowseItem(it.id, buyOffers, sellOffers); + + player->sendMarketDetail(it.id); + + return true; +} + +bool Game::playerBrowseMarketOwnOffers(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + if (player->getMarketDepotId() == -1) { + return false; + } + + const MarketOfferList& buyOffers = IOMarket::getInstance()->getOwnOffers(MARKETACTION_BUY, player->getGUID()); + + const MarketOfferList& sellOffers = IOMarket::getInstance()->getOwnOffers(MARKETACTION_SELL, player->getGUID()); + + player->sendMarketBrowseOwnOffers(buyOffers, sellOffers); + + return true; +} + +bool Game::playerBrowseMarketOwnHistory(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + if (player->getMarketDepotId() == -1) { + return false; + } + + const HistoryMarketOfferList& buyOffers = IOMarket::getInstance()->getOwnHistory(MARKETACTION_BUY, player->getGUID()); + + const HistoryMarketOfferList& sellOffers = IOMarket::getInstance()->getOwnHistory(MARKETACTION_SELL, player->getGUID()); + + player->sendMarketBrowseOwnHistory(buyOffers, sellOffers); + + return true; +} + +bool Game::playerCreateMarketOffer(uint32_t playerId, uint8_t type, uint16_t spriteId, uint16_t amount, uint32_t price, bool anonymous) +{ + if (amount == 0 || amount > 64000) { + return false; + } + + if (price == 0 || price > 999999999) { + return false; + } + + if (type != MARKETACTION_BUY && type != MARKETACTION_SELL) { + return false; + } + + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + if (player->getMarketDepotId() == -1) { + return false; + } + + if (g_config.getBoolean(ConfigManager::MARKET_PREMIUM) && !player->isPremium()) { + player->sendMarketLeave(); + return false; + } + + const ItemType& it = Item::items.getItemIdByClientId(spriteId); + + if (it.id == 0) { + return false; + } + + if (!it.ware) { + return false; + } + + const int32_t maxOfferCount = g_config.getNumber(ConfigManager::MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER); + + if (maxOfferCount > 0) { + const int32_t offerCount = IOMarket::getInstance()->getPlayerOfferCount(player->getGUID()); + + if (offerCount == -1 || offerCount >= maxOfferCount) { + return false; + } + } + + uint64_t fee = (price / 100.) * amount; + + if (fee < 20) { + fee = 20; + } else if (fee > 1000) { + fee = 1000; + } + + if (type == MARKETACTION_SELL) { + if (fee > player->bankBalance) { + return false; + } + + DepotChest* depotChest = player->getDepotChest(player->getMarketDepotId(), false); + + if (!depotChest) { + return false; + } + + ItemList itemList; + uint32_t count = 0; + std::list containerList; + containerList.push_back(depotChest); + containerList.push_back(player->getInbox()); + bool enough = false; + + do { + Container* container = containerList.front(); + containerList.pop_front(); + + for (ItemDeque::const_iterator iter = container->getItems(), end = container->getEnd(); iter != end; ++iter) { + Item* item = (*iter); + Container* c = item->getContainer(); + + if (c && !c->empty()) { + containerList.push_back(c); + continue; + } + + if (item->getID() != it.id) { + continue; + } + + const ItemType& itemType = Item::items[item->getID()]; + + if (!itemType.isRune() && item->getCharges() != itemType.charges) { + continue; + } + + if (item->getDuration() != itemType.decayTime) { + continue; + } + + itemList.push_back(item); + count += Item::countByType(item, -1); + + if (count >= amount) { + enough = true; + break; + } + } + + if (enough) { + break; + } + } while (!containerList.empty()); + + if (!enough) { + return false; + } + + if (it.stackable) { + uint16_t tmpAmount = amount; + + for (ItemList::const_iterator iter = itemList.begin(), end = itemList.end(); iter != end; ++iter) { + uint16_t removeCount = std::min(tmpAmount, (*iter)->getItemCount()); + tmpAmount -= removeCount; + internalRemoveItem(*iter, removeCount); + + if (tmpAmount == 0) { + break; + } + } + } else { + for (ItemList::const_iterator iter = itemList.begin(), end = itemList.end(); iter != end; ++iter) { + internalRemoveItem(*iter); + } + } + + player->bankBalance -= fee; + } else { + uint64_t totalPrice = (uint64_t)price * amount; + totalPrice += fee; + + if (totalPrice > player->bankBalance) { + return false; + } + + player->bankBalance -= totalPrice; + } + + IOMarket::getInstance()->createOffer(player->getGUID(), (MarketAction_t)type, it.id, amount, price, anonymous); + + player->sendMarketEnter(player->getMarketDepotId()); + const MarketOfferList& buyOffers = IOMarket::getInstance()->getActiveOffers(MARKETACTION_BUY, it.id); + const MarketOfferList& sellOffers = IOMarket::getInstance()->getActiveOffers(MARKETACTION_SELL, it.id); + player->sendMarketBrowseItem(it.id, buyOffers, sellOffers); + return true; +} + +bool Game::playerCancelMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + if (player->getMarketDepotId() == -1) { + return false; + } + + uint32_t offerId = IOMarket::getInstance()->getOfferIdByCounter(timestamp, counter); + + if (offerId == 0) { + return false; + } + + MarketOfferEx offer = IOMarket::getInstance()->getOfferById(offerId); + + if (offer.playerId != player->getGUID()) { + return false; + } + + if (offer.type == MARKETACTION_BUY) { + player->bankBalance += (uint64_t)offer.price * offer.amount; + player->sendMarketEnter(player->getMarketDepotId()); + } else { + const ItemType& it = Item::items[offer.itemId]; + + if (it.id == 0) { + return false; + } + + if (it.stackable) { + uint16_t tmpAmount = offer.amount; + + while (tmpAmount > 0) { + int32_t stackCount = std::min(100, tmpAmount); + Item* item = Item::CreateItem(it.id, stackCount); + + if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RET_NOERROR) { + delete item; + break; + } + + tmpAmount -= stackCount; + } + } else { + int32_t subType = -1; + + if (it.charges != 0) { + subType = it.charges; + } + + for (uint16_t i = 0; i < offer.amount; ++i) { + Item* item = Item::CreateItem(it.id, subType); + + if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RET_NOERROR) { + delete item; + break; + } + } + } + } + + IOMarket::getInstance()->moveOfferToHistory(offerId, OFFERSTATE_CANCELLED); + offer.amount = 0; + offer.timestamp += g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); + player->sendMarketCancelOffer(offer); + return true; +} + +bool Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter, uint16_t amount) +{ + if (amount == 0 || amount > 64000) { + return false; + } + + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + if (player->getMarketDepotId() == -1) { + return false; + } + + uint32_t offerId = IOMarket::getInstance()->getOfferIdByCounter(timestamp, counter); + + if (offerId == 0) { + return false; + } + + MarketOfferEx offer = IOMarket::getInstance()->getOfferById(offerId); + + if (amount > offer.amount) { + return false; + } + + const ItemType& it = Item::items[offer.itemId]; + + if (it.id == 0) { + return false; + } + + uint64_t totalPrice = (uint64_t)offer.price * amount; + + if (offer.type == MARKETACTION_BUY) { + DepotChest* depotChest = player->getDepotChest(player->getMarketDepotId(), false); + + if (!depotChest) { + return false; + } + + ItemList itemList; + uint32_t count = 0; + std::list containerList; + containerList.push_back(depotChest); + containerList.push_back(player->getInbox()); + bool enough = false; + + do { + Container* container = containerList.front(); + containerList.pop_front(); + + for (ItemDeque::const_iterator iter = container->getItems(), end = container->getEnd(); iter != end; ++iter) { + Item* item = (*iter); + Container* c = item->getContainer(); + + if (c && !c->empty()) { + containerList.push_back(c); + continue; + } + + if (item->getID() != it.id) { + continue; + } + + const ItemType& itemType = Item::items[item->getID()]; + + if (!itemType.isRune() && item->getCharges() != itemType.charges) { + continue; + } + + if (item->getDuration() != itemType.decayTime) { + continue; + } + + itemList.push_back(item); + count += Item::countByType(item, -1); + + if (count >= amount) { + enough = true; + break; + } + } + + if (enough) { + break; + } + } while (!containerList.empty()); + + if (!enough) { + return false; + } + + if (it.stackable) { + uint16_t tmpAmount = amount; + + for (ItemList::const_iterator iter = itemList.begin(), end = itemList.end(); iter != end; ++iter) { + uint16_t removeCount = std::min(tmpAmount, (*iter)->getItemCount()); + tmpAmount -= removeCount; + internalRemoveItem(*iter, removeCount); + + if (tmpAmount == 0) { + break; + } + } + } else { + for (ItemList::const_iterator iter = itemList.begin(), end = itemList.end(); iter != end; ++iter) { + internalRemoveItem(*iter); + } + } + + player->bankBalance += totalPrice; + + Player* buyerPlayer = getPlayerByGUID(offer.playerId); + + if (!buyerPlayer) { + std::string buyerName; + + if (!IOLoginData::getInstance()->getNameByGuid(offer.playerId, buyerName)) { + return false; + } + + buyerPlayer = new Player(buyerName, NULL); + + if (!IOLoginData::getInstance()->loadPlayer(buyerPlayer, buyerName)) { + delete buyerPlayer; + return false; + } + } + + if (it.stackable) { + uint16_t tmpAmount = amount; + + while (tmpAmount > 0) { + uint16_t stackCount = std::min(100, tmpAmount); + Item* item = Item::CreateItem(it.id, stackCount); + + if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RET_NOERROR) { + delete item; + break; + } + + tmpAmount -= stackCount; + } + } else { + int32_t subType = -1; + + if (it.charges != 0) { + subType = it.charges; + } + + for (uint16_t i = 0; i < amount; ++i) { + Item* item = Item::CreateItem(it.id, subType); + + if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RET_NOERROR) { + delete item; + break; + } + } + } + + if (buyerPlayer->isOffline()) { + IOLoginData::getInstance()->savePlayer(buyerPlayer); + delete buyerPlayer; + } else { + buyerPlayer->onReceiveMail(); + } + } else { + if (totalPrice > player->bankBalance) { + return false; + } + + player->bankBalance -= totalPrice; + + if (it.stackable) { + uint16_t tmpAmount = amount; + + while (tmpAmount > 0) { + uint16_t stackCount = std::min(100, tmpAmount); + Item* item = Item::CreateItem(it.id, stackCount); + + if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RET_NOERROR) { + delete item; + break; + } + + tmpAmount -= stackCount; + } + } else { + int32_t subType = -1; + + if (it.charges != 0) { + subType = it.charges; + } + + for (uint16_t i = 0; i < amount; ++i) { + Item* item = Item::CreateItem(it.id, subType); + + if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RET_NOERROR) { + delete item; + break; + } + } + } + + Player* sellerPlayer = getPlayerByGUID(offer.playerId); + + if (sellerPlayer) { + sellerPlayer->bankBalance += totalPrice; + } else { + IOLoginData::getInstance()->increaseBankBalance(offer.playerId, totalPrice); + } + + player->onReceiveMail(); + } + + const int32_t marketOfferDuration = g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); + + IOMarket::getInstance()->appendHistory(player->getGUID(), (offer.type == MARKETACTION_BUY ? MARKETACTION_SELL : MARKETACTION_BUY), offer.itemId, amount, offer.price, offer.timestamp + marketOfferDuration, OFFERSTATE_ACCEPTEDEX); + + IOMarket::getInstance()->appendHistory(offer.playerId, offer.type, offer.itemId, amount, offer.price, offer.timestamp + marketOfferDuration, OFFERSTATE_ACCEPTED); + + offer.amount -= amount; + + if (offer.amount == 0) { + IOMarket::getInstance()->deleteOffer(offerId); + } else { + IOMarket::getInstance()->acceptOffer(offerId, amount); + } + + player->sendMarketEnter(player->getMarketDepotId()); + offer.timestamp += marketOfferDuration; + player->sendMarketAcceptOffer(offer); + return true; +} + +void Game::checkExpiredMarketOffers() +{ + IOMarket::getInstance()->clearOldHistory(); + + const ExpiredMarketOfferList& expiredBuyOffers = IOMarket::getInstance()->getExpiredOffers(MARKETACTION_BUY); + + for (ExpiredMarketOfferList::const_iterator it = expiredBuyOffers.begin(), end = expiredBuyOffers.end(); it != end; ++it) { + ExpiredMarketOffer offer = *it; + + Player* player = getPlayerByGUID(offer.playerId); + uint64_t totalPrice = (uint64_t)offer.price * offer.amount; + + if (player) { + player->bankBalance += totalPrice; + } else { + IOLoginData::getInstance()->increaseBankBalance(offer.playerId, totalPrice); + } + + IOMarket::getInstance()->moveOfferToHistory(offer.id, OFFERSTATE_EXPIRED); + } + + const ExpiredMarketOfferList& expiredSellOffers = IOMarket::getInstance()->getExpiredOffers(MARKETACTION_SELL); + + for (ExpiredMarketOfferList::const_iterator it = expiredSellOffers.begin(), end = expiredSellOffers.end(); it != end; ++it) { + ExpiredMarketOffer offer = *it; + + Player* player = getPlayerByGUID(offer.playerId); + + if (!player) { + std::string name; + + if (!IOLoginData::getInstance()->getNameByGuid(offer.playerId, name)) { + continue; + } + + player = new Player(name, NULL); + + if (!IOLoginData::getInstance()->loadPlayer(player, name)) { + delete player; + continue; + } + } + + const ItemType& itemType = Item::items[offer.itemId]; + + if (itemType.id == 0) { + continue; + } + + if (itemType.stackable) { + uint16_t tmpAmount = offer.amount; + + while (tmpAmount > 0) { + uint16_t stackCount = std::min(100, tmpAmount); + Item* item = Item::CreateItem(itemType.id, stackCount); + + if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RET_NOERROR) { + delete item; + break; + } + + tmpAmount -= stackCount; + } + } else { + int32_t subType = -1; + + if (itemType.charges != 0) { + subType = itemType.charges; + } + + for (uint16_t i = 0; i < offer.amount; ++i) { + Item* item = Item::CreateItem(itemType.id, subType); + + if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RET_NOERROR) { + delete item; + break; + } + } + } + + if (player->isOffline()) { + IOLoginData::getInstance()->savePlayer(player); + delete player; + } + + IOMarket::getInstance()->moveOfferToHistory(offer.id, OFFERSTATE_EXPIRED); + } + + int32_t checkExpiredMarketOffersEachMinutes = g_config.getNumber(ConfigManager::CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES); + + if (checkExpiredMarketOffersEachMinutes <= 0) { + return; + } + + g_scheduler.addEvent(createSchedulerTask(checkExpiredMarketOffersEachMinutes * 60 * 1000, boost::bind(&Game::checkExpiredMarketOffers, this))); +} + +void Game::forceAddCondition(uint32_t creatureId, Condition* condition) +{ + Creature* creature = getCreatureByID(creatureId); + + if (!creature || creature->isRemoved()) { + return; + } + + creature->addCondition(condition, true); +} + +void Game::forceRemoveCondition(uint32_t creatureId, ConditionType_t type) +{ + Creature* creature = getCreatureByID(creatureId); + + if (!creature || creature->isRemoved()) { + return; + } + + creature->removeCondition(type, true); +} + +bool Game::violationWindow(Player* player, std::string targetPlayerName, int32_t reason, int32_t action, const std::string& banComment, bool IPBanishment) +{ + if ((0 == (violationActions[player->getAccountType()] & (1 << action))) + || reason > violationReasons[player->getAccountType()] + || (IPBanishment && (violationActions[player->getAccountType()] & Action_IpBan) != Action_IpBan)) { + player->sendCancel("You do not have authorization for this action."); + return false; + } + + //If this will be configurable, the field in the database has to be edited + //since its a VARCHAR(60). + if (banComment.size() > 60) { + player->sendCancel("The comment may not exceed the limit of 60 characters."); + return false; + } + + if (!IOLoginData::getInstance()->playerExists(targetPlayerName)) { + player->sendCancel("A player with this name does not exist."); + return false; + } + + AccountType_t targetAccountType = ACCOUNT_TYPE_NORMAL; + Account account; + uint32_t guid; + + Player* targetPlayer = getPlayerByName(targetPlayerName); + + if (targetPlayer) { + targetAccountType = targetPlayer->getAccountType(); + guid = targetPlayer->getGUID(); + account = IOLoginData::getInstance()->loadAccount(targetPlayer->getAccount()); + targetPlayerName = targetPlayer->getName(); + } else { + account = IOLoginData::getInstance()->loadAccount(IOLoginData::getInstance()->getAccountNumberByName(targetPlayerName)); + targetAccountType = IOLoginData::getInstance()->getAccountType(account.id); + IOLoginData::getInstance()->getGuidByName(guid, targetPlayerName); + } + + if (targetAccountType >= player->getAccountType()) { + player->sendCancel("You do not have authorization for this action."); + return false; + } + + bool isNotation = false; + + switch (action) { + case 0: { + IOBan::getInstance()->addAccountNotation(account.id, time(NULL), reason, action, banComment, player->getGUID()); + + if (IOBan::getInstance()->getNotationsCount(account.id) > 2) { + account.warnings++; + + if (account.warnings > 3) { + action = 7; + IOBan::getInstance()->addAccountDeletion(account.id, time(NULL), reason, action, banComment, player->getGUID()); + } else if (account.warnings == 3) { + IOBan::getInstance()->addAccountBan(account.id, (time(NULL) + (g_config.getNumber(ConfigManager::FINAL_BAN_DAYS) * 86400)), reason, action, banComment, player->getGUID()); + } else { + IOBan::getInstance()->addAccountBan(account.id, (time(NULL) + (g_config.getNumber(ConfigManager::BAN_DAYS) * 86400)), reason, action, "4 notations received, auto banishment.", player->getGUID()); + } + } else { + isNotation = true; + } + + break; + } + + case 1: { + IOBan::getInstance()->addPlayerNamelock(guid, time(NULL), reason, action, banComment, player->getGUID()); + break; + } + + case 3: { + account.warnings++; + + if (account.warnings > 3) { + action = 7; + IOBan::getInstance()->addAccountDeletion(account.id, time(NULL), reason, action, banComment, player->getGUID()); + } else { + if (account.warnings == 3) { + IOBan::getInstance()->addAccountBan(account.id, (time(NULL) + (g_config.getNumber(ConfigManager::FINAL_BAN_DAYS) * 86400)), reason, action, banComment, player->getGUID()); + } else { + IOBan::getInstance()->addAccountBan(account.id, (time(NULL) + (g_config.getNumber(ConfigManager::BAN_DAYS) * 86400)), reason, action, banComment, player->getGUID()); + } + + IOBan::getInstance()->addPlayerNamelock(guid, time(NULL), reason, action, banComment, player->getGUID()); + } + + break; + } + + case 4: { + if (account.warnings < 3) { + account.warnings = 3; + IOBan::getInstance()->addAccountBan(account.id, (time(NULL) + (g_config.getNumber(ConfigManager::FINAL_BAN_DAYS) * 86400)), reason, action, banComment, player->getGUID()); + } else { + action = 7; + account.warnings++; + IOBan::getInstance()->addAccountDeletion(account.id, time(NULL), reason, action, banComment, player->getGUID()); + } + + break; + } + + case 5: { + if (account.warnings < 3) { + account.warnings = 3; + IOBan::getInstance()->addAccountBan(account.id, (time(NULL) + (g_config.getNumber(ConfigManager::FINAL_BAN_DAYS) * 86400)), reason, action, banComment, player->getGUID()); + IOBan::getInstance()->addPlayerNamelock(guid, time(NULL), reason, action, banComment, player->getGUID()); + } else { + action = 7; + account.warnings++; + IOBan::getInstance()->addAccountDeletion(account.id, time(NULL), reason, action, banComment, player->getGUID()); + } + + break; + } + + default: { + account.warnings++; + + if (account.warnings > 3) { + action = 7; + IOBan::getInstance()->addAccountDeletion(account.id, time(NULL), reason, action, banComment, player->getGUID()); + } else { + if (account.warnings == 3) { + IOBan::getInstance()->addAccountBan(account.id, (time(NULL) + (g_config.getNumber(ConfigManager::FINAL_BAN_DAYS) * 86400)), reason, action, banComment, player->getGUID()); + } else { + IOBan::getInstance()->addAccountBan(account.id, (time(NULL) + (g_config.getNumber(ConfigManager::BAN_DAYS) * 86400)), reason, action, banComment, player->getGUID()); + } + } + + break; + } + } + + if (g_config.getBoolean(ConfigManager::BROADCAST_BANISHMENTS)) { + std::ostringstream ss; + + if (isNotation) { + ss << targetPlayerName << " has received a notation by " << player->getName() << " (" << (3 - IOBan::getInstance()->getNotationsCount(account.id)) << " more to ban)."; + } else { + ss << player->getName() << " has taken the action \"" << getAction(action, IPBanishment) << "\" against: " << targetPlayerName << " (Warnings: " << account.warnings << "), with reason: \"" << getReason(reason) << "\", and comment: \"" << banComment << "\"."; + } + + broadcastMessage(ss.str(), MSG_STATUS_WARNING); + } else { + std::ostringstream ss; + + if (isNotation) { + ss << "You have taken the action notation against " << targetPlayerName << " (" << (3 - IOBan::getInstance()->getNotationsCount(account.id)) << " more to ban)."; + } else { + ss << "You have taken the action \"" << getAction(action, IPBanishment) << "\" against: " << targetPlayerName << " (Warnings: " << account.warnings << "), with reason: \"" << getReason(reason) << "\", and comment: \"" << banComment << "\"."; + } + + player->sendTextMessage(MSG_STATUS_CONSOLE_RED, ss.str()); + } + + if (targetPlayer) { + if (IPBanishment) { + uint32_t ip = targetPlayer->lastIP; + + if (ip > 0) { + IOBan::getInstance()->addIpBan(ip, 0xFFFFFFFF, (time(NULL) + 86400)); + } + } + + if (!isNotation) { + addMagicEffect(targetPlayer->getPosition(), NM_ME_MAGIC_POISON); + + uint32_t playerId = targetPlayer->getID(); + g_scheduler.addEvent(createSchedulerTask(1000, boost::bind(&Game::kickPlayer, this, playerId, false))); + } + } else if (IPBanishment) { + uint32_t lastip = IOLoginData::getInstance()->getLastIPByName(targetPlayerName); + + if (lastip != 0) { + IOBan::getInstance()->addIpBan(lastip, 0xFFFFFFFF, (time(NULL) + 86400)); + } + } + + if (!isNotation) { + IOBan::getInstance()->removeAccountNotations(account.id); + } + + IOLoginData::getInstance()->saveAccount(account); + return true; +} + +void Game::sendOfflineTrainingDialog(Player* player) +{ + if (!player) { + return; + } + + if (!player->hasModalWindowOpen(offlineTrainingWindow->getID())) { + player->sendModalWindow(*offlineTrainingWindow); + } +} + +bool Game::playerAnswerModalWindow(uint32_t playerId, uint32_t modalWindowId, uint8_t button, uint8_t choice) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + if (!player->hasModalWindowOpen(modalWindowId)) { + return false; + } + + player->onModalWindowHandled(modalWindowId); + + // offline training, hardcoded + if (modalWindowId == 0xFFFFFFFF) { + if (button == 1) { + if (choice == SKILL_SWORD || choice == SKILL_AXE || choice == SKILL_CLUB || choice == SKILL_DIST || choice == SKILL__MAGLEVEL) { + BedItem* bedItem = player->getBedItem(); + + if (bedItem && bedItem->sleep(player)) { + player->setOfflineTrainingSkill(choice); + return true; + } + } + } else { + player->sendTextMessage(MSG_EVENT_ADVANCE, "Offline training aborted."); + } + + player->setBedItem(NULL); + } + + return true; +} + +bool Game::playerCancelWalk(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + + if (!player || player->isRemoved()) { + return false; + } + + player->sendCancelWalk(); + return true; +} + +void Game::addPlayerToNameMap(Player* player) +{ + const std::string& lowercase_name = asLowerCaseString(player->getName()); + mappedPlayerNames[lowercase_name] = player; + wildcardTree->insert(lowercase_name); +} + +void Game::removePlayerFromNameMap(Player* player) +{ + const std::string& lowercase_name = asLowerCaseString(player->getName()); + mappedPlayerNames.erase(lowercase_name); + wildcardTree->remove(lowercase_name); +} + +Guild* Game::getGuild(uint32_t id) const +{ + OTSERV_HASH_MAP::const_iterator it = guilds.find(id); + + if (it == guilds.end()) { + return NULL; + } + + return it->second; +} + +void Game::addGuild(Guild* guild) +{ + guilds[guild->getId()] = guild; +} + +void Game::decreaseBrowseFieldRef(const Position& pos) +{ + Tile* tile = getTile(pos); + + if (!tile) { + return; + } + + BrowseFieldMap::const_iterator it = browseFields.find(tile); + + if (it != browseFields.end()) { + it->second->releaseThing2(); + } +} diff --git a/src/game.h b/src/game.h new file mode 100644 index 0000000000..f9fbdbeac7 --- /dev/null +++ b/src/game.h @@ -0,0 +1,663 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_GAME_H__ +#define __OTSERV_GAME_H__ + +#include +#include +#include + +#include "map.h" +#include "position.h" +#include "item.h" +#include "container.h" +#include "player.h" +#include "spawn.h" +#include "templates.h" +#include "scheduler.h" +#include "npc.h" +#include "iologindata.h" +#include "modalwindow.h" +#include "wildcardtree.h" + +class ServiceManager; +class Creature; +class Monster; +class Npc; +class CombatInfo; +class Commands; + +enum stackPosType_t { + STACKPOS_NORMAL, + STACKPOS_MOVE, + STACKPOS_LOOK, + STACKPOS_USE, + STACKPOS_USEITEM +}; + +enum WorldType_t { + WORLD_TYPE_NO_PVP = 1, + WORLD_TYPE_PVP = 2, + WORLD_TYPE_PVP_ENFORCED = 3 +}; + +enum GameState_t { + GAME_STATE_STARTUP, + GAME_STATE_INIT, + GAME_STATE_NORMAL, + GAME_STATE_CLOSED, + GAME_STATE_SHUTDOWN, + GAME_STATE_CLOSING, + GAME_STATE_MAINTAIN +}; + +enum LightState_t { + LIGHT_STATE_DAY, + LIGHT_STATE_NIGHT, + LIGHT_STATE_SUNSET, + LIGHT_STATE_SUNRISE +}; + +#define EVENT_LIGHTINTERVAL 10000 +#define EVENT_DECAYINTERVAL 250 +#define EVENT_DECAY_BUCKETS 4 +#define STATE_TIME 1000 + +typedef std::map StageList; +typedef OTSERV_HASH_MAP PlayerNameMap; + +/** + * Main Game class. + * This class is responsible to control everything that happens + */ + +class Game +{ + public: + Game(); + ~Game(); + + void start(ServiceManager* servicer); + + void forceAddCondition(uint32_t creatureId, Condition* condition); + void forceRemoveCondition(uint32_t creatureId, ConditionType_t type); + + void autoSave(); + void prepareServerSave(); + void serverSave(); + + /** + * Load a map. + * \param filename Mapfile to load + * \returns int32_t 0 built-in spawns, 1 needs xml spawns, 2 needs sql spawns, -1 if got error + */ + int32_t loadMap(const std::string& filename); + + /** + * Get the map size - info purpose only + * \param width width of the map + * \param height height of the map + */ + void getMapDimensions(uint32_t& width, uint32_t& height) const { + width = map->mapWidth; + height = map->mapHeight; + } + + void setWorldType(WorldType_t type); + WorldType_t getWorldType() const { + return worldType; + } + int32_t getInFightTicks() const { + return inFightTicks; + } + + Cylinder* internalGetCylinder(Player* player, const Position& pos); + Thing* internalGetThing(Player* player, const Position& pos, int32_t index, + uint32_t spriteId = 0, stackPosType_t type = STACKPOS_NORMAL); + void internalGetPosition(Item* item, Position& pos, uint8_t& stackpos); + + std::string getTradeErrorDescription(ReturnValue ret, Item* item); + + bool violationWindow(Player* player, std::string targetPlayerName, int32_t reason, int32_t action, const std::string& banComment, bool IPBanishment); + + /** + * Get a single tile of the map. + * \returns A pointer to the tile + */ + Tile* getTile(int32_t x, int32_t y, int32_t z); + Tile* getTile(const Position& pos); + + /** + * Set a single tile of the map, position is read from this tile + */ + void setTile(Tile* newTile); + + /** + * Get a leaf of the map. + * \returns A pointer to a leaf + */ + QTreeLeafNode* getLeaf(uint32_t x, uint32_t y); + + /** + * Returns a creature based on the unique creature identifier + * \param id is the unique creature id to get a creature pointer to + * \returns A Creature pointer to the creature + */ + Creature* getCreatureByID(uint32_t id); + + /** + * Returns a player based on the unique creature identifier + * \param id is the unique player id to get a player pointer to + * \returns A Pointer to the player + */ + Player* getPlayerByID(uint32_t id); + + /** + * Returns a creature based on a string name identifier + * \param s is the name identifier + * \returns A Pointer to the creature + */ + Creature* getCreatureByName(const std::string& s); + + /** + * Returns a player based on a string name identifier + * \param s is the name identifier + * \returns A Pointer to the player + */ + Player* getPlayerByName(const std::string& s); + + /** + * Returns a player based on guid + * \param guid + * \returns A Pointer to the player + */ + Player* getPlayerByGUID(const uint32_t& guid); + + /** + * Returns a player based on a string name identifier, with support for the "~" wildcard. + * \param s is the name identifier, with or without wildcard + * \param player will point to the found player (if any) + * \return "RET_PLAYERWITHTHISNAMEISNOTONLINE" or "RET_NAMEISTOOAMBIGIOUS" + */ + ReturnValue getPlayerByNameWildcard(const std::string& s, Player*& player); + + /** + * Returns a player based on an account number identifier + * \param acc is the account identifier + * \returns A Pointer to the player + */ + Player* getPlayerByAccount(uint32_t acc); + + /** + * Returns all players based on their account number identifier + * \param acc is the account identifier + * \return A vector of all players with the selected account number + */ + PlayerVector getPlayersByAccount(uint32_t acc); + + /** + * Returns all players with a certain IP address + * \param ip is the IP address of the clients, as an unsigned long + * \param mask An IP mask, default 255.255.255.255 + * \return A vector of all players with the selected IP + */ + PlayerVector getPlayersByIP(uint32_t ip, uint32_t mask = 0xFFFFFFFF); + + /* Place Creature on the map without sending out events to the surrounding. + * \param creature Creature to place on the map + * \param pos The position to place the creature + * \param extendedPos If true, the creature will in first-hand be placed 2 tiles away + * \param forced If true, placing the creature will not fail because of obstacles (creatures/items) + */ + bool internalPlaceCreature(Creature* creature, const Position& pos, bool extendedPos = false, bool forced = false); + + /** + * Place Creature on the map. + * \param creature Creature to place on the map + * \param pos The position to place the creature + * \param extendedPos If true, the creature will in first-hand be placed 2 tiles away + * \param forced If true, placing the creature will not fail because of obstacles (creatures/items) + */ + bool placeCreature(Creature* creature, const Position& pos, bool extendedPos = false, bool force = false); + + /** + * Remove Creature from the map. + * Removes the Creature the map + * \param c Creature to remove + */ + bool removeCreature(Creature* creature, bool isLogout = true); + + void addCreatureCheck(Creature* creature); + void removeCreatureCheck(Creature* creature); + + uint32_t getPlayersOnline() { + return (uint32_t)Player::listPlayer.list.size(); + } + uint32_t getMonstersOnline() { + return (uint32_t)Monster::listMonster.list.size(); + } + uint32_t getNpcsOnline() { + return (uint32_t)Npc::listNpc.list.size(); + } + uint32_t getCreaturesOnline() { + return (uint32_t)listCreature.list.size(); + } + uint32_t getLastPlayersRecord() const { + return lastPlayersRecord; + } + + void getWorldLightInfo(LightInfo& lightInfo) const; + + void getSpectators(SpectatorVec& list, const Position& centerPos, bool multifloor = false, bool onlyPlayers = false, + int32_t minRangeX = 0, int32_t maxRangeX = 0, + int32_t minRangeY = 0, int32_t maxRangeY = 0) { + map->getSpectators(list, centerPos, multifloor, onlyPlayers, minRangeX, maxRangeX, minRangeY, maxRangeY); + } + + const SpectatorVec& getSpectators(const Position& centerPos) { + return map->getSpectators(centerPos); + } + + void clearSpectatorCache() { + if (map) { + map->clearSpectatorCache(); + } + } + + ReturnValue internalMoveCreature(Creature* creature, Direction direction, uint32_t flags = 0); + ReturnValue internalMoveCreature(Creature* creature, Cylinder* fromCylinder, Cylinder* toCylinder, uint32_t flags = 0); + + ReturnValue internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, int32_t index, + Item* item, uint32_t count, Item** _moveItem, uint32_t flags = 0, Creature* actor = NULL); + + ReturnValue internalMoveTradeItem(Cylinder* fromCylinder, Cylinder* toCylinder, int32_t index, + Item* item, Item* tradeItem, uint32_t count, Item** _moveItem, uint32_t flags = 0, Creature* actor = NULL); + + ReturnValue internalAddItem(Cylinder* toCylinder, Item* item, int32_t index = INDEX_WHEREEVER, + uint32_t flags = 0, bool test = false); + ReturnValue internalAddItem(Cylinder* toCylinder, Item* item, int32_t index, + uint32_t flags, bool test, uint32_t& remainderCount); + ReturnValue internalRemoveItem(Item* item, int32_t count = -1, bool test = false, uint32_t flags = 0); + + ReturnValue internalPlayerAddItem(Player* player, Item* item, bool dropOnMap = true, slots_t slot = SLOT_WHEREEVER); + + /** + * Find an item of a certain type + * \param cylinder to search the item + * \param itemId is the item to remove + * \param subType is the extra type an item can have such as charges/fluidtype, default is -1 + * meaning it's not used + * \param depthSearch if true it will check child containers aswell + * \returns A pointer to the item to an item and NULL if not found + */ + Item* findItemOfType(Cylinder* cylinder, uint16_t itemId, + bool depthSearch = true, int32_t subType = -1); + + /** + * Remove item(s) of a certain type + * \param cylinder to remove the item(s) from + * \param itemId is the item to remove + * \param count is the amount to remove + * \param subType is the extra type an item can have such as charges/fluidtype, default is -1 + * meaning it's not used + * \param onlySubContainers if true it will remove only items from containers in cylinder, default is false + * \returns true if the removal was successful + */ + bool removeItemOfType(Cylinder* cylinder, uint16_t itemId, int32_t count, int32_t subType = -1, bool onlySubContainers = false); + + /** + * Get the amount of money in a a cylinder + * \returns the amount of money found + */ + uint64_t getMoney(const Cylinder* cylinder); + + /** + * Remove/Add item(s) with a monetary value + * \param cylinder to remove the money from + * \param money is the amount to remove + * \param flags optional flags to modifiy the default behaviour + * \returns true if the removal was successful + */ + bool removeMoney(Cylinder* cylinder, uint64_t money, uint32_t flags = 0); + + /** + * Add item(s) with monetary value + * \param cylinder which will receive money + * \param money the amount to give + * \param flags optional flags to modify default behavior + */ + void addMoney(Cylinder* cylinder, uint64_t money, uint32_t flags = 0); + + /** + * Transform one item to another type/count + * \param item is the item to transform + * \param newId is the new itemid + * \param newCount is the new count value, use default value (-1) to not change it + * \returns true if the tranformation was successful + */ + Item* transformItem(Item* item, uint16_t newId, int32_t newCount = -1); + + /** + * Teleports an object to another position + * \param thing is the object to teleport + * \param newPos is the new position + * \param pushMove force teleport if false + * \param flags optional flags to modify default behavior + * \returns true if the teleportation was successful + */ + ReturnValue internalTeleport(Thing* thing, const Position& newPos, bool pushMove = true, uint32_t flags = 0); + + /** + * Turn a creature to a different direction. + * \param creature Creature to change the direction + * \param dir Direction to turn to + */ + bool internalCreatureTurn(Creature* creature, Direction dir); + + /** + * Creature wants to say something. + * \param creature Creature pointer + * \param type Type of message + * \param text The text to say + */ + bool internalCreatureSay(Creature* creature, SpeakClasses type, const std::string& text, + bool ghostMode, SpectatorVec* listPtr = NULL, Position* pos = NULL); + + Position getClosestFreeTile(Player* player, Creature* teleportedCreature, const Position& toPos, bool toCreature); + + int32_t getMotdNum(); + void loadMotd(); + void loadPlayersRecord(); + void checkPlayersRecord(); + + void sendGuildMotd(uint32_t playerId, uint32_t guildId); + void kickPlayer(uint32_t playerId, bool displayEffect); + bool playerReportBug(uint32_t playerId, const std::string& bug); + bool playerDebugAssert(uint32_t playerId, const std::string& assertLine, const std::string& date, const std::string& description, const std::string& comment); + bool playerAnswerModalWindow(uint32_t playerId, uint32_t modalWindowId, uint8_t button, uint8_t choice); + bool playerCancelWalk(uint32_t playerId); + + bool internalStartTrade(Player* player, Player* partner, Item* tradeItem); + bool internalCloseTrade(Player* player); + bool playerBroadcastMessage(Player* player, const std::string& text); + bool broadcastMessage(const std::string& text, MessageClasses type); + + //Implementation of player invoked events + bool playerMoveThing(uint32_t playerId, const Position& fromPos, uint16_t spriteId, uint8_t fromStackPos, + const Position& toPos, uint8_t count); + bool playerMoveCreature(uint32_t playerId, uint32_t movingCreatureId, + const Position& movingCreatureOrigPos, const Position& toPos); + bool playerMoveItem(uint32_t playerId, const Position& fromPos, + uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count); + bool playerMove(uint32_t playerId, Direction direction); + bool playerCreatePrivateChannel(uint32_t playerId); + bool playerChannelInvite(uint32_t playerId, const std::string& name); + bool playerChannelExclude(uint32_t playerId, const std::string& name); + bool playerRequestChannels(uint32_t playerId); + bool playerOpenChannel(uint32_t playerId, uint16_t channelId); + bool playerCloseChannel(uint32_t playerId, uint16_t channelId); + bool playerOpenPrivateChannel(uint32_t playerId, std::string& receiver); + bool playerCloseNpcChannel(uint32_t playerId); + bool playerReceivePing(uint32_t playerId); + bool playerRemoveLagging(uint32_t playerId); + bool playerReceivePingBack(uint32_t playerId); + bool playerAutoWalk(uint32_t playerId, std::list& listDir); + bool playerStopAutoWalk(uint32_t playerId); + bool playerUseItemEx(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos, + uint16_t fromSpriteId, const Position& toPos, uint8_t toStackPos, uint16_t toSpriteId, bool isHotkey); + bool playerUseItem(uint32_t playerId, const Position& pos, uint8_t stackPos, + uint8_t index, uint16_t spriteId, bool isHotkey); + bool playerUseWithCreature(uint32_t playerId, const Position& fromPos, + uint8_t fromStackPos, uint32_t creatureId, uint16_t spriteId, bool isHotkey); + bool playerCloseContainer(uint32_t playerId, uint8_t cid); + bool playerMoveUpContainer(uint32_t playerId, uint8_t cid); + bool playerUpdateContainer(uint32_t playerId, uint8_t cid); + bool playerUpdateTile(uint32_t playerId, const Position& pos); + bool playerRotateItem(uint32_t playerId, const Position& pos, uint8_t stackPos, const uint16_t spriteId); + bool playerWriteItem(uint32_t playerId, uint32_t windowTextId, const std::string& text); + bool playerBrowseField(uint32_t playerId, const Position& pos); + bool playerSeekInContainer(uint32_t playerId, uint8_t containerId, uint16_t index); + bool playerUpdateHouseWindow(uint32_t playerId, uint8_t listId, uint32_t windowTextId, const std::string& text); + bool playerRequestTrade(uint32_t playerId, const Position& pos, uint8_t stackPos, + uint32_t tradePlayerId, uint16_t spriteId); + bool playerAcceptTrade(uint32_t playerId); + bool playerLookInTrade(uint32_t playerId, bool lookAtCounterOffer, int index); + bool playerPurchaseItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount, + bool ignoreCap = false, bool inBackpacks = false); + bool playerSellItem(uint32_t playerId, uint16_t spriteId, uint8_t count, + uint8_t amount, bool ignoreEquipped = false); + bool playerCloseShop(uint32_t playerId); + bool playerLookInShop(uint32_t playerId, uint16_t spriteId, uint8_t count); + bool playerCloseTrade(uint32_t playerId); + bool playerSetAttackedCreature(uint32_t playerId, uint32_t creatureId); + bool playerFollowCreature(uint32_t playerId, uint32_t creatureId); + bool playerCancelAttackAndFollow(uint32_t playerId); + bool playerSetFightModes(uint32_t playerId, fightMode_t fightMode, chaseMode_t chaseMode, secureMode_t secureMode); + bool playerLookAt(uint32_t playerId, const Position& pos, uint16_t spriteId, uint8_t stackPos); + bool playerLookInBattleList(uint32_t playerId, uint32_t creatureId); + bool playerRequestAddVip(uint32_t playerId, const std::string& name); + bool playerRequestRemoveVip(uint32_t playerId, uint32_t guid); + bool playerRequestEditVip(uint32_t playerId, uint32_t guid, const std::string& description, uint32_t icon, bool notify); + bool playerTurn(uint32_t playerId, Direction dir); + bool playerRequestOutfit(uint32_t playerId); + bool playerShowQuestLog(uint32_t playerId); + bool playerShowQuestLine(uint32_t playerId, uint16_t questId); + bool playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type, + const std::string& receiver, const std::string& text); + bool playerChangeOutfit(uint32_t playerId, Outfit_t outfit); + bool playerInviteToParty(uint32_t playerId, uint32_t invitedId); + bool playerJoinParty(uint32_t playerId, uint32_t leaderId); + bool playerRevokePartyInvitation(uint32_t playerId, uint32_t invitedId); + bool playerPassPartyLeadership(uint32_t playerId, uint32_t newLeaderId); + bool playerLeaveParty(uint32_t playerId); + bool playerEnableSharedPartyExperience(uint32_t playerId, bool sharedExpActive); + bool playerToggleMount(uint32_t playerId, bool mount); + bool playerLeaveMarket(uint32_t playerId); + bool playerBrowseMarket(uint32_t playerId, uint16_t spriteId); + bool playerBrowseMarketOwnOffers(uint32_t playerId); + bool playerBrowseMarketOwnHistory(uint32_t playerId); + bool playerCreateMarketOffer(uint32_t playerId, uint8_t type, uint16_t spriteId, uint16_t amount, uint32_t price, bool anonymous); + bool playerCancelMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter); + bool playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter, uint16_t amount); + void checkExpiredMarketOffers(); + + void updatePremium(Account& account); + + void cleanup(); + void shutdown(); + void FreeThing(Thing* thing); + + bool canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight = true, + int32_t rangex = Map::maxClientViewportX, int32_t rangey = Map::maxClientViewportY); + bool isSightClear(const Position& fromPos, const Position& toPos, bool sameFloor); + + bool getPathTo(const Creature* creature, const Position& destPos, + std::list& listDir, int32_t maxSearchDist /*= -1*/); + + bool getPathToEx(const Creature* creature, const Position& targetPos, std::list& dirList, + const FindPathParams& fpp); + + bool getPathToEx(const Creature* creature, const Position& targetPos, std::list& dirList, + uint32_t minTargetDist, uint32_t maxTargetDist, bool fullPathSearch = true, + bool clearSight = true, int32_t maxSearchDist = -1); + + void changeSpeed(Creature* creature, int32_t varSpeedDelta); + void internalCreatureChangeOutfit(Creature* creature, const Outfit_t& oufit); + void internalCreatureChangeVisible(Creature* creature, bool visible); + void changeLight(const Creature* creature); + void updatePlayerSkull(Player* player); + void updatePlayerShield(Player* player); + void updatePlayerHelpers(Player* player); + void updateCreatureType(Creature* creature); + void updateCreatureWalkthrough(Creature* creature); + + void sendPublicSquare(Player* sender, SquareColor_t color); + + GameState_t getGameState() const; + void setGameState(GameState_t newState); + void saveGameState(); + void loadGameState(); + void refreshMap(); + void cleanMap() { + map->clean(); + } + + //Events + void checkCreatureWalk(uint32_t creatureId); + void updateCreatureWalk(uint32_t creatureId); + void checkCreatureAttack(uint32_t creatureId); + void checkCreatures(); + void checkLight(); + + bool combatBlockHit(CombatType_t combatType, Creature* attacker, Creature* target, + int32_t& healthChange, bool checkDefense, bool checkArmor); + + bool combatChangeHealth(CombatType_t combatType, Creature* attacker, Creature* target, int32_t healthChange); + bool combatChangeMana(Creature* attacker, Creature* target, int32_t manaChange); + + //animation help functions + void addCreatureHealth(const Creature* target); + void addCreatureHealth(const SpectatorVec& list, const Creature* target); + void addMagicEffect(const Position& pos, uint8_t effect, bool ghostMode = false); + void addMagicEffect(const SpectatorVec& list, const Position& pos, uint8_t effect, bool ghostMode = false); + void addDistanceEffect(const Position& fromPos, const Position& toPos, + uint8_t effect); + + Map* getMap() { + return map; + } + const Map* getMap() const { + return map; + } + + int64_t getStateTime() const { + return stateTime; + } + void setStateTime(int64_t _stateTime) { + stateTime = _stateTime; + } + + void addCommandTag(const std::string& tag); + void resetCommandTag(); + + void startDecay(Item* item); + int32_t getLightHour() const { + return lightHour; + } + bool npcSpeakToPlayer(Npc* npc, Player* player, const std::string& text, bool publicize); + + bool loadExperienceStages(); + uint64_t getExperienceStage(uint32_t level); + + void setServerSaveMessage(int16_t key, bool value) { + serverSaveMessage[key] = value; + } + bool getServerSaveMessage(int16_t key) const { + return serverSaveMessage[key]; + } + + void sendOfflineTrainingDialog(Player* player); + + void addPlayerToNameMap(Player* player); + void removePlayerFromNameMap(Player* player); + + Guild* getGuild(uint32_t id) const; + void addGuild(Guild* guild); + void decreaseBrowseFieldRef(const Position& pos); + + typedef OTSERV_HASH_MAP BrowseFieldMap; + BrowseFieldMap browseFields; + + protected: + bool playerSayCommand(Player* player, SpeakClasses type, const std::string& text); + bool playerSaySpell(Player* player, SpeakClasses type, const std::string& text); + bool playerWhisper(Player* player, const std::string& text); + bool playerYell(Player* player, const std::string& text); + bool playerSpeakTo(Player* player, SpeakClasses type, const std::string& receiver, const std::string& text); + bool playerTalkToChannel(Player* player, SpeakClasses type, const std::string& text, uint16_t channelId); + bool playerSpeakToNpc(Player* player, const std::string& text); + + bool serverSaveMessage[3]; + int64_t stateTime; + + std::vector ToReleaseThings; + + uint32_t checkLightEvent; + uint32_t checkCreatureEvent; + uint32_t checkDecayEvent; + + //list of items that are in trading state, mapped to the player + std::map tradeItems; + + AutoList listCreature; + + size_t checkCreatureLastIndex; + std::vector checkCreatureVectors[EVENT_CREATURECOUNT]; + std::vector toAddCheckCreatureVector; + + struct GameEvent { + int64_t tick; + int type; + void* data; + }; + + void checkDecay(); + void internalDecayItem(Item* item); + + typedef std::list DecayList; + DecayList decayItems[EVENT_DECAY_BUCKETS]; + DecayList toDecayItems; + size_t lastBucket; + + static const int32_t LIGHT_LEVEL_DAY = 250; + static const int32_t LIGHT_LEVEL_NIGHT = 40; + static const int32_t SUNSET = 1305; + static const int32_t SUNRISE = 430; + int32_t lightLevel; + LightState_t lightState; + int32_t lightHour; + int32_t lightHourDelta; + + uint32_t inFightTicks; + + GameState_t gameState; + WorldType_t worldType; + + ServiceManager* services; + Map* map; + + void savePlayersRecord(); + std::string lastMotdText; + int32_t lastMotdNum; + uint32_t lastPlayersRecord; + + StageList stages; + bool stagesEnabled; + uint32_t lastStageLevel; + bool useLastStageLevel; + + PlayerNameMap mappedPlayerNames; + WildcardTreeNode* wildcardTree; + + ModalWindow* offlineTrainingWindow; + + OTSERV_HASH_MAP guilds; + std::vector commandTags; +}; +#endif diff --git a/src/globalevent.cpp b/src/globalevent.cpp new file mode 100644 index 0000000000..896b337486 --- /dev/null +++ b/src/globalevent.cpp @@ -0,0 +1,367 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "globalevent.h" +#include "tools.h" +#include "player.h" +#include "scheduler.h" + +#include + +GlobalEvents::GlobalEvents() : + m_scriptInterface("GlobalEvent Interface") +{ + m_scriptInterface.initState(); + thinkEventId = 0; + timerEventId = 0; +} + +GlobalEvents::~GlobalEvents() +{ + clear(); +} + +void GlobalEvents::clearMap(GlobalEventMap& map) +{ + for (GlobalEventMap::iterator it = map.begin(); it != map.end(); ++it) { + delete it->second; + } + + map.clear(); +} + +void GlobalEvents::clear() +{ + g_scheduler.stopEvent(thinkEventId); + thinkEventId = 0; + g_scheduler.stopEvent(timerEventId); + timerEventId = 0; + + clearMap(thinkMap); + clearMap(serverMap); + clearMap(timerMap); + + m_scriptInterface.reInitState(); +} + +Event* GlobalEvents::getEvent(const std::string& nodeName) +{ + if (asLowerCaseString(nodeName) == "globalevent") { + return new GlobalEvent(&m_scriptInterface); + } + + return NULL; +} + +bool GlobalEvents::registerEvent(Event* event, xmlNodePtr) +{ + GlobalEvent* globalEvent = dynamic_cast(event); + + if (!globalEvent) { + return false; + } + + if (globalEvent->getEventType() == GLOBALEVENT_TIMER) { + GlobalEventMap::iterator it = timerMap.find(globalEvent->getName()); + + if (it == timerMap.end()) { + timerMap.insert(std::make_pair(globalEvent->getName(), globalEvent)); + + if (timerEventId == 0) { + timerEventId = g_scheduler.addEvent(createSchedulerTask(TIMER_INTERVAL, + boost::bind(&GlobalEvents::timer, this))); + } + + return true; + } + } else if (globalEvent->getEventType() != GLOBALEVENT_NONE) { + GlobalEventMap::iterator it = serverMap.find(globalEvent->getName()); + + if (it == serverMap.end()) { + serverMap.insert(std::make_pair(globalEvent->getName(), globalEvent)); + return true; + } + } else { // think event + GlobalEventMap::iterator it = thinkMap.find(globalEvent->getName()); + + if (it == thinkMap.end()) { + thinkMap.insert(std::make_pair(globalEvent->getName(), globalEvent)); + + if (thinkEventId == 0) { + thinkEventId = g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, + boost::bind(&GlobalEvents::think, this))); + } + + return true; + } + } + + std::cout << "[Warning - GlobalEvents::configureEvent] Duplicate registered globalevent with name: " << globalEvent->getName() << std::endl; + return false; +} + +void GlobalEvents::startup() +{ + execute(GLOBALEVENT_STARTUP); +} + +void GlobalEvents::timer() +{ + time_t now = time(NULL); + + for (GlobalEventMap::iterator it = timerMap.begin(), end = timerMap.end(); it != end; ++it) { + GlobalEvent* globalEvent = it->second; + + if (globalEvent->getNextExecution() > now) { + continue; + } + + globalEvent->setNextExecution(globalEvent->getNextExecution() + 86400); + + if (!globalEvent->executeEvent()) { + std::cout << "[Error - GlobalEvents::timer] Failed to execute event: " << globalEvent->getName() << std::endl; + } + } + + g_scheduler.addEvent(createSchedulerTask(TIMER_INTERVAL, + boost::bind(&GlobalEvents::timer, this))); +} + +void GlobalEvents::think() +{ + int64_t now = OTSYS_TIME(); + + for (GlobalEventMap::iterator it = thinkMap.begin(); it != thinkMap.end(); ++it) { + GlobalEvent* globalEvent = it->second; + + if (globalEvent->getNextExecution() > now) { + continue; + } + + globalEvent->setNextExecution(globalEvent->getNextExecution() + now); + + if (!globalEvent->executeEvent()) { + std::cout << "[Error - GlobalEvents::think] Failed to execute event: " << globalEvent->getName() << std::endl; + } + } + + g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, + boost::bind(&GlobalEvents::think, this))); +} + +void GlobalEvents::execute(GlobalEvent_t type) +{ + for (GlobalEventMap::iterator it = serverMap.begin(); it != serverMap.end(); ++it) { + GlobalEvent* globalEvent = it->second; + + if (globalEvent->getEventType() == type) { + globalEvent->executeEvent(); + } + } +} + +GlobalEventMap GlobalEvents::getEventMap(GlobalEvent_t type) +{ + switch (type) { + case GLOBALEVENT_NONE: + return thinkMap; + + case GLOBALEVENT_TIMER: + return timerMap; + + case GLOBALEVENT_STARTUP: + case GLOBALEVENT_SHUTDOWN: + case GLOBALEVENT_RECORD: { + GlobalEventMap retMap; + + for (GlobalEventMap::iterator it = serverMap.begin(); it != serverMap.end(); ++it) { + if (it->second->getEventType() == type) { + retMap[it->first] = it->second; + } + } + + return retMap; + } + + default: + break; + } + + return GlobalEventMap(); +} + +GlobalEvent::GlobalEvent(LuaScriptInterface* _interface): + Event(_interface) +{ + m_nextExecution = 0; + m_interval = 0; +} + +bool GlobalEvent::configureEvent(xmlNodePtr p) +{ + std::string strValue; + + if (!readXMLString(p, "name", strValue)) { + std::cout << "[Error - GlobalEvent::configureEvent] No name for a globalevent." << std::endl; + return false; + } + + m_name = strValue; + m_eventType = GLOBALEVENT_NONE; + + if (readXMLString(p, "type", strValue)) { + std::string tmpStrValue = asLowerCaseString(strValue); + + if (tmpStrValue == "startup" || tmpStrValue == "start" || tmpStrValue == "load") { + m_eventType = GLOBALEVENT_STARTUP; + } else if (tmpStrValue == "shutdown" || tmpStrValue == "quit" || tmpStrValue == "exit") { + m_eventType = GLOBALEVENT_SHUTDOWN; + } else if (tmpStrValue == "record" || tmpStrValue == "playersrecord") { + m_eventType = GLOBALEVENT_RECORD; + } else { + std::cout << "[Error - GlobalEvent::configureEvent] No valid type \"" << strValue << "\" for globalevent with name " << m_name << std::endl; + return false; + } + + return true; + } else if (readXMLString(p, "time", strValue) || readXMLString(p, "at", strValue)) { + std::vector params = vectorAtoi(explodeString(strValue, ":")); + + if (params[0] < 0 || params[0] > 23) { + std::cout << "[Error - GlobalEvent::configureEvent] No valid hour \"" << strValue << "\" for globalevent with name " << m_name << std::endl; + return false; + } + + m_interval |= params[0] << 16; + int32_t hour = params[0]; + int32_t min = 0; + int32_t sec = 0; + + if (params.size() > 1) { + if (params[1] < 0 || params[1] > 59) { + std::cout << "[Error - GlobalEvent::configureEvent] No valid minute \"" << strValue << "\" for globalevent with name " << m_name << std::endl; + return false; + } + + min = params[1]; + + if (params.size() > 2) { + if (params[2] < 0 || params[2] > 59) { + std::cout << "[Error - GlobalEvent::configureEvent] No valid second \"" << strValue << "\" for globalevent with name " << m_name << std::endl; + return false; + } + + sec = params[2]; + } + } + + time_t current_time = time(NULL); + tm* timeinfo = localtime(¤t_time); + timeinfo->tm_hour = hour; + timeinfo->tm_min = min; + timeinfo->tm_sec = sec; + time_t difference = (time_t)difftime(mktime(timeinfo), current_time); + + if (difference < 0) { + difference += 86400; + } + + m_nextExecution = current_time + difference; + m_eventType = GLOBALEVENT_TIMER; + return true; + } + + int32_t intValue; + + if (readXMLInteger(p, "interval", intValue)) { + m_interval = std::max(SCHEDULER_MINTICKS, intValue); + m_nextExecution = OTSYS_TIME() + m_interval; + return true; + } + + std::cout << "[Error - GlobalEvent::configureEvent] No interval for globalevent with name " << m_name << std::endl; + return false; +} + +std::string GlobalEvent::getScriptEventName() +{ + switch (m_eventType) { + case GLOBALEVENT_STARTUP: + return "onStartup"; + case GLOBALEVENT_SHUTDOWN: + return "onShutdown"; + case GLOBALEVENT_RECORD: + return "onRecord"; + case GLOBALEVENT_TIMER: + return "onTime"; + default: + break; + } + + return "onThink"; +} + +uint32_t GlobalEvent::executeRecord(uint32_t current, uint32_t old) +{ + //onRecord(current, old, cid) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + env->setScriptId(m_scriptId, m_scriptInterface); + + lua_State* L = m_scriptInterface->getLuaState(); + m_scriptInterface->pushFunction(m_scriptId); + lua_pushnumber(L, current); + lua_pushnumber(L, old); + + bool result = m_scriptInterface->callFunction(2); + m_scriptInterface->releaseScriptEnv(); + + return result; + } else { + std::cout << "[Error - GlobalEvent::executeRecord] Call stack overflow." << std::endl; + return 0; + } +} + +uint32_t GlobalEvent::executeEvent() +{ + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + env->setScriptId(m_scriptId, m_scriptInterface); + lua_State* L = m_scriptInterface->getLuaState(); + m_scriptInterface->pushFunction(m_scriptId); + + int32_t params = 0; + + if (m_eventType == GLOBALEVENT_NONE || m_eventType == GLOBALEVENT_TIMER) { + lua_pushnumber(L, m_interval); + params = 1; + } + + bool result = m_scriptInterface->callFunction(params); + m_scriptInterface->releaseScriptEnv(); + return result; + } else { + std::cout << "[Error - GlobalEvent::executeEvent] Call stack overflow." << std::endl; + return 0; + } +} diff --git a/src/globalevent.h b/src/globalevent.h new file mode 100644 index 0000000000..be9704c138 --- /dev/null +++ b/src/globalevent.h @@ -0,0 +1,110 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_GLOBALEVENT_H__ +#define __OTSERV_GLOBALEVENT_H__ +#include "baseevents.h" + +#include "const.h" + +#define TIMER_INTERVAL 1000 + +enum GlobalEvent_t { + GLOBALEVENT_NONE, + GLOBALEVENT_TIMER, + + GLOBALEVENT_STARTUP, + GLOBALEVENT_SHUTDOWN, + GLOBALEVENT_RECORD +}; + +class GlobalEvent; +typedef std::map GlobalEventMap; + +class GlobalEvents : public BaseEvents +{ + public: + GlobalEvents(); + virtual ~GlobalEvents(); + void startup(); + + void timer(); + void think(); + void execute(GlobalEvent_t type); + + GlobalEventMap getEventMap(GlobalEvent_t type); + void clearMap(GlobalEventMap& map); + + protected: + virtual std::string getScriptBaseName() { + return "globalevents"; + } + virtual void clear(); + + virtual Event* getEvent(const std::string& nodeName); + virtual bool registerEvent(Event* event, xmlNodePtr p); + + virtual LuaScriptInterface& getScriptInterface() { + return m_scriptInterface; + } + LuaScriptInterface m_scriptInterface; + + GlobalEventMap thinkMap, serverMap, timerMap; + int32_t thinkEventId, timerEventId; +}; + +class GlobalEvent : public Event +{ + public: + GlobalEvent(LuaScriptInterface* _interface); + virtual ~GlobalEvent() {} + + virtual bool configureEvent(xmlNodePtr p); + + uint32_t executeRecord(uint32_t current, uint32_t old); + uint32_t executeEvent(); + + GlobalEvent_t getEventType() const { + return m_eventType; + } + std::string getName() const { + return m_name; + } + + uint32_t getInterval() const { + return m_interval; + } + + int64_t getNextExecution() const { + return m_nextExecution; + } + void setNextExecution(int64_t time) { + m_nextExecution = time; + } + + protected: + GlobalEvent_t m_eventType; + + virtual std::string getScriptEventName(); + + std::string m_name; + int64_t m_nextExecution; + uint32_t m_interval; +}; + +#endif diff --git a/src/guild.cpp b/src/guild.cpp new file mode 100644 index 0000000000..80e505d9b8 --- /dev/null +++ b/src/guild.cpp @@ -0,0 +1,84 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "guild.h" + +#include "game.h" + +extern Game g_game; + +Guild::Guild(uint32_t id, const std::string& name) +{ + this->id = id; + this->name = name; + this->memberCount = 0; +} + +void Guild::addMember(Player* player) +{ + membersOnline.push_back(player); + + for (PlayerVector::const_iterator it = membersOnline.begin(); it != membersOnline.end(); ++it) { + g_game.updatePlayerHelpers(*it); + } +} + +void Guild::removeMember(Player* player) +{ + for (PlayerVector::iterator it = membersOnline.begin(); it != membersOnline.end(); ++it) { + if (*it == player) { + membersOnline.erase(it); + break; + } + } + + for (PlayerVector::const_iterator it = membersOnline.begin(); it != membersOnline.end(); ++it) { + g_game.updatePlayerHelpers(*it); + } + + g_game.updatePlayerHelpers(player); +} + +GuildRank* Guild::getRankById(uint32_t id) +{ + for (size_t i = 0; i < ranks.size(); ++i) { + if (ranks[i].id == id) { + return &ranks[i]; + } + } + + return NULL; +} + +GuildRank* Guild::getRankByLevel(uint8_t level) +{ + for (size_t i = 0; i < ranks.size(); ++i) { + if (ranks[i].level == level) { + return &ranks[i]; + } + } + + return NULL; +} + +void Guild::addRank(uint32_t id, const std::string& name, uint8_t level) +{ + ranks.push_back(GuildRank(id, name, level)); +} diff --git a/src/guild.h b/src/guild.h new file mode 100644 index 0000000000..dd252d2743 --- /dev/null +++ b/src/guild.h @@ -0,0 +1,87 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __GUILD_H__ +#define __GUILD_H__ + +#include "player.h" + +typedef std::vector PlayerVector; + +struct GuildRank { + uint32_t id; + std::string name; + uint8_t level; + + GuildRank(uint32_t id, const std::string& name, uint8_t level) { + this->id = id; + this->name = name; + this->level = level; + } +}; + +class Guild +{ + public: + Guild(uint32_t id, const std::string& name); + ~Guild() {} + + void addMember(Player* player); + void removeMember(Player* player); + + uint32_t getId() const { + return id; + } + const std::string& getName() const { + return name; + } + const PlayerVector& getMembersOnline() const { + return membersOnline; + } + size_t getMembersOnlineCount() const { + return membersOnline.size(); + } + + uint32_t getMemberCount() const { + return memberCount; + } + void setMemberCount(uint32_t count) { + memberCount = count; + } + + GuildRank* getRankById(uint32_t id); + GuildRank* getRankByLevel(uint8_t level); + void addRank(uint32_t id, const std::string& name, uint8_t level); + + const std::string& getMotd() const { + return motd; + } + void setMotd(const std::string& motd) { + this->motd = motd; + } + + private: + PlayerVector membersOnline; + std::vector ranks; + std::string name; + std::string motd; + uint32_t id; + uint32_t memberCount; +}; + +#endif diff --git a/src/house.cpp b/src/house.cpp new file mode 100644 index 0000000000..b5ff6dd2d9 --- /dev/null +++ b/src/house.cpp @@ -0,0 +1,985 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include + +#include "house.h" +#include "iologindata.h" +#include "game.h" +#include "town.h" +#include "configmanager.h" +#include "tools.h" +#include "beds.h" + +extern ConfigManager g_config; +extern Game g_game; + +House::House(uint32_t _houseid) : + transfer_container(ITEM_LOCKER1) +{ + isLoaded = false; + houseName = "Forgotten headquarter (Flat 1, Area 42)"; + houseOwner = 0; + posEntry.x = 0; + posEntry.y = 0; + posEntry.z = 0; + paidUntil = 0; + houseid = _houseid; + rentWarnings = 0; + rent = 0; + townid = 0; + transferItem = NULL; +} + +void House::addTile(HouseTile* tile) +{ + tile->setFlag(TILESTATE_PROTECTIONZONE); + houseTiles.push_back(tile); +} + +void House::setHouseOwner(uint32_t guid, bool updateDatabase/* = true*/, Player* player/* = NULL*/) +{ + if (updateDatabase) { + Database* db = Database::getInstance(); + + DBQuery query; + query << "UPDATE `houses` SET `owner` = " << guid << " WHERE `id` = " << houseid; + db->executeQuery(query.str()); + } + + if (isLoaded && houseOwner == guid) { + return; + } + + isLoaded = true; + + if (houseOwner) { + //send items to depot + if (player) { + transferToDepot(player); + } else { + transferToDepot(); + } + + PlayerVector toKick; + + for (HouseTileList::iterator it = houseTiles.begin(); it != houseTiles.end(); ++it) { + if (const CreatureVector* creatures = (*it)->getCreatures()) { + for (CreatureVector::const_iterator cit = creatures->begin(), cend = creatures->end(); cit != cend; ++cit) { + Player* player = (*cit)->getPlayer(); + + if (player) { + toKick.push_back(player); + } + } + } + } + + while (!toKick.empty()) { + Player* c = toKick.back(); + toKick.pop_back(); + kickPlayer(NULL, c->getName()); + } + + // we need to remove players from beds + HouseBedItemList::iterator bit; + + for (bit = bedsList.begin(); bit != bedsList.end(); ++bit) { + if ((*bit)->getSleeper() != 0) { + (*bit)->wakeUp(NULL); + } + } + + //clean access lists + houseOwner = 0; + setAccessList(SUBOWNER_LIST, ""); + setAccessList(GUEST_LIST, ""); + + for (HouseDoorList::iterator it = doorList.begin(); it != doorList.end(); ++it) { + (*it)->setAccessList(""); + } + + //reset paid date + paidUntil = 0; + rentWarnings = 0; + } + + std::string name; + + if (guid != 0 && IOLoginData::getInstance()->getNameByGuid(guid, name)) { + houseOwner = guid; + houseOwnerName = name; + } + + updateDoorDescription(); +} + +void House::updateDoorDescription() +{ + std::ostringstream ss; + + if (houseOwner != 0) { + ss << "It belongs to house '" << houseName << "'. " << houseOwnerName << " owns this house."; + } else { + ss << "It belongs to house '" << houseName << "'. Nobody owns this house."; + } + + HouseDoorList::iterator it; + + for (it = doorList.begin(); it != doorList.end(); ++it) { + (*it)->setSpecialDescription(ss.str()); + } +} + +AccessHouseLevel_t House::getHouseAccessLevel(const Player* player) +{ + if (!player) { + return HOUSE_OWNER; + } + + if (player->hasFlag(PlayerFlag_CanEditHouses)) { + return HOUSE_OWNER; + } + + if (player->getGUID() == houseOwner) { + return HOUSE_OWNER; + } + + if (subOwnerList.isInList(player)) { + return HOUSE_SUBOWNER; + } + + if (guestList.isInList(player)) { + return HOUSE_GUEST; + } + + return HOUSE_NO_INVITED; +} + +bool House::kickPlayer(Player* player, const std::string& name) +{ + Player* kickingPlayer = g_game.getPlayerByName(name); + + if (kickingPlayer) { + HouseTile* houseTile = dynamic_cast(kickingPlayer->getTile()); + + if (houseTile && houseTile->getHouse() == this) { + if (getHouseAccessLevel(player) >= getHouseAccessLevel(kickingPlayer) && !kickingPlayer->hasFlag(PlayerFlag_CanEditHouses)) { + Position oldPosition = kickingPlayer->getPosition(); + + if (g_game.internalTeleport(kickingPlayer, getEntryPosition()) == RET_NOERROR) { + g_game.addMagicEffect(oldPosition, NM_ME_POFF); + g_game.addMagicEffect(getEntryPosition(), NM_ME_TELEPORT); + } + + return true; + } + } + } + + return false; +} + +void House::setAccessList(uint32_t listId, const std::string& textlist) +{ + if (listId == GUEST_LIST) { + guestList.parseList(textlist); + } else if (listId == SUBOWNER_LIST) { + subOwnerList.parseList(textlist); + } else { + Door* door = getDoorByNumber(listId); + + if (door) { + door->setAccessList(textlist); + } + + // We dont have kick anyone + return; + } + + //kick uninvited players + typedef std::list KickPlayerList; + KickPlayerList kickList; + HouseTileList::iterator it; + + for (it = houseTiles.begin(); it != houseTiles.end(); ++it) { + HouseTile* hTile = *it; + + if (CreatureVector* creatures = hTile->getCreatures()) { + CreatureVector::iterator cit; + + for (cit = creatures->begin(); cit != creatures->end(); ++cit) { + Player* player = (*cit)->getPlayer(); + + if (player && isInvited(player) == false) { + kickList.push_back(player); + } + } + } + } + + KickPlayerList::iterator itkick; + + for (itkick = kickList.begin(); itkick != kickList.end(); ++itkick) { + if (g_game.internalTeleport(*itkick, getEntryPosition()) == RET_NOERROR) { + g_game.addMagicEffect(getEntryPosition(), NM_ME_TELEPORT); + } + } +} + +bool House::transferToDepot() +{ + if (townid == 0 || houseOwner == 0) { + return false; + } + + std::string ownerName; + + if (!IOLoginData::getInstance()->getNameByGuid(houseOwner, ownerName)) { + return false; + } + + Player* player = g_game.getPlayerByName(ownerName); + + if (!player) { + player = new Player(ownerName, NULL); + + if (!IOLoginData::getInstance()->loadPlayer(player, ownerName)) { + delete player; + return false; + } + } + + transferToDepot(player); + + if (player->isOffline()) { + IOLoginData::getInstance()->savePlayer(player); + delete player; + } + + return true; +} + +bool House::transferToDepot(Player* player) +{ + if (townid == 0 || houseOwner == 0) { + return false; + } + + ItemList moveItemList; + Container* tmpContainer = NULL; + Item* item = NULL; + + for (HouseTileList::iterator it = houseTiles.begin(); it != houseTiles.end(); ++it) { + if (const TileItemVector* items = (*it)->getItemList()) { + for (ItemVector::const_iterator iit = items->begin(), iend = items->end(); iit != iend; ++iit) { + item = (*iit); + + if (item->isPickupable()) { + moveItemList.push_back(item); + } else if ((tmpContainer = item->getContainer())) { + for (ItemDeque::const_iterator cit = tmpContainer->getItems(); cit != tmpContainer->getEnd(); ++cit) { + moveItemList.push_back(*cit); + } + } + } + } + } + + for (ItemList::iterator it = moveItemList.begin(); it != moveItemList.end(); ++it) { + g_game.internalMoveItem((*it)->getParent(), player->getInbox(), INDEX_WHEREEVER, + (*it), (*it)->getItemCount(), NULL, FLAG_NOLIMIT); + } + + return true; +} + +bool House::getAccessList(uint32_t listId, std::string& list) const +{ + if (listId == GUEST_LIST) { + guestList.getList(list); + return true; + } else if (listId == SUBOWNER_LIST) { + subOwnerList.getList(list); + return true; + } + + Door* door = getDoorByNumber(listId); + + if (!door) { + return false; + } + + return door->getAccessList(list); +} + +bool House::isInvited(const Player* player) +{ + return getHouseAccessLevel(player) != HOUSE_NO_INVITED; +} + +void House::addDoor(Door* door) +{ + door->useThing2(); + doorList.push_back(door); + door->setHouse(this); + updateDoorDescription(); +} + +void House::removeDoor(Door* door) +{ + HouseDoorList::iterator it = std::find(doorList.begin(), doorList.end(), door); + + if (it != doorList.end()) { + (*it)->releaseThing2(); + doorList.erase(it); + } +} + +void House::addBed(BedItem* bed) +{ + bedsList.push_back(bed); + bed->setHouse(this); +} + +Door* House::getDoorByNumber(uint32_t doorId) +{ + HouseDoorList::iterator it; + + for (it = doorList.begin(); it != doorList.end(); ++it) { + if ((*it)->getDoorId() == doorId) { + return *it; + } + } + + return NULL; +} + +Door* House::getDoorByNumber(uint32_t doorId) const +{ + HouseDoorList::const_iterator it; + + for (it = doorList.begin(); it != doorList.end(); ++it) { + if ((*it)->getDoorId() == doorId) { + return *it; + } + } + + return NULL; +} + +Door* House::getDoorByPosition(const Position& pos) +{ + for (HouseDoorList::iterator it = doorList.begin(); it != doorList.end(); ++it) { + if ((*it)->getPosition() == pos) { + return *it; + } + } + + return NULL; +} + +bool House::canEditAccessList(uint32_t listId, const Player* player) +{ + switch (getHouseAccessLevel(player)) { + case HOUSE_OWNER: + return true; + + case HOUSE_SUBOWNER: + return listId == GUEST_LIST; + + default: + return false; + } +} + +HouseTransferItem* House::getTransferItem() +{ + if (transferItem != NULL) { + return NULL; + } + + transfer_container.setParent(NULL); + transferItem = HouseTransferItem::createHouseTransferItem(this); + transfer_container.__addThing(transferItem); + return transferItem; +} + +void House::resetTransferItem() +{ + if (transferItem) { + Item* tmpItem = transferItem; + transferItem = NULL; + transfer_container.setParent(NULL); + + transfer_container.__removeThing(tmpItem, tmpItem->getItemCount()); + g_game.FreeThing(tmpItem); + } +} + +HouseTransferItem* HouseTransferItem::createHouseTransferItem(House* house) +{ + HouseTransferItem* transferItem = new HouseTransferItem(house); + transferItem->useThing2(); + transferItem->setID(ITEM_DOCUMENT_RO); + transferItem->setSubType(1); + std::ostringstream ss; + ss << "It is a house transfer document for '" << house->getName() << "'."; + transferItem->setSpecialDescription(ss.str()); + return transferItem; +} + +bool HouseTransferItem::onTradeEvent(TradeEvents_t event, Player* owner) +{ + House* house; + + switch (event) { + case ON_TRADE_TRANSFER: { + house = getHouse(); + + if (house) { + house->executeTransfer(this, owner); + } + + g_game.internalRemoveItem(this, 1); + break; + } + + case ON_TRADE_CANCEL: { + house = getHouse(); + + if (house) { + house->resetTransferItem(); + } + + break; + } + + default: + break; + } + + return true; +} + +bool House::executeTransfer(HouseTransferItem* item, Player* newOwner) +{ + if (transferItem != item) { + return false; + } + + setHouseOwner(newOwner->getGUID()); + transferItem = NULL; + return true; +} + +AccessList::AccessList() +{ + // +} + +AccessList::~AccessList() +{ + // +} + +bool AccessList::parseList(const std::string& _list) +{ + playerList.clear(); + guildList.clear(); + expressionList.clear(); + regExList.clear(); + list = _list; + + if (_list == "") { + return true; + } + + std::istringstream listStream(_list); + std::string line; + + while (getline(listStream, line)) { + trimString(line); + trim_left(line, "\t"); + trim_right(line, "\t"); + trimString(line); + + std::transform(line.begin(), line.end(), line.begin(), tolower); + + if (line.substr(0, 1) == "#" || line.length() > 100) { + continue; + } + + if (line.find("@") != std::string::npos) { + std::string::size_type pos = line.find("@"); + addGuild(line.substr(pos + 1), ""); + } else if (line.find("!") != std::string::npos || line.find("*") != std::string::npos || line.find("?") != std::string::npos) { + addExpression(line); + } else { + addPlayer(line); + } + } + + return true; +} + +bool AccessList::addPlayer(std::string& name) +{ + uint32_t guid; + std::string dbName = name; + + if (IOLoginData::getInstance()->getGuidByName(guid, dbName)) { + if (playerList.find(guid) == playerList.end()) { + playerList.insert(guid); + return true; + } + } + + return false; +} + +bool AccessList::addGuild(const std::string& guildName, const std::string& rank) +{ + uint32_t guildId; + + if (IOGuild::getInstance()->getGuildIdByName(guildId, guildName)) { + if (guildId != 0 && guildList.find(guildId) == guildList.end()) { + guildList.insert(guildId); + return true; + } + } + + return false; +} + +bool AccessList::addExpression(const std::string& expression) +{ + ExpressionList::iterator it; + + for (it = expressionList.begin(); it != expressionList.end(); ++it) { + if ((*it) == expression) { + return false; + } + } + + std::string outExp; + std::string metachars = ".[{}()\\+|^$"; + + for (std::string::const_iterator it = expression.begin(); it != expression.end(); ++it) { + if (metachars.find(*it) != std::string::npos) { + outExp += "\\"; + } + + outExp += (*it); + } + + replaceString(outExp, "*", ".*"); + replaceString(outExp, "?", ".?"); + + try { + if (outExp.length() > 0) { + expressionList.push_back(outExp); + + if (outExp.substr(0, 1) == "!") { + if (outExp.length() > 1) { + regExList.push_front(std::make_pair(boost::regex(outExp.substr(1)), false)); + } + } else { + regExList.push_back(std::make_pair(boost::regex(outExp), true)); + } + } + } catch (...) {} + + return true; +} + +bool AccessList::isInList(const Player* player) +{ + RegExList::iterator it; + std::string name = player->getName(); + boost::cmatch what; + + std::transform(name.begin(), name.end(), name.begin(), tolower); + + for (it = regExList.begin(); it != regExList.end(); ++it) { + if (boost::regex_match(name.c_str(), what, it->first)) { + return it->second; + } + } + + PlayerList::iterator playerIt = playerList.find(player->getGUID()); + + if (playerIt != playerList.end()) { + return true; + } + + Guild* guild = player->getGuild(); + + if (guild) { + GuildList::iterator guildIt = guildList.find(guild->getId()); + + if (guildIt != guildList.end()) { + return true; + } + } + + return false; +} + +void AccessList::getList(std::string& _list) const +{ + _list = list; +} + +Door::Door(uint16_t _type) + : Item(_type) +{ + house = NULL; + accessList = NULL; +} + +Door::~Door() +{ + delete accessList; +} + +Attr_ReadValue Door::readAttr(AttrTypes_t attr, PropStream& propStream) +{ + if (ATTR_HOUSEDOORID == attr) { + unsigned char _doorId = 0; + + if (!propStream.GET_UCHAR(_doorId)) { + return ATTR_READ_ERROR; + } + + setDoorId(_doorId); + return ATTR_READ_CONTINUE; + } else { + return Item::readAttr(attr, propStream); + } +} + +bool Door::serializeAttr(PropWriteStream& propWriteStream) const +{ + return true; +} + +void Door::setHouse(House* _house) +{ + if (house != NULL) { + return; + } + + house = _house; + + if (!accessList) { + accessList = new AccessList(); + } +} + +bool Door::canUse(const Player* player) +{ + if (!house) { + return true; + } + + if (house->getHouseAccessLevel(player) >= HOUSE_SUBOWNER) { + return true; + } + + return accessList->isInList(player); +} + +void Door::setAccessList(const std::string& textlist) +{ + if (!accessList) { + accessList = new AccessList(); + } + + accessList->parseList(textlist); +} + +bool Door::getAccessList(std::string& list) const +{ + if (!house) { + return false; + } + + accessList->getList(list); + return true; +} + +void Door::copyAttributes(Item* item) +{ + Item::copyAttributes(item); + + if (Door* door = item->getDoor()) { + std::string list; + + if (door->getAccessList(list)) { + setAccessList(list); + } + } +} + +void Door::onRemoved() +{ + Item::onRemoved(); + + if (house) { + house->removeDoor(this); + } +} + +Houses::Houses() +{ + rentPeriod = RENTPERIOD_NEVER; + std::string strRentPeriod = asLowerCaseString(g_config.getString(ConfigManager::HOUSE_RENT_PERIOD)); + + if (strRentPeriod == "yearly") { + rentPeriod = RENTPERIOD_YEARLY; + } else if (strRentPeriod == "weekly") { + rentPeriod = RENTPERIOD_WEEKLY; + } else if (strRentPeriod == "monthly") { + rentPeriod = RENTPERIOD_MONTHLY; + } else if (strRentPeriod == "daily") { + rentPeriod = RENTPERIOD_DAILY; + } +} + +House* Houses::getHouseByPlayerId(uint32_t playerId) +{ + for (HouseMap::iterator it = houseMap.begin(); it != houseMap.end(); ++it) { + House* house = it->second; + + if (house->getHouseOwner() == playerId) { + return house; + } + } + + return NULL; +} + +bool Houses::loadHousesXML(const std::string& filename) +{ + xmlDocPtr doc = xmlParseFile(filename.c_str()); + + if (doc) { + xmlNodePtr root, houseNode; + root = xmlDocGetRootElement(doc); + + if (xmlStrcmp(root->name, (const xmlChar*)"houses") != 0) { + xmlFreeDoc(doc); + return false; + } + + int32_t intValue; + std::string strValue; + + houseNode = root->children; + + while (houseNode) { + if (xmlStrcmp(houseNode->name, (const xmlChar*)"house") == 0) { + int32_t _houseid = 0; + Position entryPos(0, 0, 0); + + if (!readXMLInteger(houseNode, "houseid", _houseid)) { + xmlFreeDoc(doc); + return false; + } + + House* house = Houses::getInstance().getHouse(_houseid); + + if (!house) { + std::cout << "Error: [Houses::loadHousesXML] Unknown house, id = " << _houseid << std::endl; + xmlFreeDoc(doc); + return false; + } + + if (readXMLString(houseNode, "name", strValue)) { + house->setName(strValue); + } + + if (readXMLInteger(houseNode, "entryx", intValue)) { + entryPos.x = intValue; + } + + if (readXMLInteger(houseNode, "entryy", intValue)) { + entryPos.y = intValue; + } + + if (readXMLInteger(houseNode, "entryz", intValue)) { + entryPos.z = intValue; + } + + if (entryPos.x == 0 && entryPos.y == 0 && entryPos.z == 0) { + std::cout << "Warning: [Houses::loadHousesXML] House entry not set" + << " - Name: " << house->getName() + << " - House id: " << _houseid << std::endl; + } + + house->setEntryPos(entryPos); + + if (readXMLInteger(houseNode, "rent", intValue)) { + house->setRent(intValue); + } + + if (readXMLInteger(houseNode, "townid", intValue)) { + house->setTownId(intValue); + } + + house->setHouseOwner(0, false); + } + + houseNode = houseNode->next; + } + + xmlFreeDoc(doc); + return true; + } + + return false; +} + +bool Houses::payHouses() +{ + if (rentPeriod == RENTPERIOD_NEVER) { + return true; + } + + uint32_t currentTime = time(NULL); + + for (HouseMap::iterator it = houseMap.begin(); it != houseMap.end(); ++it) { + House* house = it->second; + + if (house->getHouseOwner() != 0) { + uint32_t ownerid = house->getHouseOwner(); + Town* town = Towns::getInstance().getTown(house->getTownId()); + + if (!town) { +#ifdef __DEBUG_HOUSES__ + std::cout << "Warning: [Houses::payHouses] town = NULL, townid = " << + house->getTownId() << ", houseid = " << house->getHouseId() << std::endl; +#endif + continue; + } + + std::string name; + + if (!IOLoginData::getInstance()->getNameByGuid(ownerid, name)) { + //player doesnt exist, remove it as house owner? + house->setHouseOwner(0); + continue; + } + + Player* player = g_game.getPlayerByName(name); + + if (!player) { + player = new Player(name, NULL); + + if (!IOLoginData::getInstance()->loadPlayer(player, name)) { +#ifdef __DEBUG__ + std::cout << "Failure: [Houses::payHouses], can not load player: " << name << std::endl; +#endif + delete player; + continue; + } + } + + int32_t housePrice = 0; + + for (HouseTileList::iterator it = house->getHouseTileBegin(), end = house->getHouseTileEnd(); it != end; ++it) { + housePrice += g_config.getNumber(ConfigManager::HOUSE_PRICE); + } + + bool paid = false; + + if (player->getBankBalance() >= house->getRent()) { + player->setBankBalance(player->getBankBalance() - house->getRent()); + paid = true; + } + + if (paid) { + time_t paidUntil = currentTime; + + switch (rentPeriod) { + case RENTPERIOD_DAILY: + paidUntil += 24 * 60 * 60; + break; + case RENTPERIOD_WEEKLY: + paidUntil += 24 * 60 * 60 * 7; + break; + case RENTPERIOD_MONTHLY: + paidUntil += 24 * 60 * 60 * 30; + break; + case RENTPERIOD_YEARLY: + paidUntil += 24 * 60 * 60 * 365; + break; + default: + break; + } + + house->setPaidUntil(paidUntil); + } else { + if (house->getPayRentWarnings() < 7) { + int32_t daysLeft = 7 - house->getPayRentWarnings(); + + Item* letter = Item::CreateItem(ITEM_LETTER_STAMPED); + std::string period = ""; + + switch (rentPeriod) { + case RENTPERIOD_DAILY: + period = "daily"; + break; + + case RENTPERIOD_WEEKLY: + period = "weekly"; + break; + + case RENTPERIOD_MONTHLY: + period = "monthly"; + break; + + case RENTPERIOD_YEARLY: + period = "annual"; + break; + + default: + break; + } + + std::ostringstream ss; + ss << "Warning! \nThe " << period << " rent of " << house->getRent() << " gold for your house \"" << house->getName() << "\" is payable. Have it within " << daysLeft << " days or you will lose this house."; + letter->setText(ss.str()); + g_game.internalAddItem(player->getInbox(), letter, INDEX_WHEREEVER, FLAG_NOLIMIT); + house->setPayRentWarnings(house->getPayRentWarnings() + 1); + } else { + house->setHouseOwner(0, true, player); + } + } + + if (player->isOffline()) { + IOLoginData::getInstance()->savePlayer(player); + delete player; + } + } + } + return true; +} diff --git a/src/house.h b/src/house.h new file mode 100644 index 0000000000..35fab3fd94 --- /dev/null +++ b/src/house.h @@ -0,0 +1,345 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_HOUSE_H__ +#define __OTSERV_HOUSE_H__ + +#include +#include +#include + +#include + +#include "definitions.h" +#include "position.h" +#include "housetile.h" +#include "player.h" + +class House; +class BedItem; + +class AccessList +{ + public: + AccessList(); + ~AccessList(); + + bool parseList(const std::string& _list); + bool addPlayer(std::string& name); + bool addGuild(const std::string& guildName, const std::string& rank); + bool addExpression(const std::string& expression); + + bool isInList(const Player* player); + + void getList(std::string& _list) const; + + private: + typedef OTSERV_HASH_SET PlayerList; + typedef OTSERV_HASH_SET GuildList; //TODO: include ranks + + typedef std::list ExpressionList; + typedef std::list > RegExList; + std::string list; + PlayerList playerList; + GuildList guildList; + ExpressionList expressionList; + RegExList regExList; +}; + +class Door : public Item +{ + public: + Door(uint16_t _type); + virtual ~Door(); + + virtual Door* getDoor() { + return this; + } + virtual const Door* getDoor() const { + return this; + } + + House* getHouse() { + return house; + } + + //serialization + virtual Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream); + virtual bool serializeAttr(PropWriteStream& propWriteStream) const; + + void setDoorId(uint32_t _doorId) { + setIntAttr(ATTR_ITEM_DOORID, (uint32_t)_doorId); + } + uint32_t getDoorId() const { + return getIntAttr(ATTR_ITEM_DOORID); + } + + bool canUse(const Player* player); + + void setAccessList(const std::string& textlist); + bool getAccessList(std::string& list) const; + + //overrides + virtual void onRemoved(); + void copyAttributes(Item* item); + + protected: + void setHouse(House* _house); + + private: + House* house; + AccessList* accessList; + friend class House; +}; + +enum AccessList_t { + GUEST_LIST = 0x100, + SUBOWNER_LIST = 0x101 +}; + +enum AccessHouseLevel_t { + HOUSE_NO_INVITED = 0, + HOUSE_GUEST = 1, + HOUSE_SUBOWNER = 2, + HOUSE_OWNER = 3 +}; + +typedef std::list HouseTileList; +typedef std::list HouseDoorList; +typedef std::list HouseBedItemList; + +class HouseTransferItem : public Item +{ + public: + static HouseTransferItem* createHouseTransferItem(House* house); + + HouseTransferItem(House* _house) : Item(0) { + house = _house; + } + virtual ~HouseTransferItem() {} + + virtual bool onTradeEvent(TradeEvents_t event, Player* owner); + + House* getHouse() { + return house; + } + virtual bool canTransform() const { + return false; + } + + protected: + House* house; +}; + +class House +{ + public: + House(uint32_t _houseid); + ~House() {} + + void addTile(HouseTile* tile); + void updateDoorDescription(); + + bool canEditAccessList(uint32_t listId, const Player* player); + // listId special values: + // GUEST_LIST guest list + // SUBOWNER_LIST subowner list + void setAccessList(uint32_t listId, const std::string& textlist); + bool getAccessList(uint32_t listId, std::string& list) const; + + bool isInvited(const Player* player); + + AccessHouseLevel_t getHouseAccessLevel(const Player* player); + bool kickPlayer(Player* player, const std::string& name); + + void setEntryPos(const Position& pos) { + posEntry = pos; + } + const Position& getEntryPosition() const { + return posEntry; + } + + void setName(const std::string& _houseName) { + houseName = _houseName; + } + const std::string& getName() const { + return houseName; + } + + void setHouseOwner(uint32_t guid, bool updateDatabase = true, Player* player = NULL); + uint32_t getHouseOwner() const { + return houseOwner; + } + + void setPaidUntil(uint32_t paid) { + paidUntil = paid; + } + uint32_t getPaidUntil() const { + return paidUntil; + } + + void setRent(uint32_t _rent) { + rent = _rent; + } + uint32_t getRent() const { + return rent; + } + + void setPayRentWarnings(uint32_t warnings) { + rentWarnings = warnings; + } + uint32_t getPayRentWarnings() const { + return rentWarnings; + } + + void setTownId(uint32_t _town) { + townid = _town; + } + uint32_t getTownId() const { + return townid; + } + + uint32_t getHouseId() const { + return houseid; + } + + void addDoor(Door* door); + void removeDoor(Door* door); + Door* getDoorByNumber(uint32_t doorId); + Door* getDoorByNumber(uint32_t doorId) const; + Door* getDoorByPosition(const Position& pos); + + HouseTransferItem* getTransferItem(); + void resetTransferItem(); + bool executeTransfer(HouseTransferItem* item, Player* player); + + HouseTileList::iterator getHouseTileBegin() { + return houseTiles.begin(); + } + HouseTileList::iterator getHouseTileEnd() { + return houseTiles.end(); + } + size_t getHouseTileSize() { + return houseTiles.size(); + } + + HouseDoorList::iterator getHouseDoorBegin() { + return doorList.begin(); + } + HouseDoorList::iterator getHouseDoorEnd() { + return doorList.end(); + } + + void addBed(BedItem* bed); + HouseBedItemList::iterator getHouseBedsBegin() { + return bedsList.begin(); + } + HouseBedItemList::iterator getHouseBedsEnd() { + return bedsList.end(); + } + size_t getBedTiles() { + return bedsList.size(); + } + uint32_t getBedCount() { + return (uint32_t)std::ceil((double)getBedTiles() / 2); //each bed takes 2 sqms of space, ceil is just for bad maps + } + + private: + bool transferToDepot(); + bool transferToDepot(Player* player); + + bool isLoaded; + uint32_t houseid; + uint32_t houseOwner; + std::string houseOwnerName; + HouseTileList houseTiles; + HouseDoorList doorList; + HouseBedItemList bedsList; + AccessList guestList; + AccessList subOwnerList; + std::string houseName; + Position posEntry; + uint32_t paidUntil; + uint32_t rentWarnings; + uint32_t rent; + uint32_t townid; + + HouseTransferItem* transferItem; + Container transfer_container; +}; + +typedef std::map HouseMap; + +enum RentPeriod_t { + RENTPERIOD_DAILY, + RENTPERIOD_WEEKLY, + RENTPERIOD_MONTHLY, + RENTPERIOD_YEARLY, + RENTPERIOD_NEVER +}; + +class Houses +{ + Houses(); + ~Houses() { + for (HouseMap::iterator it = houseMap.begin(); it != houseMap.end(); ++it) { + delete it->second; + } + } + + public: + static Houses& getInstance() { + static Houses instance; + return instance; + } + + House* getHouse(uint32_t houseid, bool add = false) { + HouseMap::iterator it = houseMap.find(houseid); + + if (it != houseMap.end()) { + return it->second; + } + + if (add) { + House* house = new House(houseid); + houseMap[houseid] = house; + return house; + } else { + return NULL; + } + } + + House* getHouseByPlayerId(uint32_t playerId); + + bool loadHousesXML(const std::string& filename); + + bool payHouses(); + + HouseMap::iterator getHouseBegin() { + return houseMap.begin(); + } + HouseMap::iterator getHouseEnd() { + return houseMap.end(); + } + + private: + RentPeriod_t rentPeriod; + HouseMap houseMap; +}; + +#endif diff --git a/src/housetile.cpp b/src/housetile.cpp new file mode 100644 index 0000000000..46bd5014b5 --- /dev/null +++ b/src/housetile.cpp @@ -0,0 +1,135 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "housetile.h" +#include "house.h" +#include "game.h" + +extern Game g_game; + +HouseTile::HouseTile(int32_t x, int32_t y, int32_t z, House* _house) : + DynamicTile(x, y, z) +{ + house = _house; + setFlag(TILESTATE_HOUSE); +} + +HouseTile::~HouseTile() +{ + // +} + +void HouseTile::__addThing(int32_t index, Thing* thing) +{ + Tile::__addThing(index, thing); + + if (!thing->getParent()) { + return; + } + + if (Item* item = thing->getItem()) { + updateHouse(item); + } +} + +void HouseTile::__internalAddThing(uint32_t index, Thing* thing) +{ + Tile::__internalAddThing(index, thing); + + if (!thing->getParent()) { + return; + } + + if (Item* item = thing->getItem()) { + updateHouse(item); + } +} + +void HouseTile::updateHouse(Item* item) +{ + if (item->getTile() == this) { + Door* door = item->getDoor(); + + if (door && door->getDoorId() != 0) { + house->addDoor(door); + } + + if (!door) { + BedItem* bed = item->getBed(); + + if (bed) { + house->addBed(bed); + } + } + } +} + +ReturnValue HouseTile::__queryAdd(int32_t index, const Thing* thing, uint32_t count, uint32_t flags, Creature* actor/* = NULL*/) const +{ + if (const Creature* creature = thing->getCreature()) { + if (const Player* player = creature->getPlayer()) { + if (!house->isInvited(player)) { + return RET_PLAYERISNOTINVITED; + } + } else { + return RET_NOTPOSSIBLE; + } + } else if (thing->getItem() && actor) { + Player* actorPlayer = actor->getPlayer(); + + if (!house->isInvited(actorPlayer)) { + return RET_CANNOTTHROW; + } + } + + return Tile::__queryAdd(index, thing, count, flags, actor); +} + +Cylinder* HouseTile::__queryDestination(int32_t& index, const Thing* thing, Item** destItem, uint32_t& flags) +{ + if (const Creature* creature = thing->getCreature()) { + if (const Player* player = creature->getPlayer()) { + if (!house->isInvited(player)) { + const Position& entryPos = house->getEntryPosition(); + Tile* destTile = g_game.getTile(entryPos.x, entryPos.y, entryPos.z); + + if (!destTile) { + std::cout << "Error: [HouseTile::__queryDestination] House entry not correct" + << " - Name: " << house->getName() + << " - House id: " << house->getHouseId() + << " - Tile not found: " << entryPos << std::endl; + + const Position& templePos = player->getTemplePosition(); + destTile = g_game.getTile(templePos.x, templePos.y, templePos.z); + + if (!destTile) { + destTile = &(Tile::null_tile); + } + } + + index = -1; + *destItem = NULL; + return destTile; + } + } + } + + return Tile::__queryDestination(index, thing, destItem, flags); +} diff --git a/src/housetile.h b/src/housetile.h new file mode 100644 index 0000000000..bb072e1215 --- /dev/null +++ b/src/housetile.h @@ -0,0 +1,52 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __HOUSETILE_H__ +#define __HOUSETILE_H__ + +#include "tile.h" + +class House; + +class HouseTile : public DynamicTile +{ + public: + HouseTile(int32_t x, int32_t y, int32_t z, House* _house); + ~HouseTile(); + + //cylinder implementations + virtual ReturnValue __queryAdd(int32_t index, const Thing* thing, uint32_t count, + uint32_t flags, Creature* actor = NULL) const; + + virtual Cylinder* __queryDestination(int32_t& index, const Thing* thing, Item** destItem, + uint32_t& flags); + + virtual void __addThing(int32_t index, Thing* thing); + virtual void __internalAddThing(uint32_t index, Thing* thing); + + House* getHouse() { + return house; + } + + private: + void updateHouse(Item* item); + + House* house; +}; + +#endif diff --git a/src/inbox.cpp b/src/inbox.cpp new file mode 100644 index 0000000000..7aca8e00e3 --- /dev/null +++ b/src/inbox.cpp @@ -0,0 +1,59 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "inbox.h" +#include "tools.h" + +Inbox::Inbox(uint16_t _type) : + Container(_type) +{ + maxSize = 30; +} + +Inbox::~Inbox() +{ + // +} + +ReturnValue Inbox::__queryAdd(int32_t index, const Thing* thing, uint32_t count, + uint32_t flags, Creature* actor/* = NULL*/) const +{ + bool skipLimit = hasBitSet(FLAG_NOLIMIT, flags); + + if (!skipLimit) { + return RET_CONTAINERNOTENOUGHROOM; + } + + return Container::__queryAdd(index, thing, count, flags, actor); +} + +void Inbox::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) +{ + if (getParent() != NULL) { + getParent()->postAddNotification(thing, oldParent, index, LINK_PARENT); + } +} + +void Inbox::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, bool isCompleteRemoval, cylinderlink_t link /*= LINK_OWNER*/) +{ + if (getParent() != NULL) { + getParent()->postRemoveNotification(thing, newParent, index, isCompleteRemoval, LINK_PARENT); + } +} diff --git a/src/inbox.h b/src/inbox.h new file mode 100644 index 0000000000..089acea038 --- /dev/null +++ b/src/inbox.h @@ -0,0 +1,44 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_INBOX_H__ +#define __OTSERV_INBOX_H__ + +#include "container.h" + +class Inbox : public Container +{ + public: + Inbox(uint16_t _type); + ~Inbox(); + + //cylinder implementations + ReturnValue __queryAdd(int32_t index, const Thing* thing, uint32_t count, + uint32_t flags, Creature* actor = NULL) const; + + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER); + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, bool isCompleteRemoval, cylinderlink_t link = LINK_OWNER); + + //overrides + bool canRemove() const { + return false; + } +}; + +#endif + diff --git a/src/ioguild.cpp b/src/ioguild.cpp new file mode 100644 index 0000000000..e0365e7f5c --- /dev/null +++ b/src/ioguild.cpp @@ -0,0 +1,65 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "ioguild.h" +#include "database.h" + +bool IOGuild::getGuildIdByName(uint32_t& guildId, const std::string& guildName) +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "SELECT `id` FROM `guilds` WHERE `name` = " << db->escapeString(guildName); + + DBResult* result = db->storeQuery(query.str()); + if (!result) { + return false; + } + + guildId = result->getDataInt("id"); + db->freeResult(result); + return true; +} + +void IOGuild::getWarList(uint32_t guildId, GuildWarList& guildWarList) +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "SELECT `guild1`, `guild2` FROM `guild_wars` WHERE (`guild1` = " << guildId << " OR `guild2` = " << guildId << ") AND `ended` = 0 AND `status` = 1"; + + DBResult* result = db->storeQuery(query.str()); + + if (!result) { + return; + } + + do { + uint32_t guild1 = result->getDataInt("guild1"); + + if (guildId == guild1) { + guildWarList.push_back(guild1); + } else { + guildWarList.push_back(result->getDataInt("guild2")); + } + } while (result->next()); + + db->freeResult(result); +} diff --git a/src/ioguild.h b/src/ioguild.h new file mode 100644 index 0000000000..d96e32b3a0 --- /dev/null +++ b/src/ioguild.h @@ -0,0 +1,41 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_IOGUILD_H__ +#define __OTSERV_IOGUILD_H__ + +#include "player.h" + +typedef std::vector GuildWarList; + +class IOGuild +{ + public: + IOGuild() {} + ~IOGuild() {} + + static IOGuild* getInstance() { + static IOGuild instance; + return &instance; + } + + bool getGuildIdByName(uint32_t& guildId, const std::string& guildName); + void getWarList(uint32_t guildId, GuildWarList& guildWarList); +}; + +#endif diff --git a/src/iologindata.cpp b/src/iologindata.cpp new file mode 100644 index 0000000000..e00dfe7ad9 --- /dev/null +++ b/src/iologindata.cpp @@ -0,0 +1,1345 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "iologindata.h" +#include +#include +#include "item.h" +#include "configmanager.h" +#include "tools.h" +#include "town.h" +#include "definitions.h" +#include "game.h" +#include "vocation.h" +#include "house.h" +#include "ban.h" +#include +#include + +extern ConfigManager g_config; +extern Vocations g_vocations; +extern Game g_game; + +#ifndef __GNUC__ +#pragma warning( disable : 4005) +#pragma warning( disable : 4996) +#endif + +Account IOLoginData::loadAccount(uint32_t accno) +{ + Database* db = Database::getInstance(); + Account account; + + DBQuery query; + query << "SELECT `id`, `name`, `password`, `type`, `premdays`, `lastday`, `warnings` FROM `accounts` WHERE `id` = " << accno; + + DBResult* result = db->storeQuery(query.str()); + + if (!result) { + return account; + } + + account.id = result->getDataInt("id"); + account.name = result->getDataString("name"); + account.password = result->getDataString("password"); + account.accountType = (AccountType_t)result->getDataInt("type"); + account.premiumDays = result->getDataInt("premdays"); + account.lastDay = result->getDataInt("lastday"); + account.warnings = result->getDataInt("warnings"); + db->freeResult(result); + + query.str(""); + query << "SELECT `name` FROM `players` WHERE `account_id` = " << account.id; + result = db->storeQuery(query.str()); + + if (!result) { + return account; + } + + do { + std::string characterName = result->getDataString("name"); + account.charList.push_back(characterName.c_str()); + } while (result->next()); + + db->freeResult(result); + account.charList.sort(); + return account; +} + +Account IOLoginData::loadAccount(const std::string& name) +{ + Database* db = Database::getInstance(); + Account account; + + DBQuery query; + query << "SELECT `id`, `name`, `password`, `type`, `premdays`, `lastday`, `key`, `warnings` FROM `accounts` WHERE `name` = " << db->escapeString(name); + + DBResult* result = db->storeQuery(query.str()); + + if (!result) { + return account; + } + + account.id = result->getDataInt("id"); + account.name = result->getDataString("name"); + account.password = result->getDataString("password"); + account.accountType = (AccountType_t)result->getDataInt("type"); + account.premiumDays = result->getDataInt("premdays"); + account.lastDay = result->getDataInt("lastday"); + account.warnings = result->getDataInt("warnings"); + db->freeResult(result); + + query.str(""); + query << "SELECT `name` FROM `players` WHERE `account_id` = " << account.id; + result = db->storeQuery(query.str()); + + if (!result) { + return account; + } + + do { + std::string characterName = result->getDataString("name"); + account.charList.push_back(characterName.c_str()); + } while (result->next()); + + db->freeResult(result); + account.charList.sort(); + return account; +} + +bool IOLoginData::saveAccount(const Account& acc) +{ + DBQuery query; + query << "UPDATE `accounts` SET `premdays` = " << acc.premiumDays << ", `warnings` = " << acc.warnings << ", `lastday` = " << acc.lastDay << " WHERE `id` = " << acc.id; + return Database::getInstance()->executeQuery(query.str()); +} + +bool IOLoginData::getPassword(const std::string& accname, const std::string& name, std::string& password, uint32_t& accNumber) +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "SELECT `id`, `password` FROM `accounts` WHERE `name` = " << db->escapeString(accname); + + DBResult* result = db->storeQuery(query.str()); + + if (!result) { + return false; + } + + std::string accountPassword = result->getDataString("password"); + accNumber = result->getDataInt("id"); + db->freeResult(result); + + query.str(""); + query << "SELECT `name` FROM `players` WHERE `account_id` = " << accNumber; + + result = db->storeQuery(query.str()); + + if (!result) { + return false; + } + + do { + if (result->getDataString("name") == name) { + password = accountPassword; + db->freeResult(result); + return true; + } + } while (result->next()); + + db->freeResult(result); + return false; +} + +AccountType_t IOLoginData::getAccountType(uint32_t accountId) +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "SELECT `type` FROM `accounts` WHERE `id` = " << accountId; + + DBResult* result = db->storeQuery(query.str()); + + if (!result) { + return ACCOUNT_TYPE_NORMAL; + } + + AccountType_t accountType = (AccountType_t)result->getDataInt("type"); + db->freeResult(result); + return accountType; +} + +void IOLoginData::setAccountType(uint32_t accountId, AccountType_t accountType) +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "UPDATE `accounts` SET `type` = " << accountType << " WHERE `id` = " << accountId; + db->executeQuery(query.str()); +} + +uint32_t IOLoginData::getLastIPByName(const std::string& name) +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "SELECT `lastip` FROM `players` WHERE `name` = " << db->escapeString(name); + + DBResult* result = db->storeQuery(query.str()); + + if (!result) { + return 0; + } + + uint32_t lastIp = result->getDataInt("lastip"); + db->freeResult(result); + return lastIp; +} + +bool IOLoginData::resetOnlineStatus() +{ + DBQuery query; // keep for locking + return Database::getInstance()->executeQuery("TRUNCATE TABLE `players_online`"); +} + +bool IOLoginData::updateOnlineStatus(uint32_t guid, bool login) +{ + Database* db = Database::getInstance(); + + DBQuery query; + + if (login) { + query << "INSERT INTO `players_online` VALUES (" << guid << ")"; + } else { + query << "DELETE FROM `players_online` WHERE `player_id` = " << guid; + } + + return db->executeQuery(query.str()); +} + +bool IOLoginData::loadPlayer(Player* player, const std::string& name, bool preload /*= false*/) +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "SELECT `id`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina` FROM `players` WHERE `name` = " << db->escapeString(name); + + DBResult* result = db->storeQuery(query.str()); + + if (!result) { + return false; + } + + uint32_t accno = result->getDataInt("account_id"); + Account acc = loadAccount(accno); + + player->setGUID(result->getDataInt("id")); + player->accountNumber = accno; + + player->accountType = acc.accountType; + + if (g_config.getBoolean(ConfigManager::FREE_PREMIUM)) { + player->premiumDays = 0xFFFF; + } else { + player->premiumDays = acc.premiumDays; + } + + player->setGroupId(result->getDataInt("group_id")); + + if (preload) { + //only loading basic info + db->freeResult(result); + return true; + } + + player->bankBalance = (uint64_t)result->getDataLong("balance"); + + player->setSex((PlayerSex_t)result->getDataInt("sex")); + player->level = std::max(1, result->getDataInt("level")); + + uint64_t currExpCount = Player::getExpForLevel(player->level); + uint64_t nextExpCount = Player::getExpForLevel(player->level + 1); + uint64_t experience = (uint64_t)result->getDataLong("experience"); + + if (experience < currExpCount || experience > nextExpCount) { + experience = currExpCount; + } + + player->experience = experience; + + if (currExpCount < nextExpCount) { + player->levelPercent = Player::getPercentLevel(player->experience - currExpCount, nextExpCount - currExpCount); + } else { + player->levelPercent = 0; + } + + player->soul = result->getDataInt("soul"); + player->capacity = result->getDataInt("cap"); + player->blessings = result->getDataInt("blessings"); + + unsigned long conditionsSize = 0; + const char* conditions = result->getDataStream("conditions", conditionsSize); + PropStream propStream; + propStream.init(conditions, conditionsSize); + + Condition* condition; + + while ((condition = Condition::createCondition(propStream))) { + if (condition->unserialize(propStream)) { + player->storedConditionList.push_back(condition); + } else { + delete condition; + } + } + + player->setVocation(result->getDataInt("vocation")); + player->mana = result->getDataInt("mana"); + player->manaMax = result->getDataInt("manamax"); + player->magLevel = result->getDataInt("maglevel"); + + uint64_t nextManaCount = player->vocation->getReqMana(player->magLevel + 1); + uint64_t manaSpent = result->getDataLong("manaspent"); + + if (manaSpent > nextManaCount) { + manaSpent = 0; + } + + player->manaSpent = manaSpent; + player->magLevelPercent = Player::getPercentLevel(player->manaSpent, + nextManaCount); + + player->health = result->getDataInt("health"); + player->healthMax = result->getDataInt("healthmax"); + + player->defaultOutfit.lookType = result->getDataInt("looktype"); + player->defaultOutfit.lookHead = result->getDataInt("lookhead"); + player->defaultOutfit.lookBody = result->getDataInt("lookbody"); + player->defaultOutfit.lookLegs = result->getDataInt("looklegs"); + player->defaultOutfit.lookFeet = result->getDataInt("lookfeet"); + player->defaultOutfit.lookAddons = result->getDataInt("lookaddons"); + player->currentOutfit = player->defaultOutfit; + + if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { + int32_t skullSeconds = result->getDataInt("skulltime") - time(NULL); + + if (skullSeconds > 0) { + //ensure that we round up the number of ticks + player->skullTicks = (skullSeconds + 2) * 1000; + int32_t skull = result->getDataInt("skull"); + + if (skull == SKULL_RED) { + player->skull = SKULL_RED; + } else if (skull == SKULL_BLACK) { + player->skull = SKULL_BLACK; + } + } + } + + player->loginPosition.x = result->getDataInt("posx"); + player->loginPosition.y = result->getDataInt("posy"); + player->loginPosition.z = result->getDataInt("posz"); + + player->lastLoginSaved = result->getDataLong("lastlogin"); + player->lastLogout = result->getDataLong("lastlogout"); + + player->offlineTrainingTime = result->getDataInt("offlinetraining_time") * 1000; + player->offlineTrainingSkill = result->getDataInt("offlinetraining_skill"); + + player->town = result->getDataInt("town_id"); + + Town* town = Towns::getInstance().getTown(player->town); + + if (town) { + player->masterPos = town->getTemplePosition(); + } + + Position loginPos = player->loginPosition; + + if (loginPos.x == 0 && loginPos.y == 0 && loginPos.z == 0) { + player->loginPosition = player->masterPos; + } + + player->staminaMinutes = result->getDataInt("stamina"); + + db->freeResult(result); + + query.str(""); + query << "SELECT `guild_id`, `rank_id`, `nick` FROM `guild_membership` WHERE `player_id` = " << player->getGUID(); + + if ((result = db->storeQuery(query.str()))) { + uint32_t guildId = result->getDataInt("guild_id"); + uint32_t playerRankId = result->getDataInt("rank_id"); + player->guildNick = result->getDataString("nick"); + db->freeResult(result); + + Guild* guild = g_game.getGuild(guildId); + + if (!guild) { + query.str(""); + query << "SELECT `name` FROM `guilds` WHERE `id` = " << guildId; + + if ((result = db->storeQuery(query.str()))) { + guild = new Guild(guildId, result->getDataString("name")); + db->freeResult(result); + g_game.addGuild(guild); + + query.str(""); + query << "SELECT `id`, `name`, `level` FROM `guild_ranks` WHERE `guild_id` = " << guildId << " LIMIT 3"; + + if ((result = db->storeQuery(query.str()))) { + do { + guild->addRank(result->getDataInt("id"), result->getDataString("name"), result->getDataInt("level")); + } while (result->next()); + + db->freeResult(result); + } + } + } + + if (guild) { + player->guild = guild; + GuildRank* rank = guild->getRankById(playerRankId); + + if (rank) { + player->guildLevel = rank->level; + } else { + player->guildLevel = 1; + } + + IOGuild::getInstance()->getWarList(guildId, player->guildWarList); + + query.str(""); + query << "SELECT COUNT(*) AS `members` FROM `guild_membership` WHERE `guild_id` = " << guildId; + + if ((result = db->storeQuery(query.str()))) { + guild->setMemberCount(result->getDataInt("members")); + db->freeResult(result); + } + } + } + + //get password + query.str(""); + query << "SELECT `password` FROM `accounts` WHERE `id` = " << accno; + + if (!(result = db->storeQuery(query.str()))) { + return false; + } + + player->password = result->getDataString("password"); + db->freeResult(result); + + // we need to find out our skills + // so we query the skill table + query.str(""); + query << "SELECT `skillid`, `value`, `count` FROM `player_skills` WHERE `player_id` = " << player->getGUID(); + + if ((result = db->storeQuery(query.str()))) { + //now iterate over the skills + do { + int32_t skillid = result->getDataInt("skillid"); + + if (skillid >= SKILL_FIRST && skillid <= SKILL_LAST) { + uint32_t skillLevel = result->getDataInt("value"); + uint64_t skillCount = result->getDataLong("count"); + + uint64_t nextSkillCount = player->vocation->getReqSkillTries(skillid, skillLevel + 1); + + if (skillCount > nextSkillCount) { + skillCount = 0; + } + + player->skills[skillid][SKILL_LEVEL] = skillLevel; + player->skills[skillid][SKILL_TRIES] = skillCount; + player->skills[skillid][SKILL_PERCENT] = Player::getPercentLevel(skillCount, nextSkillCount); + } + } while (result->next()); + + db->freeResult(result); + } + + query.str(""); + query << "SELECT `player_id`, `name` FROM `player_spells` WHERE `player_id` = " << player->getGUID(); + + if ((result = db->storeQuery(query.str()))) { + do { + std::string spellName = result->getDataString("name"); + player->learnedInstantSpellList.push_back(spellName); + } while (result->next()); + + db->freeResult(result); + } + + //load inventory items + ItemMap itemMap; + + query.str(""); + query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_items` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC"; + + if ((result = db->storeQuery(query.str()))) { + loadItems(itemMap, result); + db->freeResult(result); + + for (ItemMap::reverse_iterator it = itemMap.rbegin(); it != itemMap.rend(); ++it) { + const std::pair& pair = it->second; + Item* item = pair.first; + int32_t pid = pair.second; + + if (pid >= 1 && pid <= 10) { + player->__internalAddThing(pid, item); + } else { + ItemMap::const_iterator it2 = itemMap.find(pid); + + if (it2 == itemMap.end()) { + continue; + } + + Container* container = it2->second.first->getContainer(); + + if (container) { + container->__internalAddThing(item); + } + } + } + } + + //load depot items + itemMap.clear(); + + query.str(""); + query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_depotitems` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC"; + + if ((result = db->storeQuery(query.str()))) { + loadItems(itemMap, result); + db->freeResult(result); + + for (ItemMap::reverse_iterator it = itemMap.rbegin(); it != itemMap.rend(); ++it) { + const std::pair& pair = it->second; + Item* item = pair.first; + int32_t pid = pair.second; + + if (pid >= 0 && pid < 100) { + DepotChest* depotChest = player->getDepotChest(pid, true); + + if (depotChest) { + depotChest->__internalAddThing(item); + } + } else { + ItemMap::const_iterator it2 = itemMap.find(pid); + + if (it2 == itemMap.end()) { + continue; + } + + Container* container = it2->second.first->getContainer(); + + if (container) { + container->__internalAddThing(item); + } + } + } + } + + //load inbox items + itemMap.clear(); + + query.str(""); + query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_inboxitems` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC"; + + if ((result = db->storeQuery(query.str()))) { + loadItems(itemMap, result); + db->freeResult(result); + + for (ItemMap::reverse_iterator it = itemMap.rbegin(); it != itemMap.rend(); ++it) { + const std::pair& pair = it->second; + Item* item = pair.first; + int32_t pid = pair.second; + + if (pid >= 0 && pid < 100) { + player->getInbox()->__internalAddThing(item); + } else { + ItemMap::const_iterator it2 = itemMap.find(pid); + + if (it2 == itemMap.end()) { + continue; + } + + Container* container = it2->second.first->getContainer(); + + if (container) { + container->__internalAddThing(item); + } + } + } + } + + //load storage map + query.str(""); + query << "SELECT `key`, `value` FROM `player_storage` WHERE `player_id` = " << player->getGUID(); + + if ((result = db->storeQuery(query.str()))) { + do { + player->addStorageValue(result->getDataInt("key"), result->getDataLong("value"), true); + } while (result->next()); + + db->freeResult(result); + } + + //load vip + query.str(""); + query << "SELECT `player_id` FROM `account_viplist` WHERE `account_id` = " << player->getAccount(); + + if ((result = db->storeQuery(query.str()))) { + do { + player->addVIPInternal(result->getDataInt("player_id")); + } while (result->next()); + + db->freeResult(result); + } + + player->updateBaseSpeed(); + player->updateInventoryWeight(); + player->updateItemsLight(true); + return true; +} + +bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert) +{ + std::ostringstream stream; + + typedef std::pair containerBlock; + std::list stack; + + int32_t parentId = 0; + int32_t runningId = 100; + + Database* db = Database::getInstance(); + Item* item; + int32_t pid; + + for (ItemBlockList::const_iterator it = itemList.begin(); it != itemList.end(); ++it) { + pid = it->first; + item = it->second; + ++runningId; + + uint32_t attributesSize = 0; + + PropWriteStream propWriteStream; + item->serializeAttr(propWriteStream); + const char* attributes = propWriteStream.getStream(attributesSize); + + stream << player->getGUID() << "," << pid << "," << runningId << "," << item->getID() << "," << (int32_t)item->getSubType() << "," << db->escapeBlob(attributes, attributesSize); + + if (!query_insert.addRow(stream)) { + return false; + } + + if (Container* container = item->getContainer()) { + stack.push_back(containerBlock(container, runningId)); + } + } + + while (!stack.empty()) { + const containerBlock& cb = stack.front(); + Container* container = cb.first; + parentId = cb.second; + stack.pop_front(); + + for (ItemDeque::const_iterator it = container->getItems(), end = container->getEnd(); it != end; ++it) { + ++runningId; + item = *it; + Container* container = item->getContainer(); + + if (container) { + stack.push_back(containerBlock(container, runningId)); + } + + uint32_t attributesSize = 0; + PropWriteStream propWriteStream; + item->serializeAttr(propWriteStream); + const char* attributes = propWriteStream.getStream(attributesSize); + + stream << player->getGUID() << "," << parentId << "," << runningId << "," << item->getID() << "," << (int32_t)item->getSubType() << "," << db->escapeBlob(attributes, attributesSize); + + if (!query_insert.addRow(stream)) { + return false; + } + } + } + + return query_insert.execute(); +} + +bool IOLoginData::savePlayer(Player* player) +{ + if (player->getHealth() <= 0) { + player->changeHealth(1); + } + + Database* db = Database::getInstance(); + + DBQuery query; + query << "SELECT `save` FROM `players` WHERE `id` = " << player->getGUID(); + + DBResult* result = db->storeQuery(query.str()); + + if (!result) { + return false; + } + + if (result->getDataInt("save") == 0) { + db->freeResult(result); + query.str(""); + query << "UPDATE `players` SET `lastlogin` = " << player->lastLoginSaved << ", `lastip` = " << player->lastIP << " WHERE `id` = " << player->getGUID(); + return db->executeQuery(query.str()); + } + + db->freeResult(result); + + //serialize conditions + PropWriteStream propWriteStream; + + for (ConditionList::const_iterator it = player->conditions.begin(); it != player->conditions.end(); ++it) { + Condition* condition = *it; + + if (condition->isPersistent()) { + if (!condition->serialize(propWriteStream)) { + return false; + } + + propWriteStream.ADD_UCHAR(CONDITIONATTR_END); + } + } + + uint32_t conditionsSize = 0; + const char* conditions = propWriteStream.getStream(conditionsSize); + + //First, an UPDATE query to write the player itself + query.str(""); + query << "UPDATE `players` SET "; + query << "`level` = " << player->level << ", "; + query << "`group_id` = " << player->groupId << ", "; + query << "`vocation` = " << (int32_t)player->getVocationId() << ", "; + query << "`health` = " << player->health << ", "; + query << "`healthmax` = " << player->healthMax << ", "; + query << "`experience` = " << player->experience << ", "; + query << "`lookbody` = " << (int32_t)player->defaultOutfit.lookBody << ", "; + query << "`lookfeet` = " << (int32_t)player->defaultOutfit.lookFeet << ", "; + query << "`lookhead` = " << (int32_t)player->defaultOutfit.lookHead << ", "; + query << "`looklegs` = " << (int32_t)player->defaultOutfit.lookLegs << ", "; + query << "`looktype` = " << (int32_t)player->defaultOutfit.lookType << ", "; + query << "`lookaddons` = " << (int32_t)player->defaultOutfit.lookAddons << ", "; + query << "`maglevel` = " << player->magLevel << ", "; + query << "`mana` = " << player->mana << ", "; + query << "`manamax` = " << player->manaMax << ", "; + query << "`manaspent` = " << player->manaSpent << ", "; + query << "`soul` = " << player->soul << ", "; + query << "`town_id` = " << player->town << ", "; + + const Position& loginPosition = player->getLoginPosition(); + query << "`posx` = " << loginPosition.x << ", "; + query << "`posy` = " << loginPosition.y << ", "; + query << "`posz` = " << loginPosition.z << ", "; + + query << "`cap` = " << player->getCapacity() << ", "; + query << "`sex` = " << player->sex << ", "; + + if (player->lastLoginSaved != 0) { + query << "`lastlogin` = " << player->lastLoginSaved << ", "; + } + + if (player->lastIP != 0) { + query << "`lastip` = " << player->lastIP << ", "; + } + + query << "`conditions` = " << db->escapeBlob(conditions, conditionsSize) << ", "; + + if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { + int32_t skullTime = 0; + + if (player->skullTicks > 0) { + skullTime = time(NULL) + player->skullTicks / 1000; + } + + query << "`skulltime` = " << skullTime << ", "; + int32_t skull = 0; + + if (player->skull == SKULL_RED) { + skull = SKULL_RED; + } else if (player->skull == SKULL_BLACK) { + skull = SKULL_BLACK; + } + + query << "`skull` = " << skull << ", "; + } + + query << "`lastlogout` = " << player->getLastLogout() << ", "; + query << "`balance` = " << player->bankBalance << ", "; + query << "`offlinetraining_time` = " << player->getOfflineTrainingTime() / 1000 << ", "; + query << "`offlinetraining_skill` = " << player->getOfflineTrainingSkill() << ", "; + query << "`stamina` = " << player->getStaminaMinutes() << ", "; + query << "`blessings` = " << player->blessings << ", "; + query << "`onlinetime` = `onlinetime` + " << (time(NULL) - player->lastLoginSaved); + query << " WHERE `id` = " << player->getGUID(); + + DBTransaction transaction(db); + + if (!transaction.begin()) { + return false; + } + + if (!db->executeQuery(query.str())) { + return false; + } + + // skills + for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; i++) { + query.str(""); + query << "UPDATE `player_skills` SET `value` = " << player->skills[i][SKILL_LEVEL] << ", `count` = " << player->skills[i][SKILL_TRIES] << " WHERE `player_id` = " << player->getGUID() << " AND `skillid` = " << i; + + if (!db->executeQuery(query.str())) { + return false; + } + } + + // learned spells + query.str(""); + query << "DELETE FROM `player_spells` WHERE `player_id` = " << player->getGUID(); + + if (!db->executeQuery(query.str())) { + return false; + } + + query.str(""); + + DBInsert stmt(db); + stmt.setQuery("INSERT INTO `player_spells` (`player_id`, `name` ) VALUES "); + + for (LearnedInstantSpellList::const_iterator it = player->learnedInstantSpellList.begin(); + it != player->learnedInstantSpellList.end(); ++it) { + query << player->getGUID() << "," << db->escapeString(*it); + + if (!stmt.addRow(query)) { + return false; + } + } + + if (!stmt.execute()) { + return false; + } + + //item saving + query << "DELETE FROM `player_items` WHERE `player_id` = " << player->getGUID(); + + if (!db->executeQuery(query.str())) { + return false; + } + + stmt.setQuery("INSERT INTO `player_items` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); + + ItemBlockList itemList; + Item* item; + + for (int32_t slotId = 1; slotId <= 10; ++slotId) { + if ((item = player->inventory[slotId])) { + itemList.push_back(itemBlock(slotId, item)); + } + } + + if (!saveItems(player, itemList, stmt)) { + return false; + } + + if (player->depotChange) { + //save depot items + query.str(""); + query << "DELETE FROM `player_depotitems` WHERE `player_id` = " << player->getGUID(); + + if (!db->executeQuery(query.str())) { + return false; + } + + stmt.setQuery("INSERT INTO `player_depotitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); + itemList.clear(); + + for (DepotMap::iterator it = player->depotChests.begin(); it != player->depotChests.end() ; ++it) { + DepotChest* depotChest = it->second; + + for (ItemDeque::const_iterator iit = depotChest->getItems(), end = depotChest->getEnd(); iit != end; ++iit) { + itemList.push_back(itemBlock(it->first, *iit)); + } + } + + if (!saveItems(player, itemList, stmt)) { + return false; + } + } + + //save inbox items + query.str(""); + query << "DELETE FROM `player_inboxitems` WHERE `player_id` = " << player->getGUID(); + + if (!db->executeQuery(query.str())) { + return false; + } + + stmt.setQuery("INSERT INTO `player_inboxitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); + itemList.clear(); + + for (ItemDeque::const_iterator it = player->getInbox()->getItems(), end = player->getInbox()->getEnd(); it != end; ++it) { + itemList.push_back(itemBlock(0, *it)); + } + + if (!saveItems(player, itemList, stmt)) { + return false; + } + + query.str(""); + query << "DELETE FROM `player_storage` WHERE `player_id` = " << player->getGUID(); + + if (!db->executeQuery(query.str())) { + return false; + } + + query.str(""); + + stmt.setQuery("INSERT INTO `player_storage` (`player_id`, `key`, `value`) VALUES "); + player->genReservedStorageRange(); + + for (StorageMap::const_iterator cit = player->getStorageIteratorBegin(), end = player->getStorageIteratorEnd(); cit != end; ++cit) { + query << player->getGUID() << "," << cit->first << "," << cit->second; + + if (!stmt.addRow(query)) { + return false; + } + } + + if (!stmt.execute()) { + return false; + } + + //End the transaction + return transaction.commit(); +} + +bool IOLoginData::getNameByGuid(uint32_t guid, std::string& name) +{ + NameCacheMap::const_iterator it = nameCacheMap.find(guid); + + if (it != nameCacheMap.end()) { + name = it->second; + return true; + } + + DBQuery query; + query << "SELECT `name` FROM `players` WHERE `id` = " << guid; + + Database* db = Database::getInstance(); + + DBResult* result; + + if (!(result = db->storeQuery(query.str()))) { + return false; + } + + name = result->getDataString("name"); + db->freeResult(result); + + nameCacheMap[guid] = name; + return true; +} + +bool IOLoginData::getGuidByName(uint32_t& guid, std::string& name) +{ + GuidCacheMap::const_iterator it = guidCacheMap.find(name); + + if (it != guidCacheMap.end()) { + name = it->first; + guid = it->second; + return true; + } + + Database* db = Database::getInstance(); + + DBQuery query; + DBResult* result; + + query << "SELECT `id`, `name` FROM `players` WHERE `name` = " << db->escapeString(name); + + if (!(result = db->storeQuery(query.str()))) { + return false; + } + + name = result->getDataString("name"); + guid = result->getDataInt("id"); + db->freeResult(result); + + guidCacheMap[name] = guid; + return true; +} + +bool IOLoginData::getGuidByNameEx(uint32_t& guid, bool& specialVip, std::string& name) +{ + Database* db = Database::getInstance(); + + DBQuery query; + DBResult* result; + + query << "SELECT `name`, `id`, `group_id`, `account_id` FROM `players` WHERE `name` = " << db->escapeString(name); + + if (!(result = db->storeQuery(query.str()))) { + return false; + } + + name = result->getDataString("name"); + guid = result->getDataInt("id"); + const PlayerGroup* accountGroup = getPlayerGroupByAccount(result->getDataInt("account_id")); + const PlayerGroup* playerGroup = getPlayerGroup(result->getDataInt("group_id")); + db->freeResult(result); + + uint64_t flags = 0; + + if (playerGroup) { + flags |= playerGroup->m_flags; + } + + if (accountGroup) { + flags |= accountGroup->m_flags; + } + + specialVip = (0 != (flags & ((uint64_t)1 << PlayerFlag_SpecialVIP))); + return true; +} + +bool IOLoginData::playerExists(const std::string& name) +{ + Database* db = Database::getInstance(); + + DBQuery query; + DBResult* result; + + query << "SELECT `id` FROM `players` WHERE `name` = " << db->escapeString(name); + + if ((result = db->storeQuery(query.str()))) { + db->freeResult(result); + return true; + } + + return false; +} + +bool IOLoginData::playerExists(std::string& name) +{ + Database* db = Database::getInstance(); + + DBQuery query; + DBResult* result; + + query << "SELECT `name` FROM `players` WHERE `name` = " << db->escapeString(name); + + if ((result = db->storeQuery(query.str()))) { + name = result->getDataString("name"); + db->freeResult(result); + return true; + } + + return false; +} + +bool IOLoginData::playerExists(uint32_t guid) +{ + Database* db = Database::getInstance(); + + DBQuery query; + DBResult* result; + + query << "SELECT `id` FROM `players` WHERE `id` = " << guid; + + if (!(result = db->storeQuery(query.str()))) { + return false; + } + + db->freeResult(result); + return true; +} + +const PlayerGroup* IOLoginData::getPlayerGroup(uint32_t groupid) +{ + PlayerGroupMap::const_iterator it = playerGroupMap.find(groupid); + + if (it != playerGroupMap.end()) { + return it->second; + } + + Database* db = Database::getInstance(); + + DBQuery query; + DBResult* result; + + query << "SELECT `name`, `flags`, `access`, `maxdepotitems`, `maxviplist` FROM `groups` WHERE `id` = " << groupid; + + if (!(result = db->storeQuery(query.str()))) { + return NULL; + } + + PlayerGroup* group = new PlayerGroup; + group->m_name = result->getDataString("name"); + group->m_flags = result->getDataLong("flags"); + group->m_access = result->getDataInt("access"); + group->m_maxdepotitems = result->getDataInt("maxdepotitems"); + group->m_maxviplist = result->getDataInt("maxviplist"); + db->freeResult(result); + + playerGroupMap[groupid] = group; + return group; +} + +const PlayerGroup* IOLoginData::getPlayerGroupByAccount(uint32_t accno) +{ + Database* db = Database::getInstance(); + DBQuery query; + DBResult* result; + + query << "SELECT `group_id` FROM `accounts` WHERE `id` = " << accno; + + if (!(result = db->storeQuery(query.str()))) { + return NULL; + } + + uint32_t groupId = result->getDataInt("group_id"); + db->freeResult(result); + return getPlayerGroup(groupId); +} + +void IOLoginData::loadItems(ItemMap& itemMap, DBResult* result) +{ + do { + int32_t sid = result->getDataInt("sid"); + int32_t pid = result->getDataInt("pid"); + int32_t type = result->getDataInt("itemtype"); + int32_t count = result->getDataInt("count"); + + unsigned long attrSize = 0; + const char* attr = result->getDataStream("attributes", attrSize); + + PropStream propStream; + propStream.init(attr, attrSize); + + Item* item = Item::CreateItem(type, count); + + if (item) { + if (!item->unserializeAttr(propStream)) { + std::cout << "WARNING: Serialize error in IOLoginData::loadItems" << std::endl; + } + + std::pair pair(item, pid); + itemMap[sid] = pair; + } + } while (result->next()); +} + +bool IOLoginData::changeName(uint32_t guid, const std::string& newName) +{ + Database* db = Database::getInstance(); + DBQuery query; + query << "UPDATE `players` SET `name` = " << db->escapeString(newName) << " WHERE `id` = " << guid; + + if (!db->executeQuery(query.str())) { + return false; + } + + nameCacheMap[guid] = newName; + return true; +} + +uint32_t IOLoginData::getAccountNumberByName(const std::string& name) +{ + Database* db = Database::getInstance(); + DBQuery query; + DBResult* result; + query << "SELECT `account_id` FROM `players` WHERE `name` = " << db->escapeString(name); + + if (!(result = db->storeQuery(query.str()))) { + return 0; + } + + uint32_t accountId = result->getDataInt("account_id"); + db->freeResult(result); + return accountId; +} + +void IOLoginData::increaseBankBalance(uint32_t guid, uint64_t bankBalance) +{ + DBQuery query; + query << "UPDATE `players` SET `balance` = `balance` + " << bankBalance << " WHERE `id` = " << guid; + Database::getInstance()->executeQuery(query.str()); +} + +time_t IOLoginData::getLastLoginSaved(uint32_t guid) +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "SELECT `lastlogin` FROM `players` WHERE `id` = " << guid; + DBResult* result; + time_t lastLoginSaved; + + if ((result = db->storeQuery(query.str()))) { + lastLoginSaved = result->getDataLong("lastlogin"); + db->freeResult(result); + } else { + lastLoginSaved = 0; + } + + return lastLoginSaved; +} + +bool IOLoginData::isPendingDeletion(const std::string& characterName) +{ + Database* db = Database::getInstance(); + + DBQuery query; + DBResult* result; + query << "SELECT `deletion` FROM `players` WHERE `name` = " << db->escapeString(characterName); + + if ((result = db->storeQuery(query.str()))) { + uint32_t deletion = result->getDataInt("deletion"); + db->freeResult(result); + return deletion != 0; + } + + return false; +} + +void IOLoginData::updateHouseOwners() +{ + Database* db = Database::getInstance(); + + DBQuery query; + DBResult* result; + query << "SELECT `id`, `highest_bidder`, `last_bid` FROM `houses` WHERE `owner` = 0 AND `bid_end` != 0 AND `bid_end` < " << time(NULL); + + if ((result = db->storeQuery(query.str()))) { + do { + House* house = Houses::getInstance().getHouse(result->getDataInt("id")); + + if (house) { + DBResult* result2; + query.str(""); + query << "SELECT `balance` FROM `players` WHERE `id` = " << result->getDataInt("highest_bidder"); + bool canPay = false; + + if ((result2 = db->storeQuery(query.str()))) { + if (result2->getDataInt("balance") >= result->getDataInt("last_bid")) { + canPay = true; + } + + db->freeResult(result2); + } + + if (canPay) { + query.str(""); + query << "UPDATE `players` SET `balance` = `balance` - " << result->getDataInt("last_bid") << " WHERE `id` = " << result->getDataInt("highest_bidder"); + db->executeQuery(query.str()); + + house->setHouseOwner(result->getDataInt("highest_bidder")); + } + + query.str(""); + query << "UPDATE `houses` SET `last_bid` = 0, `bid_end` = 0, `highest_bidder` = 0, `bid` = 0 WHERE `id` = " << result->getDataInt("id"); + db->executeQuery(query.str()); + } + } while (result->next()); + + db->freeResult(result); + } +} + +bool IOLoginData::hasBiddedOnHouse(uint32_t guid) +{ + Database* db = Database::getInstance(); + + DBQuery query; + DBResult* result; + query << "SELECT `id` FROM `houses` WHERE `highest_bidder` = " << guid << " LIMIT 1"; + + if ((result = db->storeQuery(query.str()))) { + db->freeResult(result); + return true; + } + + return false; +} + +std::list IOLoginData::getVIPEntries(uint32_t accountId) +{ + std::list entries; + + Database* db = Database::getInstance(); + + DBQuery query; + query << "SELECT `player_id`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `name`, `description`, `icon`, `notify` FROM `account_viplist` WHERE `account_id` = " << accountId; + + DBResult* result; + + if ((result = db->storeQuery(query.str()))) { + do { + VIPEntry entry; + entry.guid = result->getDataInt("player_id"); + entry.name = result->getDataString("name"); + entry.description = result->getDataString("description"); + entry.icon = result->getDataInt("icon"); + entry.notify = result->getDataInt("notify") != 0; + entries.push_back(entry); + } while (result->next()); + + db->freeResult(result); + } + + return entries; +} + +void IOLoginData::addVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon, bool notify) +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "INSERT INTO `account_viplist` (`account_id`, `player_id`, `description`, `icon`, `notify`) VALUES (" << accountId << ", " << guid << ", " << db->escapeString(description) << ", " << icon << ", " << notify << ")"; + db->executeQuery(query.str()); +} + +void IOLoginData::editVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon, bool notify) +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "UPDATE `account_viplist` SET `description` = " << db->escapeString(description) << ", `icon` = " << icon << ", `notify` = " << notify << " WHERE `account_id` = " << accountId << " AND `player_id` = " << guid; + db->executeQuery(query.str()); +} + +void IOLoginData::removeVIPEntry(uint32_t accountId, uint32_t guid) +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "DELETE FROM `account_viplist` WHERE `account_id` = " << accountId << " AND `player_id` = " << guid; + db->executeQuery(query.str()); +} diff --git a/src/iologindata.h b/src/iologindata.h new file mode 100644 index 0000000000..07a7eaa559 --- /dev/null +++ b/src/iologindata.h @@ -0,0 +1,109 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __IOLOGINDATA_H +#define __IOLOGINDATA_H + +#include +#include "account.h" +#include "player.h" +#include "database.h" + +struct PlayerGroup { + std::string m_name; + uint64_t m_flags; + uint32_t m_access; + uint32_t m_maxdepotitems; + uint32_t m_maxviplist; +}; + +typedef std::pair itemBlock; +typedef std::list ItemBlockList; + +class IOLoginData +{ + public: + IOLoginData() {} + ~IOLoginData() { + for (PlayerGroupMap::iterator it = playerGroupMap.begin(); it != playerGroupMap.end(); ++it) { + delete it->second; + } + } + + static IOLoginData* getInstance() { + static IOLoginData instance; + return &instance; + } + + Account loadAccount(uint32_t accno); + Account loadAccount(const std::string& name); + bool saveAccount(const Account& acc); + bool getPassword(const std::string& accname, const std::string& name, std::string& password, uint32_t& accNumber); + AccountType_t getAccountType(uint32_t accountId); + void setAccountType(uint32_t accountId, AccountType_t accountType); + + bool updateOnlineStatus(uint32_t guid, bool login); + bool resetOnlineStatus(); + + bool loadPlayer(Player* player, const std::string& name, bool preload = false); + bool savePlayer(Player* player); + bool getGuidByName(uint32_t& guid, std::string& name); + bool getGuidByNameEx(uint32_t& guid, bool& specialVip, std::string& name); + bool getNameByGuid(uint32_t guid, std::string& name); + bool playerExists(const std::string& name); + bool playerExists(std::string& name); + bool playerExists(uint32_t guid); + bool changeName(uint32_t guid, const std::string& newName); + uint32_t getAccountNumberByName(const std::string& name); + bool addStorageValue(uint32_t guid, uint32_t storageKey, uint32_t storageValue); + const PlayerGroup* getPlayerGroup(uint32_t groupid); + uint32_t getLastIPByName(const std::string& name); + void increaseBankBalance(uint32_t guid, uint64_t bankBalance); + time_t getLastLoginSaved(uint32_t guid); + bool isPendingDeletion(const std::string& characterName); + bool hasBiddedOnHouse(uint32_t guid); + void updateHouseOwners(); + + std::list getVIPEntries(uint32_t accountId); + void addVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon, bool notify); + void editVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon, bool notify); + void removeVIPEntry(uint32_t accountId, uint32_t guid); + + protected: + const PlayerGroup* getPlayerGroupByAccount(uint32_t accno); + struct StringCompareCase { + bool operator()(const std::string& l, const std::string& r) const { + return strcasecmp(l.c_str(), r.c_str()) < 0; + } + }; + + typedef std::map > ItemMap; + + void loadItems(ItemMap& itemMap, DBResult* result); + bool saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert); + + typedef std::map NameCacheMap; + typedef std::map GuidCacheMap; + typedef std::map PlayerGroupMap; + + PlayerGroupMap playerGroupMap; + NameCacheMap nameCacheMap; + GuidCacheMap guidCacheMap; +}; + +#endif diff --git a/src/iomap.cpp b/src/iomap.cpp new file mode 100644 index 0000000000..cad5e88e38 --- /dev/null +++ b/src/iomap.cpp @@ -0,0 +1,528 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "iomap.h" +#include "game.h" +#include "map.h" + +#include "tile.h" +#include "item.h" +#include "container.h" +#include "teleport.h" +#include "fileloader.h" +#include "town.h" + +#include "beds.h" + +typedef uint8_t attribute_t; +typedef uint32_t flags_t; + +extern Game g_game; + +/* + OTBM_ROOTV1 + | + |--- OTBM_MAP_DATA + | | + | |--- OTBM_TILE_AREA + | | |--- OTBM_TILE + | | |--- OTBM_TILE_SQUARE (not implemented) + | | |--- OTBM_TILE_REF (not implemented) + | | |--- OTBM_HOUSETILE + | | + | |--- OTBM_SPAWNS (not implemented) + | | |--- OTBM_SPAWN_AREA (not implemented) + | | |--- OTBM_MONSTER (not implemented) + | | + | |--- OTBM_TOWNS + | | |--- OTBM_TOWN + | | + | |--- OTBM_WAYPOINTS + | |--- OTBM_WAYPOINT + | + |--- OTBM_ITEM_DEF (not implemented) +*/ + +Tile* IOMap::createTile(Item*& ground, Item* item, int px, int py, int pz) +{ + Tile* tile; + + if (ground) { + if ((item && item->isBlocking()) || ground->isBlocking()) { + tile = new StaticTile(px, py, pz); + } else { + tile = new DynamicTile(px, py, pz); + } + + tile->__internalAddThing(ground); + ground->__startDecaying(); + ground = NULL; + } else { + tile = new StaticTile(px, py, pz); + } + + return tile; +} + +bool IOMap::loadMap(Map* map, const std::string& identifier) +{ + int64_t start = OTSYS_TIME(); + + FileLoader f; + + if (!f.openFile(identifier.c_str(), "OTBM", false, true)) { + std::ostringstream ss; + ss << "Could not open the file " << identifier << "."; + setLastErrorString(ss.str()); + return false; + } + + uint32_t type; + PropStream propStream; + + NODE root = f.getChildNode((NODE)NULL, type); + + if (!f.getProps(root, propStream)) { + setLastErrorString("Could not read root property."); + return false; + } + + OTBM_root_header* root_header; + + if (!propStream.GET_STRUCT(root_header)) { + setLastErrorString("Could not read header."); + return false; + } + + uint32_t headerVersion = root_header->version; + + if (headerVersion <= 0) { + //In otbm version 1 the count variable after splashes/fluidcontainers and stackables + //are saved as attributes instead, this solves alot of problems with items + //that is changed (stackable/charges/fluidcontainer/splash) during an update. + setLastErrorString("This map need to be upgraded by using the latest map editor version to be able to load correctly."); + return false; + } + + if (headerVersion > 2) { + setLastErrorString("Unknown OTBM version detected."); + return false; + } + + if (root_header->majorVersionItems < 3) { + setLastErrorString("This map need to be upgraded by using the latest map editor version to be able to load correctly."); + return false; + } + + if (root_header->majorVersionItems > (uint32_t)Items::dwMajorVersion) { + setLastErrorString("The map was saved with a different items.otb version, an upgraded items.otb is required."); + return false; + } + + if (root_header->minorVersionItems < CLIENT_VERSION_810) { + setLastErrorString("This map needs to be updated."); + return false; + } + + if (root_header->minorVersionItems > (uint32_t)Items::dwMinorVersion) { + std::cout << "Warning: [OTBM loader] This map needs an updated items.otb." << std::endl; + } + + std::cout << "> Map size: " << root_header->width << "x" << root_header->height << "." << std::endl; + map->mapWidth = root_header->width; + map->mapHeight = root_header->height; + + NODE nodeMap = f.getChildNode(root, type); + + if (type != OTBM_MAP_DATA) { + setLastErrorString("Could not read data node."); + return false; + } + + if (!f.getProps(nodeMap, propStream)) { + setLastErrorString("Could not read map data attributes."); + return false; + } + + unsigned char attribute; + std::string mapDescription; + std::string tmp; + + while (propStream.GET_UCHAR(attribute)) { + switch (attribute) { + case OTBM_ATTR_DESCRIPTION: + + if (!propStream.GET_STRING(mapDescription)) { + setLastErrorString("Invalid description tag."); + return false; + } + + break; + + case OTBM_ATTR_EXT_SPAWN_FILE: + + if (!propStream.GET_STRING(tmp)) { + setLastErrorString("Invalid spawn tag."); + return false; + } + + map->spawnfile = identifier.substr(0, identifier.rfind('/') + 1); + map->spawnfile += tmp; + break; + + case OTBM_ATTR_EXT_HOUSE_FILE: + + if (!propStream.GET_STRING(tmp)) { + setLastErrorString("Invalid house tag."); + return false; + } + + map->housefile = identifier.substr(0, identifier.rfind('/') + 1); + map->housefile += tmp; + break; + + default: + setLastErrorString("Unknown header node."); + return false; + } + } + + NODE nodeMapData = f.getChildNode(nodeMap, type); + + while (nodeMapData != NO_NODE) { + if (f.getError() != ERROR_NONE) { + setLastErrorString("Invalid map node."); + return false; + } + + if (type == OTBM_TILE_AREA) { + if (!f.getProps(nodeMapData, propStream)) { + setLastErrorString("Invalid map node."); + return false; + } + + OTBM_Destination_coords* area_coord; + + if (!propStream.GET_STRUCT(area_coord)) { + setLastErrorString("Invalid map node."); + return false; + } + + int32_t base_x, base_y, base_z; + base_x = area_coord->_x; + base_y = area_coord->_y; + base_z = area_coord->_z; + + NODE nodeTile = f.getChildNode(nodeMapData, type); + + while (nodeTile != NO_NODE) { + if (f.getError() != ERROR_NONE) { + setLastErrorString("Could not read node data."); + return false; + } + + if (type == OTBM_TILE || type == OTBM_HOUSETILE) { + if (!f.getProps(nodeTile, propStream)) { + setLastErrorString("Could not read node data."); + return false; + } + + unsigned short px, py, pz; + OTBM_Tile_coords* tile_coord; + + if (!propStream.GET_STRUCT(tile_coord)) { + setLastErrorString("Could not read tile position."); + return false; + } + + px = base_x + tile_coord->_x; + py = base_y + tile_coord->_y; + pz = base_z; + + bool isHouseTile = false; + House* house = NULL; + Tile* tile = NULL; + Item* ground_item = NULL; + uint32_t tileflags = TILESTATE_NONE; + + if (type == OTBM_HOUSETILE) { + uint32_t _houseid; + + if (!propStream.GET_ULONG(_houseid)) { + std::ostringstream ss; + ss << "[x:" << px << ", y:" << py << ", z:" << pz << "] " << "Could not read house id."; + setLastErrorString(ss.str()); + return false; + } + + house = Houses::getInstance().getHouse(_houseid, true); + + if (!house) { + std::ostringstream ss; + ss << "[x:" << px << ", y:" << py << ", z:" << pz << "] " << "Could not create house id: " << _houseid; + setLastErrorString(ss.str()); + return false; + } + + tile = new HouseTile(px, py, pz, house); + house->addTile(static_cast(tile)); + isHouseTile = true; + } + + //read tile attributes + unsigned char attribute; + + while (propStream.GET_UCHAR(attribute)) { + switch (attribute) { + case OTBM_ATTR_TILE_FLAGS: { + uint32_t flags; + + if (!propStream.GET_ULONG(flags)) { + std::ostringstream ss; + ss << "[x:" << px << ", y:" << py << ", z:" << pz << "] " << "Failed to read tile flags."; + setLastErrorString(ss.str()); + return false; + } + + if ((flags & TILESTATE_PROTECTIONZONE) == TILESTATE_PROTECTIONZONE) { + tileflags |= TILESTATE_PROTECTIONZONE; + } else if ((flags & TILESTATE_NOPVPZONE) == TILESTATE_NOPVPZONE) { + tileflags |= TILESTATE_NOPVPZONE; + } else if ((flags & TILESTATE_PVPZONE) == TILESTATE_PVPZONE) { + tileflags |= TILESTATE_PVPZONE; + } + + if ((flags & TILESTATE_NOLOGOUT) == TILESTATE_NOLOGOUT) { + tileflags |= TILESTATE_NOLOGOUT; + } + + break; + } + + case OTBM_ATTR_ITEM: { + Item* item = Item::CreateItem(propStream); + + if (!item) { + std::ostringstream ss; + ss << "[x:" << px << ", y:" << py << ", z:" << pz << "] " << "Failed to create item."; + setLastErrorString(ss.str()); + return false; + } + + if (isHouseTile && !item->isNotMoveable()) { + std::cout << "Warning: [OTBM loader] Moveable item with ID: " << item->getID() << ", in house: " << house->getHouseId() << ", at position [x: " << px << ", y: " << py << ", z: " << pz << "]." << std::endl; + delete item; + item = NULL; + } else { + if (item->getItemCount() <= 0) { + item->setItemCount(1); + } + + if (tile) { + tile->__internalAddThing(item); + item->__startDecaying(); + item->setLoadedFromMap(true); + } else if (item->isGroundTile()) { + delete ground_item; + ground_item = item; + } else { + tile = createTile(ground_item, item, px, py, pz); + tile->__internalAddThing(item); + item->__startDecaying(); + item->setLoadedFromMap(true); + } + } + + break; + } + + default: + std::ostringstream ss; + ss << "[x:" << px << ", y:" << py << ", z:" << pz << "] " << "Unknown tile attribute."; + setLastErrorString(ss.str()); + return false; + } + } + + NODE nodeItem = f.getChildNode(nodeTile, type); + + while (nodeItem) { + if (type == OTBM_ITEM) { + PropStream propStream; + f.getProps(nodeItem, propStream); + + Item* item = Item::CreateItem(propStream); + + if (!item) { + std::ostringstream ss; + ss << "[x:" << px << ", y:" << py << ", z:" << pz << "] " << "Failed to create item."; + setLastErrorString(ss.str()); + return false; + } + + if (item->unserializeItemNode(f, nodeItem, propStream)) { + if (isHouseTile && !item->isNotMoveable()) { + std::cout << "Warning: [OTBM loader] Moveable item with ID: " << item->getID() << ", in house: " << house->getHouseId() << ", at position [x: " << px << ", y: " << py << ", z: " << pz << "]." << std::endl; + delete item; + } else { + if (item->getItemCount() <= 0) { + item->setItemCount(1); + } + + if (tile) { + tile->__internalAddThing(item); + item->__startDecaying(); + item->setLoadedFromMap(true); + } else if (item->isGroundTile()) { + delete ground_item; + ground_item = item; + } else { + tile = createTile(ground_item, item, px, py, pz); + tile->__internalAddThing(item); + item->__startDecaying(); + item->setLoadedFromMap(true); + } + } + } else { + std::ostringstream ss; + ss << "[x:" << px << ", y:" << py << ", z:" << pz << "] " << "Failed to load item " << item->getID() << "."; + setLastErrorString(ss.str()); + delete item; + return false; + } + } else { + std::ostringstream ss; + ss << "[x:" << px << ", y:" << py << ", z:" << pz << "] " << "Unknown node type."; + setLastErrorString(ss.str()); + } + + nodeItem = f.getNextNode(nodeItem, type); + } + + if (!tile) { + tile = createTile(ground_item, NULL, px, py, pz); + } + + tile->setFlag((tileflags_t)tileflags); + + map->setTile(px, py, pz, tile); + } else { + setLastErrorString("Unknown tile node."); + return false; + } + + nodeTile = f.getNextNode(nodeTile, type); + } + } else if (type == OTBM_TOWNS) { + NODE nodeTown = f.getChildNode(nodeMapData, type); + + while (nodeTown != NO_NODE) { + if (type == OTBM_TOWN) { + if (!f.getProps(nodeTown, propStream)) { + setLastErrorString("Could not read town data."); + return false; + } + + uint32_t townid = 0; + + if (!propStream.GET_ULONG(townid)) { + setLastErrorString("Could not read town id."); + return false; + } + + Town* town = Towns::getInstance().getTown(townid); + + if (!town) { + town = new Town(townid); + Towns::getInstance().addTown(townid, town); + } + + std::string townName = ""; + + if (!propStream.GET_STRING(townName)) { + setLastErrorString("Could not read town name."); + return false; + } + + town->setName(townName); + + OTBM_Destination_coords* town_coords; + + if (!propStream.GET_STRUCT(town_coords)) { + setLastErrorString("Could not read town coordinates."); + return false; + } + + Position pos; + pos.x = town_coords->_x; + pos.y = town_coords->_y; + pos.z = town_coords->_z; + town->setTemplePos(pos); + } else { + setLastErrorString("Unknown town node."); + return false; + } + + nodeTown = f.getNextNode(nodeTown, type); + } + } else if (type == OTBM_WAYPOINTS && headerVersion > 1) { + NODE nodeWaypoint = f.getChildNode(nodeMapData, type); + + while (nodeWaypoint != NO_NODE) { + if (type == OTBM_WAYPOINT) { + if (!f.getProps(nodeWaypoint, propStream)) { + setLastErrorString("Could not read waypoint data."); + return false; + } + + std::string name; + + if (!propStream.GET_STRING(name)) { + setLastErrorString("Could not read waypoint name."); + return false; + } + + OTBM_Destination_coords* waypoint_coords; + + if (!propStream.GET_STRUCT(waypoint_coords)) { + setLastErrorString("Could not read waypoint coordinates."); + return false; + } + + map->waypoints.addWaypoint(WaypointPtr(new Waypoint(name, + Position(waypoint_coords->_x, waypoint_coords->_y, waypoint_coords->_z)))); + } else { + setLastErrorString("Unknown waypoint node."); + return false; + } + + nodeWaypoint = f.getNextNode(nodeWaypoint, type); + } + } else { + setLastErrorString("Unknown map node."); + return false; + } + + nodeMapData = f.getNextNode(nodeMapData, type); + } + + std::cout << "> Map loading time: " << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl; + return true; +} diff --git a/src/iomap.h b/src/iomap.h new file mode 100644 index 0000000000..3af6b5117d --- /dev/null +++ b/src/iomap.h @@ -0,0 +1,155 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_IOMAP_H__ +#define __OTSERV_IOMAP_H__ + +#include "item.h" +#include "map.h" +#include "house.h" +#include "spawn.h" +#include "status.h" +#include "configmanager.h" + +extern ConfigManager g_config; + +enum OTBM_AttrTypes_t { + OTBM_ATTR_DESCRIPTION = 1, + OTBM_ATTR_EXT_FILE = 2, + OTBM_ATTR_TILE_FLAGS = 3, + OTBM_ATTR_ACTION_ID = 4, + OTBM_ATTR_UNIQUE_ID = 5, + OTBM_ATTR_TEXT = 6, + OTBM_ATTR_DESC = 7, + OTBM_ATTR_TELE_DEST = 8, + OTBM_ATTR_ITEM = 9, + OTBM_ATTR_DEPOT_ID = 10, + OTBM_ATTR_EXT_SPAWN_FILE = 11, + OTBM_ATTR_RUNE_CHARGES = 12, + OTBM_ATTR_EXT_HOUSE_FILE = 13, + OTBM_ATTR_HOUSEDOORID = 14, + OTBM_ATTR_COUNT = 15, + OTBM_ATTR_DURATION = 16, + OTBM_ATTR_DECAYING_STATE = 17, + OTBM_ATTR_WRITTENDATE = 18, + OTBM_ATTR_WRITTENBY = 19, + OTBM_ATTR_SLEEPERGUID = 20, + OTBM_ATTR_SLEEPSTART = 21, + OTBM_ATTR_CHARGES = 22 +}; + +enum OTBM_NodeTypes_t { + OTBM_ROOTV1 = 1, + OTBM_MAP_DATA = 2, + OTBM_ITEM_DEF = 3, + OTBM_TILE_AREA = 4, + OTBM_TILE = 5, + OTBM_ITEM = 6, + OTBM_TILE_SQUARE = 7, + OTBM_TILE_REF = 8, + OTBM_SPAWNS = 9, + OTBM_SPAWN_AREA = 10, + OTBM_MONSTER = 11, + OTBM_TOWNS = 12, + OTBM_TOWN = 13, + OTBM_HOUSETILE = 14, + OTBM_WAYPOINTS = 15, + OTBM_WAYPOINT = 16 +}; + +#pragma pack(1) + +struct OTBM_root_header { + uint32_t version; + uint16_t width; + uint16_t height; + uint32_t majorVersionItems; + uint32_t minorVersionItems; +}; + +struct OTBM_Destination_coords { + uint16_t _x; + uint16_t _y; + uint8_t _z; +}; + +struct OTBM_Tile_coords { + uint8_t _x; + uint8_t _y; +}; + +struct OTBM_HouseTile_coords { + uint8_t _x; + uint8_t _y; + uint32_t _houseid; +}; + +#pragma pack() + +class IOMap +{ + static Tile* createTile(Item*& ground, Item* item, int px, int py, int pz); + public: + IOMap() {} + ~IOMap() {} + + bool loadMap(Map* map, const std::string& identifier); + + /* Load the spawns + * \param map pointer to the Map class + * \returns Returns true if the spawns were loaded successfully + */ + bool loadSpawns(Map* map) { + if (map->spawnfile.empty()) { + //OTBM file doesn't tell us about the spawnfile, + //lets guess it is mapname-spawn.xml. + map->spawnfile = g_config.getString(ConfigManager::MAP_NAME); + map->spawnfile += "-spawn.xml"; + } + + return Spawns::getInstance()->loadFromXml(map->spawnfile); + } + + /* Load the houses (not house tile-data) + * \param map pointer to the Map class + * \returns Returns true if the houses were loaded successfully + */ + bool loadHouses(Map* map) { + if (map->housefile.empty()) { + //OTBM file doesn't tell us about the housefile, + //lets guess it is mapname-house.xml. + map->housefile = g_config.getString(ConfigManager::MAP_NAME); + map->housefile += "-house.xml"; + } + + return Houses::getInstance().loadHousesXML(map->housefile); + } + + const std::string& getLastErrorString() const { + return errorString; + } + + void setLastErrorString(const std::string& _errorString) { + errorString = _errorString; + } + + protected: + std::string errorString; +}; + +#endif diff --git a/src/iomapserialize.cpp b/src/iomapserialize.cpp new file mode 100644 index 0000000000..b0c1f1f8cb --- /dev/null +++ b/src/iomapserialize.cpp @@ -0,0 +1,823 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "iomapserialize.h" +#include "house.h" +#include "configmanager.h" +#include "game.h" + +extern ConfigManager g_config; +extern Game g_game; + +bool IOMapSerialize::loadMap(Map* map) +{ + int64_t start = OTSYS_TIME(); + bool s = false; + + if (g_config.getString(ConfigManager::MAP_STORAGE_TYPE) == "binary-tilebased") { + s = true; + loadMapBinaryTileBased(map); + } else if (g_config.getString(ConfigManager::MAP_STORAGE_TYPE) == "binary") { + s = true; + loadMapBinary(map); + } else { + s = loadMapRelational(map); + } + + std::cout << "Notice: Map load (" << g_config.getString(ConfigManager::MAP_STORAGE_TYPE) << ") took: " << + (OTSYS_TIME() - start) / (1000.) << " s" << std::endl; + + return s; +} + +bool IOMapSerialize::saveMap(Map* map) +{ + int64_t start = OTSYS_TIME(); + bool s = false; + + if (g_config.getString(ConfigManager::MAP_STORAGE_TYPE) == "binary-tilebased") { + s = saveMapBinaryTileBased(map); + } else if (g_config.getString(ConfigManager::MAP_STORAGE_TYPE) == "binary") { + s = saveMapBinary(map); + } else { + s = saveMapRelational(map); + } + + std::cout << "Notice: Map save (" << g_config.getString(ConfigManager::MAP_STORAGE_TYPE) << ") took : " << + (OTSYS_TIME() - start) / (1000.) << " s" << std::endl; + + return s; +} + +bool IOMapSerialize::loadMapRelational(Map* map) +{ + Database* db = Database::getInstance(); + + for (HouseMap::iterator it = Houses::getInstance().getHouseBegin(); it != Houses::getInstance().getHouseEnd(); ++it) { + //load tile + House* house = it->second; + + for (HouseTileList::iterator it = house->getHouseTileBegin(); it != house->getHouseTileEnd(); ++it) { + loadTile(*db, *it); + } + } + + return true; +} + +bool IOMapSerialize::loadTile(Database& db, Tile* tile) +{ + typedef std::map > ItemMap; + ItemMap itemMap; + + const Position& tilePos = tile->getPosition(); + + DBQuery query; + query << "SELECT `id` FROM `tiles` WHERE `x` = " << tilePos.x + << " AND `y` = " << tilePos.y + << " AND `z` = " << tilePos.z; + + DBResult* result; + + if (!(result = db.storeQuery(query.str()))) { + return false; + } + + int tileId = result->getDataInt("id"); + db.freeResult(result); + + query.str(""); + query << "SELECT * FROM `tile_items` WHERE `tile_id` = " << tileId << " ORDER BY `sid` DESC"; + + if ((result = db.storeQuery(query.str()))) { + Item* item = NULL; + + do { + int sid = result->getDataInt("sid"); + int pid = result->getDataInt("pid"); + int type = result->getDataInt("itemtype"); + int count = result->getDataInt("count"); + item = NULL; + + unsigned long attrSize = 0; + const char* attr = result->getDataStream("attributes", attrSize); + PropStream propStream; + propStream.init(attr, attrSize); + + const ItemType& iType = Item::items[type]; + + if (iType.moveable || /* or object in a container*/ pid != 0) { + //create a new item + item = Item::CreateItem(type, count); + + if (item) { + if (item->unserializeAttr(propStream)) { + if (pid == 0) { + tile->__internalAddThing(item); + item->__startDecaying(); + } + } else { + std::cout << "WARNING: Serialize error in IOMapSerialize::loadTile()" << std::endl; + } + } else { + continue; + } + } else { + //find this type in the tile + if (const TileItemVector* items = tile->getItemList()) { + for (ItemVector::const_iterator it = items->begin(), end = items->end(); it != end; ++it) { + Item* findItem = (*it); + + if (findItem->getID() == type) { + item = findItem; + break; + } else if (iType.isDoor() && findItem->getDoor()) { + item = findItem; + break; + } else if (iType.isBed() && findItem->getBed()) { + item = findItem; + break; + } + } + } + } + + if (item) { + if (item->unserializeAttr(propStream)) { + item = g_game.transformItem(item, type); + + if (item) { + std::pair myPair(item, pid); + itemMap[sid] = myPair; + } + } else { + std::cout << "WARNING: Serialize error in IOMapSerialize::loadTile()" << std::endl; + } + } else { + std::cout << "WARNING: IOMapSerialize::loadTile(). NULL item at " << tile->getPosition() << ". type = " << type << ", sid = " << sid << ", pid = " << pid << std::endl; + } + } while (result->next()); + + db.freeResult(result); + } + + ItemMap::reverse_iterator it; + ItemMap::iterator it2; + + for (it = itemMap.rbegin(); it != itemMap.rend(); ++it) { + Item* item = it->second.first; + int pid = it->second.second; + + it2 = itemMap.find(pid); + + if (it2 != itemMap.end()) { + if (Container* container = it2->second.first->getContainer()) { + container->__internalAddThing(item); + g_game.startDecay(item); + } + } + } + + return true; +} + +bool IOMapSerialize::saveMapRelational(Map* map) +{ + Database* db = Database::getInstance(); + DBQuery query; // KEEP FOR DATABASE LOCKING! + + //Start the transaction + DBTransaction transaction(db); + + if (!transaction.begin()) { + return false; + } + + //clear old tile data + if (!db->executeQuery("DELETE FROM `tile_items`")) { + return false; + } + + if (!db->executeQuery("DELETE FROM `tiles`")) { + return false; + } + + uint32_t tileId = 0; + + for (HouseMap::iterator it = Houses::getInstance().getHouseBegin(); it != Houses::getInstance().getHouseEnd(); ++it) { + //save house items + House* house = it->second; + + for (HouseTileList::iterator it = house->getHouseTileBegin(); it != house->getHouseTileEnd(); ++it) { + ++tileId; + saveTile(db, tileId, *it); + } + } + + //End the transaction + return transaction.commit(); +} + +bool IOMapSerialize::saveTile(Database* db, uint32_t tileId, const Tile* tile) +{ + typedef std::list > ContainerStackList; + typedef ContainerStackList::value_type ContainerStackList_Pair; + ContainerStackList containerStackList; + + bool storedTile = false; + int32_t runningID = 0; + Item* item = NULL; + Container* container = NULL; + + int32_t parentid = 0; + std::ostringstream streamitems; + + DBQuery tileListQuery; + + DBInsert stmt(db); + stmt.setQuery("INSERT INTO `tile_items` (`tile_id`, `sid` , `pid` , `itemtype` , `count`, `attributes` ) VALUES "); + + if (const TileItemVector* items = tile->getItemList()) { + for (ItemVector::const_iterator it = items->begin(), end = items->end(); it != end; ++it) { + item = (*it); + + if (!(!item->isNotMoveable() || + item->getDoor() || + (item->getContainer() && item->getContainer()->size() != 0) || + item->canWriteText() || + item->getBed())) { + continue; + } + + //only save beds in houses + if (item->getBed() && !tile->hasFlag(TILESTATE_HOUSE)) { + continue; + } + + if (!storedTile) { + const Position& tilePos = tile->getPosition(); + tileListQuery << "INSERT INTO `tiles` (`id`, `x` , `y` , `z` ) VALUES (" << tileId << "," << tilePos.x << "," << tilePos.y << "," << tilePos.z << ")"; + + if (!db->executeQuery(tileListQuery.str())) { + return false; + } + + tileListQuery.str(""); + storedTile = true; + } + + ++runningID; + + uint32_t attributesSize; + + PropWriteStream propWriteStream; + item->serializeAttr(propWriteStream); + const char* attributes = propWriteStream.getStream(attributesSize); + + streamitems << tileId << "," << runningID << "," << parentid << "," << item->getID() << "," + << (int32_t)item->getSubType() << "," << db->escapeBlob(attributes, attributesSize); + + if (!stmt.addRow(streamitems)) { + return false; + } + + if (item->getContainer()) { + containerStackList.push_back(ContainerStackList_Pair(item->getContainer(), runningID)); + } + } + + while (!containerStackList.empty()) { + ContainerStackList_Pair csPair = containerStackList.front(); + container = csPair.first; + parentid = csPair.second; + containerStackList.pop_front(); + + for (ItemDeque::const_iterator it = container->getItems(); it != container->getEnd(); ++it) { + item = (*it); + ++runningID; + + if (item->getContainer()) { + containerStackList.push_back(ContainerStackList_Pair(item->getContainer(), runningID)); + } + + uint32_t attributesSize; + + PropWriteStream propWriteStream; + item->serializeAttr(propWriteStream); + const char* attributes = propWriteStream.getStream(attributesSize); + + streamitems << tileId << "," << runningID << "," << parentid << "," << item->getID() << "," + << (int32_t)item->getSubType() << "," << db->escapeBlob(attributes, attributesSize); + + if (!stmt.addRow(streamitems)) { + return false; + } + } + } + } + + return stmt.execute(); +} + +void IOMapSerialize::loadMapBinary(Map* map) +{ + Database* db = Database::getInstance(); + DBQuery query; // KEEP FOR DATABASE LOCKING! + + DBResult* result; + + if (!(result = db->storeQuery("SELECT `house_id`, `data` FROM `map_store`"))) { + return; + } + + do { + unsigned long attrSize = 0; + const char* attr = result->getDataStream("data", attrSize); + PropStream propStream; + propStream.init(attr, attrSize); + + while (propStream.size()) { + uint32_t item_count = 0; + uint16_t x = 0, y = 0; + uint8_t z = 0; + + propStream.GET_USHORT(x); + propStream.GET_USHORT(y); + propStream.GET_UCHAR(z); + + Tile* tile = map->getTile(x, y, z); + + if (!tile) { + std::cout << "ERROR: Unserialization of invalid tile in IOMapSerialize::loadTile(), at position: [X: " << x << ", Y: " << y << ", Z: " << z << "]. House ID: " << result->getDataInt("house_id") << std::endl; + break; + } + + propStream.GET_ULONG(item_count); + + while (item_count--) { + loadItem(propStream, tile); + } + } + } while (result->next()); + + db->freeResult(result); +} + +void IOMapSerialize::loadMapBinaryTileBased(Map* map) +{ + Database* db = Database::getInstance(); + DBQuery query; + + DBResult* result = db->storeQuery("SELECT `id` FROM `houses`"); + + if (!result) { + return; + } + + do { + query.str(""); + query << "SELECT `data` FROM `tile_store` WHERE `house_id` = " << result->getDataInt("id"); + DBResult* tileResult = db->storeQuery(query.str()); + + if (!tileResult) { + continue; + } + + do { + unsigned long attrSize = 0; + const char* attr = tileResult->getDataStream("data", attrSize); + + PropStream propStream; + propStream.init(attr, attrSize); + + uint16_t x = 0, y = 0; + uint8_t z = 0; + propStream.GET_USHORT(x); + propStream.GET_USHORT(y); + propStream.GET_UCHAR(z); + + if (x == 0 || y == 0) { + continue; + } + + Tile* tile = map->getTile(x, y, z); + + if (!tile) { + continue; + } + + uint32_t item_count = 0; + propStream.GET_ULONG(item_count); + + while (item_count--) { + loadItem(propStream, tile); + } + } while (tileResult->next()); + + db->freeResult(tileResult); + } while (result->next()); + + db->freeResult(result); +} + +bool IOMapSerialize::loadContainer(PropStream& propStream, Container* container) +{ + while (container->serializationCount > 0) { + if (!loadItem(propStream, container)) { + std::cout << "WARNING: Unserialization error for containing item in IOMapSerialize::loadContainer() - " << container->getID() << std::endl; + return false; + } + + container->serializationCount--; + } + + uint8_t endAttr = 0; + propStream.GET_UCHAR(endAttr); + + if (endAttr != 0x00) { + std::cout << "WARNING: Unserialization error for containing item in IOMapSerialize::loadContainer() - " << container->getID() << std::endl; + return false; + } + + return true; +} + +bool IOMapSerialize::loadItem(PropStream& propStream, Cylinder* parent) +{ + Item* item = NULL; + + uint16_t id = 0; + propStream.GET_USHORT(id); + + const ItemType& iType = Item::items[id]; + Tile* tile = NULL; + + if (parent->getParent() == NULL) { + tile = parent->getTile(); + } + + if (iType.moveable || !tile) { + //create a new item + item = Item::CreateItem(id); + + if (item) { + if (item->unserializeAttr(propStream)) { + Container* container = item->getContainer(); + + if (container && !loadContainer(propStream, container)) { + delete item; + return false; + } + + parent->__internalAddThing(item); + item->__startDecaying(); + } else { + std::cout << "WARNING: Unserialization error in IOMapSerialize::loadItem()" << id << std::endl; + delete item; + return false; + } + } + } else { + // Stationary items like doors/beds/blackboards/bookcases + if (const TileItemVector* items = tile->getItemList()) { + for (ItemVector::const_iterator it = items->begin(), end = items->end(); it != end; ++it) { + Item* findItem = (*it); + + if (findItem->getID() == id) { + item = findItem; + break; + } else if (iType.isDoor() && findItem->getDoor()) { + item = findItem; + break; + } else if (iType.isBed() && findItem->getBed()) { + item = findItem; + break; + } + } + } + + if (item) { + if (item->unserializeAttr(propStream)) { + Container* container = item->getContainer(); + + if (container && !loadContainer(propStream, container)) { + return false; + } + + item = g_game.transformItem(item, id); + } else { + std::cout << "WARNING: Unserialization error in IOMapSerialize::loadItem()" << id << std::endl; + } + } else { + //The map changed since the last save, just read the attributes + Item* dummy = Item::CreateItem(id); + + if (dummy) { + dummy->unserializeAttr(propStream); + Container* container = dummy->getContainer(); + + if (container && !loadContainer(propStream, container)) { + delete dummy; + return false; + } + + delete dummy; + } + } + } + + return true; +} + +bool IOMapSerialize::saveMapBinary(Map* map) +{ + Database* db = Database::getInstance(); + DBQuery query; + + //Start the transaction + DBTransaction transaction(db); + + if (!transaction.begin()) { + return false; + } + + if (!db->executeQuery("DELETE FROM `map_store`")) { + return false; + } + + DBInsert stmt(db); + stmt.setQuery("INSERT INTO `map_store` (`house_id`, `data`) VALUES "); + + //clear old tile data + for (HouseMap::iterator it = Houses::getInstance().getHouseBegin(); + it != Houses::getInstance().getHouseEnd(); + ++it) { + //save house items + House* house = it->second; + PropWriteStream stream; + + for (HouseTileList::iterator tile_iter = house->getHouseTileBegin(); tile_iter != house->getHouseTileEnd(); ++tile_iter) { + saveTile(stream, *tile_iter); + } + + uint32_t attributesSize; + const char* attributes = stream.getStream(attributesSize); + + query << it->second->getHouseId() << "," << db->escapeBlob(attributes, attributesSize); + + if (!stmt.addRow(query)) { + return false; + } + } + + if (!stmt.execute()) { + return false; + } + + //End the transaction + return transaction.commit(); +} + +bool IOMapSerialize::saveMapBinaryTileBased(Map* map) +{ + Database* db = Database::getInstance(); + DBQuery query; + + //Start the transaction + DBTransaction transaction(db); + + if (!transaction.begin()) { + return false; + } + + if (!db->executeQuery("DELETE FROM `tile_store`")) { + return false; + } + + DBInsert stmt(db); + stmt.setQuery("INSERT INTO `tile_store` (`house_id`, `data`) VALUES "); + + //clear old tile data + for (HouseMap::iterator it = Houses::getInstance().getHouseBegin(); + it != Houses::getInstance().getHouseEnd(); + ++it) { + //save house items + House* house = it->second; + + for (HouseTileList::iterator tile_iter = house->getHouseTileBegin(); tile_iter != house->getHouseTileEnd(); ++tile_iter) { + PropWriteStream stream; + saveTile(stream, *tile_iter); + uint32_t attributesSize; + const char* attributes = stream.getStream(attributesSize); + + if (attributesSize > 0) { + query << it->second->getHouseId() << "," << db->escapeBlob(attributes, attributesSize); + + if (!stmt.addRow(query)) { + return false; + } + } + } + } + + if (!stmt.execute()) { + return false; + } + + //End the transaction + return transaction.commit(); +} + +void IOMapSerialize::saveItem(PropWriteStream& stream, const Item* item) +{ + const Container* container = item->getContainer(); + + // Write ID & props + stream.ADD_USHORT(item->getID()); + item->serializeAttr(stream); + + if (container) { + // Hack our way into the attributes + stream.ADD_UCHAR(ATTR_CONTAINER_ITEMS); + stream.ADD_ULONG(container->size()); + + for (ItemDeque::const_reverse_iterator i = container->getReversedItems(); i != container->getReversedEnd(); ++i) { + saveItem(stream, *i); + } + } + + stream.ADD_UCHAR(0x00); // attr end +} + +void IOMapSerialize::saveTile(PropWriteStream& stream, const Tile* tile) +{ + const Position& tilePosition = tile->getPosition(); + + if (tilePosition.x == 0 || tilePosition.y == 0) { + return; + } + + std::vector items; + + if (const TileItemVector* tileItems = tile->getItemList()) { + for (ItemVector::const_reverse_iterator it = tileItems->rbegin(), rend = tileItems->rend(); it != rend; ++it) { + Item* item = (*it); + + // Note that these are NEGATED, ie. these are the items that will be saved. + if (!(!item->isNotMoveable() || item->getDoor() || (item->getContainer() && + item->getContainer()->size() != 0) || item->canWriteText() || item->getBed())) { + continue; + } + + items.push_back(item); + } + } + + if (!items.empty()) { + stream.ADD_USHORT(tilePosition.x); + stream.ADD_USHORT(tilePosition.y); + stream.ADD_UCHAR(tilePosition.z); + + stream.ADD_ULONG(items.size()); + + for (std::vector::iterator iter = items.begin(), end = items.end(); iter != end; ++iter) { + saveItem(stream, *iter); + } + } +} + +bool IOMapSerialize::loadHouseInfo(Map* map) +{ + Database* db = Database::getInstance(); + DBQuery query; + + DBResult* result = db->storeQuery("SELECT `id`, `owner`, `paid`, `warnings` FROM `houses`"); + + if (!result) { + return false; + } + + do { + House* house = Houses::getInstance().getHouse(result->getDataInt("id")); + + if (house) { + house->setHouseOwner(result->getDataInt("owner")); + house->setPaidUntil(result->getDataInt("paid")); + house->setPayRentWarnings(result->getDataInt("warnings")); + } + } while (result->next()); + + db->freeResult(result); + + for (HouseMap::iterator it = Houses::getInstance().getHouseBegin(); it != Houses::getInstance().getHouseEnd(); ++it) { + House* house = it->second; + + if (house->getHouseOwner() != 0 && house->getHouseId() != 0) { + query.str(""); + query << "SELECT `listid`, `list` FROM `house_lists` WHERE `house_id` = " << house->getHouseId(); + + if ((result = db->storeQuery(query.str()))) { + do { + house->setAccessList(result->getDataInt("listid"), result->getDataString("list")); + } while (result->next()); + + db->freeResult(result); + } + } + } + + return true; +} + +bool IOMapSerialize::saveHouseInfo(Map* map) +{ + Database* db = Database::getInstance(); + DBQuery query; + + DBTransaction transaction(db); + + if (!transaction.begin()) { + return false; + } + + if (!db->executeQuery("DELETE FROM `house_lists`")) { + return false; + } + + for (HouseMap::iterator it = Houses::getInstance().getHouseBegin(); it != Houses::getInstance().getHouseEnd(); ++it) { + House* house = it->second; + query << "SELECT `id` FROM `houses` WHERE `id` = " << house->getHouseId(); + DBResult* result = db->storeQuery(query.str()); + query.str(""); + + if (result) { + db->freeResult(result); + query << "UPDATE `houses` SET `owner` = " << house->getHouseOwner() << ", `paid` = " << house->getPaidUntil() << ", `warnings` = " << house->getPayRentWarnings() << ", `name` = " << db->escapeString(house->getName()) << ", `town_id` = " << house->getTownId() << ", `rent` = " << house->getRent() << ", `size` = " << house->getHouseTileSize() << ", `beds` = " << house->getBedCount() << " WHERE `id` = " << house->getHouseId(); + } else { + query << "INSERT INTO `houses` (`id`, `owner`, `paid`, `warnings`, `name`, `town_id`, `rent`, `size`, `beds`) VALUES (" << house->getHouseId() << "," << house->getHouseOwner() << "," << house->getPaidUntil() << "," << house->getPayRentWarnings() << "," << db->escapeString(house->getName()) << "," << house->getTownId() << "," << house->getRent() << "," << house->getHouseTileSize() << "," << house->getBedCount() << ")"; + } + + db->executeQuery(query.str()); + query.str(""); + } + + DBInsert stmt(db); + stmt.setQuery("INSERT INTO `house_lists` (`house_id` , `listid` , `list`) VALUES "); + + for (HouseMap::iterator it = Houses::getInstance().getHouseBegin(); it != Houses::getInstance().getHouseEnd(); ++it) { + House* house = it->second; + + std::string listText; + + if (house->getAccessList(GUEST_LIST, listText) && listText != "") { + query << house->getHouseId() << "," << GUEST_LIST << "," << db->escapeString(listText); + + if (!stmt.addRow(query)) { + return false; + } + } + + if (house->getAccessList(SUBOWNER_LIST, listText) && listText != "") { + query << house->getHouseId() << "," << SUBOWNER_LIST << "," << db->escapeString(listText); + + if (!stmt.addRow(query)) { + return false; + } + } + + for (HouseDoorList::iterator it = house->getHouseDoorBegin(); it != house->getHouseDoorEnd(); ++it) { + const Door* door = *it; + + if (door->getAccessList(listText) && listText != "") { + query << house->getHouseId() << "," << door->getDoorId() << "," << db->escapeString(listText); + + if (!stmt.addRow(query)) { + return false; + } + } + } + } + + if (!stmt.execute()) { + return false; + } + + return transaction.commit(); +} diff --git a/src/iomapserialize.h b/src/iomapserialize.h new file mode 100644 index 0000000000..714012cd0f --- /dev/null +++ b/src/iomapserialize.h @@ -0,0 +1,58 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __IOMAPSERIALIZE_H__ +#define __IOMAPSERIALIZE_H__ + +#include "database.h" +#include "map.h" + +#include + +class IOMapSerialize +{ + public: + IOMapSerialize() {} + ~IOMapSerialize() {} + + bool loadMap(Map* map); + bool saveMap(Map* map); + bool loadHouseInfo(Map* map); + bool saveHouseInfo(Map* map); + + protected: + // Relational storage uses a row for each item/tile + bool loadMapRelational(Map* map); + bool saveMapRelational(Map* map); + + void loadMapBinary(Map* map); + bool saveMapBinary(Map* map); + + void loadMapBinaryTileBased(Map* map); + bool saveMapBinaryTileBased(Map* map); + + void saveItem(PropWriteStream& stream, const Item* item); + void saveTile(PropWriteStream& stream, const Tile* tile); + + bool loadContainer(PropStream& propStream, Container* container); + bool loadItem(PropStream& propStream, Cylinder* parent); + bool saveTile(Database* db, uint32_t tileId, const Tile* tile); + bool loadTile(Database& db, Tile* tile); +}; + +#endif diff --git a/src/iomarket.cpp b/src/iomarket.cpp new file mode 100644 index 0000000000..76ef13d76c --- /dev/null +++ b/src/iomarket.cpp @@ -0,0 +1,351 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "iomarket.h" +#include "iologindata.h" +#include "configmanager.h" + +extern ConfigManager g_config; + +MarketOfferList IOMarket::getActiveOffers(MarketAction_t action, uint16_t itemId) +{ + MarketOfferList offerList; + + DBQuery query; + query << "SELECT `id`, `player_id`, `amount`, `price`, `created`, `anonymous` FROM `market_offers` WHERE `sale` = " << action << " AND `itemtype` = " << itemId; + + Database* db = Database::getInstance(); + DBResult* result; + + if (!(result = db->storeQuery(query.str()))) { + return offerList; + } + + const int32_t marketOfferDuration = g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); + + do { + MarketOffer offer; + offer.amount = result->getDataInt("amount"); + offer.price = result->getDataInt("price"); + offer.timestamp = result->getDataInt("created") + marketOfferDuration; + offer.counter = result->getDataInt("id") & 0xFFFF; + + if (result->getDataInt("anonymous") == 0) { + IOLoginData::getInstance()->getNameByGuid(result->getDataInt("player_id"), offer.playerName); + + if (offer.playerName.empty()) { + offer.playerName = "Anonymous"; + } + } else { + offer.playerName = "Anonymous"; + } + + offerList.push_back(offer); + } while (result->next()); + + db->freeResult(result); + return offerList; +} + +MarketOfferList IOMarket::getOwnOffers(MarketAction_t action, uint32_t playerId) +{ + MarketOfferList offerList; + + const int32_t marketOfferDuration = g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); + + DBQuery query; + query << "SELECT `id`, `amount`, `price`, `created`, `anonymous`, `itemtype` FROM `market_offers` WHERE `player_id` = " << playerId << " AND `sale` = " << action; + + Database* db = Database::getInstance(); + DBResult* result; + + if (!(result = db->storeQuery(query.str()))) { + return offerList; + } + + do { + MarketOffer offer; + offer.amount = result->getDataInt("amount"); + offer.price = result->getDataInt("price"); + offer.timestamp = result->getDataInt("created") + marketOfferDuration; + offer.counter = result->getDataInt("id") & 0xFFFF; + offer.itemId = result->getDataInt("itemtype"); + + offerList.push_back(offer); + } while (result->next()); + + db->freeResult(result); + return offerList; +} + +HistoryMarketOfferList IOMarket::getOwnHistory(MarketAction_t action, uint32_t playerId) +{ + HistoryMarketOfferList offerList; + + DBQuery query; + query << "SELECT `id`, `itemtype`, `amount`, `price`, `expires_at`, `state` FROM `market_history` WHERE `player_id` = " << playerId << " AND `sale` = " << action; + + Database* db = Database::getInstance(); + DBResult* result; + + if (!(result = db->storeQuery(query.str()))) { + return offerList; + } + + do { + HistoryMarketOffer offer; + offer.itemId = result->getDataInt("itemtype"); + offer.amount = result->getDataInt("amount"); + offer.price = result->getDataInt("price"); + offer.timestamp = result->getDataInt("expires_at"); + + MarketOfferState_t offerState = (MarketOfferState_t)result->getDataInt("state"); + + if (offerState == OFFERSTATE_ACCEPTEDEX) { + offerState = OFFERSTATE_ACCEPTED; + } + + offer.state = offerState; + + offerList.push_back(offer); + } while (result->next()); + + db->freeResult(result); + return offerList; +} + +ExpiredMarketOfferList IOMarket::getExpiredOffers(MarketAction_t action) +{ + ExpiredMarketOfferList offerList; + + const time_t lastExpireDate = time(NULL) - g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); + + DBQuery query; + query << "SELECT `id`, `amount`, `price`, `itemtype`, `player_id` FROM `market_offers` WHERE `sale` = " << action << " AND `created` <= " << lastExpireDate; + + Database* db = Database::getInstance(); + DBResult* result; + + if (!(result = db->storeQuery(query.str()))) { + return offerList; + } + + do { + ExpiredMarketOffer offer; + offer.id = result->getDataInt("id"); + offer.amount = result->getDataInt("amount"); + offer.price = result->getDataInt("price"); + offer.itemId = result->getDataInt("itemtype"); + offer.playerId = result->getDataInt("player_id"); + + offerList.push_back(offer); + } while (result->next()); + + db->freeResult(result); + return offerList; +} + +int32_t IOMarket::getPlayerOfferCount(uint32_t playerId) +{ + int32_t count = -1; + Database* db = Database::getInstance(); + DBResult* result; + + DBQuery query; + query << "SELECT COUNT(*) AS `count` FROM `market_offers` WHERE `player_id` = " << playerId; + + if (!(result = db->storeQuery(query.str()))) { + return count; + } + + count = result->getDataInt("count"); + db->freeResult(result); + return count; +} + +MarketOfferEx IOMarket::getOfferById(uint32_t id) +{ + MarketOfferEx offer; + DBQuery query; + query << "SELECT `id`, `sale`, `itemtype`, `amount`, `created`, `price`, `player_id`, `anonymous` FROM `market_offers` WHERE `id` = " << id; + Database* db = Database::getInstance(); + DBResult* result; + + if ((result = db->storeQuery(query.str()))) { + offer.type = (MarketAction_t)result->getDataInt("sale"); + offer.amount = result->getDataInt("amount"); + offer.counter = result->getDataInt("id") & 0xFFFF; + offer.timestamp = result->getDataInt("created"); + offer.price = result->getDataInt("price"); + offer.itemId = result->getDataInt("itemtype"); + + int32_t playerId = result->getDataInt("player_id"); + offer.playerId = playerId; + + if (result->getDataInt("anonymous") == 0) { + IOLoginData::getInstance()->getNameByGuid(playerId, offer.playerName); + + if (offer.playerName.empty()) { + offer.playerName = "Anonymous"; + } + } else { + offer.playerName = "Anonymous"; + } + + db->freeResult(result); + } + + return offer; +} + +uint32_t IOMarket::getOfferIdByCounter(uint32_t timestamp, uint16_t counter) +{ + const int32_t created = timestamp - g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); + + DBQuery query; + query << "SELECT `id` FROM `market_offers` WHERE `created` = " << created << " AND (`id` & 65535) = " << counter << " LIMIT 1"; + Database* db = Database::getInstance(); + DBResult* result; + + if ((result = db->storeQuery(query.str()))) { + uint32_t offerId = result->getDataInt("id"); + db->freeResult(result); + return offerId; + } + + return 0; +} + +void IOMarket::createOffer(uint32_t playerId, MarketAction_t action, uint32_t itemId, uint16_t amount, uint32_t price, bool anonymous) +{ + DBQuery query; + query << "INSERT INTO `market_offers` (`player_id`, `sale`, `itemtype`, `amount`, `price`, `created`, `anonymous`) VALUES (" << playerId << ", " << action << ", " << itemId << ", " << amount << ", " << price << ", " << time(NULL) << ", " << anonymous << ")"; + Database::getInstance()->executeQuery(query.str()); +} + +void IOMarket::acceptOffer(uint32_t offerId, uint16_t amount) +{ + DBQuery query; + query << "UPDATE `market_offers` SET `amount` = `amount` - " << amount << " WHERE `id` = " << offerId; + Database::getInstance()->executeQuery(query.str()); +} + +void IOMarket::deleteOffer(uint32_t offerId) +{ + DBQuery query; + query << "DELETE FROM `market_offers` WHERE `id` = " << offerId; + Database::getInstance()->executeQuery(query.str()); +} + +void IOMarket::appendHistory(uint32_t playerId, MarketAction_t type, uint16_t itemId, uint16_t amount, uint32_t price, time_t timestamp, MarketOfferState_t state) +{ + DBQuery query; + query << "INSERT INTO `market_history` (`player_id`, `sale`, `itemtype`, `amount`, `price`, `expires_at`, `inserted`, `state`) VALUES " + << "(" << playerId << ", " << type << ", " << itemId << ", " << amount << ", " << price << ", " + << timestamp << ", " << time(NULL) << ", " << state << ")"; + Database::getInstance()->executeQuery(query.str()); +} + +void IOMarket::moveOfferToHistory(uint32_t offerId, MarketOfferState_t state) +{ + const int32_t marketOfferDuration = g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); + + Database* db = Database::getInstance(); + + DBQuery query; + DBResult* result; + query << "SELECT `player_id`, `sale`, `itemtype`, `amount`, `price`, `created` FROM `market_offers` WHERE `id` = " << offerId; + + if (!(result = db->storeQuery(query.str()))) { + return; + } + + query.str(""); + query << "DELETE FROM `market_offers` WHERE `id` = " << offerId; + + if (!db->executeQuery(query.str())) { + db->freeResult(result); + return; + } + + appendHistory(result->getDataInt("player_id"), (MarketAction_t)result->getDataInt("sale"), result->getDataInt("itemtype"), result->getDataInt("amount"), result->getDataInt("price"), result->getDataInt("created") + marketOfferDuration, state); + db->freeResult(result); +} + +void IOMarket::clearOldHistory() +{ + const time_t lastExpireDate = time(NULL) - g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); + DBQuery query; + query << "DELETE FROM `market_history` WHERE `inserted` <= " << lastExpireDate; + Database::getInstance()->executeQuery(query.str()); +} + +void IOMarket::updateStatistics() +{ + Database* db = Database::getInstance(); + + DBQuery query; + query << "SELECT `sale` AS `sale`, `itemtype` AS `itemtype`, COUNT(`price`) AS `num`, MIN(`price`) AS `min`, MAX(`price`) AS `max`, SUM(`price`) AS `sum` FROM `market_history` WHERE `state` = " << OFFERSTATE_ACCEPTED << " GROUP BY `itemtype`, `sale`"; + + DBResult* result; + + if (!(result = db->storeQuery(query.str()))) { + return; + } + + do { + MarketStatistics* statistics; + + if (result->getDataInt("sale") == MARKETACTION_BUY) { + statistics = &purchaseStatistics[result->getDataInt("itemtype")]; + } else { + statistics = &saleStatistics[result->getDataInt("itemtype")]; + } + + statistics->numTransactions = result->getDataInt("num"); + statistics->lowestPrice = result->getDataInt("min"); + statistics->totalPrice = result->getDataLong("sum"); + statistics->highestPrice = result->getDataInt("max"); + } while (result->next()); + + db->freeResult(result); +} + +MarketStatistics* IOMarket::getPurchaseStatistics(uint16_t itemId) +{ + std::map::iterator it = purchaseStatistics.find(itemId); + + if (it == purchaseStatistics.end()) { + return NULL; + } + + return &it->second; +} + +MarketStatistics* IOMarket::getSaleStatistics(uint16_t itemId) +{ + std::map::iterator it = saleStatistics.find(itemId); + + if (it == saleStatistics.end()) { + return NULL; + } + + return &it->second; +} diff --git a/src/iomarket.h b/src/iomarket.h new file mode 100644 index 0000000000..3e4d69d3cd --- /dev/null +++ b/src/iomarket.h @@ -0,0 +1,66 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_IOMARKET_H__ +#define __OTSERV_IOMARKET_H__ + +#include +#include "account.h" +#include "player.h" +#include "database.h" + +class IOMarket +{ + public: + IOMarket() {} + ~IOMarket() {} + + static IOMarket* getInstance() { + static IOMarket instance; + return &instance; + } + + MarketOfferList getActiveOffers(MarketAction_t action, uint16_t itemId); + MarketOfferList getOwnOffers(MarketAction_t action, uint32_t playerId); + HistoryMarketOfferList getOwnHistory(MarketAction_t action, uint32_t playerId); + + ExpiredMarketOfferList getExpiredOffers(MarketAction_t action); + + int32_t getPlayerOfferCount(uint32_t playerId); + uint32_t getOfferIdByCounter(uint32_t timestamp, uint16_t counter); + MarketOfferEx getOfferById(uint32_t id); + + void createOffer(uint32_t playerId, MarketAction_t action, uint32_t itemId, uint16_t amount, uint32_t price, bool anonymous); + void acceptOffer(uint32_t offerId, uint16_t amount); + void deleteOffer(uint32_t offerId); + + void appendHistory(uint32_t playerId, MarketAction_t type, uint16_t itemId, uint16_t amount, uint32_t price, time_t timestamp, MarketOfferState_t state); + void moveOfferToHistory(uint32_t offerId, MarketOfferState_t state); + void clearOldHistory(); + + void updateStatistics(); + + MarketStatistics* getPurchaseStatistics(uint16_t itemId); + MarketStatistics* getSaleStatistics(uint16_t itemId); + + private: + std::map purchaseStatistics; + std::map saleStatistics; +}; + +#endif diff --git a/src/item.cpp b/src/item.cpp new file mode 100644 index 0000000000..1b4538092d --- /dev/null +++ b/src/item.cpp @@ -0,0 +1,1550 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "definitions.h" +#include "item.h" +#include "container.h" +#include "teleport.h" +#include "trashholder.h" +#include "mailbox.h" +#include "house.h" +#include "game.h" +#include "luascript.h" +#include "configmanager.h" +#include "weapons.h" +#include "beds.h" + +#include "actions.h" +#include "combat.h" + +#include +#include + +extern Game g_game; +extern ConfigManager g_config; +extern Weapons* g_weapons; + +Items Item::items; + +Item* Item::CreateItem(const uint16_t _type, uint16_t _count /*= 0*/) +{ + Item* newItem = NULL; + + const ItemType& it = Item::items[_type]; + + if (it.group == ITEM_GROUP_DEPRECATED) { + return NULL; + } + + if (it.stackable && _count == 0) { + _count = 1; + } + + if (it.id != 0) { + if (it.isDepot()) { + newItem = new DepotLocker(_type); + } else if (it.isContainer()) { + newItem = new Container(_type); + } else if (it.isTeleport()) { + newItem = new Teleport(_type); + } else if (it.isMagicField()) { + newItem = new MagicField(_type); + } else if (it.isDoor()) { + newItem = new Door(_type); + } else if (it.isTrashHolder()) { + newItem = new TrashHolder(_type, it.magicEffect); + } else if (it.isMailbox()) { + newItem = new Mailbox(_type); + } else if (it.isBed()) { + newItem = new BedItem(_type); + } else if (it.id >= 2210 && it.id <= 2212) { + newItem = new Item(_type - 3, _count); + } else if (it.id == 2215 || it.id == 2216) { + newItem = new Item(_type - 2, _count); + } else if (it.id >= 2202 && it.id <= 2206) { + newItem = new Item(_type - 37, _count); + } else if (it.id == 2640) { + newItem = new Item(6132, _count); + } else if (it.id == 6301) { + newItem = new Item(6300, _count); + } else if (it.id == 18528) { + newItem = new Item(18408, _count); + } else { + newItem = new Item(_type, _count); + } + + newItem->useThing2(); + } + + return newItem; +} + +Item* Item::CreateItem(PropStream& propStream) +{ + uint16_t _id; + + if (!propStream.GET_USHORT(_id)) { + return NULL; + } + + switch (_id) { + case ITEM_FIREFIELD_PVP_FULL: + _id = ITEM_FIREFIELD_PERSISTENT_FULL; + break; + + case ITEM_FIREFIELD_PVP_MEDIUM: + _id = ITEM_FIREFIELD_PERSISTENT_MEDIUM; + break; + + case ITEM_FIREFIELD_PVP_SMALL: + _id = ITEM_FIREFIELD_PERSISTENT_SMALL; + break; + + case ITEM_ENERGYFIELD_PVP: + _id = ITEM_ENERGYFIELD_PERSISTENT; + break; + + case ITEM_POISONFIELD_PVP: + _id = ITEM_POISONFIELD_PERSISTENT; + break; + + case ITEM_MAGICWALL: + _id = ITEM_MAGICWALL_PERSISTENT; + break; + + case ITEM_WILDGROWTH: + _id = ITEM_WILDGROWTH_PERSISTENT; + break; + + default: + break; + } + + return Item::CreateItem(_id, 0); +} + +Item::Item(const uint16_t _type, uint16_t _count /*= 0*/) : + ItemAttributes() +{ + id = _type; + + const ItemType& it = items[id]; + + setItemCount(1); + + if (it.isFluidContainer() || it.isSplash()) { + setFluidType(_count); + } else if (it.stackable) { + if (_count != 0) { + setItemCount(_count); + } else if (it.charges != 0) { + setItemCount(it.charges); + } + } else if (it.charges != 0) { + if (_count != 0) { + setCharges(_count); + } else { + setCharges(it.charges); + } + } + + loadedFromMap = false; + setDefaultDuration(); +} + +Item::Item(const Item& i) : + Thing(), ItemAttributes() +{ + //std::cout << "Item copy constructor " << this << std::endl; + id = i.id; + count = i.count; + loadedFromMap = i.loadedFromMap; + + m_attributes = i.m_attributes; + + if (i.m_firstAttr) { + m_firstAttr = new Attribute(*i.m_firstAttr); + } +} + +Item* Item::clone() const +{ + Item* _item = Item::CreateItem(id, count); + _item->m_attributes = m_attributes; + + if (m_firstAttr) { + _item->m_firstAttr = new Attribute(*m_firstAttr); + } + + return _item; +} + +void Item::copyAttributes(Item* item) +{ + m_attributes = item->m_attributes; + + if (item->m_firstAttr) { + m_firstAttr = new Attribute(*item->m_firstAttr); + } + + removeAttribute(ATTR_ITEM_DECAYING); + removeAttribute(ATTR_ITEM_DURATION); +} + +Item::~Item() +{ + //std::cout << "Item destructor " << this << std::endl; +} + +void Item::setDefaultSubtype() +{ + const ItemType& it = items[id]; + + setItemCount(1); + + if (it.charges != 0) { + if (it.stackable) { + setItemCount(it.charges); + } else { + setCharges(it.charges); + } + } +} + +void Item::onRemoved() +{ + ScriptEnvironment::removeTempItem(this); + + if (getUniqueId() != 0) { + ScriptEnvironment::removeUniqueThing(this); + } +} + +void Item::setID(uint16_t newid) +{ + const ItemType& prevIt = Item::items[id]; + id = newid; + + const ItemType& it = Item::items[newid]; + uint32_t newDuration = it.decayTime * 1000; + + if (newDuration == 0 && !it.stopTime && it.decayTo == -1) { + removeAttribute(ATTR_ITEM_DECAYING); + removeAttribute(ATTR_ITEM_DURATION); + } + + if (hasAttribute(ATTR_ITEM_CORPSEOWNER)) { + removeAttribute(ATTR_ITEM_CORPSEOWNER); + } + + if (newDuration > 0 && (!prevIt.stopTime || !hasAttribute(ATTR_ITEM_DURATION))) { + setDecaying(DECAYING_FALSE); + setDuration(newDuration); + } +} + +bool Item::hasSubType() const +{ + const ItemType& it = items[id]; + return it.hasSubType(); +} + +uint16_t Item::getSubType() const +{ + const ItemType& it = items[getID()]; + + if (it.isFluidContainer() || it.isSplash()) { + return getFluidType(); + } else if (it.stackable) { + return getItemCount(); + } else if (it.charges != 0) { + return getCharges(); + } + + return getItemCount(); +} + +Player* Item::getHoldingPlayer() +{ + Cylinder* p = getParent(); + + while (p) { + if (p->getCreature()) { + return p->getCreature()->getPlayer(); + } + + p = p->getParent(); + } + + return NULL; +} + +const Player* Item::getHoldingPlayer() const +{ + return const_cast(this)->getHoldingPlayer(); +} + +void Item::setSubType(uint16_t n) +{ + const ItemType& it = items[id]; + + if (it.isFluidContainer() || it.isSplash()) { + setFluidType(n); + } else if (it.stackable) { + setItemCount(n); + } else if (it.charges != 0) { + setCharges(n); + } else { + setItemCount(n); + } +} + +Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) +{ + switch (attr) { + case ATTR_COUNT: { + uint8_t _count = 0; + + if (!propStream.GET_UCHAR(_count)) { + return ATTR_READ_ERROR; + } + + setSubType(_count); + break; + } + + case ATTR_ACTION_ID: { + uint16_t _actionid = 0; + + if (!propStream.GET_USHORT(_actionid)) { + return ATTR_READ_ERROR; + } + + setActionId(_actionid); + break; + } + + case ATTR_UNIQUE_ID: { + uint16_t _uniqueid; + + if (!propStream.GET_USHORT(_uniqueid)) { + return ATTR_READ_ERROR; + } + + setUniqueId(_uniqueid); + break; + } + + case ATTR_TEXT: { + std::string _text; + + if (!propStream.GET_STRING(_text)) { + return ATTR_READ_ERROR; + } + + setText(_text); + break; + } + + case ATTR_WRITTENDATE: { + uint32_t _writtenDate; + + if (!propStream.GET_ULONG(_writtenDate)) { + return ATTR_READ_ERROR; + } + + setDate(_writtenDate); + break; + } + + case ATTR_WRITTENBY: { + std::string _writer; + + if (!propStream.GET_STRING(_writer)) { + return ATTR_READ_ERROR; + } + + setWriter(_writer); + break; + } + + case ATTR_DESC: { + std::string _text; + + if (!propStream.GET_STRING(_text)) { + return ATTR_READ_ERROR; + } + + setSpecialDescription(_text); + break; + } + + case ATTR_RUNE_CHARGES: { + uint8_t _charges = 1; + + if (!propStream.GET_UCHAR(_charges)) { + return ATTR_READ_ERROR; + } + + setSubType(_charges); + break; + } + + case ATTR_CHARGES: { + uint16_t _charges = 1; + + if (!propStream.GET_USHORT(_charges)) { + return ATTR_READ_ERROR; + } + + setSubType(_charges); + break; + } + + case ATTR_DURATION: { + uint32_t duration = 0; + + if (!propStream.GET_ULONG(duration)) { + return ATTR_READ_ERROR; + } + + if (((int32_t)duration) < 0) { + duration = 0; + } + + setDuration(duration); + break; + } + + case ATTR_DECAYING_STATE: { + uint8_t state = 0; + + if (!propStream.GET_UCHAR(state)) { + return ATTR_READ_ERROR; + } + + if (state != DECAYING_FALSE) { + setDecaying(DECAYING_PENDING); + } + + break; + } + + //these should be handled through derived classes + //If these are called then something has changed in the items.xml since the map was saved + //just read the values + + //Depot class + case ATTR_DEPOT_ID: { + uint16_t _depotId; + + if (!propStream.GET_USHORT(_depotId)) { + return ATTR_READ_ERROR; + } + + return ATTR_READ_CONTINUE; + } + + //Door class + case ATTR_HOUSEDOORID: { + uint8_t _doorId; + + if (!propStream.GET_UCHAR(_doorId)) { + return ATTR_READ_ERROR; + } + + return ATTR_READ_CONTINUE; + } + + //Bed class + case ATTR_SLEEPERGUID: { + uint32_t _guid; + + if (!propStream.GET_ULONG(_guid)) { + return ATTR_READ_ERROR; + } + + return ATTR_READ_CONTINUE; + } + + case ATTR_SLEEPSTART: { + uint32_t sleep_start; + + if (!propStream.GET_ULONG(sleep_start)) { + return ATTR_READ_ERROR; + } + + return ATTR_READ_CONTINUE; + } + + //Teleport class + case ATTR_TELE_DEST: { + TeleportDest* tele_dest; + + if (!propStream.GET_STRUCT(tele_dest)) { + return ATTR_READ_ERROR; + } + + return ATTR_READ_CONTINUE; + } + + //Container class + case ATTR_CONTAINER_ITEMS: { + uint32_t count; + + if (!propStream.GET_ULONG(count)) { + return ATTR_READ_ERROR; + } + + return ATTR_READ_ERROR; + } + + default: + return ATTR_READ_ERROR; + } + + return ATTR_READ_CONTINUE; +} + +bool Item::unserializeAttr(PropStream& propStream) +{ + uint8_t attr_type; + + while (propStream.GET_UCHAR(attr_type) && attr_type != 0) { + Attr_ReadValue ret = readAttr((AttrTypes_t)attr_type, propStream); + + if (ret == ATTR_READ_ERROR) { + return false; + } else if (ret == ATTR_READ_END) { + return true; + } + } + + return true; +} + +bool Item::unserializeItemNode(FileLoader& f, NODE node, PropStream& propStream) +{ + return unserializeAttr(propStream); +} + +bool Item::serializeAttr(PropWriteStream& propWriteStream) const +{ + if (isStackable() || isFluidContainer() || isSplash()) { + uint8_t _count = getSubType(); + propWriteStream.ADD_UCHAR(ATTR_COUNT); + propWriteStream.ADD_UCHAR(_count); + } + + if (hasCharges()) { + uint16_t _count = getCharges(); + propWriteStream.ADD_UCHAR(ATTR_CHARGES); + propWriteStream.ADD_USHORT(_count); + } + + if (!isNotMoveable()) { + uint16_t _actionId = getActionId(); + + if (_actionId) { + propWriteStream.ADD_UCHAR(ATTR_ACTION_ID); + propWriteStream.ADD_USHORT(_actionId); + } + } + + const std::string& _text = getText(); + + if (_text.length() > 0) { + propWriteStream.ADD_UCHAR(ATTR_TEXT); + propWriteStream.ADD_STRING(_text); + } + + const time_t _writtenDate = getDate(); + + if (_writtenDate > 0) { + propWriteStream.ADD_UCHAR(ATTR_WRITTENDATE); + propWriteStream.ADD_ULONG(_writtenDate); + } + + const std::string& _writer = getWriter(); + + if (_writer.length() > 0) { + propWriteStream.ADD_UCHAR(ATTR_WRITTENBY); + propWriteStream.ADD_STRING(_writer); + } + + const std::string& _specialDesc = getSpecialDescription(); + + if (_specialDesc.length() > 0) { + propWriteStream.ADD_UCHAR(ATTR_DESC); + propWriteStream.ADD_STRING(_specialDesc); + } + + if (hasAttribute(ATTR_ITEM_DURATION)) { + uint32_t duration = getDuration(); + propWriteStream.ADD_UCHAR(ATTR_DURATION); + propWriteStream.ADD_ULONG(duration); + } + + ItemDecayState_t decayState = getDecaying(); + + if (decayState == DECAYING_TRUE || decayState == DECAYING_PENDING) { + propWriteStream.ADD_UCHAR(ATTR_DECAYING_STATE); + propWriteStream.ADD_UCHAR(decayState); + } + + return true; +} + +bool Item::hasProperty(enum ITEMPROPERTY prop) const +{ + const ItemType& it = items[id]; + + switch (prop) { + case BLOCKSOLID: + + if (it.blockSolid) { + return true; + } + + break; + + case MOVEABLE: + + if (it.moveable && getUniqueId() == 0) { + return true; + } + + break; + + case HASHEIGHT: + + if (it.hasHeight) { + return true; + } + + break; + + case BLOCKPROJECTILE: + + if (it.blockProjectile) { + return true; + } + + break; + + case BLOCKPATH: + + if (it.blockPathFind) { + return true; + } + + break; + + case ISVERTICAL: + + if (it.isVertical) { + return true; + } + + break; + + case ISHORIZONTAL: + + if (it.isHorizontal) { + return true; + } + + break; + + case IMMOVABLEBLOCKSOLID: + + if (it.blockSolid && (!it.moveable || getUniqueId() != 0)) { + return true; + } + + break; + + case IMMOVABLEBLOCKPATH: + + if (it.blockPathFind && (!it.moveable || getUniqueId() != 0)) { + return true; + } + + break; + + case SUPPORTHANGABLE: + + if (it.isHorizontal || it.isVertical) { + return true; + } + + break; + + case IMMOVABLENOFIELDBLOCKPATH: + + if (!it.isMagicField() && it.blockPathFind && (!it.moveable || getUniqueId() != 0)) { + return true; + } + + break; + + case NOFIELDBLOCKPATH: + + if (!it.isMagicField() && it.blockPathFind) { + return true; + } + + break; + + default: + return false; + break; + } + + return false; +} + +double Item::getWeight() const +{ + if (isStackable()) { + return items[id].weight * std::max(1, getItemCount()); + } + + return items[id].weight; +} + +std::string Item::getDescription(const ItemType& it, int32_t lookDistance, + const Item* item /*= NULL*/, int32_t subType /*= -1*/, bool addArticle /*= true*/) +{ + std::ostringstream s; + s << getNameDescription(it, item, subType, addArticle); + + if (item) { + subType = item->getSubType(); + } + + if (it.isRune()) { + if (!it.runeSpellName.empty()) { + s << " (\"" << it.runeSpellName << "\")"; + } + + if (it.runeLevel > 0 || it.runeMagLevel > 0) { + int32_t tmpSubType = subType; + + if (item) { + tmpSubType = item->getSubType(); + } + + s << ". " << (it.stackable && tmpSubType > 1 ? "They" : "It") << " can only be used with"; + + if (it.runeLevel > 0) { + s << " level " << it.runeLevel; + } + + if (it.runeMagLevel > 0) { + if (it.runeLevel > 0) { + s << " and"; + } + + s << " magic level " << it.runeMagLevel; + } + + s << " or higher"; + } + } else if (it.weaponType != WEAPON_NONE) { + if (it.weaponType == WEAPON_DIST && it.ammoType != AMMO_NONE) { + s << " (Range:" << it.shootRange; + + if (it.attack != 0) { + s << ", Atk " << std::showpos << it.attack << std::noshowpos; + } + + if (it.hitChance != 0) { + s << ", Hit% " << std::showpos << it.hitChance << std::noshowpos; + } + + s << ")"; + } else if (it.weaponType != WEAPON_AMMO) { + bool begin = true; + + if (it.attack != 0) { + begin = false; + s << " (Atk:" << it.attack; + + if (it.abilities && it.abilities->elementType != COMBAT_NONE && it.abilities->elementDamage != 0) { + s << " physical + " << it.abilities->elementDamage << " " << getCombatName(it.abilities->elementType); + } + } + + if (it.defense != 0 || it.extraDefense != 0) { + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << "Def:" << it.defense; + + if (it.extraDefense != 0 || (item && item->getExtraDefense() != 0)) { + s << " " << std::showpos << it.extraDefense << std::noshowpos; + } + } + + if (it.abilities) { + for (uint16_t i = SKILL_FIRST; i <= SKILL_LAST; i++) { + if (!it.abilities->skills[i]) { + continue; + } + + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << getSkillName(i) << " " << std::showpos << it.abilities->skills[i] << std::noshowpos; + } + + if (it.abilities->stats[STAT_MAGICPOINTS]) { + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << "magic level " << std::showpos << it.abilities->stats[STAT_MAGICPOINTS] << std::noshowpos; + } + + int32_t show = it.abilities->absorbPercent[COMBAT_FIRST]; + + for (uint32_t i = (COMBAT_FIRST + 1); i <= COMBAT_COUNT; ++i) { + if (it.abilities->absorbPercent[i] == show) { + continue; + } + + show = 0; + break; + } + + if (!show) { + bool tmp = true; + + for (uint32_t i = COMBAT_FIRST; i <= COMBAT_COUNT; i++) { + if (!it.abilities->absorbPercent[i]) { + continue; + } + + if (tmp) { + tmp = false; + + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << "protection "; + } else { + s << ", "; + } + + s << getCombatName(indexToCombatType(i)) << " " << std::showpos << it.abilities->absorbPercent[i] << std::noshowpos << "%"; + } + } else { + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << "protection all " << std::showpos << show << std::noshowpos << "%"; + } + + if (it.abilities->speed) { + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << "speed " << std::showpos << (int32_t)(it.abilities->speed / 2) << std::noshowpos; + } + } + + if (!begin) { + s << ")"; + } + } + } else if (it.armor || (item && item->getArmor()) || it.showAttributes) { + int32_t tmp = it.armor; + + if (item) { + tmp = item->getArmor(); + } + + bool begin = true; + + if (tmp) { + s << " (Arm:" << tmp; + begin = false; + } + + if (it.abilities) { + for (uint16_t i = SKILL_FIRST; i <= SKILL_LAST; i++) { + if (!it.abilities->skills[i]) { + continue; + } + + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << getSkillName(i) << " " << std::showpos << it.abilities->skills[i] << std::noshowpos; + } + + if (it.abilities->stats[STAT_MAGICPOINTS]) { + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << "magic level " << std::showpos << it.abilities->stats[STAT_MAGICPOINTS] << std::noshowpos; + } + + int32_t show = it.abilities->absorbPercent[COMBAT_FIRST]; + + for (uint32_t i = (COMBAT_FIRST + 1); i <= COMBAT_COUNT; ++i) { + if (it.abilities->absorbPercent[i] == show) { + continue; + } + + show = 0; + break; + } + + if (!show) { + bool tmp = true; + + for (uint32_t i = COMBAT_FIRST; i <= COMBAT_COUNT; i++) { + if (!it.abilities->absorbPercent[i]) { + continue; + } + + if (tmp) { + tmp = false; + + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << "protection "; + } else { + s << ", "; + } + + s << getCombatName(indexToCombatType(i)) << " " << std::showpos << it.abilities->absorbPercent[i] << std::noshowpos << "%"; + } + } else { + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << "protection all " << std::showpos << show << std::noshowpos << "%"; + } + + if (it.abilities->speed) { + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << "speed " << std::showpos << (int32_t)(it.abilities->speed / 2) << std::noshowpos; + } + } + + if (!begin) { + s << ")"; + } + } else if (it.isContainer()) { + s << " (Vol:" << (int32_t)it.maxItems << ")"; + } else { + bool found = true; + + if (it.abilities) { + if (it.abilities->speed > 0) { + s << " (speed " << std::showpos << (it.abilities->speed / 2) << std::noshowpos << ")"; + } else if (it.abilities && hasBitSet(CONDITION_DRUNK, it.abilities->conditionSuppressions)) { + s << " (hard drinking)"; + } else if (it.abilities->invisible) { + s << " (invisibility)"; + } else if (it.abilities->regeneration) { + s << " (faster regeneration)"; + } else if (it.abilities->manaShield) { + s << " (mana shield)"; + } else { + found = false; + } + } else { + found = false; + } + + if (!found) { + if (it.isKey()) { + s << " (Key:" << (item ? (int32_t)item->getActionId() : 0) << ")"; + } else if (it.isFluidContainer()) { + if (subType > 0) { + const std::string& itemName = items[subType].name; + s << " of " << (itemName.length() ? itemName : "unknown"); + } else { + s << ". It is empty"; + } + } else if (it.isSplash()) { + s << " of "; + + if (subType > 0 && items[subType].name.length()) { + s << items[subType].name; + } else { + s << "unknown"; + } + } else if (it.allowDistRead && it.id != 7369 && it.id != 7370 && it.id != 7371) { + s << "." << std::endl; + + if (lookDistance <= 4) { + if (item && item->getText() != "") { + if (item->getWriter().length()) { + s << item->getWriter() << " wrote"; + time_t date = item->getDate(); + + if (date > 0) { + s << " on " << formatDateShort(date); + } + + s << ": "; + } else { + s << "You read: "; + } + + std::string outtext; + + if (utf8ToLatin1(item->getText().c_str(), outtext)) { + s << outtext; + } else { + s << item->getText(); + } + } else { + s << "Nothing is written on it"; + } + } else { + s << "You are too far away to read it"; + } + } else if (it.levelDoor && item && item->getActionId() >= (int32_t)it.levelDoor) { + s << " for level " << item->getActionId() - it.levelDoor; + } + } + } + + if (it.showCharges) { + s << " that has " << subType << " charge" << (subType != 1 ? "s" : "") << " left"; + } + + if (it.showDuration) { + if (item && item->hasAttribute(ATTR_ITEM_DURATION)) { + int32_t duration = item->getDuration() / 1000; + s << " that will expire in "; + + if (duration >= 86400) { + uint16_t days = duration / 86400; + uint16_t hours = (duration % 86400) / 3600; + s << days << " day" << (days != 1 ? "s" : ""); + + if (hours > 0) { + s << " and " << hours << " hour" << (hours != 1 ? "s" : ""); + } + } else if (duration >= 3600) { + uint16_t hours = duration / 3600; + uint16_t minutes = (duration % 3600) / 60; + s << hours << " hour" << (hours != 1 ? "s" : ""); + + if (minutes > 0) { + s << " and " << minutes << " minute" << (minutes != 1 ? "s" : ""); + } + } else if (duration >= 60) { + uint16_t minutes = duration / 60; + s << minutes << " minute" << (minutes != 1 ? "s" : ""); + uint16_t seconds = duration % 60; + + if (seconds > 0) { + s << " and " << seconds << " second" << (seconds != 1 ? "s" : ""); + } + } else { + s << duration << " second" << (duration != 1 ? "s" : ""); + } + } else { + s << " that is brand-new"; + } + } + + if (!it.allowDistRead || item->getText() == "" || (it.id >= 7369 && it.id <= 7371)) { + s << "."; + } + + if (it.wieldInfo != 0) { + s << std::endl << "It can only be wielded properly by "; + + if (it.wieldInfo & WIELDINFO_PREMIUM) { + s << "premium "; + } + + if (it.wieldInfo & WIELDINFO_VOCREQ) { + s << it.vocationString; + } else { + s << "players"; + } + + if (it.wieldInfo & WIELDINFO_LEVEL) { + s << " of level " << (int32_t)it.minReqLevel << " or higher"; + } + + if (it.wieldInfo & WIELDINFO_MAGLV) { + if (it.wieldInfo & WIELDINFO_LEVEL) { + s << " and"; + } else { + s << " of"; + } + + s << " magic level " << (int32_t)it.minReqMagicLevel << " or higher"; + } + + s << "."; + } + + if (lookDistance <= 1) { + double weight = (item == NULL ? it.weight : item->getWeight()); + + if (weight > 0 && it.pickupable) { + int32_t count = weight / it.weight; + s << std::endl << getWeightDescription(it, weight, count); + } + } + + if (item && item->getSpecialDescription() != "") { + s << std::endl << item->getSpecialDescription().c_str(); + } else if (it.description.length() && lookDistance <= 1) { + s << std::endl << it.description; + } + + if (it.allowDistRead && it.id >= 7369 && it.id <= 7371 && item->getText() != "") { + s << std::endl << item->getText(); + } + + return s.str(); +} + +std::string Item::getDescription(int32_t lookDistance) const +{ + const ItemType& it = items[id]; + return getDescription(it, lookDistance, this); +} + +std::string Item::getNameDescription(const ItemType& it, const Item* item /*= NULL*/, int32_t subType /*= -1*/, bool addArticle /*= true*/) +{ + if (item) { + subType = item->getSubType(); + } + + std::ostringstream s; + + if (it.name.length()) { + if (it.stackable && subType > 1) { + if (it.showCount) { + s << subType << " "; + } + + s << it.getPluralName(); + } else { + if (addArticle && !it.article.empty()) { + s << it.article << " "; + } + + s << it.name; + } + } else { + s << "an item of type " << it.id; + } + + return s.str(); +} + +std::string Item::getNameDescription() const +{ + const ItemType& it = items[id]; + return getNameDescription(it, this); +} + +std::string Item::getWeightDescription(const ItemType& it, double weight, uint32_t _count /*= 1*/) +{ + std::ostringstream ss; + + if (it.stackable && _count > 1 && it.showCount != 0) { + ss << "They weigh " << std::fixed << std::setprecision(2) << weight << " oz."; + } else { + ss << "It weighs " << std::fixed << std::setprecision(2) << weight << " oz."; + } + + return ss.str(); +} + +std::string Item::getWeightDescription(double weight) const +{ + const ItemType& it = Item::items[id]; + return getWeightDescription(it, weight, getItemCount()); +} + +std::string Item::getWeightDescription() const +{ + double weight = getWeight(); + return (weight > 0 ? getWeightDescription(weight) : ""); +} + +void Item::setUniqueId(uint16_t n) +{ + if (getUniqueId() != 0) { + return; + } + + ItemAttributes::setUniqueId(n); + ScriptEnvironment::addUniqueThing(this); +} + +bool Item::canDecay() +{ + if (isRemoved()) { + return false; + } + + if (getUniqueId() != 0) { + return false; + } + + const ItemType& it = Item::items[id]; + + if (it.decayTo == -1 || it.decayTime == 0) { + return false; + } + + return true; +} + +int32_t Item::getWorth() const +{ + switch (getID()) { + case ITEM_COINS_GOLD: + return getItemCount(); + + case ITEM_COINS_PLATINUM: + return getItemCount() * 100; + + case ITEM_COINS_CRYSTAL: + return getItemCount() * 10000; + + default: + return 0; + } +} + +void Item::getLight(LightInfo& lightInfo) +{ + const ItemType& it = items[id]; + lightInfo.color = it.lightColor; + lightInfo.level = it.lightLevel; +} + +std::string ItemAttributes::emptyString(""); + +const std::string& ItemAttributes::getStrAttr(itemAttrTypes type) const +{ + if (!validateStrAttrType(type)) { + return emptyString; + } + + Attribute* attr = getAttrConst(type); + + if (attr) { + return *(std::string*)attr->value; + } else { + return emptyString; + } +} + +void ItemAttributes::setStrAttr(itemAttrTypes type, const std::string& value) +{ + if (!validateStrAttrType(type)) { + return; + } + + if (value.length() == 0) { + return; + } + + Attribute* attr = getAttr(type); + + if (attr) { + if (attr->value) { + delete (std::string*)attr->value; + } + + attr->value = (void*)new std::string(value); + } +} + +bool ItemAttributes::hasAttribute(itemAttrTypes type) const +{ + if (!validateIntAttrType(type)) { + return false; + } + + Attribute* attr = getAttrConst(type); + + if (attr) { + return true; + } + + return false; +} + +void ItemAttributes::removeAttribute(itemAttrTypes type) +{ + //check if we have it + if ((type & m_attributes) != 0) { + //go trough the linked list until find it + Attribute* prevAttr = NULL; + Attribute* curAttr = m_firstAttr; + + while (curAttr != NULL) { + if (curAttr->type == type) { + //found so remove it from the linked list + if (prevAttr) { + prevAttr->next = curAttr->next; + } else { + m_firstAttr = curAttr->next; + } + + //remove it from flags + m_attributes = m_attributes & ~type; + + //delete string if it is string type + if (validateStrAttrType(type)) { + delete (std::string*)curAttr->value; + } + + //finally delete the attribute and return + delete curAttr; + return; + } + + //advance in the linked list + prevAttr = curAttr; + curAttr = curAttr->next; + } + } +} + +uint32_t ItemAttributes::getIntAttr(itemAttrTypes type) const +{ + if (!validateIntAttrType(type)) { + return 0; + } + + Attribute* attr = getAttrConst(type); + + if (attr) { + return static_cast(0xFFFFFFFF & reinterpret_cast(attr->value)); + } else { + return 0; + } +} + +void ItemAttributes::setIntAttr(itemAttrTypes type, int32_t value) +{ + if (!validateIntAttrType(type)) { + return; + } + + Attribute* attr = getAttr(type); + + if (attr) { + attr->value = reinterpret_cast(static_cast(value)); + } +} + +void ItemAttributes::increaseIntAttr(itemAttrTypes type, int32_t value) +{ + if (!validateIntAttrType(type)) { + return; + } + + Attribute* attr = getAttr(type); + + if (attr) { + attr->value = reinterpret_cast(static_cast(static_cast(0xFFFFFFFF & reinterpret_cast(attr->value)) + value)); + } +} + +bool ItemAttributes::validateIntAttrType(itemAttrTypes type) +{ + switch (type) { + case ATTR_ITEM_ACTIONID: + case ATTR_ITEM_UNIQUEID: + case ATTR_ITEM_OWNER: + case ATTR_ITEM_DURATION: + case ATTR_ITEM_DECAYING: + case ATTR_ITEM_WRITTENDATE: + case ATTR_ITEM_CORPSEOWNER: + case ATTR_ITEM_CHARGES: + case ATTR_ITEM_FLUIDTYPE: + case ATTR_ITEM_DOORID: + return true; + + default: + break; + } + + return false; +} + +bool ItemAttributes::validateStrAttrType(itemAttrTypes type) +{ + switch (type) { + case ATTR_ITEM_DESC: + case ATTR_ITEM_TEXT: + case ATTR_ITEM_WRITTENBY: + return true; + + default: + break; + } + + return false; +} + +void ItemAttributes::addAttr(Attribute* attr) +{ + if (m_firstAttr) { + Attribute* curAttr = m_firstAttr; + + while (curAttr->next) { + curAttr = curAttr->next; + } + + curAttr->next = attr; + } else { + m_firstAttr = attr; + } + + m_attributes = m_attributes | attr->type; +} + +ItemAttributes::Attribute* ItemAttributes::getAttrConst(itemAttrTypes type) const +{ + if ((type & m_attributes) == 0) { + return NULL; + } + + Attribute* curAttr = m_firstAttr; + + while (curAttr) { + if (curAttr->type == type) { + return curAttr; + } + + curAttr = curAttr->next; + } + + std::cout << "Warning: [ItemAttributes::getAttrConst] (type & m_attributes) != 0 but attribute not found" << std::endl; + return NULL; +} + +ItemAttributes::Attribute* ItemAttributes::getAttr(itemAttrTypes type) +{ + Attribute* curAttr; + + if ((type & m_attributes) == 0) { + curAttr = new Attribute(type); + addAttr(curAttr); + return curAttr; + } else { + curAttr = m_firstAttr; + + while (curAttr) { + if (curAttr->type == type) { + return curAttr; + } + + curAttr = curAttr->next; + } + } + + std::cout << "Warning: [ItemAttributes::getAttr] (type & m_attributes) != 0 but attribute not found" << std::endl; + curAttr = new Attribute(type); + addAttr(curAttr); + return curAttr; +} + +void ItemAttributes::deleteAttrs(Attribute* attr) +{ + if (attr) { + if (validateStrAttrType(attr->type)) { + delete (std::string*)attr->value; + } + + Attribute* next_attr = attr->next; + attr->next = NULL; + + if (next_attr) { + deleteAttrs(next_attr); + } + + delete attr; + } +} + +void Item::__startDecaying() +{ + g_game.startDecay(this); +} diff --git a/src/item.h b/src/item.h new file mode 100644 index 0000000000..95a3f9b209 --- /dev/null +++ b/src/item.h @@ -0,0 +1,615 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_ITEM_H__ +#define __OTSERV_ITEM_H__ + +#include "thing.h" +#include "items.h" + +#include +#include +#include +#include + +class Creature; +class Player; +class Container; +class Depot; +class Teleport; +class TrashHolder; +class Mailbox; +class Door; +class MagicField; +class BedItem; + +enum ITEMPROPERTY { + BLOCKSOLID = 0, + HASHEIGHT, + BLOCKPROJECTILE, + BLOCKPATH, + ISVERTICAL, + ISHORIZONTAL, + MOVEABLE, + IMMOVABLEBLOCKSOLID, + IMMOVABLEBLOCKPATH, + IMMOVABLENOFIELDBLOCKPATH, + NOFIELDBLOCKPATH, + SUPPORTHANGABLE +}; + +enum TradeEvents_t { + ON_TRADE_TRANSFER, + ON_TRADE_CANCEL, +}; + +enum ItemDecayState_t { + DECAYING_FALSE = 0, + DECAYING_TRUE, + DECAYING_PENDING +}; + +/*from iomap.h*/ +#pragma pack(1) +struct TeleportDest { + uint16_t _x; + uint16_t _y; + uint8_t _z; +}; +#pragma pack() + +enum AttrTypes_t { + //ATTR_DESCRIPTION = 1, + //ATTR_EXT_FILE = 2, + ATTR_TILE_FLAGS = 3, + ATTR_ACTION_ID = 4, + ATTR_UNIQUE_ID = 5, + ATTR_TEXT = 6, + ATTR_DESC = 7, + ATTR_TELE_DEST = 8, + ATTR_ITEM = 9, + ATTR_DEPOT_ID = 10, + //ATTR_EXT_SPAWN_FILE = 11, + ATTR_RUNE_CHARGES = 12, + //ATTR_EXT_HOUSE_FILE = 13, + ATTR_HOUSEDOORID = 14, + ATTR_COUNT = 15, + ATTR_DURATION = 16, + ATTR_DECAYING_STATE = 17, + ATTR_WRITTENDATE = 18, + ATTR_WRITTENBY = 19, + ATTR_SLEEPERGUID = 20, + ATTR_SLEEPSTART = 21, + ATTR_CHARGES = 22, + ATTR_CONTAINER_ITEMS = 23 +}; + +enum Attr_ReadValue { + ATTR_READ_CONTINUE, + ATTR_READ_ERROR, + ATTR_READ_END +}; + +class ItemAttributes +{ + public: + ItemAttributes() { + m_attributes = 0; + m_firstAttr = NULL; + } + + virtual ~ItemAttributes() { + if (m_firstAttr) { + deleteAttrs(m_firstAttr); + } + } + + ItemAttributes(const ItemAttributes& i) { + m_attributes = i.m_attributes; + + if (i.m_firstAttr) { + m_firstAttr = new Attribute(*i.m_firstAttr); + } + } + + void setSpecialDescription(const std::string& desc) { + setStrAttr(ATTR_ITEM_DESC, desc); + } + void resetSpecialDescription() { + removeAttribute(ATTR_ITEM_DESC); + } + const std::string& getSpecialDescription() const { + return getStrAttr(ATTR_ITEM_DESC); + } + + void setText(const std::string& text) { + setStrAttr(ATTR_ITEM_TEXT, text); + } + void resetText() { + removeAttribute(ATTR_ITEM_TEXT); + } + const std::string& getText() const { + return getStrAttr(ATTR_ITEM_TEXT); + } + + void setDate(int32_t n) { + setIntAttr(ATTR_ITEM_WRITTENDATE, n); + } + void resetDate() { + removeAttribute(ATTR_ITEM_WRITTENDATE); + } + time_t getDate() const { + return (time_t)getIntAttr(ATTR_ITEM_WRITTENDATE); + } + + void setWriter(const std::string& _writer) { + setStrAttr(ATTR_ITEM_WRITTENBY, _writer); + } + void resetWriter() { + removeAttribute(ATTR_ITEM_WRITTENBY); + } + const std::string& getWriter() const { + return getStrAttr(ATTR_ITEM_WRITTENBY); + } + + void setActionId(uint16_t n) { + if (n < 100) { + n = 100; + } + + setIntAttr(ATTR_ITEM_ACTIONID, n); + } + uint16_t getActionId() const { + return (uint16_t)getIntAttr(ATTR_ITEM_ACTIONID); + } + + void setUniqueId(uint16_t n) { + if (n < 1000) { + n = 1000; + } + + setIntAttr(ATTR_ITEM_UNIQUEID, n); + } + uint16_t getUniqueId() const { + return (uint16_t)getIntAttr(ATTR_ITEM_UNIQUEID); + } + + void setCharges(uint16_t n) { + setIntAttr(ATTR_ITEM_CHARGES, n); + } + uint16_t getCharges() const { + return (uint16_t)getIntAttr(ATTR_ITEM_CHARGES); + } + + void setFluidType(uint16_t n) { + setIntAttr(ATTR_ITEM_FLUIDTYPE, n); + } + uint16_t getFluidType() const { + return (uint16_t)getIntAttr(ATTR_ITEM_FLUIDTYPE); + } + + void setOwner(uint32_t _owner) { + setIntAttr(ATTR_ITEM_OWNER, _owner); + } + uint32_t getOwner() const { + return getIntAttr(ATTR_ITEM_OWNER); + } + + void setCorpseOwner(uint32_t _corpseOwner) { + setIntAttr(ATTR_ITEM_CORPSEOWNER, _corpseOwner); + } + uint32_t getCorpseOwner() { + return getIntAttr(ATTR_ITEM_CORPSEOWNER); + } + + void setDuration(int32_t time) { + setIntAttr(ATTR_ITEM_DURATION, time); + } + void decreaseDuration(int32_t time) { + increaseIntAttr(ATTR_ITEM_DURATION, -time); + } + uint32_t getDuration() const { + return getIntAttr(ATTR_ITEM_DURATION); + } + + void setDecaying(ItemDecayState_t decayState) { + setIntAttr(ATTR_ITEM_DECAYING, decayState); + } + ItemDecayState_t getDecaying() const { + return (ItemDecayState_t)getIntAttr(ATTR_ITEM_DECAYING); + } + + protected: + enum itemAttrTypes { + ATTR_ITEM_ACTIONID = 1, + ATTR_ITEM_UNIQUEID = 2, + ATTR_ITEM_DESC = 4, + ATTR_ITEM_TEXT = 8, + ATTR_ITEM_WRITTENDATE = 16, + ATTR_ITEM_WRITTENBY = 32, + ATTR_ITEM_OWNER = 65536, + ATTR_ITEM_DURATION = 131072, + ATTR_ITEM_DECAYING = 262144, + ATTR_ITEM_CORPSEOWNER = 524288, + ATTR_ITEM_CHARGES = 1048576, + ATTR_ITEM_FLUIDTYPE = 2097152, + ATTR_ITEM_DOORID = 4194304 + }; + + bool hasAttribute(itemAttrTypes type) const; + void removeAttribute(itemAttrTypes type); + + static std::string emptyString; + + class Attribute + { + public: + itemAttrTypes type; + void* value; + Attribute* next; + Attribute(itemAttrTypes _type) { + type = _type; + value = NULL; + next = NULL; + } + + Attribute(const Attribute& i) { + type = i.type; + + if (ItemAttributes::validateIntAttrType(type)) { + value = i.value; + } else if (ItemAttributes::validateStrAttrType(type)) { + value = (void*)new std::string( *((std::string*)i.value) ); + } else { + value = NULL; + } + + next = NULL; + + if (i.next) { + next = new Attribute(*i.next); + } + } + }; + + uint32_t m_attributes; + Attribute* m_firstAttr; + + const std::string& getStrAttr(itemAttrTypes type) const; + void setStrAttr(itemAttrTypes type, const std::string& value); + + uint32_t getIntAttr(itemAttrTypes type) const; + void setIntAttr(itemAttrTypes type, int32_t value); + void increaseIntAttr(itemAttrTypes type, int32_t value); + + static bool validateIntAttrType(itemAttrTypes type); + static bool validateStrAttrType(itemAttrTypes type); + + void addAttr(Attribute* attr); + Attribute* getAttrConst(itemAttrTypes type) const; + Attribute* getAttr(itemAttrTypes type); + + void deleteAttrs(Attribute* attr); +}; + +class Item : virtual public Thing, public ItemAttributes +{ + public: + //Factory member to create item of right type based on type + static Item* CreateItem(const uint16_t _type, uint16_t _count = 0); + static Item* CreateItem(PropStream& propStream); + static Items items; + + // Constructor for items + Item(const uint16_t _type, uint16_t _count = 0); + Item(const Item& i); + virtual Item* clone() const; + virtual void copyAttributes(Item* item); + + virtual ~Item(); + + virtual Item* getItem() { + return this; + } + virtual const Item* getItem() const { + return this; + } + virtual Container* getContainer() { + return NULL; + } + virtual const Container* getContainer() const { + return NULL; + } + virtual Teleport* getTeleport() { + return NULL; + } + virtual const Teleport* getTeleport() const { + return NULL; + } + virtual TrashHolder* getTrashHolder() { + return NULL; + } + virtual const TrashHolder* getTrashHolder() const { + return NULL; + } + virtual Mailbox* getMailbox() { + return NULL; + } + virtual const Mailbox* getMailbox() const { + return NULL; + } + virtual Door* getDoor() { + return NULL; + } + virtual const Door* getDoor() const { + return NULL; + } + virtual MagicField* getMagicField() { + return NULL; + } + virtual const MagicField* getMagicField() const { + return NULL; + } + virtual BedItem* getBed() { + return NULL; + } + virtual const BedItem* getBed() const { + return NULL; + } + + static std::string getDescription(const ItemType& it, int32_t lookDistance, const Item* item = NULL, int32_t subType = -1, bool addArticle = true); + static std::string getNameDescription(const ItemType& it, const Item* item = NULL, int32_t subType = -1, bool addArticle = true); + static std::string getWeightDescription(const ItemType& it, double weight, uint32_t count = 1); + + virtual std::string getDescription(int32_t lookDistance) const; + std::string getNameDescription() const; + std::string getWeightDescription() const; + + //serialization + virtual Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream); + virtual bool unserializeAttr(PropStream& propStream); + virtual bool unserializeItemNode(FileLoader& f, NODE node, PropStream& propStream); + + virtual bool serializeAttr(PropWriteStream& propWriteStream) const; + + virtual bool isPushable() const { + return !isNotMoveable(); + } + virtual int32_t getThrowRange() const { + return (isPickupable() ? 15 : 2); + } + + uint16_t getID() const { + return id; + } + uint16_t getClientID() const { + return items[id].clientId; + } + void setID(uint16_t newid); + + // Returns the player that is holding this item in his inventory + Player* getHoldingPlayer(); + const Player* getHoldingPlayer() const; + + WeaponType_t getWeaponType() const { + return items[id].weaponType; + } + Ammo_t getAmmoType() const { + return items[id].ammoType; + } + int32_t getShootRange() const { + return items[id].shootRange; + } + + virtual double getWeight() const; + int32_t getAttack() const { + return items[id].attack; + } + int32_t getArmor() const { + return items[id].armor; + } + int32_t getDefense() const { + return items[id].defense; + } + int32_t getExtraDefense() const { + return items[id].extraDefense; + } + int32_t getSlotPosition() const { + return items[id].slotPosition; + } + int32_t getHitChance() const { + return items[id].hitChance; + } + + bool isReadable() const { + return items[id].canReadText; + } + bool canWriteText() const { + return items[id].canWriteText; + } + uint16_t getMaxWriteLength() const { + return items[id].maxTextLen; + } + + int32_t getWorth() const; + void getLight(LightInfo& lightInfo); + + bool hasProperty(enum ITEMPROPERTY prop) const; + bool isBlocking() const { + return items[id].blockSolid; + } + bool isStackable() const { + return items[id].stackable; + } + bool isRune() const { + return items[id].isRune(); + } + bool isFluidContainer() const { + return (items[id].isFluidContainer()); + } + bool isAlwaysOnTop() const { + return items[id].alwaysOnTop; + } + bool isGroundTile() const { + return items[id].isGroundTile(); + } + bool isSplash() const { + return items[id].isSplash(); + } + bool isMagicField() const { + return items[id].isMagicField(); + } + bool isNotMoveable() const { + return !items[id].moveable; + } + bool isPickupable() const { + return items[id].pickupable; + } + bool isWeapon() const { + return (items[id].weaponType != WEAPON_NONE); + } + bool isUseable() const { + return items[id].useable; + } + bool isHangable() const { + return items[id].isHangable; + } + bool isRoteable() const { + const ItemType& it = items[id]; + return it.rotable && it.rotateTo; + } + bool isDoor() const { + return items[id].isDoor(); + } + bool isBed() const { + return items[id].isBed(); + } + bool hasCharges() const { + return getCharges() > 0; + } + bool hasWalkStack() const { + return items[id].walkStack; + } + + bool floorChangeDown() const { + return items[id].floorChangeDown; + } + bool floorChangeNorth() const { + return items[id].floorChangeNorth; + } + bool floorChangeSouth() const { + return items[id].floorChangeSouth; + } + bool floorChangeSouthAlt() const { + return items[id].floorChangeSouthAlt; + } + bool floorChangeEast() const { + return items[id].floorChangeEast; + } + bool floorChangeEastAlt() const { + return items[id].floorChangeEastAlt; + } + bool floorChangeWest() const { + return items[id].floorChangeWest; + } + + const std::string& getName() const { + return items[id].name; + } + const std::string getPluralName() const { + return items[id].getPluralName(); + } + const std::string& getArticle() const { + return items[id].article; + } + + // get the number of items + uint16_t getItemCount() const { + return count; + } + void setItemCount(uint8_t n) { + count = n; + } + + static uint32_t countByType(const Item* i, int32_t subType); + + void setDefaultSubtype(); + bool hasSubType() const; + uint16_t getSubType() const; + void setSubType(uint16_t n); + + void setUniqueId(uint16_t n); + + void setDefaultDuration() { + uint32_t duration = getDefaultDuration(); + + if (duration != 0) { + setDuration(duration); + } + } + uint32_t getDefaultDuration() const { + return items[id].decayTime * 1000; + } + bool canDecay(); + + virtual bool canRemove() const { + return true; + } + virtual bool canTransform() const { + return true; + } + virtual void onRemoved(); + virtual bool onTradeEvent(TradeEvents_t event, Player* owner) { + return true; + } + + virtual void __startDecaying(); + + bool isLoadedFromMap() const { + return loadedFromMap; + } + void setLoadedFromMap(bool value) { + loadedFromMap = value; + } + bool isCleanable() const { + return(!loadedFromMap && (getUniqueId() == 0 && getActionId() == 0) && isPickupable() && canRemove()); + } + + protected: + std::string getWeightDescription(double weight) const; + uint16_t id; // the same id as in ItemType + uint8_t count; // number of stacked items + + bool loadedFromMap; + + //Don't add variables here, use the ItemAttribute class. +}; + +typedef std::list ItemList; +typedef std::deque ItemDeque; + +inline uint32_t Item::countByType(const Item* i, int32_t subType) +{ + if (subType == -1 || subType == i->getSubType()) { + return i->getItemCount(); + } + + return 0; +} + +#endif diff --git a/src/itemloader.h b/src/itemloader.h new file mode 100644 index 0000000000..66d5718483 --- /dev/null +++ b/src/itemloader.h @@ -0,0 +1,191 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_ITEMLOADER_H__ +#define __OTSERV_ITEMLOADER_H__ + +#include "fileloader.h" +#include "definitions.h" + +typedef uint8_t attribute_t; +typedef uint16_t datasize_t; +typedef uint32_t flags_t; + +enum itemgroup_t { + ITEM_GROUP_NONE = 0, + ITEM_GROUP_GROUND, + ITEM_GROUP_CONTAINER, + ITEM_GROUP_WEAPON, //deprecated + ITEM_GROUP_AMMUNITION, //deprecated + ITEM_GROUP_ARMOR, //deprecated + ITEM_GROUP_CHARGES, + ITEM_GROUP_TELEPORT, //deprecated + ITEM_GROUP_MAGICFIELD, //deprecated + ITEM_GROUP_WRITEABLE, //deprecated + ITEM_GROUP_KEY, //deprecated + ITEM_GROUP_SPLASH, + ITEM_GROUP_FLUID, + ITEM_GROUP_DOOR, //deprecated + ITEM_GROUP_DEPRECATED, + ITEM_GROUP_LAST +}; + +/////////OTB specific////////////// +enum clientVersion_t { + CLIENT_VERSION_750 = 1, + CLIENT_VERSION_755 = 2, + CLIENT_VERSION_760 = 3, + CLIENT_VERSION_770 = 3, + CLIENT_VERSION_780 = 4, + CLIENT_VERSION_790 = 5, + CLIENT_VERSION_792 = 6, + CLIENT_VERSION_800 = 7, + CLIENT_VERSION_810 = 8, + CLIENT_VERSION_811 = 9, + CLIENT_VERSION_820 = 10, + CLIENT_VERSION_830 = 11, + CLIENT_VERSION_840 = 12, + CLIENT_VERSION_841 = 13, + CLIENT_VERSION_842 = 14, + CLIENT_VERSION_850 = 15, + CLIENT_VERSION_854_BAD = 16, + CLIENT_VERSION_854 = 17, + CLIENT_VERSION_855 = 18, + CLIENT_VERSION_860_OLD = 19, + CLIENT_VERSION_860 = 20, + CLIENT_VERSION_861 = 21, + CLIENT_VERSION_862 = 22, + CLIENT_VERSION_870 = 23, + CLIENT_VERSION_871 = 24, + CLIENT_VERSION_872 = 25, + CLIENT_VERSION_873 = 26, + CLIENT_VERSION_900 = 27, + CLIENT_VERSION_910 = 28, + CLIENT_VERSION_920 = 29, + CLIENT_VERSION_940 = 30, + CLIENT_VERSION_944_V1 = 31, + CLIENT_VERSION_944_V2 = 32, + CLIENT_VERSION_944_V3 = 33, + CLIENT_VERSION_944_V4 = 34, + CLIENT_VERSION_946 = 35, + CLIENT_VERSION_950 = 36, + CLIENT_VERSION_952 = 37, + CLIENT_VERSION_953 = 38, + CLIENT_VERSION_954 = 39, + CLIENT_VERSION_960 = 40, + CLIENT_VERSION_961 = 41, + CLIENT_VERSION_963 = 42, + CLIENT_VERSION_970 = 43, + CLIENT_VERSION_980 = 44, + CLIENT_VERSION_981 = 45, + CLIENT_VERSION_982 = 46, + CLIENT_VERSION_983 = 47, + CLIENT_VERSION_985 = 48, + CLIENT_VERSION_986 = 49 +}; + +enum rootattrib_ { + ROOT_ATTR_VERSION = 0x01 +}; + +enum itemattrib_t { + ITEM_ATTR_FIRST = 0x10, + ITEM_ATTR_SERVERID = ITEM_ATTR_FIRST, + ITEM_ATTR_CLIENTID, + ITEM_ATTR_NAME, + ITEM_ATTR_DESCR, + ITEM_ATTR_SPEED, + ITEM_ATTR_SLOT, + ITEM_ATTR_MAXITEMS, + ITEM_ATTR_WEIGHT, + ITEM_ATTR_WEAPON, + ITEM_ATTR_AMU, + ITEM_ATTR_ARMOR, + ITEM_ATTR_MAGLEVEL, + ITEM_ATTR_MAGFIELDTYPE, + ITEM_ATTR_WRITEABLE, + ITEM_ATTR_ROTATETO, + ITEM_ATTR_DECAY, + ITEM_ATTR_SPRITEHASH, + ITEM_ATTR_MINIMAPCOLOR, + ITEM_ATTR_07, + ITEM_ATTR_08, + ITEM_ATTR_LIGHT, + + //1-byte aligned + ITEM_ATTR_DECAY2, //deprecated + ITEM_ATTR_WEAPON2, //deprecated + ITEM_ATTR_AMU2, //deprecated + ITEM_ATTR_ARMOR2, //deprecated + ITEM_ATTR_WRITEABLE2, //deprecated + ITEM_ATTR_LIGHT2, + ITEM_ATTR_TOPORDER, + ITEM_ATTR_WRITEABLE3, //deprecated + + ITEM_ATTR_WAREID, + + ITEM_ATTR_LAST +}; + +enum itemflags_t { + FLAG_BLOCK_SOLID = 1, + FLAG_BLOCK_PROJECTILE = 2, + FLAG_BLOCK_PATHFIND = 4, + FLAG_HAS_HEIGHT = 8, + FLAG_USEABLE = 16, + FLAG_PICKUPABLE = 32, + FLAG_MOVEABLE = 64, + FLAG_STACKABLE = 128, + FLAG_FLOORCHANGEDOWN = 256, + FLAG_FLOORCHANGENORTH = 512, + FLAG_FLOORCHANGEEAST = 1024, + FLAG_FLOORCHANGESOUTH = 2048, + FLAG_FLOORCHANGEWEST = 4096, + FLAG_ALWAYSONTOP = 8192, + FLAG_READABLE = 16384, + FLAG_ROTABLE = 32768, + FLAG_HANGABLE = 65536, + FLAG_VERTICAL = 131072, + FLAG_HORIZONTAL = 262144, + FLAG_CANNOTDECAY = 524288, + FLAG_ALLOWDISTREAD = 1048576, + FLAG_UNUSED = 2097152, + FLAG_CLIENTCHARGES = 4194304, /* deprecated */ + FLAG_LOOKTHROUGH = 8388608, + FLAG_ANIMATION = 16777216, + FLAG_FULLTILE = 33554432 +}; + +//1-byte aligned structs +#pragma pack(1) + +struct VERSIONINFO { + uint32_t dwMajorVersion; + uint32_t dwMinorVersion; + uint32_t dwBuildNumber; + uint8_t CSDVersion[128]; +}; + +struct lightBlock2 { + uint16_t lightLevel; + uint16_t lightColor; +}; + +#pragma pack() +/////////OTB specific////////////// +#endif diff --git a/src/items.cpp b/src/items.cpp new file mode 100644 index 0000000000..b24d8e6fec --- /dev/null +++ b/src/items.cpp @@ -0,0 +1,1397 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "items.h" +#include "spells.h" +#include "condition.h" +#include "movement.h" +#include "weapons.h" + +#include + +#include +#include +#include + +uint32_t Items::dwMajorVersion = 0; +uint32_t Items::dwMinorVersion = 0; +uint32_t Items::dwBuildNumber = 0; + +extern MoveEvents* g_moveEvents; +extern Spells* g_spells; +extern Weapons* g_weapons; + +ItemType::ItemType() +{ + abilities = NULL; + article = ""; + group = ITEM_GROUP_NONE; + type = ITEM_TYPE_NONE; + stackable = false; + useable = false; + moveable = true; + alwaysOnTop = false; + alwaysOnTopOrder = 0; + pickupable = false; + rotable = false; + rotateTo = 0; + hasHeight = false; + walkStack = true; + + floorChangeDown = false; + floorChangeNorth = false; + floorChangeSouth = false; + floorChangeSouthAlt = false; + floorChangeEast = false; + floorChangeEastAlt = false; + floorChangeWest = false; + + blockSolid = false; + blockProjectile = false; + blockPathFind = false; + + allowPickupable = false; + + wieldInfo = 0; + minReqLevel = 0; + minReqMagicLevel = 0; + + runeMagLevel = 0; + runeLevel = 0; + + speed = 0; + id = 0; + clientId = 100; + maxItems = 8; // maximum size if this is a container + weight = 0; // weight of the item, e.g. throwing distance depends on it + showCount = true; + weaponType = WEAPON_NONE; + slotPosition = SLOTP_HAND; + ammoType = AMMO_NONE; + ammoAction = AMMOACTION_NONE; + shootType = (ShootType_t)0; + magicEffect = NM_ME_NONE; + attack = 0; + defense = 0; + extraDefense = 0; + armor = 0; + decayTo = -1; + decayTime = 0; + stopTime = false; + corpseType = RACE_NONE; + fluidSource = FLUID_NONE; + allowDistRead = false; + + isVertical = false; + isHorizontal = false; + isHangable = false; + + lightLevel = 0; + lightColor = 0; + + maxTextLen = 0; + canReadText = false; + canWriteText = false; + writeOnceItemId = 0; + + transformEquipTo = 0; + transformDeEquipTo = 0; + showDuration = false; + showCharges = false; + showAttributes = false; + charges = 0; + hitChance = 0; + maxHitChance = -1; + breakChance = -1; + shootRange = 1; + + condition = NULL; + combatType = COMBAT_NONE; + + replaceable = true; + + bedPartnerDir = NORTH; + transformToOnUse[PLAYERSEX_MALE] = 0; + transformToOnUse[PLAYERSEX_FEMALE] = 0; + transformToFree = 0; + + levelDoor = 0; + + ware = false; +} + +ItemType::~ItemType() +{ + delete condition; + delete abilities; +} + +Items::Items() // : items(35000) +{ + this->items = new Array(20500); +} + +Items::~Items() +{ + clear(); +} + +void Items::clear() +{ + if (this->items) { + this->items->reset(); + } +} + +bool Items::reload() +{ + // TODO: make this atomic somehow. + if (!this->items) { + return false; + } + + this->items->reset(); + loadFromOtb("data/items/items.otb"); + + if (!loadFromXml()) { + return false; + } + + g_moveEvents->reload(); + g_weapons->reload(); + return true; +} + +int32_t Items::loadFromOtb(const std::string& file) +{ + FileLoader f; + + if (!f.openFile(file.c_str(), "OTBI", false, true)) { + return f.getError(); + } + + uint32_t type; + NODE node = f.getChildNode(NO_NODE, type); + + PropStream props; + + if (f.getProps(node, props)) { + //4 byte flags + //attributes + //0x01 = version data + uint32_t flags; + + if (!props.GET_ULONG(flags)) { + return ERROR_INVALID_FORMAT; + } + + attribute_t attr; + + if (!props.GET_VALUE(attr)) { + return ERROR_INVALID_FORMAT; + } + + if (attr == ROOT_ATTR_VERSION) { + datasize_t datalen = 0; + + if (!props.GET_VALUE(datalen)) { + return ERROR_INVALID_FORMAT; + } + + if (datalen != sizeof(VERSIONINFO)) { + return ERROR_INVALID_FORMAT; + } + + VERSIONINFO* vi; + + if (!props.GET_STRUCT(vi)) { + return ERROR_INVALID_FORMAT; + } + + Items::dwMajorVersion = vi->dwMajorVersion; //items otb format file version + Items::dwMinorVersion = vi->dwMinorVersion; //client version + Items::dwBuildNumber = vi->dwBuildNumber; //revision + } + } + + if (Items::dwMajorVersion == 0xFFFFFFFF) { + std::cout << "[Warning] Items::loadFromOtb items.otb using generic client version." << std::endl; + } else if (Items::dwMajorVersion != 3) { + std::cout << "Old version detected, a newer version of items.otb is required." << std::endl; + return ERROR_INVALID_FORMAT; + } else if (Items::dwMinorVersion < CLIENT_VERSION_980) { + std::cout << "A newer version of items.otb is required." << std::endl; + return ERROR_INVALID_FORMAT; + } + + node = f.getChildNode(node, type); + + while (node != NO_NODE) { + PropStream props; + + if (!f.getProps(node, props)) { + return f.getError(); + } + + flags_t flags; + ItemType* iType = new ItemType(); + iType->group = (itemgroup_t)type; + + switch (type) { + case ITEM_GROUP_CONTAINER: + iType->type = ITEM_TYPE_CONTAINER; + break; + case ITEM_GROUP_DOOR: + //not used + iType->type = ITEM_TYPE_DOOR; + break; + case ITEM_GROUP_MAGICFIELD: + //not used + iType->type = ITEM_TYPE_MAGICFIELD; + break; + case ITEM_GROUP_TELEPORT: + //not used + iType->type = ITEM_TYPE_TELEPORT; + break; + case ITEM_GROUP_NONE: + case ITEM_GROUP_GROUND: + case ITEM_GROUP_SPLASH: + case ITEM_GROUP_FLUID: + case ITEM_GROUP_CHARGES: + case ITEM_GROUP_DEPRECATED: + break; + default: + return ERROR_INVALID_FORMAT; + break; + } + + //read 4 byte flags + if (!props.GET_VALUE(flags)) { + return ERROR_INVALID_FORMAT; + } + + iType->blockSolid = hasBitSet(FLAG_BLOCK_SOLID, flags); + iType->blockProjectile = hasBitSet(FLAG_BLOCK_PROJECTILE, flags); + iType->blockPathFind = hasBitSet(FLAG_BLOCK_PATHFIND, flags); + iType->hasHeight = hasBitSet(FLAG_HAS_HEIGHT, flags); + iType->useable = hasBitSet(FLAG_USEABLE, flags); + iType->pickupable = hasBitSet(FLAG_PICKUPABLE, flags); + iType->moveable = hasBitSet(FLAG_MOVEABLE, flags); + iType->stackable = hasBitSet(FLAG_STACKABLE, flags); + + iType->alwaysOnTop = hasBitSet(FLAG_ALWAYSONTOP, flags); + iType->isVertical = hasBitSet(FLAG_VERTICAL, flags); + iType->isHorizontal = hasBitSet(FLAG_HORIZONTAL, flags); + iType->isHangable = hasBitSet(FLAG_HANGABLE, flags); + iType->allowDistRead = hasBitSet(FLAG_ALLOWDISTREAD, flags); + iType->rotable = hasBitSet(FLAG_ROTABLE, flags); + iType->canReadText = hasBitSet(FLAG_READABLE, flags); + iType->lookThrough = hasBitSet(FLAG_LOOKTHROUGH, flags); + iType->isAnimation = hasBitSet(FLAG_ANIMATION, flags); + // iType->walkStack = !hasBitSet(FLAG_FULLTILE, flags); + + attribute_t attrib; + datasize_t datalen = 0; + + while (props.GET_VALUE(attrib)) { + //size of data + if (!props.GET_VALUE(datalen)) { + delete iType; + return ERROR_INVALID_FORMAT; + } + + switch (attrib) { + case ITEM_ATTR_SERVERID: { + if (datalen != sizeof(uint16_t)) { + return ERROR_INVALID_FORMAT; + } + + uint16_t serverid; + + if (!props.GET_USHORT(serverid)) { + return ERROR_INVALID_FORMAT; + } + + if (serverid > 30000 && serverid < 30100) { + serverid = serverid - 30000; + } + + iType->id = serverid; + break; + } + + case ITEM_ATTR_CLIENTID: { + if (datalen != sizeof(uint16_t)) { + return ERROR_INVALID_FORMAT; + } + + uint16_t clientid; + + if (!props.GET_USHORT(clientid)) { + return ERROR_INVALID_FORMAT; + } + + iType->clientId = clientid; + break; + } + + case ITEM_ATTR_SPEED: { + if (datalen != sizeof(uint16_t)) { + return ERROR_INVALID_FORMAT; + } + + uint16_t speed; + + if (!props.GET_USHORT(speed)) { + return ERROR_INVALID_FORMAT; + } + + iType->speed = speed; + break; + } + + case ITEM_ATTR_LIGHT2: { + if (datalen != sizeof(lightBlock2)) { + return ERROR_INVALID_FORMAT; + } + + lightBlock2* lb2; + + if (!props.GET_STRUCT(lb2)) { + return ERROR_INVALID_FORMAT; + } + + iType->lightLevel = lb2->lightLevel; + iType->lightColor = lb2->lightColor; + break; + } + + case ITEM_ATTR_TOPORDER: { + if (datalen != sizeof(uint8_t)) { + return ERROR_INVALID_FORMAT; + } + + uint8_t v; + + if (!props.GET_UCHAR(v)) { + return ERROR_INVALID_FORMAT; + } + + iType->alwaysOnTopOrder = v; + break; + } + + case ITEM_ATTR_WAREID: { + if (!props.SKIP_N(datalen)) { + return ERROR_INVALID_FORMAT; + } + + iType->ware = true; + break; + } + + /* + case ITEM_ATTR_NAME: + { + std::string name; + if(!props.GET_STRING(name, datalen)) + return ERROR_INVALID_FORMAT; + + iType->marketName = name; + break; + } + */ + + default: { + //skip unknown attributes + if (!props.SKIP_N(datalen)) { + return ERROR_INVALID_FORMAT; + } + + break; + } + } + } + + if (reverseItemMap.find(iType->clientId) == reverseItemMap.end()) { + reverseItemMap[iType->clientId] = iType->id; + } + + // store the found item + items->addElement(iType, iType->id); + + node = f.getNextNode(node, type); + } + + return ERROR_NONE; +} + +bool Items::loadFromXml() +{ + std::string filename = "data/items/items.xml"; + std::string xmlSchema = "data/items/items.xsd"; + xmlDocPtr doc = xmlParseFile(filename.c_str()); + + std::string strValue; + std::string endString; + + std::vector intVector; + std::vector endVector; + + if (!doc) { + return false; + } + + //validation against xml-schema + xmlDocPtr schemaDoc = xmlReadFile(xmlSchema.c_str(), NULL, XML_PARSE_NONET); + + if (schemaDoc != NULL) { + xmlSchemaParserCtxtPtr schemaParserContext = xmlSchemaNewDocParserCtxt(schemaDoc); + + if (schemaParserContext != NULL) { + xmlSchemaPtr schema = xmlSchemaParse(schemaParserContext); + + if (schema != NULL) { + xmlSchemaValidCtxtPtr validContext = xmlSchemaNewValidCtxt(schema); + + if (validContext != NULL) { + int returnVal = xmlSchemaValidateDoc(validContext, doc); + + if (returnVal != 0) { + if (returnVal > 0) { + std::cout << std::endl << "Warning: [XMLSCHEMA] items.xml could not be validated against XSD" << std::endl; + } else { + std::cout << std::endl << "Warning: [XMLSCHEMA] validation generated an internal error." << std::endl; + } + } + + xmlSchemaFreeValidCtxt(validContext); + } + + xmlSchemaFree(schema); + } + + xmlSchemaFreeParserCtxt(schemaParserContext); + } + + xmlFreeDoc(schemaDoc); + } + + xmlNodePtr root = xmlDocGetRootElement(doc); + + if (xmlStrcmp(root->name, (const xmlChar*)"items") != 0) { + xmlFreeDoc(doc); + return false; + } + + xmlNodePtr itemNode = root->children; + + while (itemNode) { + if (xmlStrcmp(itemNode->name, (const xmlChar*)"item") == 0) { + if (readXMLString(itemNode, "id", strValue)) { + intVector = vectorAtoi(explodeString(strValue, ";")); + size_t vec_size = intVector.size(); + + for (size_t i = 0; i < vec_size; i++) { + this->parseItemNode(itemNode, intVector[i]); + } + } else if (readXMLString(itemNode, "fromid", strValue) && readXMLString(itemNode, "toid", endString)) { + intVector = vectorAtoi(explodeString(strValue, ";")); + endVector = vectorAtoi(explodeString(endString, ";")); + + if (!intVector.empty() && intVector.size() == endVector.size()) { + size_t vec_size = intVector.size(); + + for (size_t i = 0; i < vec_size; i++) { + while (intVector[i] <= endVector[i]) { + parseItemNode(itemNode, intVector[i]++); + } + } + } + } else { + std::cout << "Warning: [Items::loadFromXml] - No itemid found" << std::endl; + } + } + + itemNode = itemNode->next; + } + + xmlFreeDoc(doc); + + //Lets do some checks... + for (uint32_t i = 0; i < items->size(); ++i) { + const ItemType* it = items->getElement(i); + + if (!it) { + continue; + } + + //check bed items + if ((it->transformToFree != 0 || it->transformToOnUse[PLAYERSEX_FEMALE] != 0 || it->transformToOnUse[PLAYERSEX_MALE] != 0) && it->type != ITEM_TYPE_BED) { + std::cout << "Warning: [Items::loadFromXml] Item " << it->id << " is not set as a bed-type." << std::endl; + } + + /* + if(it->marketName.length() > 0 && it->marketName != it->name) + { + std::cout << "ID: " << it->id << ". Market Name: " << it->marketName << ". Item Name: " << it->name << "." << std::endl; + } + */ + } + + return true; +} + +bool Items::parseItemNode(xmlNodePtr itemNode, uint32_t id) +{ + int32_t intValue; + std::string strValue; + + if (id > 30000 && id < 30100) { + id -= 30000; + ItemType* iType = new ItemType(); + iType->id = id; + + items->addElement(iType, id); + } + + ItemType& it = getItemType(id); + + if (readXMLString(itemNode, "name", strValue)) { + it.name = strValue; + } + + if (readXMLString(itemNode, "article", strValue)) { + it.article = strValue; + } + + if (readXMLString(itemNode, "plural", strValue)) { + it.pluralName = strValue; + } + + xmlNodePtr itemAttributesNode = itemNode->children; + + while (itemAttributesNode) { + if (readXMLString(itemAttributesNode, "key", strValue)) { + std::string tmpStrValue = asLowerCaseString(strValue); + + if (tmpStrValue == "type") { + if (readXMLString(itemAttributesNode, "value", strValue)) { + tmpStrValue = asLowerCaseString(strValue); + + if (tmpStrValue == "key") { + it.type = ITEM_TYPE_KEY; + } else if (tmpStrValue == "magicfield") { + it.type = ITEM_TYPE_MAGICFIELD; + } else if (tmpStrValue == "container") { + it.group = ITEM_GROUP_CONTAINER; + it.type = ITEM_TYPE_CONTAINER; + } else if (tmpStrValue == "depot") { + it.type = ITEM_TYPE_DEPOT; + } else if (tmpStrValue == "mailbox") { + it.type = ITEM_TYPE_MAILBOX; + } else if (tmpStrValue == "trashholder") { + it.type = ITEM_TYPE_TRASHHOLDER; + } else if (tmpStrValue == "teleport") { + it.type = ITEM_TYPE_TELEPORT; + } else if (tmpStrValue == "door") { + it.type = ITEM_TYPE_DOOR; + } else if (tmpStrValue == "bed") { + it.type = ITEM_TYPE_BED; + } else if (tmpStrValue == "rune") { + it.type = ITEM_TYPE_RUNE; + } else { + std::cout << "Warning: [Items::loadFromXml] " << "Unknown type " << strValue << std::endl; + } + } + } else if (tmpStrValue == "name") { + if (readXMLString(itemAttributesNode, "value", strValue)) { + it.name = strValue; + } + } else if (tmpStrValue == "article") { + if (readXMLString(itemAttributesNode, "value", strValue)) { + it.article = strValue; + } + } else if (tmpStrValue == "plural") { + if (readXMLString(itemAttributesNode, "value", strValue)) { + it.pluralName = strValue; + } + } else if (tmpStrValue == "description") { + if (readXMLString(itemAttributesNode, "value", strValue)) { + it.description = strValue; + } + } else if (tmpStrValue == "runespellname") { + if (readXMLString(itemAttributesNode, "value", strValue)) { + it.runeSpellName = strValue; + } + } else if (tmpStrValue == "weight") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.weight = intValue / 100.f; + } + } else if (tmpStrValue == "showcount") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.showCount = (intValue != 0); + } + } else if (tmpStrValue == "armor") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.armor = intValue; + } + } else if (tmpStrValue == "defense") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.defense = intValue; + } + } else if (tmpStrValue == "extradef") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.extraDefense = intValue; + } + } else if (tmpStrValue == "attack") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.attack = intValue; + } + } else if (tmpStrValue == "rotateto") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.rotateTo = intValue; + } + } else if (tmpStrValue == "moveable" || tmpStrValue == "movable") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.moveable = (intValue == 1); + } + } else if (tmpStrValue == "blockprojectile") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.blockProjectile = (intValue == 1); + } + } else if (tmpStrValue == "allowpickupable" || tmpStrValue == "pickupable") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.allowPickupable = (intValue != 0); + } + } else if (tmpStrValue == "floorchange") { + if (readXMLString(itemAttributesNode, "value", strValue)) { + tmpStrValue = asLowerCaseString(strValue); + + if (tmpStrValue == "down") { + it.floorChangeDown = true; + } else if (tmpStrValue == "north") { + it.floorChangeNorth = true; + } else if (tmpStrValue == "south") { + it.floorChangeSouth = true; + } else if (tmpStrValue == "southalt" || tmpStrValue == "southex") { + it.floorChangeSouthAlt = true; + } else if (tmpStrValue == "west") { + it.floorChangeWest = true; + } else if (tmpStrValue == "east") { + it.floorChangeEast = true; + } else if (tmpStrValue == "eastalt" || tmpStrValue == "eastex") { + it.floorChangeEastAlt = true; + } + } + } else if (tmpStrValue == "corpsetype") { + tmpStrValue = asLowerCaseString(strValue); + + if (readXMLString(itemAttributesNode, "value", strValue)) { + tmpStrValue = asLowerCaseString(strValue); + + if (tmpStrValue == "venom") { + it.corpseType = RACE_VENOM; + } else if (tmpStrValue == "blood") { + it.corpseType = RACE_BLOOD; + } else if (tmpStrValue == "undead") { + it.corpseType = RACE_UNDEAD; + } else if (tmpStrValue == "fire") { + it.corpseType = RACE_FIRE; + } else if (tmpStrValue == "energy") { + it.corpseType = RACE_ENERGY; + } else { + std::cout << "Warning: [Items::loadFromXml] " << "Unknown corpseType " << strValue << std::endl; + } + } + } else if (tmpStrValue == "containersize") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.maxItems = intValue; + } + } else if (tmpStrValue == "fluidsource") { + if (readXMLString(itemAttributesNode, "value", strValue)) { + tmpStrValue = asLowerCaseString(strValue); + + if (tmpStrValue == "water") { + it.fluidSource = FLUID_WATER; + } else if (tmpStrValue == "blood") { + it.fluidSource = FLUID_BLOOD; + } else if (tmpStrValue == "beer") { + it.fluidSource = FLUID_BEER; + } else if (tmpStrValue == "slime") { + it.fluidSource = FLUID_SLIME; + } else if (tmpStrValue == "lemonade") { + it.fluidSource = FLUID_LEMONADE; + } else if (tmpStrValue == "milk") { + it.fluidSource = FLUID_MILK; + } else if (tmpStrValue == "mana") { + it.fluidSource = FLUID_MANA; + } else if (tmpStrValue == "life") { + it.fluidSource = FLUID_LIFE; + } else if (tmpStrValue == "oil") { + it.fluidSource = FLUID_OIL; + } else if (tmpStrValue == "urine") { + it.fluidSource = FLUID_URINE; + } else if (tmpStrValue == "coconut") { + it.fluidSource = FLUID_COCONUTMILK; + } else if (tmpStrValue == "wine") { + it.fluidSource = FLUID_WINE; + } else if (tmpStrValue == "mud") { + it.fluidSource = FLUID_MUD; + } else if (tmpStrValue == "fruitjuice") { + it.fluidSource = FLUID_FRUITJUICE; + } else if (tmpStrValue == "lava") { + it.fluidSource = FLUID_LAVA; + } else if (tmpStrValue == "rum") { + it.fluidSource = FLUID_RUM; + } else if (tmpStrValue == "swamp") { + it.fluidSource = FLUID_SWAMP; + } else if (tmpStrValue == "tea") { + it.fluidSource = FLUID_TEA; + } else if (tmpStrValue == "mead") { + it.fluidSource = FLUID_MEAD; + } else { + std::cout << "Warning: [Items::loadFromXml] " << "Unknown fluidSource " << strValue << std::endl; + } + } + } else if (tmpStrValue == "readable") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.canReadText = (intValue != 0); + } + } else if (tmpStrValue == "writeable") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.canWriteText = (intValue != 0); + it.canReadText = (intValue != 0); + } + } else if (tmpStrValue == "maxtextlen") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.maxTextLen = intValue; + } + } else if (tmpStrValue == "writeonceitemid") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.writeOnceItemId = intValue; + } + } else if (tmpStrValue == "weapontype") { + if (readXMLString(itemAttributesNode, "value", strValue)) { + tmpStrValue = asLowerCaseString(strValue); + + if (tmpStrValue == "sword") { + it.weaponType = WEAPON_SWORD; + } else if (tmpStrValue == "club") { + it.weaponType = WEAPON_CLUB; + } else if (tmpStrValue == "axe") { + it.weaponType = WEAPON_AXE; + } else if (tmpStrValue == "shield") { + it.weaponType = WEAPON_SHIELD; + } else if (tmpStrValue == "distance") { + it.weaponType = WEAPON_DIST; + } else if (tmpStrValue == "wand") { + it.weaponType = WEAPON_WAND; + } else if (tmpStrValue == "ammunition") { + it.weaponType = WEAPON_AMMO; + } else { + std::cout << "Warning: [Items::loadFromXml] " << "Unknown weaponType " << strValue << std::endl; + } + } + } else if (tmpStrValue == "slottype") { + if (readXMLString(itemAttributesNode, "value", strValue)) { + tmpStrValue = asLowerCaseString(strValue); + + if (tmpStrValue == "head") { + it.slotPosition |= SLOTP_HEAD; + } else if (tmpStrValue == "body") { + it.slotPosition |= SLOTP_ARMOR; + } else if (tmpStrValue == "legs") { + it.slotPosition |= SLOTP_LEGS; + } else if (tmpStrValue == "feet") { + it.slotPosition |= SLOTP_FEET; + } else if (tmpStrValue == "backpack") { + it.slotPosition |= SLOTP_BACKPACK; + } else if (tmpStrValue == "two-handed") { + it.slotPosition |= SLOTP_TWO_HAND; + } else if (tmpStrValue == "right-hand") { + it.slotPosition &= ~SLOTP_LEFT; + } else if (tmpStrValue == "left-hand") { + it.slotPosition &= ~SLOTP_RIGHT; + } else if (tmpStrValue == "necklace") { + it.slotPosition |= SLOTP_NECKLACE; + } else if (tmpStrValue == "ring") { + it.slotPosition |= SLOTP_RING; + } else if (tmpStrValue == "ammo") { + it.slotPosition |= SLOTP_AMMO; + } else if (tmpStrValue == "hand") { + it.slotPosition |= SLOTP_HAND; + } else { + std::cout << "Warning: [Items::loadFromXml] " << "Unknown slotType " << strValue << std::endl; + } + } + } else if (tmpStrValue == "ammotype") { + if (readXMLString(itemAttributesNode, "value", strValue)) { + it.ammoType = getAmmoType(strValue); + + if (it.ammoType == AMMO_NONE) { + std::cout << "Warning: [Items::loadFromXml] " << "Unknown ammoType " << strValue << std::endl; + } + } + } else if (tmpStrValue == "shoottype") { + if (readXMLString(itemAttributesNode, "value", strValue)) { + ShootType_t shoot = getShootType(strValue); + + if (shoot != NM_SHOOT_UNK) { + it.shootType = shoot; + } else { + std::cout << "Warning: [Items::loadFromXml] " << "Unknown shootType " << strValue << std::endl; + } + } + } else if (tmpStrValue == "effect") { + if (readXMLString(itemAttributesNode, "value", strValue)) { + MagicEffectClasses effect = getMagicEffect(strValue); + + if (effect != NM_ME_UNK) { + it.magicEffect = effect; + } else { + std::cout << "Warning: [Items::loadFromXml] " << "Unknown effect " << strValue << std::endl; + } + } + } else if (tmpStrValue == "range") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.shootRange = intValue; + } + } else if (tmpStrValue == "stopduration") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.stopTime = (intValue != 0); + } + } else if (tmpStrValue == "decayto") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.decayTo = intValue; + } + } else if (tmpStrValue == "transformequipto") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.transformEquipTo = intValue; + } + } else if (tmpStrValue == "transformdeequipto") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.transformDeEquipTo = intValue; + } + } else if (tmpStrValue == "duration") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.decayTime = std::max(0, intValue); + } + } else if (tmpStrValue == "showduration") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.showDuration = (intValue != 0); + } + } else if (tmpStrValue == "charges") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.charges = intValue; + } + } else if (tmpStrValue == "showcharges") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.showCharges = (intValue != 0); + } + } else if (tmpStrValue == "showattributes") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.showAttributes = (intValue != 0); + } + } else if (tmpStrValue == "breakchance") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.breakChance = std::min(100, std::max(0, intValue)); + } + } else if (tmpStrValue == "ammoaction") { + if (readXMLString(itemAttributesNode, "value", strValue)) { + it.ammoAction = getAmmoAction(strValue); + + if (it.ammoAction == AMMOACTION_NONE) { + std::cout << "Warning: [Items::loadFromXml] " << "Unknown ammoAction " << strValue << std::endl; + } + } + } else if (tmpStrValue == "hitchance") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.hitChance = std::min(100, std::max(-100, intValue)); + } + } else if (tmpStrValue == "maxhitchance") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.maxHitChance = std::min(100, std::max(0, intValue)); + } + } else if (tmpStrValue == "invisible") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->invisible = (intValue != 0); + } + } else if (tmpStrValue == "speed") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->speed = intValue; + } + } else if (tmpStrValue == "healthgain") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + Abilities* abilities = it.getAbilities(); + abilities->regeneration = true; + abilities->healthGain = intValue; + } + } else if (tmpStrValue == "healthticks") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + Abilities* abilities = it.getAbilities(); + abilities->regeneration = true; + abilities->healthTicks = intValue; + } + } else if (tmpStrValue == "managain") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + Abilities* abilities = it.getAbilities(); + abilities->regeneration = true; + abilities->manaGain = intValue; + } + } else if (tmpStrValue == "manaticks") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + Abilities* abilities = it.getAbilities(); + abilities->regeneration = true; + abilities->manaTicks = intValue; + } + } else if (tmpStrValue == "manashield") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->manaShield = (intValue != 0); + } + } else if (tmpStrValue == "skillsword") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->skills[SKILL_SWORD] = intValue; + } + } else if (tmpStrValue == "skillaxe") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->skills[SKILL_AXE] = intValue; + } + } else if (tmpStrValue == "skillclub") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->skills[SKILL_CLUB] = intValue; + } + } else if (tmpStrValue == "skilldist") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->skills[SKILL_DIST] = intValue; + } + } else if (tmpStrValue == "skillfish") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->skills[SKILL_FISH] = intValue; + } + } else if (tmpStrValue == "skillshield") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->skills[SKILL_SHIELD] = intValue; + } + } else if (tmpStrValue == "skillfist") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->skills[SKILL_FIST] = intValue; + } + } else if (tmpStrValue == "maxhitpoints") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->stats[STAT_MAXHITPOINTS] = intValue; + } + } else if (tmpStrValue == "maxhitpointspercent") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->statsPercent[STAT_MAXHITPOINTS] = intValue; + } + } else if (tmpStrValue == "maxmanapoints") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->stats[STAT_MAXMANAPOINTS] = intValue; + } + } else if (tmpStrValue == "maxmanapointspercent") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->statsPercent[STAT_MAXMANAPOINTS] = intValue; + } + } else if (tmpStrValue == "soulpoints") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->stats[STAT_SOULPOINTS] = intValue; + } + } else if (tmpStrValue == "soulpointspercent") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->statsPercent[STAT_SOULPOINTS] = intValue; + } + } else if (tmpStrValue == "magicpoints" || tmpStrValue == "magiclevelpoints") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->stats[STAT_MAGICPOINTS] = intValue; + } + } else if (tmpStrValue == "magicpointspercent") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->statsPercent[STAT_MAGICPOINTS] = intValue; + } + } else if (tmpStrValue == "absorbpercentall" || tmpStrValue == "absorbpercentallelements") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + Abilities* abilities = it.getAbilities(); + + for (uint32_t i = COMBAT_FIRST; i <= COMBAT_COUNT; i++) { + abilities->absorbPercent[i] += intValue; + } + } + } else if (tmpStrValue == "absorbpercentelements") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + Abilities* abilities = it.getAbilities(); + abilities->absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += intValue; + abilities->absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += intValue; + abilities->absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += intValue; + abilities->absorbPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += intValue; + } + } else if (tmpStrValue == "absorbpercentmagic") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + Abilities* abilities = it.getAbilities(); + abilities->absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += intValue; + abilities->absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += intValue; + abilities->absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += intValue; + abilities->absorbPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += intValue; + abilities->absorbPercent[combatTypeToIndex(COMBAT_HOLYDAMAGE)] += intValue; + abilities->absorbPercent[combatTypeToIndex(COMBAT_DEATHDAMAGE)] += intValue; + } + } else if (tmpStrValue == "absorbpercentenergy") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += intValue; + } + } else if (tmpStrValue == "absorbpercentfire") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += intValue; + } + } else if (tmpStrValue == "absorbpercentpoison" || tmpStrValue == "absorbpercentearth") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += intValue; + } + } else if (tmpStrValue == "absorbpercentice") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->absorbPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += intValue; + } + } else if (tmpStrValue == "absorbpercentholy") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->absorbPercent[combatTypeToIndex(COMBAT_HOLYDAMAGE)] += intValue; + } + } else if (tmpStrValue == "absorbpercentdeath") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->absorbPercent[combatTypeToIndex(COMBAT_DEATHDAMAGE)] += intValue; + } + } else if (tmpStrValue == "absorbpercentlifedrain") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->absorbPercent[combatTypeToIndex(COMBAT_LIFEDRAIN)] += intValue; + } + } else if (tmpStrValue == "absorbpercentmanadrain") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->absorbPercent[combatTypeToIndex(COMBAT_MANADRAIN)] += intValue; + } + } else if (tmpStrValue == "absorbpercentdrown") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->absorbPercent[combatTypeToIndex(COMBAT_DROWNDAMAGE)] += intValue; + } + } else if (tmpStrValue == "absorbpercentphysical") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->absorbPercent[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)] += intValue; + } + } else if (tmpStrValue == "absorbpercenthealing") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->absorbPercent[combatTypeToIndex(COMBAT_HEALING)] += intValue; + } + } else if (tmpStrValue == "absorbpercentundefined") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->absorbPercent[combatTypeToIndex(COMBAT_UNDEFINEDDAMAGE)] += intValue; + } + } else if (tmpStrValue == "suppressdrunk") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->conditionSuppressions |= CONDITION_DRUNK; + } + } else if (tmpStrValue == "suppressenergy") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->conditionSuppressions |= CONDITION_ENERGY; + } + } else if (tmpStrValue == "suppressfire") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->conditionSuppressions |= CONDITION_FIRE; + } + } else if (tmpStrValue == "suppresspoison") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->conditionSuppressions |= CONDITION_POISON; + } + } else if (tmpStrValue == "suppressdrown") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->conditionSuppressions |= CONDITION_DROWN; + } + } else if (tmpStrValue == "suppressphysical") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->conditionSuppressions |= CONDITION_BLEEDING; + } + } else if (tmpStrValue == "suppressfreeze") { + if (readXMLInteger(itemAttributesNode, "value", intValue) && intValue != 0) { + it.getAbilities()->conditionSuppressions |= CONDITION_FREEZING; + } + } else if (tmpStrValue == "suppressdazzle") { + if (readXMLInteger(itemAttributesNode, "value", intValue) && intValue != 0) { + it.getAbilities()->conditionSuppressions |= CONDITION_DAZZLED; + } + } else if (tmpStrValue == "suppresscurse") { + if (readXMLInteger(itemAttributesNode, "value", intValue) && intValue != 0) { + it.getAbilities()->conditionSuppressions |= CONDITION_CURSED; + } + } else if (tmpStrValue == "field") { + it.group = ITEM_GROUP_MAGICFIELD; + it.type = ITEM_TYPE_MAGICFIELD; + + if (readXMLString(itemAttributesNode, "value", strValue)) { + CombatType_t combatType = COMBAT_NONE; + ConditionDamage* conditionDamage = NULL; + + tmpStrValue = asLowerCaseString(strValue); + + if (tmpStrValue == "fire") { + conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_FIRE); + combatType = COMBAT_FIREDAMAGE; + } else if (tmpStrValue == "energy") { + conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_ENERGY); + combatType = COMBAT_ENERGYDAMAGE; + } else if (tmpStrValue == "poison") { + conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_POISON); + combatType = COMBAT_EARTHDAMAGE; + } else if (tmpStrValue == "drown") { + conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_DROWN); + combatType = COMBAT_DROWNDAMAGE; + } else if (tmpStrValue == "physical") { + conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_BLEEDING); + combatType = COMBAT_PHYSICALDAMAGE; + } else { + std::cout << "Warning: [Items::loadFromXml] " << "Unknown field value " << strValue << std::endl; + } + + if (combatType != COMBAT_NONE) { + it.combatType = combatType; + it.condition = conditionDamage; + uint32_t ticks = 0; + int32_t damage = 0; + int32_t start = 0; + int32_t count = 1; + + xmlNodePtr fieldAttributesNode = itemAttributesNode->children; + + while (fieldAttributesNode) { + if (readXMLString(fieldAttributesNode, "key", strValue)) { + tmpStrValue = asLowerCaseString(strValue); + + if (tmpStrValue == "ticks") { + if (readXMLInteger(fieldAttributesNode, "value", intValue)) { + ticks = std::max(0, intValue); + } + } + + if (tmpStrValue == "count") { + if (readXMLInteger(fieldAttributesNode, "value", intValue)) { + count = std::max(1, intValue); + } + } + + if (tmpStrValue == "start") { + if (readXMLInteger(fieldAttributesNode, "value", intValue)) { + start = std::max(0, intValue); + } + } + + if (tmpStrValue == "damage") { + if (readXMLInteger(fieldAttributesNode, "value", intValue)) { + damage = -intValue; + + if (start > 0) { + std::list damageList; + ConditionDamage::generateDamageList(damage, start, damageList); + + for (std::list::iterator it = damageList.begin(); it != damageList.end(); ++it) { + conditionDamage->addDamage(1, ticks, -*it); + } + + start = 0; + } else { + conditionDamage->addDamage(count, ticks, damage); + } + } + } + } + + fieldAttributesNode = fieldAttributesNode->next; + } + + if (conditionDamage->getTotalDamage() > 0) { + conditionDamage->setParam(CONDITIONPARAM_FORCEUPDATE, 1); + } + } + } + } else if (tmpStrValue == "replaceable") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.replaceable = (intValue != 0); + } + } else if (tmpStrValue == "partnerdirection") { + if (readXMLString(itemAttributesNode, "value", strValue)) { + it.bedPartnerDir = getDirection(strValue); + } + } else if (tmpStrValue == "leveldoor") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.levelDoor = intValue; + } + } else if (tmpStrValue == "maletransformto" || tmpStrValue == "malesleeper") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.transformToOnUse[PLAYERSEX_MALE] = intValue; + ItemType& other = getItemType(intValue); + + if (other.transformToFree == 0) { + other.transformToFree = it.id; + } + + if (it.transformToOnUse[PLAYERSEX_FEMALE] == 0) { + it.transformToOnUse[PLAYERSEX_FEMALE] = intValue; + } + } + } else if (tmpStrValue == "femaletransformto" || tmpStrValue == "femalesleeper") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.transformToOnUse[PLAYERSEX_FEMALE] = intValue; + } + + ItemType& other = getItemType(intValue); + + if (other.transformToFree == 0) { + other.transformToFree = it.id; + } + + if (it.transformToOnUse[PLAYERSEX_MALE] == 0) { + it.transformToOnUse[PLAYERSEX_MALE] = intValue; + } + } else if (tmpStrValue == "transformto") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.transformToFree = intValue; + } + } else if (tmpStrValue == "elementice") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->elementDamage = intValue; + it.getAbilities()->elementType = COMBAT_ICEDAMAGE; + } + } else if (tmpStrValue == "elementearth") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->elementDamage = intValue; + it.getAbilities()->elementType = COMBAT_EARTHDAMAGE; + } + } else if (tmpStrValue == "elementfire") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->elementDamage = intValue; + it.getAbilities()->elementType = COMBAT_FIREDAMAGE; + } + } else if (tmpStrValue == "elementenergy") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.getAbilities()->elementDamage = intValue; + it.getAbilities()->elementType = COMBAT_ENERGYDAMAGE; + } + } else if (tmpStrValue == "walkstack") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.walkStack = (intValue != 0); + } + } else if (tmpStrValue == "alwaysontop") { + if (readXMLString(itemAttributesNode, "value", strValue)) { + it.alwaysOnTop = booleanString(strValue); + } + } else if (tmpStrValue == "toporder") { + if (readXMLInteger(itemAttributesNode, "value", intValue)) { + it.alwaysOnTopOrder = intValue; + } + } else if (tmpStrValue == "allowdistread") { + if (readXMLString(itemAttributesNode, "value", strValue)) { + it.allowDistRead = booleanString(strValue); + } + } else { + std::cout << "Warning: [Items::loadFromXml] Unknown key value " << strValue << std::endl; + } + } + + itemAttributesNode = itemAttributesNode->next; + } + + return true; +} + +ItemType& Items::getItemType(int32_t id) +{ + ItemType* iType = items->getElement(id); + + if (iType) { + return *iType; + } + + static ItemType dummyItemType; // use this for invalid ids + return dummyItemType; +} + +const ItemType& Items::getItemType(int32_t id) const +{ + ItemType* iType = items->getElement(id); + + if (iType) { + return *iType; + } + + static ItemType dummyItemType; // use this for invalid ids + return dummyItemType; +} + +const ItemType& Items::getItemIdByClientId(int32_t spriteId) const +{ + ReverseItemMap::const_iterator it = reverseItemMap.find(spriteId); + + if (it != reverseItemMap.end()) { + ItemType* iType = items->getElement(it->second); + + if (iType) { + return *iType; + } + } + + static ItemType dummyItemType; // use this for invalid ids + return dummyItemType; +} + +const std::list Items::getItemIdsByClientId(int32_t spriteId) const +{ + std::list itemIds; + + uint32_t i = 100; + ItemType* iType = items->getElement(i); + + while (iType) { + if (iType->clientId == spriteId) { + itemIds.push_back(iType); + } + + iType = items->getElement(++i); + } + + return itemIds; +} + +int32_t Items::getItemIdByName(const std::string& name) +{ + if (name.empty()) { + return -1; + } + + const char* tmpName = name.c_str(); + + uint32_t i = 100; + + ItemType* iType = items->getElement(i); + + while (iType) { + if (strcasecmp(tmpName, iType->name.c_str()) == 0) { + return i; + } + + iType = items->getElement(++i); + } + + return -1; +} diff --git a/src/items.h b/src/items.h new file mode 100644 index 0000000000..d7a1c62117 --- /dev/null +++ b/src/items.h @@ -0,0 +1,421 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_ITEMS_H__ +#define __OTSERV_ITEMS_H__ + +#include "definitions.h" +#include "const.h" +#include "enums.h" +#include "itemloader.h" +#include "position.h" +#include + +#include +#include + +#define SLOTP_WHEREEVER 0xFFFFFFFF +#define SLOTP_HEAD 1 +#define SLOTP_NECKLACE 2 +#define SLOTP_BACKPACK 4 +#define SLOTP_ARMOR 8 +#define SLOTP_RIGHT 16 +#define SLOTP_LEFT 32 +#define SLOTP_LEGS 64 +#define SLOTP_FEET 128 +#define SLOTP_RING 256 +#define SLOTP_AMMO 512 +#define SLOTP_DEPOT 1024 +#define SLOTP_TWO_HAND 2048 +#define SLOTP_HAND (SLOTP_LEFT | SLOTP_RIGHT) + +enum ItemTypes_t { + ITEM_TYPE_NONE = 0, + ITEM_TYPE_DEPOT, + ITEM_TYPE_MAILBOX, + ITEM_TYPE_TRASHHOLDER, + ITEM_TYPE_CONTAINER, + ITEM_TYPE_DOOR, + ITEM_TYPE_MAGICFIELD, + ITEM_TYPE_TELEPORT, + ITEM_TYPE_BED, + ITEM_TYPE_KEY, + ITEM_TYPE_RUNE, + ITEM_TYPE_LAST +}; + +struct Abilities { + Abilities() { + elementType = COMBAT_NONE; + elementDamage = 0; + memset(skills, 0, sizeof(skills)); + memset(absorbPercent, 0, sizeof(absorbPercent)); + memset(stats, 0 , sizeof(stats)); + memset(statsPercent, 0, sizeof(statsPercent)); + + speed = 0; + manaShield = false; + invisible = false; + regeneration = false; + + healthGain = 0; + healthTicks = 0; + manaGain = 0; + manaTicks = 0; + + conditionImmunities = 0; + conditionSuppressions = 0; + }; + + //elemental damage + CombatType_t elementType; + int16_t elementDamage; + + //extra skill modifiers + int32_t skills[SKILL_LAST + 1]; + + //damage abilities modifiers + int16_t absorbPercent[COMBAT_COUNT + 1]; + + //stats modifiers + int32_t stats[STAT_LAST + 1]; + int32_t statsPercent[STAT_LAST + 1]; + + int32_t speed; + bool manaShield; + bool invisible; + bool regeneration; + + uint32_t healthGain; + uint32_t healthTicks; + uint32_t manaGain; + uint32_t manaTicks; + + uint32_t conditionImmunities; + uint32_t conditionSuppressions; +}; + +class Condition; + +class ItemType +{ + private: + ItemType(const ItemType& it) {} + + public: + ItemType(); + virtual ~ItemType(); + + itemgroup_t group; + ItemTypes_t type; + + bool isGroundTile() const { + return (group == ITEM_GROUP_GROUND); + } + bool isContainer() const { + return (group == ITEM_GROUP_CONTAINER); + } + bool isSplash() const { + return (group == ITEM_GROUP_SPLASH); + } + bool isFluidContainer() const { + return (group == ITEM_GROUP_FLUID); + } + + bool isDoor() const { + return (type == ITEM_TYPE_DOOR); + } + bool isMagicField() const { + return (type == ITEM_TYPE_MAGICFIELD); + } + bool isTeleport() const { + return (type == ITEM_TYPE_TELEPORT); + } + bool isKey() const { + return (type == ITEM_TYPE_KEY); + } + bool isDepot() const { + return (type == ITEM_TYPE_DEPOT); + } + bool isMailbox() const { + return (type == ITEM_TYPE_MAILBOX); + } + bool isTrashHolder() const { + return (type == ITEM_TYPE_TRASHHOLDER); + } + bool isBed() const { + return (type == ITEM_TYPE_BED); + } + + bool isRune() const { + return type == ITEM_TYPE_RUNE; + } + bool hasSubType() const { + return (isFluidContainer() || isSplash() || stackable || charges != 0); + } + + Abilities* getAbilities() { + if (abilities == NULL) { + abilities = new Abilities(); + } + + return abilities; + } + + std::string getPluralName() const { + std::string str = pluralName; + + if (str.size() == 0 && name.size() != 0) { + str = name; + + if (showCount != 0) { + str += "s"; + } + } + + return str; + } + + Direction bedPartnerDir; + uint16_t transformToOnUse[2]; + uint16_t transformToFree; + + uint16_t id; + uint16_t clientId; + + std::string name; + std::string article; + std::string pluralName; + std::string description; + uint16_t maxItems; + float weight; + bool showCount; + WeaponType_t weaponType; + Ammo_t ammoType; + ShootType_t shootType; + MagicEffectClasses magicEffect; + int32_t attack; + int32_t defense; + int32_t extraDefense; + int32_t armor; + uint16_t slotPosition; + uint32_t levelDoor; + bool isVertical; + bool isHorizontal; + bool isHangable; + bool allowDistRead; + bool lookThrough; + bool isAnimation; + uint16_t speed; + int32_t decayTo; + uint32_t decayTime; + bool stopTime; + RaceType_t corpseType; + + bool canReadText; + bool canWriteText; + uint16_t maxTextLen; + uint16_t writeOnceItemId; + + bool stackable; + bool useable; + bool moveable; + bool alwaysOnTop; + int32_t alwaysOnTopOrder; + bool pickupable; + bool rotable; + int32_t rotateTo; + + int32_t runeMagLevel; + int32_t runeLevel; + std::string runeSpellName; + + uint32_t wieldInfo; + std::string vocationString; + uint32_t minReqLevel; + uint32_t minReqMagicLevel; + + int32_t lightLevel; + int32_t lightColor; + + bool floorChangeDown; + bool floorChangeNorth; + bool floorChangeSouth; + bool floorChangeSouthAlt; + bool floorChangeEast; + bool floorChangeEastAlt; + bool floorChangeWest; + bool hasHeight; + + bool walkStack; + + bool blockSolid; + bool blockPickupable; + bool blockProjectile; + bool blockPathFind; + + bool allowPickupable; + + unsigned short transformEquipTo; + unsigned short transformDeEquipTo; + bool showDuration; + bool showCharges; + bool showAttributes; + uint32_t charges; + int32_t breakChance; + int32_t hitChance; + int32_t maxHitChance; + uint32_t shootRange; + AmmoAction_t ammoAction; + FluidTypes_t fluidSource; + + Abilities* abilities; + + Condition* condition; + CombatType_t combatType; + bool replaceable; + bool ware; + + // std::string marketName; +}; + +template +class Array +{ + public: + Array(uint32_t n); + ~Array(); + + A getElement(uint32_t id); + const A getElement(uint32_t id) const; + void addElement(A a, uint32_t pos); + + void reset(); + + uint32_t size() { + return m_size; + } + + private: + A* m_data; + uint32_t m_size; +}; + +class Items +{ + public: + Items(); + ~Items(); + + bool reload(); + void clear(); + + int32_t loadFromOtb(const std::string& file); + + const ItemType& operator[](int32_t id) const { + return getItemType(id); + } + const ItemType& getItemType(int32_t id) const; + ItemType& getItemType(int32_t id); + const ItemType& getItemIdByClientId(int32_t spriteId) const; + const std::list getItemIdsByClientId(int32_t spriteId) const; + + int32_t getItemIdByName(const std::string& name); + + static uint32_t dwMajorVersion; + static uint32_t dwMinorVersion; + static uint32_t dwBuildNumber; + + bool loadFromXml(); + bool parseItemNode(xmlNodePtr itemNode, uint32_t id); + + void addItemType(ItemType* iType); + + const ItemType* getElement(uint32_t id) const { + return items->getElement(id); + } + uint32_t size() { + return items->size(); + } + + protected: + typedef std::map ReverseItemMap; + ReverseItemMap reverseItemMap; + + Array* items; +}; + +template +Array::Array(uint32_t n) +{ + m_data = (A*)malloc(sizeof(A) * n); + memset(m_data, 0, sizeof(A)*n); + m_size = n; +} + +template +Array::~Array() +{ + free(m_data); +} + +template +A Array::getElement(uint32_t id) +{ + if (id < m_size) { + return m_data[id]; + } + + return 0; +} + +template +const A Array::getElement(uint32_t id) const +{ + if (id < m_size) { + return m_data[id]; + } + + return 0; +} + +template +void Array::addElement(A a, uint32_t pos) +{ +#define INCREMENT 5000 + + if (pos >= m_size) { + m_data = (A*)realloc(m_data, sizeof(A) * (pos + INCREMENT)); + memset(m_data + m_size, 0, sizeof(A) * (pos + INCREMENT - m_size)); + m_size = pos + INCREMENT; + } + + m_data[pos] = a; +} + +template +void Array::reset() +{ + for (uint32_t i = 0; i < m_size; i++) { + delete m_data[i]; + m_data[i] = NULL; + } + + memset(this->m_data, 0, sizeof(A) * this->m_size); +} +#endif diff --git a/src/logger.cpp b/src/logger.cpp new file mode 100644 index 0000000000..b389c11f96 --- /dev/null +++ b/src/logger.cpp @@ -0,0 +1,75 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include + +#include "logger.h" +#include +#include "tools.h" + +Logger::Logger() +{ + m_file = fopen("data/logs/otadmin.log", "a"); + + if (!m_file) { + m_file = stderr; + } +} + +Logger::~Logger() +{ + if (m_file) { + fclose(m_file); + } +} + +void Logger::logMessage(const char* channel, eLogType type, int32_t level, const std::string& message, const char* func) +{ + fprintf(m_file, "%s", formatDate(time(NULL)).c_str()); + + if (channel) { + fprintf(m_file, " [%s] ", channel); + } + + if (strcmp(func, "") != 0) { + fprintf(m_file, " %s ", func); + } + + std::string type_str; + + switch (type) { + case LOGTYPE_EVENT: + type_str = "event"; + break; + case LOGTYPE_WARNING: + type_str = "warning"; + break; + case LOGTYPE_ERROR: + type_str = "error"; + break; + default: + type_str = "unknown"; + break; + } + + fprintf(m_file, " %s:", type_str.c_str()); + fprintf(m_file, " %s\n", message.c_str()); + fflush(m_file); +} diff --git a/src/logger.h b/src/logger.h new file mode 100644 index 0000000000..0fe404f7af --- /dev/null +++ b/src/logger.h @@ -0,0 +1,64 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_LOGGER_H__ +#define __OTSERV_LOGGER_H__ + +#include "definitions.h" + +#ifdef __GNUC__ +#define __OTSERV_PRETTY_FUNCTION__ __PRETTY_FUNCTION__ +#endif +#ifdef _MSC_VER +#define __OTSERV_PRETTY_FUNCTION__ __FUNCDNAME__ +#endif + +/* +#define LOG_MESSAGE(channel, type, level, message) \ + Logger::getInstance()->logMessage(channel, type, level, message, __OTSERV_PRETTY_FUNCTION__, __LINE__, __FILE__); +*/ + +#define LOG_MESSAGE(channel, type, level, message) \ + Logger::getInstance()->logMessage(channel, type, level, message, __OTSERV_PRETTY_FUNCTION__); + +#include +#include + +enum eLogType { + LOGTYPE_EVENT, + LOGTYPE_WARNING, + LOGTYPE_ERROR, +}; + +class Logger +{ + public: + ~Logger(); + static Logger* getInstance() { + static Logger instance; + return &instance; + } + + void logMessage(const char* channel, eLogType type, int32_t level, const std::string& message, const char* func); + + private: + FILE* m_file; + Logger(); +}; + +#endif diff --git a/src/luascript.cpp b/src/luascript.cpp new file mode 100644 index 0000000000..5f0377b5da --- /dev/null +++ b/src/luascript.cpp @@ -0,0 +1,8581 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include +#include +#include + +#include "luascript.h" +#include "chat.h" +#include "player.h" +#include "item.h" +#include "game.h" +#include "house.h" +#include "housetile.h" +#include "status.h" +#include "combat.h" +#include "spells.h" +#include "condition.h" +#include "monsters.h" +#include "baseevents.h" +#include "iologindata.h" +#include "configmanager.h" +#include "town.h" +#include "vocation.h" +#include "teleport.h" +#include "ban.h" +#include "mounts.h" +#include "databasemanager.h" + +extern Chat g_chat; +extern Game g_game; +extern Monsters g_monsters; +extern ConfigManager g_config; +extern Vocations g_vocations; +extern Spells* g_spells; + +enum { + EVENT_ID_LOADING = 1, + EVENT_ID_USER = 1000, +}; + +ScriptEnvironment::ThingMap ScriptEnvironment::m_globalMap; + +ScriptEnvironment::AreaMap ScriptEnvironment::m_areaMap; +uint32_t ScriptEnvironment::m_lastAreaId = 0; + +ScriptEnvironment::CombatMap ScriptEnvironment::m_combatMap; +uint32_t ScriptEnvironment::m_lastCombatId = 0; + +ScriptEnvironment::ConditionMap ScriptEnvironment::m_conditionMap; +uint32_t ScriptEnvironment::m_lastConditionId = 0; + +ScriptEnvironment::DBResultMap ScriptEnvironment::m_tempResults; +uint32_t ScriptEnvironment::m_lastResultId = 0; + +ScriptEnvironment::StorageMap ScriptEnvironment::m_globalStorageMap; + +ScriptEnvironment::TempItemListMap ScriptEnvironment::m_tempItems; + +ScriptEnvironment::ScriptEnvironment() +{ + m_curNpc = NULL; + resetEnv(); + m_lastUID = 70000; +} + +ScriptEnvironment::~ScriptEnvironment() +{ + resetEnv(); + + for (CombatMap::iterator it = m_combatMap.begin(); it != m_combatMap.end(); ++it) { + delete it->second; + } + + m_combatMap.clear(); + + for (AreaMap::iterator it = m_areaMap.begin(); it != m_areaMap.end(); ++it) { + delete it->second; + } + + m_areaMap.clear(); + + for (ConditionMap::iterator it = m_conditionMap.begin(); it != m_conditionMap.end(); ++it) { + delete it->second; + } + + m_conditionMap.clear(); +} + +void ScriptEnvironment::resetEnv() +{ + m_scriptId = 0; + m_callbackId = 0; + m_timerEvent = false; + m_interface = NULL; + m_localMap.clear(); + + for (TempItemListMap::iterator mit = m_tempItems.begin(); mit != m_tempItems.end(); ++mit) { + ItemList& itemList = mit->second; + + for (ItemList::iterator it = itemList.begin(); it != itemList.end(); ++it) { + if ((*it)->getParent() == VirtualCylinder::virtualCylinder) { + g_game.FreeThing(*it); + } + } + } + + m_tempItems.clear(); + + if (!m_tempResults.empty()) { + DBQuery query; + Database* db = Database::getInstance(); + + for (DBResultMap::iterator it = m_tempResults.begin(); it != m_tempResults.end(); ++it) { + if (it->second) { + db->freeResult(it->second); + } + } + } + + m_tempResults.clear(); + + m_realPos.x = 0; + m_realPos.y = 0; + m_realPos.z = 0; +} + +bool ScriptEnvironment::saveGameState() +{ + if (!g_config.getBoolean(ConfigManager::SAVE_GLOBAL_STORAGE)) { + return true; + } + + Database* db = Database::getInstance(); + + DBQuery query; + + if (!db->executeQuery("DELETE FROM `global_storage`")) { + return false; + } + + DBInsert stmt(db); + stmt.setQuery("INSERT INTO `global_storage` (`key`, `value`) VALUES "); + + for (StorageMap::const_iterator it = m_globalStorageMap.begin(); it != m_globalStorageMap.end(); ++it) { + query << it->first << "," << it->second; + + if (!stmt.addRow(query)) { + return false; + } + } + + return stmt.execute(); +} + +bool ScriptEnvironment::loadGameState() +{ + if (!g_config.getBoolean(ConfigManager::SAVE_GLOBAL_STORAGE)) { + return true; + } + + Database* db = Database::getInstance(); + + DBQuery query; + DBResult* result; + query << "SELECT `key`, `value` FROM `global_storage`"; + + if ((result = db->storeQuery(query.str()))) { + do { + m_globalStorageMap[result->getDataInt("key")] = result->getDataInt("value"); + } while (result->next()); + + db->freeResult(result); + } + + return true; +} + +bool ScriptEnvironment::setCallbackId(int32_t callbackId, LuaScriptInterface* scriptInterface) +{ + if (m_callbackId == 0) { + m_callbackId = callbackId; + m_interface = scriptInterface; + return true; + } else { + //nested callbacks are not allowed + if (m_interface) { + m_interface->reportErrorFunc("Nested callbacks!"); + } + + return false; + } +} + +void ScriptEnvironment::getEventInfo(int32_t& scriptId, std::string& desc, LuaScriptInterface*& scriptInterface, int32_t& callbackId, bool& timerEvent) const +{ + scriptId = m_scriptId; + desc = m_eventdesc; + scriptInterface = m_interface; + callbackId = m_callbackId; + timerEvent = m_timerEvent; +} + +void ScriptEnvironment::addUniqueThing(Thing* thing) +{ + Item* item = thing->getItem(); + + if (item && item->getUniqueId() != 0) { + int32_t uid = item->getUniqueId(); + + ThingMap::const_iterator it = m_globalMap.find(uid); + + if (it == m_globalMap.end()) { + m_globalMap[uid] = thing; + } else { + std::cout << "Duplicate uniqueId " << uid << std::endl; + } + } +} + +void ScriptEnvironment::removeUniqueThing(Thing* thing) +{ + Item* item = thing->getItem(); + + if (item && item->getUniqueId() != 0) { + int32_t uid = item->getUniqueId(); + + ThingMap::iterator it = m_globalMap.find(uid); + + if (it != m_globalMap.end()) { + m_globalMap.erase(it); + } + } +} + +uint32_t ScriptEnvironment::addThing(Thing* thing) +{ + if (!thing || thing->isRemoved()) { + return 0; + } + + for (ThingMap::const_iterator it = m_localMap.begin(), end = m_localMap.end(); it != end; ++it) { + if (it->second == thing) { + return it->first; + } + } + + uint32_t newUid; + + if (Creature* creature = thing->getCreature()) { + newUid = creature->getID(); + } else { + if (Item* item = thing->getItem()) { + uint32_t uid = item->getUniqueId(); + + if (uid && item->getTile() == item->getParent()) { + m_localMap[uid] = thing; + return uid; + } + } + + ++m_lastUID; + + if (m_lastUID < 70000) { + m_lastUID = 70000; + } + + while (m_localMap.find(m_lastUID) != m_localMap.end()) { + ++m_lastUID; + } + + newUid = m_lastUID; + } + + m_localMap[newUid] = thing; + return newUid; +} + +void ScriptEnvironment::insertThing(uint32_t uid, Thing* thing) +{ + if (m_localMap.find(uid) == m_localMap.end()) { + m_localMap[uid] = thing; + } else { + std::cout << std::endl << "Lua Script Error: Thing uid already taken."; + } +} + +Thing* ScriptEnvironment::getThingByUID(uint32_t uid) +{ + ThingMap::const_iterator it = m_localMap.find(uid); + + if (it != m_localMap.end()) { + Thing* thing = it->second; + + if (thing && !thing->isRemoved()) { + return thing; + } + } + + it = m_globalMap.find(uid); + + if (it != m_globalMap.end()) { + Thing* thing = it->second; + + if (thing && !thing->isRemoved()) { + return thing; + } + } + + if (uid >= 0x10000000) { + Thing* thing = g_game.getCreatureByID(uid); + + if (thing && !thing->isRemoved()) { + m_localMap[uid] = thing; + return thing; + } + } + + return NULL; +} + +Item* ScriptEnvironment::getItemByUID(uint32_t uid) +{ + Thing* thing = getThingByUID(uid); + + if (thing) { + if (Item* item = thing->getItem()) { + return item; + } + } + + return NULL; +} + +Container* ScriptEnvironment::getContainerByUID(uint32_t uid) +{ + Item* item = getItemByUID(uid); + + if (item) { + if (Container* container = item->getContainer()) { + return container; + } + } + + return NULL; +} + +Creature* ScriptEnvironment::getCreatureByUID(uint32_t uid) +{ + Thing* thing = getThingByUID(uid); + + if (thing) { + if (Creature* creature = thing->getCreature()) { + return creature; + } + } + + return NULL; +} + +Player* ScriptEnvironment::getPlayerByUID(uint32_t uid) +{ + Creature* creature = getCreatureByUID(uid); + + if (creature) { + if (Player* player = creature->getPlayer()) { + return player; + } + } + + return NULL; +} + +Monster* ScriptEnvironment::getMonsterByUID(uint32_t uid) +{ + Creature* creature = getCreatureByUID(uid); + + if (creature) { + if (Monster* monster = creature->getMonster()) { + return monster; + } + } + + return NULL; +} + +Npc* ScriptEnvironment::getNpcByUID(uint32_t uid) +{ + Creature* creature = getCreatureByUID(uid); + + if (creature) { + if (Npc* npc = creature->getNpc()) { + return npc; + } + } + + return NULL; +} + +void ScriptEnvironment::removeItemByUID(uint32_t uid) +{ + ThingMap::iterator it = m_localMap.find(uid); + + if (it != m_localMap.end()) { + m_localMap.erase(it); + } + + it = m_globalMap.find(uid); + + if (it != m_globalMap.end()) { + m_globalMap.erase(it); + } +} + +uint32_t ScriptEnvironment::addCombatArea(AreaCombat* area) +{ + uint32_t newAreaId = m_lastAreaId + 1; + m_areaMap[newAreaId] = area; + + m_lastAreaId++; + return newAreaId; +} + +AreaCombat* ScriptEnvironment::getCombatArea(uint32_t areaId) +{ + AreaMap::const_iterator it = m_areaMap.find(areaId); + + if (it != m_areaMap.end()) { + return it->second; + } + + return NULL; +} + +uint32_t ScriptEnvironment::addCombatObject(Combat* combat) +{ + uint32_t newCombatId = m_lastCombatId + 1; + m_combatMap[newCombatId] = combat; + + m_lastCombatId++; + return newCombatId; +} + +Combat* ScriptEnvironment::getCombatObject(uint32_t combatId) +{ + CombatMap::iterator it = m_combatMap.find(combatId); + + if (it != m_combatMap.end()) { + return it->second; + } + + return NULL; +} + +uint32_t ScriptEnvironment::addConditionObject(Condition* condition) +{ + uint32_t newConditionId = m_lastConditionId + 1; + m_conditionMap[newConditionId] = condition; + + m_lastConditionId++; + return m_lastConditionId; +} + +Condition* ScriptEnvironment::getConditionObject(uint32_t conditionId) +{ + ConditionMap::iterator it = m_conditionMap.find(conditionId); + + if (it != m_conditionMap.end()) { + return it->second; + } + + return NULL; +} + +void ScriptEnvironment::addTempItem(ScriptEnvironment* env, Item* item) +{ + m_tempItems[env].push_back(item); +} + +void ScriptEnvironment::removeTempItem(ScriptEnvironment* env, Item* item) +{ + ItemList& itemList = m_tempItems[env]; + ItemList::iterator it = std::find(itemList.begin(), itemList.end(), item); + + if (it != itemList.end()) { + itemList.erase(it); + } +} + +void ScriptEnvironment::removeTempItem(Item* item) +{ + for (TempItemListMap::iterator mit = m_tempItems.begin(); mit != m_tempItems.end(); ++mit) { + ItemList& itemList = mit->second; + ItemList::iterator it = std::find(itemList.begin(), itemList.end(), item); + + if (it != itemList.end()) { + itemList.erase(it); + break; + } + } +} + +uint32_t ScriptEnvironment::addResult(DBResult* res) +{ + uint32_t newResultId = m_lastResultId + 1; + m_tempResults[newResultId] = res; + + m_lastResultId++; + return m_lastResultId; +} + +bool ScriptEnvironment::removeResult(uint32_t id) +{ + DBResultMap::iterator it = m_tempResults.find(id); + + if (it == m_tempResults.end()) { + return false; + } + + if (it->second) { + DBQuery query; + Database::getInstance()->freeResult(it->second); + } + + m_tempResults.erase(it); + return true; +} + +DBResult* ScriptEnvironment::getResultByID(uint32_t id) +{ + DBResultMap::iterator it = m_tempResults.find(id); + + if (it != m_tempResults.end()) { + return it->second; + } + + return NULL; +} + +void ScriptEnvironment::addGlobalStorageValue(const uint32_t key, const int32_t value) +{ + m_globalStorageMap[key] = value; +} + +bool ScriptEnvironment::getGlobalStorageValue(const uint32_t key, int32_t& value) const +{ + StorageMap::const_iterator it; + it = m_globalStorageMap.find(key); + + if (it != m_globalStorageMap.end()) { + value = it->second; + return true; + } + + value = 0; + return false; +} + +std::string LuaScriptInterface::getErrorDesc(ErrorCode_t code) +{ + switch (code) { + case LUA_ERROR_PLAYER_NOT_FOUND: + return "Player not found"; + + case LUA_ERROR_CREATURE_NOT_FOUND: + return "Creature not found"; + + case LUA_ERROR_ITEM_NOT_FOUND: + return "Item not found"; + + case LUA_ERROR_THING_NOT_FOUND: + return "Thing not found"; + + case LUA_ERROR_TILE_NOT_FOUND: + return "Tile not found"; + + case LUA_ERROR_HOUSE_NOT_FOUND: + return "House not found"; + + case LUA_ERROR_COMBAT_NOT_FOUND: + return "Combat not found"; + + case LUA_ERROR_CONDITION_NOT_FOUND: + return "Condition not found"; + + case LUA_ERROR_AREA_NOT_FOUND: + return "Area not found"; + + case LUA_ERROR_CONTAINER_NOT_FOUND: + return "Container not found"; + + case LUA_ERROR_VARIANT_NOT_FOUND: + return "Variant not found"; + + case LUA_ERROR_VARIANT_UNKNOWN: + return "Unknown variant type"; + + case LUA_ERROR_SPELL_NOT_FOUND: + return "Spell not found"; + + default: + return "Wrong error code!"; + } +} + +ScriptEnvironment LuaScriptInterface::m_scriptEnv[16]; +int32_t LuaScriptInterface::m_scriptEnvIndex = -1; + +LuaScriptInterface::LuaScriptInterface(const std::string& interfaceName) +{ + m_luaState = NULL; + m_interfaceName = interfaceName; + m_lastEventTimerId = 1000; +} + +LuaScriptInterface::~LuaScriptInterface() +{ + for (LuaTimerEvents::iterator it = m_timerEvents.begin(); it != m_timerEvents.end(); ++it) { + g_scheduler.stopEvent(it->second.eventId); + } + + closeState(); +} + +bool LuaScriptInterface::reInitState() +{ + closeState(); + return initState(); +} + +/// Same as lua_pcall, but adds stack trace to error strings in called function. +int32_t LuaScriptInterface::protectedCall(lua_State* L, int32_t nargs, int32_t nresults) +{ + int error_index = lua_gettop(L) - nargs; + lua_pushcfunction(L, luaErrorHandler); + lua_insert(L, error_index); + + int ret = lua_pcall(L, nargs, nresults, error_index); + lua_remove(L, error_index); + return ret; +} + +int32_t LuaScriptInterface::loadFile(const std::string& file, Npc* npc /* = NULL*/) +{ + //loads file as a chunk at stack top + int32_t ret = luaL_loadfile(m_luaState, file.c_str()); + + if (ret != 0) { + m_lastLuaError = popString(m_luaState); + return -1; + } + + //check that it is loaded as a function + if (lua_isfunction(m_luaState, -1) == 0) { + return -1; + } + + m_loadingFile = file; + this->reserveScriptEnv(); + ScriptEnvironment* env = this->getScriptEnv(); + env->setScriptId(EVENT_ID_LOADING, this); + env->setNpc(npc); + + //execute it + ret = protectedCall(m_luaState, 0, 0); + + if (ret != 0) { + reportError(NULL, popString(m_luaState)); + this->releaseScriptEnv(); + return -1; + } + + this->releaseScriptEnv(); + return 0; +} + +int32_t LuaScriptInterface::loadBuffer(const std::string& text, Npc* npc /* = NULL*/) +{ + //loads file as a chunk at stack top + const char* buffer = text.c_str(); + int ret = luaL_loadbuffer(m_luaState, buffer, strlen(buffer), "loadBuffer"); + + if (ret != 0) { + m_lastLuaError = popString(m_luaState); + reportError(NULL, m_lastLuaError); + return -1; + } + + //check that it is loaded as a function + if (lua_isfunction(m_luaState, -1) == 0) { + return -1; + } + + m_loadingFile = "loadBuffer"; + this->reserveScriptEnv(); + ScriptEnvironment* env = this->getScriptEnv(); + env->setScriptId(EVENT_ID_LOADING, this); + env->setNpc(npc); + + //execute it + ret = protectedCall(m_luaState, 0, 0); + + if (ret != 0) { + reportError(NULL, popString(m_luaState)); + this->releaseScriptEnv(); + return -1; + } + + this->releaseScriptEnv(); + return 0; +} + +int32_t LuaScriptInterface::getEvent(const std::string& eventName) +{ + //get our events table + lua_getfield(m_luaState, LUA_REGISTRYINDEX, "EVENTS"); + + if (lua_istable(m_luaState, -1) == 0) { + lua_pop(m_luaState, 1); + return -1; + } + + //get current event function pointer + lua_getglobal(m_luaState, eventName.c_str()); + + if (lua_isfunction(m_luaState, -1) == 0) { + lua_pop(m_luaState, 2); + return -1; + } + + //save in our events table + lua_pushnumber(m_luaState, m_runningEventId); + lua_pushvalue(m_luaState, -2); + lua_rawset(m_luaState, -4); + lua_pop(m_luaState, 2); + + //reset global value of this event + lua_pushnil(m_luaState); + lua_setglobal(m_luaState, eventName.c_str()); + + m_cacheFiles[m_runningEventId] = m_loadingFile + ":" + eventName; + ++m_runningEventId; + return m_runningEventId - 1; +} + +const std::string& LuaScriptInterface::getFileById(int32_t scriptId) +{ + const static std::string unk = "(Unknown scriptfile)"; + + if (scriptId != EVENT_ID_LOADING) { + ScriptsCache::iterator it = m_cacheFiles.find(scriptId); + + if (it != m_cacheFiles.end()) { + return it->second; + } else { + return unk; + } + } else { + return m_loadingFile; + } +} + +std::string LuaScriptInterface::getStackTrace(const std::string& error_desc) +{ + lua_State* L = m_luaState; + lua_getfield(L, LUA_GLOBALSINDEX, "debug"); + + if (!lua_istable(L, -1)) { + lua_pop(L, 1); + return error_desc; + } + + lua_getfield(L, -1, "traceback"); + + if (!lua_isfunction(L, -1)) { + lua_pop(L, 1); + return error_desc; + } + + lua_pushstring(L, error_desc.c_str()); + lua_call(L, 1, 1); + std::string trace(lua_tostring(L, -1)); + lua_pop(L, 1); + return trace; +} + +void LuaScriptInterface::reportError(const char* function, const std::string& error_desc, bool stack_trace/* = false*/) +{ + ScriptEnvironment* env = getScriptEnv(); + int32_t scriptId; + int32_t callbackId; + bool timerEvent; + std::string event_desc; + LuaScriptInterface* scriptInterface; + env->getEventInfo(scriptId, event_desc, scriptInterface, callbackId, timerEvent); + + std::cout << std::endl << "Lua Script Error: "; + + if (scriptInterface) { + std::cout << "[" << scriptInterface->getInterfaceName() << "] " << std::endl; + + if (timerEvent) { + std::cout << "in a timer event called from: " << std::endl; + } + + if (callbackId) { + std::cout << "in callback: " << scriptInterface->getFileById(callbackId) << std::endl; + } + + std::cout << scriptInterface->getFileById(scriptId) << std::endl; + } + + if (!event_desc.empty()) { + std::cout << "Event: " << event_desc << std::endl; + } + + if (function) { + std::cout << function << "(). "; + } + + if (stack_trace) { + std::cout << scriptInterface->getStackTrace(error_desc) << std::endl; + } else { + std::cout << error_desc << std::endl; + } +} + +bool LuaScriptInterface::pushFunction(int32_t functionId) +{ + lua_getfield(m_luaState, LUA_REGISTRYINDEX, "EVENTS"); + + if (lua_istable(m_luaState, -1) != 0) { + lua_pushnumber(m_luaState, functionId); + lua_rawget(m_luaState, -2); + lua_remove(m_luaState, -2); + + if (lua_isfunction(m_luaState, -1) != 0) { + return true; + } + } + + return false; +} + +bool LuaScriptInterface::initState() +{ + m_luaState = luaL_newstate(); + + if (!m_luaState) { + return false; + } + + luaL_openlibs(m_luaState); + + registerFunctions(); + + if (loadFile("data/global.lua") == -1) { + std::cout << "Warning: [LuaScriptInterface::initState] Can not load data/global.lua." << std::endl; + } + + lua_newtable(m_luaState); + lua_setfield(m_luaState, LUA_REGISTRYINDEX, "EVENTS"); + + m_runningEventId = EVENT_ID_USER; + return true; +} + +bool LuaScriptInterface::closeState() +{ + if (m_luaState) { + m_cacheFiles.clear(); + + LuaTimerEvents::iterator it, end; + + for (it = m_timerEvents.begin(), end = m_timerEvents.end(); it != end; ++it) { + LuaTimerEventDesc& timerEventDesc = it->second; + + for (std::list::iterator lt = timerEventDesc.parameters.begin(), lend = timerEventDesc.parameters.end(); lt != lend; ++lt) { + luaL_unref(m_luaState, LUA_REGISTRYINDEX, *lt); + } + + timerEventDesc.parameters.clear(); + + luaL_unref(m_luaState, LUA_REGISTRYINDEX, timerEventDesc.function); + } + + m_timerEvents.clear(); + + lua_close(m_luaState); + } + + return true; +} + +void LuaScriptInterface::executeTimerEvent(uint32_t eventIndex) +{ + LuaTimerEvents::iterator it = m_timerEvents.find(eventIndex); + + if (it != m_timerEvents.end()) { + LuaTimerEventDesc& timerEventDesc = it->second; + + //push function + lua_rawgeti(m_luaState, LUA_REGISTRYINDEX, timerEventDesc.function); + + //push parameters + for (std::list::reverse_iterator rt = timerEventDesc.parameters.rbegin(), rend = timerEventDesc.parameters.rend(); rt != rend; ++rt) { + lua_rawgeti(m_luaState, LUA_REGISTRYINDEX, *rt); + } + + //call the function + if (reserveScriptEnv()) { + ScriptEnvironment* env = getScriptEnv(); + env->setTimerEvent(); + env->setScriptId(timerEventDesc.scriptId, this); + callFunction(timerEventDesc.parameters.size()); + releaseScriptEnv(); + } else { + std::cout << "[Error] Call stack overflow. LuaScriptInterface::executeTimerEvent" << std::endl; + } + + //free resources + for (std::list::iterator lt = timerEventDesc.parameters.begin(), end = timerEventDesc.parameters.end(); lt != end; ++lt) { + luaL_unref(m_luaState, LUA_REGISTRYINDEX, *lt); + } + + timerEventDesc.parameters.clear(); + + luaL_unref(m_luaState, LUA_REGISTRYINDEX, timerEventDesc.function); + m_timerEvents.erase(it); + } +} + +int32_t LuaScriptInterface::luaErrorHandler(lua_State* L) +{ + std::string err_msg(lua_tostring(L, -1)); + lua_pop(L, 1); + lua_pushstring(L, getScriptEnv()->getScriptInterface()->getStackTrace(err_msg).c_str()); + return 1; +} + +bool LuaScriptInterface::callFunction(uint32_t nParams) +{ + bool result = false; + int32_t size0 = lua_gettop(m_luaState); + int32_t ret = protectedCall(m_luaState, nParams, 1); + + if (ret != 0) { + LuaScriptInterface::reportError(NULL, LuaScriptInterface::popString(m_luaState)); + } else { + result = LuaScriptInterface::popBoolean(m_luaState); + } + + if ((lua_gettop(m_luaState) + (int)nParams + 1) != size0) { + LuaScriptInterface::reportError(NULL, "Stack size changed!"); + } + + return result; +} + +void LuaScriptInterface::pushVariant(lua_State* L, const LuaVariant& var) +{ + lua_newtable(L); + setField(L, "type", var.type); + + switch (var.type) { + case VARIANT_NUMBER: + setField(L, "number", var.number); + break; + case VARIANT_STRING: + setField(L, "string", var.text); + break; + case VARIANT_TARGETPOSITION: + case VARIANT_POSITION: { + lua_pushstring(L, "pos"); + pushPosition(L, var.pos); + lua_settable(L, -3); + break; + } + case VARIANT_NONE: + break; + } +} + +void LuaScriptInterface::pushThing(lua_State* L, Thing* thing, uint32_t thingid) +{ + lua_newtable(L); + + if (thing && thing->getItem()) { + const Item* item = thing->getItem(); + setField(L, "uid", thingid); + setField(L, "itemid", item->getID()); + + if (item->hasSubType()) { + setField(L, "type", item->getSubType()); + } else { + setField(L, "type", 0); + } + + setField(L, "actionid", item->getActionId()); + } else if (thing && thing->getCreature()) { + const Creature* creature = thing->getCreature(); + setField(L, "uid", thingid); + setField(L, "itemid", 1); + char type; + + if (creature->getPlayer()) { + type = 1; + } else if (creature->getMonster()) { + type = 2; + } else { + type = 3; //npc + } + + setField(L, "type", type); + setField(L, "actionid", 0); + } else { + setField(L, "uid", 0); + setField(L, "itemid", 0); + setField(L, "type", 0); + setField(L, "actionid", 0); + } +} + +void LuaScriptInterface::pushPosition(lua_State* L, const PositionEx& position) +{ + lua_newtable(L); + setField(L, "z", position.z); + setField(L, "y", position.y); + setField(L, "x", position.x); + setField(L, "stackpos", position.stackpos); +} + +void LuaScriptInterface::pushPosition(lua_State* L, const Position& position, uint32_t stackpos) +{ + lua_newtable(L); + setField(L, "z", position.z); + setField(L, "y", position.y); + setField(L, "x", position.x); + setField(L, "stackpos", stackpos); +} + +void LuaScriptInterface::pushCallback(lua_State* L, int32_t callback) +{ + lua_rawgeti(L, LUA_REGISTRYINDEX, callback); +} + +LuaVariant LuaScriptInterface::popVariant(lua_State* L) +{ + uint32_t type = getField(L, "type"); + + LuaVariant var; + var.type = (LuaVariantType_t)type; + + switch (type) { + case VARIANT_NUMBER: { + var.number = getFieldU32(L, "number"); + break; + } + + case VARIANT_STRING: { + var.text = getField(L, "string"); + break; + } + + case VARIANT_POSITION: + case VARIANT_TARGETPOSITION: { + lua_pushstring(L, "pos"); + lua_gettable(L, -2); + popPosition(L, var.pos); + break; + } + + default: { + var.type = VARIANT_NONE; + break; + } + } + + lua_pop(L, 1); //table + + return var; +} + +void LuaScriptInterface::popPosition(lua_State* L, PositionEx& position) +{ + position.z = getField(L, "z"); + position.y = getField(L, "y"); + position.x = getField(L, "x"); + position.stackpos = getField(L, "stackpos"); + + lua_pop(L, 1); //table +} + +void LuaScriptInterface::popPosition(lua_State* L, Position& position, uint32_t& stackpos) +{ + position.z = getField(L, "z"); + position.y = getField(L, "y"); + position.x = getField(L, "x"); + stackpos = getField(L, "stackpos"); + lua_pop(L, 1); //table +} + +template +T LuaScriptInterface::popNumber(lua_State* L) +{ + if (lua_gettop(L) == 0) { + return T(); + } + + T ret = lua_tonumber(L, -1); + lua_pop(L, 1); + return ret; +} + +uint32_t LuaScriptInterface::popNumber(lua_State* L) +{ + if (lua_gettop(L) == 0) { + return 0; + } + + uint32_t number = lua_tonumber(L, -1); + lua_pop(L, 1); + return number; +} + +double LuaScriptInterface::popFloatNumber(lua_State* L) +{ + if (lua_gettop(L) == 0) { + return 0.0; + } + + double number = lua_tonumber(L, -1); + lua_pop(L, 1); + return number; +} + +std::string LuaScriptInterface::popString(lua_State* L) +{ + if (lua_gettop(L) == 0) { + return ""; + } + + std::string str; + size_t len; + const char* c_str = lua_tolstring(L, -1, &len); + + if (c_str && len > 0) { + str.assign(c_str, len); + } + + lua_pop(L, 1); + return str; +} + +int32_t LuaScriptInterface::popCallback(lua_State* L) +{ + return luaL_ref(L, LUA_REGISTRYINDEX); +} + +bool LuaScriptInterface::popBoolean(lua_State* L) +{ + if (lua_gettop(L) == 0) { + return false; + } + + bool value = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + return value; +} + +void LuaScriptInterface::setField(lua_State* L, const char* index, double val) +{ + lua_pushstring(L, index); + lua_pushnumber(L, val); + lua_settable(L, -3); +} + +void LuaScriptInterface::setField(lua_State* L, const char* index, const std::string& val) +{ + lua_pushstring(L, index); + lua_pushstring(L, val.c_str()); + lua_settable(L, -3); +} + +void LuaScriptInterface::setFieldBool(lua_State* L, const char* index, bool val) +{ + lua_pushstring(L, index); + lua_pushboolean(L, val); + lua_settable(L, -3); +} + +int32_t LuaScriptInterface::getField(lua_State* L, const char* key) +{ + int32_t result; + lua_pushstring(L, key); + lua_gettable(L, -2); // get table[key] + result = (int32_t)lua_tonumber(L, -1); + lua_pop(L, 1); // remove number and key + return result; +} + +uint32_t LuaScriptInterface::getFieldU32(lua_State* L, const char* key) +{ + uint32_t result; + lua_pushstring(L, key); + lua_gettable(L, -2); // get table[key] + result = (uint32_t)lua_tonumber(L, -1); + lua_pop(L, 1); // remove number and key + return result; +} + +bool LuaScriptInterface::getFieldBool(lua_State* L, const char* key) +{ + bool result; + lua_pushstring(L, key); + lua_gettable(L, -2); // get table[key] + result = (lua_toboolean(L, -1) == 1); + lua_pop(L, 1); // remove number and key + return result; +} + +std::string LuaScriptInterface::getFieldString(lua_State* L, const char* key) +{ + std::string result = ""; + lua_pushstring(L, key); + lua_gettable(L, -2); // get table[key] + + if (lua_isstring(L, -1)) { + result = lua_tostring(L, -1); + } + + lua_pop(L, 1); // remove number and key + return result; +} + +void LuaScriptInterface::registerFunctions() +{ + //lua_register(L, "name", C_function); + + //getPlayerFood(cid) + lua_register(m_luaState, "getPlayerFood", LuaScriptInterface::luaGetPlayerFood); + + //getCreatureHealth(cid) + lua_register(m_luaState, "getCreatureHealth", LuaScriptInterface::luaGetCreatureHealth); + + //getCreatureMaxHealth(cid) + lua_register(m_luaState, "getCreatureMaxHealth", LuaScriptInterface::luaGetCreatureMaxHealth); + + //getPlayerMana(cid) + lua_register(m_luaState, "getPlayerMana", LuaScriptInterface::luaGetPlayerMana); + + //getPlayerMaxMana(cid) + lua_register(m_luaState, "getPlayerMaxMana", LuaScriptInterface::luaGetPlayerMaxMana); + + //getPlayerLevel(cid) + lua_register(m_luaState, "getPlayerLevel", LuaScriptInterface::luaGetPlayerLevel); + + //getPlayerMagLevel(cid) + lua_register(m_luaState, "getPlayerMagLevel", LuaScriptInterface::luaGetPlayerMagLevel); + + //getPlayerName(cid) + lua_register(m_luaState, "getPlayerName", LuaScriptInterface::luaGetPlayerName); + + //getPlayerAccess(cid) + lua_register(m_luaState, "getPlayerAccess", LuaScriptInterface::luaGetPlayerAccess); + + //getPlayerPosition(cid) + lua_register(m_luaState, "getPlayerPosition", LuaScriptInterface::luaGetPlayerPosition); + + //getPlayerSkill(cid, skillid) + lua_register(m_luaState, "getPlayerSkill", LuaScriptInterface::luaGetPlayerSkill); + + //getPlayerMasterPos(cid) + lua_register(m_luaState, "getPlayerMasterPos", LuaScriptInterface::luaGetPlayerMasterPos); + + //getPlayerTown(cid) + lua_register(m_luaState, "getPlayerTown", LuaScriptInterface::luaGetPlayerTown); + + //getPlayerVocation(cid) + lua_register(m_luaState, "getPlayerVocation", LuaScriptInterface::luaGetPlayerVocation); + + //getPlayerItemCount(cid, itemid[, subtype]) + lua_register(m_luaState, "getPlayerItemCount", LuaScriptInterface::luaGetPlayerItemCount); + + //getPlayerSoul(cid) + lua_register(m_luaState, "getPlayerSoul", LuaScriptInterface::luaGetPlayerSoul); + + //getPlayerFreeCap(cid) + lua_register(m_luaState, "getPlayerFreeCap", LuaScriptInterface::luaGetPlayerFreeCap); + + //getPlayerLight(cid) + lua_register(m_luaState, "getPlayerLight", LuaScriptInterface::luaGetPlayerLight); + + //getPlayerSlotItem(cid, slot) + lua_register(m_luaState, "getPlayerSlotItem", LuaScriptInterface::luaGetPlayerSlotItem); + + //getPlayerItemById(cid, deepSearch, itemId, subType) + lua_register(m_luaState, "getPlayerItemById", LuaScriptInterface::luaGetPlayerItemById); + + //getPlayerDepotItems(cid, depotid) + lua_register(m_luaState, "getPlayerDepotItems", LuaScriptInterface::luaGetPlayerDepotItems); + + //getPlayerGuildId(cid) + lua_register(m_luaState, "getPlayerGuildId", LuaScriptInterface::luaGetPlayerGuildId); + + //getPlayerGuildLevel(cid) + lua_register(m_luaState, "getPlayerGuildLevel", LuaScriptInterface::luaGetPlayerGuildLevel); + + //getPlayerGuildName(cid) + lua_register(m_luaState, "getPlayerGuildName", LuaScriptInterface::luaGetPlayerGuildName); + + //getPlayerGuildRank(cid) + lua_register(m_luaState, "getPlayerGuildRank", LuaScriptInterface::luaGetPlayerGuildRank); + + //getPlayerGuildNick(cid) + lua_register(m_luaState, "getPlayerGuildNick", LuaScriptInterface::luaGetPlayerGuildNick); + + //getPlayerSex(cid) + lua_register(m_luaState, "getPlayerSex", LuaScriptInterface::luaGetPlayerSex); + + //getPlayerLookDir(cid) + lua_register(m_luaState, "getPlayerLookDir", LuaScriptInterface::luaGetPlayerLookDir); + + //doCreatureSetLookDir(cid, direction) + lua_register(m_luaState, "doCreatureSetLookDir", LuaScriptInterface::luaDoCreatureSetLookDir); + //doSetCreatureDirection(cid, direction) + lua_register(m_luaState, "doSetCreatureDirection", LuaScriptInterface::luaDoCreatureSetLookDir); + + //getPlayerGUID(cid) + lua_register(m_luaState, "getPlayerGUID", LuaScriptInterface::luaGetPlayerGUID); + + //getPlayerFlagValue(cid, flag) + lua_register(m_luaState, "getPlayerFlagValue", LuaScriptInterface::luaGetPlayerFlagValue); + + //getPlayerLossPercent(cid) + lua_register(m_luaState, "getPlayerLossPercent", LuaScriptInterface::luaGetPlayerLossPercent); + + //getPlayerSkullType(cid) + lua_register(m_luaState, "getPlayerSkullType", LuaScriptInterface::luaGetPlayerSkullType); + + //getPlayerGroupId(cid) + lua_register(m_luaState, "getPlayerGroupId", LuaScriptInterface::luaGetPlayerGroupId); + + //setPlayerGroupId(cid, newGroupId) + lua_register(m_luaState, "setPlayerGroupId", LuaScriptInterface::luaSetPlayerGroupId); + + //getPlayerIp(cid) + lua_register(m_luaState, "getPlayerIp", LuaScriptInterface::luaGetPlayerIp); + + //playerLearnInstantSpell(cid, name) + lua_register(m_luaState, "playerLearnInstantSpell", LuaScriptInterface::luaPlayerLearnInstantSpell); + + //canPlayerLearnInstantSpell(cid, name) + lua_register(m_luaState, "canPlayerLearnInstantSpell", LuaScriptInterface::luaCanPlayerLearnInstantSpell); + + //getPlayerLearnedInstantSpell(cid, name) + lua_register(m_luaState, "getPlayerLearnedInstantSpell", LuaScriptInterface::luaGetPlayerLearnedInstantSpell); + + //getPlayerInstantSpellCount(cid) + lua_register(m_luaState, "getPlayerInstantSpellCount", LuaScriptInterface::luaGetPlayerInstantSpellCount); + + //getPlayerInstantSpellInfo(cid, index) + lua_register(m_luaState, "getPlayerInstantSpellInfo", LuaScriptInterface::luaGetPlayerInstantSpellInfo); + + //getInstantSpellInfoByName(cid, name) + lua_register(m_luaState, "getInstantSpellInfoByName", LuaScriptInterface::luaGetInstantSpellInfoByName); + + //getInstantSpellWords(name) + lua_register(m_luaState, "getInstantSpellWords", LuaScriptInterface::luaGetInstantSpellWords); + + //getPlayerStorageValue(uid, valueid) + lua_register(m_luaState, "getPlayerStorageValue", LuaScriptInterface::luaGetPlayerStorageValue); + + //setPlayerStorageValue(uid, valueid, newvalue) + lua_register(m_luaState, "setPlayerStorageValue", LuaScriptInterface::luaSetPlayerStorageValue); + + //getGlobalStorageValue(valueid) + lua_register(m_luaState, "getGlobalStorageValue", LuaScriptInterface::luaGetGlobalStorageValue); + + //setGlobalStorageValue(valueid, newvalue) + lua_register(m_luaState, "setGlobalStorageValue", LuaScriptInterface::luaSetGlobalStorageValue); + + //getOnlinePlayers() + lua_register(m_luaState, "getOnlinePlayers", LuaScriptInterface::luaGetOnlinePlayers); + + //getTilePzInfo(pos) + //1 is pz. 0 no pz. + lua_register(m_luaState, "getTilePzInfo", LuaScriptInterface::luaGetTilePzInfo); + + //getTileInfo(pos) + lua_register(m_luaState, "getTileInfo", LuaScriptInterface::luaGetTileInfo); + + //getTileHouseInfo(pos) + //0 no house. != 0 house id + lua_register(m_luaState, "getTileHouseInfo", LuaScriptInterface::luaGetTileHouseInfo); + + //getItemRWInfo(uid) + lua_register(m_luaState, "getItemRWInfo", LuaScriptInterface::luaGetItemRWInfo); + + //getThingfromPos(pos) + lua_register(m_luaState, "getThingfromPos", LuaScriptInterface::luaGetThingfromPos); + + //getThing(uid) + lua_register(m_luaState, "getThing", LuaScriptInterface::luaGetThing); + + //queryTileAddThing(uid, pos, flags) + lua_register(m_luaState, "queryTileAddThing", LuaScriptInterface::luaQueryTileAddThing); + + //getThingPos(uid) + lua_register(m_luaState, "getThingPos", LuaScriptInterface::luaGetThingPos); + + //getTileItemById(pos, itemId, subType) + lua_register(m_luaState, "getTileItemById", LuaScriptInterface::luaGetTileItemById); + + //getTileItemByType(pos, type) + lua_register(m_luaState, "getTileItemByType", LuaScriptInterface::luaGetTileItemByType); + + //getTileThingByPos(pos) + lua_register(m_luaState, "getTileThingByPos", LuaScriptInterface::luaGetTileThingByPos); + + //getTileThingByTopOrder(pos, topOrder) + lua_register(m_luaState, "getTileThingByTopOrder", LuaScriptInterface::luaGetTileThingByTopOrder); + + //getTopCreature(pos) + lua_register(m_luaState, "getTopCreature", LuaScriptInterface::luaGetTopCreature); + + //doRemoveItem(uid, n) + lua_register(m_luaState, "doRemoveItem", LuaScriptInterface::luaDoRemoveItem); + + //doPlayerFeed(cid, food) + lua_register(m_luaState, "doPlayerFeed", LuaScriptInterface::luaDoFeedPlayer); + + //doPlayerSendCancel(cid, text) + lua_register(m_luaState, "doPlayerSendCancel", LuaScriptInterface::luaDoPlayerSendCancel); + + //doPlayerSendDefaultCancel(cid, ReturnValue) + lua_register(m_luaState, "doPlayerSendDefaultCancel", LuaScriptInterface::luaDoSendDefaultCancel); + + //doTeleportThing(cid, newpos, pushmove) + lua_register(m_luaState, "doTeleportThing", LuaScriptInterface::luaDoTeleportThing); + + //doTransformItem(uid, toitemid, count/subtype) + lua_register(m_luaState, "doTransformItem", LuaScriptInterface::luaDoTransformItem); + + //doPlayerChangeName(cid, newName) + lua_register(m_luaState, "doPlayerChangeName", LuaScriptInterface::luaDoPlayerChangeName); + + //doCreatureSay(uid, text, type[, ghost = false[, cid = 0[, pos]]]) + lua_register(m_luaState, "doCreatureSay", LuaScriptInterface::luaDoCreatureSay); + + //doSendMagicEffect(pos, type[, player]) + lua_register(m_luaState, "doSendMagicEffect", LuaScriptInterface::luaDoSendMagicEffect); + + //doSendDistanceShoot(frompos, topos, type) + lua_register(m_luaState, "doSendDistanceShoot", LuaScriptInterface::luaDoSendDistanceShoot); + + //doChangeTypeItem(uid, newtype) + lua_register(m_luaState, "doChangeTypeItem", LuaScriptInterface::luaDoChangeTypeItem); + + //doSetItemActionId(uid, actionid) + lua_register(m_luaState, "doSetItemActionId", LuaScriptInterface::luaDoSetItemActionId); + + //doSetItemText(uid, text) + lua_register(m_luaState, "doSetItemText", LuaScriptInterface::luaDoSetItemText); + + //doSetItemSpecialDescription(uid, desc) + lua_register(m_luaState, "doSetItemSpecialDescription", LuaScriptInterface::luaDoSetItemSpecialDescription); + + //doSendAnimatedText(pos, text, color) + lua_register(m_luaState, "doSendAnimatedText", LuaScriptInterface::luaDoSendAnimatedText); + + //doPlayerAddSkillTry(cid, skillid, n) + lua_register(m_luaState, "doPlayerAddSkillTry", LuaScriptInterface::luaDoPlayerAddSkillTry); + + //doCreatureAddHealth(cid, health) + lua_register(m_luaState, "doCreatureAddHealth", LuaScriptInterface::luaDoCreatureAddHealth); + + //doPlayerAddMana(cid, mana[, animationOnLoss]) + lua_register(m_luaState, "doPlayerAddMana", LuaScriptInterface::luaDoPlayerAddMana); + + //doPlayerAddManaSpent(cid, mana) + lua_register(m_luaState, "doPlayerAddManaSpent", LuaScriptInterface::luaDoPlayerAddManaSpent); + + //doPlayerAddSoul(cid, soul) + lua_register(m_luaState, "doPlayerAddSoul", LuaScriptInterface::luaDoPlayerAddSoul); + + //doPlayerAddItem(uid, itemid, count/subtype) + //doPlayerAddItem(cid, itemid, count, canDropOnMap, subtype) + //Returns uid of the created item + lua_register(m_luaState, "doPlayerAddItem", LuaScriptInterface::luaDoPlayerAddItem); + + //doPlayerAddItemEx(cid, uid, canDropOnMap) + lua_register(m_luaState, "doPlayerAddItemEx", LuaScriptInterface::luaDoPlayerAddItemEx); + + //doPlayerSendTextMessage(cid, MessageClasses, message[, position, value, color]) + lua_register(m_luaState, "doPlayerSendTextMessage", LuaScriptInterface::luaDoPlayerSendTextMessage); + + //doPlayerRemoveMoney(cid, money) + lua_register(m_luaState, "doPlayerRemoveMoney", LuaScriptInterface::luaDoPlayerRemoveMoney); + + //doPlayerAddMoney(cid, money) + lua_register(m_luaState, "doPlayerAddMoney", LuaScriptInterface::luaDoPlayerAddMoney); + + //doShowTextDialog(cid, itemid, text) + lua_register(m_luaState, "doShowTextDialog", LuaScriptInterface::luaDoShowTextDialog); + + //doSendTutorial(cid, tutorialid) + lua_register(m_luaState, "doSendTutorial", LuaScriptInterface::luaDoSendTutorial); + + //doAddMapMark(cid, pos, type, description) + lua_register(m_luaState, "doAddMapMark", LuaScriptInterface::luaDoAddMark); + + //doDecayItem(uid) + lua_register(m_luaState, "doDecayItem", LuaScriptInterface::luaDoDecayItem); + + //doCreateItem(itemid, type/count, pos) + //Returns uid of the created item, only works on tiles. + lua_register(m_luaState, "doCreateItem", LuaScriptInterface::luaDoCreateItem); + + //doCreateItemEx(itemid, count/subtype) + lua_register(m_luaState, "doCreateItemEx", LuaScriptInterface::luaDoCreateItemEx); + + //doTileAddItemEx(pos, uid) + lua_register(m_luaState, "doTileAddItemEx", LuaScriptInterface::luaDoTileAddItemEx); + + //doAddContainerItemEx(uid, virtuid) + lua_register(m_luaState, "doAddContainerItemEx", LuaScriptInterface::luaDoAddContainerItemEx); + + //doRelocate(pos, posTo) + //Moves all moveable objects from pos to posTo + lua_register(m_luaState, "doRelocate", LuaScriptInterface::luaDoRelocate); + + //doCreateTeleport(itemid, topos, createpos) + lua_register(m_luaState, "doCreateTeleport", LuaScriptInterface::luaDoCreateTeleport); + + //doCreateNpc(name, pos) + lua_register(m_luaState, "doCreateNpc", LuaScriptInterface::luaDoCreateNpc); + + //doSummonCreature(name, pos) + lua_register(m_luaState, "doSummonCreature", LuaScriptInterface::luaDoSummonCreature); + + //doConvinceCreature(cid, target) + lua_register(m_luaState, "doConvinceCreature", LuaScriptInterface::luaDoConvinceCreature); + + //getMonsterTargetList(cid) + lua_register(m_luaState, "getMonsterTargetList", LuaScriptInterface::luaGetMonsterTargetList); + + //getMonsterFriendList(cid) + lua_register(m_luaState, "getMonsterFriendList", LuaScriptInterface::luaGetMonsterFriendList); + + //doSetMonsterTarget(cid, target) + lua_register(m_luaState, "doSetMonsterTarget", LuaScriptInterface::luaDoSetMonsterTarget); + + //doMonsterChangeTarget(cid) + lua_register(m_luaState, "doMonsterChangeTarget", LuaScriptInterface::luaDoMonsterChangeTarget); + + //doAddCondition(cid, condition) + lua_register(m_luaState, "doAddCondition", LuaScriptInterface::luaDoAddCondition); + + //doRemoveCondition(cid, type) + lua_register(m_luaState, "doRemoveCondition", LuaScriptInterface::luaDoRemoveCondition); + + //doRemoveCreature(cid) + lua_register(m_luaState, "doRemoveCreature", LuaScriptInterface::luaDoRemoveCreature); + + //doMoveCreature(cid, direction) + lua_register(m_luaState, "doMoveCreature", LuaScriptInterface::luaDoMoveCreature); + + //doPlayerSetTown(cid, townid) + lua_register(m_luaState, "doPlayerSetTown", LuaScriptInterface::luaDoPlayerSetTown); + + //doPlayerSetVocation(cid,voc) + lua_register(m_luaState, "doPlayerSetVocation", LuaScriptInterface::luaDoPlayerSetVocation); + + //doPlayerRemoveItem(cid, itemid, count, subtype, ignoreEquipped) + lua_register(m_luaState, "doPlayerRemoveItem", LuaScriptInterface::luaDoPlayerRemoveItem); + + //doPlayerAddExp(cid, exp, usemultiplier, sendtext) + lua_register(m_luaState, "doPlayerAddExp", LuaScriptInterface::luaDoPlayerAddExp); + + //doPlayerSetGuildLevel(cid, level) + lua_register(m_luaState, "doPlayerSetGuildLevel", LuaScriptInterface::luaDoPlayerSetGuildLevel); + + //doPlayerSetGuildNick(cid, nick) + lua_register(m_luaState, "doPlayerSetGuildNick", LuaScriptInterface::luaDoPlayerSetGuildNick); + + //doPlayerAddOutfit(cid,looktype,addons) + lua_register(m_luaState, "doPlayerAddOutfit", LuaScriptInterface::luaDoPlayerAddOutfit); + + //doPlayerRemOutfit(cid,looktype,addons) + lua_register(m_luaState, "doPlayerRemOutfit", LuaScriptInterface::luaDoPlayerRemOutfit); + + //canPlayerWearOutfit(cid, looktype, addons) + lua_register(m_luaState, "canPlayerWearOutfit", LuaScriptInterface::luaCanPlayerWearOutfit); + + //doPlayerAddMount(cid, mountid) + lua_register(m_luaState, "doPlayerAddMount", LuaScriptInterface::luaDoPlayerAddMount); + + //doPlayerRemoveMount(cid, mountid) + lua_register(m_luaState, "doPlayerRemoveMount", LuaScriptInterface::luaDoPlayerRemoveMount); + + //getPlayerMount(cid, mountid) + lua_register(m_luaState, "getPlayerMount", LuaScriptInterface::luaGetPlayerMount); + + //doSetCreatureLight(cid, lightLevel, lightColor, time) + lua_register(m_luaState, "doSetCreatureLight", LuaScriptInterface::luaDoSetCreatureLight); + + //doSetCreatureDropLoot(cid, doDrop) + lua_register(m_luaState, "doSetCreatureDropLoot", LuaScriptInterface::luaDoSetCreatureDropLoot); + + //getPlayerParty(cid) + lua_register(m_luaState, "getPlayerParty", LuaScriptInterface::luaGetPlayerParty); + + //doPlayerJoinParty(cid, leaderId) + lua_register(m_luaState, "doPlayerJoinParty", LuaScriptInterface::luaDoPlayerJoinParty); + + //getPartyMembers(leaderId) + lua_register(m_luaState, "getPartyMembers", LuaScriptInterface::luaGetPartyMembers); + + //getCreatureMaster(cid) + //returns the creature's master or itself if the creature isn't a summon + lua_register(m_luaState, "getCreatureMaster", LuaScriptInterface::luaGetCreatureMaster); + + //getCreatureSummons(cid) + //returns a table with all the summons of the creature + lua_register(m_luaState, "getCreatureSummons", LuaScriptInterface::luaGetCreatureSummons); + + //getSpectators(centerPos, rangex, rangey, multifloor, onlyPlayers) + lua_register(m_luaState, "getSpectators", LuaScriptInterface::luaGetSpectators); + + //getCreatureCondition(cid, condition) + lua_register(m_luaState, "getCreatureCondition", LuaScriptInterface::luaGetCreatureCondition); + + //isPlayer(cid) + lua_register(m_luaState, "isPlayer", LuaScriptInterface::luaIsPlayer); + + //isValidUID(uid) + lua_register(m_luaState, "isValidUID", LuaScriptInterface::luaIsValidUID); + + //isItem(uid) + lua_register(m_luaState, "isItem", LuaScriptInterface::luaIsItem); + + //isPlayerGhost(cid) + lua_register(m_luaState, "isPlayerGhost", LuaScriptInterface::luaIsPlayerGhost); + + //isPlayerPzLocked(cid) + lua_register(m_luaState, "isPlayerPzLocked", LuaScriptInterface::luaIsPlayerPzLocked); + + //isCreature(cid) + lua_register(m_luaState, "isCreature", LuaScriptInterface::luaIsCreature); + + //isMonster(cid) + lua_register(m_luaState, "isMonster", LuaScriptInterface::luaIsMonster); + + //isNpc(cid) + lua_register(m_luaState, "isNpc", LuaScriptInterface::luaIsNpc); + + //isContainer(uid) + lua_register(m_luaState, "isContainer", LuaScriptInterface::luaIsContainer); + + //isDepot(uid) + lua_register(m_luaState, "isDepot", LuaScriptInterface::luaIsDepot); + + //isCorpse(uid) + lua_register(m_luaState, "isCorpse", LuaScriptInterface::luaIsCorpse); + + //isMovable(uid) + lua_register(m_luaState, "isMovable", LuaScriptInterface::luaIsMoveable); + //isMoveable(uid) + lua_register(m_luaState, "isMoveable", LuaScriptInterface::luaIsMoveable); + + //getPlayerByName(name) + lua_register(m_luaState, "getPlayerByName", LuaScriptInterface::luaGetPlayerByName); + + //getPlayerGUIDByName(name) + lua_register(m_luaState, "getPlayerGUIDByName", LuaScriptInterface::luaGetPlayerGUIDByName); + + //registerCreatureEvent(uid, eventName) + lua_register(m_luaState, "registerCreatureEvent", LuaScriptInterface::luaRegisterCreatureEvent); + + //getContainerSize(uid) + lua_register(m_luaState, "getContainerSize", LuaScriptInterface::luaGetContainerSize); + + //getContainerCap(uid) + lua_register(m_luaState, "getContainerCap", LuaScriptInterface::luaGetContainerCap); + + //getContainerCapById(itemid) + lua_register(m_luaState, "getContainerCapById", LuaScriptInterface::luaGetContainerCapById); + + //getContainerItem(uid, slot) + lua_register(m_luaState, "getContainerItem", LuaScriptInterface::luaGetContainerItem); + + //doAddContainerItem(uid, itemid, count/subtype) + lua_register(m_luaState, "doAddContainerItem", LuaScriptInterface::luaDoAddContainerItem); + + //getDepotId(uid) + lua_register(m_luaState, "getDepotId", LuaScriptInterface::luaGetDepotId); + + //getHouseOwner(houseid) + lua_register(m_luaState, "getHouseOwner", LuaScriptInterface::luaGetHouseOwner); + + //getHouseName(houseid) + lua_register(m_luaState, "getHouseName", LuaScriptInterface::luaGetHouseName); + + //getHouseEntry(houseid) + lua_register(m_luaState, "getHouseEntry", LuaScriptInterface::luaGetHouseEntry); + + //getHouseRent(houseid) + lua_register(m_luaState, "getHouseRent", LuaScriptInterface::luaGetHouseRent); + + //getHouseTown(houseid) + lua_register(m_luaState, "getHouseTown", LuaScriptInterface::luaGetHouseTown); + + //getHouseAccessList(houseid, listid) + lua_register(m_luaState, "getHouseAccessList", LuaScriptInterface::luaGetHouseAccessList); + + //getHouseByPlayerGUID(playerGUID) + lua_register(m_luaState, "getHouseByPlayerGUID", LuaScriptInterface::luaGetHouseByPlayerGUID); + + //getHouseTilesSize(houseid) + lua_register(m_luaState, "getHouseTilesSize", LuaScriptInterface::luaGetHouseTilesSize); + + //setHouseAccessList(houseid, listid, listtext) + lua_register(m_luaState, "setHouseAccessList", LuaScriptInterface::luaSetHouseAccessList); + + //setHouseOwner(houseid, ownerGUID) + lua_register(m_luaState, "setHouseOwner", LuaScriptInterface::luaSetHouseOwner); + + //getWorldType() + lua_register(m_luaState, "getWorldType", LuaScriptInterface::luaGetWorldType); + + //getWorldTime() + lua_register(m_luaState, "getWorldTime", LuaScriptInterface::luaGetWorldTime); + + //getWorldLight() + lua_register(m_luaState, "getWorldLight", LuaScriptInterface::luaGetWorldLight); + + //getWorldCreatures(type) + //0 players, 1 monsters, 2 npcs, 3 all + lua_register(m_luaState, "getWorldCreatures", LuaScriptInterface::luaGetWorldCreatures); + + //getWorldUpTime() + lua_register(m_luaState, "getWorldUpTime", LuaScriptInterface::luaGetWorldUpTime); + + //broadcastMessage(message, type) + lua_register(m_luaState, "broadcastMessage", LuaScriptInterface::luaBroadcastMessage); + + //getGuildId(guild_name) + lua_register(m_luaState, "getGuildId", LuaScriptInterface::luaGetGuildId); + + //getPlayerSex(cid) + lua_register(m_luaState, "getPlayerSex", LuaScriptInterface::luaGetPlayerSex); + + //doPlayerSetSex(cid, newSex) + lua_register(m_luaState, "doPlayerSetSex", LuaScriptInterface::luaDoPlayerSetSex); + + //createCombatArea( {area}, {extArea} ) + lua_register(m_luaState, "createCombatArea", LuaScriptInterface::luaCreateCombatArea); + + //createConditionObject(type) + lua_register(m_luaState, "createConditionObject", LuaScriptInterface::luaCreateConditionObject); + + //setCombatArea(combat, area) + lua_register(m_luaState, "setCombatArea", LuaScriptInterface::luaSetCombatArea); + + //setCombatCondition(combat, condition) + lua_register(m_luaState, "setCombatCondition", LuaScriptInterface::luaSetCombatCondition); + + //setCombatParam(combat, key, value) + lua_register(m_luaState, "setCombatParam", LuaScriptInterface::luaSetCombatParam); + + //setConditionParam(condition, key, value) + lua_register(m_luaState, "setConditionParam", LuaScriptInterface::luaSetConditionParam); + + //addDamageCondition(condition, rounds, time, value) + lua_register(m_luaState, "addDamageCondition", LuaScriptInterface::luaAddDamageCondition); + + //addOutfitCondition(condition, lookTypeEx, lookType, lookHead, lookBody, lookLegs, lookFeet) + lua_register(m_luaState, "addOutfitCondition", LuaScriptInterface::luaAddOutfitCondition); + + //setCombatCallBack(combat, key, function_name) + lua_register(m_luaState, "setCombatCallback", LuaScriptInterface::luaSetCombatCallBack); + + //setCombatFormula(combat, type, mina, minb, maxa, maxb) + lua_register(m_luaState, "setCombatFormula", LuaScriptInterface::luaSetCombatFormula); + + //setConditionFormula(combat, mina, minb, maxa, maxb) + lua_register(m_luaState, "setConditionFormula", LuaScriptInterface::luaSetConditionFormula); + + //doCombat(cid, combat, param) + lua_register(m_luaState, "doCombat", LuaScriptInterface::luaDoCombat); + + //createCombatObject() + lua_register(m_luaState, "createCombatObject", LuaScriptInterface::luaCreateCombatObject); + + //doAreaCombatHealth(cid, type, pos, area, min, max, effect) + lua_register(m_luaState, "doAreaCombatHealth", LuaScriptInterface::luaDoAreaCombatHealth); + + //doTargetCombatHealth(cid, target, type, min, max, effect) + lua_register(m_luaState, "doTargetCombatHealth", LuaScriptInterface::luaDoTargetCombatHealth); + + //doAreaCombatMana(cid, pos, area, min, max, effect) + lua_register(m_luaState, "doAreaCombatMana", LuaScriptInterface::luaDoAreaCombatMana); + + //doTargetCombatMana(cid, target, min, max, effect) + lua_register(m_luaState, "doTargetCombatMana", LuaScriptInterface::luaDoTargetCombatMana); + + //doAreaCombatCondition(cid, pos, area, condition, effect) + lua_register(m_luaState, "doAreaCombatCondition", LuaScriptInterface::luaDoAreaCombatCondition); + + //doTargetCombatCondition(cid, target, condition, effect) + lua_register(m_luaState, "doTargetCombatCondition", LuaScriptInterface::luaDoTargetCombatCondition); + + //doAreaCombatDispel(cid, pos, area, type, effect) + lua_register(m_luaState, "doAreaCombatDispel", LuaScriptInterface::luaDoAreaCombatDispel); + + //doTargetCombatDispel(cid, target, type, effect) + lua_register(m_luaState, "doTargetCombatDispel", LuaScriptInterface::luaDoTargetCombatDispel); + + //doChallengeCreature(cid, target) + lua_register(m_luaState, "doChallengeCreature", LuaScriptInterface::luaDoChallengeCreature); + + //numberToVariant(number) + lua_register(m_luaState, "numberToVariant", LuaScriptInterface::luaNumberToVariant); + + //stringToVariant(string) + lua_register(m_luaState, "stringToVariant", LuaScriptInterface::luaStringToVariant); + + //positionToVariant(pos) + lua_register(m_luaState, "positionToVariant", LuaScriptInterface::luaPositionToVariant); + + //targetPositionToVariant(pos) + lua_register(m_luaState, "targetPositionToVariant", LuaScriptInterface::luaTargetPositionToVariant); + + //variantToNumber(var) + lua_register(m_luaState, "variantToNumber", LuaScriptInterface::luaVariantToNumber); + + //variantToString(var) + lua_register(m_luaState, "variantToString", LuaScriptInterface::luaVariantToString); + + //variantToPosition(var) + lua_register(m_luaState, "variantToPosition", LuaScriptInterface::luaVariantToPosition); + + //doChangeSpeed(cid, delta) + lua_register(m_luaState, "doChangeSpeed", LuaScriptInterface::luaDoChangeSpeed); + + //doCreatureChangeOutfit(cid, outfit) + lua_register(m_luaState, "doCreatureChangeOutfit", LuaScriptInterface::luaDoCreatureChangeOutfit); + + //doSetMonsterOutfit(cid, name, time) + lua_register(m_luaState, "doSetMonsterOutfit", LuaScriptInterface::luaSetMonsterOutfit); + + //doSetItemOutfit(cid, item, time) + lua_register(m_luaState, "doSetItemOutfit", LuaScriptInterface::luaSetItemOutfit); + + //doSetCreatureOutfit(cid, outfit, time) + lua_register(m_luaState, "doSetCreatureOutfit", LuaScriptInterface::luaSetCreatureOutfit); + + //getCreatureOutfit(cid) + lua_register(m_luaState, "getCreatureOutfit", LuaScriptInterface::luaGetCreatureOutfit); + + //getCreaturePos(cid) + lua_register(m_luaState, "getCreaturePos", LuaScriptInterface::luaGetCreaturePosition); + + //getCreaturePosition(cid) + lua_register(m_luaState, "getCreaturePosition", LuaScriptInterface::luaGetCreaturePosition); + + //getCreatureName(cid) + lua_register(m_luaState, "getCreatureName", LuaScriptInterface::luaGetCreatureName); + + //getCreatureSpeed(cid) + lua_register(m_luaState, "getCreatureSpeed", LuaScriptInterface::luaGetCreatureSpeed); + + //getCreatureBaseSpeed(cid) + lua_register(m_luaState, "getCreatureBaseSpeed", LuaScriptInterface::luaGetCreatureBaseSpeed); + + //getCreatureTarget(cid) + lua_register(m_luaState, "getCreatureTarget", LuaScriptInterface::luaGetCreatureTarget); + + //isItemStackable(itemid) + lua_register(m_luaState, "isItemStackable", LuaScriptInterface::luaIsItemStackable); + + //isItemRune(itemid) + lua_register(m_luaState, "isItemRune", LuaScriptInterface::luaIsItemRune); + + //isItemDoor(itemid) + lua_register(m_luaState, "isItemDoor", LuaScriptInterface::luaIsItemDoor); + + //isItemContainer(itemid) + lua_register(m_luaState, "isItemContainer", LuaScriptInterface::luaIsItemContainer); + + //isItemFluidContainer(itemid) + lua_register(m_luaState, "isItemFluidContainer", LuaScriptInterface::luaIsItemFluidContainer); + + //isItemMovable(itemid) + lua_register(m_luaState, "isItemMovable", LuaScriptInterface::luaIsItemMoveable); + //isItemMoveable(itemid) + lua_register(m_luaState, "isItemMoveable", LuaScriptInterface::luaIsItemMoveable); + + //getItemDescriptions(itemid) + lua_register(m_luaState, "getItemDescriptions", LuaScriptInterface::luaGetItemDescriptions); + + //getItemName(itemid) + lua_register(m_luaState, "getItemName", LuaScriptInterface::luaGetItemName); + + //getItemWeight(itemid, count, precise) + lua_register(m_luaState, "getItemWeight", LuaScriptInterface::luaGetItemWeight); + + //getItemWeightByUID(uid) + lua_register(m_luaState, "getItemWeightByUID", LuaScriptInterface::luaGetItemWeightByUID); + + //hasProperty(uid, prop) + lua_register(m_luaState, "hasProperty", LuaScriptInterface::luaHasProperty); + + //getItemIdByName(name) + lua_register(m_luaState, "getItemIdByName", LuaScriptInterface::luaGetItemIdByName); + + //getTownId(townName) + lua_register(m_luaState, "getTownId", LuaScriptInterface::luaGetTownId); + + //getTownName(townId) + lua_register(m_luaState, "getTownName", LuaScriptInterface::luaGetTownName); + + //getTownTemplePosition(townId) + lua_register(m_luaState, "getTownTemplePosition", LuaScriptInterface::luaGetTownTemplePosition); + + //isSightClear(fromPos, toPos, floorCheck) + lua_register(m_luaState, "isSightClear", LuaScriptInterface::luaIsSightClear); + + //getFluidSourceType(type) + lua_register(m_luaState, "getFluidSourceType", LuaScriptInterface::luaGetFluidSourceType); + + //isInArray(array, value) + lua_register(m_luaState, "isInArray", LuaScriptInterface::luaIsInArray); + + //addEvent(callback, delay, ...) + lua_register(m_luaState, "addEvent", LuaScriptInterface::luaAddEvent); + + //stopEvent(eventid) + lua_register(m_luaState, "stopEvent", LuaScriptInterface::luaStopEvent); + + //doPlayerPopupFYI(cid, message) + lua_register(m_luaState, "doPlayerPopupFYI", LuaScriptInterface::luaDoPlayerPopupFYI); + + //mayNotMove(cid, value) + lua_register(m_luaState, "mayNotMove", LuaScriptInterface::luaMayNotMove); + + //doPlayerAddPremiumDays(cid, days) + lua_register(m_luaState, "doPlayerAddPremiumDays", LuaScriptInterface::luaDoPlayerAddPremiumDays); + + //doPlayerRemovePremiumDays(cid, days) + lua_register(m_luaState, "doPlayerRemovePremiumDays", LuaScriptInterface::luaDoPlayerRemovePremiumDays); + + //getPlayerPremiumDays(cid) + lua_register(m_luaState, "getPlayerPremiumDays", LuaScriptInterface::luaGetPlayerPremiumDays); + + //getPromotedVocation(vocation) + lua_register(m_luaState, "getPromotedVocation", LuaScriptInterface::luaGetPromotedVocation); + + //getPlayerBlessing(cid, blessing) + lua_register(m_luaState, "getPlayerBlessing", LuaScriptInterface::luaGetPlayerBlessing); + + //doPlayerAddBlessing(cid, blessing) + lua_register(m_luaState, "doPlayerAddBlessing", LuaScriptInterface::luaDoPlayerAddBlessing); + + //getPlayerAccountBalance(cid), added for compatibility with otserv + lua_register(m_luaState, "getPlayerAccountBalance", LuaScriptInterface::luaGetPlayerBankBalance); + //getPlayerBalance(cid) + lua_register(m_luaState, "getPlayerBalance", LuaScriptInterface::luaGetPlayerBankBalance); + + //doPlayerSetBalance(cid, balance) + lua_register(m_luaState, "doPlayerSetBalance", LuaScriptInterface::luaDoPlayerSetBankBalance); + + //getPlayerMoney(cid) + lua_register(m_luaState, "getPlayerMoney", LuaScriptInterface::luaGetPlayerMoney); + + //getPlayerLastLoginSaved(cid) + lua_register(m_luaState, "getPlayerLastLoginSaved", LuaScriptInterface::luaGetPlayerLastLoginSaved); + + //saveServer() + //saveData() + lua_register(m_luaState, "saveServer", LuaScriptInterface::luaSaveServer); + lua_register(m_luaState, "saveData", LuaScriptInterface::luaSaveServer); + + //refreshMap() + lua_register(m_luaState, "refreshMap", LuaScriptInterface::luaRefreshMap); + + //cleanMap() + lua_register(m_luaState, "cleanMap", LuaScriptInterface::luaCleanMap); + + //getPlayersByAccountNumber(accountNumber) + lua_register(m_luaState, "getPlayersByAccountNumber", LuaScriptInterface::luaGetPlayersByAccountNumber); + + //getAccountNumberByPlayerName(name) + lua_register(m_luaState, "getAccountNumberByPlayerName", LuaScriptInterface::luaGetAccountNumberByPlayerName); + + //getIPByPlayerName(name) + //getIpByName(name) + lua_register(m_luaState, "getIPByPlayerName", LuaScriptInterface::luaGetIPByPlayerName); + lua_register(m_luaState, "getIpByName", LuaScriptInterface::luaGetIPByPlayerName); + + //getPlayersByIPAddress(ip[, mask = 0xFFFFFFFF]) + lua_register(m_luaState, "getPlayersByIPAddress", LuaScriptInterface::luaGetPlayersByIPAddress); + + //debugPrint(text) + lua_register(m_luaState, "debugPrint", LuaScriptInterface::luaDebugPrint); + + //isInWar(cid, target) + lua_register(m_luaState, "isInWar", LuaScriptInterface::luaIsInWar); + + //doPlayerSetOfflineTrainingSkill(cid, skill) + lua_register(m_luaState, "doPlayerSetOfflineTrainingSkill", LuaScriptInterface::luaDoPlayerSetOfflineTrainingSkill); + + //getWaypointPosition(name) + lua_register(m_luaState, "getWaypointPosition", LuaScriptInterface::luaGetWaypointPosition); + + //doWaypointAddTemporial(name, pos) + lua_register(m_luaState, "doWaypointAddTemporial", LuaScriptInterface::luaDoWaypointAddTemporial); + + //sendGuildChannelMessage(guildId, type, message) + lua_register(m_luaState, "sendGuildChannelMessage", LuaScriptInterface::luaSendGuildChannelMessage); + +#ifndef __LUAJIT__ + //bit operations for Lua, based on bitlib project release 24 + //bit.bnot, bit.band, bit.bor, bit.bxor, bit.lshift, bit.rshift + luaL_register(m_luaState, "bit", LuaScriptInterface::luaBitReg); +#endif + + //db table + luaL_register(m_luaState, "db", LuaScriptInterface::luaDatabaseTable); + + //result table + luaL_register(m_luaState, "result", LuaScriptInterface::luaResultTable); +} + +int32_t LuaScriptInterface::internalGetPlayerInfo(lua_State* L, PlayerInfo_t info) +{ + uint32_t cid = popNumber(L); + ScriptEnvironment* env = getScriptEnv(); + const Player* player = env->getPlayerByUID(cid); + + if (player) { + const Tile* tile; + Position pos; + uint32_t stackpos; + int32_t value; + + switch (info) { + case PlayerInfoAccess: + value = player->accessLevel; + break; + + case PlayerInfoLevel: + value = player->level; + break; + + case PlayerInfoMagLevel: + value = player->magLevel; + break; + + case PlayerInfoMana: + value = player->mana; + break; + + case PlayerInfoMaxMana: + value = player->getMaxMana(); + break; + + case PlayerInfoMasterPos: { + Position pos; + pos = player->masterPos; + pushPosition(L, pos, 0); + return 1; + } + + case PlayerInfoName: + lua_pushstring(L, player->name.c_str()); + return 1; + + case PlayerInfoPosition: + pos = player->getPosition(); + tile = player->getTile(); + + if (tile) { + stackpos = player->getParent()->__getIndexOfThing(player); + } else { + stackpos = 0; + } + + pushPosition(L, pos, stackpos); + return 1; + + case PlayerInfoLookDirection: + value = player->direction; + break; + + case PlayerInfoTown: + value = player->getTown(); + break; + + case PlayerInfoGUID: + value = player->guid; + break; + + case PlayerInfoPremiumDays: + value = player->premiumDays; + break; + + case PlayerInfoSkullType: + value = (uint32_t)player->getSkull(); + break; + + case PlayerInfoFood: { + Condition* condition = player->getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT); + + if (condition) { + value = condition->getTicks() / 1000; + } else { + value = 0; + } + + break; + } + + case PlayerInfoVocation: + value = player->getVocationId(); + break; + + case PlayerInfoSoul: + value = player->getPlayerInfo(PLAYERINFO_SOUL); + break; + + case PlayerInfoFreeCap: + value = (int32_t)player->getFreeCapacity(); + break; + + case PlayerInfoGuildId: { + Guild* guild = player->getGuild(); + + if (guild) { + value = guild->getId(); + } else { + value = 0; + } + + break; + } + + case PlayerInfoGuildLevel: + value = player->getGuildLevel(); + break; + + case PlayerInfoGuildName: { + Guild* guild = player->getGuild(); + + if (guild) { + lua_pushstring(L, guild->getName().c_str()); + } else { + lua_pushstring(L, ""); + } + + return 1; + } + + case PlayerInfoGuildRank: { + Guild* guild = player->getGuild(); + + if (!guild) { + lua_pushstring(L, ""); + return 1; + } + + GuildRank* rank = guild->getRankByLevel(player->getGuildLevel()); + + if (!rank) { + lua_pushstring(L, ""); + return 1; + } + + lua_pushstring(L, rank->name.c_str()); + return 1; + } + + case PlayerInfoGuildNick: + lua_pushstring(L, player->getGuildNick().c_str()); + return 1; + + case PlayerInfoSex: + value = player->getSex(); + break; + + case PlayerInfoGroupId: + value = player->groupId; + break; + + case PlayerInfoPzLock: + lua_pushboolean(L, player->isPzLocked()); + return 1; + + case PlayerInfoGhostStatus: + lua_pushboolean(L, player->isInGhostMode()); + return 1; + + case PlayerInfoIp: + value = (int32_t)player->getIP(); + break; + + case PlayerInfoBankBalance: + lua_pushnumber(L, player->getBankBalance()); + return 1; + + case PlayerInfoMoney: + lua_pushnumber(L, g_game.getMoney(player)); + return 1; + + case PlayerInfoLastLoginSaved: + lua_pushnumber(L, player->getLastLoginSaved()); + return 1; + + default: + std::string error_str = "Unknown player info. info = " + info; + reportErrorFunc(error_str); + value = 0; + break; + } + + lua_pushnumber(L, value); + return 1; + } else { + lua_pushboolean(L, false); + return 1; + } + + lua_pushboolean(L, true); + return 1; +} +//getPlayer[Info](uid) +int32_t LuaScriptInterface::luaGetPlayerFood(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoFood); +} +int32_t LuaScriptInterface::luaGetPlayerAccess(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoAccess); +} +int32_t LuaScriptInterface::luaGetPlayerLevel(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoLevel); +} +int32_t LuaScriptInterface::luaGetPlayerMagLevel(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoMagLevel); +} +int32_t LuaScriptInterface::luaGetPlayerMana(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoMana); +} +int32_t LuaScriptInterface::luaGetPlayerMaxMana(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoMaxMana); +} +int32_t LuaScriptInterface::luaGetPlayerName(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoName); +} +int32_t LuaScriptInterface::luaGetPlayerPosition(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoPosition); +} +int32_t LuaScriptInterface::luaGetPlayerVocation(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoVocation); +} +int32_t LuaScriptInterface::luaGetPlayerMasterPos(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoMasterPos); +} +int32_t LuaScriptInterface::luaGetPlayerSoul(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoSoul); +} +int32_t LuaScriptInterface::luaGetPlayerFreeCap(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoFreeCap); +} +int32_t LuaScriptInterface::luaGetPlayerGuildId(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoGuildId); +} +int32_t LuaScriptInterface::luaGetPlayerGuildLevel(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoGuildLevel); +} +int32_t LuaScriptInterface::luaGetPlayerGuildName(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoGuildName); +} +int32_t LuaScriptInterface::luaGetPlayerGuildRank(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoGuildRank); +} +int32_t LuaScriptInterface::luaGetPlayerGuildNick(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoGuildNick); +} +int32_t LuaScriptInterface::luaGetPlayerSex(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoSex); +} +int32_t LuaScriptInterface::luaGetPlayerLookDir(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoLookDirection); +} +int32_t LuaScriptInterface::luaGetPlayerTown(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoTown); +} +int32_t LuaScriptInterface::luaGetPlayerGroupId(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoGroupId); +} +int32_t LuaScriptInterface::luaGetPlayerGUID(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoGUID); +} +int32_t LuaScriptInterface::luaGetPlayerPremiumDays(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoPremiumDays); +} +int32_t LuaScriptInterface::luaGetPlayerSkullType(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoSkullType); +} +int32_t LuaScriptInterface::luaIsPlayerPzLocked(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoPzLock); +} +int32_t LuaScriptInterface::luaIsPlayerGhost(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoGhostStatus); +} +int32_t LuaScriptInterface::luaGetPlayerIp(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoIp); +} +int32_t LuaScriptInterface::luaGetPlayerBankBalance(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoBankBalance); +} +int32_t LuaScriptInterface::luaGetPlayerMoney(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoMoney); +} +int32_t LuaScriptInterface::luaGetPlayerLastLoginSaved(lua_State* L) +{ + return internalGetPlayerInfo(L, PlayerInfoLastLoginSaved); +} + +int32_t LuaScriptInterface::luaGetPlayerFlagValue(lua_State* L) +{ + //getPlayerFlagValue(cid, flag) + uint32_t flagindex = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Player* player = env->getPlayerByUID(cid); + + if (player) { + if (flagindex < PlayerFlag_LastFlag) { + lua_pushboolean(L, player->hasFlag((PlayerFlags)flagindex)); + } else { + reportErrorFunc("No valid flag index."); + lua_pushboolean(L, false); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaPlayerLearnInstantSpell(lua_State* L) +{ + //playerLearnInstantSpell(cid, name) + std::string spellName = popString(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Player* player = env->getPlayerByUID(cid); + + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + InstantSpell* spell = g_spells->getInstantSpellByName(spellName); + + if (!spell) { + std::string error_str = (std::string)"Spell \"" + spellName + (std::string)"\" not found"; + reportErrorFunc(error_str); + lua_pushboolean(L, false); + return 1; + } + + player->learnInstantSpell(spell->getName()); + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaCanPlayerLearnInstantSpell(lua_State* L) +{ + //canPlayerLearnInstantSpell(cid, name) + std::string spellName = popString(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Player* player = env->getPlayerByUID(cid); + + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + InstantSpell* spell = g_spells->getInstantSpellByName(spellName); + + if (!spell) { + std::string error_str = (std::string)"Spell \"" + spellName + (std::string)"\" not found"; + reportErrorFunc(error_str); + lua_pushboolean(L, false); + return 1; + } + + if (!player->hasFlag(PlayerFlag_IgnoreSpellCheck)) { + if (player->getLevel() < spell->getLevel()) { + lua_pushboolean(L, false); + return 1; + } + + if (player->getMagicLevel() < spell->getMagicLevel()) { + lua_pushboolean(L, false); + return 1; + } + } + + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaGetPlayerLearnedInstantSpell(lua_State* L) +{ + //getPlayerLearnedInstantSpell(cid, name) + std::string spellName = popString(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Player* player = env->getPlayerByUID(cid); + + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + InstantSpell* spell = g_spells->getInstantSpellByName(spellName); + + if (!spell) { + std::string error_str = (std::string)"Spell \"" + spellName + (std::string)"\" not found"; + reportErrorFunc(error_str); + lua_pushboolean(L, false); + return 1; + } + + if (!player->hasLearnedInstantSpell(spellName)) { + lua_pushboolean(L, false); + return 1; + } + + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaGetPlayerInstantSpellCount(lua_State* L) +{ + //getPlayerInstantSpellCount(cid) + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Player* player = env->getPlayerByUID(cid); + + if (player) { + lua_pushnumber(L, g_spells->getInstantSpellCount(player)); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetPlayerInstantSpellInfo(lua_State* L) +{ + //getPlayerInstantSpellInfo(cid, index) + uint32_t index = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Player* player = env->getPlayerByUID(cid); + + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + InstantSpell* spell = g_spells->getInstantSpellByIndex(player, index); + + if (!spell) { + reportErrorFunc(getErrorDesc(LUA_ERROR_SPELL_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + lua_newtable(L); + setField(L, "name", spell->getName()); + setField(L, "words", spell->getWords()); + setField(L, "level", spell->getLevel()); + setField(L, "mlevel", spell->getMagicLevel()); + setField(L, "mana", spell->getManaCost(player)); + setField(L, "manapercent", spell->getManaPercent()); + return 1; +} + +int32_t LuaScriptInterface::luaGetInstantSpellInfoByName(lua_State* L) +{ + //getInstantSpellInfoByName(cid, name) + std::string spellName = popString(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Player* player = env->getPlayerByUID(cid); + + if (!player && cid != 0) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + InstantSpell* spell = g_spells->getInstantSpellByName(spellName); + + if (!spell) { + reportErrorFunc(getErrorDesc(LUA_ERROR_SPELL_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + lua_newtable(L); + setField(L, "name", spell->getName()); + setField(L, "words", spell->getWords()); + setField(L, "level", spell->getLevel()); + setField(L, "mlevel", spell->getMagicLevel()); + setField(L, "mana", (player != NULL ? spell->getManaCost(player) : 0)); + setField(L, "manapercent", spell->getManaPercent()); + return 1; +} + +int32_t LuaScriptInterface::luaGetInstantSpellWords(lua_State* L) +{ + //getInstantSpellWords(name) + std::string spellName = popString(L); + InstantSpell* spell = g_spells->getInstantSpellByName(spellName); + + if (!spell) { + reportErrorFunc(getErrorDesc(LUA_ERROR_SPELL_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + lua_pushstring(L, spell->getWords().c_str()); + return 1; +} + +int32_t LuaScriptInterface::luaDoRemoveItem(lua_State* L) +{ + //doRemoveItem(uid, n) + int32_t parameters = lua_gettop(L); + + int32_t count = -1; + + if (parameters > 1) { + count = popNumber(L); + } + + uint32_t uid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Item* item = env->getItemByUID(uid); + + if (!item) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + ReturnValue ret = g_game.internalRemoveItem(item, count); + + if (ret != RET_NOERROR) { + lua_pushboolean(L, false); + return 1; + } + + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerRemoveItem(lua_State* L) +{ + //doPlayerRemoveItem(cid, itemid, count, subtype, ignoreEquipped) + int32_t parameters = lua_gettop(L); + + bool ignoreEquipped = false; + + if (parameters > 4) { + ignoreEquipped = popBoolean(L); + } + + int32_t subType = -1; + + if (parameters > 3) { + subType = popNumber(L); + } + + uint32_t count = popNumber(L); + uint16_t itemId = (uint16_t)popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Player* player = env->getPlayerByUID(cid); + + if (player) { + lua_pushboolean(L, g_game.removeItemOfType(player, itemId, count, subType, ignoreEquipped)); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoFeedPlayer(lua_State* L) +{ + //doFeedPlayer(cid, food) + int32_t food = (int32_t)popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Player* player = env->getPlayerByUID(cid); + + if (player) { + player->addDefaultRegeneration(food * 1000); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerSendCancel(lua_State* L) +{ + //doPlayerSendCancel(cid, text) + std::string text = popString(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + const Player* player = env->getPlayerByUID(cid); + + if (player) { + player->sendCancel(text); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoSendDefaultCancel(lua_State* L) +{ + //doPlayerSendDefaultCancel(cid, ReturnValue) + ReturnValue ret = (ReturnValue)popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + const Player* player = env->getPlayerByUID(cid); + + if (player) { + player->sendCancelMessage(ret); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoTeleportThing(lua_State* L) +{ + //doTeleportThing(cid, newpos, pushmove) + int32_t parameters = lua_gettop(L); + + bool pushMovement = false; + + if (parameters > 2) { + pushMovement = popBoolean(L); + } + + PositionEx pos; + popPosition(L, pos); + uint32_t uid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Thing* tmp = env->getThingByUID(uid); + + if (tmp) { + Position oldPos = tmp->getPosition(); + + if (g_game.internalTeleport(tmp, pos, pushMovement) == RET_NOERROR) { + Creature* creature = tmp->getCreature(); + + if (!pushMovement && creature) { + if (oldPos.x == pos.x) { + if (oldPos.y < pos.y) { + g_game.internalCreatureTurn(creature, SOUTH); + } else { + g_game.internalCreatureTurn(creature, NORTH); + } + } else if (oldPos.x > pos.x) { + g_game.internalCreatureTurn(creature, WEST); + } else if (oldPos.x < pos.x) { + g_game.internalCreatureTurn(creature, EAST); + } + } + + lua_pushboolean(L, true); + } else { + lua_pushboolean(L, false); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_THING_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoTransformItem(lua_State* L) +{ + //doTransformItem(uid, toitemid, count/subtype) + int32_t parameters = lua_gettop(L); + + int32_t count = -1; + + if (parameters > 2) { + count = popNumber(L); + } + + uint16_t toId = (uint16_t)popNumber(L); + uint32_t uid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Item* item = env->getItemByUID(uid); + + if (!item) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + if (item->getID() == toId) { + lua_pushboolean(L, true); + return 1; + } + + const ItemType& it = Item::items[toId]; + + if (it.stackable && count > 100) { + reportErrorFunc("Stack count cannot be higher than 100."); + count = 100; + } + + Item* newItem = g_game.transformItem(item, toId, count); + + if (item->isRemoved()) { + env->removeItemByUID(uid); + } + + if (newItem && newItem != item) { + env->insertThing(uid, newItem); + } + + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaDoCreatureSay(lua_State* L) +{ + //doCreatureSay(uid, text, type[, ghost = false[, cid = 0[, pos]]]) + uint32_t params = lua_gettop(L); + uint32_t cid = 0, uid = 0; + PositionEx pos; + + if (params > 5) { + popPosition(L, pos); + } + + if (params > 4) { + cid = popNumber(L); + } + + bool ghost = false; + + if (params > 3) { + ghost = popBoolean(L); + } + + SpeakClasses type = (SpeakClasses)popNumber(L); + std::string text = popString(L); + + uid = popNumber(L); + + if (params > 5 && (!pos.x || !pos.y)) { + reportErrorFunc("Invalid position specified."); + lua_pushboolean(L, false); + return 1; + } + + ScriptEnvironment* env = getScriptEnv(); + Creature* creature = env->getCreatureByUID(uid); + + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + SpectatorVec list; + + if (cid) { + Creature* target = env->getCreatureByUID(cid); + + if (!target) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + list.insert(target); + } + + if (params > 5) { + lua_pushboolean(L, g_game.internalCreatureSay(creature, type, text, ghost, &list, &pos)); + } else { + lua_pushboolean(L, g_game.internalCreatureSay(creature, type, text, ghost, &list)); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoCreateNpc(lua_State* L) +{ + //doCreateNpc(name, pos) + PositionEx pos; + popPosition(L, pos); + + std::string name = popString(L); + Npc* npc = Npc::createNpc(name.c_str()); + + if (!npc) { + lua_pushboolean(L, false); + return 1; + } + + // Place the npc + if (g_game.placeCreature(npc, pos)) { + npc->setMasterPos(npc->getPosition()); + lua_pushboolean(L, true); + } else { + delete npc; + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoSendMagicEffect(lua_State* L) +{ + //doSendMagicEffect(pos, type[, player]) + ScriptEnvironment* env = getScriptEnv(); + + uint32_t parameters = lua_gettop(L); + SpectatorVec list; + + if (parameters > 2) { + uint32_t cid = popNumber(L); + Player* player = env->getPlayerByUID(cid); + + if (player) { + list.insert(player); + } + } + + uint32_t type = popNumber(L); + PositionEx pos; + popPosition(L, pos); + + if (pos.x == 0xFFFF) { + pos = env->getRealPos(); + } + + if (!list.empty()) { + g_game.addMagicEffect(list, pos, type); + } else { + g_game.addMagicEffect(pos, type); + } + + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaDoSendDistanceShoot(lua_State* L) +{ + //doSendDistanceShoot(frompos, topos, type) + uint32_t type = popNumber(L); + PositionEx toPos; + popPosition(L, toPos); + PositionEx fromPos; + popPosition(L, fromPos); + ScriptEnvironment* env = getScriptEnv(); + + if (fromPos.x == 0xFFFF) { + fromPos = env->getRealPos(); + } + + if (toPos.x == 0xFFFF) { + toPos = env->getRealPos(); + } + + g_game.addDistanceEffect(fromPos, toPos, type); + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaDoChangeTypeItem(lua_State* L) +{ + //doChangeTypeItem(uid,new_type) + int32_t subtype = (int32_t)popNumber(L); + uint32_t uid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Item* item = env->getItemByUID(uid); + + if (!item) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + Item* newItem = g_game.transformItem(item, item->getID(), subtype); + + if (item->isRemoved()) { + env->removeItemByUID(uid); + } + + if (newItem && newItem != item) { + env->insertThing(uid, newItem); + } + + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerAddSkillTry(lua_State* L) +{ + //doPlayerAddSkillTry(uid,skillid,n) + uint32_t n = popNumber(L); + uint32_t skillid = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Player* player = env->getPlayerByUID(cid); + + if (player) { + player->addSkillAdvance((skills_t)skillid, n); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + + +int32_t LuaScriptInterface::luaDoCreatureAddHealth(lua_State* L) +{ + //doCreatureAddHealth(uid,health) + int32_t healthChange = (int32_t)popNumber(L); + uint32_t cid = popNumber(L); + ScriptEnvironment* env = getScriptEnv(); + Creature* creature = env->getCreatureByUID(cid); + + if (creature) { + if (healthChange >= 0) { + g_game.combatChangeHealth(COMBAT_HEALING, NULL, creature, healthChange); + } else { + g_game.combatChangeHealth(COMBAT_UNDEFINEDDAMAGE, NULL, creature, healthChange); + } + + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerAddMana(lua_State* L) +{ + //doPlayerAddMana(uid, mana[, animationOnLoss]) + int32_t parameters = lua_gettop(L); + + bool animationOnLoss = true; + + if (parameters > 2) { + animationOnLoss = popBoolean(L); + } + + int32_t manaChange = (int32_t)popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Player* player = env->getPlayerByUID(cid); + + if (player) { + if (!animationOnLoss && manaChange < 0) { + player->changeMana(manaChange); + } else { + g_game.combatChangeMana(NULL, player, manaChange); + } + + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerChangeName(lua_State* L) +{ + uint32_t guid = popNumber(L); + std::string newName = popString(L); + + if (IOLoginData::getInstance()->changeName(guid, newName)) { + if (House* house = Houses::getInstance().getHouseByPlayerId(guid)) { + house->updateDoorDescription(); + } + + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerAddManaSpent(lua_State* L) +{ + //doPlayerAddManaSpent(cid,mana) + uint32_t mana = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Player* player = env->getPlayerByUID(cid); + + if (player) { + player->addManaSpent(mana); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerAddItem(lua_State* L) +{ + //doPlayerAddItem(cid, itemid, count/subtype, canDropOnMap) + //doPlayerAddItem(cid, itemid, count, canDropOnMap, subtype) + int32_t parameters = lua_gettop(L); + + int32_t subType = 1; + + if (parameters > 4) { + subType = popNumber(L); + } + + bool canDropOnMap = true; + + if (parameters > 3) { + canDropOnMap = popBoolean(L); + } + + uint32_t count = 1; + + if (parameters > 2) { + count = popNumber(L); + } + + uint32_t itemId = (uint32_t)popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Player* player = env->getPlayerByUID(cid); + + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + const ItemType& it = Item::items[itemId]; + + int32_t itemCount = 1; + + if (parameters > 4) { + //subtype already supplied, count then is the amount + itemCount = std::max(1, count); + } else if (it.hasSubType()) { + if (it.stackable) { + itemCount = (int32_t)std::ceil((float)count / 100); + } + + subType = count; + } else { + itemCount = std::max(1, count); + } + + while (itemCount > 0) { + int32_t stackCount = subType; + + if (it.stackable && stackCount > 100) { + stackCount = 100; + } + + Item* newItem = Item::CreateItem(itemId, stackCount); + + if (!newItem) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + if (it.stackable) { + subType -= stackCount; + } + + ReturnValue ret = g_game.internalPlayerAddItem(player, newItem, canDropOnMap); + + if (ret != RET_NOERROR) { + delete newItem; + lua_pushboolean(L, false); + return 1; + } + + --itemCount; + + if (itemCount == 0) { + if (newItem->getParent()) { + uint32_t uid = env->addThing(newItem); + lua_pushnumber(L, uid); + return 1; + } else { + //stackable item stacked with existing object, newItem will be released + lua_pushboolean(L, false); + return 1; + } + } + } + + lua_pushboolean(L, false); + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerAddItemEx(lua_State* L) +{ + //doPlayerAddItemEx(cid, uid, canDropOnMap) + int32_t parameters = lua_gettop(L); + + bool canDropOnMap = false; + + if (parameters > 2) { + canDropOnMap = popBoolean(L); + } + + uint32_t uid = (uint32_t)popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Player* player = env->getPlayerByUID(cid); + + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + Item* item = env->getItemByUID(uid); + + if (!item) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + if (item->getParent() != VirtualCylinder::virtualCylinder) { + reportErrorFunc("Item already has a parent"); + lua_pushboolean(L, false); + return 1; + } + + ReturnValue ret = RET_NOERROR; + + if (canDropOnMap) { + ret = g_game.internalPlayerAddItem(player, item); + } else { + ret = g_game.internalAddItem(player, item); + } + + lua_pushnumber(L, ret); + return 1; +} + +int32_t LuaScriptInterface::luaDoTileAddItemEx(lua_State* L) +{ + //doTileAddItemEx(pos, uid) + uint32_t uid = (uint32_t)popNumber(L); + PositionEx pos; + popPosition(L, pos); + + ScriptEnvironment* env = getScriptEnv(); + Tile* tile = g_game.getTile(pos.x, pos.y, pos.z); + + if (!tile) { + std::ostringstream ss; + ss << pos << " " << getErrorDesc(LUA_ERROR_TILE_NOT_FOUND); + reportErrorFunc(ss.str().c_str()); + lua_pushboolean(L, false); + return 1; + } + + Item* item = env->getItemByUID(uid); + + if (!item) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + if (item->getParent() != VirtualCylinder::virtualCylinder) { + reportErrorFunc("Item already has a parent"); + lua_pushboolean(L, false); + return 1; + } + + ReturnValue ret = g_game.internalAddItem(tile, item); + lua_pushnumber(L, ret); + return 1; +} + +int32_t LuaScriptInterface::luaDoAddContainerItemEx(lua_State* L) +{ + //doAddContainerItemEx(uid, virtuid) + uint32_t virtuid = popNumber(L); + uint32_t uid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Container* container = env->getContainerByUID(uid); + + if (container) { + Item* item = env->getItemByUID(virtuid); + + if (!item) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + if (item->getParent() != VirtualCylinder::virtualCylinder) { + reportErrorFunc("Item already has a parent"); + lua_pushboolean(L, false); + return 1; + } + + ReturnValue ret = g_game.internalAddItem(container, item); + + if (ret == RET_NOERROR) { + env->removeTempItem(item); + } + + lua_pushnumber(L, ret); + return 1; + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CONTAINER_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } +} + +int32_t LuaScriptInterface::luaDoRelocate(lua_State* L) +{ + //doRelocate(pos, posTo) + //Moves all moveable objects from pos to posTo + + PositionEx toPos; + popPosition(L, toPos); + + PositionEx fromPos; + popPosition(L, fromPos); + + Tile* fromTile = g_game.getTile(fromPos.x, fromPos.y, fromPos.z); + + if (!fromTile) { + std::ostringstream ss; + ss << fromPos << " " << getErrorDesc(LUA_ERROR_TILE_NOT_FOUND); + reportErrorFunc(ss.str()); + lua_pushboolean(L, false); + return 1; + } + + Tile* toTile = g_game.getTile(toPos.x, toPos.y, toPos.z); + + if (!toTile) { + std::ostringstream ss; + ss << toPos << " " << getErrorDesc(LUA_ERROR_TILE_NOT_FOUND); + reportErrorFunc(ss.str()); + lua_pushboolean(L, false); + return 1; + } + + if (fromTile != toTile) { + int32_t thingCount = fromTile->getThingCount(); + + for (int32_t i = thingCount - 1; i >= 0; --i) { + Thing* thing = fromTile->__getThing(i); + + if (thing) { + if (Item* item = thing->getItem()) { + const ItemType& it = Item::items[item->getID()]; + + if (!it.isGroundTile() && !it.alwaysOnTop && !it.isMagicField() && !it.isDoor()) { + g_game.internalTeleport(item, toPos, false, FLAG_IGNORENOTMOVEABLE); + } + } else if (Creature* creature = thing->getCreature()) { + g_game.internalTeleport(creature, toPos); + } + } + } + } + + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerSendTextMessage(lua_State* L) +{ + //doPlayerSendTextMessage(cid, MessageClasses, message[, position, value, color]) + int parameters = lua_gettop(L); + PositionEx position; + uint32_t value = 0; + TextColor_t color = TEXTCOLOR_NONE; + + if (parameters > 5) { + color = (TextColor_t)popNumber(L); + value = popNumber(L); + popPosition(L, position); + } + + std::string text = popString(L); + uint32_t messageClass = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + const Player* player = env->getPlayerByUID(cid); + + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + if (parameters > 5) { + player->sendTextMessage((MessageClasses)messageClass, text, &position, value, color); + } else { + player->sendTextMessage((MessageClasses)messageClass, text); + } + + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaDoSendAnimatedText(lua_State* L) +{ + //doSendAnimatedText(pos, text, color) + popNumber(L); + popString(L); + PositionEx pos; + popPosition(L, pos); + + reportErrorFunc("Deprecated function."); + + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaGetPlayerSkill(lua_State* L) +{ + //getPlayerSkill(cid, skillid) + uint32_t skillid = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + const Player* player = env->getPlayerByUID(cid); + + if (player) { + if (skillid <= SKILL_LAST) { + uint32_t value = player->skills[skillid][SKILL_LEVEL]; + lua_pushnumber(L, value); + } else { + reportErrorFunc("No valid skillId"); + lua_pushboolean(L, false); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetPlayerLossPercent(lua_State* L) +{ + //getPlayerLossPercent(cid) + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Player* player = env->getPlayerByUID(cid); + + if (player) { + uint32_t value = (uint32_t)(player->getLostPercent() * 100); + lua_pushnumber(L, value); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoSetCreatureDropLoot(lua_State* L) +{ + //doSetCreatureDropLoot(cid, doDrop) + bool doDrop = popBoolean(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (creature) { + creature->setDropLoot(doDrop); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoShowTextDialog(lua_State* L) +{ + //doShowTextDialog(cid, itemid, text) + std::string text = popString(L); + uint32_t itemId = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Player* player = env->getPlayerByUID(cid); + + if (player) { + player->setWriteItem(NULL, 0); + player->sendTextWindow(itemId, text); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoSendTutorial(lua_State* L) +{ + //doSendTutorial(cid, tutorialid) + uint32_t tutorial = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Player* player = env->getPlayerByUID(cid); + + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + player->sendTutorial(tutorial); + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaDoAddMark(lua_State* L) +{ + //doAddMapMark(cid, pos, type, description) + int32_t parameters = lua_gettop(L); + std::string description = ""; + Position pos; + uint32_t stackpos; + + if (parameters > 3) { + description = popString(L); + } + + uint32_t type = popNumber(L); + popPosition(L, pos, stackpos); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Player* player = env->getPlayerByUID(cid); + + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + player->sendAddMarker(pos, type, description); + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaGetItemRWInfo(lua_State* L) +{ + //getItemRWInfo(uid) + uint32_t uid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + const Item* item = env->getItemByUID(uid); + + if (item) { + uint32_t rwflags = 0; + + if (item->isReadable()) { + rwflags |= 1; + } + + if (item->canWriteText()) { + rwflags |= 2; + } + + lua_pushnumber(L, rwflags); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoDecayItem(lua_State* L) +{ + //doDecayItem(uid) + //Note: to stop decay set decayTo = 0 in items.otb + uint32_t uid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Item* item = env->getItemByUID(uid); + + if (!item) { + lua_pushboolean(L, false); + return 1; + } + + g_game.startDecay(item); + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaGetTileInfo(lua_State* L) +{ + PositionEx pos; + popPosition(L, pos); + + if (Tile* tile = g_game.getMap()->getTile(pos)) { + ScriptEnvironment* env = getScriptEnv(); + pushThing(L, tile->ground, env->addThing(tile->ground)); + + setFieldBool(L, "protection", tile->hasFlag(TILESTATE_PROTECTIONZONE)); + setFieldBool(L, "nopz", tile->hasFlag(TILESTATE_PROTECTIONZONE)); + setFieldBool(L, "nologout", tile->hasFlag(TILESTATE_NOLOGOUT)); + setFieldBool(L, "refresh", tile->hasFlag(TILESTATE_REFRESH)); + setFieldBool(L, "house", tile->hasFlag(TILESTATE_HOUSE)); + setFieldBool(L, "bed", tile->hasFlag(TILESTATE_BED)); + setFieldBool(L, "depot", tile->hasFlag(TILESTATE_DEPOT)); + + setField(L, "things", tile->getThingCount()); + setField(L, "creatures", tile->getCreatureCount()); + setField(L, "items", tile->getItemCount()); + setField(L, "topItems", tile->getTopItemCount()); + setField(L, "downItems", tile->getDownItemCount()); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_TILE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetThingfromPos(lua_State* L) +{ + //Consider using getTileItemById/getTileItemByType/getTileThingByPos/getTopCreature instead. + + //getThingfromPos(pos) + //Note: + // stackpos = 255. Get the top thing(item moveable or creature) + // stackpos = 254. Get MagicFieldtItem + // stackpos = 253. Get the top creature (moveable creature) + + PositionEx pos; + popPosition(L, pos); + + ScriptEnvironment* env = getScriptEnv(); + + Tile* tile = g_game.getMap()->getTile(pos); + Thing* thing = NULL; + + if (tile) { + if (pos.stackpos == 255) { + thing = tile->getTopCreature(); + + if (thing == NULL) { + Item* item = tile->getTopDownItem(); + + if (item && !item->isNotMoveable()) { + thing = item; + } + } + } else if (pos.stackpos == 254) { + thing = tile->getFieldItem(); + } else if (pos.stackpos == 253) { + thing = tile->getTopCreature(); + } else { + thing = tile->__getThing(pos.stackpos); + } + + if (thing) { + uint32_t thingid = env->addThing(thing); + pushThing(L, thing, thingid); + } else { + pushThing(L, NULL, 0); + } + + return 1; + } else { + pushThing(L, NULL, 0); + return 1; + } +} + +int32_t LuaScriptInterface::luaGetTileItemById(lua_State* L) +{ + //getTileItemById(pos, itemId, subType) + ScriptEnvironment* env = getScriptEnv(); + + uint32_t parameters = lua_gettop(L); + + int32_t subType = -1; + + if (parameters > 2) { + subType = (int32_t)popNumber(L); + } + + int32_t itemId = (int32_t)popNumber(L); + + PositionEx pos; + popPosition(L, pos); + + Tile* tile = g_game.getTile(pos.x, pos.y, pos.z); + + if (!tile) { + pushThing(L, NULL, 0); + return 1; + } + + Item* item = g_game.findItemOfType(tile, itemId, false, subType); + + if (!item) { + pushThing(L, NULL, 0); + return 1; + } + + uint32_t uid = env->addThing(item); + pushThing(L, item, uid); + return 1; +} + +int32_t LuaScriptInterface::luaGetTileItemByType(lua_State* L) +{ + //getTileItemByType(pos, type) + + ScriptEnvironment* env = getScriptEnv(); + + uint32_t rType = (uint32_t)popNumber(L); + + if (rType >= ITEM_TYPE_LAST) { + reportErrorFunc("Not a valid item type"); + pushThing(L, NULL, 0); + return 1; + } + + PositionEx pos; + popPosition(L, pos); + + Tile* tile = g_game.getTile(pos.x, pos.y, pos.z); + + if (!tile) { + pushThing(L, NULL, 0); + return 1; + } + + bool notFound = false; + + switch ((ItemTypes_t)rType) { + case ITEM_TYPE_TELEPORT: + + if (!tile->hasFlag(TILESTATE_TELEPORT)) { + notFound = true; + } + + break; + + case ITEM_TYPE_MAGICFIELD: + + if (!tile->hasFlag(TILESTATE_MAGICFIELD)) { + notFound = true; + } + + break; + + case ITEM_TYPE_MAILBOX: + + if (!tile->hasFlag(TILESTATE_MAILBOX)) { + notFound = true; + } + + break; + + case ITEM_TYPE_TRASHHOLDER: + + if (!tile->hasFlag(TILESTATE_TRASHHOLDER)) { + notFound = true; + } + + break; + + case ITEM_TYPE_BED: + + if (!tile->hasFlag(TILESTATE_BED)) { + notFound = true; + } + + break; + + case ITEM_TYPE_DEPOT: + + if (!tile->hasFlag(TILESTATE_DEPOT)) { + notFound = true; + } + + break; + + default: + break; + } + + if (!notFound) { + if (tile->ground) { + const ItemType& it = Item::items[tile->ground->getID()]; + + if (it.type == (ItemTypes_t) rType) { + uint32_t uid = env->addThing(tile->ground); + pushThing(L, tile->ground, uid); + return 1; + } + } + + if (const TileItemVector* items = tile->getItemList()) { + for (ItemVector::const_iterator it = items->begin(), end = items->end(); it != end; ++it) { + Item* item = (*it); + const ItemType& itemType = Item::items[item->getID()]; + + if (itemType.type == (ItemTypes_t)rType) { + uint32_t uid = env->addThing(item); + pushThing(L, item, uid); + return 1; + } + } + } + } + + pushThing(L, NULL, 0); + return 1; +} + +int32_t LuaScriptInterface::luaGetTileThingByPos(lua_State* L) +{ + //getTileThingByPos(pos) + + PositionEx pos; + popPosition(L, pos); + + ScriptEnvironment* env = getScriptEnv(); + + Tile* tile = g_game.getTile(pos.x, pos.y, pos.z); + + if (!tile) { + if (pos.stackpos == -1) { + lua_pushnumber(L, -1); + return 1; + } else { + pushThing(L, NULL, 0); + return 1; + } + } + + if (pos.stackpos == -1) { + lua_pushnumber(L, tile->getThingCount()); + return 1; + } + + Thing* thing = tile->__getThing(pos.stackpos); + + if (!thing) { + pushThing(L, NULL, 0); + return 1; + } + + uint32_t uid = env->addThing(thing); + pushThing(L, thing, uid); + return 1; +} + +int32_t LuaScriptInterface::luaGetTileThingByTopOrder(lua_State* L) +{ + //getTileThingByTopOrder(pos, topOrder) + uint32_t topOrder = popNumber(L); + PositionEx pos; + popPosition(L, pos); + + ScriptEnvironment* env = getScriptEnv(); + + Tile* tile = g_game.getTile(pos.x, pos.y, pos.z); + + if (!tile) { + pushThing(L, NULL, 0); + return 1; + } + + Thing* thing = tile->getItemByTopOrder(topOrder); + + if (!thing) { + pushThing(L, NULL, 0); + return 1; + } + + uint32_t uid = env->addThing(thing); + pushThing(L, thing, uid); + return 1; +} + +int32_t LuaScriptInterface::luaGetTopCreature(lua_State* L) +{ + //getTopCreature(pos) + PositionEx pos; + popPosition(L, pos); + + ScriptEnvironment* env = getScriptEnv(); + + Tile* tile = g_game.getTile(pos.x, pos.y, pos.z); + + if (!tile) { + pushThing(L, NULL, 0); + return 1; + } + + Thing* thing = tile->getTopCreature(); + + if (!thing || !thing->getCreature()) { + pushThing(L, NULL, 0); + return 1; + } + + uint32_t uid = env->addThing(thing); + pushThing(L, thing, uid); + return 1; +} + +int32_t LuaScriptInterface::luaDoCreateItem(lua_State* L) +{ + //doCreateItem(itemid, type/count, pos) + //Returns uid of the created item, only works on tiles. + uint32_t parameters = lua_gettop(L); + + PositionEx pos; + popPosition(L, pos); + + uint32_t count = 1; + + if (parameters > 2) { + count = popNumber(L); + } + + uint32_t itemId = (uint32_t)popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Tile* tile = g_game.getTile(pos.x, pos.y, pos.z); + + if (!tile) { + std::ostringstream ss; + ss << pos << " " << getErrorDesc(LUA_ERROR_TILE_NOT_FOUND); + reportErrorFunc(ss.str().c_str()); + lua_pushboolean(L, false); + return 1; + } + + const ItemType& it = Item::items[itemId]; + + int32_t itemCount = 1; + + int32_t subType = 1; + + if (it.hasSubType()) { + if (it.stackable) { + itemCount = (int32_t)std::ceil((float)count / 100); + } + + subType = count; + } else { + itemCount = std::max(1, count); + } + + while (itemCount > 0) { + int32_t stackCount = std::min(100, subType); + Item* newItem = Item::CreateItem(itemId, stackCount); + + if (!newItem) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + if (it.stackable) { + subType -= stackCount; + } + + ReturnValue ret = g_game.internalAddItem(tile, newItem, INDEX_WHEREEVER, FLAG_NOLIMIT); + + if (ret != RET_NOERROR) { + delete newItem; + lua_pushboolean(L, false); + return 1; + } + + --itemCount; + + if (itemCount == 0) { + if (newItem->getParent()) { + uint32_t uid = env->addThing(newItem); + lua_pushnumber(L, uid); + return 1; + } else { + //stackable item stacked with existing object, newItem will be released + lua_pushboolean(L, false); + return 1; + } + } + } + + lua_pushboolean(L, false); + return 1; +} + +int32_t LuaScriptInterface::luaDoCreateItemEx(lua_State* L) +{ + //doCreateItemEx(itemid, count/subtype) + //Returns uid of the created item + + int32_t parameters = lua_gettop(L); + + uint32_t count = 1; + + if (parameters > 1) { + count = popNumber(L); + } + + uint32_t itemId = (uint32_t)popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + const ItemType& it = Item::items[itemId]; + + if (it.stackable && count > 100) { + reportErrorFunc("Stack count cannot be higher than 100."); + count = 100; + } + + Item* newItem = Item::CreateItem(itemId, count); + + if (!newItem) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + newItem->setParent(VirtualCylinder::virtualCylinder); + env->addTempItem(env, newItem); + + uint32_t uid = env->addThing(newItem); + lua_pushnumber(L, uid); + return 1; +} + +int32_t LuaScriptInterface::luaDoCreateTeleport(lua_State* L) +{ + //doCreateTeleport(itemid, topos, createpos) + PositionEx createPos; + popPosition(L, createPos); + PositionEx toPos; + popPosition(L, toPos); + uint32_t itemId = (uint32_t)popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Tile* tile = g_game.getMap()->getTile(createPos); + + if (!tile) { + std::ostringstream ss; + ss << createPos << " " << getErrorDesc(LUA_ERROR_TILE_NOT_FOUND); + reportErrorFunc(ss.str().c_str()); + lua_pushboolean(L, false); + return 1; + } + + Item* newItem = Item::CreateItem(itemId); + + if (!newItem) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + Teleport* newTeleport = newItem->getTeleport(); + + if (!newTeleport) { + delete newItem; + reportErrorFunc("Invalid teleport ItemID."); + lua_pushboolean(L, false); + return 1; + } + + newTeleport->setDestPos(toPos); + + ReturnValue ret = g_game.internalAddItem(tile, newTeleport, INDEX_WHEREEVER, FLAG_NOLIMIT); + + if (ret != RET_NOERROR) { + delete newItem; + reportErrorFunc("Can not add Item"); + lua_pushboolean(L, false); + return 1; + } + + if (newItem->getParent()) { + uint32_t uid = env->addThing(newItem); + lua_pushnumber(L, uid); + } else { + lua_pushboolean(L, false); //stackable item stacked with existing object, newItem will be released + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetPlayerStorageValue(lua_State* L) +{ + //getPlayerStorageValue(cid, valueid) + uint32_t key = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + const Player* player = env->getPlayerByUID(cid); + + if (player) { + int32_t value; + + if (player->getStorageValue(key, value)) { + lua_pushnumber(L, value); + } else { + lua_pushnumber(L, -1); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaSetPlayerStorageValue(lua_State* L) +{ + //setPlayerStorageValue(cid, valueid, newvalue) + int32_t value = (int32_t)popNumber(L); + uint32_t key = popNumber(L); + uint32_t cid = popNumber(L); + + if (IS_IN_KEYRANGE(key, RESERVED_RANGE)) { + std::ostringstream ss; + ss << "Accessing reserved range: " << key; + reportErrorFunc(ss.str()); + lua_pushboolean(L, false); + return 1; + } + + ScriptEnvironment* env = getScriptEnv(); + + Player* player = env->getPlayerByUID(cid); + + if (player) { + player->addStorageValue(key, value); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoSetItemActionId(lua_State* L) +{ + //doSetItemActionId(uid, actionid) + uint32_t actionid = popNumber(L); + uint32_t uid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Item* item = env->getItemByUID(uid); + + if (item) { + item->setActionId(actionid); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoSetItemText(lua_State* L) +{ + //doSetItemText(uid, text) + std::string text = popString(L); + uint32_t uid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Item* item = env->getItemByUID(uid); + + if (item) { + std::string str(text); + item->setText(str); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoSetItemSpecialDescription(lua_State* L) +{ + //doSetItemSpecialDescription(uid, desc) + std::string desc = popString(L); + uint32_t uid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Item* item = env->getItemByUID(uid); + + if (item) { + if (desc == "") { + item->resetSpecialDescription(); + } else { + item->setSpecialDescription(desc); + } + + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetTilePzInfo(lua_State* L) +{ + //getTilePzInfo(pos) + PositionEx pos; + popPosition(L, pos); + + Tile* tile = g_game.getMap()->getTile(pos); + + if (tile) { + if (tile->hasFlag(TILESTATE_PROTECTIONZONE)) { + lua_pushboolean(L, true); + } else { + lua_pushboolean(L, false); + } + } else { + std::ostringstream ss; + ss << pos << " " << getErrorDesc(LUA_ERROR_TILE_NOT_FOUND); + reportErrorFunc(ss.str().c_str()); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetTileHouseInfo(lua_State* L) +{ + //getTileHouseInfo(pos) + PositionEx pos; + popPosition(L, pos); + + Tile* tile = g_game.getMap()->getTile(pos); + + if (tile) { + if (HouseTile* houseTile = dynamic_cast(tile)) { + House* house = houseTile->getHouse(); + + if (house) { + lua_pushnumber(L, house->getHouseId()); + } else { + lua_pushboolean(L, false); + } + } else { + lua_pushboolean(L, false); + } + } else { + std::ostringstream ss; + ss << pos << " " << getErrorDesc(LUA_ERROR_TILE_NOT_FOUND); + reportErrorFunc(ss.str().c_str()); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoSummonCreature(lua_State* L) +{ + //doSummonCreature(name, pos) + PositionEx pos; + popPosition(L, pos); + std::string name = popString(L); + + ScriptEnvironment* env = getScriptEnv(); + + Monster* monster = Monster::createMonster(name); + + if (!monster) { + std::string error_str = (std::string)"Monster name(" + name + (std::string)") not found"; + reportErrorFunc(error_str); + lua_pushboolean(L, false); + return 1; + } + + if (!g_game.placeCreature(monster, pos)) { + delete monster; + lua_pushboolean(L, false); + return 1; + } + + uint32_t cid = env->addThing(monster); + lua_pushnumber(L, cid); + return 1; +} + +int32_t LuaScriptInterface::luaDoRemoveCreature(lua_State* L) +{ + //doRemoveCreature(cid) + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (creature) { + Player* player = creature->getPlayer(); + + if (player) { + player->kickPlayer(true); //Players will get kicked without restrictions + } else { + g_game.removeCreature(creature); //Monsters/NPCs will get removed + } + + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerRemoveMoney(lua_State* L) +{ + //doPlayerRemoveMoney(cid,money) + uint32_t money = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Player* player = env->getPlayerByUID(cid); + + if (player) { + if (g_game.removeMoney(player, money)) { + lua_pushboolean(L, true); + } else { + lua_pushboolean(L, false); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerAddMoney(lua_State* L) +{ + //doPlayerAddMoney(cid,money) + uint32_t money = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Player* player = env->getPlayerByUID(cid); + + if (player) { + g_game.addMoney(player, money); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoCreatureSetLookDir(lua_State* L) +{ + //doCreatureSetLookDir(cid, dir) + Direction dir = (Direction)popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (creature) { + g_game.internalCreatureTurn(creature, dir); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerSetTown(lua_State* L) +{ + //doPlayerSetTown(cid, townid) + uint32_t townid = (uint32_t)popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Player* player = env->getPlayerByUID(cid); + + if (player) { + Town* town = Towns::getInstance().getTown(townid); + + if (town) { + player->masterPos = town->getTemplePosition(); + player->setTown(townid); + lua_pushboolean(L, true); + } else { + reportErrorFunc("Not found townid"); + lua_pushboolean(L, false); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerSetVocation(lua_State* L) +{ + //doPlayerSetVocation(cid,voc) + uint32_t voc = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Player* player = env->getPlayerByUID(cid); + + if (player) { + player->setVocation(voc); + uint32_t promotedVocation = g_vocations.getPromotedVocation(player->getVocationId()); + + if (promotedVocation == 0 && player->getVocationId() != promotedVocation) { + player->addStorageValue(STORAGEVALUE_PROMOTION, 1); + } + + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerSetSex(lua_State* L) +{ + //doPlayerSetSex(cid,voc) + uint32_t newSex = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Player* player = env->getPlayerByUID(cid); + + if (player) { + player->setSex((PlayerSex_t)newSex); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerSetBankBalance(lua_State* L) +{ + //doPlayerSetBalance(cid, balance) + uint64_t balance = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Player* player = env->getPlayerByUID(cid); + + if (player) { + player->setBankBalance(balance); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDebugPrint(lua_State* L) +{ + //debugPrint(text) + std::string text = popString(L); + reportErrorFunc(text); + return 0; +} + +int32_t LuaScriptInterface::luaDoPlayerAddSoul(lua_State* L) +{ + //doPlayerAddSoul(cid,soul) + int32_t addsoul = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Player* player = env->getPlayerByUID(cid); + + if (player) { + player->changeSoul(addsoul); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetPlayerItemCount(lua_State* L) +{ + //getPlayerItemCount(cid, itemid[, subtype]) + int32_t subtype = -1; + + if (lua_gettop(L) > 2) { + subtype = popNumber(L); + } + + uint32_t itemId = (uint32_t)popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + const Player* player = env->getPlayerByUID(cid); + + if (player) { + uint32_t n = player->__getItemTypeCount(itemId, subtype); + lua_pushnumber(L, n); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetHouseOwner(lua_State* L) +{ + //getHouseOwner(houseid) + uint32_t houseid = popNumber(L); + + House* house = Houses::getInstance().getHouse(houseid); + + if (house) { + uint32_t owner = house->getHouseOwner(); + lua_pushnumber(L, owner); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_HOUSE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetHouseName(lua_State* L) +{ + //getHouseName(houseid) + uint32_t houseid = popNumber(L); + + House* house = Houses::getInstance().getHouse(houseid); + + if (house) { + lua_pushstring(L, house->getName().c_str()); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_HOUSE_NOT_FOUND)); + lua_pushnil(L); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetHouseEntry(lua_State* L) +{ + //getHouseEntry(houseid) + uint32_t houseid = popNumber(L); + + House* house = Houses::getInstance().getHouse(houseid); + + if (house) { + pushPosition(L, house->getEntryPosition(), 0); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_HOUSE_NOT_FOUND)); + lua_pushnil(L); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetHouseRent(lua_State* L) +{ + //getHouseRent(houseid) + uint32_t houseid = popNumber(L); + + House* house = Houses::getInstance().getHouse(houseid); + + if (house) { + lua_pushnumber(L, house->getRent()); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_HOUSE_NOT_FOUND)); + lua_pushnil(L); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetHouseTown(lua_State* L) +{ + //getHouseTown(houseid) + uint32_t houseid = popNumber(L); + + House* house = Houses::getInstance().getHouse(houseid); + + if (house) { + lua_pushnumber(L, house->getTownId()); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_HOUSE_NOT_FOUND)); + lua_pushnil(L); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetHouseAccessList(lua_State* L) +{ + //getHouseAccessList(houseid, listid) + uint32_t listid = popNumber(L); + uint32_t houseid = popNumber(L); + + House* house = Houses::getInstance().getHouse(houseid); + + if (house) { + std::string list; + + if (house->getAccessList(listid, list)) { + lua_pushstring(L, list.c_str()); + } else { + reportErrorFunc("No valid listid."); + lua_pushnil(L); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_HOUSE_NOT_FOUND)); + lua_pushnil(L); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetHouseByPlayerGUID(lua_State* L) +{ + //getHouseByPlayerGUID(playerGUID) + uint32_t guid = popNumber(L); + + House* house = Houses::getInstance().getHouseByPlayerId(guid); + + if (house) { + lua_pushnumber(L, house->getHouseId()); + } else { + lua_pushnil(L); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetHouseTilesSize(lua_State* L) +{ + //getHouseTilesSize(houseid) + uint32_t houseid = popNumber(L); + + House* house = Houses::getInstance().getHouse(houseid); + + if (house) { + lua_pushnumber(L, house->getHouseTileSize()); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_HOUSE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaSetHouseAccessList(lua_State* L) +{ + //setHouseAccessList(houseid, listid, listtext) + std::string list = popString(L); + uint32_t listid = popNumber(L); + uint32_t houseid = popNumber(L); + + House* house = Houses::getInstance().getHouse(houseid); + + if (house) { + house->setAccessList(listid, list); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_HOUSE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaSetHouseOwner(lua_State* L) +{ + //setHouseOwner(houseid, owner) + uint32_t owner = popNumber(L); + uint32_t houseid = popNumber(L); + + House* house = Houses::getInstance().getHouse(houseid); + + if (house) { + house->setHouseOwner(owner); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_HOUSE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetWorldType(lua_State* L) +{ + //getWorldType() + switch (g_game.getWorldType()) { + case WORLD_TYPE_NO_PVP: + lua_pushnumber(L, 1); + break; + case WORLD_TYPE_PVP: + lua_pushnumber(L, 2); + break; + case WORLD_TYPE_PVP_ENFORCED: + lua_pushnumber(L, 3); + break; + default: + lua_pushboolean(L, false); + break; + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetWorldTime(lua_State* L) +{ + //getWorldTime() + uint32_t time = g_game.getLightHour(); + lua_pushnumber(L, time); + return 1; +} + +int32_t LuaScriptInterface::luaGetWorldLight(lua_State* L) +{ + //getWorldLight() + LightInfo lightInfo; + g_game.getWorldLightInfo(lightInfo); + lua_pushnumber(L, lightInfo.level); + lua_pushnumber(L, lightInfo.color); + return 2; +} + +int32_t LuaScriptInterface::luaGetWorldCreatures(lua_State* L) +{ + //getWorldCreatures(type) + //0 players, 1 monsters, 2 npcs, 3 all + uint32_t type = popNumber(L); + uint32_t value; + + switch (type) { + case 0: + value = g_game.getPlayersOnline(); + break; + + case 1: + value = g_game.getMonstersOnline(); + break; + + case 2: + value = g_game.getNpcsOnline(); + break; + + case 3: + value = g_game.getCreaturesOnline(); + break; + + default: + reportErrorFunc("Wrong creature type."); + lua_pushboolean(L, false); + return 1; + } + + lua_pushnumber(L, value); + return 1; +} + +int32_t LuaScriptInterface::luaGetWorldUpTime(lua_State* L) +{ + //getWorldUpTime() + uint32_t uptime = 0; + Status* status = Status::getInstance(); + + if (status) { + uptime = status->getUptime(); + } + + lua_pushnumber(L, uptime); + return 1; +} + +int32_t LuaScriptInterface::luaBroadcastMessage(lua_State* L) +{ + //broadcastMessage(message, type) + uint32_t type = MSG_STATUS_WARNING; + int32_t parameters = lua_gettop(L); + + if (parameters >= 2) { + type = popNumber(L); + } + + std::string message = popString(L); + + if (g_game.broadcastMessage(message, (MessageClasses)type)) { + lua_pushboolean(L, true); + } else { + reportErrorFunc("Bad messageClass type."); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetPlayerLight(lua_State* L) +{ + //getPlayerLight(cid) + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + const Player* player = env->getPlayerByUID(cid); + + if (player) { + LightInfo lightInfo; + player->getCreatureLight(lightInfo); + lua_pushnumber(L, lightInfo.level); + lua_pushnumber(L, lightInfo.color);//color + return 2; + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } +} + +int32_t LuaScriptInterface::luaDoPlayerAddExp(lua_State* L) +{ + //doPlayerAddExp(cid,exp,usemultiplier,sendtext) + int32_t parameters = lua_gettop(L); + + bool sendText = false; + + if (parameters > 3) { + sendText = popBoolean(L); + } + + bool useMult = false; + + if (parameters > 2) { + useMult = popBoolean(L); + } + + uint64_t exp = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Player* player = env->getPlayerByUID(cid); + + if (player) { + if (exp > 0) { + player->addExperience(exp, useMult, sendText); + lua_pushboolean(L, true); + } else { + lua_pushboolean(L, false); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetPlayerSlotItem(lua_State* L) +{ + //getPlayerSlotItem(cid, slot) + uint32_t slot = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + const Player* player = env->getPlayerByUID(cid); + + if (player) { + Thing* thing = player->__getThing(slot); + + if (thing) { + uint32_t uid = env->addThing(thing); + pushThing(L, thing, uid); + } else { + pushThing(L, NULL, 0); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushThing(L, NULL, 0); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetPlayerItemById(lua_State* L) +{ + //getPlayerItemById(cid, deepSearch, itemId, subType) + ScriptEnvironment* env = getScriptEnv(); + + uint32_t parameters = lua_gettop(L); + + int32_t subType = -1; + + if (parameters > 3) { + subType = (int32_t)popNumber(L); + } + + int32_t itemId = (int32_t)popNumber(L); + bool deepSearch = popBoolean(L); + uint32_t cid = popNumber(L); + + Player* player = env->getPlayerByUID(cid); + + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushThing(L, NULL, 0); + return 1; + } + + Item* item = g_game.findItemOfType(player, itemId, deepSearch, subType); + + if (!item) { + pushThing(L, NULL, 0); + return 1; + } + + uint32_t uid = env->addThing(item); + pushThing(L, item, uid); + return 1; +} + +int32_t LuaScriptInterface::luaGetThing(lua_State* L) +{ + //getThing(uid) + uint32_t uid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Thing* thing = env->getThingByUID(uid); + + if (thing) { + pushThing(L, thing, uid); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_THING_NOT_FOUND)); + pushThing(L, NULL, 0); + } + + return 1; +} + +int32_t LuaScriptInterface::luaQueryTileAddThing(lua_State* L) +{ + //queryTileAddThing(uid, pos, flags) + int32_t parameters = lua_gettop(L); + + uint32_t flags = 0; + + if (parameters > 2) { + flags = popNumber(L); + } + + PositionEx pos; + popPosition(L, pos); + uint32_t uid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Tile* tile = g_game.getTile(pos.x, pos.y, pos.z); + + if (!tile) { + std::ostringstream ss; + ss << pos << " " << getErrorDesc(LUA_ERROR_TILE_NOT_FOUND); + reportErrorFunc(ss.str().c_str()); + lua_pushnumber(L, (uint32_t)RET_NOTPOSSIBLE); + return 1; + } + + Thing* thing = env->getThingByUID(uid); + + if (!thing) { + reportErrorFunc(getErrorDesc(LUA_ERROR_THING_NOT_FOUND)); + lua_pushnumber(L, (uint32_t)RET_NOTPOSSIBLE); + return 1; + } + + ReturnValue ret = tile->__queryAdd(0, thing, 1, flags); + lua_pushnumber(L, (uint32_t)ret); + return 1; +} + +int32_t LuaScriptInterface::luaGetThingPos(lua_State* L) +{ + //getThingPos(uid) + uint32_t uid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Thing* thing = env->getThingByUID(uid); + Position pos(0, 0, 0); + uint32_t stackpos = 0; + + if (thing) { + pos = thing->getPosition(); + + if (Tile* tile = thing->getTile()) { + stackpos = tile->__getIndexOfThing(thing); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_THING_NOT_FOUND)); + } + + pushPosition(L, pos, stackpos); + return 1; +} + +int32_t LuaScriptInterface::luaCreateCombatObject(lua_State* L) +{ + //createCombatObject() + ScriptEnvironment* env = getScriptEnv(); + + if (env->getScriptId() != EVENT_ID_LOADING) { + reportErrorFunc("This function can only be used while loading the script."); + lua_pushboolean(L, false); + return 1; + } + + Combat* combat = new Combat; + + if (!combat) { + reportErrorFunc(getErrorDesc(LUA_ERROR_COMBAT_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + uint32_t newCombatId = env->addCombatObject(combat); + lua_pushnumber(L, newCombatId); + return 1; +} + +bool LuaScriptInterface::getArea(lua_State* L, std::list& list, uint32_t& rows) +{ + rows = 0; + uint32_t i = 0, j = 0; + + lua_pushnil(L); // first key // + + while (lua_next(L, -2) != 0) { + if (lua_istable(L, -1) == 0) { + return false; + } + + lua_pushnil(L); + + while (lua_next(L, -2) != 0) { + if (lua_isnumber(L, -1) == 0) { + return false; + } + + list.push_back((uint32_t)lua_tonumber(L, -1)); + + lua_pop(L, 1); // removes `value'; keeps `key' for next iteration // + j++; + } + + ++rows; + + j = 0; + lua_pop(L, 1); // removes `value'; keeps `key' for next iteration // + i++; + } + + lua_pop(L, 1); + return (rows != 0); +} + +int32_t LuaScriptInterface::luaCreateCombatArea(lua_State* L) +{ + //createCombatArea( {area}, {extArea} ) + ScriptEnvironment* env = getScriptEnv(); + + if (env->getScriptId() != EVENT_ID_LOADING) { + reportErrorFunc("This function can only be used while loading the script."); + lua_pushboolean(L, false); + return 1; + } + + int32_t parameters = lua_gettop(L); + + AreaCombat* area = new AreaCombat; + + if (parameters > 1) { + //has extra parameter with diagonal area information + + uint32_t rowsExtArea; + std::list listExtArea; + + if (!getArea(L, listExtArea, rowsExtArea)) { + reportErrorFunc("Invalid extended area table."); + lua_pushboolean(L, false); + return 1; + } + + /*setup all possible rotations*/ + area->setupExtArea(listExtArea, rowsExtArea); + } + + uint32_t rowsArea = 0; + std::list listArea; + + if (!getArea(L, listArea, rowsArea)) { + reportErrorFunc("Invalid area table."); + lua_pushboolean(L, false); + return 1; + } + + /*setup all possible rotations*/ + area->setupArea(listArea, rowsArea); + uint32_t newAreaId = env->addCombatArea(area); + + lua_pushnumber(L, newAreaId); + return 1; +} + +int32_t LuaScriptInterface::luaCreateConditionObject(lua_State* L) +{ + //createConditionObject(type) + ScriptEnvironment* env = getScriptEnv(); + + if (env->getScriptId() != EVENT_ID_LOADING) { + reportErrorFunc("This function can only be used while loading the script."); + lua_pushboolean(L, false); + return 1; + } + + ConditionType_t type = (ConditionType_t)popNumber(L); + + Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, type, 0, 0); + + if (condition) { + uint32_t newConditionId = env->addConditionObject(condition); + lua_pushnumber(L, newConditionId); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CONDITION_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaSetCombatArea(lua_State* L) +{ + //setCombatArea(combat, area) + uint32_t areaId = popNumber(L); + uint32_t combatId = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + if (env->getScriptId() != EVENT_ID_LOADING) { + reportErrorFunc("This function can only be used while loading the script."); + lua_pushboolean(L, false); + return 1; + } + + Combat* combat = env->getCombatObject(combatId); + + if (!combat) { + reportErrorFunc(getErrorDesc(LUA_ERROR_COMBAT_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + const AreaCombat* area = env->getCombatArea(areaId); + + if (!area) { + reportErrorFunc(getErrorDesc(LUA_ERROR_AREA_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + combat->setArea(new AreaCombat(*area)); + + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaSetCombatCondition(lua_State* L) +{ + //setCombatCondition(combat, condition) + uint32_t conditionId = popNumber(L); + uint32_t combatId = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + if (env->getScriptId() != EVENT_ID_LOADING) { + reportErrorFunc("This function can only be used while loading the script."); + lua_pushboolean(L, false); + return 1; + } + + Combat* combat = env->getCombatObject(combatId); + + if (!combat) { + reportErrorFunc(getErrorDesc(LUA_ERROR_COMBAT_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + const Condition* condition = env->getConditionObject(conditionId); + + if (!condition) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CONDITION_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + combat->setCondition(condition->clone()); + + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaSetCombatParam(lua_State* L) +{ + //setCombatParam(combat, key, value) + uint32_t value = popNumber(L); + CombatParam_t key = (CombatParam_t)popNumber(L); + uint32_t combatId = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + if (env->getScriptId() != EVENT_ID_LOADING) { + reportErrorFunc("This function can only be used while loading the script."); + lua_pushboolean(L, false); + return 1; + } + + Combat* combat = env->getCombatObject(combatId); + + if (combat) { + combat->setParam(key, value); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_COMBAT_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaSetConditionParam(lua_State* L) +{ + //setConditionParam(condition, key, value) + int32_t value = (int32_t)popNumber(L); + ConditionParam_t key = (ConditionParam_t)popNumber(L); + uint32_t conditionId = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + if (env->getScriptId() != EVENT_ID_LOADING) { + reportErrorFunc("This function can only be used while loading the script."); + lua_pushboolean(L, false); + return 1; + } + + Condition* condition = env->getConditionObject(conditionId); + + if (condition) { + condition->setParam(key, value); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CONDITION_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaAddDamageCondition(lua_State* L) +{ + //addDamageCondition(condition, rounds, time, value) + int32_t value = (int32_t)popNumber(L); + int32_t time = (int32_t)popNumber(L); + int32_t rounds = (int32_t)popNumber(L); + uint32_t conditionId = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + if (env->getScriptId() != EVENT_ID_LOADING) { + reportErrorFunc("This function can only be used while loading the script."); + lua_pushboolean(L, false); + return 1; + } + + ConditionDamage* condition = dynamic_cast(env->getConditionObject(conditionId)); + + if (condition) { + condition->addDamage(rounds, time, value); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CONDITION_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaAddOutfitCondition(lua_State* L) +{ + //addOutfitCondition(condition, lookTypeEx, lookType, lookHead, lookBody, lookLegs, lookFeet) + Outfit_t outfit; + outfit.lookFeet = popNumber(L); + outfit.lookLegs = popNumber(L); + outfit.lookBody = popNumber(L); + outfit.lookHead = popNumber(L); + outfit.lookType = popNumber(L); + outfit.lookTypeEx = popNumber(L); + uint32_t conditionId = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + if (env->getScriptId() != EVENT_ID_LOADING) { + reportErrorFunc("This function can only be used while loading the script."); + lua_pushboolean(L, false); + return 1; + } + + ConditionOutfit* condition = dynamic_cast(env->getConditionObject(conditionId)); + + if (condition) { + condition->addOutfit(outfit); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CONDITION_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaSetCombatCallBack(lua_State* L) +{ + //setCombatCallBack(combat, key, function_name) + std::string function = popString(L); + CallBackParam_t key = (CallBackParam_t)popNumber(L); + uint32_t combatId = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + if (env->getScriptId() != EVENT_ID_LOADING) { + reportErrorFunc("This function can only be used while loading the script."); + lua_pushboolean(L, false); + return 1; + } + + Combat* combat = env->getCombatObject(combatId); + + if (!combat) { + reportErrorFunc(getErrorDesc(LUA_ERROR_COMBAT_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + LuaScriptInterface* scriptInterface = env->getScriptInterface(); + + combat->setCallback(key); + CallBack* callback = combat->getCallback(key); + + if (!callback) { + std::ostringstream ss; + ss << key << " is not a valid callback key."; + reportErrorFunc(ss.str()); + lua_pushboolean(L, false); + return 1; + } + + if (!callback->loadCallBack(scriptInterface, function)) { + reportErrorFunc("Can not load callback"); + lua_pushboolean(L, false); + return 1; + } + + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaSetCombatFormula(lua_State* L) +{ + //setCombatFormula(combat, type, mina, minb, maxa, maxb) + ScriptEnvironment* env = getScriptEnv(); + + if (env->getScriptId() != EVENT_ID_LOADING) { + reportErrorFunc("This function can only be used while loading the script."); + lua_pushboolean(L, false); + return 1; + } + + float maxb = (float)popFloatNumber(L); + float maxa = (float)popFloatNumber(L); + float minb = (float)popFloatNumber(L); + float mina = (float)popFloatNumber(L); + + formulaType_t type = (formulaType_t)popNumber(L); + uint32_t combatId = popNumber(L); + + Combat* combat = env->getCombatObject(combatId); + + if (combat) { + combat->setPlayerCombatValues(type, mina, minb, maxa, maxb); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_COMBAT_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaSetConditionFormula(lua_State* L) +{ + //setConditionFormula(condition, mina, minb, maxa, maxb) + ScriptEnvironment* env = getScriptEnv(); + + if (env->getScriptId() != EVENT_ID_LOADING) { + reportErrorFunc("This function can only be used while loading the script."); + lua_pushboolean(L, false); + return 1; + } + + double maxb = popFloatNumber(L); + double maxa = popFloatNumber(L); + double minb = popFloatNumber(L); + double mina = popFloatNumber(L); + + uint32_t conditionId = popNumber(L); + + ConditionSpeed* condition = dynamic_cast(env->getConditionObject(conditionId)); + + if (condition) { + condition->setFormulaVars(mina, minb, maxa, maxb); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CONDITION_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoCombat(lua_State* L) +{ + //doCombat(cid, combat, param) + ScriptEnvironment* env = getScriptEnv(); + + LuaVariant var = popVariant(L); + uint32_t combatId = (uint32_t)popNumber(L); + uint32_t cid = (uint32_t)popNumber(L); + + Creature* creature = NULL; + + if (cid != 0) { + creature = env->getCreatureByUID(cid); + + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + } + + const Combat* combat = env->getCombatObject(combatId); + + if (!combat) { + reportErrorFunc(getErrorDesc(LUA_ERROR_COMBAT_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + if (var.type == VARIANT_NONE) { + reportErrorFunc(getErrorDesc(LUA_ERROR_VARIANT_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + switch (var.type) { + case VARIANT_NUMBER: { + Creature* target = g_game.getCreatureByID(var.number); + + if (!target) { + lua_pushboolean(L, false); + return 1; + } + + if (combat->hasArea()) { + combat->doCombat(creature, target->getPosition()); + //std::cout << "Combat->hasArea()" << std::endl; + } else { + combat->doCombat(creature, target); + } + + break; + } + + case VARIANT_POSITION: { + combat->doCombat(creature, var.pos); + break; + } + + case VARIANT_TARGETPOSITION: { + if (combat->hasArea()) { + combat->doCombat(creature, var.pos); + } else { + combat->postCombatEffects(creature, var.pos); + g_game.addMagicEffect(var.pos, NM_ME_POFF); + } + + break; + } + + case VARIANT_STRING: { + Player* target = g_game.getPlayerByName(var.text); + + if (!target) { + lua_pushboolean(L, false); + return 1; + } + + combat->doCombat(creature, target); + break; + } + + default: { + reportErrorFunc(getErrorDesc(LUA_ERROR_VARIANT_UNKNOWN)); + lua_pushboolean(L, false); + return 1; + } + } + + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaDoAreaCombatHealth(lua_State* L) +{ + //doAreaCombatHealth(cid, type, pos, area, min, max, effect) + uint8_t effect = (uint8_t)popNumber(L); + int32_t maxChange = (int32_t)popNumber(L); + int32_t minChange = (int32_t)popNumber(L); + uint32_t areaId = popNumber(L); + + PositionEx pos; + popPosition(L, pos); + + CombatType_t combatType = (CombatType_t)popNumber(L); + uint32_t cid = (uint32_t)popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = NULL; + + if (cid != 0) { + creature = env->getCreatureByUID(cid); + + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + } + + const AreaCombat* area = env->getCombatArea(areaId); + + if (area || areaId == 0) { + CombatParams params; + params.combatType = combatType; + params.impactEffect = effect; + Combat::doCombatHealth(creature, pos, area, minChange, maxChange, params); + + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_AREA_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoTargetCombatHealth(lua_State* L) +{ + //doTargetCombatHealth(cid, target, type, min, max, effect) + uint8_t effect = (uint8_t)popNumber(L); + int32_t maxChange = (int32_t)popNumber(L); + int32_t minChange = (int32_t)popNumber(L); + CombatType_t combatType = (CombatType_t)popNumber(L); + uint32_t targetCid = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = NULL; + + if (cid != 0) { + creature = env->getCreatureByUID(cid); + + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + } + + Creature* target = env->getCreatureByUID(targetCid); + + if (target) { + CombatParams params; + params.combatType = combatType; + params.impactEffect = effect; + Combat::doCombatHealth(creature, target, minChange, maxChange, params); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoAreaCombatMana(lua_State* L) +{ + //doAreaCombatMana(cid, pos, area, min, max, effect) + uint8_t effect = (uint8_t)popNumber(L); + int32_t maxChange = (int32_t)popNumber(L); + int32_t minChange = (int32_t)popNumber(L); + uint32_t areaId = popNumber(L); + + PositionEx pos; + popPosition(L, pos); + + uint32_t cid = (uint32_t)popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = NULL; + + if (cid != 0) { + creature = env->getCreatureByUID(cid); + + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + } + + const AreaCombat* area = env->getCombatArea(areaId); + + if (area || areaId == 0) { + CombatParams params; + params.impactEffect = effect; + Combat::doCombatMana(creature, pos, area, minChange, maxChange, params); + + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_AREA_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoTargetCombatMana(lua_State* L) +{ + //doTargetCombatMana(cid, target, min, max, effect) + uint8_t effect = (uint8_t)popNumber(L); + int32_t maxChange = (int32_t)popNumber(L); + int32_t minChange = (int32_t)popNumber(L); + uint32_t targetCid = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = NULL; + + if (cid != 0) { + creature = env->getCreatureByUID(cid); + + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + } + + Creature* target = env->getCreatureByUID(targetCid); + + if (target) { + CombatParams params; + params.impactEffect = effect; + Combat::doCombatMana(creature, target, minChange, maxChange, params); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoAreaCombatCondition(lua_State* L) +{ + //doAreaCombatCondition(cid, pos, area, condition, effect) + uint8_t effect = (uint8_t)popNumber(L); + uint32_t conditionId = popNumber(L); + uint32_t areaId = popNumber(L); + PositionEx pos; + popPosition(L, pos); + uint32_t cid = (uint32_t)popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = NULL; + + if (cid != 0) { + creature = env->getCreatureByUID(cid); + + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + } + + const Condition* condition = env->getConditionObject(conditionId); + + if (condition) { + const AreaCombat* area = env->getCombatArea(areaId); + + if (area || areaId == 0) { + CombatParams params; + params.impactEffect = effect; + params.conditionList.push_back(condition); + Combat::doCombatCondition(creature, pos, area, params); + + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_AREA_NOT_FOUND)); + lua_pushboolean(L, false); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CONDITION_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoTargetCombatCondition(lua_State* L) +{ + //doTargetCombatCondition(cid, target, condition, effect) + uint8_t effect = (uint8_t)popNumber(L); + uint32_t conditionId = popNumber(L); + uint32_t targetCid = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = NULL; + + if (cid != 0) { + creature = env->getCreatureByUID(cid); + + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + } + + Creature* target = env->getCreatureByUID(targetCid); + + if (target) { + const Condition* condition = env->getConditionObject(conditionId); + + if (condition) { + CombatParams params; + params.impactEffect = effect; + params.conditionList.push_back(condition); + Combat::doCombatCondition(creature, target, params); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CONDITION_NOT_FOUND)); + lua_pushboolean(L, false); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoAreaCombatDispel(lua_State* L) +{ + //doAreaCombatDispel(cid, pos, area, type, effect) + uint8_t effect = (uint8_t)popNumber(L); + ConditionType_t dispelType = (ConditionType_t)popNumber(L); + uint32_t areaId = popNumber(L); + PositionEx pos; + popPosition(L, pos); + uint32_t cid = (uint32_t)popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = NULL; + + if (cid != 0) { + creature = env->getCreatureByUID(cid); + + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + } + + const AreaCombat* area = env->getCombatArea(areaId); + + if (area || areaId == 0) { + CombatParams params; + params.impactEffect = effect; + params.dispelType = dispelType; + Combat::doCombatDispel(creature, pos, area, params); + + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_AREA_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoTargetCombatDispel(lua_State* L) +{ + //doTargetCombatDispel(cid, target, type, effect) + uint8_t effect = (uint8_t)popNumber(L); + ConditionType_t dispelType = (ConditionType_t)popNumber(L); + uint32_t targetCid = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = NULL; + + if (cid != 0) { + creature = env->getCreatureByUID(cid); + + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + } + + Creature* target = env->getCreatureByUID(targetCid); + + if (target) { + CombatParams params; + params.impactEffect = effect; + params.dispelType = dispelType; + Combat::doCombatDispel(creature, target, params); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoChallengeCreature(lua_State* L) +{ + //doChallengeCreature(cid, target) + uint32_t targetCid = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + Creature* target = env->getCreatureByUID(targetCid); + + if (!target) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + target->challengeCreature(creature); + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaDoConvinceCreature(lua_State* L) +{ + //doConvinceCreature(cid, target) + uint32_t targetCid = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + Creature* target = env->getCreatureByUID(targetCid); + + if (!target) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + target->convinceCreature(creature); + g_game.updateCreatureType(target); + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaGetMonsterTargetList(lua_State* L) +{ + //getMonsterTargetList(cid) + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + Monster* monster = creature->getMonster(); + + if (!monster) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + lua_newtable(L); + uint32_t i = 0; + const CreatureList& targetList = monster->getTargetList(); + + for (CreatureList::const_iterator it = targetList.begin(); it != targetList.end(); ++it) { + if (monster->isTarget(*it)) { + uint32_t targetCid = env->addThing(*it); + lua_pushnumber(L, i); + lua_pushnumber(L, targetCid); + lua_settable(L, -3); + ++i; + } + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetMonsterFriendList(lua_State* L) +{ + //getMonsterFriendList(cid) + + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + Monster* monster = creature->getMonster(); + + if (!monster) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + lua_newtable(L); + uint32_t i = 0; + Creature* friendCreature; + const CreatureHashSet& friendList = monster->getFriendList(); + + for (CreatureHashSet::const_iterator it = friendList.begin(); it != friendList.end(); ++it) { + friendCreature = *it; + + if (!friendCreature->isRemoved() && friendCreature->getPosition().z == monster->getPosition().z) { + uint32_t friendCid = env->addThing(*it); + lua_pushnumber(L, i); + lua_pushnumber(L, friendCid); + lua_settable(L, -3); + ++i; + } + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoSetMonsterTarget(lua_State* L) +{ + //doSetMonsterTarget(cid, target) + uint32_t targetCid = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + Monster* monster = creature->getMonster(); + + if (!monster) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + Creature* target = env->getCreatureByUID(targetCid); + + if (!target) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + if (!monster->isSummon()) { + monster->selectTarget(target); + } + + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaDoMonsterChangeTarget(lua_State* L) +{ + //doMonsterChangeTarget(cid) + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + Monster* monster = creature->getMonster(); + + if (!monster) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + if (!monster->isSummon()) { + monster->searchTarget(TARGETSEARCH_RANDOM); + } + + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaDoAddCondition(lua_State* L) +{ + //doAddCondition(cid, condition) + + uint32_t conditionId = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + Condition* condition = env->getConditionObject(conditionId); + + if (!condition) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CONDITION_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + creature->addCondition(condition->clone()); + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaDoRemoveCondition(lua_State* L) +{ + //doRemoveCondition(cid, type) + ConditionType_t conditionType = (ConditionType_t)popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + Condition* condition = creature->getCondition(conditionType, CONDITIONID_COMBAT); + + if (!condition) { + condition = creature->getCondition(conditionType, CONDITIONID_DEFAULT); + } + + if (condition) { + creature->removeCondition(condition); + } + + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaNumberToVariant(lua_State* L) +{ + //numberToVariant(number) + LuaVariant var; + var.type = VARIANT_NUMBER; + var.number = popNumber(L); + + LuaScriptInterface::pushVariant(L, var); + return 1; +} + +int32_t LuaScriptInterface::luaStringToVariant(lua_State* L) +{ + //stringToVariant(string) + LuaVariant var; + var.type = VARIANT_STRING; + var.text = popString(L); + + LuaScriptInterface::pushVariant(L, var); + return 1; +} + +int32_t LuaScriptInterface::luaPositionToVariant(lua_State* L) +{ + //positionToVariant(pos) + LuaVariant var; + var.type = VARIANT_POSITION; + popPosition(L, var.pos); + + LuaScriptInterface::pushVariant(L, var); + return 1; +} + +int32_t LuaScriptInterface::luaTargetPositionToVariant(lua_State* L) +{ + //targetPositionToVariant(pos) + LuaVariant var; + var.type = VARIANT_TARGETPOSITION; + popPosition(L, var.pos); + + LuaScriptInterface::pushVariant(L, var); + return 1; +} + +int32_t LuaScriptInterface::luaVariantToNumber(lua_State* L) +{ + //variantToNumber(var) + LuaVariant var = popVariant(L); + + uint32_t number = 0; + + if (var.type == VARIANT_NUMBER) { + number = var.number; + } + + lua_pushnumber(L, number); + return 1; +} + +int32_t LuaScriptInterface::luaVariantToString(lua_State* L) +{ + //variantToString(var) + LuaVariant var = popVariant(L); + + std::string text = ""; + + if (var.type == VARIANT_STRING) { + text = var.text; + } + + lua_pushstring(L, text.c_str()); + return 1; +} + +int32_t LuaScriptInterface::luaVariantToPosition(lua_State* L) +{ + //luaVariantToPosition(var) + LuaVariant var = popVariant(L); + + PositionEx pos(0, 0, 0, 0); + + if (var.type == VARIANT_POSITION || var.type == VARIANT_TARGETPOSITION) { + pos = var.pos; + } + + pushPosition(L, pos, pos.stackpos); + return 1; +} + +int32_t LuaScriptInterface::luaDoChangeSpeed(lua_State* L) +{ + //doChangeSpeed(cid, delta) + int32_t delta = (int32_t)popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (creature) { + g_game.changeSpeed(creature, delta); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaSetCreatureOutfit(lua_State* L) +{ + //doSetCreatureOutfit(cid, outfit, time) + int32_t time = (int32_t)popNumber(L); + Outfit_t outfit; + outfit.lookType = getField(L, "lookType"); + outfit.lookHead = getField(L, "lookHead"); + outfit.lookBody = getField(L, "lookBody"); + outfit.lookLegs = getField(L, "lookLegs"); + outfit.lookFeet = getField(L, "lookFeet"); + outfit.lookAddons = getField(L, "lookAddons"); + lua_pop(L, 1); + + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (creature) { + ReturnValue ret = Spell::CreateIllusion(creature, outfit, time); + + if (ret == RET_NOERROR) { + lua_pushboolean(L, true); + } else { + lua_pushboolean(L, false); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetCreatureOutfit(lua_State* L) +{ + //getCreatureOutfit(cid) + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (creature) { + const Outfit_t outfit = creature->getCurrentOutfit(); + + lua_newtable(L); + setField(L, "lookType", outfit.lookType); + setField(L, "lookHead", outfit.lookHead); + setField(L, "lookBody", outfit.lookBody); + setField(L, "lookLegs", outfit.lookLegs); + setField(L, "lookFeet", outfit.lookFeet); + setField(L, "lookAddons", outfit.lookAddons); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaSetMonsterOutfit(lua_State* L) +{ + //doSetMonsterOutfit(cid, name, time) + int32_t time = (int32_t)popNumber(L); + std::string name = popString(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (creature) { + ReturnValue ret = Spell::CreateIllusion(creature, name, time); + + if (ret == RET_NOERROR) { + lua_pushboolean(L, true); + } else { + lua_pushboolean(L, false); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaSetItemOutfit(lua_State* L) +{ + //doSetItemOutfit(cid, item, time) + int32_t time = (int32_t)popNumber(L); + uint32_t item = (uint32_t)popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (creature) { + ReturnValue ret = Spell::CreateIllusion(creature, item, time); + + if (ret == RET_NOERROR) { + lua_pushboolean(L, true); + } else { + lua_pushboolean(L, false); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetGlobalStorageValue(lua_State* L) +{ + //getGlobalStorageValue(valueid) + uint32_t key = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + int32_t value; + + if (env->getGlobalStorageValue(key, value)) { + lua_pushnumber(L, value); + } else { + lua_pushnumber(L, -1); + } + + return 1; +} + +int32_t LuaScriptInterface::luaSetGlobalStorageValue(lua_State* L) +{ + //setGlobalStorageValue(valueid, newvalue) + int32_t value = (int32_t)popNumber(L); + uint32_t key = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + env->addGlobalStorageValue(key, value); + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaGetPlayerDepotItems(lua_State* L) +{ + //getPlayerDepotItems(cid, depotid) + uint32_t depotid = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Player* player = env->getPlayerByUID(cid); + + if (player) { + const DepotChest* depotChest = player->getDepotChest(depotid, true); + + if (depotChest) { + lua_pushnumber(L, depotChest->getItemHoldingCount()); + } else { + reportErrorFunc("Depot not found"); + lua_pushboolean(L, false); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerSetGuildLevel(lua_State* L) +{ + //doPlayerSetGuildLevel(cid, level) + uint8_t level = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Player* player = env->getPlayerByUID(cid); + + if (player) { + player->setGuildLevel(level); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerSetGuildNick(lua_State* L) +{ + //doPlayerSetGuildNick(cid, nick) + std::string nick = popString(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Player* player = env->getPlayerByUID(cid); + + if (player) { + player->setGuildNick(nick); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetGuildId(lua_State* L) +{ + //getGuildId(guild_name) + std::string name = popString(L); + + uint32_t guildId; + + if (IOGuild::getInstance()->getGuildIdByName(guildId, name)) { + lua_pushnumber(L, guildId); + } else { + reportErrorFunc("Guild not found"); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoMoveCreature(lua_State* L) +{ + //doMoveCreature(cid, direction) + uint32_t direction = popNumber(L); + uint32_t cid = popNumber(L); + + switch (direction) { + case NORTH: + case SOUTH: + case WEST: + case EAST: + case SOUTHWEST: + case NORTHWEST: + case NORTHEAST: + case SOUTHEAST: + break; + default: + reportErrorFunc("No valid direction"); + lua_pushboolean(L, false); + return 1; + } + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (creature) { + ReturnValue ret = g_game.internalMoveCreature(creature, (Direction)direction, FLAG_NOLIMIT); + lua_pushnumber(L, ret); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaIsValidUID(lua_State* L) +{ + //isValidUID(uid) + uint32_t uid = popNumber(L); + lua_pushboolean(L, getScriptEnv()->getThingByUID(uid) != NULL); + return 1; +} + +int32_t LuaScriptInterface::luaIsPlayer(lua_State* L) +{ + //isPlayer(cid) + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + if (env->getPlayerByUID(cid)) { + lua_pushboolean(L, true); + } else { + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaIsCreature(lua_State* L) +{ + //isCreature(cid) + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + if (env->getCreatureByUID(cid)) { + lua_pushboolean(L, true); + } else { + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaIsMonster(lua_State* L) +{ + //isMonster(cid) + uint32_t cid = popNumber(L); + lua_pushboolean(L, getScriptEnv()->getMonsterByUID(cid) != NULL); + return 1; +} + +int32_t LuaScriptInterface::luaIsNpc(lua_State* L) +{ + //isNpc(cid) + uint32_t cid = popNumber(L); + lua_pushboolean(L, getScriptEnv()->getNpcByUID(cid) != NULL); + return 1; +} + +int32_t LuaScriptInterface::luaIsItem(lua_State* L) +{ + //isItem(uid) + uint32_t uid = popNumber(L); + lua_pushboolean(L, getScriptEnv()->getItemByUID(uid) != NULL); + return 1; +} + +int32_t LuaScriptInterface::luaIsContainer(lua_State* L) +{ + //isContainer(uid) + uint32_t uid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + if (env->getContainerByUID(uid)) { + lua_pushboolean(L, true); + } else { + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaIsDepot(lua_State* L) +{ + //isDepot(uid) + uint32_t uid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Container* container = env->getContainerByUID(uid); + + if (container && container->getDepotLocker()) { + lua_pushboolean(L, true); + return 1; + } + + lua_pushboolean(L, false); + return 1; +} + +int32_t LuaScriptInterface::luaIsCorpse(lua_State* L) +{ + //isCorpse(uid) + uint32_t uid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Item* item = env->getItemByUID(uid); + + if (item) { + const ItemType& it = Item::items[item->getID()]; + lua_pushboolean(L, (it.corpseType != RACE_NONE ? true : false)); + } else { + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaIsMoveable(lua_State* L) +{ + //isMoveable(uid) + //isMovable(uid) + uint32_t uid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Thing* thing = env->getThingByUID(uid); + + if (thing && thing->isPushable()) { + lua_pushboolean(L, true); + } else { + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetPlayerByName(lua_State* L) +{ + //getPlayerByName(name) + std::string name = popString(L); + + ScriptEnvironment* env = getScriptEnv(); + + if (Player* player = g_game.getPlayerByName(name)) { + uint32_t cid = env->addThing(player); + lua_pushnumber(L, cid); + } else { + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetPlayersByAccountNumber(lua_State* L) +{ + //getPlayersByAccountNumber(accountNumber) + uint32_t accno = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + lua_newtable(L); + const PlayerVector& players = g_game.getPlayersByAccount(accno); + int index = 0; + PlayerVector::const_iterator iter = players.begin(); + + while (iter != players.end()) { + uint32_t cid = env->addThing(*iter); + + lua_pushnumber(L, index); + lua_pushnumber(L, cid); + lua_settable(L, -3); + + ++iter, ++index; + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetIPByPlayerName(lua_State* L) +{ + //getIPByPlayerName(playerName) + std::string name = popString(L); + + if (Player* player = g_game.getPlayerByName(name)) { + lua_pushnumber(L, player->getIP()); + } else { + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetPlayersByIPAddress(lua_State* L) +{ + //getPlayersByIPAddress(ip[, mask]) + int parameters = lua_gettop(L); + + uint32_t mask = 0xFFFFFFFF; + + if (parameters > 1) { + mask = (uint32_t)popNumber(L); + } + + uint32_t ip = (uint32_t)popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + lua_newtable(L); + const PlayerVector& players = g_game.getPlayersByIP(ip, mask); + int index = 0; + PlayerVector::const_iterator iter = players.begin(); + + while (iter != players.end()) { + uint32_t cid = env->addThing(*iter); + + lua_pushnumber(L, index); + lua_pushnumber(L, cid); + lua_settable(L, -3); + + ++iter, ++index; + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetAccountNumberByPlayerName(lua_State* L) +{ + //getAccountNumberByPlayerName(name) + std::string name = popString(L); + + Player* player = g_game.getPlayerByName(name); + uint32_t value = 0; + + if (player) { + value = player->getAccount(); + } else { + value = IOLoginData::getInstance()->getAccountNumberByName(name); + } + + lua_pushnumber(L, value); + return 1; +} + +int32_t LuaScriptInterface::luaGetPlayerGUIDByName(lua_State* L) +{ + //getPlayerGUIDByName(name) + std::string name = popString(L); + + Player* player = g_game.getPlayerByName(name); + uint32_t value = 0; + + if (player) { + value = player->getGUID(); + } else { + uint32_t guid; + std::string strName(name); + + if (IOLoginData::getInstance()->getGuidByName(guid, strName)) { + value = guid; + } + } + + lua_pushnumber(L, value); + return 1; +} + +int32_t LuaScriptInterface::luaRegisterCreatureEvent(lua_State* L) +{ + //registerCreatureEvent(cid, name) + std::string name = popString(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (creature) { + creature->registerCreatureEvent(name); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetContainerSize(lua_State* L) +{ + //getContainerSize(uid) + uint32_t uid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + if (Container* container = env->getContainerByUID(uid)) { + lua_pushnumber(L, container->size()); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CONTAINER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetContainerCap(lua_State* L) +{ + //getContainerCap(uid) + uint32_t uid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + if (Container* container = env->getContainerByUID(uid)) { + lua_pushnumber(L, container->capacity()); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CONTAINER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetContainerCapById(lua_State* L) +{ + //getContainerCapById(itemid) + uint32_t itemId = popNumber(L); + + const ItemType& it = Item::items[itemId]; + + if (it.isContainer()) { + lua_pushnumber(L, it.maxItems); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CONTAINER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetContainerItem(lua_State* L) +{ + //getContainerItem(uid, slot) + uint32_t slot = popNumber(L); + uint32_t uid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + if (Container* container = env->getContainerByUID(uid)) { + Item* item = container->getItem(slot); + + if (item) { + uint32_t uid = env->addThing(item); + pushThing(L, item, uid); + } else { + pushThing(L, NULL, 0); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CONTAINER_NOT_FOUND)); + pushThing(L, NULL, 0); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoAddContainerItem(lua_State* L) +{ + //doAddContainerItem(uid, itemid, count/subtype) + int32_t parameters = lua_gettop(L); + + uint32_t count = 1; + + if (parameters > 2) { + count = popNumber(L); + } + + uint16_t itemId = (uint16_t)popNumber(L); + uint32_t uid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Container* container = env->getContainerByUID(uid); + + if (!container) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CONTAINER_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + const ItemType& it = Item::items[itemId]; + + int32_t itemCount = 1; + + int32_t subType = 1; + + if (it.hasSubType()) { + if (it.stackable) { + itemCount = (int32_t)std::ceil((float)count / 100); + } + + subType = count; + } else { + itemCount = std::max(1, count); + } + + while (itemCount > 0) { + int32_t stackCount = std::min(100, subType); + Item* newItem = Item::CreateItem(itemId, stackCount); + + if (!newItem) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + if (it.stackable) { + subType -= stackCount; + } + + ReturnValue ret = g_game.internalAddItem(container, newItem); + + if (ret != RET_NOERROR) { + delete newItem; + lua_pushboolean(L, false); + return 1; + } + + --itemCount; + + if (itemCount == 0) { + if (newItem->getParent()) { + uint32_t uid = env->addThing(newItem); + lua_pushnumber(L, uid); + return 1; + } else { + //stackable item stacked with existing object, newItem will be released + lua_pushboolean(L, false); + return 1; + } + } + } + + lua_pushboolean(L, false); + return 1; +} + +int32_t LuaScriptInterface::luaGetDepotId(lua_State* L) +{ + //getDepotId(uid) + uint32_t uid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Container* container = env->getContainerByUID(uid); + + if (container) { + DepotLocker* depotLocker = container->getDepotLocker(); + + if (depotLocker) { + lua_pushnumber(L, depotLocker->getDepotId()); + } else { + reportErrorFunc("Depot not found"); + lua_pushboolean(L, false); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CONTAINER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetFluidSourceType(lua_State* L) +{ + //getFluidSourceType(type) + uint32_t type = popNumber(L); + + const ItemType& it = Item::items[type]; + + if (it.id != 0) { + lua_pushnumber(L, it.fluidSource); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaIsInArray(lua_State* L) +{ + //isInArray(array, value) + int32_t value = (int32_t)popNumber(L); + + if (lua_istable(L, -1) == 0) { + lua_pop(L, 1); + lua_pushboolean(L, false); + return 1; + } + + int32_t i = 1; + + while (true) { + lua_pushnumber(L, i); + lua_gettable(L, -2); + + if (lua_isnil(L, -1) == 1) { + lua_pop(L, 2); + lua_pushboolean(L, false); + return 1; + } else if (lua_isnumber(L, -1) == 1) { + int32_t array_value = (int32_t)popNumber(L); + + if (array_value == value) { + lua_pop(L, 1); + lua_pushboolean(L, true); + return 1; + } + } else { + lua_pop(L, 2); + lua_pushboolean(L, false); + return 1; + } + + ++i; + } +} + +int32_t LuaScriptInterface::luaDoPlayerAddOutfit(lua_State* L) +{ + //doPlayerAddOutfit(cid, looktype, addon) + uint32_t addon = popNumber(L); + uint32_t looktype = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Player* player = env->getPlayerByUID(cid); + + if (player) { + player->addOutfit(looktype, addon); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerRemOutfit(lua_State* L) +{ + //doPlayerRemOutfit(cid, looktype, addon) + uint32_t addon = popNumber(L); + uint32_t looktype = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Player* player = env->getPlayerByUID(cid); + + if (player) { + player->remOutfit(looktype, addon); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerAddMount(lua_State* L) +{ + //doPlayerAddMount(cid, mountid) + uint8_t mountid = (uint8_t)popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Player* player = env->getPlayerByUID(cid); + + if (player) { + if (!player->tameMount(mountid)) { + reportErrorFunc("There is no mount with the specified id."); + lua_pushboolean(L, false); + } else { + lua_pushboolean(L, true); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerRemoveMount(lua_State* L) +{ + //doPlayerRemoveMount(cid, mountid) + uint8_t mountid = (uint8_t)popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Player* player = env->getPlayerByUID(cid); + + if (player) { + if (!player->untameMount(mountid)) { + reportErrorFunc("There is no mount with the specified id."); + lua_pushboolean(L, false); + } else { + lua_pushboolean(L, true); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetPlayerMount(lua_State* L) +{ + //getPlayerMount(cid, mountid) + uint8_t mountid = (uint8_t)popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Player* player = env->getPlayerByUID(cid); + + if (player) { + Mount* mount = Mounts::getInstance()->getMountByID(mountid); + + if (!mount) { + reportErrorFunc("There is no mount with the specified id."); + lua_pushboolean(L, false); + } else { + lua_pushboolean(L, mount->isTamed(player)); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaCanPlayerWearOutfit(lua_State* L) +{ + //canPlayerWearOutfit(cid, looktype, addon) + uint32_t addon = popNumber(L); + uint32_t looktype = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Player* player = env->getPlayerByUID(cid); + + if (player) { + lua_pushboolean(L, player->canWear(looktype, addon)); + return 1; + } + + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; +} + +int32_t LuaScriptInterface::luaDoCreatureChangeOutfit(lua_State* L) +{ + //doCreatureChangeOutfit(cid, outfit) + Outfit_t outfit; + outfit.lookType = getField(L, "lookType"); + outfit.lookHead = getField(L, "lookHead"); + outfit.lookBody = getField(L, "lookBody"); + outfit.lookLegs = getField(L, "lookLegs"); + outfit.lookFeet = getField(L, "lookFeet"); + outfit.lookAddons = getField(L, "lookAddons"); + lua_pop(L, 1); + + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Creature* creature = env->getCreatureByUID(cid); + + if (creature) { + creature->defaultOutfit = outfit; + g_game.internalCreatureChangeOutfit(creature, outfit); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoSetCreatureLight(lua_State* L) +{ + //doSetCreatureLight(cid, lightLevel, lightColor, time) + uint32_t time = popNumber(L); + uint8_t color = (uint8_t)popNumber(L); + uint8_t level = (uint8_t)popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Creature* creature = env->getCreatureByUID(cid); + + if (creature) { + Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_LIGHT, time, level | (color << 8)); + creature->addCondition(condition); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerPopupFYI(lua_State* L) +{ + //doPlayerPopupFYI(cid, message) + std::string message = popString(L); + uint32_t cid = (uint32_t)popNumber(L); + ScriptEnvironment* env = getScriptEnv(); + Player* player = env->getPlayerByUID(cid); + + if (player) { + player->sendFYIBox(message); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaMayNotMove(lua_State* L) +{ + //mayNotMove(cid, value) + bool boolValue = popBoolean(L); + uint32_t cid = (uint32_t)popNumber(L); + ScriptEnvironment* env = getScriptEnv(); + Player* player = env->getPlayerByUID(cid); + + if (player) { + player->mayNotMove = boolValue; + + if (player->mayNotMove) { + player->onWalkAborted(); + } + + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerAddPremiumDays(lua_State* L) +{ + //doPlayerAddPremiumDays(cid, days) + uint32_t days = popNumber(L); + uint32_t cid = popNumber(L); + ScriptEnvironment* env = getScriptEnv(); + + if (Player* player = env->getPlayerByUID(cid)) { + if (player->premiumDays != 65535) { + Account account = IOLoginData::getInstance()->loadAccount(player->getAccount()); + account.premiumDays = std::min(0xFFFE, account.premiumDays + days); + player->setPremiumDays(account.premiumDays); + IOLoginData::getInstance()->saveAccount(account); + } + + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerRemovePremiumDays(lua_State* L) +{ + //doPlayerRemovePremiumDays(cid, days) + int32_t days = popNumber(L); + uint32_t cid = popNumber(L); + ScriptEnvironment* env = getScriptEnv(); + + if (Player* player = env->getPlayerByUID(cid)) { + if (player->premiumDays != 65535) { + Account account = IOLoginData::getInstance()->loadAccount(player->getAccount()); + account.premiumDays = std::max(0, account.premiumDays - days); + player->setPremiumDays(account.premiumDays); + IOLoginData::getInstance()->saveAccount(account); + } + + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetItemName(lua_State* L) +{ + //getItemName(itemid) + uint32_t itemid = popNumber(L); + const ItemType& it = Item::items[itemid]; + lua_pushstring(L, it.name.c_str()); + return 1; +} + +int32_t LuaScriptInterface::luaGetItemDescriptions(lua_State* L) +{ + //getItemDescriptions(itemid) + //returns the name, the article and the plural name of the item + uint32_t itemid = popNumber(L); + const ItemType& it = Item::items[itemid]; + + lua_newtable(L); + setField(L, "name", it.name.c_str()); + setField(L, "article", it.article.c_str()); + setField(L, "plural", it.getPluralName().c_str()); + setField(L, "description", it.description.c_str()); + return 1; +} + +int32_t LuaScriptInterface::luaGetItemWeight(lua_State* L) +{ + //getItemWeight(itemid, count, precise) + int32_t parameters = lua_gettop(L); + + bool precise = true; + + if (parameters > 2) { + precise = popBoolean(L); + } + + int32_t count = 1; + + if (parameters > 1) { + count = popNumber(L); + } + + uint32_t itemid = popNumber(L); + + const ItemType& it = Item::items[itemid]; + double weight = it.weight * std::max(1, count); + + if (precise) { + std::ostringstream ws; + ws << std::fixed << std::setprecision(2) << weight; + weight = atof(ws.str().c_str()); + } + + lua_pushnumber(L, weight); + return 1; +} + +int32_t LuaScriptInterface::luaGetItemWeightByUID(lua_State* L) +{ + //getItemWeight(itemid[, precise = true]) + bool precise = true; + + if (lua_gettop(L) > 2) { + precise = popBoolean(L); + } + + ScriptEnvironment* env = getScriptEnv(); + Item* item = env->getItemByUID(popNumber(L)); + + if (!item) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + double weight = item->getWeight(); + + if (precise) { + std::ostringstream ws; + ws << std::fixed << std::setprecision(2) << weight; + weight = atof(ws.str().c_str()); + } + + lua_pushnumber(L, weight); + return 1; +} + +int32_t LuaScriptInterface::luaHasProperty(lua_State* L) +{ + //hasProperty(uid, prop) + uint32_t prop = popNumber(L); + uint32_t uid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Item* item = env->getItemByUID(uid); + + if (!item) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + //Check if the item is a tile, so we can get more accurate properties + bool hasProp = item->hasProperty((ITEMPROPERTY)prop); + const Tile* itemTile = item->getTile(); + + if (itemTile && itemTile->ground == item) { + hasProp = itemTile->hasProperty((ITEMPROPERTY)prop); + } + + lua_pushboolean(L, hasProp); + return 1; +} + +int32_t LuaScriptInterface::luaGetCreatureMaster(lua_State* L) +{ + //getCreatureMaster(cid) + //returns the creature's master or itself if the creature isn't a summon + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + Creature* master = creature->getMaster(); + + if (!master) { + lua_pushnumber(L, cid); + return 1; + } + + uint32_t masterId = env->addThing(master); + lua_pushnumber(L, masterId); + return 1; +} + +int32_t LuaScriptInterface::luaGetCreatureSummons(lua_State* L) +{ + //getCreatureSummons(cid) + //returns a table with all the summons of the creature + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + lua_newtable(L); + const std::list& summons = creature->getSummons(); + std::list::const_iterator it = summons.begin(); + + for (uint32_t i = 1; it != summons.end(); ++it, ++i) { + uint32_t summonCid = env->addThing(*it); + lua_pushnumber(L, i); + lua_pushnumber(L, summonCid); + lua_settable(L, -3); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetSpectators(lua_State* L) +{ + //getSpectators(centerPos, rangex, rangey, multifloor, onlyPlayers = false) + int32_t parameters = lua_gettop(L); + + bool onlyPlayers = false; + + if (parameters > 4) { + onlyPlayers = popBoolean(L); + } + + bool multifloor = popBoolean(L); + uint32_t rangey = popNumber(L); + uint32_t rangex = popNumber(L); + + PositionEx centerPos; + popPosition(L, centerPos); + + SpectatorVec list; + g_game.getSpectators(list, centerPos, multifloor, onlyPlayers, rangex, rangex, rangey, rangey); + + if (list.empty()) { + lua_pushnil(L); + return 1; + } + + lua_newtable(L); + SpectatorVec::const_iterator it = list.begin(); + + for (uint32_t i = 1; it != list.end(); ++it, ++i) { + lua_pushnumber(L, i); + lua_pushnumber(L, (*it)->getID()); + lua_settable(L, -3); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetItemIdByName(lua_State* L) +{ + //getItemIdByName(name) + std::string name = popString(L); + + int32_t itemid = Item::items.getItemIdByName(name); + + if (itemid == -1) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + lua_pushnumber(L, itemid); + return 1; +} + +int32_t LuaScriptInterface::luaGetTownTemplePosition(lua_State* L) +{ + //getTownTemplePosition(townId) + uint32_t townId = popNumber(L); + + if (Town* town = Towns::getInstance().getTown(townId)) { + pushPosition(L, town->getTemplePosition(), 255); + } else { + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetTownId(lua_State* L) +{ + //getTownId(townName) + std::string townName = popString(L); + + if (Town* town = Towns::getInstance().getTown(townName)) { + lua_pushnumber(L, town->getTownID()); + } else { + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetTownName(lua_State* L) +{ + //getTownName(townId) + uint32_t townId = popNumber(L); + + if (Town* town = Towns::getInstance().getTown(townId)) { + lua_pushstring(L, town->getName().c_str()); + } else { + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaIsSightClear(lua_State* L) +{ + //isSightClear(fromPos, toPos, floorCheck) + PositionEx fromPos, toPos; + bool floorCheck = popBoolean(L); + popPosition(L, toPos); + popPosition(L, fromPos); + + lua_pushboolean(L, g_game.isSightClear(fromPos, toPos, floorCheck)); + return 1; +} + +int32_t LuaScriptInterface::luaGetCreaturePosition(lua_State* L) +{ + //getCreaturePosition(cid) + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (creature) { + Position pos = creature->getPosition(); + uint32_t stackpos = 0; + + if (Tile* tile = creature->getTile()) { + stackpos = tile->__getIndexOfThing(creature); + } + + pushPosition(L, pos, stackpos); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetCreatureName(lua_State* L) +{ + //getCreatureName(cid) + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (creature) { + lua_pushstring(L, creature->getName().c_str()); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetCreatureSpeed(lua_State* L) +{ + //getCreatureSpeed(cid) + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (creature) { + lua_pushnumber(L, creature->getSpeed()); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetCreatureBaseSpeed(lua_State* L) +{ + //getCreatureBaseSpeed(cid) + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (creature) { + lua_pushnumber(L, creature->getBaseSpeed()); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetCreatureTarget(lua_State* L) +{ + //getCreatureTarget(cid) + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Creature* creature = env->getCreatureByUID(cid); + + if (creature) { + Creature* target = creature->getAttackedCreature(); + + if (target) { + uint32_t targetCid = env->addThing(target); + lua_pushnumber(L, targetCid); + } else { + lua_pushnumber(L, 0); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaIsItemStackable(lua_State* L) +{ + //isItemStackable(itemid) + uint32_t itemid = popNumber(L); + const ItemType& it = Item::items[itemid]; + + if (it.stackable) { + lua_pushboolean(L, true); + } else { + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaIsItemRune(lua_State* L) +{ + //isItemRune(itemid) + uint32_t itemid = popNumber(L); + const ItemType& it = Item::items[itemid]; + + if (it.isRune()) { + lua_pushboolean(L, true); + } else { + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaIsItemDoor(lua_State* L) +{ + //isItemDoor(itemid) + uint32_t itemid = popNumber(L); + const ItemType& it = Item::items[itemid]; + + if (it.isDoor()) { + lua_pushboolean(L, true); + } else { + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaIsItemContainer(lua_State* L) +{ + //isItemContainer(itemid) + uint32_t itemid = popNumber(L); + const ItemType& it = Item::items[itemid]; + + if (it.isContainer()) { + lua_pushboolean(L, true); + } else { + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaIsItemFluidContainer(lua_State* L) +{ + //isItemFluidContainer(itemid) + uint32_t itemid = popNumber(L); + const ItemType& it = Item::items[itemid]; + + if (it.isFluidContainer()) { + lua_pushboolean(L, true); + } else { + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaIsItemMoveable(lua_State* L) +{ + uint32_t itemid = popNumber(L); + const ItemType& it = Item::items[itemid]; + + if (it.moveable) { + lua_pushboolean(L, true); + } else { + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaAddEvent(lua_State* L) +{ + //addEvent(callback, delay, ...) + ScriptEnvironment* env = getScriptEnv(); + + LuaScriptInterface* script_interface = env->getScriptInterface(); + + if (!script_interface) { + reportErrorFunc("No valid script interface!"); + lua_pushboolean(L, false); + return 1; + } + + int32_t parameters = lua_gettop(L); + + if (lua_isfunction(L, -parameters) == 0) { //-parameters means the first parameter from left to right + reportErrorFunc("callback parameter should be a function."); + lua_pushboolean(L, false); + return 1; + } + + LuaTimerEventDesc eventDesc; + std::list params; + + for (int32_t i = 0; i < parameters - 2; ++i) { //-2 because addEvent needs at least two parameters + params.push_back(luaL_ref(L, LUA_REGISTRYINDEX)); + } + + eventDesc.parameters = params; + + uint32_t delay = std::max(100, popNumber(L)); + eventDesc.function = luaL_ref(L, LUA_REGISTRYINDEX); + + eventDesc.scriptId = env->getScriptId(); + + script_interface->m_lastEventTimerId++; + + eventDesc.eventId = g_scheduler.addEvent(createSchedulerTask(delay, boost::bind(&LuaScriptInterface::executeTimerEvent, + script_interface, script_interface->m_lastEventTimerId))); + script_interface->m_timerEvents[script_interface->m_lastEventTimerId] = eventDesc; + lua_pushnumber(L, script_interface->m_lastEventTimerId); + return 1; +} + +int32_t LuaScriptInterface::luaStopEvent(lua_State* L) +{ + //stopEvent(eventid) + uint32_t eventId = popNumber(L); + ScriptEnvironment* env = getScriptEnv(); + + LuaScriptInterface* script_interface = env->getScriptInterface(); + + if (!script_interface) { + reportErrorFunc("No valid script interface!"); + lua_pushboolean(L, false); + return 1; + } + + LuaTimerEvents::iterator it = script_interface->m_timerEvents.find(eventId); + + if (it != script_interface->m_timerEvents.end()) { + LuaTimerEventDesc& timerEventDesc = it->second; + g_scheduler.stopEvent(timerEventDesc.eventId); + + for (std::list::iterator lt = timerEventDesc.parameters.begin(), end = timerEventDesc.parameters.end(); lt != end; ++lt) { + luaL_unref(script_interface->m_luaState, LUA_REGISTRYINDEX, *lt); + } + + timerEventDesc.parameters.clear(); + + luaL_unref(script_interface->m_luaState, LUA_REGISTRYINDEX, timerEventDesc.function); + script_interface->m_timerEvents.erase(it); + lua_pushboolean(L, true); + } else { + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetPromotedVocation(lua_State* L) +{ + int32_t vocationId = (int32_t)popNumber(L); + lua_pushnumber(L, g_vocations.getPromotedVocation(vocationId)); + return 1; +} + +int32_t LuaScriptInterface::luaGetCreatureCondition(lua_State* L) +{ + uint32_t condition = popNumber(L); + uint32_t cid = popNumber(L); + ScriptEnvironment* env = getScriptEnv(); + Creature* creature = env->getCreatureByUID(cid); + + if (creature) { + if (creature->hasCondition((ConditionType_t)condition)) { + lua_pushboolean(L, true); + } else { + lua_pushboolean(L, false); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetPlayerBlessing(lua_State* L) +{ + int16_t blessing = popNumber(L) - 1; + uint32_t cid = popNumber(L); + ScriptEnvironment* env = getScriptEnv(); + Player* player = env->getPlayerByUID(cid); + + if (player) { + lua_pushboolean(L, player->hasBlessing(blessing)); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerAddBlessing(lua_State* L) +{ + int16_t blessing = popNumber(L) - 1; + uint32_t cid = popNumber(L); + ScriptEnvironment* env = getScriptEnv(); + Player* player = env->getPlayerByUID(cid); + + if (player) { + if (!player->hasBlessing(blessing)) { + player->addBlessing(1 << blessing); + lua_pushboolean(L, true); + } else { + lua_pushboolean(L, false); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaSetPlayerGroupId(lua_State* L) +{ + uint32_t newGroupId = popNumber(L); + uint32_t cid = popNumber(L); + ScriptEnvironment* env = getScriptEnv(); + Player* player = env->getPlayerByUID(cid); + + if (player) { + player->setGroupId(newGroupId); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetCreatureHealth(lua_State* L) +{ + uint32_t cid = popNumber(L); + ScriptEnvironment* env = getScriptEnv(); + Creature* creature = env->getCreatureByUID(cid); + + if (creature) { + lua_pushnumber(L, creature->getHealth()); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetCreatureMaxHealth(lua_State* L) +{ + uint32_t cid = popNumber(L); + ScriptEnvironment* env = getScriptEnv(); + Creature* creature = env->getCreatureByUID(cid); + + if (creature) { + Player* player = creature->getPlayer(); + + if (player) { + lua_pushnumber(L, player->getMaxHealth()); + } else { + lua_pushnumber(L, creature->getMaxHealth()); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaSaveServer(lua_State* L) +{ + g_dispatcher.addTask( + createTask(boost::bind(&Game::saveGameState, &g_game))); + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaRefreshMap(lua_State* L) +{ + g_dispatcher.addTask( + createTask(boost::bind(&Game::refreshMap, &g_game))); + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaCleanMap(lua_State* L) +{ + g_dispatcher.addTask( + createTask(boost::bind(&Game::cleanMap, &g_game))); + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaGetOnlinePlayers(lua_State* L) +{ + //getOnlinePlayers() + int32_t i = 0; + lua_newtable(L); + + for (AutoList::listiterator it = Player::listPlayer.list.begin(); it != Player::listPlayer.list.end(); ++it) { + lua_pushnumber(L, ++i); + lua_pushstring(L, it->second->getName().c_str()); + lua_settable(L, -3); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetPlayerParty(lua_State* L) +{ + //getPlayerParty(cid) + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + if (Player* player = env->getPlayerByUID(cid)) { + if (Party* party = player->getParty()) { + lua_pushnumber(L, env->addThing(party->getLeader())); + } else { + lua_pushnil(L); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerJoinParty(lua_State* L) +{ + //doPlayerJoinParty(cid, lid) + ScriptEnvironment* env = getScriptEnv(); + + uint32_t cid = popNumber(L); + Player* leader = env->getPlayerByUID(cid); + + if (!leader) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + Player* player = env->getPlayerByUID(cid); + + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + g_game.playerJoinParty(player->getID(), leader->getID()); + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaGetPartyMembers(lua_State* L) +{ + //getPartyMembers(leaderId) + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Player* player = env->getPlayerByUID(cid); + + if (player) { + Party* party = player->getParty(); + + if (party) { + PlayerVector list = party->getMembers(); + list.push_back(party->getLeader()); + + PlayerVector::const_iterator it = list.begin(); + lua_newtable(L); + + for (uint32_t i = 1; it != list.end(); ++it, ++i) { + lua_pushnumber(L, i); + lua_pushnumber(L, (*it)->getID()); + lua_settable(L, -3); + } + + return 1; + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + } + + lua_pushboolean(L, false); + return 1; +} + +int32_t LuaScriptInterface::luaIsInWar(lua_State* L) +{ + //isInWar(cid, target) + uint32_t target = popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Player* player = env->getPlayerByUID(cid); + Player* targetPlayer = env->getPlayerByUID(target); + + if (player && targetPlayer) { + if (player->isInWar(targetPlayer)) { + lua_pushboolean(L, true); + return 1; + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + } + + lua_pushboolean(L, false); + return 1; +} + +int32_t LuaScriptInterface::luaDoPlayerSetOfflineTrainingSkill(lua_State* L) +{ + //doPlayerSetOfflineTrainingSkill(cid, skillid) + uint32_t skillid = (uint32_t)popNumber(L); + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Player* player = env->getPlayerByUID(cid); + + if (player) { + player->setOfflineTrainingSkill(skillid); + lua_pushboolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaGetWaypointPosition(lua_State* L) +{ + //getWaypointPosition(name) + if (WaypointPtr waypoint = g_game.getMap()->waypoints.getWaypointByName(popString(L))) { + pushPosition(L, waypoint->pos, 0); + } else { + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDoWaypointAddTemporial(lua_State* L) +{ + //doWaypointAddTemporial(name, pos) + PositionEx pos; + popPosition(L, pos); + + g_game.getMap()->waypoints.addWaypoint(WaypointPtr(new Waypoint(popString(L), pos))); + lua_pushboolean(L, true); + return 1; +} + +int32_t LuaScriptInterface::luaSendGuildChannelMessage(lua_State* L) +{ + //sendGuildChannelMessage(guildId, type, message) + std::string message = popString(L); + SpeakClasses type = (SpeakClasses)popNumber(L); + uint32_t guildId = popNumber(L); + + ChatChannel* channel = g_chat.getGuildChannelById(guildId); + + if (channel) { + channel->sendToAll(message, type); + lua_pushboolean(L, true); + } else { + lua_pushboolean(L, false); + } + + return 1; +} + +std::string LuaScriptInterface::escapeString(const std::string& string) +{ + std::string s = string; + replaceString(s, "\\", "\\\\"); + replaceString(s, "\"", "\\\""); + replaceString(s, "'", "\\'"); + replaceString(s, "[[", "\\[["); + return s; +} + +#ifndef __LUAJIT__ +const luaL_Reg LuaScriptInterface::luaBitReg[] = { + //{"cast", LuaScriptInterface::luaBitCast}, + {"bnot", LuaScriptInterface::luaBitNot}, + {"band", LuaScriptInterface::luaBitAnd}, + {"bor", LuaScriptInterface::luaBitOr}, + {"bxor", LuaScriptInterface::luaBitXor}, + {"lshift", LuaScriptInterface::luaBitLeftShift}, + {"rshift", LuaScriptInterface::luaBitRightShift}, + // Unsigned + {"ubnot", LuaScriptInterface::luaBitUNot}, + {"uband", LuaScriptInterface::luaBitUAnd}, + {"ubor", LuaScriptInterface::luaBitUOr}, + {"ubxor", LuaScriptInterface::luaBitUXor}, + {"ulshift", LuaScriptInterface::luaBitULeftShift}, + {"urshift", LuaScriptInterface::luaBitURightShift}, + //{"arshift", LuaScriptInterface::luaBitArithmeticalRightShift}, + {NULL, NULL} +}; + +int32_t LuaScriptInterface::luaBitNot(lua_State* L) +{ + int32_t number = (int32_t)popNumber(L); + lua_pushnumber(L, ~number); + return 1; +} + +int32_t LuaScriptInterface::luaBitUNot(lua_State* L) +{ + uint32_t number = (uint32_t)popNumber(L); + lua_pushnumber(L, ~number); + return 1; +} + +#define MULTIOP(type, name, op) \ + int32_t LuaScriptInterface::luaBit##name(lua_State* L) \ + { \ + int32_t n = lua_gettop(L); \ + type w = (type)popNumber(L); \ + for(int32_t i = 2; i <= n; ++i) \ + w op popNumber(L); \ + lua_pushnumber(L, w); \ + return 1; \ + } + +MULTIOP(int32_t, And, &= ) +MULTIOP(int32_t, Or, |= ) +MULTIOP(int32_t, Xor, ^= ) +MULTIOP(uint32_t, UAnd, &= ) +MULTIOP(uint32_t, UOr, |= ) +MULTIOP(uint32_t, UXor, ^= ) + +#define SHIFTOP(type, name, op) \ + int32_t LuaScriptInterface::luaBit##name(lua_State* L) \ + { \ + type n2 = (type)popNumber(L), n1 = (type)popNumber(L); \ + lua_pushnumber(L, (n1 op n2)); \ + return 1; \ + } + +SHIFTOP(int32_t, LeftShift, << ) +SHIFTOP(int32_t, RightShift, >> ) +SHIFTOP(uint32_t, ULeftShift, << ) +SHIFTOP(uint32_t, URightShift, >> ) +#endif + +const luaL_Reg LuaScriptInterface::luaDatabaseTable[] = { + {"query", LuaScriptInterface::luaDatabaseExecute}, + {"storeQuery", LuaScriptInterface::luaDatabaseStoreQuery}, + {"escapeString", LuaScriptInterface::luaDatabaseEscapeString}, + {"escapeBlob", LuaScriptInterface::luaDatabaseEscapeBlob}, + {"lastInsertId", LuaScriptInterface::luaDatabaseLastInsertId}, + {"connected", LuaScriptInterface::luaDatabaseConnected}, + {"tableExists", LuaScriptInterface::luaDatabaseTableExists}, + {NULL, NULL} +}; + +int32_t LuaScriptInterface::luaDatabaseExecute(lua_State* L) +{ + DBQuery query; + lua_pushboolean(L, Database::getInstance()->executeQuery(popString(L))); + return 1; +} + +int32_t LuaScriptInterface::luaDatabaseStoreQuery(lua_State* L) +{ + ScriptEnvironment* env = getScriptEnv(); + + DBQuery query; + + if (DBResult* res = Database::getInstance()->storeQuery(popString(L))) { + lua_pushnumber(L, env->addResult(res)); + } else { + lua_pushboolean(L, false); + } + + return 1; +} + +int32_t LuaScriptInterface::luaDatabaseEscapeString(lua_State* L) +{ + DBQuery query; + lua_pushstring(L, Database::getInstance()->escapeString(popString(L)).c_str()); + return 1; +} + +int32_t LuaScriptInterface::luaDatabaseEscapeBlob(lua_State* L) +{ + uint32_t length = popNumber(L); + DBQuery query; + + lua_pushstring(L, Database::getInstance()->escapeBlob(popString(L).c_str(), length).c_str()); + return 1; +} + +int32_t LuaScriptInterface::luaDatabaseLastInsertId(lua_State* L) +{ + DBQuery query; + lua_pushnumber(L, Database::getInstance()->getLastInsertId()); + return 1; +} + +int32_t LuaScriptInterface::luaDatabaseConnected(lua_State* L) +{ + lua_pushboolean(L, Database::getInstance()->isConnected()); + return 1; +} + +int32_t LuaScriptInterface::luaDatabaseTableExists(lua_State* L) +{ + lua_pushboolean(L, DatabaseManager::getInstance()->tableExists(popString(L))); + return 1; +} + +const luaL_Reg LuaScriptInterface::luaResultTable[] = { + {"getDataInt", LuaScriptInterface::luaResultGetDataInt}, + {"getDataLong", LuaScriptInterface::luaResultGetDataLong}, + {"getDataString", LuaScriptInterface::luaResultGetDataString}, + {"getDataStream", LuaScriptInterface::luaResultGetDataStream}, + {"getAllData", LuaScriptInterface::luaResultGetAllData}, + {"next", LuaScriptInterface::luaResultNext}, + {"free", LuaScriptInterface::luaResultFree}, + {NULL, NULL} +}; + +int32_t LuaScriptInterface::luaResultGetDataInt(lua_State* L) +{ + const std::string& s = popString(L); + ScriptEnvironment* env = getScriptEnv(); + + DBResult* res = env->getResultByID(popNumber(L)); + + if (!res) { + lua_pushboolean(L, false); + return 1; + } + + lua_pushnumber(L, res->getDataInt(s)); + return 1; +} + +int32_t LuaScriptInterface::luaResultGetDataLong(lua_State* L) +{ + const std::string& s = popString(L); + ScriptEnvironment* env = getScriptEnv(); + + DBResult* res = env->getResultByID(popNumber(L)); + + if (!res) { + lua_pushboolean(L, false); + return 1; + } + + lua_pushnumber(L, res->getDataLong(s)); + return 1; +} + +int32_t LuaScriptInterface::luaResultGetDataString(lua_State* L) +{ + const std::string& s = popString(L); + ScriptEnvironment* env = getScriptEnv(); + + DBResult* res = env->getResultByID(popNumber(L)); + + if (!res) { + lua_pushboolean(L, false); + return 1; + } + + lua_pushstring(L, res->getDataString(s).c_str()); + return 1; +} + +int32_t LuaScriptInterface::luaResultGetDataStream(lua_State* L) +{ + const std::string& s = popString(L); + ScriptEnvironment* env = getScriptEnv(); + + DBResult* res = env->getResultByID(popNumber(L)); + + if (!res) { + lua_pushboolean(L, false); + return 1; + } + + unsigned long length = 0; + lua_pushstring(L, res->getDataStream(s, length)); + lua_pushnumber(L, length); + return 2; +} + +int32_t LuaScriptInterface::luaResultGetAllData(lua_State* L) +{ + ScriptEnvironment* env = getScriptEnv(); + + DBResult* res = env->getResultByID(popNumber(L)); + + if (!res) { + lua_pushboolean(L, false); + return 1; + } + + lua_newtable(L); + listNames_t listNames = res->getListNames(); + + for (listNames_t::iterator it = listNames.begin(); it != listNames.end(); ++it) { + setField(L, it->first.c_str(), res->getDataString(it->first)); + } + + return 1; +} + +int32_t LuaScriptInterface::luaResultNext(lua_State* L) +{ + ScriptEnvironment* env = getScriptEnv(); + + DBResult* res = env->getResultByID(popNumber(L)); + + if (!res) { + lua_pushboolean(L, false); + return 1; + } + + lua_pushboolean(L, res->next()); + return 1; +} + +int32_t LuaScriptInterface::luaResultFree(lua_State* L) +{ + ScriptEnvironment* env = getScriptEnv(); + lua_pushboolean(L, env->removeResult(popNumber(L))); + return 1; +} diff --git a/src/luascript.h b/src/luascript.h new file mode 100644 index 0000000000..3b7339d5f0 --- /dev/null +++ b/src/luascript.h @@ -0,0 +1,746 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_LUASCRIPT_H__ +#define __OTSERV_LUASCRIPT_H__ + +#include +#include +#include +#include + +extern "C" +{ +#include +#include +#include +} + +#include "position.h" +#include "definitions.h" +#include "database.h" + +class Thing; +class Creature; +class Player; +class Item; +class Container; +class AreaCombat; +class Combat; +class Condition; +class Npc; +class Monster; + +enum LuaVariantType_t { + VARIANT_NONE = 0, + VARIANT_NUMBER, + VARIANT_POSITION, + VARIANT_TARGETPOSITION, + VARIANT_STRING, +}; + +struct LuaVariant { + LuaVariant() { + type = VARIANT_NONE; + text = ""; + pos.x = 0; + pos.y = 0; + pos.z = 0; + pos.stackpos = 0; + number = 0; + } + + LuaVariantType_t type; + std::string text; + PositionEx pos; + uint32_t number; +}; + +class LuaScriptInterface; +class Game; +class Npc; + +class ScriptEnvironment +{ + public: + ScriptEnvironment(); + ~ScriptEnvironment(); + + void resetEnv(); + void resetCallback() { + m_callbackId = 0; + } + + static bool saveGameState(); + static bool loadGameState(); + + void setScriptId(int32_t scriptId, LuaScriptInterface* scriptInterface) { + m_scriptId = scriptId; + m_interface = scriptInterface; + } + bool setCallbackId(int32_t callbackId, LuaScriptInterface* scriptInterface); + void setEventDesc(const std::string& desc) { + m_eventdesc = desc; + } + + std::string getEventDesc() const { + return m_eventdesc; + } + int32_t getScriptId() const { + return m_scriptId; + } + int32_t getCallbackId() const { + return m_callbackId; + } + LuaScriptInterface* getScriptInterface() { + return m_interface; + } + + void setTimerEvent() { + m_timerEvent = true; + } + void resetTimerEvent() { + m_timerEvent = false; + } + + void getEventInfo(int32_t& scriptId, std::string& desc, LuaScriptInterface*& scriptInterface, int32_t& callbackId, bool& timerEvent) const; + + static void addTempItem(ScriptEnvironment* env, Item* item); + static void removeTempItem(ScriptEnvironment* env, Item* item); + static void removeTempItem(Item* item); + static void addUniqueThing(Thing* thing); + static void removeUniqueThing(Thing* thing); + uint32_t addThing(Thing* thing); + void insertThing(uint32_t uid, Thing* thing); + + DBResult* getResultByID(uint32_t id); + uint32_t addResult(DBResult* res); + bool removeResult(uint32_t id); + + void addGlobalStorageValue(const uint32_t key, const int32_t value); + bool getGlobalStorageValue(const uint32_t key, int32_t& value) const; + + void setRealPos(const Position& realPos) { + m_realPos = realPos; + } + Position getRealPos() const { + return m_realPos; + } + + void setNpc(Npc* npc) { + m_curNpc = npc; + } + Npc* getNpc() const { + return m_curNpc; + } + + Thing* getThingByUID(uint32_t uid); + Item* getItemByUID(uint32_t uid); + Container* getContainerByUID(uint32_t uid); + Creature* getCreatureByUID(uint32_t uid); + Player* getPlayerByUID(uint32_t uid); + Monster* getMonsterByUID(uint32_t uid); + Npc* getNpcByUID(uint32_t uid); + void removeItemByUID(uint32_t uid); + + static uint32_t addCombatArea(AreaCombat* area); + static AreaCombat* getCombatArea(uint32_t areaId); + + static uint32_t addCombatObject(Combat* combat); + static Combat* getCombatObject(uint32_t combatId); + + static uint32_t addConditionObject(Condition* condition); + static Condition* getConditionObject(uint32_t conditionId); + + static uint32_t getLastCombatId() { + return m_lastCombatId; + } + + private: + typedef OTSERV_HASH_MAP ThingMap; + typedef std::vector VariantVector; + typedef std::map AreaMap; + typedef std::map CombatMap; + typedef std::map ConditionMap; + typedef std::map StorageMap; + typedef std::map DBResultMap; + typedef std::list ItemList; + + //script file id + int32_t m_scriptId; + int32_t m_callbackId; + bool m_timerEvent; + LuaScriptInterface* m_interface; + //script event desc + std::string m_eventdesc; + + static StorageMap m_globalStorageMap; + //unique id map + static ThingMap m_globalMap; + + Position m_realPos; + + //item/creature map + int32_t m_lastUID; + ThingMap m_localMap; + + //temporary item list + typedef std::map TempItemListMap; + static TempItemListMap m_tempItems; + + //area map + static uint32_t m_lastAreaId; + static AreaMap m_areaMap; + + //combat map + static uint32_t m_lastCombatId; + static CombatMap m_combatMap; + + //condition map + static uint32_t m_lastConditionId; + static ConditionMap m_conditionMap; + + //result map + static uint32_t m_lastResultId; + static DBResultMap m_tempResults; + + //for npc scripts + Npc* m_curNpc; +}; + +class Position; + +enum PlayerInfo_t { + PlayerInfoFood, + PlayerInfoAccess, + PlayerInfoLevel, + PlayerInfoMagLevel, + PlayerInfoMana, + PlayerInfoMaxMana, + PlayerInfoName, + PlayerInfoPosition, + PlayerInfoVocation, + PlayerInfoMasterPos, + PlayerInfoTown, + PlayerInfoSoul, + PlayerInfoFreeCap, + PlayerInfoGuildId, + PlayerInfoGuildLevel, + PlayerInfoGuildName, + PlayerInfoGuildRank, + PlayerInfoGuildNick, + PlayerInfoSex, + PlayerInfoLookDirection, + PlayerInfoGroupId, + PlayerInfoGUID, + PlayerInfoPremiumDays, + PlayerInfoSkullType, + PlayerInfoPzLock, + PlayerInfoGhostStatus, + PlayerInfoIp, + PlayerInfoBankBalance, + PlayerInfoMoney, + PlayerInfoLastLoginSaved +}; + +#define reportErrorFunc(a) reportError(__FUNCTION__, a, true) + +enum ErrorCode_t { + LUA_ERROR_PLAYER_NOT_FOUND, + LUA_ERROR_CREATURE_NOT_FOUND, + LUA_ERROR_ITEM_NOT_FOUND, + LUA_ERROR_THING_NOT_FOUND, + LUA_ERROR_TILE_NOT_FOUND, + LUA_ERROR_HOUSE_NOT_FOUND, + LUA_ERROR_COMBAT_NOT_FOUND, + LUA_ERROR_CONDITION_NOT_FOUND, + LUA_ERROR_AREA_NOT_FOUND, + LUA_ERROR_CONTAINER_NOT_FOUND, + LUA_ERROR_VARIANT_NOT_FOUND, + LUA_ERROR_VARIANT_UNKNOWN, + LUA_ERROR_SPELL_NOT_FOUND +}; + +class LuaScriptInterface +{ + public: + LuaScriptInterface(const std::string& interfaceName); + virtual ~LuaScriptInterface(); + + virtual bool initState(); + bool reInitState(); + + int32_t loadFile(const std::string& file, Npc* npc = NULL); + int32_t loadBuffer(const std::string& text, Npc* npc /* = NULL*/); + const std::string& getFileById(int32_t scriptId); + + int32_t getEvent(const std::string& eventName); + + static ScriptEnvironment* getScriptEnv() { + assert(m_scriptEnvIndex >= 0 && m_scriptEnvIndex < 16); + return &m_scriptEnv[m_scriptEnvIndex]; + } + + static bool reserveScriptEnv() { + ++m_scriptEnvIndex; + + if (m_scriptEnvIndex < 15) { + return true; + } else { + --m_scriptEnvIndex; + return false; + } + } + + static void releaseScriptEnv() { + if (m_scriptEnvIndex >= 0) { + m_scriptEnv[m_scriptEnvIndex].resetEnv(); + --m_scriptEnvIndex; + } + } + + static void reportError(const char* function, const std::string& error_desc, bool stack_trace = false); + + std::string getInterfaceName() const { + return m_interfaceName; + } + const std::string& getLastLuaError() const { + return m_lastLuaError; + } + + lua_State* getLuaState() { + return m_luaState; + } + + bool pushFunction(int32_t functionId); + + static int32_t luaErrorHandler(lua_State* L); + bool callFunction(uint32_t nParams); + + //push/pop common structures + static void pushThing(lua_State* L, Thing* thing, uint32_t thingid); + static void pushVariant(lua_State* L, const LuaVariant& var); + static void pushPosition(lua_State* L, const PositionEx& position); + static void pushPosition(lua_State* L, const Position& position, uint32_t stackpos); + static void pushCallback(lua_State* L, int32_t callback); + + static LuaVariant popVariant(lua_State* L); + static void popPosition(lua_State* L, PositionEx& position); + static void popPosition(lua_State* L, Position& position, uint32_t& stackpos); + static uint32_t popNumber(lua_State* L); + static double popFloatNumber(lua_State* L); + static std::string popString(lua_State* L); + static int32_t popCallback(lua_State* L); + static bool popBoolean(lua_State* L); + + template + static T popNumber(lua_State* L); + + static int32_t getField(lua_State* L, const char* key); + static uint32_t getFieldU32(lua_State* L, const char* key); + static void setField(lua_State* L, const char* index, double val); + static void setField(lua_State* L, const char* index, const std::string& val); + static std::string getFieldString(lua_State* L, const char* key); + static void setFieldBool(lua_State* L, const char* index, bool val); + static bool getFieldBool(lua_State* L, const char* key); + static std::string escapeString(const std::string& string); + + protected: + virtual bool closeState(); + + virtual void registerFunctions(); + + static std::string getErrorDesc(ErrorCode_t code); + static bool getArea(lua_State* L, std::list& list, uint32_t& rows); + + //lua functions + static int32_t luaDoRemoveItem(lua_State* L); + static int32_t luaDoFeedPlayer(lua_State* L); + static int32_t luaDoPlayerSendCancel(lua_State* L); + static int32_t luaDoSendDefaultCancel(lua_State* L); + static int32_t luaDoTeleportThing(lua_State* L); + static int32_t luaDoTransformItem(lua_State* L); + static int32_t luaDoSendMagicEffect(lua_State* L); + static int32_t luaDoChangeTypeItem(lua_State* L); + static int32_t luaDoSendAnimatedText(lua_State* L); + static int32_t luaDoSendDistanceShoot(lua_State* L); + static int32_t luaDoShowTextWindow(lua_State* L); + static int32_t luaDoShowTextDialog(lua_State* L); + static int32_t luaDoDecayItem(lua_State* L); + static int32_t luaDoCreateItem(lua_State* L); + static int32_t luaDoCreateItemEx(lua_State* L); + static int32_t luaDoCreateTeleport(lua_State* L); + static int32_t luaDoCreateNpc(lua_State* L); + static int32_t luaDoSummonCreature(lua_State* L); + static int32_t luaDoConvinceCreature(lua_State* L); + static int32_t luaGetMonsterTargetList(lua_State* L); + static int32_t luaGetMonsterFriendList(lua_State* L); + static int32_t luaDoSetMonsterTarget(lua_State* L); + static int32_t luaDoMonsterChangeTarget(lua_State* L); + static int32_t luaDoAddCondition(lua_State* L); + static int32_t luaDoRemoveCondition(lua_State* L); + static int32_t luaDoRemoveCreature(lua_State* L); + static int32_t luaDoMoveCreature(lua_State* L); + static int32_t luaGetHouseTilesSize(lua_State* L); + static int32_t luaGetTileInfo(lua_State* L); + + static int32_t luaDoCreatureSay(lua_State* L); + static int32_t luaDoPlayerAddSkillTry(lua_State* L); + static int32_t luaDoCreatureAddHealth(lua_State* L); + static int32_t luaDoPlayerAddMana(lua_State* L); + static int32_t luaDoPlayerAddManaSpent(lua_State* L); + static int32_t luaDoPlayerAddItem(lua_State* L); + static int32_t luaDoPlayerAddItemEx(lua_State* L); + static int32_t luaDoTileAddItemEx(lua_State* L); + static int32_t luaDoAddContainerItemEx(lua_State* L); + static int32_t luaDoRelocate(lua_State* L); + static int32_t luaDoPlayerSendTextMessage(lua_State* L); + static int32_t luaDoPlayerRemoveMoney(lua_State* L); + static int32_t luaDoPlayerAddMoney(lua_State* L); + static int32_t luaDoPlayerSetTown(lua_State* L); + static int32_t luaDoPlayerSetVocation(lua_State* L); + static int32_t luaDoPlayerRemoveItem(lua_State* L); + static int32_t luaDoPlayerAddSoul(lua_State* L); + static int32_t luaDoPlayerAddExp(lua_State* L); + static int32_t luaDoPlayerSetGuildLevel(lua_State* L); + static int32_t luaDoPlayerSetGuildNick(lua_State* L); + static int32_t luaDoPlayerSetSex(lua_State* L); + static int32_t luaDoPlayerChangeName(lua_State* L); + static int32_t luaDoSetCreatureLight(lua_State* L); + static int32_t luaDoSetCreatureDropLoot(lua_State* L); + static int32_t luaDoPlayerSetBankBalance(lua_State* L); + + //queries + static int32_t luaGetPlayerByName(lua_State* L); + static int32_t luaGetPlayerGUIDByName(lua_State* L); + static int32_t luaGetAccountNumberByPlayerName(lua_State* L); + static int32_t luaGetPlayersByAccountNumber(lua_State* L); + static int32_t luaGetIPByPlayerName(lua_State* L); + static int32_t luaGetPlayersByIPAddress(lua_State* L); + + //get item info + static int32_t luaGetItemRWInfo(lua_State* L); + static int32_t luaGetThingfromPos(lua_State* L); + static int32_t luaGetThing(lua_State* L); + static int32_t luaGetThingPos(lua_State* L); + static int32_t luaGetTileItemById(lua_State* L); + static int32_t luaGetTileItemByType(lua_State* L); + static int32_t luaGetTileThingByPos(lua_State* L); + static int32_t luaGetTileThingByTopOrder(lua_State* L); + static int32_t luaGetTopCreature(lua_State* L); + static int32_t luaHasProperty(lua_State* L); + static int32_t luaGetDepotId(lua_State* L); + + //set item + static int32_t luaDoSetItemActionId(lua_State* L); + static int32_t luaDoSetItemText(lua_State* L); + static int32_t luaDoSetItemSpecialDescription(lua_State* L); + + //get tile info + static int32_t luaGetTilePzInfo(lua_State* L); + static int32_t luaGetTileHouseInfo(lua_State* L); + static int32_t luaQueryTileAddThing(lua_State* L); + + //houses + static int32_t luaGetHouseOwner(lua_State* L); + static int32_t luaGetHouseName(lua_State* L); + static int32_t luaGetHouseEntry(lua_State* L); + static int32_t luaGetHouseRent(lua_State* L); + static int32_t luaGetHouseTown(lua_State* L); + static int32_t luaGetHouseAccessList(lua_State* L); + static int32_t luaGetHouseByPlayerGUID(lua_State* L); + static int32_t luaSetHouseOwner(lua_State* L); + static int32_t luaSetHouseAccessList(lua_State* L); + + //get creature info functions + static int32_t luaGetPlayerFood(lua_State* L); + static int32_t luaGetPlayerIp(lua_State* L); + static int32_t luaGetPlayerAccess(lua_State* L); + static int32_t luaGetPlayerLevel(lua_State* L); + static int32_t luaGetPlayerMagLevel(lua_State* L); + static int32_t luaGetPlayerMana(lua_State* L); + static int32_t luaGetPlayerMaxMana(lua_State* L); + static int32_t luaGetCreatureHealth(lua_State* L); + static int32_t luaGetCreatureMaxHealth(lua_State* L); + static int32_t luaGetCreatureMaster(lua_State* L); + static int32_t luaGetCreatureSummons(lua_State* L); + static int32_t luaGetSpectators(lua_State* L); + static int32_t luaGetCreatureSpeed(lua_State* L); + static int32_t luaGetCreatureBaseSpeed(lua_State* L); + static int32_t luaGetCreatureTarget(lua_State* L); + static int32_t luaGetPlayerName(lua_State* L); + static int32_t luaGetPlayerPosition(lua_State* L); + static int32_t luaGetPlayerSkill(lua_State* L); + static int32_t luaGetPlayerVocation(lua_State* L); + static int32_t luaGetPlayerMasterPos(lua_State* L); + static int32_t luaGetPromotedVocation(lua_State* L); + static int32_t luaGetPlayerTown(lua_State* L); + static int32_t luaGetPlayerItemCount(lua_State* L); + static int32_t luaGetPlayerSoul(lua_State* L); + static int32_t luaGetPlayerFreeCap(lua_State* L); + static int32_t luaGetPlayerLight(lua_State* L); + static int32_t luaGetPlayerSlotItem(lua_State* L); + static int32_t luaGetPlayerItemById(lua_State* L); + static int32_t luaGetPlayerLossPercent(lua_State* L); + static int32_t luaGetPlayerSkullType(lua_State* L); + static int32_t luaGetPlayerBankBalance(lua_State* L); + static int32_t luaGetPlayerMoney(lua_State* L); + static int32_t luaGetPlayerLastLoginSaved(lua_State* L); + + static int32_t luaGetPlayerDepotItems(lua_State* L); + static int32_t luaGetPlayerGuildId(lua_State* L); + static int32_t luaGetPlayerGuildLevel(lua_State* L); + static int32_t luaGetPlayerGuildName(lua_State* L); + static int32_t luaGetPlayerGuildRank(lua_State* L); + static int32_t luaGetPlayerGuildNick(lua_State* L); + static int32_t luaGetPlayerSex(lua_State* L); + static int32_t luaGetPlayerLookDir(lua_State* L); + static int32_t luaDoCreatureSetLookDir(lua_State* L); + static int32_t luaGetPlayerBlessing(lua_State* L); + static int32_t luaDoPlayerAddBlessing(lua_State* L); + static int32_t luaGetPlayerGUID(lua_State* L); + static int32_t luaGetPlayerFlagValue(lua_State* L); + static int32_t luaGetCreatureCondition(lua_State* L); + + static int32_t luaGetPlayerGroupId(lua_State* L); + static int32_t luaSetPlayerGroupId(lua_State* L); + + static int32_t luaPlayerLearnInstantSpell(lua_State* L); + static int32_t luaCanPlayerLearnInstantSpell(lua_State* L); + static int32_t luaGetPlayerLearnedInstantSpell(lua_State* L); + static int32_t luaGetPlayerInstantSpellInfo(lua_State* L); + static int32_t luaGetPlayerInstantSpellCount(lua_State* L); + static int32_t luaGetInstantSpellInfoByName(lua_State* L); + static int32_t luaGetInstantSpellWords(lua_State* L); + + static int32_t luaGetPlayerStorageValue(lua_State* L); + static int32_t luaSetPlayerStorageValue(lua_State* L); + + static int32_t luaGetGlobalStorageValue(lua_State* L); + static int32_t luaSetGlobalStorageValue(lua_State* L); + + static int32_t luaDoPlayerAddOutfit(lua_State* L); + static int32_t luaDoPlayerRemOutfit(lua_State* L); + static int32_t luaCanPlayerWearOutfit(lua_State* L); + + static int32_t luaDoPlayerAddMount(lua_State* L); + static int32_t luaDoPlayerRemoveMount(lua_State* L); + static int32_t luaGetPlayerMount(lua_State* L); + + static int32_t luaGetWorldType(lua_State* L); + static int32_t luaGetWorldTime(lua_State* L); + static int32_t luaGetWorldLight(lua_State* L); + static int32_t luaGetWorldCreatures(lua_State* L); + static int32_t luaGetWorldUpTime(lua_State* L); + static int32_t luaBroadcastMessage(lua_State* L); + static int32_t luaGetGuildId(lua_State* L); + + //type validation + static int32_t luaIsPlayer(lua_State* L); + static int32_t luaIsPlayerPzLocked(lua_State* L); + static int32_t luaIsPlayerGhost(lua_State* L); + static int32_t luaIsCreature(lua_State* L); + static int32_t luaIsContainer(lua_State* L); + static int32_t luaIsDepot(lua_State* L); + static int32_t luaIsCorpse(lua_State* L); + static int32_t luaIsMoveable(lua_State* L); + static int32_t luaIsValidUID(lua_State* L); + static int32_t luaIsMonster(lua_State* L); + static int32_t luaIsNpc(lua_State* L); + static int32_t luaIsItem(lua_State* L); + + //container + static int32_t luaGetContainerSize(lua_State* L); + static int32_t luaGetContainerCap(lua_State* L); + static int32_t luaGetContainerCapById(lua_State* L); + static int32_t luaGetContainerItem(lua_State* L); + static int32_t luaDoAddContainerItem(lua_State* L); + + // + static int32_t luaCreateCombatObject(lua_State* L); + static int32_t luaCreateCombatArea(lua_State* L); + static int32_t luaSetCombatArea(lua_State* L); + static int32_t luaSetCombatCondition(lua_State* L); + static int32_t luaSetCombatParam(lua_State* L); + static int32_t luaCreateConditionObject(lua_State* L); + static int32_t luaSetConditionParam(lua_State* L); + static int32_t luaAddDamageCondition(lua_State* L); + static int32_t luaAddOutfitCondition(lua_State* L); + + static int32_t luaSetCombatCallBack(lua_State* L); + static int32_t luaSetCombatFormula(lua_State* L); + static int32_t luaSetConditionFormula(lua_State* L); + static int32_t luaDoCombat(lua_State* L); + + static int32_t luaDoAreaCombatHealth(lua_State* L); + static int32_t luaDoTargetCombatHealth(lua_State* L); + + // + static int32_t luaDoAreaCombatMana(lua_State* L); + static int32_t luaDoTargetCombatMana(lua_State* L); + + static int32_t luaDoAreaCombatCondition(lua_State* L); + static int32_t luaDoTargetCombatCondition(lua_State* L); + + static int32_t luaDoAreaCombatDispel(lua_State* L); + static int32_t luaDoTargetCombatDispel(lua_State* L); + + static int32_t luaDoChallengeCreature(lua_State* L); + + static int32_t luaNumberToVariant(lua_State* L); + static int32_t luaStringToVariant(lua_State* L); + static int32_t luaPositionToVariant(lua_State* L); + static int32_t luaTargetPositionToVariant(lua_State* L); + + static int32_t luaVariantToNumber(lua_State* L); + static int32_t luaVariantToString(lua_State* L); + static int32_t luaVariantToPosition(lua_State* L); + + static int32_t luaDoChangeSpeed(lua_State* L); + + static int32_t luaDoCreatureChangeOutfit(lua_State* L); + static int32_t luaSetCreatureOutfit(lua_State* L); + static int32_t luaGetCreatureOutfit(lua_State* L); + static int32_t luaSetMonsterOutfit(lua_State* L); + static int32_t luaSetItemOutfit(lua_State* L); + static int32_t luaGetCreaturePosition(lua_State* L); + static int32_t luaGetCreatureName(lua_State* L); + + static int32_t luaIsItemStackable(lua_State* L); + static int32_t luaIsItemRune(lua_State* L); + static int32_t luaIsItemDoor(lua_State* L); + static int32_t luaIsItemContainer(lua_State* L); + static int32_t luaIsItemFluidContainer(lua_State* L); + static int32_t luaIsItemMoveable(lua_State* L); + static int32_t luaGetItemName(lua_State* L); + static int32_t luaGetItemDescriptions(lua_State* L); + static int32_t luaGetItemWeight(lua_State* L); + static int32_t luaGetItemWeightByUID(lua_State* L); + static int32_t luaGetItemIdByName(lua_State* L); + static int32_t luaGetTownId(lua_State* L); + static int32_t luaGetTownName(lua_State* L); + static int32_t luaGetTownTemplePosition(lua_State* L); + static int32_t luaIsSightClear(lua_State* L); + + static int32_t luaDebugPrint(lua_State* L); + static int32_t luaIsInArray(lua_State* L); + static int32_t luaGetFluidSourceType(lua_State* L); + static int32_t luaAddEvent(lua_State* L); + static int32_t luaStopEvent(lua_State* L); + static int32_t luaRegisterCreatureEvent(lua_State* L); + + static int32_t luaDoPlayerPopupFYI(lua_State* L); + static int32_t luaMayNotMove(lua_State* L); + + static int32_t luaDoPlayerAddPremiumDays(lua_State* L); + static int32_t luaDoPlayerRemovePremiumDays(lua_State* L); + static int32_t luaGetPlayerPremiumDays(lua_State* L); + + static int32_t luaGetOnlinePlayers(lua_State* L); + static int32_t luaSaveServer(lua_State* L); + static int32_t luaRefreshMap(lua_State* L); + static int32_t luaCleanMap(lua_State* L); + + static int32_t luaDoSendTutorial(lua_State* L); + static int32_t luaDoAddMark(lua_State* L); + + static int32_t luaIsInWar(lua_State* L); + static int32_t luaDoPlayerSetOfflineTrainingSkill(lua_State* L); + + static int32_t luaGetWaypointPosition(lua_State* L); + static int32_t luaDoWaypointAddTemporial(lua_State* L); + + static int32_t luaSendGuildChannelMessage(lua_State* L); + + static int32_t luaGetPlayerParty(lua_State* L); + static int32_t luaDoPlayerJoinParty(lua_State* L); + static int32_t luaGetPartyMembers(lua_State* L); + + // + + static int32_t internalGetPlayerInfo(lua_State* L, PlayerInfo_t info); + +#ifndef __LUAJIT__ + static const luaL_Reg luaBitReg[13]; + static int32_t luaBitNot(lua_State* L); + static int32_t luaBitAnd(lua_State* L); + static int32_t luaBitOr(lua_State* L); + static int32_t luaBitXor(lua_State* L); + static int32_t luaBitLeftShift(lua_State* L); + static int32_t luaBitRightShift(lua_State* L); + static int32_t luaBitUNot(lua_State* L); + static int32_t luaBitUAnd(lua_State* L); + static int32_t luaBitUOr(lua_State* L); + static int32_t luaBitUXor(lua_State* L); + static int32_t luaBitULeftShift(lua_State* L); + static int32_t luaBitURightShift(lua_State* L); +#endif + + static const luaL_Reg luaDatabaseTable[10]; + static int32_t luaDatabaseExecute(lua_State* L); + static int32_t luaDatabaseStoreQuery(lua_State* L); + static int32_t luaDatabaseEscapeString(lua_State* L); + static int32_t luaDatabaseEscapeBlob(lua_State* L); + static int32_t luaDatabaseLastInsertId(lua_State* L); + static int32_t luaDatabaseConnected(lua_State* L); + static int32_t luaDatabaseTableExists(lua_State* L); + + static const luaL_Reg luaResultTable[8]; + static int32_t luaResultGetDataInt(lua_State* L); + static int32_t luaResultGetDataLong(lua_State* L); + static int32_t luaResultGetDataString(lua_State* L); + static int32_t luaResultGetDataStream(lua_State* L); + static int32_t luaResultGetAllData(lua_State* L); + static int32_t luaResultNext(lua_State* L); + static int32_t luaResultFree(lua_State* L); + + lua_State* m_luaState; + std::string m_lastLuaError; + + private: + static ScriptEnvironment m_scriptEnv[16]; + static int32_t m_scriptEnvIndex; + + int32_t m_runningEventId; + std::string m_loadingFile; + + //script file cache + typedef std::map ScriptsCache; + ScriptsCache m_cacheFiles; + + //events information + struct LuaTimerEventDesc { + int32_t scriptId; + int32_t function; + std::list parameters; + uint32_t eventId; + }; + uint32_t m_lastEventTimerId; + + typedef std::map LuaTimerEvents; + LuaTimerEvents m_timerEvents; + + static int32_t protectedCall(lua_State* L, int32_t nargs, int32_t nresults); + std::string getStackTrace(const std::string& error_desc); + + void executeTimerEvent(uint32_t eventIndex); + + std::string m_interfaceName; +}; + +#endif diff --git a/src/mailbox.cpp b/src/mailbox.cpp new file mode 100644 index 0000000000..bda4a5b59a --- /dev/null +++ b/src/mailbox.cpp @@ -0,0 +1,195 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "mailbox.h" +#include "game.h" +#include "player.h" +#include "iologindata.h" +#include "town.h" + +extern Game g_game; + +Mailbox::Mailbox(uint16_t _type) : Item(_type) +{ + // +} + +Mailbox::~Mailbox() +{ + // +} + +ReturnValue Mailbox::__queryAdd(int32_t index, const Thing* thing, uint32_t count, + uint32_t flags, Creature* actor/* = NULL*/) const +{ + if (const Item* item = thing->getItem()) { + if (canSend(item)) { + return RET_NOERROR; + } + } + + return RET_NOTPOSSIBLE; +} + +ReturnValue Mailbox::__queryMaxCount(int32_t index, const Thing* thing, uint32_t count, uint32_t& maxQueryCount, + uint32_t flags) const +{ + maxQueryCount = std::max(1, count); + return RET_NOERROR; +} + +ReturnValue Mailbox::__queryRemove(const Thing* thing, uint32_t count, uint32_t flags) const +{ + return RET_NOTPOSSIBLE; +} + +Cylinder* Mailbox::__queryDestination(int32_t& index, const Thing* thing, Item** destItem, + uint32_t& flags) +{ + return this; +} + +void Mailbox::__addThing(Thing* thing) +{ + return __addThing(0, thing); +} + +void Mailbox::__addThing(int32_t index, Thing* thing) +{ + if (Item* item = thing->getItem()) { + if (canSend(item)) { + sendItem(item); + } + } +} + +void Mailbox::__updateThing(Thing* thing, uint16_t itemId, uint32_t count) +{ + // +} + +void Mailbox::__replaceThing(uint32_t index, Thing* thing) +{ + // +} + +void Mailbox::__removeThing(Thing* thing, uint32_t count) +{ + // +} + +void Mailbox::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) +{ + getParent()->postAddNotification(thing, oldParent, index, LINK_PARENT); +} + +void Mailbox::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, bool isCompleteRemoval, cylinderlink_t link /*= LINK_OWNER*/) +{ + getParent()->postRemoveNotification(thing, newParent, index, isCompleteRemoval, LINK_PARENT); +} + +bool Mailbox::sendItem(Item* item) +{ + std::string receiver = std::string(""); + + if (!getReceiver(item, receiver)) { + return false; + } + + /**No need to continue if its still empty**/ + if (receiver == "") { + return false; + } + + uint32_t guid; + + if (!IOLoginData::getInstance()->getGuidByName(guid, receiver)) { + return false; + } + + Player* player = g_game.getPlayerByName(receiver); + + if (player) { + if (g_game.internalMoveItem(item->getParent(), player->getInbox(), INDEX_WHEREEVER, + item, item->getItemCount(), NULL, FLAG_NOLIMIT) == RET_NOERROR) { + g_game.transformItem(item, item->getID() + 1); + player->onReceiveMail(); + return true; + } + } else { + player = new Player(receiver, NULL); + + if (!IOLoginData::getInstance()->loadPlayer(player, receiver)) { + delete player; + return false; + } + + if (g_game.internalMoveItem(item->getParent(), player->getInbox(), INDEX_WHEREEVER, + item, item->getItemCount(), NULL, FLAG_NOLIMIT) == RET_NOERROR) { + g_game.transformItem(item, item->getID() + 1); + IOLoginData::getInstance()->savePlayer(player); + delete player; + return true; + } + + delete player; + } + + return false; +} + +bool Mailbox::getReceiver(Item* item, std::string& name) +{ + if (!item) { + return false; + } + + if (item->getID() == ITEM_PARCEL) { /**We need to get the text from the label incase its a parcel**/ + Container* parcel = item->getContainer(); + + if (parcel) { + for (ItemDeque::const_iterator cit = parcel->getItems(), end = parcel->getEnd(); cit != end; ++cit) { + if ((*cit)->getID() == ITEM_LABEL) { + item = (*cit); + + if (item->getText() != "") { + break; + } + } + } + } + } else if (item->getID() != ITEM_LETTER) { /**The item is somehow not a parcel or letter**/ + std::cout << "Mailbox::getReciver error, trying to get reciecer from unkown item! ID: " << item->getID() << "." << std::endl; + return false; + } + + if (!item || item->getText() == "") { /**No label/letter found or its empty.**/ + return false; + } + + name = getFirstLine(item->getText()); + trimString(name); + return true; +} + +bool Mailbox::canSend(const Item* item) const +{ + return item->getID() == ITEM_PARCEL || item->getID() == ITEM_LETTER; +} diff --git a/src/mailbox.h b/src/mailbox.h new file mode 100644 index 0000000000..0d75202382 --- /dev/null +++ b/src/mailbox.h @@ -0,0 +1,65 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_MAILBOX_H__ +#define __OTSERV_MAILBOX_H__ + +#include "item.h" +#include "cylinder.h" +#include "const.h" + + +class Mailbox : public Item, public Cylinder +{ + public: + Mailbox(uint16_t _type); + ~Mailbox(); + + virtual Mailbox* getMailbox() { + return this; + } + virtual const Mailbox* getMailbox() const { + return this; + } + + //cylinder implementations + virtual ReturnValue __queryAdd(int32_t index, const Thing* thing, uint32_t count, + uint32_t flags, Creature* actor = NULL) const; + virtual ReturnValue __queryMaxCount(int32_t index, const Thing* thing, uint32_t count, + uint32_t& maxQueryCount, uint32_t flags) const; + virtual ReturnValue __queryRemove(const Thing* thing, uint32_t count, uint32_t flags) const; + virtual Cylinder* __queryDestination(int32_t& index, const Thing* thing, Item** destItem, + uint32_t& flags); + + virtual void __addThing(Thing* thing); + virtual void __addThing(int32_t index, Thing* thing); + + virtual void __updateThing(Thing* thing, uint16_t itemId, uint32_t count); + virtual void __replaceThing(uint32_t index, Thing* thing); + + virtual void __removeThing(Thing* thing, uint32_t count); + + virtual void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER); + virtual void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, bool isCompleteRemoval, cylinderlink_t link = LINK_OWNER); + + bool getReceiver(Item* item, std::string& name); + bool sendItem(Item* item); + bool canSend(const Item* item) const; +}; + +#endif diff --git a/src/map.cpp b/src/map.cpp new file mode 100644 index 0000000000..6ab2f83f07 --- /dev/null +++ b/src/map.cpp @@ -0,0 +1,1323 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "definitions.h" + +#include +#include +#include + +#include +#include + +#include "iomap.h" + +#include "otsystem.h" +#include "iomapserialize.h" + +#include +#include + +#include "items.h" +#include "map.h" +#include "tile.h" +#include "combat.h" +#include "creature.h" + +#include "player.h" +#include "configmanager.h" +#include "game.h" + +extern Game g_game; +extern ConfigManager g_config; +IOMapSerialize IOMapSerialize; + +Map::Map() +{ + mapWidth = 0; + mapHeight = 0; +} + +Map::~Map() +{ + // +} + +bool Map::loadMap(const std::string& identifier) +{ + IOMap* loader = new IOMap(); + + if (!loader->loadMap(this, identifier)) { + std::cout << "FATAL: [OTBM loader] " << loader->getLastErrorString() << std::endl; + return false; + } + + if (!loader->loadSpawns(this)) { + std::cout << "WARNING: could not load spawn data." << std::endl; + } + + if (!loader->loadHouses(this)) { + std::cout << "WARNING: could not load house data." << std::endl; + } + + delete loader; + + IOMapSerialize.loadHouseInfo(this); + IOMapSerialize.loadMap(this); + return true; +} + +bool Map::saveMap() +{ + bool saved = false; + + for (uint32_t tries = 0; tries < 3; tries++) { + if (IOMapSerialize.saveHouseInfo(this)) { + saved = true; + break; + } + } + + if (!saved) { + return false; + } + + saved = false; + + for (uint32_t tries = 0; tries < 3; tries++) { + if (IOMapSerialize.saveMap(this)) { + saved = true; + break; + } + } + + return saved; +} + +Tile* Map::getTile(int32_t x, int32_t y, int32_t z) +{ + if (x < 0 || x >= 0xFFFF || y < 0 || y >= 0xFFFF || z < 0 || z >= MAP_MAX_LAYERS) { + return NULL; + } + + QTreeLeafNode* leaf = QTreeNode::getLeafStatic(&root, x, y); + + if (leaf) { + Floor* floor = leaf->getFloor(z); + + if (floor) { + return floor->tiles[x & FLOOR_MASK][y & FLOOR_MASK]; + } + } + + return NULL; +} + +Tile* Map::getTile(const Position& pos) +{ + return getTile(pos.x, pos.y, pos.z); +} + +void Map::setTile(int32_t x, int32_t y, int32_t z, Tile* newTile) +{ + if (x < 0 || x >= 0xFFFF || y < 0 || y >= 0xFFFF || z < 0 || z >= MAP_MAX_LAYERS) { + std::cout << "ERROR: Attempt to set tile on invalid coordinate " << Position(x, y, z) << "!" << std::endl; + return; + } + + QTreeLeafNode::newLeaf = false; + QTreeLeafNode* leaf = root.createLeaf(x, y, 15); + + if (QTreeLeafNode::newLeaf) { + //update north + QTreeLeafNode* northLeaf = root.getLeaf(x, y - FLOOR_SIZE); + + if (northLeaf) { + northLeaf->m_leafS = leaf; + } + + //update west leaf + QTreeLeafNode* westLeaf = root.getLeaf(x - FLOOR_SIZE, y); + + if (westLeaf) { + westLeaf->m_leafE = leaf; + } + + //update south + QTreeLeafNode* southLeaf = root.getLeaf(x, y + FLOOR_SIZE); + + if (southLeaf) { + leaf->m_leafS = southLeaf; + } + + //update east + QTreeLeafNode* eastLeaf = root.getLeaf(x + FLOOR_SIZE, y); + + if (eastLeaf) { + leaf->m_leafE = eastLeaf; + } + } + + Floor* floor = leaf->createFloor(z); + uint32_t offsetX = x & FLOOR_MASK; + uint32_t offsetY = y & FLOOR_MASK; + + if (!floor->tiles[offsetX][offsetY]) { + floor->tiles[offsetX][offsetY] = newTile; + newTile->qt_node = leaf; + } else { + std::cout << "Error: Map::setTile() already exists." << std::endl; + } + + if (newTile->hasFlag(TILESTATE_REFRESH)) { + RefreshBlock_t rb; + rb.lastRefresh = OTSYS_TIME(); + + if (TileItemVector* newTileItems = newTile->getItemList()) { + for (ItemVector::iterator it = newTileItems->getBeginDownItem(); it != newTileItems->getEndDownItem(); ++it) { + rb.list.push_back((*it)->clone()); + } + } + + refreshTileMap[newTile] = rb; + } +} + +bool Map::placeCreature(const Position& centerPos, Creature* creature, bool extendedPos /*=false*/, bool forceLogin /*=false*/) +{ + Tile* tile = getTile(centerPos); + + bool foundTile = false; + bool placeInPZ = false; + + if (tile) { + placeInPZ = tile->hasFlag(TILESTATE_PROTECTIONZONE); + ReturnValue ret = tile->__queryAdd(0, creature, 1, FLAG_IGNOREBLOCKITEM); + + if (forceLogin || ret == RET_NOERROR || ret == RET_PLAYERISNOTINVITED) { + foundTile = true; + } + } + + typedef std::vector > RelPosList; + RelPosList relPosList; + + if (extendedPos) { + relPosList.push_back(std::make_pair(0, -2)); + relPosList.push_back(std::make_pair(-2, 0)); + relPosList.push_back(std::make_pair(0, 2)); + relPosList.push_back(std::make_pair(2, 0)); + + std::random_shuffle(relPosList.begin(), relPosList.end()); + } + + relPosList.push_back(std::make_pair(-1, -1)); + relPosList.push_back(std::make_pair(-1, 0)); + relPosList.push_back(std::make_pair(-1, 1)); + relPosList.push_back(std::make_pair(0, -1)); + relPosList.push_back(std::make_pair(0, 1)); + relPosList.push_back(std::make_pair(1, -1)); + relPosList.push_back(std::make_pair(1, 0)); + relPosList.push_back(std::make_pair(1, 1)); + + std::random_shuffle(relPosList.begin() + (extendedPos ? 4 : 0), relPosList.end()); + uint32_t radius = 1; + + Position tryPos; + + for (uint32_t n = 1; n <= radius && !foundTile; ++n) { + for (RelPosList::iterator it = relPosList.begin(); it != relPosList.end() && !foundTile; ++it) { + int32_t dx = it->first * n; + int32_t dy = it->second * n; + + tryPos = centerPos; + tryPos.x = tryPos.x + dx; + tryPos.y = tryPos.y + dy; + + tile = getTile(tryPos); + + if (!tile || (placeInPZ && !tile->hasFlag(TILESTATE_PROTECTIONZONE))) { + continue; + } + + if (tile->__queryAdd(0, creature, 1, 0) == RET_NOERROR) { + if (extendedPos) { + if (isSightClear(centerPos, tryPos, false)) { + foundTile = true; + break; + } + } else { + foundTile = true; + break; + } + } + } + } + + if (!foundTile) { + return false; + } + + int32_t index = 0; + Item* toItem = NULL; + uint32_t flags = 0; + Cylinder* toCylinder = tile->__queryDestination(index, creature, &toItem, flags); + toCylinder->__internalAddThing(creature); + Tile* toTile = toCylinder->getTile(); + toTile->qt_node->addCreature(creature); + return true; +} + +bool Map::removeCreature(Creature* creature) +{ + Tile* tile = creature->getTile(); + + if (!tile) { + return false; + } + + tile->qt_node->removeCreature(creature); + tile->__removeThing(creature, 0); + return true; +} + +void Map::getSpectatorsInternal(SpectatorVec& list, const Position& centerPos, int32_t minRangeX, int32_t maxRangeX, int32_t minRangeY, int32_t maxRangeY, int32_t minRangeZ, int32_t maxRangeZ, bool onlyPlayers) +{ + int32_t minoffset = centerPos.z - maxRangeZ; + int32_t x1 = std::min(0xFFFF, std::max(0, (centerPos.x + minRangeX + minoffset))); + int32_t y1 = std::min(0xFFFF, std::max(0, (centerPos.y + minRangeY + minoffset))); + + int32_t maxoffset = centerPos.z - minRangeZ; + int32_t x2 = std::min(0xFFFF, std::max(0, (centerPos.x + maxRangeX + maxoffset))); + int32_t y2 = std::min(0xFFFF, std::max(0, (centerPos.y + maxRangeY + maxoffset))); + + int32_t startx1 = x1 - (x1 % FLOOR_SIZE); + int32_t starty1 = y1 - (y1 % FLOOR_SIZE); + int32_t endx2 = x2 - (x2 % FLOOR_SIZE); + int32_t endy2 = y2 - (y2 % FLOOR_SIZE); + + QTreeLeafNode* startLeaf; + QTreeLeafNode* leafE; + QTreeLeafNode* leafS; + + startLeaf = getLeaf(startx1, starty1); + leafS = startLeaf; + + for (int32_t ny = starty1; ny <= endy2; ny += FLOOR_SIZE) { + leafE = leafS; + + for (int32_t nx = startx1; nx <= endx2; nx += FLOOR_SIZE) { + if (leafE) { + CreatureVector node_list; + + if (onlyPlayers) { + node_list = leafE->player_list; + } else { + node_list = leafE->creature_list; + } + + CreatureVector::const_iterator node_iter = node_list.begin(); + CreatureVector::const_iterator node_end = node_list.end(); + + if (node_iter != node_end) { + do { + Creature* creature = *node_iter; + const Position& cpos = creature->getPosition(); + + int32_t offsetZ = centerPos.z - cpos.z; + + if (cpos.z < minRangeZ || cpos.z > maxRangeZ) { + continue; + } + + if (cpos.y < (centerPos.y + minRangeY + offsetZ) || cpos.y > (centerPos.y + maxRangeY + offsetZ)) { + continue; + } + + if (cpos.x < (centerPos.x + minRangeX + offsetZ) || cpos.x > (centerPos.x + maxRangeX + offsetZ)) { + continue; + } + + list.insert(creature); + } while (++node_iter != node_end); + } + + leafE = leafE->stepEast(); + } else { + leafE = getLeaf(nx + FLOOR_SIZE, ny); + } + } + + if (leafS) { + leafS = leafS->stepSouth(); + } else { + leafS = getLeaf(startx1, ny + FLOOR_SIZE); + } + } +} + +void Map::getSpectators(SpectatorVec& list, const Position& centerPos, bool multifloor /*= false*/, bool onlyPlayers /*= false*/, + int32_t minRangeX /*= 0*/, int32_t maxRangeX /*= 0*/, + int32_t minRangeY /*= 0*/, int32_t maxRangeY /*= 0*/) +{ + if (centerPos.z >= MAP_MAX_LAYERS) { + return; + } + + bool foundCache = false; + bool cacheResult = false; + + minRangeX = (minRangeX == 0 ? -maxViewportX : -minRangeX); + maxRangeX = (maxRangeX == 0 ? maxViewportX : maxRangeX); + minRangeY = (minRangeY == 0 ? -maxViewportY : -minRangeY); + maxRangeY = (maxRangeY == 0 ? maxViewportY : maxRangeY); + + if (minRangeX == -maxViewportX && maxRangeX == maxViewportX && minRangeY == -maxViewportY && maxRangeY == maxViewportY && multifloor) { + if (onlyPlayers) { + SpectatorCache::iterator it = playersSpectatorCache.find(centerPos); + + if (it != playersSpectatorCache.end()) { + if (!list.empty()) { + const SpectatorVec& cachedList = *it->second; + list.insert(cachedList.begin(), cachedList.end()); + } else { + list = *it->second; + } + + foundCache = true; + } + } + + if (!foundCache) { + SpectatorCache::iterator it = spectatorCache.find(centerPos); + + if (it != spectatorCache.end()) { + if (!onlyPlayers) { + if (!list.empty()) { + const SpectatorVec& cachedList = *it->second; + list.insert(cachedList.begin(), cachedList.end()); + } else { + list = *it->second; + } + } else { + const SpectatorVec& cachedList = *it->second; + + for (SpectatorVec::const_iterator iter = cachedList.begin(), end = cachedList.end(); iter != end; ++iter) { + if ((*iter)->getPlayer()) { + list.insert(*iter); + } + } + } + + foundCache = true; + } else { + cacheResult = true; + } + } + } + + if (!foundCache) { + int32_t minRangeZ; + int32_t maxRangeZ; + + if (multifloor) { + if (centerPos.z > 7) { + //underground + + //8->15 + minRangeZ = std::max(centerPos.z - 2, 0); + maxRangeZ = std::min(centerPos.z + 2, MAP_MAX_LAYERS - 1); + } + //above ground + else if (centerPos.z == 6) { + minRangeZ = 0; + maxRangeZ = 8; + } else if (centerPos.z == 7) { + minRangeZ = 0; + maxRangeZ = 9; + } else { + minRangeZ = 0; + maxRangeZ = 7; + } + } else { + minRangeZ = centerPos.z; + maxRangeZ = centerPos.z; + } + + getSpectatorsInternal(list, centerPos, minRangeX, maxRangeX, minRangeY, maxRangeY, minRangeZ, maxRangeZ, onlyPlayers); + + if (cacheResult) { + if (onlyPlayers) { + playersSpectatorCache[centerPos].reset(new SpectatorVec(list)); + } else { + spectatorCache[centerPos].reset(new SpectatorVec(list)); + } + } + } +} + +const SpectatorVec& Map::getSpectators(const Position& centerPos) +{ + if (centerPos.z >= MAP_MAX_LAYERS) { + boost::shared_ptr p(new SpectatorVec()); + SpectatorVec& list = *p; + return list; + } + + SpectatorCache::iterator it = spectatorCache.find(centerPos); + + if (it != spectatorCache.end()) { + return *it->second; + } + + boost::shared_ptr p(new SpectatorVec()); + spectatorCache[centerPos] = p; + SpectatorVec& list = *p; + + int32_t minRangeX = -maxViewportX; + int32_t maxRangeX = maxViewportX; + int32_t minRangeY = -maxViewportY; + int32_t maxRangeY = maxViewportY; + int32_t minRangeZ, maxRangeZ; + + if (centerPos.z > 7) { + //underground + + //8->15 + minRangeZ = std::max(centerPos.z - 2, 0); + maxRangeZ = std::min(centerPos.z + 2, MAP_MAX_LAYERS - 1); + } + //above ground + else if (centerPos.z == 6) { + minRangeZ = 0; + maxRangeZ = 8; + } else if (centerPos.z == 7) { + minRangeZ = 0; + maxRangeZ = 9; + } else { + minRangeZ = 0; + maxRangeZ = 7; + } + + getSpectatorsInternal(list, centerPos, minRangeX, maxRangeX, minRangeY, maxRangeY, minRangeZ, maxRangeZ, false); + return list; +} + +void Map::clearSpectatorCache() +{ + spectatorCache.clear(); + playersSpectatorCache.clear(); +} + +bool Map::canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight /*= true*/, + int32_t rangex /*= Map::maxClientViewportX*/, int32_t rangey /*= Map::maxClientViewportY*/) +{ + //z checks + //underground 8->15 + //ground level and above 7->0 + if ((fromPos.z >= 8 && toPos.z < 8) || (toPos.z >= 8 && fromPos.z < 8)) { + return false; + } + + int32_t deltaz = std::abs(fromPos.z - toPos.z); + + if (deltaz > 2) { + return false; + } + + int32_t deltax = std::abs(fromPos.x - toPos.x); + int32_t deltay = std::abs(fromPos.y - toPos.y); + + //distance checks + if (deltax - deltaz > rangex || deltay - deltaz > rangey) { + return false; + } + + if (!checkLineOfSight) { + return true; + } + + return isSightClear(fromPos, toPos, false); +} + +bool Map::checkSightLine(const Position& fromPos, const Position& toPos) const +{ + if (fromPos == toPos) { + return true; + } + + Position start(fromPos.z > toPos.z ? toPos : fromPos); + Position destination(fromPos.z > toPos.z ? fromPos : toPos); + + const int8_t mx = start.x < destination.x ? 1 : start.x == destination.x ? 0 : -1; + const int8_t my = start.y < destination.y ? 1 : start.y == destination.y ? 0 : -1; + + int32_t A = destination.y - start.y; + int32_t B = start.x - destination.x; + int32_t C = -(A * destination.x + B * destination.y); + + while (!Position::areInRange<0, 0, 15>(start, destination)) { + int32_t move_hor = std::abs(A * (start.x + mx) + B * (start.y) + C); + int32_t move_ver = std::abs(A * (start.x) + B * (start.y + my) + C); + int32_t move_cross = std::abs(A * (start.x + mx) + B * (start.y + my) + C); + + if (start.y != destination.y && (start.x == destination.x || move_hor > move_ver || move_hor > move_cross)) { + start.y += my; + } + + if (start.x != destination.x && (start.y == destination.y || move_ver > move_hor || move_ver > move_cross)) { + start.x += mx; + } + + const Tile* tile = const_cast(this)->getTile(start.x, start.y, start.z); + + if (tile && tile->hasProperty(BLOCKPROJECTILE)) { + return false; + } + } + + // now we need to perform a jump between floors to see if everything is clear (literally) + while (start.z != destination.z) { + const Tile* tile = const_cast(this)->getTile(start.x, start.y, start.z); + + if (tile && tile->getThingCount() > 0) { + return false; + } + + start.z++; + } + + return true; +} + +bool Map::isSightClear(const Position& fromPos, const Position& toPos, bool floorCheck) const +{ + if (floorCheck && fromPos.z != toPos.z) { + return false; + } + + // Cast two converging rays and see if either yields a result. + return checkSightLine(fromPos, toPos) || checkSightLine(toPos, fromPos); +} + +const Tile* Map::canWalkTo(const Creature* creature, const Position& pos) +{ + int32_t walkCache = creature->getWalkCache(pos); + + if (walkCache == 0) { + return NULL; + } else if (walkCache == 1) { + return getTile(pos); + } + + //used for none-cached tiles + Tile* tile = getTile(pos); + + if (creature->getTile() != tile) { + if (!tile || tile->__queryAdd(0, creature, 1, FLAG_PATHFINDING | FLAG_IGNOREFIELDDAMAGE) != RET_NOERROR) { + return NULL; + } + } + + return tile; +} + +bool Map::getPathTo(const Creature* creature, const Position& destPos, + std::list& listDir, int32_t maxSearchDist /*= -1*/) +{ + if (canWalkTo(creature, destPos) == NULL) { + return false; + } + + listDir.clear(); + + Position startPos = destPos; + Position endPos = creature->getPosition(); + + if (startPos.z != endPos.z) { + return false; + } + + OTSERV_HASH_MAP nodeTable; + + AStarNodes nodes; + AStarNode* startNode = nodes.createOpenNode(); + + nodeTable[(startPos.x * 0xFFFF) + startPos.y] = startNode; + + startNode->x = startPos.x; + startNode->y = startPos.y; + + startNode->g = 0; + startNode->h = nodes.getEstimatedDistance(startPos.x, startPos.y, endPos.x, endPos.y); + startNode->f = startNode->h; + startNode->parent = NULL; + + Position pos; + pos.z = startPos.z; + + static int32_t neighbourOrderList[8][2] = { + { -1, 0}, + {0, 1}, + {1, 0}, + {0, -1}, + + //diagonal + { -1, -1}, + {1, -1}, + {1, 1}, + { -1, 1}, + }; + + const Tile* tile = NULL; + AStarNode* found = NULL; + + while (maxSearchDist != -1 || nodes.countClosedNodes() < 100) { + AStarNode* n = nodes.getBestNode(); + + if (!n) { + listDir.clear(); + return false; //no path found + } + + if (n->x == endPos.x && n->y == endPos.y) { + found = n; + break; + } else { + for (int i = 0; i < 8; ++i) { + pos.x = n->x + neighbourOrderList[i][0]; + pos.y = n->y + neighbourOrderList[i][1]; + + bool outOfRange = false; + + if (maxSearchDist != -1 && (std::abs(endPos.x - pos.x) > maxSearchDist || + std::abs(endPos.y - pos.y) > maxSearchDist)) { + outOfRange = true; + } + + if (!outOfRange && (tile = canWalkTo(creature, pos))) { + //The cost (g) for this neighbour + int32_t cost = nodes.getMapWalkCost(creature, n, tile, pos); + int32_t extraCost = nodes.getTileWalkCost(creature, tile); + int32_t newg = n->g + cost + extraCost; + uint32_t tableIndex = (pos.x * 0xFFFF) + pos.y; + + //Check if the node is already in the closed/open list + //If it exists and the nodes already on them has a lower cost (g) then we can ignore this neighbour node + + AStarNode* neighbourNode; + OTSERV_HASH_MAP::iterator it = nodeTable.find(tableIndex); + + if (it != nodeTable.end()) { + neighbourNode = it->second; + } else { + neighbourNode = NULL; + } + + if (neighbourNode) { + if (neighbourNode->g <= newg) { + continue; //The node on the closed/open list is cheaper than this one + } + + nodes.openNode(neighbourNode); + } else { + //Does not exist in the open/closed list, create a new node + neighbourNode = nodes.createOpenNode(); + + if (!neighbourNode) { + //seems we ran out of nodes + listDir.clear(); + return false; + } + + nodeTable[tableIndex] = neighbourNode; + + neighbourNode->x = pos.x; + neighbourNode->y = pos.y; + } + + //This node is the best node so far with this state + neighbourNode->parent = n; + neighbourNode->g = newg; + neighbourNode->h = nodes.getEstimatedDistance(pos.x, pos.y, endPos.x, endPos.y); + neighbourNode->f = newg + neighbourNode->h; + } + } + + nodes.closeNode(n); + } + } + + int32_t prevx = endPos.x; + int32_t prevy = endPos.y; + int32_t dx, dy; + + while (found) { + pos.x = found->x; + pos.y = found->y; + + found = found->parent; + + dx = pos.x - prevx; + dy = pos.y - prevy; + + prevx = pos.x; + prevy = pos.y; + + if (dx == -1 && dy == -1) { + listDir.push_back(NORTHWEST); + } else if (dx == 1 && dy == -1) { + listDir.push_back(NORTHEAST); + } else if (dx == -1 && dy == 1) { + listDir.push_back(SOUTHWEST); + } else if (dx == 1 && dy == 1) { + listDir.push_back(SOUTHEAST); + } else if (dx == -1) { + listDir.push_back(WEST); + } else if (dx == 1) { + listDir.push_back(EAST); + } else if (dy == -1) { + listDir.push_back(NORTH); + } else if (dy == 1) { + listDir.push_back(SOUTH); + } + } + + return !listDir.empty(); +} + +bool Map::getPathMatching(const Creature* creature, std::list& dirList, + const FrozenPathingConditionCall& pathCondition, const FindPathParams& fpp) +{ + dirList.clear(); + + Position startPos = creature->getPosition(); + Position endPos; + + OTSERV_HASH_MAP nodeTable; + + AStarNodes nodes; + AStarNode* startNode = nodes.createOpenNode(); + + nodeTable[(startPos.x * 0xFFFF) + startPos.y] = startNode; + + startNode->x = startPos.x; + startNode->y = startPos.y; + + startNode->f = 0; + startNode->parent = NULL; + + Position pos; + pos.z = startPos.z; + int32_t bestMatch = 0; + + static int32_t neighbourOrderList[8][2] = { + { -1, 0}, + {0, 1}, + {1, 0}, + {0, -1}, + + //diagonal + { -1, -1}, + {1, -1}, + {1, 1}, + { -1, 1}, + }; + + const Tile* tile = NULL; + AStarNode* found = NULL; + + while (fpp.maxSearchDist != -1 || nodes.countClosedNodes() < 100) { + AStarNode* n = nodes.getBestNode(); + + if (!n) { + if (found) { + //not quite what we want, but we found something + break; + } + + dirList.clear(); + return false; //no path found + } + + if (pathCondition(startPos, Position(n->x, n->y, startPos.z), fpp, bestMatch)) { + found = n; + endPos = Position(n->x, n->y, startPos.z); + + if (bestMatch == 0) { + break; + } + } + + int32_t dirCount = (fpp.allowDiagonal ? 8 : 4); + + for (int32_t i = 0; i < dirCount; ++i) { + pos.x = n->x + neighbourOrderList[i][0]; + pos.y = n->y + neighbourOrderList[i][1]; + + bool inRange = true; + + if (fpp.maxSearchDist != -1 && (std::abs(startPos.x - pos.x) > fpp.maxSearchDist || + std::abs(startPos.y - pos.y) > fpp.maxSearchDist)) { + inRange = false; + } + + if (fpp.keepDistance) { + if (!pathCondition.isInRange(startPos, pos, fpp)) { + inRange = false; + } + } + + if (inRange && (tile = canWalkTo(creature, pos))) { + //The cost (g) for this neighbour + int32_t cost = nodes.getMapWalkCost(creature, n, tile, pos); + int32_t extraCost = nodes.getTileWalkCost(creature, tile); + int32_t newf = n->f + cost + extraCost; + uint32_t tableIndex = (pos.x * 0xFFFF) + pos.y; + + //Check if the node is already in the closed/open list + //If it exists and the nodes already on them has a lower cost (g) then we can ignore this neighbour node + + AStarNode* neighbourNode; + + OTSERV_HASH_MAP::iterator it = nodeTable.find(tableIndex); + + if (it != nodeTable.end()) { + neighbourNode = it->second; + } else { + neighbourNode = NULL; + } + + if (neighbourNode) { + if (neighbourNode->f <= newf) { + //The node on the closed/open list is cheaper than this one + continue; + } + + nodes.openNode(neighbourNode); + } else { + //Does not exist in the open/closed list, create a new node + neighbourNode = nodes.createOpenNode(); + + if (!neighbourNode) { + if (found) { + //not quite what we want, but we found something + break; + } + + //seems we ran out of nodes + dirList.clear(); + return false; + } + + nodeTable[tableIndex] = neighbourNode; + + neighbourNode->x = pos.x; + neighbourNode->y = pos.y; + } + + //This node is the best node so far with this state + neighbourNode->parent = n; + neighbourNode->f = newf; + } + } + + nodes.closeNode(n); + } + + int32_t prevx = endPos.x; + int32_t prevy = endPos.y; + int32_t dx, dy; + + if (!found) { + return false; + } + + found = found->parent; + + while (found) { + pos.x = found->x; + pos.y = found->y; + + dx = pos.x - prevx; + dy = pos.y - prevy; + + prevx = pos.x; + prevy = pos.y; + + if (dx == 1 && dy == 1) { + dirList.push_front(NORTHWEST); + } else if (dx == -1 && dy == 1) { + dirList.push_front(NORTHEAST); + } else if (dx == 1 && dy == -1) { + dirList.push_front(SOUTHWEST); + } else if (dx == -1 && dy == -1) { + dirList.push_front(SOUTHEAST); + } else if (dx == 1) { + dirList.push_front(WEST); + } else if (dx == -1) { + dirList.push_front(EAST); + } else if (dy == 1) { + dirList.push_front(NORTH); + } else if (dy == -1) { + dirList.push_front(SOUTH); + } + + found = found->parent; + } + + return true; +} + +//*********** AStarNodes ************* + +AStarNodes::AStarNodes() +{ + curNode = 0; + openNodes.reset(); +} + +AStarNode* AStarNodes::createOpenNode() +{ + if (curNode >= MAX_NODES) { + return NULL; + } + + uint32_t ret_node = curNode; + curNode++; + openNodes[ret_node] = 1; + return &nodes[ret_node]; +} + +AStarNode* AStarNodes::getBestNode() +{ + if (curNode == 0) { + return NULL; + } + + int best_node_f = 100000; + uint32_t best_node = 0; + bool found = false; + + for (uint32_t i = 0; i < curNode; i++) { + if (nodes[i].f < best_node_f && openNodes[i] == 1) { + found = true; + best_node_f = nodes[i].f; + best_node = i; + } + } + + if (found) { + return &nodes[best_node]; + } + + return NULL; +} + +void AStarNodes::closeNode(AStarNode* node) +{ + uint32_t pos = GET_NODE_INDEX(node); + + if (pos >= MAX_NODES) { + assert(pos >= MAX_NODES); + std::cout << "AStarNodes. trying to close node out of range" << std::endl; + return; + } + + openNodes[pos] = 0; +} + +void AStarNodes::openNode(AStarNode* node) +{ + uint32_t pos = GET_NODE_INDEX(node); + + if (pos >= MAX_NODES) { + assert(pos >= MAX_NODES); + std::cout << "AStarNodes. trying to open node out of range" << std::endl; + return; + } + + openNodes[pos] = 1; +} + +uint32_t AStarNodes::countClosedNodes() +{ + uint32_t counter = 0; + + for (uint32_t i = 0; i < curNode; i++) { + if (openNodes[i] == 0) { + counter++; + } + } + + return counter; +} + +uint32_t AStarNodes::countOpenNodes() +{ + uint32_t counter = 0; + + for (uint32_t i = 0; i < curNode; i++) { + if (openNodes[i] == 1) { + counter++; + } + } + + return counter; +} + +int32_t AStarNodes::getMapWalkCost(const Creature* creature, AStarNode* node, + const Tile* neighbourTile, const Position& neighbourPos) +{ + int cost = 0; + + if (std::abs((int)node->x - neighbourPos.x) == std::abs((int)node->y - neighbourPos.y)) { + //diagonal movement extra cost + cost = MAP_DIAGONALWALKCOST; + } else { + cost = MAP_NORMALWALKCOST; + } + + return cost; +} + +int32_t AStarNodes::getTileWalkCost(const Creature* creature, const Tile* tile) +{ + int cost = 0; + + if (tile->getTopVisibleCreature(creature) != NULL) { + //destroy creature cost + cost += MAP_NORMALWALKCOST * 3; + } + + if (const MagicField* field = tile->getFieldItem()) { + CombatType_t combatType = field->getCombatType(); + + if (!creature->isImmune(combatType) && !creature->hasCondition(Combat::DamageToConditionType(combatType))) { + cost += MAP_NORMALWALKCOST * 18; + } + } + + return cost; +} + +int32_t AStarNodes::getEstimatedDistance(int32_t x, int32_t y, int32_t xGoal, int32_t yGoal) +{ + int32_t h_diagonal = std::min(std::abs(x - xGoal), std::abs(y - yGoal)); + int32_t h_straight = (std::abs(x - xGoal) + std::abs(y - yGoal)); + return MAP_DIAGONALWALKCOST * h_diagonal + MAP_NORMALWALKCOST * (h_straight - 2 * h_diagonal); +} + +//*********** Floor constructor ************** + +Floor::Floor() +{ + for (uint32_t i = 0; i < FLOOR_SIZE; ++i) { + for (uint32_t j = 0; j < FLOOR_SIZE; ++j) { + tiles[i][j] = NULL; + } + } +} + +Floor::~Floor() +{ + for (uint32_t i = 0; i < FLOOR_SIZE; ++i) { + for (uint32_t j = 0; j < FLOOR_SIZE; ++j) { + delete tiles[i][j]; + } + } +} + +//**************** QTreeNode ********************** +QTreeNode::QTreeNode() +{ + m_isLeaf = false; + m_child[0] = NULL; + m_child[1] = NULL; + m_child[2] = NULL; + m_child[3] = NULL; +} + +QTreeNode::~QTreeNode() +{ + delete m_child[0]; + delete m_child[1]; + delete m_child[2]; + delete m_child[3]; +} + +QTreeLeafNode* QTreeNode::getLeaf(uint32_t x, uint32_t y) +{ + if (!isLeaf()) { + uint32_t index = ((x & 0x8000) >> 15) | ((y & 0x8000) >> 14); + + if (m_child[index]) { + return m_child[index]->getLeaf(x * 2, y * 2); + } else { + return NULL; + } + } else { + return static_cast(this); + } +} + +QTreeLeafNode* QTreeNode::getLeafStatic(QTreeNode* root, uint32_t x, uint32_t y) +{ + QTreeNode* currentNode = root; + uint32_t currentX = x, currentY = y; + + while (currentNode) { + if (!currentNode->isLeaf()) { + uint32_t index = ((currentX & 0x8000) >> 15) | ((currentY & 0x8000) >> 14); + + if (currentNode->m_child[index]) { + currentNode = currentNode->m_child[index]; + currentX = currentX * 2; + currentY = currentY * 2; + } else { + return NULL; + } + } else { + return static_cast(currentNode); + } + } + + return NULL; +} + +QTreeLeafNode* QTreeNode::createLeaf(uint32_t x, uint32_t y, uint32_t level) +{ + if (!isLeaf()) { + uint32_t index = ((x & 0x8000) >> 15) | ((y & 0x8000) >> 14); + + if (!m_child[index]) { + if (level != FLOOR_BITS) { + m_child[index] = new QTreeNode(); + } else { + m_child[index] = new QTreeLeafNode(); + QTreeLeafNode::newLeaf = true; + } + } + + return m_child[index]->createLeaf(x * 2, y * 2, level - 1); + } + + return static_cast(this); +} + + +//************ LeafNode ************************ +bool QTreeLeafNode::newLeaf = false; +QTreeLeafNode::QTreeLeafNode() +{ + for (uint32_t i = 0; i < MAP_MAX_LAYERS; ++i) { + m_array[i] = NULL; + } + + m_isLeaf = true; + m_leafS = NULL; + m_leafE = NULL; +} + +QTreeLeafNode::~QTreeLeafNode() +{ + for (uint32_t i = 0; i < MAP_MAX_LAYERS; ++i) { + delete m_array[i]; + } +} + +Floor* QTreeLeafNode::createFloor(uint32_t z) +{ + if (!m_array[z]) { + m_array[z] = new Floor(); + } + + return m_array[z]; +} + +void QTreeLeafNode::addCreature(Creature* c) +{ + creature_list.push_back(c); + + if (c->getPlayer()) { + player_list.push_back(c); + } +} + +void QTreeLeafNode::removeCreature(Creature* c) +{ + CreatureVector::iterator iter = std::find(creature_list.begin(), creature_list.end(), c); + assert(iter != creature_list.end()); + std::swap(*iter, creature_list.back()); + creature_list.pop_back(); + + if (c->getPlayer()) { + iter = std::find(player_list.begin(), player_list.end(), c); + assert(iter != player_list.end()); + std::swap(*iter, player_list.back()); + player_list.pop_back(); + } +} + +uint32_t Map::clean() +{ + uint64_t start = OTSYS_TIME(); + uint32_t count = 0, tiles = 0; + + if (g_game.getGameState() == GAME_STATE_NORMAL) { + g_game.setGameState(GAME_STATE_MAINTAIN); + } + + Tile* tile; + + for (int32_t z = 0; z < (int32_t)MAP_MAX_LAYERS; z++) { + for (uint32_t y = 1; y <= mapHeight; y++) { + for (uint32_t x = 1; x <= mapWidth; x++) { + if (!(tile = getTile(x, y, z)) || tile->hasFlag(TILESTATE_PROTECTIONZONE) || !tile->getItemList()) { + continue; + } + + ++tiles; + TileItemVector* itemList = tile->getItemList(); + ItemVector::iterator it = itemList->begin(), end = itemList->end(); + + while (it != end) { + if ((*it)->isCleanable()) { + g_game.internalRemoveItem(*it, -1); + it = itemList->begin(); + end = itemList->end(); + ++count; + } else { + ++it; + } + } + } + } + } + + if (g_game.getGameState() == GAME_STATE_MAINTAIN) { + g_game.setGameState(GAME_STATE_NORMAL); + } + + std::cout << "> CLEAN: Removed " << count << " item" << (count != 1 ? "s" : "") + << " from " << tiles << " tile" << (tiles != 1 ? "s" : "") << " in " + << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl; + return count; +} diff --git a/src/map.h b/src/map.h new file mode 100644 index 0000000000..c2f560d696 --- /dev/null +++ b/src/map.h @@ -0,0 +1,308 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_MAP_H__ +#define __OTSERV_MAP_H__ + +#include +#include +#include + +#include +using boost::shared_ptr; + +#include "position.h" +#include "item.h" +#include "fileloader.h" + +#include "tools.h" +#include "tile.h" +#include "waypoints.h" + +class Creature; +class Player; +class Game; +class Tile; +class Map; + +#define MAP_MAX_LAYERS 16 + +struct FindPathParams; +struct AStarNode { + int32_t x, y; + AStarNode* parent; + int32_t f, g, h; +}; + +#define MAX_NODES 512 +#define GET_NODE_INDEX(a) (a - &nodes[0]) + +#define MAP_NORMALWALKCOST 10 +#define MAP_DIAGONALWALKCOST 25 + +class AStarNodes +{ + public: + AStarNodes(); + ~AStarNodes() {} + + AStarNode* createOpenNode(); + AStarNode* getBestNode(); + void closeNode(AStarNode* node); + void openNode(AStarNode* node); + uint32_t countClosedNodes(); + uint32_t countOpenNodes(); + bool isInList(int32_t x, int32_t y); + AStarNode* getNodeInList(int32_t x, int32_t y); + + int32_t getMapWalkCost(const Creature* creature, AStarNode* node, + const Tile* neighbourTile, const Position& neighbourPos); + static int32_t getTileWalkCost(const Creature* creature, const Tile* tile); + int32_t getEstimatedDistance(int32_t x, int32_t y, int32_t xGoal, int32_t yGoal); + + private: + AStarNode nodes[MAX_NODES]; + std::bitset openNodes; + uint32_t curNode; +}; + +template class lessPointer : public std::binary_function +{ + public: + bool operator()(T*& t1, T*& t2) { + return *t1 < *t2; + } +}; + +typedef OTSERV_HASH_SET SpectatorVec; +typedef std::map > SpectatorCache; + +#define FLOOR_BITS 3 +#define FLOOR_SIZE (1 << FLOOR_BITS) +#define FLOOR_MASK (FLOOR_SIZE - 1) + +struct Floor { + Floor(); + ~Floor(); + Tile* tiles[FLOOR_SIZE][FLOOR_SIZE]; +}; + +class FrozenPathingConditionCall; +class QTreeLeafNode; + +class QTreeNode +{ + public: + QTreeNode(); + virtual ~QTreeNode(); + + bool isLeaf() const { + return m_isLeaf; + } + QTreeLeafNode* getLeaf(uint32_t x, uint32_t y); + static QTreeLeafNode* getLeafStatic(QTreeNode* root, uint32_t x, uint32_t y); + QTreeLeafNode* createLeaf(uint32_t x, uint32_t y, uint32_t level); + + protected: + bool m_isLeaf; + QTreeNode* m_child[4]; + + friend class Map; +}; + +class QTreeLeafNode : public QTreeNode +{ + public: + QTreeLeafNode(); + virtual ~QTreeLeafNode(); + + Floor* createFloor(uint32_t z); + Floor* getFloor(uint16_t z) { + return m_array[z]; + } + + QTreeLeafNode* stepSouth() { + return m_leafS; + } + QTreeLeafNode* stepEast() { + return m_leafE; + } + + void addCreature(Creature* c); + void removeCreature(Creature* c); + + protected: + static bool newLeaf; + QTreeLeafNode* m_leafS; + QTreeLeafNode* m_leafE; + Floor* m_array[MAP_MAX_LAYERS]; + CreatureVector creature_list; + CreatureVector player_list; + + friend class Map; + friend class QTreeNode; +}; + +/** + * Map class. + * Holds all the actual map-data + */ + +class Map +{ + public: + Map(); + ~Map(); + + static const int32_t maxViewportX = 11; //min value: maxClientViewportX + 1 + static const int32_t maxViewportY = 11; //min value: maxClientViewportY + 1 + static const int32_t maxClientViewportX = 8; + static const int32_t maxClientViewportY = 6; + + /** + * Load a map. + * \returns true if the map was loaded successfully + */ + bool loadMap(const std::string& identifier); + + /** + * Save a map. + * \param identifier file/database to save to + * \returns true if the map was saved successfully + */ + bool saveMap(); + + /** + * Get a single tile. + * \returns A pointer to that tile. + */ + Tile* getTile(int32_t x, int32_t y, int32_t z); + Tile* getTile(const Position& pos); + + uint32_t clean(); + + QTreeLeafNode* getLeaf(uint16_t x, uint16_t y) { + return root.getLeaf(x, y); + } + + /** + * Set a single tile. + * \param a tile to set for the position + */ + void setTile(int32_t _x, int32_t _y, int32_t _z, Tile* newTile); + void setTile(const Position& pos, Tile* newTile) { + setTile(pos.x, pos.y, pos.z, newTile); + } + + /** + * Place a creature on the map + * \param pos The position to place the creature + * \param creature Creature to place on the map + * \param extendedPos If true, the creature will in first-hand be placed 2 tiles away + * \param forceLogin If true, placing the creature will not fail becase of obstacles (creatures/chests) + */ + bool placeCreature(const Position& centerPos, Creature* creature, bool extendedPos = false, bool forceLogin = false); + + /** + * Remove a creature from the map. + * \param c Creature pointer to the creature to remove + */ + bool removeCreature(Creature* c); + + /** + * Checks if you can throw an object to that position + * \param fromPos from Source point + * \param toPos Destination point + * \param rangex maximum allowed range horizontially + * \param rangey maximum allowed range vertically + * \param checkLineOfSight checks if there is any blocking objects in the way + * \returns The result if you can throw there or not + */ + bool canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight = true, + int32_t rangex = Map::maxClientViewportX, int32_t rangey = Map::maxClientViewportY); + + /** + * Checks if path is clear from fromPos to toPos + * Notice: This only checks a straight line if the path is clear, for path finding use getPathTo. + * \param fromPos from Source point + * \param toPos Destination point + * \param floorCheck if true then view is not clear if fromPos.z is not the same as toPos.z + * \returns The result if there is no obstacles + */ + bool isSightClear(const Position& fromPos, const Position& toPos, bool floorCheck) const; + bool checkSightLine(const Position& fromPos, const Position& toPos) const; + + const Tile* canWalkTo(const Creature* creature, const Position& pos); + + /** + * Get the path to a specific position on the map. + * \param creature The creature that wants a path + * \param destPos The position we want a path calculated to + * \param listDir contains a list of directions to the destination + * \param maxDist Maximum distance from our current position to search, default: -1 (no limit) + * \returns returns true if a path was found + */ + bool getPathTo(const Creature* creature, const Position& destPos, + std::list& listDir, int32_t maxDist = -1); + + bool getPathMatching(const Creature* creature, std::list& dirList, + const FrozenPathingConditionCall& pathCondition, const FindPathParams& fpp); + + Waypoints waypoints; + + protected: + uint32_t mapWidth, mapHeight; + std::string spawnfile; + std::string housefile; + SpectatorCache spectatorCache; + SpectatorCache playersSpectatorCache; + + // Actually scans the map for spectators + void getSpectatorsInternal(SpectatorVec& list, const Position& centerPos, + int32_t minRangeX, int32_t maxRangeX, + int32_t minRangeY, int32_t maxRangeY, + int32_t minRangeZ, int32_t maxRangeZ, bool onlyPlayers); + + // Use this when a custom spectator vector is needed, this support many + // more parameters than the heavily cached version below. + void getSpectators(SpectatorVec& list, const Position& centerPos, bool multifloor = false, bool onlyPlayers = false, + int32_t minRangeX = 0, int32_t maxRangeX = 0, + int32_t minRangeY = 0, int32_t maxRangeY = 0); + // The returned SpectatorVec is a temporary and should not be kept around + // Take special heed in that the vector will be destroyed if any function + // that calls clearSpectatorCache is called. + const SpectatorVec& getSpectators(const Position& centerPos); + + void clearSpectatorCache(); + + QTreeNode root; + + struct RefreshBlock_t { + TileItemVector list; + uint64_t lastRefresh; + }; + + typedef std::map TileMap; + TileMap refreshTileMap; + + friend class Game; + + friend class IOMap; +}; + +#endif diff --git a/src/md5.cpp b/src/md5.cpp new file mode 100644 index 0000000000..549cd3e2b8 --- /dev/null +++ b/src/md5.cpp @@ -0,0 +1,262 @@ +// Free for all implementation of the MD5 hash algorithm + +/* + ********************************************************************** + ** MD5.cpp ** + ** ** + ** - Style modified by Tony Ray, January 2001 ** + ** Added support for randomizing initialization constants ** + ** - Style modified by Dominik Reichl, April 2003 ** + ** Optimized code ** + ** ** + ********************************************************************** + */ + +/* + ********************************************************************** + ** MD5.c ** + ** RSA Data Security, Inc. MD5 Message Digest Algorithm ** + ** Created: 2/17/90 RLR ** + ** Revised: 1/91 SRD,AJ,BSK,JT Reference C Version ** + ********************************************************************** + */ + +/* + ********************************************************************** + ** Copyright (C) 1990, RSA Data Security, Inc. All rights reserved. ** + ** ** + ** License to copy and use this software is granted provided that ** + ** it is identified as the "RSA Data Security, Inc. MD5 Message ** + ** Digest Algorithm" in all material mentioning or referencing this ** + ** software or this function. ** + ** ** + ** License is also granted to make and use derivative works ** + ** provided that such works are identified as "derived from the RSA ** + ** Data Security, Inc. MD5 Message Digest Algorithm" in all ** + ** material mentioning or referencing the derived work. ** + ** ** + ** RSA Data Security, Inc. makes no representations concerning ** + ** either the merchantability of this software or the suitability ** + ** of this software for any particular purpose. It is provided "as ** + ** is" without express or implied warranty of any kind. ** + ** ** + ** These notices must be retained in any copies of any part of this ** + ** documentation and/or software. ** + ********************************************************************** + */ + +#include "otpch.h" + +#include +#include + +#include "md5.h" + +/* Padding */ +static unsigned char MD5_PADDING[64] = { + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/* MD5_F, MD5_G and MD5_H are basic MD5 functions: selection, majority, parity */ +#define MD5_F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define MD5_G(x, y, z) (((x) & (z)) | ((y) & (~z))) +#define MD5_H(x, y, z) ((x) ^ (y) ^ (z)) +#define MD5_I(x, y, z) ((y) ^ ((x) | (~z))) + +/* ROTATE_LEFT rotates x left n bits */ +#ifndef ROTATE_LEFT +#define ROTATE_LEFT(x, n) ((((x) << (n)) & 0xffffffffU) | ((x) >> (32-(n)))) +#endif + +/* MD5_FF, MD5_GG, MD5_HH, and MD5_II transformations for rounds 1, 2, 3, and 4 */ +/* Rotation is separate from addition to prevent recomputation */ +#define MD5_FF(a, b, c, d, x, s, ac) {(a) += MD5_F ((b), (c), (d)) + (x) + (UINT4)(ac); (a) = ROTATE_LEFT ((a), (s)); (a) += (b); } +#define MD5_GG(a, b, c, d, x, s, ac) {(a) += MD5_G ((b), (c), (d)) + (x) + (UINT4)(ac); (a) = ROTATE_LEFT ((a), (s)); (a) += (b); } +#define MD5_HH(a, b, c, d, x, s, ac) {(a) += MD5_H ((b), (c), (d)) + (x) + (UINT4)(ac); (a) = ROTATE_LEFT ((a), (s)); (a) += (b); } +#define MD5_II(a, b, c, d, x, s, ac) {(a) += MD5_I ((b), (c), (d)) + (x) + (UINT4)(ac); (a) = ROTATE_LEFT ((a), (s)); (a) += (b); } + +/* Constants for transformation */ +#define MD5_S11 7 /* Round 1 */ +#define MD5_S12 12 +#define MD5_S13 17 +#define MD5_S14 22 +#define MD5_S21 5 /* Round 2 */ +#define MD5_S22 9 +#define MD5_S23 14 +#define MD5_S24 20 +#define MD5_S31 4 /* Round 3 */ +#define MD5_S32 11 +#define MD5_S33 16 +#define MD5_S34 23 +#define MD5_S41 6 /* Round 4 */ +#define MD5_S42 10 +#define MD5_S43 15 +#define MD5_S44 21 + +/* Basic MD5 step. MD5_Transform buf based on in */ +void MD5_Transform (UINT4* buf, UINT4* in) +{ + UINT4 a = buf[0], b = buf[1], c = buf[2], d = buf[3]; + + /* Round 1 */ + MD5_FF ( a, b, c, d, in[ 0], MD5_S11, (UINT4) 3614090360u); /* 1 */ + MD5_FF ( d, a, b, c, in[ 1], MD5_S12, (UINT4) 3905402710u); /* 2 */ + MD5_FF ( c, d, a, b, in[ 2], MD5_S13, (UINT4) 606105819u); /* 3 */ + MD5_FF ( b, c, d, a, in[ 3], MD5_S14, (UINT4) 3250441966u); /* 4 */ + MD5_FF ( a, b, c, d, in[ 4], MD5_S11, (UINT4) 4118548399u); /* 5 */ + MD5_FF ( d, a, b, c, in[ 5], MD5_S12, (UINT4) 1200080426u); /* 6 */ + MD5_FF ( c, d, a, b, in[ 6], MD5_S13, (UINT4) 2821735955u); /* 7 */ + MD5_FF ( b, c, d, a, in[ 7], MD5_S14, (UINT4) 4249261313u); /* 8 */ + MD5_FF ( a, b, c, d, in[ 8], MD5_S11, (UINT4) 1770035416u); /* 9 */ + MD5_FF ( d, a, b, c, in[ 9], MD5_S12, (UINT4) 2336552879u); /* 10 */ + MD5_FF ( c, d, a, b, in[10], MD5_S13, (UINT4) 4294925233u); /* 11 */ + MD5_FF ( b, c, d, a, in[11], MD5_S14, (UINT4) 2304563134u); /* 12 */ + MD5_FF ( a, b, c, d, in[12], MD5_S11, (UINT4) 1804603682u); /* 13 */ + MD5_FF ( d, a, b, c, in[13], MD5_S12, (UINT4) 4254626195u); /* 14 */ + MD5_FF ( c, d, a, b, in[14], MD5_S13, (UINT4) 2792965006u); /* 15 */ + MD5_FF ( b, c, d, a, in[15], MD5_S14, (UINT4) 1236535329u); /* 16 */ + + /* Round 2 */ + MD5_GG ( a, b, c, d, in[ 1], MD5_S21, (UINT4) 4129170786u); /* 17 */ + MD5_GG ( d, a, b, c, in[ 6], MD5_S22, (UINT4) 3225465664u); /* 18 */ + MD5_GG ( c, d, a, b, in[11], MD5_S23, (UINT4) 643717713u); /* 19 */ + MD5_GG ( b, c, d, a, in[ 0], MD5_S24, (UINT4) 3921069994u); /* 20 */ + MD5_GG ( a, b, c, d, in[ 5], MD5_S21, (UINT4) 3593408605u); /* 21 */ + MD5_GG ( d, a, b, c, in[10], MD5_S22, (UINT4) 38016083u); /* 22 */ + MD5_GG ( c, d, a, b, in[15], MD5_S23, (UINT4) 3634488961u); /* 23 */ + MD5_GG ( b, c, d, a, in[ 4], MD5_S24, (UINT4) 3889429448u); /* 24 */ + MD5_GG ( a, b, c, d, in[ 9], MD5_S21, (UINT4) 568446438u); /* 25 */ + MD5_GG ( d, a, b, c, in[14], MD5_S22, (UINT4) 3275163606u); /* 26 */ + MD5_GG ( c, d, a, b, in[ 3], MD5_S23, (UINT4) 4107603335u); /* 27 */ + MD5_GG ( b, c, d, a, in[ 8], MD5_S24, (UINT4) 1163531501u); /* 28 */ + MD5_GG ( a, b, c, d, in[13], MD5_S21, (UINT4) 2850285829u); /* 29 */ + MD5_GG ( d, a, b, c, in[ 2], MD5_S22, (UINT4) 4243563512u); /* 30 */ + MD5_GG ( c, d, a, b, in[ 7], MD5_S23, (UINT4) 1735328473u); /* 31 */ + MD5_GG ( b, c, d, a, in[12], MD5_S24, (UINT4) 2368359562u); /* 32 */ + + /* Round 3 */ + MD5_HH ( a, b, c, d, in[ 5], MD5_S31, (UINT4) 4294588738u); /* 33 */ + MD5_HH ( d, a, b, c, in[ 8], MD5_S32, (UINT4) 2272392833u); /* 34 */ + MD5_HH ( c, d, a, b, in[11], MD5_S33, (UINT4) 1839030562u); /* 35 */ + MD5_HH ( b, c, d, a, in[14], MD5_S34, (UINT4) 4259657740u); /* 36 */ + MD5_HH ( a, b, c, d, in[ 1], MD5_S31, (UINT4) 2763975236u); /* 37 */ + MD5_HH ( d, a, b, c, in[ 4], MD5_S32, (UINT4) 1272893353u); /* 38 */ + MD5_HH ( c, d, a, b, in[ 7], MD5_S33, (UINT4) 4139469664u); /* 39 */ + MD5_HH ( b, c, d, a, in[10], MD5_S34, (UINT4) 3200236656u); /* 40 */ + MD5_HH ( a, b, c, d, in[13], MD5_S31, (UINT4) 681279174u); /* 41 */ + MD5_HH ( d, a, b, c, in[ 0], MD5_S32, (UINT4) 3936430074u); /* 42 */ + MD5_HH ( c, d, a, b, in[ 3], MD5_S33, (UINT4) 3572445317u); /* 43 */ + MD5_HH ( b, c, d, a, in[ 6], MD5_S34, (UINT4) 76029189u); /* 44 */ + MD5_HH ( a, b, c, d, in[ 9], MD5_S31, (UINT4) 3654602809u); /* 45 */ + MD5_HH ( d, a, b, c, in[12], MD5_S32, (UINT4) 3873151461u); /* 46 */ + MD5_HH ( c, d, a, b, in[15], MD5_S33, (UINT4) 530742520u); /* 47 */ + MD5_HH ( b, c, d, a, in[ 2], MD5_S34, (UINT4) 3299628645u); /* 48 */ + + /* Round 4 */ + MD5_II ( a, b, c, d, in[ 0], MD5_S41, (UINT4) 4096336452u); /* 49 */ + MD5_II ( d, a, b, c, in[ 7], MD5_S42, (UINT4) 1126891415u); /* 50 */ + MD5_II ( c, d, a, b, in[14], MD5_S43, (UINT4) 2878612391u); /* 51 */ + MD5_II ( b, c, d, a, in[ 5], MD5_S44, (UINT4) 4237533241u); /* 52 */ + MD5_II ( a, b, c, d, in[12], MD5_S41, (UINT4) 1700485571u); /* 53 */ + MD5_II ( d, a, b, c, in[ 3], MD5_S42, (UINT4) 2399980690u); /* 54 */ + MD5_II ( c, d, a, b, in[10], MD5_S43, (UINT4) 4293915773u); /* 55 */ + MD5_II ( b, c, d, a, in[ 1], MD5_S44, (UINT4) 2240044497u); /* 56 */ + MD5_II ( a, b, c, d, in[ 8], MD5_S41, (UINT4) 1873313359u); /* 57 */ + MD5_II ( d, a, b, c, in[15], MD5_S42, (UINT4) 4264355552u); /* 58 */ + MD5_II ( c, d, a, b, in[ 6], MD5_S43, (UINT4) 2734768916u); /* 59 */ + MD5_II ( b, c, d, a, in[13], MD5_S44, (UINT4) 1309151649u); /* 60 */ + MD5_II ( a, b, c, d, in[ 4], MD5_S41, (UINT4) 4149444226u); /* 61 */ + MD5_II ( d, a, b, c, in[11], MD5_S42, (UINT4) 3174756917u); /* 62 */ + MD5_II ( c, d, a, b, in[ 2], MD5_S43, (UINT4) 718787259u); /* 63 */ + MD5_II ( b, c, d, a, in[ 9], MD5_S44, (UINT4) 3951481745u); /* 64 */ + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +// Set pseudoRandomNumber to zero for RFC MD5 implementation +void MD5Init (MD5_CTX* mdContext, unsigned long pseudoRandomNumber) +{ + mdContext->i[0] = mdContext->i[1] = (UINT4)0; + + /* Load magic initialization constants */ + mdContext->buf[0] = (UINT4)0x67452301 + (pseudoRandomNumber * 11); + mdContext->buf[1] = (UINT4)0xefcdab89 + (pseudoRandomNumber * 71); + mdContext->buf[2] = (UINT4)0x98badcfe + (pseudoRandomNumber * 37); + mdContext->buf[3] = (UINT4)0x10325476 + (pseudoRandomNumber * 97); +} + +void MD5Update (MD5_CTX* mdContext, const unsigned char* inBuf, unsigned int inLen) +{ + UINT4 in[16]; + int mdi = 0; + unsigned int i = 0, ii = 0; + + /* Compute number of bytes mod 64 */ + mdi = (int)((mdContext->i[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((mdContext->i[0] + ((UINT4)inLen << 3)) < mdContext->i[0]) { + mdContext->i[1]++; + } + + mdContext->i[0] += ((UINT4)inLen << 3); + mdContext->i[1] += ((UINT4)inLen >> 29); + + while (inLen--) { + /* Add new character to buffer, increment mdi */ + mdContext->in[mdi++] = *inBuf++; + + /* Transform if necessary */ + if (mdi == 0x40) { + for (i = 0, ii = 0; i < 16; i++, ii += 4) { + in[i] = (((UINT4)mdContext->in[ii + 3]) << 24) | (((UINT4)mdContext->in[ii + 2]) << 16) | (((UINT4)mdContext->in[ii + 1]) << 8) | ((UINT4)mdContext->in[ii]); + } + + MD5_Transform (mdContext->buf, in); + mdi = 0; + } + } +} + +void MD5Final (MD5_CTX* mdContext) +{ + UINT4 in[16]; + int mdi = 0; + unsigned int i = 0, ii = 0, padLen = 0; + + /* Save number of bits */ + in[14] = mdContext->i[0]; + in[15] = mdContext->i[1]; + + /* Compute number of bytes mod 64 */ + mdi = (int)((mdContext->i[0] >> 3) & 0x3F); + + /* Pad out to 56 mod 64 */ + padLen = (mdi < 56) ? (56 - mdi) : (120 - mdi); + MD5Update (mdContext, MD5_PADDING, padLen); + + /* Append length in bits and transform */ + for (i = 0, ii = 0; i < 14; i++, ii += 4) { + in[i] = (((UINT4)mdContext->in[ii + 3]) << 24) | (((UINT4)mdContext->in[ii + 2]) << 16) | (((UINT4)mdContext->in[ii + 1]) << 8) | ((UINT4)mdContext->in[ii]); + } + + MD5_Transform (mdContext->buf, in); + + /* Store buffer in digest */ + for (i = 0, ii = 0; i < 4; i++, ii += 4) { + mdContext->digest[ii] = (unsigned char)( mdContext->buf[i] & 0xFF); + mdContext->digest[ii + 1] = (unsigned char)((mdContext->buf[i] >> 8) & 0xFF); + mdContext->digest[ii + 2] = (unsigned char)((mdContext->buf[i] >> 16) & 0xFF); + mdContext->digest[ii + 3] = (unsigned char)((mdContext->buf[i] >> 24) & 0xFF); + } +} diff --git a/src/md5.h b/src/md5.h new file mode 100644 index 0000000000..2f74323302 --- /dev/null +++ b/src/md5.h @@ -0,0 +1,76 @@ +/* + ********************************************************************** + ** MD5.h ** + ** ** + ** - Style modified by Tony Ray, January 2001 ** + ** Added support for randomizing initialization constants ** + ** - Style modified by Dominik Reichl, September 2002 ** + ** Optimized code ** + ** ** + ********************************************************************** + */ + +/* + ********************************************************************** + ** MD5.h -- Header file for implementation of MD5 ** + ** RSA Data Security, Inc. MD5 Message Digest Algorithm ** + ** Created: 2/17/90 RLR ** + ** Revised: 12/27/90 SRD,AJ,BSK,JT Reference C version ** + ** Revised (for MD5): RLR 4/27/91 ** + ** -- G modified to have y&~z instead of y&z ** + ** -- FF, GG, HH modified to add in last register done ** + ** -- Access pattern: round 2 works mod 5, round 3 works mod 3 ** + ** -- distinct additive constant for each step ** + ** -- round 4 added, working mod 7 ** + ********************************************************************** + */ + +/* + ********************************************************************** + ** Copyright (C) 1990, RSA Data Security, Inc. All rights reserved. ** + ** ** + ** License to copy and use this software is granted provided that ** + ** it is identified as the "RSA Data Security, Inc. MD5 Message ** + ** Digest Algorithm" in all material mentioning or referencing this ** + ** software or this function. ** + ** ** + ** License is also granted to make and use derivative works ** + ** provided that such works are identified as "derived from the RSA ** + ** Data Security, Inc. MD5 Message Digest Algorithm" in all ** + ** material mentioning or referencing the derived work. ** + ** ** + ** RSA Data Security, Inc. makes no representations concerning ** + ** either the merchantability of this software or the suitability ** + ** of this software for any particular purpose. It is provided "as ** + ** is" without express or implied warranty of any kind. ** + ** ** + ** These notices must be retained in any copies of any part of this ** + ** documentation and/or software. ** + ********************************************************************** + */ + +#ifndef ___MD5_H___ +#define ___MD5_H___ + +#include "definitions.h" + +/* Typedef a 32 bit type */ +#ifndef UINT4 +typedef uint32_t UINT4; +#endif + +/* Data structure for MD5 (Message Digest) computation */ +typedef struct { + UINT4 i[2]; /* Number of _bits_ handled mod 2^64 */ + UINT4 buf[4]; /* Scratch buffer */ + unsigned char in[64]; /* Input buffer */ + unsigned char digest[16]; /* Actual digest after MD5Final call */ +} MD5_CTX; + +void MD5_Transform (UINT4* buf, UINT4* in); + +void MD5Init(MD5_CTX* mdContext, unsigned long pseudoRandomNumber = 0); +void MD5Update(MD5_CTX* mdContext, const unsigned char* inBuf, unsigned int inLen); +void MD5Final(MD5_CTX* mdContext); + +#endif /* ___MD5_H___ included */ diff --git a/src/modalwindow.cpp b/src/modalwindow.cpp new file mode 100644 index 0000000000..e27d35536b --- /dev/null +++ b/src/modalwindow.cpp @@ -0,0 +1,104 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "modalwindow.h" + +ModalWindow::ModalWindow(uint32_t id, const std::string& title, const std::string& message) + : defaultEnterButton(0xFF), defaultEscapeButton(0xFF), priority(false) +{ + this->id = id; + this->title = title; + this->message = message; +} + +std::string ModalWindow::getTitle() const +{ + return title; +} + +std::string ModalWindow::getMessage() const +{ + return message; +} + +uint32_t ModalWindow::getID() const +{ + return id; +} + +uint32_t ModalWindow::getButtonCount() const +{ + return buttons.size(); +} + +uint32_t ModalWindow::getChoiceCount() const +{ + return choices.size(); +} + +void ModalWindow::addChoice(uint8_t choiceId, const std::string& text) +{ + choices.push_back(make_pair(text, choiceId)); +} + +void ModalWindow::addButton(uint8_t buttonId, const std::string& text) +{ + buttons.push_back(make_pair(text, buttonId)); +} + +const ModalWindowChoiceList& ModalWindow::getButtons() const +{ + return buttons; +} + +const ModalWindowChoiceList& ModalWindow::getChoices() const +{ + return choices; +} + +void ModalWindow::setDefaultEnterButton(uint8_t enterButtonId) +{ + defaultEnterButton = enterButtonId; +} + +uint8_t ModalWindow::getDefaultEnterButton() const +{ + return defaultEnterButton; +} + +void ModalWindow::setDefaultEscapeButton(uint8_t escapeButtonId) +{ + defaultEscapeButton = escapeButtonId; +} + +uint8_t ModalWindow::getDefaultEscapeButton() const +{ + return defaultEscapeButton; +} + +bool ModalWindow::hasPriority() const +{ + return priority; +} + +void ModalWindow::setPriority(bool priority) +{ + this->priority = priority; +} diff --git a/src/modalwindow.h b/src/modalwindow.h new file mode 100644 index 0000000000..7012227e97 --- /dev/null +++ b/src/modalwindow.h @@ -0,0 +1,65 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __MODALWINDOW_H__ +#define __MODALWINDOW_H__ + +#include "definitions.h" + +#include +#include + +typedef std::pair ModalWindowChoice; +typedef std::list ModalWindowChoiceList; + +class ModalWindow +{ + public: + ModalWindow(uint32_t id, const std::string& title, const std::string& message); + ~ModalWindow() {} + + std::string getTitle() const; + std::string getMessage() const; + + uint32_t getID() const; + uint32_t getButtonCount() const; + uint32_t getChoiceCount() const; + void addChoice(uint8_t id, const std::string& text); + void addButton(uint8_t id, const std::string& text); + + void setDefaultEnterButton(uint8_t enterButtonId); + void setDefaultEscapeButton(uint8_t escapeButtonId); + uint8_t getDefaultEnterButton() const; + uint8_t getDefaultEscapeButton() const; + + bool hasPriority() const; + void setPriority(bool priority); + + const ModalWindowChoiceList& getButtons() const; + const ModalWindowChoiceList& getChoices() const; + + private: + uint32_t id; + std::string title, message; + uint8_t defaultEnterButton, defaultEscapeButton; + bool priority; + + ModalWindowChoiceList buttons, choices; +}; + +#endif diff --git a/src/monster.cpp b/src/monster.cpp new file mode 100644 index 0000000000..863fd6384f --- /dev/null +++ b/src/monster.cpp @@ -0,0 +1,2051 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include +#include +#include + +#include "monster.h" +#include "monsters.h" +#include "game.h" +#include "spells.h" +#include "combat.h" +#include "spawn.h" +#include "configmanager.h" + +extern Game g_game; +extern ConfigManager g_config; +extern Monsters g_monsters; + +AutoListMonster::listMonster; + +int32_t Monster::despawnRange; +int32_t Monster::despawnRadius; + +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ +uint32_t Monster::monsterCount = 0; +#endif + +Monster* Monster::createMonster(MonsterType* mType) +{ + return new Monster(mType); +} + +Monster* Monster::createMonster(const std::string& name) +{ + MonsterType* mType = g_monsters.getMonsterType(name); + + if (!mType) { + return NULL; + } + + return createMonster(mType); +} + +Monster::Monster(MonsterType* _mtype) : + Creature() +{ + isIdle = true; + isMasterInRange = false; + mType = _mtype; + spawn = NULL; + defaultOutfit = mType->outfit; + currentOutfit = mType->outfit; + + health = mType->health; + healthMax = mType->healthMax; + baseSpeed = mType->baseSpeed; + internalLight.level = mType->lightLevel; + internalLight.color = mType->lightColor; + + hiddenHealth = mType->hiddenHealth; + + minCombatValue = 0; + maxCombatValue = 0; + + targetTicks = 0; + targetChangeTicks = 0; + targetChangeCooldown = 0; + attackTicks = 0; + defenseTicks = 0; + yellTicks = 0; + extraMeleeAttack = false; + + strDescription = mType->nameDescription; + toLowerCaseString(strDescription); + + stepDuration = 0; + + lastMeleeAttack = 0; + + // register creature events + MonsterScriptList::iterator it; + + for (it = mType->scriptList.begin(); it != mType->scriptList.end(); ++it) { + if (!registerCreatureEvent(*it)) { + std::cout << "Warning: [Monster::Monster]. Unknown event name - " << *it << std::endl; + } + } + +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + monsterCount++; +#endif +} + +Monster::~Monster() +{ + clearTargetList(); + clearFriendList(); +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + monsterCount--; +#endif +} + +bool Monster::canSee(const Position& pos) const +{ + return Creature::canSee(getPosition(), pos, 9, 9); +} + +void Monster::onAttackedCreatureDisappear(bool isLogout) +{ + attackTicks = 0; + extraMeleeAttack = true; +} + +void Monster::onFollowCreatureDisappear(bool isLogout) +{ + // +} + +void Monster::onCreatureAppear(const Creature* creature, bool isLogin) +{ + Creature::onCreatureAppear(creature, isLogin); + + if (creature == this) { + //We just spawned lets look around to see who is there. + if (isSummon()) { + isMasterInRange = canSee(getMaster()->getPosition()); + } + + updateTargetList(); + updateIdleStatus(); + } else { + onCreatureEnter(const_cast(creature)); + } +} + +void Monster::onCreatureDisappear(const Creature* creature, uint32_t stackpos, bool isLogout) +{ + Creature::onCreatureDisappear(creature, stackpos, isLogout); + + if (creature == this) { + if (spawn) { + spawn->startSpawnCheck(); + } + + setIdle(true); + } else { + onCreatureLeave(const_cast(creature)); + } +} + +void Monster::onCreatureMove(const Creature* creature, const Tile* newTile, const Position& newPos, + const Tile* oldTile, const Position& oldPos, bool teleport) +{ + Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos, teleport); + + if (creature == this) { + if (isSummon()) { + isMasterInRange = canSee(getMaster()->getPosition()); + } + + updateTargetList(); + updateIdleStatus(); + } else { + bool canSeeNewPos = canSee(newPos); + bool canSeeOldPos = canSee(oldPos); + + if (canSeeNewPos && !canSeeOldPos) { + onCreatureEnter(const_cast(creature)); + } else if (!canSeeNewPos && canSeeOldPos) { + onCreatureLeave(const_cast(creature)); + } + + if (canSeeNewPos && isSummon() && getMaster() == creature) { + isMasterInRange = true; //Turn the summon on again + } + + updateIdleStatus(); + + if (!isSummon()) { + if (followCreature) { + const Position& followPosition = followCreature->getPosition(); + const Position& position = getPosition(); + + int32_t offset_x = std::abs(followPosition.x - position.x); + int32_t offset_y = std::abs(followPosition.y - position.y); + + if ((offset_x > 1 || offset_y > 1) && mType->changeTargetChance > 0) { + Direction dir = getDirectionTo(position, followPosition); + const Position& checkPosition = getNextPosition(dir, position); + + Tile* tile = g_game.getTile(checkPosition); + + if (tile) { + Creature* topCreature = tile->getTopCreature(); + + if (topCreature && followCreature != topCreature && isOpponent(topCreature)) { + selectTarget(topCreature); + } + } + } + } else if (isOpponent(creature)) { + //we have no target lets try pick this one + selectTarget(const_cast(creature)); + } + } + } +} + +void Monster::updateTargetList() +{ + CreatureHashSet::iterator friendIterator = friendList.begin(); + + while (friendIterator != friendList.end()) { + if ((*friendIterator)->getHealth() <= 0 || !canSee((*friendIterator)->getPosition())) { + (*friendIterator)->releaseThing2(); + friendIterator = friendList.erase(friendIterator); + } else { + ++friendIterator; + } + } + + CreatureList::iterator targetIterator = targetList.begin(); + + while (targetIterator != targetList.end()) { + if ((*targetIterator)->getHealth() <= 0 || !canSee((*targetIterator)->getPosition())) { + (*targetIterator)->releaseThing2(); + targetIterator = targetList.erase(targetIterator); + } else { + ++targetIterator; + } + } + + const SpectatorVec& list = g_game.getSpectators(getPosition()); + + for (SpectatorVec::const_iterator list_it = list.begin(), list_end = list.end(); list_it != list_end; ++list_it) { + Creature* spectator = *list_it; + + if (spectator != this && canSee(spectator->getPosition())) { + onCreatureFound(spectator); + } + } +} + +void Monster::clearTargetList() +{ + for (CreatureList::iterator it = targetList.begin(); it != targetList.end(); ++it) { + (*it)->releaseThing2(); + } + + targetList.clear(); +} + +void Monster::clearFriendList() +{ + for (CreatureHashSet::iterator it = friendList.begin(), end = friendList.end(); it != end; ++it) { + (*it)->releaseThing2(); + } + + friendList.clear(); +} + +void Monster::onCreatureFound(Creature* creature, bool pushFront /*= false*/) +{ + if (isFriend(creature)) { + assert(creature != this); + std::pair res = friendList.insert(creature); + + if (res.second) { + creature->useThing2(); + } + } + + if (isOpponent(creature)) { + assert(creature != this); + + if (std::find(targetList.begin(), targetList.end(), creature) == targetList.end()) { + creature->useThing2(); + + if (pushFront) { + targetList.push_front(creature); + } else { + targetList.push_back(creature); + } + } + } + + updateIdleStatus(); +} + +void Monster::onCreatureEnter(Creature* creature) +{ + // std::cout << "onCreatureEnter - " << creature->getName() << std::endl; + + if (getMaster() == creature) { + //Turn the summon on again + isMasterInRange = true; + updateIdleStatus(); + } + + onCreatureFound(creature, true); +} + +bool Monster::isFriend(const Creature* creature) +{ + if (isSummon() && getMaster()->getPlayer()) { + const Player* masterPlayer = getMaster()->getPlayer(); + const Player* tmpPlayer = NULL; + + if (creature->getPlayer()) { + tmpPlayer = creature->getPlayer(); + } else { + const Creature* creatureMaster = creature->getMaster(); + + if (creatureMaster && creatureMaster->getPlayer()) { + tmpPlayer = creatureMaster->getPlayer(); + } + } + + if (tmpPlayer && (tmpPlayer == getMaster() || masterPlayer->isPartner(tmpPlayer))) { + return true; + } + } else if (creature->getMonster() && !creature->isSummon()) { + return true; + } + + return false; +} + +bool Monster::isOpponent(const Creature* creature) +{ + if (isSummon() && getMaster()->getPlayer()) { + if (creature != getMaster()) { + return true; + } + } else { + if ((creature->getPlayer() && !creature->getPlayer()->hasFlag(PlayerFlag_IgnoredByMonsters)) || + (creature->getMaster() && creature->getMaster()->getPlayer())) { + return true; + } + } + + return false; +} + +void Monster::onCreatureLeave(Creature* creature) +{ + // std::cout << "onCreatureLeave - " << creature->getName() << std::endl; + + if (getMaster() == creature) { + //Turn the monster off until its master comes back + isMasterInRange = false; + updateIdleStatus(); + } + + //update friendList + if (isFriend(creature)) { + if (friendList.erase(creature) != 0) { + creature->releaseThing2(); + } + } + + //update targetList + if (isOpponent(creature)) { + CreatureList::iterator it = std::find(targetList.begin(), targetList.end(), creature); + + if (it != targetList.end()) { + (*it)->releaseThing2(); + targetList.erase(it); + + if (targetList.empty()) { + updateIdleStatus(); + } + } + } +} + +bool Monster::searchTarget(TargetSearchType_t searchType /*= TARGETSEARCH_DEFAULT*/) +{ + std::list resultList; + const Position& myPos = getPosition(); + + for (CreatureList::iterator it = targetList.begin(); it != targetList.end(); ++it) { + if (followCreature != (*it) && isTarget(*it)) { + if (searchType == TARGETSEARCH_RANDOM || canUseAttack(myPos, *it)) { + resultList.push_back(*it); + } + } + } + + switch (searchType) { + case TARGETSEARCH_NEAREAST: { + Creature* target = NULL; + int32_t minRange = -1; + + for (std::list::iterator it = resultList.begin(); it != resultList.end(); ++it) { + const Position& pos = (*it)->getPosition(); + + if (minRange == -1 || std::max(std::abs(myPos.x - pos.x), std::abs(myPos.y - pos.y)) < minRange) { + target = *it; + minRange = std::max(std::abs(myPos.x - pos.x), std::abs(myPos.y - pos.y)); + } + } + + if (target && selectTarget(target)) { + return true; + } + + break; + } + + case TARGETSEARCH_DEFAULT: + case TARGETSEARCH_ATTACKRANGE: + case TARGETSEARCH_RANDOM: + default: { + if (!resultList.empty()) { + uint32_t index = random_range(0, resultList.size() - 1); + CreatureList::iterator it = resultList.begin(); + std::advance(it, index); + return selectTarget(*it); + } + + if (searchType == TARGETSEARCH_ATTACKRANGE) { + return false; + } + + break; + } + } + + //lets just pick the first target in the list + for (CreatureList::iterator it = targetList.begin(); it != targetList.end(); ++it) { + if (followCreature != (*it) && selectTarget(*it)) { + return true; + } + } + + return false; +} + +void Monster::onFollowCreatureComplete(const Creature* creature) +{ + if (creature) { + CreatureList::iterator it = std::find(targetList.begin(), targetList.end(), creature); + Creature* target; + + if (it != targetList.end()) { + target = (*it); + targetList.erase(it); + + if (hasFollowPath) { + targetList.push_front(target); + } else if (!isSummon()) { + targetList.push_back(target); + } else { + target->releaseThing2(); + } + } + } +} + +BlockType_t Monster::blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, + bool checkDefense /* = false*/, bool checkArmor /* = false*/) +{ + BlockType_t blockType = Creature::blockHit(attacker, combatType, damage, checkDefense, checkArmor); + + if (damage != 0) { + int32_t elementMod = 0; + ElementMap::iterator it = mType->elementMap.find(combatType); + + if (it != mType->elementMap.end()) { + elementMod = it->second; + } + + if (elementMod != 0) { + damage = (int32_t)std::ceil(damage * ((float)(100 - elementMod) / 100)); + + if (damage <= 0) { + damage = 0; + blockType = BLOCK_DEFENSE; + } + } + } + + return blockType; +} + + +bool Monster::isTarget(Creature* creature) +{ + if (creature->isRemoved() || !creature->isAttackable() || + creature->getZone() == ZONE_PROTECTION || !canSeeCreature(creature)) { + return false; + } + + if (creature->getPosition().z != getPosition().z) { + return false; + } + + if (creature->getPlayer() && creature->getPlayer()->isLagging()) { + return false; + } + + return true; +} + +bool Monster::selectTarget(Creature* creature) +{ + if (!isTarget(creature)) { + return false; + } + + CreatureList::iterator it = std::find(targetList.begin(), targetList.end(), creature); + + if (it == targetList.end()) { + //Target not found in our target list. + return false; + } + + if (isHostile() || isSummon()) { + if (setAttackedCreature(creature) && !isSummon()) { + g_dispatcher.addTask(createTask( + boost::bind(&Game::checkCreatureAttack, &g_game, getID()))); + } + } + + return setFollowCreature(creature, true); +} + +void Monster::setIdle(bool _idle) +{ + if (isRemoved() || getHealth() <= 0) { + return; + } + + isIdle = _idle; + + if (!isIdle) { + g_game.addCreatureCheck(this); + } else { + onIdleStatus(); + clearTargetList(); + clearFriendList(); + g_game.removeCreatureCheck(this); + } +} + +void Monster::updateIdleStatus() +{ + bool idle = false; + + if (conditions.empty()) { + if (isSummon()) { + if (!isMasterInRange || (getMaster()->getMonster() && getMaster()->getMonster()->getIdleStatus())) { + idle = true; + } + } else if (targetList.empty()) { + idle = true; + } + } + + setIdle(idle); +} + +void Monster::onAddCondition(ConditionType_t type) +{ + if (type == CONDITION_FIRE || type == CONDITION_ENERGY || type == CONDITION_POISON) { + updateMapCache(); + } + + updateIdleStatus(); +} + +void Monster::onEndCondition(ConditionType_t type) +{ + if (type == CONDITION_FIRE || type == CONDITION_ENERGY || type == CONDITION_POISON) { + updateMapCache(); + } + + updateIdleStatus(); +} + +void Monster::onThink(uint32_t interval) +{ + Creature::onThink(interval); + + if (despawn()) { + g_game.internalTeleport(this, masterPos); + setIdle(true); + } else { + updateIdleStatus(); + + if (!isIdle) { + addEventWalk(); + + if (isSummon()) { + if (!attackedCreature) { + if (getMaster() && getMaster()->getAttackedCreature()) { + ///This happens if the monster is summoned during combat + selectTarget(getMaster()->getAttackedCreature()); + } else if (getMaster() != followCreature) { + //Our master has not ordered us to attack anything, lets follow him around instead. + setFollowCreature(getMaster()); + } + } else if (attackedCreature == this) { + setFollowCreature(NULL); + } else if (followCreature != attackedCreature) { + //This happens just after a master orders an attack, so lets follow it aswell. + setFollowCreature(attackedCreature); + } + } else if (!targetList.empty()) { + if (!followCreature || !hasFollowPath) { + searchTarget(); + } else if (isFleeing()) { + if (attackedCreature && !canUseAttack(getPosition(), attackedCreature)) { + searchTarget(TARGETSEARCH_ATTACKRANGE); + } + } + } + + onThinkTarget(interval); + onThinkYell(interval); + onThinkDefense(interval); + } + } +} + +void Monster::doAttacking(uint32_t interval) +{ + if (!attackedCreature || (isSummon() && attackedCreature == this)) { + return; + } + + if (attackedCreature->getPlayer() && attackedCreature->getPlayer()->isLagging()) { + onCreatureLeave(attackedCreature); + setAttackedCreature(NULL); + setFollowCreature(NULL); + onAttackedCreatureDisappear(false); + return; + } + + bool updateLook = true; + + resetTicks = interval != 0; + attackTicks += interval; + + const Position& myPos = getPosition(); + const Position& targetPos = attackedCreature->getPosition(); + + for (SpellList::iterator it = mType->spellAttackList.begin(); it != mType->spellAttackList.end(); ++it) { + bool inRange = false; + + if (canUseSpell(myPos, targetPos, *it, interval, inRange)) { + if (it->chance >= (uint32_t)random_range(1, 100)) { + if (updateLook) { + updateLookDirection(); + updateLook = false; + } + + minCombatValue = it->minCombatValue; + maxCombatValue = it->maxCombatValue; + it->spell->castSpell(this, attackedCreature); + + if (it->isMelee) { + extraMeleeAttack = false; + } + } + } + + if (!inRange && it->isMelee) { + //melee swing out of reach + extraMeleeAttack = true; + } + } + + if (updateLook) { + updateLookDirection(); + } + + if (resetTicks) { + attackTicks = 0; + } +} + +bool Monster::canUseAttack(const Position& pos, const Creature* target) const +{ + if (isHostile()) { + const Position& targetPos = target->getPosition(); + + for (SpellList::iterator it = mType->spellAttackList.begin(); it != mType->spellAttackList.end(); ++it) { + if (it->range != 0 && std::max(std::abs(pos.x - targetPos.x), std::abs(pos.y - targetPos.y)) <= (int32_t)it->range) { + return g_game.isSightClear(pos, targetPos, true); + } + } + + return false; + } + + return true; +} + +bool Monster::canUseSpell(const Position& pos, const Position& targetPos, + const spellBlock_t& sb, uint32_t interval, bool& inRange) +{ + inRange = true; + + if (sb.isMelee && isFleeing()) { + return false; + } + + if (extraMeleeAttack) { + lastMeleeAttack = OTSYS_TIME(); + } else if (sb.isMelee && (OTSYS_TIME() - lastMeleeAttack) < 1500) { + return false; + } + + if (!sb.isMelee || !extraMeleeAttack) { + if (sb.speed > attackTicks) { + resetTicks = false; + return false; + } + + if (attackTicks % sb.speed >= interval) { + //already used this spell for this round + return false; + } + } + + if (sb.range != 0 && std::max(std::abs(pos.x - targetPos.x), std::abs(pos.y - targetPos.y)) > (int32_t)sb.range) { + inRange = false; + return false; + } + + return true; +} + +void Monster::onThinkTarget(uint32_t interval) +{ + if (!isSummon()) { + if (mType->changeTargetSpeed > 0) { + bool canChangeTarget = true; + + if (targetChangeCooldown > 0) { + targetChangeCooldown -= interval; + + if (targetChangeCooldown <= 0) { + targetChangeCooldown = 0; + targetChangeTicks = (uint32_t)mType->changeTargetSpeed; + } else { + canChangeTarget = false; + } + } + + if (canChangeTarget) { + targetChangeTicks += interval; + + if (targetChangeTicks >= (uint32_t)mType->changeTargetSpeed) { + targetChangeTicks = 0; + targetChangeCooldown = (uint32_t)mType->changeTargetSpeed; + + if (mType->changeTargetChance >= random_range(1, 100)) { + if (mType->targetDistance <= 1) { + searchTarget(TARGETSEARCH_RANDOM); + } else { + searchTarget(TARGETSEARCH_NEAREAST); + } + } + } + } + } + } +} + +void Monster::onThinkDefense(uint32_t interval) +{ + resetTicks = true; + defenseTicks += interval; + + for (SpellList::iterator it = mType->spellDefenseList.begin(); it != mType->spellDefenseList.end(); ++it) { + if (it->speed > defenseTicks) { + resetTicks = false; + continue; + } + + if (defenseTicks % it->speed >= interval) { + //already used this spell for this round + continue; + } + + if ((it->chance >= (uint32_t)random_range(1, 100))) { + minCombatValue = it->minCombatValue; + maxCombatValue = it->maxCombatValue; + it->spell->castSpell(this, this); + } + } + + if (!isSummon() && (int32_t)summons.size() < mType->maxSummons) { + for (SummonList::iterator it = mType->summonList.begin(); it != mType->summonList.end(); ++it) { + if (it->speed > defenseTicks) { + resetTicks = false; + continue; + } + + if ((int32_t)summons.size() >= mType->maxSummons) { + continue; + } + + if (defenseTicks % it->speed >= interval) { + //already used this spell for this round + continue; + } + + if ((it->chance >= (uint32_t)random_range(1, 100))) { + Monster* summon = Monster::createMonster(it->name); + + if (summon) { + const Position& summonPos = getPosition(); + + addSummon(summon); + + if (!g_game.placeCreature(summon, summonPos)) { + removeSummon(summon); + } else { + g_game.addMagicEffect(getPosition(), NM_ME_MAGIC_ENERGY); + g_game.addMagicEffect(summon->getPosition(), NM_ME_TELEPORT); + } + } + } + } + } + + if (resetTicks) { + defenseTicks = 0; + } +} + +void Monster::onThinkYell(uint32_t interval) +{ + if (mType->yellSpeedTicks > 0) { + yellTicks += interval; + + if (yellTicks >= mType->yellSpeedTicks) { + yellTicks = 0; + + if (!mType->voiceVector.empty() && (mType->yellChance >= (uint32_t)random_range(1, 100))) { + uint32_t index = random_range(0, mType->voiceVector.size() - 1); + const voiceBlock_t& vb = mType->voiceVector[index]; + + if (vb.yellText) { + g_game.internalCreatureSay(this, SPEAK_MONSTER_YELL, vb.text, false); + } else { + g_game.internalCreatureSay(this, SPEAK_MONSTER_SAY, vb.text, false); + } + } + } + } +} + +void Monster::onWalk() +{ + Creature::onWalk(); +} + +bool Monster::pushItem(Item* item, int32_t radius) +{ + const Position& centerPos = item->getPosition(); + + typedef std::pair relPair; + std::vector relList; + relList.push_back(relPair(-1, -1)); + relList.push_back(relPair(-1, 0)); + relList.push_back(relPair(-1, 1)); + relList.push_back(relPair(0, -1)); + relList.push_back(relPair(0, 1)); + relList.push_back(relPair(1, -1)); + relList.push_back(relPair(1, 0)); + relList.push_back(relPair(1, 1)); + + std::random_shuffle(relList.begin(), relList.end()); + + Position tryPos; + + for (int32_t n = 1; n <= radius; ++n) { + for (std::vector::iterator it = relList.begin(); it != relList.end(); ++it) { + int32_t dx = it->first * n; + int32_t dy = it->second * n; + + tryPos = centerPos; + tryPos.x = tryPos.x + dx; + tryPos.y = tryPos.y + dy; + + Tile* tile = g_game.getTile(tryPos.x, tryPos.y, tryPos.z); + + if (tile && g_game.canThrowObjectTo(centerPos, tryPos)) { + if (g_game.internalMoveItem(item->getParent(), tile, + INDEX_WHEREEVER, item, item->getItemCount(), NULL) == RET_NOERROR) { + return true; + } + } + } + } + + return false; +} + +void Monster::pushItems(Tile* tile) +{ + //We can not use iterators here since we can push the item to another tile + //which will invalidate the iterator. + //start from the end to minimize the amount of traffic + if (TileItemVector* items = tile->getItemList()) { + uint32_t moveCount = 0; + uint32_t removeCount = 0; + + int32_t downItemSize = tile->getDownItemCount(); + + for (int32_t i = downItemSize - 1; i >= 0; --i) { + assert(i >= 0 && i < downItemSize); + Item* item = items->at(i); + + if (item && item->hasProperty(MOVEABLE) && (item->hasProperty(BLOCKPATH) + || item->hasProperty(BLOCKSOLID))) { + if (moveCount < 20 && pushItem(item, 1)) { + moveCount++; + } else if (g_game.internalRemoveItem(item) == RET_NOERROR) { + ++removeCount; + } + } + } + + if (removeCount > 0) { + g_game.addMagicEffect(tile->getPosition(), NM_ME_POFF); + } + } +} + +bool Monster::pushCreature(Creature* creature) +{ + Position monsterPos = creature->getPosition(); + + std::vector dirList; + dirList.push_back(NORTH); + dirList.push_back(SOUTH); + dirList.push_back(WEST); + dirList.push_back(EAST); + + std::random_shuffle(dirList.begin(), dirList.end()); + + for (std::vector::iterator it = dirList.begin(); it != dirList.end(); ++it) { + const Position& tryPos = Spells::getCasterPosition(creature, *it); + Tile* toTile = g_game.getTile(tryPos.x, tryPos.y, tryPos.z); + + if (toTile && !toTile->hasProperty(BLOCKPATH)) { + if (g_game.internalMoveCreature(creature, *it) == RET_NOERROR) { + return true; + } + } + } + + return false; +} + +void Monster::pushCreatures(Tile* tile) +{ + //We can not use iterators here since we can push a creature to another tile + //which will invalidate the iterator. + if (CreatureVector* creatures = tile->getCreatures()) { + uint32_t removeCount = 0; + Monster* lastPushedMonster = NULL; + + for (uint32_t i = 0; i < creatures->size();) { + Monster* monster = creatures->at(i)->getMonster(); + + if (monster && monster->isPushable()) { + if (monster != lastPushedMonster && pushCreature(monster)) { + lastPushedMonster = monster; + continue; + } + + monster->changeHealth(-monster->getHealth()); + monster->setDropLoot(false); + removeCount++; + } + + ++i; + } + + if (removeCount > 0) { + g_game.addMagicEffect(tile->getPosition(), NM_ME_BLOCKHIT); + } + } +} + +bool Monster::getNextStep(Direction& dir, uint32_t& flags) +{ + if (isIdle || getHealth() <= 0) { + //we dont have anyone watching might aswell stop walking + eventWalk = 0; + return false; + } + + bool result = false; + + if ((!followCreature || !hasFollowPath) && !isSummon()) { + if (followCreature || getTimeSinceLastMove() > 1000) { + //choose a random direction + result = getRandomStep(getPosition(), dir); + } + } else if (isSummon() || followCreature) { + result = Creature::getNextStep(dir, flags); + + if (result) { + flags |= FLAG_PATHFINDING; + } else { + //target dancing + if (attackedCreature && attackedCreature == followCreature) { + if (isFleeing()) { + result = getDanceStep(getPosition(), dir, false, false); + } else if (mType->staticAttackChance < (uint32_t)random_range(1, 100)) { + result = getDanceStep(getPosition(), dir); + } + } + } + } + + if (result && (canPushItems() || canPushCreatures())) { + const Position& pos = Spells::getCasterPosition(this, dir); + Tile* tile = g_game.getTile(pos.x, pos.y, pos.z); + + if (tile) { + if (canPushItems()) { + pushItems(tile); + } + + if (canPushCreatures()) { + pushCreatures(tile); + } + } + } + + return result; +} + +bool Monster::getRandomStep(const Position& creaturePos, Direction& dir) +{ + std::vector dirList; + + dirList.push_back(NORTH); + dirList.push_back(SOUTH); + dirList.push_back(WEST); + dirList.push_back(EAST); + + std::random_shuffle(dirList.begin(), dirList.end()); + + for (std::vector::iterator it = dirList.begin(); it != dirList.end(); ++it) { + if (canWalkTo(creaturePos, *it)) { + dir = *it; + return true; + } + } + + return false; +} + +bool Monster::getDanceStep(const Position& creaturePos, Direction& dir, + bool keepAttack /*= true*/, bool keepDistance /*= true*/) +{ + bool canDoAttackNow = canUseAttack(creaturePos, attackedCreature); + + assert(attackedCreature != NULL); + const Position& centerPos = attackedCreature->getPosition(); + uint32_t centerToDist = std::max(std::abs(creaturePos.x - centerPos.x), std::abs(creaturePos.y - centerPos.y)); + uint32_t tmpDist; + + std::vector dirList; + + if (!keepDistance || creaturePos.y - centerPos.y >= 0) { + tmpDist = std::max(std::abs((creaturePos.x) - centerPos.x), std::abs((creaturePos.y - 1) - centerPos.y)); + + if (tmpDist == centerToDist && canWalkTo(creaturePos, NORTH)) { + bool result = true; + + if (keepAttack) { + result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x, creaturePos.y - 1, creaturePos.z), attackedCreature)); + } + + if (result) { + dirList.push_back(NORTH); + } + } + } + + if (!keepDistance || creaturePos.y - centerPos.y <= 0) { + tmpDist = std::max(std::abs((creaturePos.x) - centerPos.x), std::abs((creaturePos.y + 1) - centerPos.y)); + + if (tmpDist == centerToDist && canWalkTo(creaturePos, SOUTH)) { + bool result = true; + + if (keepAttack) { + result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x, creaturePos.y + 1, creaturePos.z), attackedCreature)); + } + + if (result) { + dirList.push_back(SOUTH); + } + } + } + + if (!keepDistance || creaturePos.x - centerPos.x <= 0) { + tmpDist = std::max(std::abs((creaturePos.x + 1) - centerPos.x), std::abs(creaturePos.y - centerPos.y)); + + if (tmpDist == centerToDist && canWalkTo(creaturePos, EAST)) { + bool result = true; + + if (keepAttack) { + result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x + 1, creaturePos.y, creaturePos.z), attackedCreature)); + } + + if (result) { + dirList.push_back(EAST); + } + } + } + + if (!keepDistance || creaturePos.x - centerPos.x >= 0) { + tmpDist = std::max(std::abs((creaturePos.x - 1) - centerPos.x), std::abs(creaturePos.y - centerPos.y)); + + if (tmpDist == centerToDist && canWalkTo(creaturePos, WEST)) { + bool result = true; + + if (keepAttack) { + result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x - 1, creaturePos.y, creaturePos.z), attackedCreature)); + } + + if (result) { + dirList.push_back(WEST); + } + } + } + + if (!dirList.empty()) { + std::random_shuffle(dirList.begin(), dirList.end()); + dir = dirList[random_range(0, dirList.size() - 1)]; + return true; + } + + return false; +} + +bool Monster::getDistanceStep(const Position& targetPos, Direction& dir, bool flee /* = false */) +{ + /* + OK, understand this - I didn't intend to write as low code as possible, I wanted to make it easily readable so everyone can understand it, it basically works + almost like a real Tibia for fleeing monsters (like Dragon on low health) and distance monsters (like orc spearman), they will now choose the path almost exactly like the real ones. + + I'm very well aware that the whole code can be shorter and better programmed, in fact - I'm hoping for someone to do that for me, I basically just described how should it work and how does it work on real Tibia. + */ + + const Position& creaturePos = getPosition(); + int32_t distance = std::abs(creaturePos.x - targetPos.x) > std::abs(creaturePos.y - targetPos.y) ? std::abs(creaturePos.x - targetPos.x) : std::abs(creaturePos.y - targetPos.y); + + if (!flee && (distance > mType->targetDistance || !g_game.isSightClear(creaturePos, targetPos, true))) { + return false; // let the A* calculate it + } else if (!flee && distance == mType->targetDistance) { + return true; // we don't really care here, since it's what we wanted to reach (a dancestep will take of dancing in that position) + } + + int offsetx = creaturePos.x - targetPos.x; + int offsety = creaturePos.y - targetPos.y; + + if (std::abs(offsetx) <= 1 && std::abs(offsety) <= 1) { + //seems like a target is near, it this case we need to slow down our movements (as a monster) + if (stepDuration < 2) { + stepDuration++; + } + } else if (stepDuration > 0) { + stepDuration--; + } + + if (offsetx == 0 && offsety == 0) { + return getRandomStep(creaturePos, dir); // player is "on" the monster so let's get some random step and rest will be taken care later. + } + + if (std::abs(offsetx) == std::abs(offsety)) { + //player is diagonal to the monster + if (offsetx >= 1 && offsety >= 1) { + // player is NW + //escape to SE, S or E [and some extra] + bool s = canWalkTo(creaturePos, SOUTH); + bool e = canWalkTo(creaturePos, EAST); + + if (s && e) { + dir = random_range(1, 2) == 1 ? SOUTH : EAST; + return true; + } else if (s) { + dir = SOUTH; + return true; + } else if (e) { + dir = EAST; + return true; + } else if (canWalkTo(creaturePos, SOUTHEAST)) { + dir = SOUTHEAST; + return true; + } + + /* fleeing */ + bool n = canWalkTo(creaturePos, NORTH); + bool w = canWalkTo(creaturePos, WEST); + + if (flee) { + if (n && w) { + dir = random_range(1, 2) == 1 ? NORTH : WEST; + return true; + } else if (n) { + dir = NORTH; + return true; + } else if (w) { + dir = WEST; + return true; + } + } + + /* end of fleeing */ + + if (w && canWalkTo(creaturePos, SOUTHWEST)) { + dir = WEST; + } else if (n && canWalkTo(creaturePos, NORTHEAST)) { + dir = NORTH; + } + + return true; + } else if (offsetx <= -1 && offsety <= -1) { + //player is SE + //escape to NW , W or N [and some extra] + bool w = canWalkTo(creaturePos, WEST); + bool n = canWalkTo(creaturePos, NORTH); + + if (w && n) { + dir = random_range(1, 2) == 1 ? WEST : NORTH; + return true; + } else if (w) { + dir = WEST; + return true; + } else if (n) { + dir = NORTH; + return true; + } + + if (canWalkTo(creaturePos, NORTHWEST)) { + dir = NORTHWEST; + return true; + } + + /* fleeing */ + bool s = canWalkTo(creaturePos, SOUTH); + bool e = canWalkTo(creaturePos, EAST); + + if (flee) { + if (s && e) { + dir = random_range(1, 2) == 1 ? SOUTH : EAST; + return true; + } else if (s) { + dir = SOUTH; + return true; + } else if (e) { + dir = EAST; + return true; + } + } + + /* end of fleeing */ + + if (s && canWalkTo(creaturePos, SOUTHWEST)) { + dir = SOUTH; + } else if (e && canWalkTo(creaturePos, NORTHEAST)) { + dir = EAST; + } + + return true; + } else if (offsetx >= 1 && offsety <= -1) { + //player is SW + //escape to NE, N, E [and some extra] + bool n = canWalkTo(creaturePos, NORTH); + bool e = canWalkTo(creaturePos, EAST); + + if (n && e) { + dir = random_range(1, 2) == 1 ? NORTH : EAST; + return true; + } else if (n) { + dir = NORTH; + return true; + } else if (e) { + dir = EAST; + return true; + } + + if (canWalkTo(creaturePos, NORTHEAST)) { + dir = NORTHEAST; + return true; + } + + /* fleeing */ + bool s = canWalkTo(creaturePos, SOUTH); + bool w = canWalkTo(creaturePos, WEST); + + if (flee) { + if (s && w) { + dir = random_range(1, 2) == 1 ? SOUTH : WEST; + return true; + } else if (s) { + dir = SOUTH; + return true; + } else if (w) { + dir = WEST; + return true; + } + } + + /* end of fleeing */ + + if (w && canWalkTo(creaturePos, NORTHWEST)) { + dir = WEST; + } else if (s && canWalkTo(creaturePos, SOUTHEAST)) { + dir = SOUTH; + } + + return true; + } else if (offsetx <= -1 && offsety >= 1) { + // player is NE + //escape to SW, S, W [and some extra] + bool w = canWalkTo(creaturePos, WEST); + bool s = canWalkTo(creaturePos, SOUTH); + + if (w && s) { + dir = random_range(1, 2) == 1 ? WEST : SOUTH; + return true; + } else if (w) { + dir = WEST; + return true; + } else if (s) { + dir = SOUTH; + return true; + } else if (canWalkTo(creaturePos, SOUTHWEST)) { + dir = SOUTHWEST; + return true; + } + + /* fleeing */ + bool n = canWalkTo(creaturePos, NORTH); + bool e = canWalkTo(creaturePos, EAST); + + if (flee) { + if (n && e) { + dir = random_range(1, 2) == 1 ? NORTH : EAST; + return true; + } else if (n) { + dir = NORTH; + return true; + } else if (e) { + dir = EAST; + return true; + } + } + + /* end of fleeing */ + + if (e && canWalkTo(creaturePos, SOUTHEAST)) { + dir = EAST; + } else if (n && canWalkTo(creaturePos, NORTHWEST)) { + dir = NORTH; + } + + return true; + } + } + + //Now let's decide where the player is located to the monster (what direction) so we can decide where to escape. + if (std::abs(offsety) > std::abs(offsetx)) { + Direction playerDir = offsety < 0 ? SOUTH : NORTH; + + switch (playerDir) { + case NORTH: { + // Player is to the NORTH, so obviously we need to check if we can go SOUTH, if not then let's choose WEST or EAST and again if we can't we need to decide about some diagonal movements. + if (canWalkTo(creaturePos, SOUTH)) { + dir = SOUTH; + return true; + } + + bool w = canWalkTo(creaturePos, WEST); + bool e = canWalkTo(creaturePos, EAST); + + if (w && e && offsetx == 0) { + dir = random_range(1, 2) == 1 ? WEST : EAST; + return true; + } else if (w && offsetx <= 0) { + dir = WEST; + return true; + } else if (e && offsetx >= 0) { + dir = EAST; + return true; + } + + /* fleeing */ + if (flee) { + if (w && e) { + dir = random_range(1, 2) == 1 ? WEST : EAST; + return true; + } else if (w) { + dir = WEST; + return true; + } else if (e) { + dir = EAST; + return true; + } + } + + /* end of fleeing */ + + bool sw = canWalkTo(creaturePos, SOUTHWEST); + bool se = canWalkTo(creaturePos, SOUTHEAST); + + if (sw || se) { + // we can move both dirs + if (sw && se) { + dir = random_range(1, 2) == 1 ? SOUTHWEST : SOUTHEAST; + return true; + } else if (sw && !w) { + dir = SOUTHWEST; + return true; + } else if (w) { + dir = WEST; + return true; + } else if (se && !e) { + dir = SOUTHEAST; + return true; + } else if (e) { + dir = EAST; + return true; + } + } + + /* fleeing */ + if (flee && canWalkTo(creaturePos, NORTH)) { + // towards player, yea + dir = NORTH; + return true; + } + + /* end of fleeing */ + break; + } + + case SOUTH: { + if (canWalkTo(creaturePos, NORTH)) { + dir = NORTH; + return true; + } + + bool w = canWalkTo(creaturePos, WEST); + bool e = canWalkTo(creaturePos, EAST); + + if (w && e && offsetx == 0) { + dir = random_range(1, 2) == 1 ? WEST : EAST; + return true; + } else if (w && offsetx <= 0) { + dir = WEST; + return true; + } else if (e && offsetx >= 0) { + dir = EAST; + return true; + } + + /* fleeing */ + if (flee) { + if (w && e) { + dir = random_range(1, 2) == 1 ? WEST : EAST; + return true; + } else if (w) { + dir = WEST; + return true; + } else if (e) { + dir = EAST; + return true; + } + } + + /* end of fleeing */ + + bool nw = canWalkTo(creaturePos, NORTHWEST); + bool ne = canWalkTo(creaturePos, NORTHEAST); + + if (nw || ne) { + // we can move both dirs + if (nw && ne) { + dir = random_range(1, 2) == 1 ? NORTHWEST : NORTHEAST; + return true; + } else if (nw && !w) { + dir = NORTHWEST; + return true; + } else if (w) { + dir = WEST; + return true; + } else if (ne && !e) { + dir = NORTHEAST; + return true; + } else if (e) { + dir = EAST; + return true; + } + } + + /* fleeing */ + if (flee && canWalkTo(creaturePos, SOUTH)) { + // towards player, yea + dir = SOUTH; + return true; + } + + /* end of fleeing */ + break; + } + + default: + break; + } + } else { + Direction playerDir = offsetx < 0 ? EAST : WEST; + + switch (playerDir) { + case WEST: { + if (canWalkTo(creaturePos, EAST)) { + dir = EAST; + return true; + } + + bool n = canWalkTo(creaturePos, NORTH); + bool s = canWalkTo(creaturePos, SOUTH); + + if (n && s && offsety == 0) { + dir = random_range(1, 2) == 1 ? NORTH : SOUTH; + return true; + } else if (n && offsety <= 0) { + dir = NORTH; + return true; + } else if (s && offsety >= 0) { + dir = SOUTH; + return true; + } + + /* fleeing */ + if (flee) { + if (n && s) { + dir = random_range(1, 2) == 1 ? NORTH : SOUTH; + return true; + } else if (n) { + dir = NORTH; + return true; + } else if (s) { + dir = SOUTH; + return true; + } + } + + /* end of fleeing */ + + bool se = canWalkTo(creaturePos, SOUTHEAST); + bool ne = canWalkTo(creaturePos, NORTHEAST); + + if (se || ne) { + if (se && ne) { + dir = random_range(1, 2) == 1 ? SOUTHEAST : NORTHEAST; + return true; + } else if (se && !s) { + dir = SOUTHEAST; + return true; + } else if (s) { + dir = SOUTH; + return true; + } else if (ne && !n) { + dir = NORTHEAST; + return true; + } else if (n) { + dir = NORTH; + return true; + } + } + + /* fleeing */ + if (flee && canWalkTo(creaturePos, WEST)) { + // towards player, yea + dir = WEST; + return true; + } + + /* end of fleeing */ + break; + } + + case EAST: { + if (canWalkTo(creaturePos, WEST)) { + dir = WEST; + return true; + } + + bool n = canWalkTo(creaturePos, NORTH); + bool s = canWalkTo(creaturePos, SOUTH); + + if (n && s && offsety == 0) { + dir = random_range(1, 2) == 1 ? NORTH : SOUTH; + return true; + } else if (n && offsety <= 0) { + dir = NORTH; + return true; + } else if (s && offsety >= 0) { + dir = SOUTH; + return true; + } + + /* fleeing */ + if (flee) { + if (n && s) { + dir = random_range(1, 2) == 1 ? NORTH : SOUTH; + return true; + } else if (n) { + dir = NORTH; + return true; + } else if (s) { + dir = SOUTH; + return true; + } + } + + /* end of fleeing */ + + bool nw = canWalkTo(creaturePos, NORTHWEST); + bool sw = canWalkTo(creaturePos, SOUTHWEST); + + if (nw || sw) { + if (nw && sw) { + dir = random_range(1, 2) == 1 ? NORTHWEST : SOUTHWEST; + return true; + } else if (nw && !n) { + dir = NORTHWEST; + return true; + } else if (n) { + dir = NORTH; + return true; + } else if (sw && !s) { + dir = SOUTHWEST; + return true; + } else if (s) { + dir = SOUTH; + return true; + } + } + + /* fleeing */ + if (flee && canWalkTo(creaturePos, EAST)) { + // towards player, yea + dir = EAST; + return true; + } + + /* end of fleeing */ + break; + } + + default: + break; + } + } + + return true; +} + +bool Monster::isInSpawnRange(const Position& toPos) +{ + if (masterRadius == -1) { + return true; + } + + return !inDespawnRange(toPos); +} + +bool Monster::canWalkTo(Position pos, Direction dir) +{ + switch (dir) { + case NORTH: + pos.y += -1; + break; + case WEST: + pos.x += -1; + break; + case EAST: + pos.x += 1; + break; + case SOUTH: + pos.y += 1; + break; + default: + break; + } + + if (isInSpawnRange(pos)) { + if (getWalkCache(pos) == 0) { + return false; + } + + Tile* tile = g_game.getTile(pos.x, pos.y, pos.z); + + if (tile && tile->getTopVisibleCreature(this) == NULL && tile->__queryAdd(0, this, 1, FLAG_PATHFINDING) == RET_NOERROR) { + return true; + } + } + + return false; +} + +void Monster::death() +{ + setAttackedCreature(NULL); + + for (std::list::iterator cit = summons.begin(); cit != summons.end(); ++cit) { + (*cit)->changeHealth(-(*cit)->getHealth()); + (*cit)->setMaster(NULL); + (*cit)->releaseThing2(); + } + + summons.clear(); + + clearTargetList(); + clearFriendList(); + onIdleStatus(); +} + +Item* Monster::getCorpse() +{ + Item* corpse = Creature::getCorpse(); + + if (corpse) { + Creature* lastHitCreature_ = NULL; + Creature* mostDamageCreature = NULL; + + if (getKillers(&lastHitCreature_, &mostDamageCreature) && mostDamageCreature) { + uint32_t corpseOwner = 0; + + if (mostDamageCreature->getPlayer()) { + corpseOwner = mostDamageCreature->getID(); + } else { + const Creature* mostDamageCreatureMaster = mostDamageCreature->getMaster(); + + if (mostDamageCreatureMaster && mostDamageCreatureMaster->getPlayer()) { + corpseOwner = mostDamageCreatureMaster->getID(); + } + } + + if (corpseOwner != 0) { + corpse->setCorpseOwner(corpseOwner); + } + } + } + + return corpse; +} + +bool Monster::inDespawnRange(const Position& pos) +{ + if (spawn) { + if (Monster::despawnRadius == 0) { + return false; + } + + if (!Spawns::getInstance()->isInZone(masterPos, Monster::despawnRadius, pos)) { + return true; + } + + if (Monster::despawnRange == 0) { + return false; + } + + if (std::abs(pos.z - masterPos.z) > Monster::despawnRange) { + return true; + } + + return false; + } + + return false; +} + +bool Monster::despawn() +{ + return inDespawnRange(getPosition()); +} + +bool Monster::getCombatValues(int32_t& min, int32_t& max) +{ + if (minCombatValue == 0 && maxCombatValue == 0) { + return false; + } + + min = minCombatValue; + max = maxCombatValue; + return true; +} + +void Monster::updateLookDirection() +{ + Direction newDir = getDirection(); + + if (attackedCreature) { + const Position& pos = getPosition(); + const Position& attackedCreaturePos = attackedCreature->getPosition(); + int32_t dx = attackedCreaturePos.x - pos.x; + int32_t dy = attackedCreaturePos.y - pos.y; + + if (std::abs(dx) > std::abs(dy)) { + //look EAST/WEST + if (dx < 0) { + newDir = WEST; + } else { + newDir = EAST; + } + } else if (std::abs(dx) < std::abs(dy)) { + //look NORTH/SOUTH + if (dy < 0) { + newDir = NORTH; + } else { + newDir = SOUTH; + } + } else { + if (dx < 0 && dy < 0) { + if (getDirection() == SOUTH) { + newDir = WEST; + } else if (getDirection() == EAST) { + newDir = NORTH; + } + } else if (dx < 0 && dy > 0) { + if (getDirection() == NORTH) { + newDir = WEST; + } else if (getDirection() == EAST) { + newDir = SOUTH; + } + } else if (dx > 0 && dy < 0) { + if (getDirection() == SOUTH) { + newDir = EAST; + } else if (getDirection() == WEST) { + newDir = NORTH; + } + } else { + if (getDirection() == NORTH) { + newDir = EAST; + } else if (getDirection() == WEST) { + newDir = SOUTH; + } + } + } + } + + g_game.internalCreatureTurn(this, newDir); +} + +void Monster::dropLoot(Container* corpse) +{ + if (corpse && lootDrop) { + mType->createLoot(corpse); + } +} + +void Monster::setNormalCreatureLight() +{ + internalLight.level = mType->lightLevel; + internalLight.color = mType->lightColor; +} + +void Monster::drainHealth(Creature* attacker, CombatType_t combatType, int32_t damage) +{ + Creature::drainHealth(attacker, combatType, damage); + + if (isInvisible()) { + removeCondition(CONDITION_INVISIBLE); + } +} + +void Monster::changeHealth(int32_t healthChange, bool sendHealthChange/* = true*/) +{ + //In case a player with ignore flag set attacks the monster + setIdle(false); + Creature::changeHealth(healthChange, sendHealthChange); +} + +bool Monster::challengeCreature(Creature* creature) +{ + if (isSummon()) { + return false; + } else { + bool result = selectTarget(creature); + + if (result) { + targetChangeCooldown = 8000; + targetChangeTicks = 0; + } + + return result; + } + + return false; +} + +bool Monster::convinceCreature(Creature* creature) +{ + Player* player = creature->getPlayer(); + + if (player && !player->hasFlag(PlayerFlag_CanConvinceAll)) { + if (!mType->isConvinceable) { + return false; + } + } + + if (isSummon()) { + if (getMaster()->getPlayer()) { + return false; + } else if (getMaster() != creature) { + Creature* oldMaster = getMaster(); + oldMaster->removeSummon(this); + creature->addSummon(this); + + setFollowCreature(NULL); + setAttackedCreature(NULL); + + //destroy summons + for (std::list::iterator cit = summons.begin(); cit != summons.end(); ++cit) { + (*cit)->changeHealth(-(*cit)->getHealth()); + (*cit)->setMaster(NULL); + (*cit)->releaseThing2(); + } + + summons.clear(); + + isMasterInRange = true; + updateTargetList(); + updateIdleStatus(); + + //Notify surrounding about the change + SpectatorVec list; + g_game.getSpectators(list, getPosition(), true); + g_game.getSpectators(list, creature->getPosition(), true); + + for (SpectatorVec::iterator it = list.begin(); it != list.end(); ++it) { + (*it)->onCreatureConvinced(creature, this); + } + + if (spawn) { + spawn->removeMonster(this); + spawn = NULL; + masterRadius = -1; + } + + return true; + } + } else { + creature->addSummon(this); + setFollowCreature(NULL); + setAttackedCreature(NULL); + + for (std::list::iterator cit = summons.begin(); cit != summons.end(); ++cit) { + (*cit)->changeHealth(-(*cit)->getHealth()); + (*cit)->setMaster(NULL); + (*cit)->releaseThing2(); + } + + summons.clear(); + + isMasterInRange = true; + updateTargetList(); + updateIdleStatus(); + + //Notify surrounding about the change + SpectatorVec list; + g_game.getSpectators(list, getPosition(), true); + g_game.getSpectators(list, creature->getPosition(), true); + + for (SpectatorVec::iterator it = list.begin(); it != list.end(); ++it) { + (*it)->onCreatureConvinced(creature, this); + } + + if (spawn) { + spawn->removeMonster(this); + spawn = NULL; + masterRadius = -1; + } + + return true; + } + + return false; +} + +void Monster::onCreatureConvinced(const Creature* convincer, const Creature* creature) +{ + if (convincer != this && (isFriend(creature) || isOpponent(creature))) { + updateTargetList(); + updateIdleStatus(); + } +} + +void Monster::getPathSearchParams(const Creature* creature, FindPathParams& fpp) const +{ + Creature::getPathSearchParams(creature, fpp); + + fpp.minTargetDist = 1; + fpp.maxTargetDist = mType->targetDistance; + + if (isSummon()) { + if (getMaster() == creature) { + fpp.maxTargetDist = 2; + fpp.fullPathSearch = true; + } else if (mType->targetDistance <= 1) { + fpp.fullPathSearch = true; + } else { + fpp.fullPathSearch = !canUseAttack(getPosition(), creature); + } + } else if (isFleeing()) { + //Distance should be higher than the client view range (Map::maxClientViewportX/Map::maxClientViewportY) + fpp.maxTargetDist = Map::maxViewportX; + fpp.clearSight = false; + fpp.keepDistance = true; + fpp.fullPathSearch = false; + } else if (mType->targetDistance <= 1) { + fpp.fullPathSearch = true; + } else { + fpp.fullPathSearch = !canUseAttack(getPosition(), creature); + } +} diff --git a/src/monster.h b/src/monster.h new file mode 100644 index 0000000000..a3f34ab78a --- /dev/null +++ b/src/monster.h @@ -0,0 +1,268 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_MONSTER_H__ +#define __OTSERV_MONSTER_H__ + +#include "tile.h" +#include "monsters.h" + +class Creature; +class Game; +class Spawn; + +typedef OTSERV_HASH_SET CreatureHashSet; +typedef std::list CreatureList; + +enum TargetSearchType_t { + TARGETSEARCH_DEFAULT, + TARGETSEARCH_RANDOM, + TARGETSEARCH_ATTACKRANGE, + TARGETSEARCH_NEAREAST +}; + +class Monster : public Creature +{ + private: + Monster(MonsterType* mtype); + + public: +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + static uint32_t monsterCount; +#endif + static Monster* createMonster(MonsterType* mType); + static Monster* createMonster(const std::string& name); + static int32_t despawnRange; + static int32_t despawnRadius; + + virtual ~Monster(); + + virtual Monster* getMonster() { + return this; + } + virtual const Monster* getMonster() const { + return this; + } + + virtual uint32_t idRange() { + return 0x40000000; + } + static AutoList listMonster; + void removeList() { + listMonster.removeList(getID()); + } + void addList() { + listMonster.addList(this); + } + + virtual const std::string& getName() const { + return mType->name; + } + virtual const std::string& getNameDescription() const { + return mType->nameDescription; + } + virtual std::string getDescription(int32_t lookDistance) const { + return strDescription + '.'; + } + + virtual CreatureType_t getType() const { + return CREATURETYPE_MONSTER; + } + + virtual RaceType_t getRace() const { + return mType->race; + } + virtual int32_t getArmor() const { + return mType->armor; + } + virtual int32_t getDefense() const { + return mType->defense; + } + virtual bool isPushable() const { + return mType->pushable && (baseSpeed > 0); + } + virtual bool isAttackable() const { + return mType->isAttackable; + } + + bool canPushItems() const { + return mType->canPushItems; + } + bool canPushCreatures() const { + return mType->canPushCreatures; + } + bool isHostile() const { + return mType->isHostile; + } + virtual bool canSee(const Position& pos) const; + virtual bool canSeeInvisibility() const { + return isImmune(CONDITION_INVISIBLE); + } + uint32_t getManaCost() const { + return mType->manaCost; + } + void setSpawn(Spawn* _spawn) { + spawn = _spawn; + } + + virtual void onAttackedCreatureDisappear(bool isLogout); + virtual void onFollowCreatureDisappear(bool isLogout); + + virtual void onCreatureAppear(const Creature* creature, bool isLogin); + virtual void onCreatureDisappear(const Creature* creature, uint32_t stackpos, bool isLogout); + virtual void onCreatureMove(const Creature* creature, const Tile* newTile, const Position& newPos, + const Tile* oldTile, const Position& oldPos, bool teleport); + + virtual void drainHealth(Creature* attacker, CombatType_t combatType, int32_t damage); + virtual void changeHealth(int32_t healthChange, bool sendHealthChange = true); + virtual void onWalk(); + virtual bool getNextStep(Direction& dir, uint32_t& flags); + virtual void onFollowCreatureComplete(const Creature* creature); + + virtual void onThink(uint32_t interval); + + virtual bool challengeCreature(Creature* creature); + virtual bool convinceCreature(Creature* creature); + + virtual void setNormalCreatureLight(); + virtual bool getCombatValues(int32_t& min, int32_t& max); + + virtual void doAttacking(uint32_t interval); + virtual bool hasExtraSwing() { + return extraMeleeAttack; + } + + bool searchTarget(TargetSearchType_t searchType = TARGETSEARCH_DEFAULT); + bool selectTarget(Creature* creature); + + const CreatureList& getTargetList() { + return targetList; + } + const CreatureHashSet& getFriendList() { + return friendList; + } + + bool isTarget(Creature* creature); + bool isFleeing() const { + return getHealth() <= mType->runAwayHealth; + } + + bool getDistanceStep(const Position& targetPos, Direction& dir, bool flee = false); + bool isTargetNearby() const { + return stepDuration >= 1; + } + + BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, + bool checkDefense = false, bool checkArmor = false); + + private: + CreatureList targetList; + CreatureHashSet friendList; + + MonsterType* mType; + + int32_t minCombatValue; + int32_t maxCombatValue; + uint32_t attackTicks; + uint32_t targetTicks; + uint32_t targetChangeTicks; + uint32_t defenseTicks; + uint32_t yellTicks; + int32_t targetChangeCooldown; + bool resetTicks; + bool isIdle; + bool extraMeleeAttack; + + int64_t lastMeleeAttack; + + int32_t stepDuration; + + Spawn* spawn; + bool isMasterInRange; + + std::string strDescription; + + virtual void onCreatureEnter(Creature* creature); + virtual void onCreatureLeave(Creature* creature); + void onCreatureFound(Creature* creature, bool pushFront = false); + + void updateLookDirection(); + + void updateTargetList(); + void clearTargetList(); + void clearFriendList(); + + void death(); + Item* getCorpse(); + bool despawn(); + bool inDespawnRange(const Position& pos); + + void setIdle(bool _idle); + void updateIdleStatus(); + bool getIdleStatus() const { + return isIdle; + } + + virtual void onAddCondition(ConditionType_t type); + virtual void onEndCondition(ConditionType_t type); + virtual void onCreatureConvinced(const Creature* convincer, const Creature* creature); + + bool canUseAttack(const Position& pos, const Creature* target) const; + bool canUseSpell(const Position& pos, const Position& targetPos, + const spellBlock_t& sb, uint32_t interval, bool& inRange); + bool getRandomStep(const Position& creaturePos, Direction& dir); + bool getDanceStep(const Position& creaturePos, Direction& dir, + bool keepAttack = true, bool keepDistance = true); + bool isInSpawnRange(const Position& toPos); + bool canWalkTo(Position pos, Direction dir); + + bool pushItem(Item* item, int32_t radius); + void pushItems(Tile* tile); + bool pushCreature(Creature* creature); + void pushCreatures(Tile* tile); + + void onThinkTarget(uint32_t interval); + void onThinkYell(uint32_t interval); + void onThinkDefense(uint32_t interval); + + bool isFriend(const Creature* creature); + bool isOpponent(const Creature* creature); + + virtual uint64_t getLostExperience() const { + return ((skillLoss ? mType->experience : 0)); + } + virtual uint16_t getLookCorpse() { + return mType->lookcorpse; + } + virtual void dropLoot(Container* corpse); + virtual uint32_t getDamageImmunities() const { + return mType->damageImmunities; + } + virtual uint32_t getConditionImmunities() const { + return mType->conditionImmunities; + } + virtual uint16_t getLookCorpse() const { + return mType->lookcorpse; + } + virtual void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const; + virtual bool useCacheMap() const { + return true; + } +}; + +#endif diff --git a/src/monsters.cpp b/src/monsters.cpp new file mode 100644 index 0000000000..492a5de175 --- /dev/null +++ b/src/monsters.cpp @@ -0,0 +1,1425 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "monsters.h" +#include "monster.h" +#include "container.h" +#include "tools.h" +#include "spells.h" +#include "combat.h" +#include "luascript.h" +#include "weapons.h" +#include "configmanager.h" +#include "game.h" + +#include +#include + +extern Game g_game; +extern Spells* g_spells; +extern Monsters g_monsters; +extern ConfigManager g_config; + +MonsterType::MonsterType() +{ + reset(); +} + +void MonsterType::reset() +{ + experience = 0; + + defense = 0; + armor = 0; + + hiddenHealth = false; + + canPushItems = false; + canPushCreatures = false; + staticAttackChance = 95; + maxSummons = 0; + targetDistance = 1; + runAwayHealth = 0; + pushable = true; + baseSpeed = 200; + health = 100; + healthMax = 100; + + outfit.lookHead = 0; + outfit.lookBody = 0; + outfit.lookLegs = 0; + outfit.lookFeet = 0; + outfit.lookType = 0; + outfit.lookTypeEx = 0; + outfit.lookAddons = 0; + outfit.lookMount = 0; + lookcorpse = 0; + + conditionImmunities = 0; + damageImmunities = 0; + race = RACE_BLOOD; + isSummonable = false; + isIllusionable = false; + isConvinceable = false; + isAttackable = true; + isHostile = true; + + lightLevel = 0; + lightColor = 0; + + manaCost = 0; + summonList.clear(); + lootItems.clear(); + elementMap.clear(); + + for (SpellList::iterator it = spellAttackList.begin(); it != spellAttackList.end(); ++it) { + if (it->combatSpell) { + delete it->spell; + it->spell = NULL; + } + } + + spellAttackList.clear(); + + for (SpellList::iterator it = spellDefenseList.begin(); it != spellDefenseList.end(); ++it) { + if (it->combatSpell) { + delete it->spell; + it->spell = NULL; + } + } + + spellDefenseList.clear(); + + yellSpeedTicks = 0; + yellChance = 0; + voiceVector.clear(); + + changeTargetSpeed = 0; + changeTargetChance = 0; + + scriptList.clear(); +} + +MonsterType::~MonsterType() +{ + reset(); +} + +uint32_t Monsters::getLootRandom() +{ + return random_range(0, MAX_LOOTCHANCE) / g_config.getNumber(ConfigManager::RATE_LOOT); +} + +void MonsterType::createLoot(Container* corpse) +{ + Player* owner = g_game.getPlayerByID(corpse->getCorpseOwner()); + + if (!owner || owner->getStaminaMinutes() > 840) { + for (LootItems::const_reverse_iterator it = lootItems.rbegin(), end = lootItems.rend(); it != end; ++it) { + std::list itemList = createLootItem(*it); + + if (itemList.empty()) { + continue; + } + + for (std::list::iterator iit = itemList.begin(), iend = itemList.end(); iit != iend; ++iit) { + Item* tmpItem = *iit; + + //check containers + if (Container* container = tmpItem->getContainer()) { + if (!createLootContainer(container, *it)) { + delete container; + } else if (g_game.internalAddItem(corpse, tmpItem) != RET_NOERROR) { + corpse->__internalAddThing(tmpItem); + } + } else if (g_game.internalAddItem(corpse, tmpItem) != RET_NOERROR) { + corpse->__internalAddThing(tmpItem); + } + } + } + + if (owner) { + std::ostringstream ss; + ss << "Loot of " << nameDescription << ": " << corpse->getContentDescription(); + + if (owner->getParty()) { + owner->getParty()->broadcastPartyLoot(ss.str()); + } else { + owner->sendTextMessage(MSG_INFO_DESCR, ss.str()); + } + } + } else { + std::ostringstream ss; + ss << "Loot of " << nameDescription << ": nothing (due to low stamina)"; + + if (owner->getParty()) { + owner->getParty()->broadcastPartyLoot(ss.str()); + } else { + owner->sendTextMessage(MSG_INFO_DESCR, ss.str()); + } + } + + corpse->__startDecaying(); +} + +std::list MonsterType::createLootItem(const LootBlock& lootBlock) +{ + Item* tmpItem = NULL; + int32_t itemCount = 0; + + uint32_t randvalue = Monsters::getLootRandom(); + + if (randvalue < lootBlock.chance) { + if (Item::items[lootBlock.id].stackable) { + itemCount = randvalue % lootBlock.countmax + 1; + } else { + itemCount = 1; + } + } + + std::list itemList; + + while (itemCount > 0) { + uint16_t n = (uint16_t)std::min(itemCount, 100); + tmpItem = Item::CreateItem(lootBlock.id, n); + + if (!tmpItem) { + break; + } + + itemCount -= n; + + if (lootBlock.subType != -1) { + tmpItem->setSubType(lootBlock.subType); + } + + if (lootBlock.actionId != -1) { + tmpItem->setActionId(lootBlock.actionId); + } + + if (lootBlock.text != "") { + tmpItem->setText(lootBlock.text); + } + + itemList.push_back(tmpItem); + } + + return itemList; +} + +bool MonsterType::createLootContainer(Container* parent, const LootBlock& lootblock) +{ + LootItems::const_iterator it = lootblock.childLoot.begin(), + end = lootblock.childLoot.end(); + + if (it == end) { + return true; + } + + for (; it != end && parent->size() < parent->capacity(); ++it) { + std::list itemList = createLootItem(*it); + + if (!itemList.empty()) { + for (std::list::iterator iit = itemList.begin(), iend = itemList.end(); iit != iend; ++iit) { + Item* tmpItem = *iit; + + if (Container* container = tmpItem->getContainer()) { + if (!createLootContainer(container, *it)) { + delete container; + } else { + parent->__internalAddThing(container); + } + } else { + parent->__internalAddThing(tmpItem); + } + } + } + } + + return parent->size() != 0; +} + +Monsters::Monsters() +{ + loaded = false; +} + +bool Monsters::loadFromXml(bool reloading /*= false*/) +{ + loaded = false; + std::string filename = "data/monster/monsters.xml"; + + xmlDocPtr doc = xmlParseFile(filename.c_str()); + + if (doc) { + loaded = true; + xmlNodePtr root, p; + root = xmlDocGetRootElement(doc); + + if (xmlStrcmp(root->name, (const xmlChar*)"monsters") != 0) { + xmlFreeDoc(doc); + loaded = false; + return false; + } + + p = root->children; + + while (p) { + if (p->type != XML_ELEMENT_NODE) { + p = p->next; + continue; + } + + if (xmlStrcmp(p->name, (const xmlChar*)"monster") == 0) { + std::string file; + std::string name; + + if (readXMLString(p, "file", file) && readXMLString(p, "name", name)) { + file = "data/monster/" + file; + loadMonster(file, name, reloading); + } + } else { + std::cout << "[Warning - Monsters::loadFromXml]. Unknown node name. " << p->name << std::endl; + } + + p = p->next; + } + + xmlFreeDoc(doc); + } + + return loaded; +} + +bool Monsters::reload() +{ + return loadFromXml(true); +} + +ConditionDamage* Monsters::getDamageCondition(ConditionType_t conditionType, + int32_t maxDamage, int32_t minDamage, int32_t startDamage, uint32_t tickInterval) +{ + ConditionDamage* condition = dynamic_cast(Condition::createCondition(CONDITIONID_COMBAT, conditionType, 0, 0)); + condition->setParam(CONDITIONPARAM_TICKINTERVAL, tickInterval); + condition->setParam(CONDITIONPARAM_MINVALUE, minDamage); + condition->setParam(CONDITIONPARAM_MAXVALUE, maxDamage); + condition->setParam(CONDITIONPARAM_STARTVALUE, startDamage); + condition->setParam(CONDITIONPARAM_DELAYED, 1); + return condition; +} + +bool Monsters::deserializeSpell(xmlNodePtr node, spellBlock_t& sb, const std::string& description) +{ + sb.chance = 100; + sb.speed = 2000; + sb.range = 0; + sb.minCombatValue = 0; + sb.maxCombatValue = 0; + sb.combatSpell = false; + sb.isMelee = false; + + std::string name = ""; + std::string scriptName = ""; + bool isScripted = false; + + if (readXMLString(node, "script", scriptName)) { + isScripted = true; + } else if (!readXMLString(node, "name", name)) { + return false; + } + + int intValue; + std::string strValue; + + if (readXMLInteger(node, "speed", intValue) || readXMLInteger(node, "interval", intValue)) { + sb.speed = std::max(1, intValue); + } + + if (readXMLInteger(node, "chance", intValue)) { + if (intValue < 0 || intValue > 100) { + intValue = 100; + } + + sb.chance = intValue; + } + + if (readXMLInteger(node, "range", intValue)) { + if (intValue < 0 ) { + intValue = 0; + } + + if (intValue > Map::maxViewportX * 2) { + intValue = Map::maxViewportX * 2; + } + + sb.range = intValue; + } + + if (readXMLInteger(node, "min", intValue)) { + sb.minCombatValue = intValue; + } + + if (readXMLInteger(node, "max", intValue)) { + sb.maxCombatValue = intValue; + + //normalize values + if (std::abs(sb.minCombatValue) > std::abs(sb.maxCombatValue)) { + int32_t value = sb.maxCombatValue; + sb.maxCombatValue = sb.minCombatValue; + sb.minCombatValue = value; + } + } + + if ((sb.spell = g_spells->getSpellByName(name))) { + return true; + } + + CombatSpell* combatSpell = NULL; + bool needTarget = false; + bool needDirection = false; + + if (isScripted) { + if (readXMLInteger(node, "direction", intValue)) { + needDirection = (intValue == 1); + } + + if (readXMLInteger(node, "target", intValue)) { + needTarget = (intValue != 0); + } + + combatSpell = new CombatSpell(NULL, needTarget, needDirection); + + if (!combatSpell->loadScript("data/" + g_spells->getScriptBaseName() + "/scripts/" + scriptName)) { + delete combatSpell; + return false; + } + + if (!combatSpell->loadScriptCombat()) { + delete combatSpell; + return false; + } + + combatSpell->getCombat()->setPlayerCombatValues(FORMULA_VALUE, sb.minCombatValue, 0, sb.maxCombatValue, 0); + } else { + Combat* combat = new Combat; + sb.combatSpell = true; + + if (readXMLInteger(node, "length", intValue)) { + int32_t length = intValue; + + if (length > 0) { + int32_t spread = 3; + + //need direction spell + if (readXMLInteger(node, "spread", intValue)) { + spread = std::max(0, intValue); + } + + AreaCombat* area = new AreaCombat(); + area->setupArea(length, spread); + combat->setArea(area); + + needDirection = true; + } + } + + if (readXMLInteger(node, "radius", intValue)) { + int32_t radius = intValue; + + //target spell + if (readXMLInteger(node, "target", intValue)) { + needTarget = (intValue != 0); + } + + AreaCombat* area = new AreaCombat(); + area->setupArea(radius); + combat->setArea(area); + } + + std::string tmpName = asLowerCaseString(name); + + if (tmpName == "melee") { + sb.isMelee = true; + + int attack, skill; + + if (readXMLInteger(node, "attack", attack) && readXMLInteger(node, "skill", skill)) { + sb.minCombatValue = 0; + sb.maxCombatValue = -Weapons::getMaxMeleeDamage(skill, attack); + } + + ConditionType_t conditionType = CONDITION_NONE; + int32_t minDamage = 0; + int32_t maxDamage = 0; + uint32_t tickInterval = 2000; + + if (readXMLInteger(node, "fire", intValue)) { + conditionType = CONDITION_FIRE; + + minDamage = intValue; + maxDamage = intValue; + tickInterval = 9000; + } else if (readXMLInteger(node, "poison", intValue)) { + conditionType = CONDITION_POISON; + + minDamage = intValue; + maxDamage = intValue; + tickInterval = 5000; + } else if (readXMLInteger(node, "energy", intValue)) { + conditionType = CONDITION_ENERGY; + + minDamage = intValue; + maxDamage = intValue; + tickInterval = 10000; + } else if (readXMLInteger(node, "drown", intValue)) { + conditionType = CONDITION_DROWN; + + minDamage = intValue; + maxDamage = intValue; + tickInterval = 5000; + } else if (readXMLInteger(node, "freeze", intValue)) { + conditionType = CONDITION_FREEZING; + + minDamage = intValue; + maxDamage = intValue; + tickInterval = 8000; + } else if (readXMLInteger(node, "dazzle", intValue)) { + conditionType = CONDITION_DAZZLED; + + minDamage = intValue; + maxDamage = intValue; + tickInterval = 10000; + } else if (readXMLInteger(node, "curse", intValue)) { + conditionType = CONDITION_CURSED; + + minDamage = intValue; + maxDamage = intValue; + tickInterval = 4000; + } else if (readXMLInteger(node, "bleed", intValue) || readXMLInteger(node, "physical", intValue)) { + conditionType = CONDITION_BLEEDING; + tickInterval = 5000; + } + + if (readXMLInteger(node, "tick", intValue) && intValue > 0) { + tickInterval = intValue; + } + + if (conditionType != CONDITION_NONE) { + Condition* condition = getDamageCondition(conditionType, maxDamage, minDamage, 0, tickInterval); + combat->setCondition(condition); + } + + sb.range = 1; + combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_PHYSICALDAMAGE); + combat->setParam(COMBATPARAM_BLOCKEDBYARMOR, 1); + combat->setParam(COMBATPARAM_BLOCKEDBYSHIELD, 1); + } else if (tmpName == "physical") { + combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_PHYSICALDAMAGE); + combat->setParam(COMBATPARAM_BLOCKEDBYARMOR, 1); + } else if (tmpName == "bleed") { + combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_PHYSICALDAMAGE); + } else if (tmpName == "poison" || tmpName == "earth") { + combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_EARTHDAMAGE); + } else if (tmpName == "fire") { + combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_FIREDAMAGE); + } else if (tmpName == "energy") { + combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_ENERGYDAMAGE); + } else if (tmpName == "drown") { + combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_DROWNDAMAGE); + } else if (tmpName == "ice") { + combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_ICEDAMAGE); + } else if (tmpName == "holy") { + combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_HOLYDAMAGE); + } else if (tmpName == "death") { + combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_DEATHDAMAGE); + } else if (tmpName == "lifedrain") { + combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_LIFEDRAIN); + } else if (tmpName == "manadrain") { + combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_MANADRAIN); + } else if (tmpName == "healing") { + combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_HEALING); + combat->setParam(COMBATPARAM_AGGRESSIVE, 0); + } else if (tmpName == "speed") { + int32_t speedChange = 0; + int32_t duration = 10000; + + if (readXMLInteger(node, "duration", intValue)) { + duration = intValue; + } + + if (readXMLInteger(node, "speedchange", intValue)) { + speedChange = intValue; + + if (speedChange < -1000) { + //cant be slower than 100% + speedChange = -1000; + } + } + + ConditionType_t conditionType; + + if (speedChange > 0) { + conditionType = CONDITION_HASTE; + combat->setParam(COMBATPARAM_AGGRESSIVE, 0); + } else { + conditionType = CONDITION_PARALYZE; + } + + ConditionSpeed* condition = dynamic_cast(Condition::createCondition(CONDITIONID_COMBAT, conditionType, duration, 0)); + condition->setFormulaVars(speedChange / 1000.0, 0, speedChange / 1000.0, 0); + combat->setCondition(condition); + } else if (tmpName == "outfit") { + int32_t duration = 10000; + + if (readXMLInteger(node, "duration", intValue)) { + duration = intValue; + } + + if (readXMLString(node, "monster", strValue)) { + MonsterType* mType = g_monsters.getMonsterType(strValue); + + if (mType) { + ConditionOutfit* condition = dynamic_cast(Condition::createCondition(CONDITIONID_COMBAT, CONDITION_OUTFIT, duration, 0)); + condition->addOutfit(mType->outfit); + combat->setParam(COMBATPARAM_AGGRESSIVE, 0); + combat->setCondition(condition); + } + } else if (readXMLInteger(node, "item", intValue)) { + Outfit_t outfit; + outfit.lookTypeEx = intValue; + + ConditionOutfit* condition = dynamic_cast(Condition::createCondition(CONDITIONID_COMBAT, CONDITION_OUTFIT, duration, 0)); + condition->addOutfit(outfit); + combat->setParam(COMBATPARAM_AGGRESSIVE, 0); + combat->setCondition(condition); + } + } else if (tmpName == "invisible") { + int32_t duration = 10000; + + if (readXMLInteger(node, "duration", intValue)) { + duration = intValue; + } + + Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_INVISIBLE, duration, 0); + combat->setParam(COMBATPARAM_AGGRESSIVE, 0); + combat->setCondition(condition); + } else if (tmpName == "drunk") { + int32_t duration = 10000; + + if (readXMLInteger(node, "duration", intValue)) { + duration = intValue; + } + + Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_DRUNK, duration, 0); + combat->setCondition(condition); + } else if (tmpName == "firefield") { + combat->setParam(COMBATPARAM_CREATEITEM, ITEM_FIREFIELD_PVP_FULL); + } else if (tmpName == "poisonfield") { + combat->setParam(COMBATPARAM_CREATEITEM, ITEM_POISONFIELD_PVP); + } else if (tmpName == "energyfield") { + combat->setParam(COMBATPARAM_CREATEITEM, ITEM_ENERGYFIELD_PVP); + } else if (tmpName == "firecondition" || tmpName == "energycondition" || + tmpName == "earthcondition" || tmpName == "poisoncondition" || + tmpName == "icecondition" || tmpName == "freezecondition" || + tmpName == "deathcondition" || tmpName == "cursecondition" || + tmpName == "holycondition" || tmpName == "dazzlecondition" || + tmpName == "drowncondition" || tmpName == "bleedcondition" || + tmpName == "physicalcondition") { + ConditionType_t conditionType = CONDITION_NONE; + uint32_t tickInterval = 2000; + + if (tmpName == "firecondition") { + conditionType = CONDITION_FIRE; + tickInterval = 10000; + } else if (tmpName == "poisoncondition" || tmpName == "earthcondition") { + conditionType = CONDITION_POISON; + tickInterval = 5000; + } else if (tmpName == "energycondition") { + conditionType = CONDITION_ENERGY; + tickInterval = 10000; + } else if (tmpName == "drowncondition") { + conditionType = CONDITION_DROWN; + tickInterval = 5000; + } else if (tmpName == "freezecondition" || tmpName == "icecondition") { + conditionType = CONDITION_FREEZING; + tickInterval = 10000; + } else if (tmpName == "cursecondition" || tmpName == "deathcondition") { + conditionType = CONDITION_CURSED; + tickInterval = 4000; + } else if (tmpName == "dazzlecondition" || tmpName == "holycondition") { + conditionType = CONDITION_DAZZLED; + tickInterval = 10000; + } else if (tmpName == "physicalcondition" || tmpName == "bleedcondition") { + conditionType = CONDITION_BLEEDING; + tickInterval = 5000; + } + + if (readXMLInteger(node, "tick", intValue) && intValue > 0) { + tickInterval = intValue; + } + + int32_t minDamage = std::abs(sb.minCombatValue); + int32_t maxDamage = std::abs(sb.maxCombatValue); + int32_t startDamage = 0; + + if (readXMLInteger(node, "start", intValue)) { + intValue = std::abs(intValue); + + if (intValue <= minDamage) { + startDamage = intValue; + } + } + + Condition* condition = getDamageCondition(conditionType, maxDamage, minDamage, startDamage, tickInterval); + combat->setCondition(condition); + } else if (tmpName == "strength") { + // + } else if (tmpName == "effect") { + // + } else { + std::cout << "Error: [Monsters::deserializeSpell] - " << description << " - Unknown spell name: " << name << std::endl; + delete combat; + return false; + } + + combat->setPlayerCombatValues(FORMULA_VALUE, sb.minCombatValue, 0, sb.maxCombatValue, 0); + combatSpell = new CombatSpell(combat, needTarget, needDirection); + + xmlNodePtr attributeNode = node->children; + + while (attributeNode) { + if (xmlStrcmp(attributeNode->name, (const xmlChar*)"attribute") == 0) { + if (readXMLString(attributeNode, "key", strValue)) { + std::string tmpStrValue = asLowerCaseString(strValue); + + if (tmpStrValue == "shooteffect") { + if (readXMLString(attributeNode, "value", strValue)) { + ShootType_t shoot = getShootType(strValue); + + if (shoot != NM_SHOOT_UNK) { + combat->setParam(COMBATPARAM_DISTANCEEFFECT, shoot); + } else { + std::cout << "Warning: [Monsters::deserializeSpell] - " << description << " - Unknown shootEffect: " << strValue << std::endl; + } + } + } else if (tmpStrValue == "areaeffect") { + if (readXMLString(attributeNode, "value", strValue)) { + MagicEffectClasses effect = getMagicEffect(strValue); + + if (effect != NM_ME_UNK) { + combat->setParam(COMBATPARAM_EFFECT, effect); + } else { + std::cout << "Warning: [Monsters::deserializeSpell] - " << description << " - Unknown areaEffect: " << strValue << std::endl; + } + } + } else { + std::cout << "[Warning - Monsters::deserializeSpells] Effect type \"" << strValue << "\" does not exist." << std::endl; + } + } + } + + attributeNode = attributeNode->next; + } + } + + sb.spell = combatSpell; + return true; +} + +#define SHOW_XML_WARNING(desc) std::cout << "[Warning - Monsters::loadMonster] " << desc << ". " << file << std::endl; +#define SHOW_XML_ERROR(desc) std::cout << "[Error - Monsters::loadMonster] " << desc << ". " << file << std::endl; + +bool Monsters::loadMonster(const std::string& file, const std::string& monster_name, bool reloading /*= false*/) +{ + bool monsterLoad; + MonsterType* mType = NULL; + bool new_mType = true; + + if (reloading) { + uint32_t id = getIdByName(monster_name); + + if (id != 0) { + mType = getMonsterType(id); + + if (mType != NULL) { + new_mType = false; + mType->reset(); + } + } + } + + if (new_mType) { + mType = new MonsterType(); + } + + monsterLoad = true; + xmlDocPtr doc = xmlParseFile(file.c_str()); + + if (doc) { + xmlNodePtr root, p; + root = xmlDocGetRootElement(doc); + + if (xmlStrcmp(root->name, (const xmlChar*)"monster") != 0) { + std::cerr << "Malformed XML: " << file << std::endl; + } + + int intValue; + std::string strValue; + + p = root->children; + + if (readXMLString(root, "name", strValue)) { + mType->name = strValue; + } else { + monsterLoad = false; + } + + if (readXMLString(root, "nameDescription", strValue)) { + mType->nameDescription = strValue; + } else { + mType->nameDescription = "a " + mType->name; + toLowerCaseString(mType->nameDescription); + } + + if (readXMLString(root, "race", strValue)) { + std::string tmpStrValue = asLowerCaseString(strValue); + + if (tmpStrValue == "venom" || atoi(strValue.c_str()) == 1) { + mType->race = RACE_VENOM; + } else if (tmpStrValue == "blood" || atoi(strValue.c_str()) == 2) { + mType->race = RACE_BLOOD; + } else if (tmpStrValue == "undead" || atoi(strValue.c_str()) == 3) { + mType->race = RACE_UNDEAD; + } else if (tmpStrValue == "fire" || atoi(strValue.c_str()) == 4) { + mType->race = RACE_FIRE; + } else if (tmpStrValue == "energy" || atoi(strValue.c_str()) == 5) { + mType->race = RACE_ENERGY; + } else { + SHOW_XML_WARNING("Unknown race type " << strValue); + } + } + + if (readXMLInteger(root, "experience", intValue)) { + mType->experience = intValue; + } + + if (readXMLInteger(root, "speed", intValue)) { + mType->baseSpeed = intValue; + } + + if (readXMLInteger(root, "manacost", intValue)) { + mType->manaCost = intValue; + } + + while (p) { + if (p->type != XML_ELEMENT_NODE) { + p = p->next; + continue; + } + + if (xmlStrcmp(p->name, (const xmlChar*)"health") == 0) { + if (readXMLInteger(p, "now", intValue)) { + mType->health = intValue; + } else { + SHOW_XML_ERROR("Missing health.now"); + monsterLoad = false; + } + + if (readXMLInteger(p, "max", intValue)) { + mType->healthMax = intValue; + } else { + SHOW_XML_ERROR("Missing health.max"); + monsterLoad = false; + } + } else if (xmlStrcmp(p->name, (const xmlChar*)"flags") == 0) { + xmlNodePtr tmpNode = p->children; + + while (tmpNode) { + if (xmlStrcmp(tmpNode->name, (const xmlChar*)"flag") == 0) { + if (readXMLInteger(tmpNode, "summonable", intValue)) { + mType->isSummonable = (intValue != 0); + } else if (readXMLInteger(tmpNode, "attackable", intValue)) { + mType->isAttackable = (intValue != 0); + } else if (readXMLInteger(tmpNode, "hostile", intValue)) { + mType->isHostile = (intValue != 0); + } else if (readXMLInteger(tmpNode, "illusionable", intValue)) { + mType->isIllusionable = (intValue != 0); + } else if (readXMLInteger(tmpNode, "convinceable", intValue)) { + mType->isConvinceable = (intValue != 0); + } else if (readXMLInteger(tmpNode, "pushable", intValue)) { + mType->pushable = (intValue != 0); + } else if (readXMLInteger(tmpNode, "canpushitems", intValue)) { + mType->canPushItems = (intValue != 0); + } else if (readXMLInteger(tmpNode, "canpushcreatures", intValue)) { + mType->canPushCreatures = (intValue != 0); + } else if (readXMLInteger(tmpNode, "staticattack", intValue)) { + if (intValue < 0) { + SHOW_XML_WARNING("staticattack lower than 0"); + intValue = 0; + } + + if (intValue > 100) { + SHOW_XML_WARNING("staticattack greater than 100"); + intValue = 100; + } + + mType->staticAttackChance = intValue; + } else if (readXMLInteger(tmpNode, "lightlevel", intValue)) { + mType->lightLevel = intValue; + } else if (readXMLInteger(tmpNode, "lightcolor", intValue)) { + mType->lightColor = intValue; + } else if (readXMLInteger(tmpNode, "targetdistance", intValue)) { + /*if(intValue > 6){ + SHOW_XML_WARNING("targetdistance greater than 6"); + }*/ + mType->targetDistance = std::max(1, intValue); + } else if (readXMLInteger(tmpNode, "runonhealth", intValue)) { + mType->runAwayHealth = intValue; + } else if (readXMLInteger(tmpNode, "hidehealth", intValue)) { + mType->hiddenHealth = (intValue != 0); + } + } + + tmpNode = tmpNode->next; + } + + //if a monster can push creatures, + // it should not be pushable + if (mType->canPushCreatures && mType->pushable) { + mType->pushable = false; + } + } else if (xmlStrcmp(p->name, (const xmlChar*)"targetchange") == 0) { + if (readXMLInteger(p, "speed", intValue) || readXMLInteger(p, "interval", intValue)) { + mType->changeTargetSpeed = std::max(1, intValue); + } else { + SHOW_XML_WARNING("Missing targetchange.speed"); + } + + if (readXMLInteger(p, "chance", intValue)) { + mType->changeTargetChance = intValue; + } else { + SHOW_XML_WARNING("Missing targetchange.chance"); + } + } else if (xmlStrcmp(p->name, (const xmlChar*)"strategy") == 0) { + if (readXMLInteger(p, "attack", intValue)) { + //mType->attackStrength = intValue; + } + + if (readXMLInteger(p, "defense", intValue)) { + //mType->defenseStrength = intValue; + } + } else if (xmlStrcmp(p->name, (const xmlChar*)"look") == 0) { + if (readXMLInteger(p, "type", intValue)) { + mType->outfit.lookType = intValue; + + if (readXMLInteger(p, "head", intValue)) { + mType->outfit.lookHead = intValue; + } + + if (readXMLInteger(p, "body", intValue)) { + mType->outfit.lookBody = intValue; + } + + if (readXMLInteger(p, "legs", intValue)) { + mType->outfit.lookLegs = intValue; + } + + if (readXMLInteger(p, "feet", intValue)) { + mType->outfit.lookFeet = intValue; + } + + if (readXMLInteger(p, "addons", intValue)) { + mType->outfit.lookAddons = intValue; + } + } else if (readXMLInteger(p, "typeex", intValue)) { + mType->outfit.lookTypeEx = intValue; + } else { + SHOW_XML_WARNING("Missing look type/typeex"); + } + + if (readXMLInteger(p, "mount", intValue)) { + mType->outfit.lookMount = intValue; + } + + if (readXMLInteger(p, "corpse", intValue)) { + mType->lookcorpse = intValue; + } + } else if (xmlStrcmp(p->name, (const xmlChar*)"attacks") == 0) { + xmlNodePtr tmpNode = p->children; + + while (tmpNode) { + if (xmlStrcmp(tmpNode->name, (const xmlChar*)"attack") == 0) { + spellBlock_t sb; + + if (deserializeSpell(tmpNode, sb, monster_name)) { + mType->spellAttackList.push_back(sb); + } else { + SHOW_XML_WARNING("Cant load spell"); + } + } + + tmpNode = tmpNode->next; + } + } else if (xmlStrcmp(p->name, (const xmlChar*)"defenses") == 0) { + if (readXMLInteger(p, "defense", intValue)) { + mType->defense = intValue; + } + + if (readXMLInteger(p, "armor", intValue)) { + mType->armor = intValue; + } + + xmlNodePtr tmpNode = p->children; + + while (tmpNode) { + if (xmlStrcmp(tmpNode->name, (const xmlChar*)"defense") == 0) { + spellBlock_t sb; + + if (deserializeSpell(tmpNode, sb, monster_name)) { + mType->spellDefenseList.push_back(sb); + } else { + SHOW_XML_WARNING("Cant load spell"); + } + } + + tmpNode = tmpNode->next; + } + } else if (xmlStrcmp(p->name, (const xmlChar*)"immunities") == 0) { + xmlNodePtr tmpNode = p->children; + + while (tmpNode) { + if (xmlStrcmp(tmpNode->name, (const xmlChar*)"immunity") == 0) { + if (readXMLString(tmpNode, "name", strValue)) { + std::string tmpStrValue = asLowerCaseString(strValue); + + if (tmpStrValue == "physical") { + mType->damageImmunities |= COMBAT_PHYSICALDAMAGE; + mType->conditionImmunities |= CONDITION_BLEEDING; + } else if (tmpStrValue == "energy") { + mType->damageImmunities |= COMBAT_ENERGYDAMAGE; + mType->conditionImmunities |= CONDITION_ENERGY; + } else if (tmpStrValue == "fire") { + mType->damageImmunities |= COMBAT_FIREDAMAGE; + mType->conditionImmunities |= CONDITION_FIRE; + } else if (tmpStrValue == "poison" || + tmpStrValue == "earth") { + mType->damageImmunities |= COMBAT_EARTHDAMAGE; + mType->conditionImmunities |= CONDITION_POISON; + } else if (tmpStrValue == "drown") { + mType->damageImmunities |= COMBAT_DROWNDAMAGE; + mType->conditionImmunities |= CONDITION_DROWN; + } else if (tmpStrValue == "ice") { + mType->damageImmunities |= COMBAT_ICEDAMAGE; + mType->conditionImmunities |= CONDITION_FREEZING; + } else if (tmpStrValue == "holy") { + mType->damageImmunities |= COMBAT_HOLYDAMAGE; + mType->conditionImmunities |= CONDITION_DAZZLED; + } else if (tmpStrValue == "death") { + mType->damageImmunities |= COMBAT_DEATHDAMAGE; + mType->conditionImmunities |= CONDITION_CURSED; + } else if (tmpStrValue == "lifedrain") { + mType->damageImmunities |= COMBAT_LIFEDRAIN; + } else if (tmpStrValue == "manadrain") { + mType->damageImmunities |= COMBAT_MANADRAIN; + } else if (tmpStrValue == "paralyze") { + mType->conditionImmunities |= CONDITION_PARALYZE; + } else if (tmpStrValue == "outfit") { + mType->conditionImmunities |= CONDITION_OUTFIT; + } else if (tmpStrValue == "drunk") { + mType->conditionImmunities |= CONDITION_DRUNK; + } else if (tmpStrValue == "invisible" || tmpStrValue == "invisibility") { + mType->conditionImmunities |= CONDITION_INVISIBLE; + } else if (tmpStrValue == "bleed") { + mType->conditionImmunities |= CONDITION_BLEEDING; + } else { + SHOW_XML_WARNING("Unknown immunity name " << strValue); + } + } + //old immunities code + else if (readXMLInteger(tmpNode, "physical", intValue)) { + if (intValue != 0) { + mType->damageImmunities |= COMBAT_PHYSICALDAMAGE; + mType->conditionImmunities |= CONDITION_BLEEDING; + } + } else if (readXMLInteger(tmpNode, "energy", intValue)) { + if (intValue != 0) { + mType->damageImmunities |= COMBAT_ENERGYDAMAGE; + mType->conditionImmunities |= CONDITION_ENERGY; + } + } else if (readXMLInteger(tmpNode, "fire", intValue)) { + if (intValue != 0) { + mType->damageImmunities |= COMBAT_FIREDAMAGE; + mType->conditionImmunities |= CONDITION_FIRE; + } + } else if (readXMLInteger(tmpNode, "poison", intValue) || + readXMLInteger(tmpNode, "earth", intValue)) { + if (intValue != 0) { + mType->damageImmunities |= COMBAT_EARTHDAMAGE; + mType->conditionImmunities |= CONDITION_POISON; + } + } else if (readXMLInteger(tmpNode, "drown", intValue)) { + if (intValue != 0) { + mType->damageImmunities |= COMBAT_DROWNDAMAGE; + mType->conditionImmunities |= CONDITION_DROWN; + } + } else if (readXMLInteger(tmpNode, "ice", intValue)) { + if (intValue != 0) { + mType->damageImmunities |= COMBAT_ICEDAMAGE; + mType->conditionImmunities |= CONDITION_FREEZING; + } + } else if (readXMLInteger(tmpNode, "holy", intValue)) { + if (intValue != 0) { + mType->damageImmunities |= COMBAT_HOLYDAMAGE; + mType->conditionImmunities |= CONDITION_DAZZLED; + } + } else if (readXMLInteger(tmpNode, "death", intValue)) { + if (intValue != 0) { + mType->damageImmunities |= COMBAT_DEATHDAMAGE; + mType->conditionImmunities |= CONDITION_CURSED; + } + } else if (readXMLInteger(tmpNode, "lifedrain", intValue)) { + if (intValue != 0) { + mType->damageImmunities |= COMBAT_LIFEDRAIN; + } + } else if (readXMLInteger(tmpNode, "manadrain", intValue)) { + if (intValue != 0) { + mType->damageImmunities |= COMBAT_MANADRAIN; + } + } else if (readXMLInteger(tmpNode, "paralyze", intValue)) { + if (intValue != 0) { + mType->conditionImmunities |= CONDITION_PARALYZE; + } + } else if (readXMLInteger(tmpNode, "outfit", intValue)) { + if (intValue != 0) { + mType->conditionImmunities |= CONDITION_OUTFIT; + } + } else if (readXMLInteger(tmpNode, "bleed", intValue)) { + if (intValue != 0) { + mType->conditionImmunities |= CONDITION_BLEEDING; + } + } else if (readXMLInteger(tmpNode, "drunk", intValue)) { + if (intValue != 0) { + mType->conditionImmunities |= CONDITION_DRUNK; + } + } else if (readXMLInteger(tmpNode, "invisible", intValue) || + readXMLInteger(tmpNode, "invisibility", intValue)) { + if (intValue != 0) { + mType->conditionImmunities |= CONDITION_INVISIBLE; + } + } else { + SHOW_XML_WARNING("Unknown immunity"); + } + } + + tmpNode = tmpNode->next; + } + } else if (xmlStrcmp(p->name, (const xmlChar*)"voices") == 0) { + xmlNodePtr tmpNode = p->children; + + if (readXMLInteger(p, "speed", intValue) || readXMLInteger(p, "interval", intValue)) { + mType->yellSpeedTicks = intValue; + } else { + SHOW_XML_WARNING("Missing voices.speed"); + } + + if (readXMLInteger(p, "chance", intValue)) { + mType->yellChance = intValue; + } else { + SHOW_XML_WARNING("Missing voices.chance"); + } + + while (tmpNode) { + if (xmlStrcmp(tmpNode->name, (const xmlChar*)"voice") == 0) { + voiceBlock_t vb; + vb.text = ""; + vb.yellText = false; + + if (readXMLString(tmpNode, "sentence", strValue)) { + vb.text = strValue; + } else { + SHOW_XML_WARNING("Missing voice.sentence"); + } + + if (readXMLInteger(tmpNode, "yell", intValue)) { + vb.yellText = (intValue != 0); + } + + mType->voiceVector.push_back(vb); + } + + tmpNode = tmpNode->next; + } + } else if (xmlStrcmp(p->name, (const xmlChar*)"loot") == 0) { + xmlNodePtr tmpNode = p->children; + + while (tmpNode) { + if (tmpNode->type != XML_ELEMENT_NODE) { + tmpNode = tmpNode->next; + continue; + } + + LootBlock lootBlock; + + if (loadLootItem(tmpNode, lootBlock)) { + mType->lootItems.push_back(lootBlock); + } else { + SHOW_XML_WARNING("Cant load loot"); + } + + tmpNode = tmpNode->next; + } + } else if (xmlStrcmp(p->name, (const xmlChar*)"elements") == 0) { + xmlNodePtr tmpNode = p->children; + + while (tmpNode) { + if (xmlStrcmp(tmpNode->name, (const xmlChar*)"element") == 0) { + if (readXMLInteger(tmpNode, "physicalPercent", intValue)) { + mType->elementMap[COMBAT_PHYSICALDAMAGE] = intValue; + } else if (readXMLInteger(tmpNode, "icePercent", intValue)) { + mType->elementMap[COMBAT_ICEDAMAGE] = intValue; + } else if (readXMLInteger(tmpNode, "poisonPercent", intValue) || readXMLInteger(tmpNode, "earthPercent", intValue)) { + mType->elementMap[COMBAT_EARTHDAMAGE] = intValue; + } else if (readXMLInteger(tmpNode, "firePercent", intValue)) { + mType->elementMap[COMBAT_FIREDAMAGE] = intValue; + } else if (readXMLInteger(tmpNode, "energyPercent", intValue)) { + mType->elementMap[COMBAT_ENERGYDAMAGE] = intValue; + } else if (readXMLInteger(tmpNode, "holyPercent", intValue)) { + mType->elementMap[COMBAT_HOLYDAMAGE] = intValue; + } else if (readXMLInteger(tmpNode, "deathPercent", intValue)) { + mType->elementMap[COMBAT_DEATHDAMAGE] = intValue; + } else if (readXMLInteger(tmpNode, "drownPercent", intValue)) { + mType->elementMap[COMBAT_DROWNDAMAGE] = intValue; + } else if (readXMLInteger(tmpNode, "lifedrainPercent", intValue)) { + mType->elementMap[COMBAT_LIFEDRAIN] = intValue; + } else if (readXMLInteger(tmpNode, "manadrainPercent", intValue)) { + mType->elementMap[COMBAT_MANADRAIN] = intValue; + } else { + SHOW_XML_WARNING("Unknown element percent"); + } + } + + tmpNode = tmpNode->next; + } + } else if (xmlStrcmp(p->name, (const xmlChar*)"summons") == 0) { + if (readXMLInteger(p, "maxSummons", intValue)) { + mType->maxSummons = std::min(intValue, 100); + } else { + SHOW_XML_WARNING("Missing summons.maxSummons"); + } + + xmlNodePtr tmpNode = p->children; + + while (tmpNode) { + if (xmlStrcmp(tmpNode->name, (const xmlChar*)"summon") == 0) { + int32_t chance = 100; + int32_t speed = 1000; + + if (readXMLInteger(tmpNode, "speed", intValue) || readXMLInteger(tmpNode, "interval", intValue)) { + speed = intValue; + } + + if (readXMLInteger(tmpNode, "chance", intValue)) { + chance = intValue; + } + + if (readXMLString(tmpNode, "name", strValue)) { + summonBlock_t sb; + sb.name = strValue; + sb.speed = speed; + sb.chance = chance; + + mType->summonList.push_back(sb); + } else { + SHOW_XML_WARNING("Missing summon.name"); + } + } + + tmpNode = tmpNode->next; + } + } else if (xmlStrcmp(p->name, (const xmlChar*)"script") == 0) { + xmlNodePtr tmpNode = p->children; + + while (tmpNode) { + if (xmlStrcmp(tmpNode->name, (const xmlChar*)"event") == 0) { + if (readXMLString(tmpNode, "name", strValue)) { + mType->scriptList.push_back(strValue); + } else { + SHOW_XML_WARNING("Missing name for script event"); + } + } + + tmpNode = tmpNode->next; + } + } else { + SHOW_XML_WARNING("Unknown attribute type - " << p->name); + } + + p = p->next; + } + + xmlFreeDoc(doc); + } else { + monsterLoad = false; + } + + if (monsterLoad) { + static uint32_t id = 0; + + if (new_mType) { + std::string lowername = monster_name; + toLowerCaseString(lowername); + + id++; + monsterNames[lowername] = id; + monsters[id] = mType; + } + + return true; + } else { + if (new_mType) { + delete mType; + } + + return false; + } +} + +bool Monsters::loadLootItem(xmlNodePtr node, LootBlock& lootBlock) +{ + int intValue; + std::string strValue; + + if (readXMLInteger(node, "id", intValue)) { + lootBlock.id = intValue; + } + + if (lootBlock.id == 0) { + return false; + } + + if (readXMLInteger(node, "countmax", intValue)) { + lootBlock.countmax = std::max(1, intValue); + } else { + lootBlock.countmax = 1; + } + + if (readXMLInteger(node, "chance", intValue) || readXMLInteger(node, "chance1", intValue)) { + lootBlock.chance = std::min(MAX_LOOTCHANCE, intValue); + } else { + lootBlock.chance = MAX_LOOTCHANCE; + } + + if (Item::items[lootBlock.id].isContainer()) { + loadLootContainer(node, lootBlock); + } + + //optional + if (readXMLInteger(node, "subtype", intValue)) { + lootBlock.subType = intValue; + } + + if (readXMLInteger(node, "actionId", intValue)) { + lootBlock.actionId = intValue; + } + + if (readXMLString(node, "text", strValue)) { + lootBlock.text = strValue; + } + + return true; +} + +bool Monsters::loadLootContainer(xmlNodePtr node, LootBlock& lBlock) +{ + if (node == NULL) { + return false; + } + + xmlNodePtr tmpNode = node->children; + xmlNodePtr p; + + if (tmpNode == NULL) { + return false; + } + + while (tmpNode) { + if (xmlStrcmp(tmpNode->name, (const xmlChar*)"inside") == 0) { + p = tmpNode->children; + + while (p) { + LootBlock lootBlock; + + if (loadLootItem(p, lootBlock)) { + lBlock.childLoot.push_back(lootBlock); + } + + p = p->next; + } + + return true; + } + + tmpNode = tmpNode->next; + } + + return false; +} + +MonsterType* Monsters::getMonsterType(const std::string& name) +{ + uint32_t mId = getIdByName(name); + + if (mId == 0) { + return NULL; + } + + return getMonsterType(mId); +} + +MonsterType* Monsters::getMonsterType(uint32_t mid) +{ + MonsterMap::iterator it = monsters.find(mid); + + if (it != monsters.end()) { + return it->second; + } else { + return NULL; + } +} + +uint32_t Monsters::getIdByName(const std::string& name) +{ + std::string lower_name = name; + std::transform(lower_name.begin(), lower_name.end(), lower_name.begin(), tolower); + MonsterNameMap::iterator it = monsterNames.find(lower_name); + + if (it != monsterNames.end()) { + return it->second; + } else { + return 0; + } +} + +Monsters::~Monsters() +{ + for (MonsterMap::iterator it = monsters.begin(), end = monsters.end(); it != end; ++it) { + delete it->second; + } +} diff --git a/src/monsters.h b/src/monsters.h new file mode 100644 index 0000000000..6b2795536a --- /dev/null +++ b/src/monsters.h @@ -0,0 +1,181 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_MONSTERS_H__ +#define __OTSERV_MONSTERS_H__ + +#include +#include "creature.h" + +#define MAX_LOOTCHANCE 100000 +#define MAX_STATICWALK 100 + +struct LootBlock { + uint16_t id; + uint32_t countmax; + uint32_t chance; + + //optional + int32_t subType; + int32_t actionId; + std::string text; + + typedef std::list LootItems; + LootItems childLoot; + LootBlock() { + id = 0; + countmax = 0; + chance = 0; + + subType = -1; + actionId = -1; + text = ""; + } +}; + +struct summonBlock_t { + std::string name; + uint32_t chance; + uint32_t speed; +}; + +class BaseSpell; + +struct spellBlock_t { + BaseSpell* spell; + uint32_t chance; + uint32_t speed; + uint32_t range; + int32_t minCombatValue; + int32_t maxCombatValue; + bool combatSpell; + bool isMelee; +}; + +struct voiceBlock_t { + std::string text; + bool yellText; +}; + +typedef std::list LootItems; +typedef std::list SummonList; +typedef std::list SpellList; +typedef std::vector VoiceVector; +typedef std::list MonsterScriptList; +typedef std::map ElementMap; + +class MonsterType +{ + public: + MonsterType(); + ~MonsterType(); + + void reset(); + + std::string name; + std::string nameDescription; + uint64_t experience; + + int32_t defense; + int32_t armor; + + bool canPushItems; + bool canPushCreatures; + uint32_t staticAttackChance; + int32_t maxSummons; + int32_t targetDistance; + int32_t runAwayHealth; + bool pushable; + int32_t baseSpeed; + int32_t health; + int32_t healthMax; + + Outfit_t outfit; + uint16_t lookcorpse; + int32_t conditionImmunities; + int32_t damageImmunities; + RaceType_t race; + bool isSummonable; + bool isIllusionable; + bool isConvinceable; + bool isAttackable; + bool isHostile; + bool hiddenHealth; + + int32_t lightLevel; + int32_t lightColor; + + uint32_t manaCost; + SummonList summonList; + LootItems lootItems; + ElementMap elementMap; + SpellList spellAttackList; + SpellList spellDefenseList; + + uint32_t yellChance; + uint32_t yellSpeedTicks; + VoiceVector voiceVector; + + int32_t changeTargetSpeed; + int32_t changeTargetChance; + + MonsterScriptList scriptList; + + void createLoot(Container* corpse); + bool createLootContainer(Container* parent, const LootBlock& lootblock); + std::list createLootItem(const LootBlock& lootblock); +}; + +class Monsters +{ + public: + Monsters(); + ~Monsters(); + + bool loadFromXml(bool reloading = false); + bool isLoaded() const { + return loaded; + } + bool reload(); + + MonsterType* getMonsterType(const std::string& name); + MonsterType* getMonsterType(uint32_t mid); + uint32_t getIdByName(const std::string& name); + + static uint32_t getLootRandom(); + + private: + ConditionDamage* getDamageCondition(ConditionType_t conditionType, + int32_t maxDamage, int32_t minDamage, int32_t startDamage, uint32_t tickInterval); + bool deserializeSpell(xmlNodePtr node, spellBlock_t& sb, const std::string& description = ""); + + bool loadMonster(const std::string& file, const std::string& monster_name, bool reloading = false); + + bool loadLootContainer(xmlNodePtr, LootBlock&); + bool loadLootItem(xmlNodePtr, LootBlock&); + + typedef std::map MonsterNameMap; + MonsterNameMap monsterNames; + + typedef std::map MonsterMap; + MonsterMap monsters; + + bool loaded; +}; + +#endif diff --git a/src/mounts.cpp b/src/mounts.cpp new file mode 100644 index 0000000000..8f182f2310 --- /dev/null +++ b/src/mounts.cpp @@ -0,0 +1,156 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "mounts.h" +#include "tools.h" + +Mount::Mount(uint8_t _id, uint16_t _clientId, const std::string& _name, int32_t _speed, bool _premium) +{ + id = _id; + clientId = _clientId; + name = _name; + speed = _speed; + premium = _premium; +} + +bool Mount::isTamed(Player* player) const +{ + if (!player) { + return false; + } + + if (player->isAccessPlayer()) { + return true; + } + + if (premium && !player->isPremium()) { + return false; + } + + uint8_t tmpId = id - 1; + + int32_t value = 0; + + if (!player->getStorageValue(PSTRG_MOUNTS_RANGE_START + (tmpId / 31), value)) { + return false; + } + + int32_t tmp = (1 << (tmpId % 31)); + return (tmp & value) == tmp; +} + +Mounts::~Mounts() +{ + for (MountsList::iterator it = mounts.begin(), end = mounts.end(); it != end; ++it) { + delete (*it); + } + + mounts.clear(); +} + +bool Mounts::reload() +{ + for (MountsList::iterator it = mounts.begin(), end = mounts.end(); it != end; ++it) { + delete (*it); + } + + mounts.clear(); + return loadFromXml(); +} + +bool Mounts::loadFromXml() +{ + xmlDocPtr doc = xmlParseFile("data/XML/mounts.xml"); + + if (!doc) { + return false; + } + + xmlNodePtr root, p; + root = xmlDocGetRootElement(doc); + + if (xmlStrcmp(root->name, (const xmlChar*)"mounts") != 0) { + xmlFreeDoc(doc); + return false; + } + + int32_t intValue; + std::string strValue; + p = root->children; + + while (p) { + if (xmlStrcmp(p->name, (const xmlChar*)"mount") == 0) { + int8_t id = 0; + int16_t clientid = 0; + std::string name = ""; + int32_t speed = 0; + bool premium = true; + + if (readXMLInteger(p, "id", intValue)) { + id = intValue; + } + + if (readXMLInteger(p, "clientid", intValue)) { + clientid = intValue; + } + + if (readXMLString(p, "name", strValue)) { + name = strValue; + } + + if (readXMLInteger(p, "speed", intValue)) { + speed = intValue; + } + + if (readXMLString(p, "premium", strValue)) { + premium = booleanString(strValue); + } + + mounts.push_back(new Mount(id, clientid, name, speed, premium)); + } + + p = p->next; + } + + xmlFreeDoc(doc); + return true; +} + +Mount* Mounts::getMountByID(uint8_t id) +{ + for (MountsList::iterator it = mounts.begin(), end = mounts.end(); it != end; ++it) { + if ((*it)->getID() == id) { + return (*it); + } + } + + return NULL; +} + +Mount* Mounts::getMountByClientID(uint16_t clientId) +{ + for (MountsList::iterator it = mounts.begin(), end = mounts.end(); it != end; ++it) { + if ((*it)->getClientID() == clientId) { + return (*it); + } + } + + return NULL; +} diff --git a/src/mounts.h b/src/mounts.h new file mode 100644 index 0000000000..8057917187 --- /dev/null +++ b/src/mounts.h @@ -0,0 +1,89 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef _MOUNTS_H_ +#define _MOUNTS_H_ + +#include +#include +#include "player.h" +#include "networkmessage.h" + +class Mount; + +typedef std::list MountsList; + +class Mount +{ + public: + Mount(uint8_t _id, uint16_t _clientId, const std::string& _name, int32_t _speed, bool _premium); + ~Mount() {} + + bool isTamed(Player* player) const; + uint8_t getID() const { + return id; + } + uint16_t getClientID() const { + return clientId; + } + std::string getName() const { + return name; + } + int32_t getSpeed() const { + return speed; + } + bool isPremium() const { + return premium; + } + + private: + uint8_t id; + uint16_t clientId; + std::string name; + int32_t speed; + bool premium; +}; + +class Mounts +{ + public: + Mounts() {} + ~Mounts(); + + static Mounts* getInstance() { + static Mounts instance; + return &instance; + } + + bool reload(); + bool loadFromXml(); + Mount* getMountByID(uint8_t id); + Mount* getMountByClientID(uint16_t clientId); + + MountsList::const_iterator getFirstMount() const { + return mounts.begin(); + } + MountsList::const_iterator getLastMount() const { + return mounts.end(); + } + + private: + MountsList mounts; +}; + +#endif diff --git a/src/movement.cpp b/src/movement.cpp new file mode 100644 index 0000000000..0f9eb18d67 --- /dev/null +++ b/src/movement.cpp @@ -0,0 +1,1081 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "game.h" +#include "creature.h" +#include "player.h" +#include "tile.h" +#include "tools.h" +#include "combat.h" +#include "vocation.h" + +#include +#include + +#include "movement.h" + +extern Game g_game; +extern Vocations g_vocations; +extern MoveEvents* g_moveEvents; + +MoveEvents::MoveEvents() : + m_scriptInterface("MoveEvents Interface") +{ + m_scriptInterface.initState(); +} + +MoveEvents::~MoveEvents() +{ + clear(); +} + +void MoveEvents::clear() +{ + MoveListMap::iterator it1 = m_itemIdMap.begin(); + + while (it1 != m_itemIdMap.end()) { + MoveEventList& tmpMoveEventList = it1->second; + + for (int32_t i = 0; i < MOVE_EVENT_LAST; ++i) { + std::list& moveEventList = tmpMoveEventList.moveEvent[i]; + + for (std::list::iterator it = moveEventList.begin(), end = moveEventList.end(); it != end; ++it) { + delete (*it); + } + } + + m_itemIdMap.erase(it1); + it1 = m_itemIdMap.begin(); + } + + MoveListMap::iterator it2 = m_actionIdMap.begin(); + + while (it2 != m_actionIdMap.end()) { + MoveEventList& tmpMoveEventList = it2->second; + + for (int32_t i = 0; i < MOVE_EVENT_LAST; ++i) { + std::list& moveEventList = tmpMoveEventList.moveEvent[i]; + + for (std::list::iterator it = moveEventList.begin(), end = moveEventList.end(); it != end; ++it) { + delete (*it); + } + } + + m_actionIdMap.erase(it2); + it2 = m_actionIdMap.begin(); + } + + MoveListMap::iterator it3 = m_uniqueIdMap.begin(); + + while (it3 != m_uniqueIdMap.end()) { + MoveEventList& tmpMoveEventList = it3->second; + + for (int32_t i = 0; i < MOVE_EVENT_LAST; ++i) { + std::list& moveEventList = tmpMoveEventList.moveEvent[i]; + + for (std::list::iterator it = moveEventList.begin(), end = moveEventList.end(); it != end; ++it) { + delete (*it); + } + } + + m_uniqueIdMap.erase(it3); + it3 = m_uniqueIdMap.begin(); + } + + MovePosListMap::iterator posIter = m_positionMap.begin(); + + while (posIter != m_positionMap.end()) { + MoveEventList& tmpMoveEventList = posIter->second; + + for (int i = 0; i < MOVE_EVENT_LAST; ++i) { + std::list& moveEventList = tmpMoveEventList.moveEvent[i]; + + for (std::list::iterator it = moveEventList.begin(), end = moveEventList.end(); it != end; ++it) { + delete (*it); + } + } + + m_positionMap.erase(posIter); + posIter = m_positionMap.begin(); + } + + m_scriptInterface.reInitState(); +} + +LuaScriptInterface& MoveEvents::getScriptInterface() +{ + return m_scriptInterface; +} + +std::string MoveEvents::getScriptBaseName() +{ + return "movements"; +} + +Event* MoveEvents::getEvent(const std::string& nodeName) +{ + if (asLowerCaseString(nodeName) == "movevent") { + return new MoveEvent(&m_scriptInterface); + } else { + return NULL; + } +} + +bool MoveEvents::registerEvent(Event* event, xmlNodePtr p) +{ + MoveEvent* moveEvent = dynamic_cast(event); + + if (!moveEvent) { + return false; + } + + bool success = true; + int32_t id, endId; + std::string str; + + MoveEvent_t eventType = moveEvent->getEventType(); + + if (eventType == MOVE_EVENT_ADD_ITEM || eventType == MOVE_EVENT_REMOVE_ITEM) { + if (readXMLInteger(p, "tileitem", id) && id == 1) { + switch (eventType) { + case MOVE_EVENT_ADD_ITEM: + moveEvent->setEventType(MOVE_EVENT_ADD_ITEM_ITEMTILE); + break; + case MOVE_EVENT_REMOVE_ITEM: + moveEvent->setEventType(MOVE_EVENT_REMOVE_ITEM_ITEMTILE); + break; + default: + break; + } + } + } + + if (readXMLInteger(p, "itemid", id)) { + addEvent(moveEvent, id, m_itemIdMap); + + if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) { + ItemType& it = Item::items.getItemType(id); + it.wieldInfo = moveEvent->getWieldInfo(); + it.minReqLevel = moveEvent->getReqLevel(); + it.minReqMagicLevel = moveEvent->getReqMagLv(); + it.vocationString = moveEvent->getVocationString(); + } + } else if (readXMLInteger(p, "fromid", id) && readXMLInteger(p, "toid", endId)) { + addEvent(moveEvent, id, m_itemIdMap); + + if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) { + ItemType& it = Item::items.getItemType(id); + it.wieldInfo = moveEvent->getWieldInfo(); + it.minReqLevel = moveEvent->getReqLevel(); + it.minReqMagicLevel = moveEvent->getReqMagLv(); + it.vocationString = moveEvent->getVocationString(); + + while (id < endId) { + id++; + addEvent(new MoveEvent(moveEvent), id, m_itemIdMap); + + ItemType& tit = Item::items.getItemType(id); + tit.wieldInfo = moveEvent->getWieldInfo(); + tit.minReqLevel = moveEvent->getReqLevel(); + tit.minReqMagicLevel = moveEvent->getReqMagLv(); + tit.vocationString = moveEvent->getVocationString(); + } + } else { + while (id < endId) { + addEvent(new MoveEvent(moveEvent), ++id, m_itemIdMap); + } + } + } else if (readXMLInteger(p, "uniqueid", id)) { + addEvent(moveEvent, id, m_uniqueIdMap); + } else if (readXMLInteger(p, "fromuid", id) && readXMLInteger(p, "touid", endId)) { + addEvent(moveEvent, id, m_uniqueIdMap); + + while (id < endId) { + addEvent(new MoveEvent(moveEvent), ++id, m_uniqueIdMap); + } + } else if (readXMLInteger(p, "actionid", id) || readXMLInteger(p, "aid", id)) { + addEvent(moveEvent, id, m_actionIdMap); + } else if (readXMLInteger(p, "fromaid", id) && readXMLInteger(p, "toaid", endId)) { + addEvent(moveEvent, id, m_actionIdMap); + + while (id < endId) { + addEvent(new MoveEvent(moveEvent), ++id, m_actionIdMap); + } + } else if (readXMLString(p, "pos", str)) { + std::vector posList = vectorAtoi(explodeString(str, ";")); + + if (posList.size() >= 3) { + Position pos(posList[0], posList[1], posList[2]); + addEvent(moveEvent, pos, m_positionMap); + } else { + success = false; + } + } else { + success = false; + } + + return success; +} + +void MoveEvents::addEvent(MoveEvent* moveEvent, int32_t id, MoveListMap& map) +{ + MoveListMap::iterator it = map.find(id); + + if (it == map.end()) { + MoveEventList moveEventList; + moveEventList.moveEvent[moveEvent->getEventType()].push_back(moveEvent); + map[id] = moveEventList; + } else { + std::list& moveEventList = it->second.moveEvent[moveEvent->getEventType()]; + + for (std::list::iterator it = moveEventList.begin(); it != moveEventList.end(); ++it) { + if ((*it)->getSlot() == moveEvent->getSlot()) { + std::cout << "Warning: [MoveEvents::addEvent] Duplicate move event found: " << id << std::endl; + } + } + + moveEventList.push_back(moveEvent); + } +} + +MoveEvent* MoveEvents::getEvent(Item* item, MoveEvent_t eventType, slots_t slot) +{ + uint32_t slotp = 0; + + switch (slot) { + case SLOT_HEAD: + slotp = SLOTP_HEAD; + break; + case SLOT_NECKLACE: + slotp = SLOTP_NECKLACE; + break; + case SLOT_BACKPACK: + slotp = SLOTP_BACKPACK; + break; + case SLOT_ARMOR: + slotp = SLOTP_ARMOR; + break; + case SLOT_RIGHT: + slotp = SLOTP_RIGHT; + break; + case SLOT_LEFT: + slotp = SLOTP_LEFT; + break; + case SLOT_LEGS: + slotp = SLOTP_LEGS; + break; + case SLOT_FEET: + slotp = SLOTP_FEET; + break; + case SLOT_AMMO: + slotp = SLOTP_AMMO; + break; + case SLOT_RING: + slotp = SLOTP_RING; + break; + default: + break; + } + + MoveListMap::iterator it = m_itemIdMap.find(item->getID()); + + if (it != m_itemIdMap.end()) { + std::list& moveEventList = it->second.moveEvent[eventType]; + + for (std::list::iterator it = moveEventList.begin(); it != moveEventList.end(); ++it) { + if (((*it)->getSlot() & slotp) != 0) { + return *it; + } + } + } + + return NULL; +} + +MoveEvent* MoveEvents::getEvent(Item* item, MoveEvent_t eventType) +{ + MoveListMap::iterator it; + + if (item->getUniqueId() != 0) { + it = m_uniqueIdMap.find(item->getUniqueId()); + + if (it != m_uniqueIdMap.end()) { + std::list& moveEventList = it->second.moveEvent[eventType]; + + if (!moveEventList.empty()) { + return *moveEventList.begin(); + } + } + } + + if (item->getActionId() != 0) { + it = m_actionIdMap.find(item->getActionId()); + + if (it != m_actionIdMap.end()) { + std::list& moveEventList = it->second.moveEvent[eventType]; + + if (!moveEventList.empty()) { + return *moveEventList.begin(); + } + } + } + + it = m_itemIdMap.find(item->getID()); + + if (it != m_itemIdMap.end()) { + std::list& moveEventList = it->second.moveEvent[eventType]; + + if (!moveEventList.empty()) { + return *moveEventList.begin(); + } + } + + return NULL; +} + +void MoveEvents::addEvent(MoveEvent* moveEvent, Position pos, MovePosListMap& map) +{ + MovePosListMap::iterator it = map.find(pos); + + if (it == map.end()) { + MoveEventList moveEventList; + moveEventList.moveEvent[moveEvent->getEventType()].push_back(moveEvent); + map[pos] = moveEventList; + } else { + std::list& moveEventList = it->second.moveEvent[moveEvent->getEventType()]; + + if (!moveEventList.empty()) { + std::cout << "Warning: [MoveEvents::addEvent] Duplicate move event found: " << pos << std::endl; + } + + moveEventList.push_back(moveEvent); + } +} + +MoveEvent* MoveEvents::getEvent(const Tile* tile, MoveEvent_t eventType) +{ + MovePosListMap::iterator it = m_positionMap.find(tile->getPosition()); + + if (it != m_positionMap.end()) { + std::list& moveEventList = it->second.moveEvent[eventType]; + + if (!moveEventList.empty()) { + return *moveEventList.begin(); + } + } + + return NULL; +} + +uint32_t MoveEvents::onCreatureMove(Creature* creature, const Tile* tile, bool isIn) +{ + MoveEvent_t eventType; + + if (isIn) { + eventType = MOVE_EVENT_STEP_IN; + } else { + eventType = MOVE_EVENT_STEP_OUT; + } + + Position pos(0, 0, 0); + + if (tile) { + pos = tile->getPosition(); + } + + uint32_t ret = 1; + MoveEvent* moveEvent = getEvent(tile, eventType); + + if (moveEvent) { + ret = ret & moveEvent->fireStepEvent(creature, NULL, pos); + } + + int32_t j = tile->__getLastIndex(); + Item* tileItem = NULL; + + for (int32_t i = tile->__getFirstIndex(); i < j; ++i) { + Thing* thing = tile->__getThing(i); + + if (thing && (tileItem = thing->getItem())) { + moveEvent = getEvent(tileItem, eventType); + + if (moveEvent) { + ret = ret & moveEvent->fireStepEvent(creature, tileItem, pos); + } + } + } + + return ret; +} + +uint32_t MoveEvents::onPlayerEquip(Player* player, Item* item, slots_t slot, bool isCheck) +{ + MoveEvent* moveEvent = getEvent(item, MOVE_EVENT_EQUIP, slot); + + if (moveEvent) { + return moveEvent->fireEquip(player, item, slot, isCheck); + } + + return 1; +} + +uint32_t MoveEvents::onPlayerDeEquip(Player* player, Item* item, slots_t slot, bool isRemoval) +{ + MoveEvent* moveEvent = getEvent(item, MOVE_EVENT_DEEQUIP, slot); + + if (moveEvent) { + return moveEvent->fireEquip(player, item, slot, isRemoval); + } + + return 1; +} + +uint32_t MoveEvents::onItemMove(Item* item, Tile* tile, bool isAdd) +{ + MoveEvent_t eventType1; + MoveEvent_t eventType2; + + if (isAdd) { + eventType1 = MOVE_EVENT_ADD_ITEM; + eventType2 = MOVE_EVENT_ADD_ITEM_ITEMTILE; + } else { + eventType1 = MOVE_EVENT_REMOVE_ITEM; + eventType2 = MOVE_EVENT_REMOVE_ITEM_ITEMTILE; + } + + uint32_t ret = 1; + MoveEvent* moveEvent = getEvent(tile, eventType1); + + if (moveEvent) { + ret &= moveEvent->fireAddRemItem(item, NULL, tile->getPosition()); + } + + moveEvent = getEvent(item, eventType1); + + if (moveEvent) { + ret &= moveEvent->fireAddRemItem(item, NULL, tile->getPosition()); + } + + int32_t j = tile->__getLastIndex(); + Item* tileItem = NULL; + + for (int32_t i = tile->__getFirstIndex(); i < j; ++i) { + Thing* thing = tile->__getThing(i); + + if (thing && (tileItem = thing->getItem()) && (tileItem != item)) { + moveEvent = getEvent(tileItem, eventType2); + + if (moveEvent) { + ret &= moveEvent->fireAddRemItem(item, tileItem, tile->getPosition()); + } + } + } + + return ret; +} + +MoveEvent::MoveEvent(LuaScriptInterface* _interface) : + Event(_interface) +{ + m_eventType = MOVE_EVENT_NONE; + stepFunction = NULL; + moveFunction = NULL; + equipFunction = NULL; + slot = SLOTP_WHEREEVER; + wieldInfo = 0; + reqLevel = 0; + reqMagLevel = 0; + premium = false; +} + +MoveEvent::MoveEvent(const MoveEvent* copy) : + Event(copy) +{ + m_eventType = copy->m_eventType; + stepFunction = copy->stepFunction; + moveFunction = copy->moveFunction; + equipFunction = copy->equipFunction; + slot = copy->slot; + + if (copy->m_eventType == MOVE_EVENT_EQUIP) { + wieldInfo = copy->wieldInfo; + reqLevel = copy->reqLevel; + reqMagLevel = copy->reqMagLevel; + vocationString = copy->vocationString; + premium = copy->premium; + vocEquipMap = copy->vocEquipMap; + } +} + +MoveEvent::~MoveEvent() +{ + // +} + +std::string MoveEvent::getScriptEventName() +{ + switch (m_eventType) { + case MOVE_EVENT_STEP_IN: + return "onStepIn"; + + case MOVE_EVENT_STEP_OUT: + return "onStepOut"; + + case MOVE_EVENT_EQUIP: + return "onEquip"; + + case MOVE_EVENT_DEEQUIP: + return "onDeEquip"; + + case MOVE_EVENT_ADD_ITEM: + return "onAddItem"; + + case MOVE_EVENT_REMOVE_ITEM: + return "onRemoveItem"; + + default: + std::cout << "Error: [MoveEvent::getScriptEventName()] No valid event type." << std::endl; + return ""; + } +} + +bool MoveEvent::configureEvent(xmlNodePtr p) +{ + std::string str; + int32_t intValue; + + if (readXMLString(p, "event", str)) { + std::string tmpStr = asLowerCaseString(str); + + if (tmpStr == "stepin") { + m_eventType = MOVE_EVENT_STEP_IN; + } else if (tmpStr == "stepout") { + m_eventType = MOVE_EVENT_STEP_OUT; + } else if (tmpStr == "equip") { + m_eventType = MOVE_EVENT_EQUIP; + } else if (tmpStr == "deequip") { + m_eventType = MOVE_EVENT_DEEQUIP; + } else if (tmpStr == "additem") { + m_eventType = MOVE_EVENT_ADD_ITEM; + } else if (tmpStr == "removeitem") { + m_eventType = MOVE_EVENT_REMOVE_ITEM; + } else { + std::cout << "Error: [MoveEvent::configureMoveEvent] No valid event name " << str << std::endl; + return false; + } + + if (m_eventType == MOVE_EVENT_EQUIP || m_eventType == MOVE_EVENT_DEEQUIP) { + if (readXMLString(p, "slot", str)) { + std::string tmpStr = asLowerCaseString(str); + + if (tmpStr == "head") { + slot = SLOTP_HEAD; + } else if (tmpStr == "necklace") { + slot = SLOTP_NECKLACE; + } else if (tmpStr == "backpack") { + slot = SLOTP_BACKPACK; + } else if (tmpStr == "armor") { + slot = SLOTP_ARMOR; + } else if (tmpStr == "right-hand") { + slot = SLOTP_RIGHT; + } else if (tmpStr == "left-hand") { + slot = SLOTP_LEFT; + } else if (tmpStr == "hand" || tmpStr == "shield") { + slot = SLOTP_RIGHT | SLOTP_LEFT; + } else if (tmpStr == "legs") { + slot = SLOTP_LEGS; + } else if (tmpStr == "feet") { + slot = SLOTP_FEET; + } else if (tmpStr == "ring") { + slot = SLOTP_RING; + } else if (tmpStr == "ammo") { + slot = SLOTP_AMMO; + } else { + std::cout << "Warning: [MoveEvent::configureMoveEvent] " << "Unknown slot type " << str << std::endl; + } + } + + wieldInfo = 0; + + if (readXMLInteger(p, "lvl", intValue) || readXMLInteger(p, "level", intValue)) { + reqLevel = intValue; + + if (reqLevel > 0) { + wieldInfo |= WIELDINFO_LEVEL; + } + } + + if (readXMLInteger(p, "maglv", intValue) || readXMLInteger(p, "maglevel", intValue)) { + reqMagLevel = intValue; + + if (reqMagLevel > 0) { + wieldInfo |= WIELDINFO_MAGLV; + } + } + + if (readXMLInteger(p, "prem", intValue) || readXMLInteger(p, "premium", intValue)) { + premium = (intValue != 0); + + if (premium) { + wieldInfo |= WIELDINFO_PREMIUM; + } + } + + //Gather vocation information + typedef std::list STRING_LIST; + STRING_LIST vocStringList; + xmlNodePtr vocationNode = p->children; + + while (vocationNode) { + if (xmlStrcmp(vocationNode->name, (const xmlChar*)"vocation") == 0) { + if (readXMLString(vocationNode, "name", str)) { + int32_t vocationId = g_vocations.getVocationId(str); + + if (vocationId != -1) { + vocEquipMap[vocationId] = true; + intValue = 1; + readXMLInteger(vocationNode, "showInDescription", intValue); + + if (intValue != 0) { + toLowerCaseString(str); + vocStringList.push_back(str); + } + } + } + } + + vocationNode = vocationNode->next; + } + + if (!vocEquipMap.empty()) { + wieldInfo |= WIELDINFO_VOCREQ; + } + + if (!vocStringList.empty()) { + for (STRING_LIST::iterator it = vocStringList.begin(); it != vocStringList.end(); ++it) { + if (*it != vocStringList.front()) { + if (*it != vocStringList.back()) { + vocationString += ", "; + } else { + vocationString += " and "; + } + } + + vocationString += *it; + vocationString += "s"; + } + } + } + } else { + std::cout << "Error: [MoveEvent::configureMoveEvent] No event found." << std::endl; + return false; + } + + return true; +} + +bool MoveEvent::loadFunction(const std::string& functionName) +{ + std::string tmpFunctionName = asLowerCaseString(functionName); + + if (tmpFunctionName == "onstepinfield") { + stepFunction = StepInField; + } else if (tmpFunctionName == "onstepoutfield") { + stepFunction = StepOutField; + } else if (tmpFunctionName == "onaddfield") { + moveFunction = AddItemField; + } else if (tmpFunctionName == "onremovefield") { + moveFunction = RemoveItemField; + } else if (tmpFunctionName == "onequipitem") { + equipFunction = EquipItem; + } else if (tmpFunctionName == "ondeequipitem") { + equipFunction = DeEquipItem; + } else { + std::cout << "[Warning - MoveEvent::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl; + return false; + } + + m_scripted = false; + return true; +} + +MoveEvent_t MoveEvent::getEventType() const +{ + if (m_eventType == MOVE_EVENT_NONE) { + std::cout << "Error: [MoveEvent::getEventType()] MOVE_EVENT_NONE" << std::endl; + return (MoveEvent_t)0; + } + + return m_eventType; +} + +void MoveEvent::setEventType(MoveEvent_t type) +{ + m_eventType = type; +} + +uint32_t MoveEvent::StepInField(Creature* creature, Item* item, const Position& pos) +{ + MagicField* field = item->getMagicField(); + + if (field) { + field->onStepInField(creature); + return 1; + } + + return LUA_ERROR_ITEM_NOT_FOUND; +} + +uint32_t MoveEvent::StepOutField(Creature* creature, Item* item, const Position& pos) +{ + return 1; +} + +uint32_t MoveEvent::AddItemField(Item* item, Item* tileItem, const Position& pos) +{ + if (MagicField* field = item->getMagicField()) { + Tile* tile = item->getTile(); + + if (CreatureVector* creatures = tile->getCreatures()) { + for (CreatureVector::iterator cit = creatures->begin(); cit != creatures->end(); ++cit) { + field->onStepInField(*cit); + } + } + + return 1; + } + + return LUA_ERROR_ITEM_NOT_FOUND; +} + +uint32_t MoveEvent::RemoveItemField(Item* item, Item* tileItem, const Position& pos) +{ + return 1; +} + +uint32_t MoveEvent::EquipItem(MoveEvent* moveEvent, Player* player, Item* item, slots_t slot, bool isCheck) +{ + if (player->isItemAbilityEnabled(slot)) { + return 1; + } + + if (!player->hasFlag(PlayerFlag_IgnoreWeaponCheck) && moveEvent->getWieldInfo() != 0) { + if (player->getLevel() < (uint32_t)moveEvent->getReqLevel() || player->getMagicLevel() < moveEvent->getReqMagLv()) { + return 0; + } + + if (moveEvent->isPremium() && !player->isPremium()) { + return 0; + } + + const VocEquipMap& vocEquipMap = moveEvent->getVocEquipMap(); + + if (!vocEquipMap.empty() && vocEquipMap.find(player->getVocationId()) == vocEquipMap.end()) { + return 0; + } + } + + if (isCheck) { + return 1; + } + + const ItemType& it = Item::items[item->getID()]; + + if (it.transformEquipTo != 0) { + Item* newItem = g_game.transformItem(item, it.transformEquipTo); + g_game.startDecay(newItem); + } else { + player->setItemAbility(slot, true); + } + + if (!it.abilities) { + return 1; + } + + if (it.abilities->invisible) { + Condition* condition = Condition::createCondition((ConditionId_t)slot, CONDITION_INVISIBLE, -1, 0); + player->addCondition(condition); + } + + if (it.abilities->manaShield) { + Condition* condition = Condition::createCondition((ConditionId_t)slot, CONDITION_MANASHIELD, -1, 0); + player->addCondition(condition); + } + + if (it.abilities->speed != 0) { + g_game.changeSpeed(player, it.abilities->speed); + } + + if (it.abilities->conditionSuppressions != 0) { + player->setConditionSuppressions(it.abilities->conditionSuppressions, false); + player->sendIcons(); + } + + if (it.abilities->regeneration) { + Condition* condition = Condition::createCondition((ConditionId_t)slot, CONDITION_REGENERATION, -1, 0); + + if (it.abilities->healthGain != 0) { + condition->setParam(CONDITIONPARAM_HEALTHGAIN, it.abilities->healthGain); + } + + if (it.abilities->healthTicks != 0) { + condition->setParam(CONDITIONPARAM_HEALTHTICKS, it.abilities->healthTicks); + } + + if (it.abilities->manaGain != 0) { + condition->setParam(CONDITIONPARAM_MANAGAIN, it.abilities->manaGain); + } + + if (it.abilities->manaTicks != 0) { + condition->setParam(CONDITIONPARAM_MANATICKS, it.abilities->manaTicks); + } + + player->addCondition(condition); + } + + //skill modifiers + bool needUpdateSkills = false; + + for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + if (it.abilities->skills[i]) { + needUpdateSkills = true; + player->setVarSkill((skills_t)i, it.abilities->skills[i]); + } + } + + if (needUpdateSkills) { + player->sendSkills(); + } + + //stat modifiers + bool needUpdateStats = false; + + for (int32_t s = STAT_FIRST; s <= STAT_LAST; ++s) { + if (it.abilities->stats[s]) { + needUpdateStats = true; + player->setVarStats((stats_t)s, it.abilities->stats[s]); + } + + if (it.abilities->statsPercent[s]) { + needUpdateStats = true; + player->setVarStats((stats_t)s, (int32_t)(player->getDefaultStats((stats_t)s) * ((it.abilities->statsPercent[s] - 100) / 100.f))); + } + } + + if (needUpdateStats) { + player->sendStats(); + } + + return 1; +} + +uint32_t MoveEvent::DeEquipItem(MoveEvent* moveEvent, Player* player, Item* item, slots_t slot, bool isRemoval) +{ + if (!player->isItemAbilityEnabled(slot)) { + return 1; + } + + player->setItemAbility(slot, false); + + const ItemType& it = Item::items[item->getID()]; + + if (isRemoval && it.transformDeEquipTo != 0) { + g_game.transformItem(item, it.transformDeEquipTo); + g_game.startDecay(item); + } + + if (!it.abilities) { + return 1; + } + + if (it.abilities->invisible) { + player->removeCondition(CONDITION_INVISIBLE, (ConditionId_t)slot); + } + + if (it.abilities->manaShield) { + player->removeCondition(CONDITION_MANASHIELD, (ConditionId_t)slot); + } + + if (it.abilities->speed != 0) { + g_game.changeSpeed(player, -it.abilities->speed); + } + + if (it.abilities->conditionSuppressions != 0) { + player->setConditionSuppressions(it.abilities->conditionSuppressions, true); + player->sendIcons(); + } + + if (it.abilities->regeneration) { + player->removeCondition(CONDITION_REGENERATION, (ConditionId_t)slot); + } + + //skill modifiers + bool needUpdateSkills = false; + + for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + if (it.abilities->skills[i] != 0) { + needUpdateSkills = true; + player->setVarSkill((skills_t)i, -it.abilities->skills[i]); + } + } + + if (needUpdateSkills) { + player->sendSkills(); + } + + //stat modifiers + bool needUpdateStats = false; + + for (int32_t s = STAT_FIRST; s <= STAT_LAST; ++s) { + if (it.abilities->stats[s]) { + needUpdateStats = true; + player->setVarStats((stats_t)s, -it.abilities->stats[s]); + } + + if (it.abilities->statsPercent[s]) { + needUpdateStats = true; + player->setVarStats((stats_t)s, -(int32_t)(player->getDefaultStats((stats_t)s) * ((it.abilities->statsPercent[s] - 100) / 100.f))); + } + } + + if (needUpdateStats) { + player->sendStats(); + } + + return 1; +} + +uint32_t MoveEvent::fireStepEvent(Creature* creature, Item* item, const Position& pos) +{ + if (m_scripted) { + return executeStep(creature, item, pos); + } else { + return stepFunction(creature, item, pos); + } +} + +uint32_t MoveEvent::executeStep(Creature* creature, Item* item, const Position& pos) +{ + //onStepIn(cid, item, pos, fromPosition) + //onStepOut(cid, item, pos, fromPosition) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + env->setScriptId(m_scriptId, m_scriptInterface); + env->setRealPos(creature->getPosition()); + + uint32_t cid = env->addThing(creature); + uint32_t itemid = env->addThing(item); + + lua_State* L = m_scriptInterface->getLuaState(); + + m_scriptInterface->pushFunction(m_scriptId); + lua_pushnumber(L, cid); + LuaScriptInterface::pushThing(L, item, itemid); + LuaScriptInterface::pushPosition(L, pos, 0); + LuaScriptInterface::pushPosition(L, creature->getLastPosition(), 0); + + bool result = m_scriptInterface->callFunction(4); + m_scriptInterface->releaseScriptEnv(); + + return result; + } else { + std::cout << "[Error] Call stack overflow. MoveEvent::executeStep" << std::endl; + return 0; + } +} + +uint32_t MoveEvent::fireEquip(Player* player, Item* item, slots_t slot, bool boolean) +{ + if (m_scripted) { + return executeEquip(player, item, slot); + } else { + return equipFunction(this, player, item, slot, boolean); + } +} + +uint32_t MoveEvent::executeEquip(Player* player, Item* item, slots_t slot) +{ + //onEquip(cid, item, slot) + //onDeEquip(cid, item, slot) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + env->setScriptId(m_scriptId, m_scriptInterface); + env->setRealPos(player->getPosition()); + + uint32_t cid = env->addThing(player); + uint32_t itemid = env->addThing(item); + + lua_State* L = m_scriptInterface->getLuaState(); + + m_scriptInterface->pushFunction(m_scriptId); + lua_pushnumber(L, cid); + LuaScriptInterface::pushThing(L, item, itemid); + lua_pushnumber(L, slot); + + bool result = m_scriptInterface->callFunction(3); + m_scriptInterface->releaseScriptEnv(); + + return result; + } else { + std::cout << "[Error] Call stack overflow. MoveEvent::executeEquip" << std::endl; + return 0; + } +} + +uint32_t MoveEvent::fireAddRemItem(Item* item, Item* tileItem, const Position& pos) +{ + if (m_scripted) { + return executeAddRemItem(item, tileItem, pos); + } else { + return moveFunction(item, tileItem, pos); + } +} + +uint32_t MoveEvent::executeAddRemItem(Item* item, Item* tileItem, const Position& pos) +{ + //onAddItem(moveitem, tileitem, pos) + //onRemoveItem(moveitem, tileitem, pos) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + env->setScriptId(m_scriptId, m_scriptInterface); + env->setRealPos(pos); + + uint32_t itemidMoved = env->addThing(item); + uint32_t itemidTile = env->addThing(tileItem); + + lua_State* L = m_scriptInterface->getLuaState(); + + m_scriptInterface->pushFunction(m_scriptId); + LuaScriptInterface::pushThing(L, item, itemidMoved); + LuaScriptInterface::pushThing(L, tileItem, itemidTile); + LuaScriptInterface::pushPosition(L, pos, 0); + + bool result = m_scriptInterface->callFunction(3); + m_scriptInterface->releaseScriptEnv(); + + return result; + } else { + std::cout << "[Error] Call stack overflow. MoveEvent::executeAddRemItem" << std::endl; + return 0; + } +} diff --git a/src/movement.h b/src/movement.h new file mode 100644 index 0000000000..6bc3eb4209 --- /dev/null +++ b/src/movement.h @@ -0,0 +1,165 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __MOVEMENT_H__ +#define __MOVEMENT_H__ + +#include "luascript.h" +#include "baseevents.h" +#include + +enum MoveEvent_t { + MOVE_EVENT_STEP_IN = 0, + MOVE_EVENT_STEP_OUT, + MOVE_EVENT_EQUIP, + MOVE_EVENT_DEEQUIP, + MOVE_EVENT_ADD_ITEM, + MOVE_EVENT_REMOVE_ITEM, + MOVE_EVENT_ADD_ITEM_ITEMTILE, + MOVE_EVENT_REMOVE_ITEM_ITEMTILE, + MOVE_EVENT_LAST, + MOVE_EVENT_NONE, +}; + +class MoveEvent; + +struct MoveEventList { + std::list moveEvent[MOVE_EVENT_LAST]; +}; + +typedef std::map VocEquipMap; + +class MoveEvents : public BaseEvents +{ + public: + MoveEvents(); + virtual ~MoveEvents(); + + uint32_t onCreatureMove(Creature* creature, const Tile* tile, bool isIn); + uint32_t onPlayerEquip(Player* player, Item* item, slots_t slot, bool isCheck); + uint32_t onPlayerDeEquip(Player* player, Item* item, slots_t slot, bool isRemoval); + uint32_t onItemMove(Item* item, Tile* tile, bool isAdd); + + MoveEvent* getEvent(Item* item, MoveEvent_t eventType); + + protected: + typedef std::map MoveListMap; + typedef std::map MovePosListMap; + virtual void clear(); + virtual LuaScriptInterface& getScriptInterface(); + virtual std::string getScriptBaseName(); + virtual Event* getEvent(const std::string& nodeName); + virtual bool registerEvent(Event* event, xmlNodePtr p); + + void registerItemID(int32_t itemId, MoveEvent_t eventType); + void registerActionID(int32_t actionId, MoveEvent_t eventType); + void registerUniqueID(int32_t uniqueId, MoveEvent_t eventType); + + void addEvent(MoveEvent* moveEvent, int32_t id, MoveListMap& map); + + void addEvent(MoveEvent* moveEvent, Position pos, MovePosListMap& map); + MoveEvent* getEvent(const Tile* tile, MoveEvent_t eventType); + + MoveEvent* getEvent(Item* item, MoveEvent_t eventType, slots_t slot); + + MoveListMap m_uniqueIdMap; + MoveListMap m_actionIdMap; + MoveListMap m_itemIdMap; + MovePosListMap m_positionMap; + + LuaScriptInterface m_scriptInterface; +}; + +typedef uint32_t (StepFunction)(Creature* creature, Item* item, const Position& pos); +typedef uint32_t (MoveFunction)(Item* item, Item* tileItem, const Position& pos); +typedef uint32_t (EquipFunction)(MoveEvent* moveEvent, Player* player, Item* item, slots_t slot, bool boolean); + +class MoveEvent : public Event +{ + public: + MoveEvent(LuaScriptInterface* _interface); + MoveEvent(const MoveEvent* copy); + virtual ~MoveEvent(); + + MoveEvent_t getEventType() const; + void setEventType(MoveEvent_t type); + + virtual bool configureEvent(xmlNodePtr p); + virtual bool loadFunction(const std::string& functionName); + + uint32_t fireStepEvent(Creature* creature, Item* item, const Position& pos); + uint32_t fireAddRemItem(Item* item, Item* tileItem, const Position& pos); + uint32_t fireEquip(Player* player, Item* item, slots_t slot, bool boolean); + + uint32_t getSlot() const { + return slot; + } + + //scripting + uint32_t executeStep(Creature* creature, Item* item, const Position& pos); + uint32_t executeEquip(Player* player, Item* item, slots_t slot); + uint32_t executeAddRemItem(Item* item, Item* tileItem, const Position& pos); + // + + //onEquip information + int32_t getReqLevel() const { + return reqLevel; + } + int32_t getReqMagLv() const { + return reqMagLevel; + } + bool isPremium() const { + return premium; + } + const std::string& getVocationString() const { + return vocationString; + } + uint32_t getWieldInfo() const { + return wieldInfo; + } + const VocEquipMap& getVocEquipMap() const { + return vocEquipMap; + } + + protected: + virtual std::string getScriptEventName(); + + static StepFunction StepInField; + static StepFunction StepOutField; + + static MoveFunction AddItemField; + static MoveFunction RemoveItemField; + static EquipFunction EquipItem; + static EquipFunction DeEquipItem; + + MoveEvent_t m_eventType; + StepFunction* stepFunction; + MoveFunction* moveFunction; + EquipFunction* equipFunction; + uint32_t slot; + + //onEquip information + int32_t reqLevel; + int32_t reqMagLevel; + bool premium; + std::string vocationString; + uint32_t wieldInfo; + VocEquipMap vocEquipMap; +}; + +#endif diff --git a/src/networkmessage.cpp b/src/networkmessage.cpp new file mode 100644 index 0000000000..04a0cf4d45 --- /dev/null +++ b/src/networkmessage.cpp @@ -0,0 +1,203 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include +#include +#include + +#include "networkmessage.h" + +#include "container.h" +#include "creature.h" +#include "player.h" + +#include "position.h" +#include "rsa.h" + +int32_t NetworkMessage::decodeHeader() +{ + int32_t size = (int32_t)(m_RealBuf[0] | m_RealBuf[1] << 8); + m_MsgSize = size; + return size; +} + +/******************************************************************************/ +std::string NetworkMessage::GetString(uint16_t stringlen/* = 0*/) +{ + if (stringlen == 0) { + stringlen = GetU16(); + } + + if (!canRead(stringlen)) { + return std::string(); + } + + char* v = (char*)m_RealBuf + m_ReadPos; + m_ReadPos += stringlen; + return std::string(v, stringlen); +} + +Position NetworkMessage::GetPosition() +{ + Position pos; + pos.x = GetU16(); + pos.y = GetU16(); + pos.z = GetByte(); + return pos; +} +/******************************************************************************/ + +void NetworkMessage::AddString(const std::string& value) +{ + uint32_t stringlen = (uint32_t)value.length(); + + if (!canAdd(stringlen + 2) || stringlen > 8192) { + return; + } + + AddU16(stringlen); + memcpy(m_MsgBuf + m_ReadPos, value.c_str(), stringlen); + m_ReadPos += stringlen; + m_MsgSize += stringlen; +} + +void NetworkMessage::AddString(const char* value) +{ + uint32_t stringlen = (uint32_t)strlen(value); + + if (!canAdd(stringlen + 2) || stringlen > 8192) { + return; + } + + AddU16(stringlen); + strcpy((char*)m_MsgBuf + m_ReadPos, value); + m_ReadPos += stringlen; + m_MsgSize += stringlen; +} + +void NetworkMessage::AddDouble(double value, uint8_t precision/* = 2*/) +{ + AddByte(precision); + AddU32((value * std::pow((float)10, precision)) + INT_MAX); +} + +void NetworkMessage::AddBytes(const char* bytes, uint32_t size) +{ + if (!canAdd(size) || size > 8192) { + return; + } + + memcpy(m_MsgBuf + m_ReadPos, bytes, size); + m_ReadPos += size; + m_MsgSize += size; +} + +void NetworkMessage::AddPaddingBytes(uint32_t n) +{ + if (!canAdd(n)) { + return; + } + + memset((void*)&m_MsgBuf[m_ReadPos], 0x33, n); + m_MsgSize += n; +} + +void NetworkMessage::AddPosition(const Position& pos) +{ + AddU16(pos.x); + AddU16(pos.y); + AddByte(pos.z); +} + +void NetworkMessage::AddItem(uint16_t id, uint8_t count, uint16_t protocolVersion) +{ + if (protocolVersion < 978 && id > 20309) { + AddU16(2187); + } else { + const ItemType& it = Item::items[id]; + + AddU16(it.clientId); + + if (protocolVersion >= 979) { + AddByte(0xFF); // MARK_UNMARKED + } + + if (it.stackable) { + AddByte(count); + } else if (it.isSplash() || it.isFluidContainer()) { + uint32_t fluidIndex = count % 8; + AddByte(fluidMap[fluidIndex]); + } + + if (it.isAnimation) { + AddByte(0xFE); // random phase (0xFF for async) + } + } +} + +void NetworkMessage::AddItem(const Item* item, uint16_t protocolVersion) +{ + if (protocolVersion < 978 && item->getID() > 20309) { + AddU16(2187); + } else { + const ItemType& it = Item::items[item->getID()]; + + AddU16(it.clientId); + + if (protocolVersion >= 979) { + AddByte(0xFF); // MARK_UNMARKED + } + + if (it.stackable) { + AddByte(std::min(0xFF, item->getSubType())); + } else if (it.isSplash() || it.isFluidContainer()) { + uint32_t fluidIndex = item->getSubType() % 8; + AddByte(fluidMap[fluidIndex]); + } + + if (it.isAnimation) { + AddByte(0xFE); // random phase (0xFF for async) + } + } +} + +void NetworkMessage::AddItemId(const Item* item, uint16_t protocolVersion) +{ + if (protocolVersion < 978 && item->getID() > 20309) { + AddU16(2187); + } else { + const ItemType& it = Item::items[item->getID()]; + AddU16(it.clientId); + } +} + +void NetworkMessage::AddItemId(uint16_t itemId, uint16_t protocolVersion) +{ + if (protocolVersion < 978 && itemId > 20309) { + AddU16(2187); + } else { + const ItemType& it = Item::items[itemId]; + AddU16(it.clientId); + } +} diff --git a/src/networkmessage.h b/src/networkmessage.h new file mode 100644 index 0000000000..41f45cc5de --- /dev/null +++ b/src/networkmessage.h @@ -0,0 +1,216 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_NETWORK_MESSAGE_H__ +#define __OTSERV_NETWORK_MESSAGE_H__ + +#include "definitions.h" +#include "otsystem.h" +#include "const.h" + +class Item; +class Creature; +class Player; +class Position; +class RSA; + +class NetworkMessage +{ + public: + enum { header_length = 2 }; + enum { crypto_length = 4 }; + enum { xtea_multiple = 8 }; + enum { max_body_length = NETWORKMESSAGE_MAXSIZE - header_length - crypto_length - xtea_multiple }; + + // constructor/destructor + NetworkMessage() { + m_MsgBuf = m_RealBuf; + Reset(); + } + virtual ~NetworkMessage() {} + + // resets the internal buffer to an empty message + + protected: + void Reset() { + m_overrun = false; + m_MsgSize = 0; + m_ReadPos = 8; + } + + public: + // simply read functions for incoming message + uint8_t GetByte() { + if (!canRead(1)) { + return 0; + } + + return m_RealBuf[m_ReadPos++]; + } + uint16_t GetU16() { + if (!canRead(2)) { + return 0; + } + + uint16_t v = *(uint16_t*)(m_MsgBuf + m_ReadPos); + m_ReadPos += 2; + return v; + } + uint16_t GetSpriteId() { + return GetU16(); + } + uint32_t GetU32() { + if (!canRead(4)) { + return 0; + } + + uint32_t v = *(uint32_t*)(m_MsgBuf + m_ReadPos); + m_ReadPos += 4; + return v; + } + uint32_t PeekU32() { + if (!canRead(4)) { + return 0; + } + + uint32_t v = *(uint32_t*)(m_MsgBuf + m_ReadPos); + return v; + } + uint64_t GetU64() { + if (!canRead(8)) { + return 0; + } + + uint64_t v = *(uint64_t*)(m_MsgBuf + m_ReadPos); + m_ReadPos += 8; + return v; + } + std::string GetString(uint16_t stringlen = 0); + std::string GetRaw() { + return GetString(m_MsgSize - m_ReadPos); + } + Position GetPosition(); + + // skips count unknown/unused bytes in an incoming message + void SkipBytes(int count) { + m_ReadPos += count; + } + + // simply write functions for outgoing message + void AddByte(uint8_t value) { + if (!canAdd(1)) { + return; + } + + m_RealBuf[m_ReadPos++] = value; + m_MsgSize++; + } + void AddU16(uint16_t value) { + if (!canAdd(2)) { + return; + } + + *(uint16_t*)(m_MsgBuf + m_ReadPos) = value; + m_ReadPos += 2; + m_MsgSize += 2; + } + void AddU32(uint32_t value) { + if (!canAdd(4)) { + return; + } + + *(uint32_t*)(m_MsgBuf + m_ReadPos) = value; + m_ReadPos += 4; + m_MsgSize += 4; + } + void AddU64(uint64_t value) { + if (!canAdd(8)) { + return; + } + + *(uint64_t*)(m_MsgBuf + m_ReadPos) = value; + m_ReadPos += 8; + m_MsgSize += 8; + } + void AddBytes(const char* bytes, uint32_t size); + void AddPaddingBytes(uint32_t n); + + void AddString(const std::string& value); + void AddString(const char* value); + + void AddDouble(double value, uint8_t precision = 2); + + // write functions for complex types + void AddPosition(const Position& pos); + void AddItem(uint16_t id, uint8_t count, uint16_t protocolVersion); + void AddItem(const Item* item, uint16_t protocolVersion); + void AddItemId(const Item* item, uint16_t protocolVersion); + void AddItemId(uint16_t itemId, uint16_t protocolVersion); + void AddCreature(const Creature* creature, bool known, unsigned int remove); + + int32_t getMessageLength() const { + return m_MsgSize; + } + void setMessageLength(int32_t newSize) { + m_MsgSize = newSize; + } + int32_t getReadPos() const { + return m_ReadPos; + } + void setReadPos(int32_t pos) { + m_ReadPos = pos; + } + + int32_t decodeHeader(); + + bool isOverrun() const { + return m_overrun; + } + + uint8_t* getBuffer() const { + return m_MsgBuf; + } + char* getBodyBuffer() { + m_ReadPos = 2; + return (char*)&m_RealBuf[header_length]; + } + + protected: + inline bool canAdd(uint32_t size) const { + return (size + m_ReadPos < max_body_length); + } + + inline bool canRead(int32_t size) { + if ((m_ReadPos + size) > (m_MsgSize + 8) || size >= (NETWORKMESSAGE_MAXSIZE - m_ReadPos)) { + m_overrun = true; + return false; + } + + return true; + } + + int32_t m_MsgSize; + int32_t m_ReadPos; + + bool m_overrun; + + uint8_t m_RealBuf[NETWORKMESSAGE_MAXSIZE]; + uint8_t* m_MsgBuf; +}; + +#endif // #ifndef __NETWORK_MESSAGE_H__ diff --git a/src/npc.cpp b/src/npc.cpp new file mode 100644 index 0000000000..571d7821a8 --- /dev/null +++ b/src/npc.cpp @@ -0,0 +1,3754 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "definitions.h" +#include "npc.h" +#include "game.h" +#include "tools.h" +#include "configmanager.h" +#include "position.h" +#include "spells.h" +#include "player.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "luascript.h" + +extern ConfigManager g_config; +extern Game g_game; +extern Spells* g_spells; + +AutoList Npc::listNpc; + +NpcScriptInterface* Npc::m_scriptInterface = NULL; + +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ +uint32_t Npc::npcCount = 0; +#endif + +void Npcs::reload() +{ + for (AutoList::listiterator it = Npc::listNpc.list.begin(); it != Npc::listNpc.list.end(); ++it) { + it->second->closeAllShopWindows(); + } + + delete Npc::m_scriptInterface; + Npc::m_scriptInterface = NULL; + + for (AutoList::listiterator it = Npc::listNpc.list.begin(); it != Npc::listNpc.list.end(); ++it) { + it->second->reload(); + } +} + +Npc* Npc::createNpc(const std::string& name) +{ + Npc* npc = new Npc(name); + + if (!npc) { + return NULL; + } + + if (!npc->load()) { + delete npc; + return NULL; + } + + return npc; +} + +Npc::Npc(const std::string& _name) : + Creature() +{ + m_filename = "data/npc/" + _name + ".xml"; + loaded = false; + + m_npcEventHandler = NULL; + reset(); + +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + npcCount++; +#endif +} + +Npc::~Npc() +{ + reset(); + +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + npcCount--; +#endif +} + +bool Npc::load() +{ + if (isLoaded()) { + return true; + } + + reset(); + + if (!m_scriptInterface) { + m_scriptInterface = new NpcScriptInterface(); + m_scriptInterface->loadNpcLib("data/npc/lib/npc.lua"); + } + + loaded = loadFromXml(m_filename); + return isLoaded(); +} + +void Npc::reset() +{ + loaded = false; + walkTicks = 1500; + floorChange = false; + attackable = false; + hasBusyReply = false; + hasScriptedFocus = false; + focusCreature = 0; + isIdle = true; + hasUsedIdleReply = false; + talkRadius = 4; + idleTimeout = 0; + lastResponseTime = OTSYS_TIME(); + defaultPublic = true; + + delete m_npcEventHandler; + m_npcEventHandler = NULL; + + for (ResponseList::iterator it = responseList.begin(); it != responseList.end(); ++it) { + delete *it; + } + + for (StateList::iterator it = stateList.begin(); it != stateList.end(); ++it) { + delete *it; + } + + responseList.clear(); + stateList.clear(); + queueList.clear(); + m_parameters.clear(); + itemListMap.clear(); + responseScriptMap.clear(); + shopPlayerList.clear(); +} + +void Npc::reload() +{ + reset(); + load(); + + //Simulate that the creature is placed on the map again. + if (m_npcEventHandler) { + m_npcEventHandler->onCreatureAppear(this); + } + + if (walkTicks > 0) { + addEventWalk(); + } +} + +bool Npc::loadFromXml(const std::string& filename) +{ + xmlDocPtr doc = xmlParseFile(filename.c_str()); + + if (doc) { + xmlNodePtr root, p; + root = xmlDocGetRootElement(doc); + + if (xmlStrcmp(root->name, (const xmlChar*)"npc") != 0) { + std::cerr << "Malformed XML" << std::endl; + return false; + } + + int32_t intValue; + std::string strValue; + + p = root->children; + + std::string scriptfile = ""; + + if (readXMLString(root, "script", strValue)) { + scriptfile = "data/npc/scripts/" + strValue; + } + + if (readXMLString(root, "name", strValue)) { + name = strValue; + } else { + name = ""; + } + + if (readXMLInteger(root, "speed", intValue)) { + baseSpeed = intValue; + } else { + baseSpeed = 100; + } + + if (readXMLInteger(root, "attackable", intValue)) { + attackable = (intValue != 0); + } + + if (readXMLInteger(root, "autowalk", intValue)) { + //Deprecated attribute. + std::cout << "[Notice - Npc::Npc] NPC Name: " << name << " - autowalk has been deprecated, use walkinterval." << std::endl; + walkTicks = 2000; + } + + if (readXMLInteger(root, "walkinterval", intValue)) { + walkTicks = intValue; + } + + if (readXMLInteger(root, "walkradius", intValue)) { + masterRadius = intValue; + } + + if (readXMLInteger(root, "floorchange", intValue)) { + floorChange = (intValue != 0); + } + + while (p) { + if (xmlStrcmp(p->name, (const xmlChar*)"health") == 0) { + if (readXMLInteger(p, "now", intValue)) { + health = intValue; + } else { + health = 100; + } + + if (readXMLInteger(p, "max", intValue)) { + healthMax = intValue; + } else { + healthMax = 100; + } + } else if (xmlStrcmp(p->name, (const xmlChar*)"look") == 0) { + if (readXMLInteger(p, "type", intValue)) { + defaultOutfit.lookType = intValue; + + if (readXMLInteger(p, "head", intValue)) { + defaultOutfit.lookHead = intValue; + } + + if (readXMLInteger(p, "body", intValue)) { + defaultOutfit.lookBody = intValue; + } + + if (readXMLInteger(p, "legs", intValue)) { + defaultOutfit.lookLegs = intValue; + } + + if (readXMLInteger(p, "feet", intValue)) { + defaultOutfit.lookFeet = intValue; + } + + if (readXMLInteger(p, "addons", intValue)) { + defaultOutfit.lookAddons = intValue; + } + } else if (readXMLInteger(p, "typeex", intValue)) { + defaultOutfit.lookTypeEx = intValue; + } + + if (readXMLInteger(p, "mount", intValue)) { + defaultOutfit.lookMount = intValue; + } + + currentOutfit = defaultOutfit; + } else if (xmlStrcmp(p->name, (const xmlChar*)"parameters") == 0) { + for (xmlNodePtr q = p->children; q != NULL; q = q->next) { + if (xmlStrcmp(q->name, (const xmlChar*)"parameter") == 0) { + std::string paramKey; + std::string paramValue; + + if (!readXMLString(q, "key", paramKey)) { + continue; + } + + if (!readXMLString(q, "value", paramValue)) { + continue; + } + + m_parameters[paramKey] = paramValue; + } + } + } else if (xmlStrcmp(p->name, (const xmlChar*)"interaction") == 0) { + if (readXMLInteger(p, "talkradius", intValue)) { + talkRadius = intValue; + } + + if (readXMLInteger(p, "idletime", intValue) || readXMLInteger(p, "idletimeout", intValue)) { + idleTimeout = intValue; + } + + if (readXMLInteger(p, "defaultpublic", intValue)) { + defaultPublic = intValue != 0; + } + + responseList = loadInteraction(p->children); + } + + p = p->next; + } + + xmlFreeDoc(doc); + + if (!scriptfile.empty()) { + m_npcEventHandler = new NpcScript(scriptfile, this); + + if (!m_npcEventHandler->isLoaded()) { + return false; + } + } + + return true; + } + + return false; +} + +StorageCondition Npc::loadStorageCondition(xmlNodePtr node) +{ + StorageCondition cond = { -1, 1, STORAGE_EQUAL}; + + std::string strValue; + + readXMLInteger(node, "storageId", cond.id) || readXMLInteger(node, "id", cond.id); + + readXMLInteger(node, "value", cond.value) || readXMLInteger(node, "storageValue", cond.value); + + if (readXMLString(node, "storageComp", strValue) || readXMLString(node, "comparator", strValue)) { + std::string tmpStr = asLowerCaseString(strValue); + + if (tmpStr == "equal") { + cond.op = STORAGE_EQUAL; + } else if (tmpStr == "notequal") { + cond.op = STORAGE_NOTEQUAL; + } else if (tmpStr == "greaterorequal") { + cond.op = STORAGE_GREATEROREQUAL; + } else if (tmpStr == "greater") { + cond.op = STORAGE_GREATER; + } else if (tmpStr == "less") { + cond.op = STORAGE_LESS; + } else if (tmpStr == "lessorequal") { + cond.op = STORAGE_LESSOREQUAL; + } + } + + return cond; +} + +uint32_t Npc::loadParams(xmlNodePtr node) +{ + uint32_t params = RESPOND_DEFAULT; + std::string strValue; + + if (readXMLString(node, "param", strValue)) { + std::vector paramList = explodeString(strValue, ";"); + + for (std::vector::iterator it = paramList.begin(); it != paramList.end(); ++it) { + std::string tmpStr = asLowerCaseString(*it); + + if (tmpStr == "male") { + params |= RESPOND_MALE; + } else if (tmpStr == "female") { + params |= RESPOND_FEMALE; + } else if (tmpStr == "pzblock") { + params |= RESPOND_PZBLOCK; + } else if (tmpStr == "lowmoney") { + params |= RESPOND_LOWMONEY; + } else if (tmpStr == "enoughmoney") { + params |= RESPOND_ENOUGHMONEY; + } else if (tmpStr == "noamount") { + params |= RESPOND_NOAMOUNT; + } else if (tmpStr == "lowamount") { + params |= RESPOND_LOWAMOUNT; + } else if (tmpStr == "enoughamount") { + params |= RESPOND_ENOUGHAMOUNT; + } else if (tmpStr == "premium") { + params |= RESPOND_PREMIUM; + } else if (tmpStr == "promoted") { + params |= RESPOND_PROMOTED; + } else if (tmpStr == "druid") { + params |= RESPOND_DRUID; + } else if (tmpStr == "knight") { + params |= RESPOND_KNIGHT; + } else if (tmpStr == "paladin") { + params |= RESPOND_PALADIN; + } else if (tmpStr == "sorcerer") { + params |= RESPOND_SORCERER; + } else if (tmpStr == "lowlevel") { + params |= RESPOND_LOWLEVEL; + } else if (tmpStr == "highlevel") { + params |= RESPOND_HIGHLEVEL; + } else if (tmpStr == "knowspell") { + params |= RESPOND_KNOWSPELL; + } else if (tmpStr == "cannotlearnspell") { + params |= RESPOND_CANNOTLEARNSPELL; + } else { + std::cout << "Warning: [Npc::loadParams] Unknown param " << (*it) << std::endl; + } + } + } + + return params; +} + +ResponseList Npc::loadInteraction(xmlNodePtr node) +{ + ResponseList _responseList; + std::string strValue; + int32_t intValue; + + while (node) { + if (xmlStrcmp(node->name, (const xmlChar*)"include") == 0) { + if (readXMLString(node, "file", strValue)) { + std::string includeFilename = "data/npc/lib/" + strValue; + xmlDocPtr doc = xmlParseFile(includeFilename.c_str()); + + if (doc) { + xmlNodePtr root; + root = xmlDocGetRootElement(doc); + + if (xmlStrcmp(root->name, (const xmlChar*)"interaction") == 0) { + ResponseList includeResponseList = loadInteraction(root->children); + _responseList.insert(_responseList.end(), includeResponseList.begin(), includeResponseList.end()); + } else { + std::cerr << "Malformed XML" << std::endl; + } + + xmlFreeDoc(doc); + } + } + } else if (xmlStrcmp(node->name, (const xmlChar*)"itemlist") == 0) { + if (readXMLString(node, "listid", strValue)) { + ItemListMap::iterator it = itemListMap.find(strValue); + + if (it != itemListMap.end()) { + //duplicate listid found + std::cout << "Warning: [Npc::loadInteraction] Duplicate listId found " << strValue << std::endl; + } else { + xmlNodePtr tmpNode = node->children; + std::list& list = itemListMap[strValue]; + + while (tmpNode) { + if (xmlStrcmp(tmpNode->name, (const xmlChar*)"item") == 0) { + ListItem li; + + if (readXMLInteger(tmpNode, "id", intValue)) { + li.itemId = intValue; + } else { + std::cout << "Warning: [Npc::loadInteraction] Missing list item itemId " << std::endl; + tmpNode = tmpNode->next; + continue; + } + + const ItemType& it = Item::items[li.itemId]; + + if (readXMLInteger(tmpNode, "sellprice", intValue)) { + li.sellPrice = intValue; + } + + if (readXMLInteger(tmpNode, "buyprice", intValue)) { + li.buyPrice = intValue; + } + + if (readXMLString(tmpNode, "keywords", strValue)) { + li.keywords = strValue; + } else { + std::cout << "Warning: [Npc::loadInteraction] Missing list item keywords " << std::endl; + tmpNode = tmpNode->next; + continue; + } + + //optional + if (readXMLInteger(tmpNode, "subtype", intValue)) { + li.subType = intValue; + } else { + if (it.stackable) { + li.subType = 1; + } else if (it.isFluidContainer() || it.isSplash()) { + li.subType = 0; + } + } + + if (readXMLString(tmpNode, "name", strValue)) { + li.name = strValue; + } + + if (readXMLString(tmpNode, "pname", strValue)) { + li.pluralName = strValue; + } + + list.push_back(li); + } + + tmpNode = tmpNode->next; + } + } + } + } else if (xmlStrcmp(node->name, (const xmlChar*)"script") == 0) { + xmlNodePtr scriptNode = node->children; + + while (scriptNode) { + if (xmlStrcmp(scriptNode->name, (const xmlChar*)"text") == 0 || + scriptNode->type == XML_CDATA_SECTION_NODE) { + if (readXMLContentString(scriptNode, strValue)) { + trim_left(strValue, "\r"); + trim_left(strValue, "\n"); + trim_left(strValue, " "); + + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* scriptEnvironment = m_scriptInterface->getScriptEnv(); + scriptEnvironment->setRealPos(getPosition()); + scriptEnvironment->setNpc(this); + m_scriptInterface->loadBuffer(strValue, NULL); + m_scriptInterface->releaseScriptEnv(); + } + } + } + + scriptNode = scriptNode->next; + } + } else if (xmlStrcmp(node->name, (const xmlChar*)"interact") == 0) { + NpcResponse::ResponseProperties iprop; + iprop.publicize = defaultPublic; + + if (readXMLString(node, "keywords", strValue)) { + iprop.inputList.push_back(asLowerCaseString(strValue)); + } + + iprop.eventType = EVENT_NONE; + + if (readXMLString(node, "event", strValue)) { + strValue = asLowerCaseString(strValue); + + if (strValue == "onbusy") { + hasBusyReply = true; + iprop.eventType = EVENT_BUSY; + } else if (strValue == "onthink") { + iprop.eventType = EVENT_THINK; + } else if (strValue == "onidle") { + if (readXMLInteger(node, "time", intValue)) { + iprop.time = intValue; + } else { + std::cout << "Warning: [Npc::loadInteraction] Missing time attribute for onidle event" << std::endl; + } + + if (readXMLInteger(node, "singleevent", intValue)) { + iprop.singleEvent = (intValue == 1); + } + + iprop.eventType = EVENT_IDLE; + } else if (strValue == "onplayerenter") { + iprop.eventType = EVENT_PLAYER_ENTER; + } else if (strValue == "onplayermove") { + iprop.eventType = EVENT_PLAYER_MOVE; + } else if (strValue == "onplayerleave") { + iprop.eventType = EVENT_PLAYER_LEAVE; + } else if (strValue == "onplayershopsell") { + iprop.eventType = EVENT_PLAYER_SHOPSELL; + } else if (strValue == "onplayershopbuy") { + iprop.eventType = EVENT_PLAYER_SHOPBUY; + } else if (strValue == "onplayershopclose") { + iprop.eventType = EVENT_PLAYER_SHOPCLOSE; + } else { + std::cout << "Warning: [Npc::loadInteraction] Invalid event type -" << strValue << std::endl; + } + } + + if (readXMLInteger(node, "topic", intValue)) { + if (intValue <= 0) { + std::cout << "Warning: [Npc::loadInteraction] Invalid topic value -" << intValue << std::endl; + } + + iprop.topic = intValue; + } else if (readXMLInteger(node, "nottopic", intValue)) { + iprop.topic = intValue; + iprop.params |= RESPOND_NOTTOPIC; + } + + if (readXMLInteger(node, "focus", intValue)) { + iprop.focusStatus = intValue; + } + + if (readXMLInteger(node, "haveitem", intValue)) { + iprop.haveItemId = intValue; + } + + if (readXMLInteger(node, "donthaveitem", intValue)) { + iprop.dontHaveItemId = intValue; + } + + if (readXMLInteger(node, "lowlevel", intValue)) { + iprop.params |= RESPOND_LOWLEVEL; + iprop.level = intValue; + } else if (readXMLInteger(node, "highlevel", intValue)) { + iprop.params |= RESPOND_HIGHLEVEL; + iprop.level = intValue; + } + + uint32_t interactParams = loadParams(node); + + StorageCondition sc = loadStorageCondition(node); + + if (sc.id != -1) { + iprop.storageConditions.push_back(sc); + } + + xmlNodePtr tmpNode = node->children; + + while (tmpNode) { + if (xmlStrcmp(tmpNode->name, (const xmlChar*)"keywords") == 0) { + //alternative input keywords + xmlNodePtr altKeyNode = tmpNode->children; + + while (altKeyNode) { + if (xmlStrcmp(altKeyNode->name, (const xmlChar*)"text") == 0) { + if (readXMLContentString(altKeyNode, strValue)) { + iprop.inputList.push_back(asLowerCaseString(strValue)); + } + } + + altKeyNode = altKeyNode->next; + } + } else if (xmlStrcmp(tmpNode->name, (const xmlChar*)"storage") == 0) { + StorageCondition sc = loadStorageCondition(tmpNode); + + if (sc.id != -1) { + iprop.storageConditions.push_back(sc); + } + } else if (xmlStrcmp(tmpNode->name, (const xmlChar*)"list") == 0) { + xmlNodePtr listNode = tmpNode->children; + + while (listNode) { + if (xmlStrcmp(listNode->name, (const xmlChar*)"text") == 0) { + if (readXMLContentString(listNode, strValue)) { + ItemListMap::iterator it = itemListMap.find(strValue); + + if (it != itemListMap.end()) { + iprop.itemList.insert(iprop.itemList.end(), it->second.begin(), it->second.end()); + } else { + std::cout << "Warning: [Npc::loadInteraction] Could not find a list id called " << strValue << std::endl; + } + } + } + + listNode = listNode->next; + } + } + + tmpNode = tmpNode->next; + } + + tmpNode = node->children; + + while (tmpNode) { + if (xmlStrcmp(tmpNode->name, (const xmlChar*)"response") == 0) { + //copy the properties from the interaction + NpcResponse::ResponseProperties prop = iprop; + + prop.params = interactParams | loadParams(tmpNode); + ScriptVars scriptVars; + + if (readXMLString(tmpNode, "knowspell", strValue)) { + prop.knowSpell = strValue; + } + + if (readXMLString(tmpNode, "text", strValue)) { + prop.responseType = RESPONSE_DEFAULT; + replaceString(strValue, "<", "<"); + replaceString(strValue, ">", ">"); + replaceString(strValue, "&", "&"); + replaceString(strValue, "'", "\\"); + replaceString(strValue, """, "\""); + prop.output = strValue; + } else if (readXMLString(tmpNode, "function", strValue)) { + prop.responseType = RESPONSE_SCRIPT; + prop.output = strValue; + } + + if (readXMLInteger(tmpNode, "public", intValue)) { + prop.publicize = (intValue == 1); + } + + if (readXMLInteger(tmpNode, "lowhealth", intValue)) { + prop.health = intValue; + } + + if (readXMLString(tmpNode, "condition", strValue)) { + if (strValue == "poison") { + prop.condition = CONDITION_POISON; + } else if (strValue == "fire") { + prop.condition = CONDITION_FIRE; + } else if (strValue == "energy") { + prop.condition = CONDITION_ENERGY; + } else if (strValue == "haste") { + prop.condition = CONDITION_HASTE; + } else if (strValue == "paralyze") { + prop.condition = CONDITION_PARALYZE; + } else if (strValue == "outfit") { + prop.condition = CONDITION_OUTFIT; + } else if (strValue == "invisible") { + prop.condition = CONDITION_INVISIBLE; + } else if (strValue == "light") { + prop.condition = CONDITION_LIGHT; + } else if (strValue == "manashield") { + prop.condition = CONDITION_MANASHIELD; + } else if (strValue == "drunk") { + prop.condition = CONDITION_DRUNK; + } else if (strValue == "exhausted") { + prop.condition = CONDITION_EXHAUST_COMBAT; + } else if (strValue == "regeneration") { + prop.condition = CONDITION_REGENERATION; + } else if (strValue == "soul") { + prop.condition = CONDITION_SOUL; + } else if (strValue == "drown") { + prop.condition = CONDITION_DROWN; + } else if (strValue == "muted") { + prop.condition = CONDITION_MUTED; + } + } + + if (readXMLInteger(tmpNode, "b1", intValue)) { + scriptVars.b1 = (intValue == 1); + } + + if (readXMLInteger(tmpNode, "b2", intValue)) { + scriptVars.b2 = (intValue == 1); + } + + if (readXMLInteger(tmpNode, "b3", intValue)) { + scriptVars.b3 = (intValue == 1); + } + + if (readXMLInteger(tmpNode, "n1", intValue)) { + scriptVars.n1 = intValue; + } + + if (readXMLInteger(tmpNode, "n2", intValue)) { + scriptVars.n2 = intValue; + } + + if (readXMLInteger(tmpNode, "n3", intValue)) { + scriptVars.n3 = intValue; + } + + ResponseList subResponseList; + + xmlNodePtr subNode = tmpNode->children; + + while (subNode) { + if (xmlStrcmp(subNode->name, (const xmlChar*)"action") == 0) { + ResponseAction action; + + if (readXMLString(subNode, "name", strValue)) { + std::string tmpStr = asLowerCaseString(strValue); + + if (tmpStr == "topic") { + if (readXMLInteger(subNode, "value", intValue)) { + action.actionType = ACTION_SETTOPIC; + action.intValue = intValue; + } else { + std::cout << "Warning: [Npc::loadInteraction] [Action 'name'] Could not find attribute 'value' " << std::endl; + } + } else if (tmpStr == "price") { + if (readXMLString(subNode, "value", strValue)) { + action.actionType = ACTION_SETPRICE; + action.strValue = strValue; + action.intValue = atoi(strValue.c_str()); + } else { + std::cout << "Warning: [Npc::loadInteraction] [Action 'price'] Could not find attribute 'value' " << std::endl; + } + } else if (tmpStr == "amount") { + if (readXMLString(subNode, "value", strValue)) { + action.actionType = ACTION_SETAMOUNT; + action.strValue = strValue; + action.intValue = atoi(strValue.c_str()); + } else { + std::cout << "Warning: [Npc::loadInteraction] [Action 'amount'] Could not find attribute 'value' " << std::endl; + } + } else if (tmpStr == "item") { + if (readXMLInteger(subNode, "value", intValue)) { + action.actionType = ACTION_SETITEM; + action.intValue = intValue; + } else { + std::cout << "Warning: [Npc::loadInteraction] [Action 'item'] Could not find attribute 'value' " << std::endl; + } + } else if (tmpStr == "subtype") { + if (readXMLInteger(subNode, "value", intValue)) { + action.actionType = ACTION_SETSUBTYPE; + action.intValue = intValue; + } else { + std::cout << "Warning: [Npc::loadInteraction] [Action 'subtype'] Could not find attribute 'value' " << std::endl; + } + } else if (tmpStr == "spell") { + if (readXMLString(subNode, "value", strValue)) { + action.actionType = ACTION_SETSPELL; + action.strValue = strValue; + + if (strValue != "|SPELL|") { + InstantSpell* spell = g_spells->getInstantSpellByName(strValue); + + if (!spell) { + std::cout << "Warning: [Npc::loadInteraction] Could not find an instant spell called " << strValue << std::endl; + } + } + } else { + std::cout << "Warning: [Npc::loadInteraction] [Action 'spell'] Could not find attribute 'value' " << std::endl; + } + } else if (tmpStr == "listname") { + if (readXMLString(subNode, "value", strValue)) { + action.actionType = ACTION_SETLISTNAME; + action.strValue = strValue; + } else { + std::cout << "Warning: [Npc::loadInteraction] [Action 'listname'] Could not find attribute 'value' " << std::endl; + } + } else if (tmpStr == "listpname") { + if (readXMLString(subNode, "value", strValue)) { + action.actionType = ACTION_SETLISTPNAME; + action.strValue = strValue; + } else { + std::cout << "Warning: [Npc::loadInteraction] [Action 'listpname'] Could not find attribute 'value' " << std::endl; + } + } else if (tmpStr == "teachspell") { + if (readXMLString(subNode, "value", strValue)) { + action.actionType = ACTION_TEACHSPELL; + action.strValue = strValue; + + if (strValue != "|SPELL|") { + InstantSpell* spell = g_spells->getInstantSpellByName(strValue); + + if (!spell) { + std::cout << "Warning: [Npc::loadInteraction] Could not find an instant spell called " << strValue << std::endl; + } + } + } else { + std::cout << "Warning: [Npc::loadInteraction] [Action 'teachspell'] Could not find attribute 'value' " << std::endl; + } + } else if (tmpStr == "sell") { + if (readXMLString(subNode, "value", strValue)) { + action.actionType = ACTION_SELLITEM; + action.strValue = strValue; + action.intValue = atoi(strValue.c_str()); + } else { + std::cout << "Warning: [Npc::loadInteraction] [Action 'sell'] Could not find attribute 'value' " << std::endl; + } + } else if (tmpStr == "buy") { + if (readXMLString(subNode, "value", strValue)) { + action.actionType = ACTION_BUYITEM; + action.strValue = strValue; + action.intValue = atoi(strValue.c_str()); + } else { + std::cout << "Warning: [Npc::loadInteraction] [Action 'buy'] Could not find attribute 'value' " << std::endl; + } + } else if (tmpStr == "takemoney") { + if (readXMLString(subNode, "value", strValue)) { + action.actionType = ACTION_TAKEMONEY; + action.strValue = strValue; + action.intValue = atoi(strValue.c_str()); + } else { + std::cout << "Warning: [Npc::loadInteraction] [Action 'takemoney'] Could not find attribute 'value' " << std::endl; + } + } else if (tmpStr == "givemoney") { + if (readXMLString(subNode, "value", strValue)) { + action.actionType = ACTION_GIVEMONEY; + action.strValue = strValue; + action.intValue = atoi(strValue.c_str()); + } else { + std::cout << "Warning: [Npc::loadInteraction] [Action 'givemoney'] Could not find attribute 'value' " << std::endl; + } + } else if (tmpStr == "level") { + if (readXMLString(subNode, "value", strValue)) { + action.actionType = ACTION_SETLEVEL; + action.strValue = strValue; + action.intValue = atoi(strValue.c_str()); + } else { + std::cout << "Warning: [Npc::loadInteraction] [Action 'level'] Could not find attribute 'value' " << std::endl; + } + } else if (tmpStr == "giveitem") { + if (readXMLString(subNode, "value", strValue)) { + action.actionType = ACTION_GIVEITEM; + action.strValue = strValue; + action.intValue = atoi(strValue.c_str()); + } else { + std::cout << "Warning: [Npc::loadInteraction] [Action 'giveitem'] Could not find attribute 'value' " << std::endl; + } + } else if (tmpStr == "takeitem") { + if (readXMLString(subNode, "value", strValue)) { + action.actionType = ACTION_TAKEITEM; + action.strValue = strValue; + action.intValue = atoi(strValue.c_str()); + } else { + std::cout << "Warning: [Npc::loadInteraction] [Action 'takeitem'] Could not find attribute 'value' " << std::endl; + } + } else if (tmpStr == "effect") { + if (readXMLString(subNode, "value", strValue)) { + action.actionType = ACTION_SETEFFECT; + action.intValue = getMagicEffect(strValue); + } else { + std::cout << "Warning: [Npc::loadInteraction] [Action 'effect'] Could not find attribute 'value' " << std::endl; + } + } else if (tmpStr == "idle") { + if (readXMLInteger(subNode, "value", intValue)) { + action.actionType = ACTION_SETIDLE; + action.intValue = intValue; + } else { + std::cout << "Warning: [Npc::loadInteraction] [Action 'idle'] Could not find attribute 'value' " << std::endl; + } + } else if (tmpStr == "script") { + if (readXMLString(subNode, "value", strValue)) { + action.actionType = ACTION_SCRIPT; + action.strValue = strValue; + } else if (subNode->children) { + xmlNodePtr scriptNode = subNode->children; + + while (scriptNode) { + if (xmlStrcmp(scriptNode->name, (const xmlChar*)"text") == 0 || + scriptNode->type == XML_CDATA_SECTION_NODE) { + if (readXMLContentString(scriptNode, strValue)) { + trim_left(strValue, "\r"); + trim_left(strValue, "\n"); + trim_left(strValue, " "); + + if (strValue.length() > action.strValue.length()) { + action.actionType = ACTION_SCRIPT; + action.strValue = strValue; + } + } + } + + scriptNode = scriptNode->next; + } + } else { + std::cout << "Warning: [Npc::loadInteraction] [Action 'script'] Could not find attribute 'value' " << std::endl; + } + } else if (tmpStr == "scriptparam") { + if (readXMLString(subNode, "value", strValue)) { + action.actionType = ACTION_SCRIPTPARAM; + action.strValue = strValue; + } else { + std::cout << "Warning: [Npc::loadInteraction] [Action 'scriptparam'] Could not find attribute 'value' " << std::endl; + } + } else if (tmpStr == "storage") { + if (readXMLInteger(subNode, "value", intValue)) { + action.actionType = ACTION_SETSTORAGE; + action.intValue = intValue; + } else { + std::cout << "Warning: [Npc::loadInteraction] [Action 'storage'] Could not find attribute 'value' " << std::endl; + } + } else if (tmpStr == "addqueue") { + if (readXMLString(subNode, "value", strValue)) { + action.actionType = ACTION_ADDQUEUE; + action.strValue = strValue; + action.intValue = atoi(strValue.c_str()); + } else { + std::cout << "Warning: [Npc::loadInteraction] [Action 'addqueue'] Could not find attribute 'value' " << std::endl; + } + } else if (tmpStr == "teleport") { + if (readXMLString(subNode, "value", strValue)) { + std::vector posList = explodeString(strValue, ";"); + action.actionType = ACTION_SETTELEPORT; + action.strValue = strValue; + action.pos.x = 0; + action.pos.y = 0; + action.pos.z = 0; + + if (posList.size() == 3) { + action.pos.x = atoi(posList[0].c_str()); + action.pos.y = atoi(posList[1].c_str()); + action.pos.z = atoi(posList[2].c_str()); + } + } else { + std::cout << "Warning: [Npc::loadInteraction] [Action 'teleport'] Could not find attribute 'value' " << std::endl; + } + } else { + std::cout << "Warning: [Npc::loadInteraction] Unknown action " << strValue << std::endl; + } + } + + if (readXMLInteger(subNode, "key", intValue)) { + action.key = intValue; + } + + if (action.actionType != ACTION_NONE) { + prop.actionList.push_back(action); + } + } else if (xmlStrcmp(subNode->name, (const xmlChar*)"interact") == 0) { + if (subResponseList.empty()) { + ResponseList nodeResponseList = loadInteraction(subNode); + subResponseList.insert(subResponseList.end(), + nodeResponseList.begin(), nodeResponseList.end()); + } + } + + subNode = subNode->next; + } + + //Check if this interaction has a |list| keyword + bool hasListKeyword = false; + + for (std::list::iterator it = prop.inputList.begin(); + it != prop.inputList.end(); ++it) { + if (it->find("|list|") != std::string::npos) { + hasListKeyword = true; + break; + } + } + + //Iterate through all input keywords and replace all |LIST| with the item list + if (hasListKeyword && !prop.itemList.empty()) { + for (std::list::iterator it = prop.itemList.begin(); it != prop.itemList.end(); ++it) { + NpcResponse::ResponseProperties listItemProp = prop; + + for (std::list::iterator iit = listItemProp.inputList.begin(); + iit != listItemProp.inputList.end(); ++iit) { + std::string& input = (*iit); + + if (input.find("|list|") == std::string::npos) { + continue; + } + + //Replace |LIST| with the keyword in the list + replaceString(input, "|list|", it->keywords); + + ResponseAction action; + + action.actionType = ACTION_SETITEM; + action.intValue = it->itemId; + listItemProp.actionList.push_front(action); + + action.actionType = ACTION_SETSELLPRICE; + action.intValue = it->sellPrice; + listItemProp.actionList.push_front(action); + + action.actionType = ACTION_SETBUYPRICE; + action.intValue = it->buyPrice; + listItemProp.actionList.push_front(action); + + action.actionType = ACTION_SETSUBTYPE; + action.intValue = it->subType; + listItemProp.actionList.push_front(action); + + action.actionType = ACTION_SETLISTNAME; + + if (!it->name.empty()) { + action.strValue = it->name; + } else { + const ItemType& itemType = Item::items[it->itemId]; + + if (itemType.id != 0) { + action.strValue = itemType.article + " " + itemType.name; + } + } + + listItemProp.actionList.push_front(action); + + action.actionType = ACTION_SETLISTPNAME; + + if (!it->pluralName.empty()) { + action.strValue = it->pluralName; + } else { + const ItemType& itemType = Item::items[it->itemId]; + + if (itemType.id != 0) { + action.strValue = itemType.getPluralName(); + } + } + + listItemProp.actionList.push_front(action); + + ResponseList list; + + for (ResponseList::iterator respIter = subResponseList.begin(); + respIter != subResponseList.end(); ++respIter) { + list.push_back(new NpcResponse(*(*respIter))); + } + + //Create a new response for this list item + NpcResponse* response = new NpcResponse(listItemProp, list, scriptVars); + _responseList.push_back(response); + } + } + } else { + NpcResponse* response = new NpcResponse(prop, subResponseList, scriptVars); + _responseList.push_back(response); + } + } + + tmpNode = tmpNode->next; + } + } + + node = node->next; + } + + return _responseList; +} + +NpcState* Npc::getState(const Player* player, bool makeNew /*= true*/) +{ + for (StateList::iterator it = stateList.begin(); it != stateList.end(); ++it) { + if ((*it)->playerId == player->getID()) { + return *it; + } + } + + if (!makeNew) { + return NULL; + } + + NpcState* state = new NpcState; + state->playerId = player->getID(); + state->price = 0; + state->sellPrice = 0; + state->buyPrice = 0; + state->amount = 1; + state->itemId = 0; + state->subType = -1; + state->ignore = false; + state->inBackpacks = false; + state->spellName = ""; + state->listName = ""; + state->listPluralName = ""; + state->level = -1; + state->topic = 0; + state->focusState = -1; + state->isQueued = false; + state->respondToText = ""; + state->lastResponse = NULL; + state->subResponse = NULL; + state->lastResponseTime = OTSYS_TIME(); + stateList.push_back(state); + return state; +} + +bool Npc::canSee(const Position& pos) const +{ + if (pos.z != getPosition().z) { + return false; + } + + return Creature::canSee(getPosition(), pos, talkRadius, talkRadius); +} + +std::string Npc::getDescription(int32_t lookDistance) const +{ + std::ostringstream s; + s << name << "."; + return s.str(); +} + +void Npc::onCreatureAppear(const Creature* creature, bool isLogin) +{ + Creature::onCreatureAppear(creature, isLogin); + + if (creature == this && walkTicks > 0) { + addEventWalk(); + } + + if (creature == this) { + if (m_npcEventHandler) { + m_npcEventHandler->onCreatureAppear(creature); + } + } + //only players for script events + else if (Player* player = const_cast(creature->getPlayer())) { + if (m_npcEventHandler) { + m_npcEventHandler->onCreatureAppear(creature); + } + + NpcState* npcState = getState(player); + + if (npcState && canSee(player->getPosition())) { + onPlayerEnter(player, npcState); + } + } +} + +void Npc::onCreatureDisappear(const Creature* creature, uint32_t stackpos, bool isLogout) +{ + Creature::onCreatureDisappear(creature, stackpos, isLogout); + + if (creature == this) { + //Close all open shop window's + closeAllShopWindows(); + + /* + Can't use this yet because Jiddo's scriptsystem isn't able to handle it. + if(m_npcEventHandler){ + m_npcEventHandler->onCreatureDisappear(creature); + } + */ + } else if (Player* player = const_cast(creature->getPlayer())) { + //only players for script events + if (m_npcEventHandler) { + m_npcEventHandler->onCreatureDisappear(creature); + } + + NpcState* npcState = getState(player); + + if (npcState) { + onPlayerLeave(player, npcState); + } + } +} + +void Npc::onCreatureMove(const Creature* creature, const Tile* newTile, const Position& newPos, + const Tile* oldTile, const Position& oldPos, bool teleport) +{ + Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos, teleport); + + if (creature == this) { + if (m_npcEventHandler) { + m_npcEventHandler->onCreatureMove(creature, oldPos, newPos); + } + } else if (Player* player = const_cast(creature->getPlayer())) { + if (m_npcEventHandler) { + m_npcEventHandler->onCreatureMove(creature, oldPos, newPos); + } + + bool canSeeNewPos = canSee(newPos); + bool canSeeOldPos = canSee(oldPos); + + if (canSeeNewPos && !canSeeOldPos) { + NpcState* npcState = getState(player); + + if (npcState) { + onPlayerEnter(player, npcState); + } + } else if (!canSeeNewPos && canSeeOldPos) { + NpcState* npcState = getState(player); + + if (npcState) { + onPlayerLeave(player, npcState); + } + } else if (canSeeNewPos && canSeeOldPos) { + NpcState* npcState = getState(player); + + if (npcState) { + const NpcResponse* response = getResponse(player, npcState, EVENT_PLAYER_MOVE, false); + processResponse(player, npcState, response); + } + } + } +} + +void Npc::onCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, Position* pos/* = NULL*/) +{ + if (creature->getID() == this->getID()) { + return; + } + + //only players for script events + if (Player* player = const_cast(creature->getPlayer())) { + if (m_npcEventHandler) { + m_npcEventHandler->onCreatureSay(player, type, text); + } + + if (type == SPEAK_SAY || type == SPEAK_PRIVATE_PN) { + const Position& pos = creature->getPosition(); + + if (canSee(pos)) { + NpcState* npcState = getState(player); + + npcState->respondToText = text; + + if (!text.empty()) { + if (hasBusyReply && focusCreature != 0 && (uint32_t)focusCreature != player->getID()) { + //Check if we have a busy reply + const NpcResponse* response = getResponse(player, npcState, EVENT_BUSY, text, false); + + if (response) { + turnToCreature(player); + processResponse(player, npcState, response, true); + } + } else { + const NpcResponse* response = getResponse(player, npcState, text, true); + + if (response) { + setCreatureFocus(player); + processResponse(player, npcState, response, true); + } + } + } + } + } + } +} + +void Npc::onPlayerCloseChannel(const Player* player) +{ + if (m_npcEventHandler) { + m_npcEventHandler->onPlayerCloseChannel(player); + } +} + +void Npc::onPlayerEnter(Player* player, NpcState* state) +{ + const NpcResponse* response = getResponse(player, state, EVENT_PLAYER_ENTER, false); + processResponse(player, state, response); +} + +void Npc::onPlayerLeave(Player* player, NpcState* state) +{ + if (player) { + player->closeShopWindow(); + const NpcResponse* response = getResponse(player, state, EVENT_PLAYER_LEAVE, false); + processResponse(player, state, response); + + if (state->focusState == -1) { + //has not started a conversation yet + state->focusState = 0; + } + } +} + +void Npc::onThink(uint32_t interval) +{ + Creature::onThink(interval); + + if (m_npcEventHandler) { + m_npcEventHandler->onThink(); + } + + if (getTimeSinceLastMove() >= walkTicks) { + addEventWalk(); + } + + isIdle = true; + hasUsedIdleReply = false; + + for (StateList::iterator it = stateList.begin(); it != stateList.end();) { + NpcState* npcState = *it; + + const NpcResponse* response = NULL; + Player* player = g_game.getPlayerByID(npcState->playerId); + bool closeConversation = (player == NULL); + bool closeDueToTimeout = false; + + if (!npcState->isQueued) { + if (npcState->focusState == -1) { + //has not started a conversation yet + } else if (npcState->focusState == 0) { + //closing conversation because focus is set to 0 or isidle set to true + closeConversation = true; + } else if (idleTimeout > 0 && (OTSYS_TIME() - npcState->lastResponseTime) > idleTimeout * 1000) { + //closing conversation due to idle + closeDueToTimeout = true; + closeConversation = true; + } + } + + //execute our latest response + if (player && npcState->lastResponse) { + turnToCreature(player); + executeResponse(player, npcState, npcState->lastResponse); + npcState->subResponse = npcState->lastResponse; + npcState->lastResponse = NULL; + } + + if (closeConversation) { + if ((uint32_t)focusCreature == npcState->playerId && !hasScriptedFocus) { + setCreatureFocus(NULL); + } + + if (queueList.empty()) { + if (closeDueToTimeout) { + onPlayerLeave(player, npcState); + } + } else { + Player* nextPlayer = NULL; + + while (!queueList.empty()) { + nextPlayer = g_game.getPlayerByID(*queueList.begin()); + queueList.erase(queueList.begin()); + + if (nextPlayer) { + NpcState* nextPlayerState = getState(nextPlayer, false); + + if (nextPlayerState) { + response = getResponse(nextPlayer, nextPlayerState, nextPlayerState->respondToText, true); + + if (response) { + setCreatureFocus(nextPlayer); + processResponse(nextPlayer, nextPlayerState, response, true); + } + + nextPlayerState->isQueued = false; + break; + } + } + } + } + + delete *it; + stateList.erase(it++); + //std::cout << "Closing conversation." << std::endl; + + continue; + } + + //idle response + response = getResponse(player, npcState, EVENT_IDLE, true); + + if (response) { + hasUsedIdleReply = true; + processResponse(player, npcState, response); + } + + //think response + response = getResponse(player, npcState, EVENT_THINK, true); + + if (response) { + processResponse(player, npcState, response); + } + + if (npcState->focusState == 1 || !queueList.empty()) { + isIdle = false; + } + + ++it; + } + + if (isIdle && !hasScriptedFocus) { + setCreatureFocus(NULL); + } +} + +void Npc::processResponse(Player* player, NpcState* npcState, const NpcResponse* response, bool delayResponse /*= false*/) +{ + if (response) { + bool resetTopic = true; + + if (response->getFocusState() == 0) { + npcState->focusState = 0; + } else if (response->getFocusState() == 1) { + npcState->focusState = 1; + } + + if (response->getAmount() != -1) { + if (npcState->itemId > 0) { + const ItemType& it = Item::items[npcState->itemId]; + + if (it.id != 0 && it.stackable == false) { + npcState->amount = std::min(response->getAmount(), 100); + } else { + npcState->amount = response->getAmount(); + } + } else { + npcState->amount = response->getAmount(); + } + } + + for (ActionList::const_iterator it = response->getFirstAction(); it != response->getEndAction(); ++it) { + switch (it->actionType) { + case ACTION_SETTOPIC: + npcState->topic = std::max(it->intValue, 0); + resetTopic = false; + break; + case ACTION_SETSELLPRICE: + npcState->sellPrice = it->intValue; + break; + case ACTION_SETBUYPRICE: + npcState->buyPrice = it->intValue; + break; + case ACTION_SETITEM: + npcState->itemId = it->intValue; + break; + case ACTION_SETSUBTYPE: + npcState->subType = it->intValue; + break; + case ACTION_SETEFFECT: + g_game.addMagicEffect(player->getPosition(), it->intValue); + break; + case ACTION_SETPRICE: { + if (it->strValue == "|SELLPRICE|") { + npcState->price = npcState->sellPrice; + } else if (it->strValue == "|BUYPRICE|") { + npcState->price = npcState->buyPrice; + } else { + npcState->price = it->intValue; + } + + break; + } + case ACTION_SETTELEPORT: { + Position teleportTo = it->pos; + + if (it->strValue == "|TEMPLE|") { + teleportTo = player->getTemplePosition(); + } + + g_game.internalTeleport(player, teleportTo); + break; + } + + case ACTION_SETIDLE: { + npcState->focusState = 0; + break; + } + + case ACTION_SETLEVEL: { + if (it->strValue == "|SPELLLEVEL|") { + npcState->level = -1; + InstantSpell* spell = g_spells->getInstantSpellByName(npcState->spellName); + + if (spell) { + npcState->level = spell->getLevel(); + } + } else { + npcState->level = it->intValue; + } + + break; + } + + case ACTION_SETSPELL: { + InstantSpell* spell = g_spells->getInstantSpellByName(it->strValue); + + if (spell) { + npcState->spellName = it->strValue; + } else { + npcState->spellName = ""; + } + + break; + } + + case ACTION_SETLISTNAME: { + npcState->listName = it->strValue; + break; + } + + case ACTION_SETLISTPNAME: { + npcState->listPluralName = it->strValue; + break; + } + + case ACTION_SETAMOUNT: { + int32_t amount; + + if (it->strValue == "|AMOUNT|") { + amount = npcState->amount; + } else { + amount = it->intValue; + } + + if (npcState->itemId > 0 && amount > 100) { + amount = 100; + } + + npcState->amount = amount; + break; + } + + case ACTION_TEACHSPELL: { + std::string spellName; + + if (it->strValue == "|SPELL|") { + spellName = npcState->spellName; + } else { + spellName = it->strValue; + } + + player->learnInstantSpell(spellName); + break; + } + + case ACTION_SETSTORAGE: { + if (it->key > 0) { + player->addStorageValue(it->key, it->intValue); + } + + break; + } + + case ACTION_ADDQUEUE: { + QueueList::iterator it = std::find(queueList.begin(), queueList.end(), player->getID()); + + if (it == queueList.end()) { + queueList.push_back(player->getID()); + npcState->isQueued = true; + } + + break; + } + + case ACTION_SELLITEM: { + uint64_t moneyCount; + + if (it->strValue == "|PRICE|") { + moneyCount = npcState->price * npcState->amount; + } else { + moneyCount = it->intValue; + } + + const ItemType& it = Item::items[npcState->itemId]; + + if (it.id != 0) { + int32_t subType; + + if (it.hasSubType()) { + subType = npcState->subType; + } else { + subType = -1; + } + + int32_t itemCount = player->__getItemTypeCount(it.id, subType); + + if (itemCount >= npcState->amount) { + g_game.removeItemOfType(player, it.id, npcState->amount, subType); + g_game.addMoney(player, moneyCount, FLAG_NOLIMIT); + } + } + + break; + } + + case ACTION_BUYITEM: { + uint64_t moneyCount; + + if (it->strValue == "|PRICE|") { + moneyCount = npcState->price * npcState->amount; + } else { + moneyCount = it->intValue; + } + + const ItemType& it = Item::items[npcState->itemId]; + + if (it.id != 0) { + int32_t subType; + + if (it.hasSubType()) { + subType = npcState->subType; + } else { + subType = -1; + } + + if (g_game.removeMoney(player, moneyCount)) { + if (it.stackable) { + int32_t amount = npcState->amount; + + while (amount > 0) { + int32_t stackCount = std::min(100, amount); + Item* item = Item::CreateItem(it.id, stackCount); + + if (g_game.internalPlayerAddItem(player, item) != RET_NOERROR) { + delete item; + break; + } + + amount -= stackCount; + } + } else { + for (int32_t i = 0; i < npcState->amount; ++i) { + Item* item = Item::CreateItem(it.id, subType); + + if (g_game.internalPlayerAddItem(player, item) != RET_NOERROR) { + delete item; + break; + } + } + } + } else { + std::cout << "Error [Npc::processResponse] Not enough money: " << player->getName() << "\tNpc: " << getName() << std::endl; + } + } + + break; + } + + case ACTION_TAKEITEM: { + int32_t itemId; + + if (it->strValue == "|ITEM|") { + itemId = npcState->itemId; + } else { + itemId = it->intValue; + } + + const ItemType& it = Item::items[npcState->itemId]; + + if (it.id != 0) { + int32_t subType; + + if (it.hasSubType()) { + subType = npcState->subType; + } else { + subType = -1; + } + + int32_t itemCount = player->__getItemTypeCount(itemId, subType); + + if (itemCount >= npcState->amount) { + g_game.removeItemOfType(player, itemId, npcState->amount, subType); + } + } + + break; + } + + case ACTION_GIVEITEM: { + int32_t itemId; + + if (it->strValue == "|ITEM|") { + itemId = npcState->itemId; + } else { + itemId = it->intValue; + } + + const ItemType& it = Item::items[itemId]; + + if (it.id != 0) { + int32_t subType; + + if (it.hasSubType()) { + subType = npcState->subType; + } else { + subType = -1; + } + + for (int32_t i = 0; i < npcState->amount; ++i) { + Item* item = Item::CreateItem(it.id, subType); + + if (g_game.internalPlayerAddItem(player, item) != RET_NOERROR) { + delete item; + } + } + } + + break; + } + + case ACTION_TAKEMONEY: { + uint64_t moneyCount; + + if (it->strValue == "|PRICE|") { + moneyCount = npcState->price * npcState->amount; + } else { + moneyCount = it->intValue; + } + + g_game.removeMoney(player, moneyCount); + break; + } + + case ACTION_GIVEMONEY: { + uint64_t moneyCount; + + if (it->strValue == "|PRICE|") { + moneyCount = npcState->price * npcState->amount; + } else { + moneyCount = it->intValue; + } + + g_game.addMoney(player, moneyCount); + break; + } + + case ACTION_SCRIPT: { + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + env->setRealPos(getPosition()); + env->setNpc(this); + + std::ostringstream scriptstream; + //attach various variables that could be interesting + scriptstream << "cid = " << env->addThing(player) << std::endl; + scriptstream << "text = \"" << LuaScriptInterface::escapeString(npcState->respondToText) << "\"" << std::endl; + scriptstream << "name = \"" << LuaScriptInterface::escapeString(player->getName()) << "\"" << std::endl; + scriptstream << "idleTimeout = " << idleTimeout << std::endl; + + scriptstream << "itemlist = {" << std::endl; + uint32_t n = 0; + + for (std::list::const_iterator iit = response->prop.itemList.begin(); iit != response->prop.itemList.end(); ++iit) { + bool addDelim = (n + 1 != response->prop.itemList.size()); + scriptstream << "{id = " << iit->itemId + << ", subtype = " << iit->subType + << ", buy=" << iit->buyPrice + << ", sell=" << iit->sellPrice << "}"; + + if (addDelim) { + scriptstream << "," << std::endl; + } + + ++n; + } + + scriptstream << "}" << std::endl; + + scriptstream << "_state = {" << std::endl; + scriptstream << "topic = " << npcState->topic << ',' << std::endl; + scriptstream << "itemid = " << npcState->itemId << ',' << std::endl; + scriptstream << "subtype = " << npcState->subType << ',' << std::endl; + scriptstream << "amount = " << npcState->amount << ',' << std::endl; + scriptstream << "price = " << npcState->price << ',' << std::endl; + scriptstream << "level = " << npcState->level << ',' << std::endl; + scriptstream << "spellname = \"" << LuaScriptInterface::escapeString(npcState->spellName) << "\"" << ',' << std::endl; + scriptstream << "listname = \"" << LuaScriptInterface::escapeString(npcState->listName) << "\"" << ',' << std::endl; + scriptstream << "listpname = \"" << LuaScriptInterface::escapeString(npcState->listPluralName) << "\"" << ',' << std::endl; + + scriptstream << "n1 = " << npcState->scriptVars.n1 << ',' << std::endl; + scriptstream << "n2 = " << npcState->scriptVars.n2 << ',' << std::endl; + scriptstream << "n3 = " << npcState->scriptVars.n3 << ',' << std::endl; + + scriptstream << "b1 = " << (npcState->scriptVars.b1 ? "true" : "false" ) << ',' << std::endl; + scriptstream << "b2 = " << (npcState->scriptVars.b2 ? "true" : "false" ) << ',' << std::endl; + scriptstream << "b3 = " << (npcState->scriptVars.b3 ? "true" : "false" ) << ',' << std::endl; + + scriptstream << "s1 = \"" << LuaScriptInterface::escapeString(npcState->scriptVars.s1) << "\"" << ',' << std::endl; + scriptstream << "s2 = \"" << LuaScriptInterface::escapeString(npcState->scriptVars.s2) << "\"" << ',' << std::endl; + scriptstream << "s3 = \"" << LuaScriptInterface::escapeString(npcState->scriptVars.s3) << "\"" << std::endl; + scriptstream << "}" << std::endl; + + scriptstream << it->strValue; + + //std::cout << scriptstream.str() << std::endl; + if (m_scriptInterface->loadBuffer(scriptstream.str(), NULL) != -1) { + lua_State* L = m_scriptInterface->getLuaState(); + lua_getglobal(L, "_state"); + NpcScriptInterface::popState(L, npcState); + } + + m_scriptInterface->releaseScriptEnv(); + } + + break; + } + + default: + break; + } + } + + if (resetTopic && response->getTopic() == npcState->topic) { + npcState->topic = 0; + } + + lastResponseTime = OTSYS_TIME(); + npcState->lastResponseTime = lastResponseTime; + + if (delayResponse) { + npcState->lastResponse = response; + } else { + turnToCreature(player); + executeResponse(player, npcState, response); + npcState->subResponse = response; + npcState->lastResponse = NULL; + } + } +} + +void Npc::executeResponse(Player* player, NpcState* npcState, const NpcResponse* response) +{ + if (response->getResponseType() == RESPONSE_DEFAULT) { + std::string responseString = formatResponse(player, npcState, response); + + if (!responseString.empty()) { + if (response->publicize()) { + doSay(responseString); + } else { + doSayToPlayer(player, responseString); + } + } + } else { + int32_t functionId = -1; + ResponseScriptMap::iterator it = responseScriptMap.find(response->getText()); + + if (it != responseScriptMap.end()) { + functionId = it->second; + } else { + functionId = m_scriptInterface->getEvent(response->getText()); + responseScriptMap[response->getText()] = functionId; + } + + if (functionId != -1) { + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + lua_State* L = m_scriptInterface->getLuaState(); + + env->setScriptId(functionId, m_scriptInterface); + env->setRealPos(getPosition()); + env->setNpc(this); + + m_scriptInterface->pushFunction(functionId); + int32_t paramCount = 0; + + for (ActionList::const_iterator it = response->getFirstAction(); it != response->getEndAction(); ++it) { + if (it->actionType == ACTION_SCRIPTPARAM) { + if (it->strValue == "|PLAYER|") { + uint32_t cid = env->addThing(player); + lua_pushnumber(L, cid); + } else if (it->strValue == "|TEXT|") { + lua_pushstring(L, npcState->respondToText.c_str()); + } else { + std::cout << "Warning [Npc::processResponse] Unknown script param: " << it->strValue << std::endl; + break; + } + + ++paramCount; + } + } + + NpcScriptInterface::pushState(L, npcState); + lua_setglobal(L, "_state"); + m_scriptInterface->callFunction(paramCount); + lua_getglobal(L, "_state"); + NpcScriptInterface::popState(L, npcState); + m_scriptInterface->releaseScriptEnv(); + } else { + std::cout << "[Error] Call stack overflow." << std::endl; + } + } + } +} + +void Npc::doSay(const std::string& text) +{ + g_game.internalCreatureSay(this, SPEAK_SAY, text, false); +} + +void Npc::doSayToPlayer(Player* player, const std::string& text) +{ + if (player) { + player->sendCreatureSay(this, SPEAK_PRIVATE_NP, text); + player->onCreatureSay(this, SPEAK_PRIVATE_NP, text); + } +} + +void Npc::doMove(Direction dir) +{ + g_game.internalMoveCreature(this, dir); +} + +void Npc::doTurn(Direction dir) +{ + g_game.internalCreatureTurn(this, dir); +} + +uint32_t Npc::getListItemPrice(uint16_t itemId, ShopEvent_t type) +{ + for (ItemListMap::iterator it = itemListMap.begin(); it != itemListMap.end(); ++it) { + std::list& itemList = it->second; + + for (std::list::iterator iit = itemList.begin(); iit != itemList.end(); ++iit) { + if (iit->itemId == itemId) { + if (type == SHOPEVENT_BUY) { + return iit->buyPrice; + } else if (type == SHOPEVENT_SELL) { + return iit->sellPrice; + } + } + } + } + + return 0; +} + +void Npc::onPlayerTrade(Player* player, ShopEvent_t type, int32_t callback, uint16_t itemId, + uint8_t count, uint8_t amount, bool ignore/* = false*/, bool inBackpacks/* = false*/) +{ + int8_t subType = -1; + const ItemType& it = Item::items[itemId]; + + if (it.hasSubType() && !it.stackable) { + subType = count; + } + + if (type == SHOPEVENT_BUY) { + NpcState* npcState = getState(player, true); + + if (npcState) { + npcState->amount = amount; + npcState->subType = subType; + npcState->itemId = itemId; + npcState->buyPrice = getListItemPrice(itemId, SHOPEVENT_BUY); + npcState->ignore = ignore; + npcState->inBackpacks = inBackpacks; + + const NpcResponse* response = getResponse(player, npcState, EVENT_PLAYER_SHOPBUY, false); + processResponse(player, npcState, response); + } + } else if (type == SHOPEVENT_SELL) { + NpcState* npcState = getState(player, true); + + if (npcState) { + npcState->amount = amount; + npcState->subType = subType; + npcState->itemId = itemId; + npcState->sellPrice = getListItemPrice(itemId, SHOPEVENT_SELL); + npcState->ignore = ignore; + + const NpcResponse* response = getResponse(player, npcState, EVENT_PLAYER_SHOPSELL, false); + processResponse(player, npcState, response); + } + } + + if (m_npcEventHandler) { + m_npcEventHandler->onPlayerTrade(player, callback, itemId, count, amount, ignore, inBackpacks); + } + + player->sendSaleItemList(); +} + +void Npc::onPlayerEndTrade(Player* player, int32_t buyCallback, int32_t sellCallback) +{ + lua_State* L = getScriptInterface()->getLuaState(); + + if (buyCallback != -1) { + luaL_unref(L, LUA_REGISTRYINDEX, buyCallback); + } + + if (sellCallback != -1) { + luaL_unref(L, LUA_REGISTRYINDEX, sellCallback); + } + + removeShopPlayer(player); + + NpcState* npcState = getState(player); + + if (npcState) { + const NpcResponse* response = getResponse(player, npcState, EVENT_PLAYER_SHOPCLOSE, false); + processResponse(player, npcState, response); + } + + if (m_npcEventHandler) { + m_npcEventHandler->onPlayerEndTrade(player); + } +} + +bool Npc::getNextStep(Direction& dir, uint32_t& flags) +{ + if (Creature::getNextStep(dir, flags)) { + return true; + } + + if (walkTicks <= 0) { + return false; + } + + if (!isIdle || focusCreature != 0) { + return false; + } + + if (getTimeSinceLastMove() < walkTicks) { + return false; + } + + return getRandomStep(dir); +} + +bool Npc::canWalkTo(const Position& fromPos, Direction dir) +{ + if (masterRadius == 0) { + return false; + } + + Position toPos = getNextPosition(dir, fromPos); + + if (!Spawns::getInstance()->isInZone(masterPos, masterRadius, toPos)) { + return false; + } + + Tile* tile = g_game.getTile(toPos.x, toPos.y, toPos.z); + + if (!tile || tile->__queryAdd(0, this, 1, 0) != RET_NOERROR) { + return false; + } + + if (!floorChange && (tile->floorChange() || tile->getTeleportItem())) { + return false; + } + + return true; +} + +bool Npc::getRandomStep(Direction& dir) +{ + std::vector dirList; + const Position& creaturePos = getPosition(); + + if (canWalkTo(creaturePos, NORTH)) { + dirList.push_back(NORTH); + } + + if (canWalkTo(creaturePos, SOUTH)) { + dirList.push_back(SOUTH); + } + + if (canWalkTo(creaturePos, EAST)) { + dirList.push_back(EAST); + } + + if (canWalkTo(creaturePos, WEST)) { + dirList.push_back(WEST); + } + + if (!dirList.empty()) { + std::random_shuffle(dirList.begin(), dirList.end()); + dir = dirList[random_range(0, dirList.size() - 1)]; + return true; + } + + return false; +} + +void Npc::doMoveTo(Position target) +{ + std::list listDir; + + if (g_game.getPathToEx(this, target, listDir, 1, 1, true, true)) { + startAutoWalk(listDir); + } +} + +void Npc::turnToCreature(Creature* creature) +{ + const Position& creaturePos = creature->getPosition(); + const Position& myPos = getPosition(); + int32_t dx = myPos.x - creaturePos.x; + int32_t dy = myPos.y - creaturePos.y; + + Direction dir = SOUTH; + float tan = 0; + + if (dx != 0) { + tan = (float)dy / dx; + } else { + tan = 10; + } + + if (std::abs(tan) < 1) { + if (dx > 0) { + dir = WEST; + } else { + dir = EAST; + } + } else { + if (dy > 0) { + dir = NORTH; + } else { + dir = SOUTH; + } + } + + g_game.internalCreatureTurn(this, dir); +} + +void Npc::setCreatureFocus(Creature* creature) +{ + if (creature) { + focusCreature = creature->getID(); + turnToCreature(creature); + } else { + focusCreature = 0; + } +} + +const NpcResponse* Npc::getResponse(const ResponseList& list, const Player* player, + NpcState* npcState, const std::string& text, bool exactMatch /*= false*/, NpcEvent_t eventType /*=EVENT_NONE*/) +{ + std::string textString = asLowerCaseString(text); + std::vector wordList = explodeString(textString, " "); + + // We choose the match that matches the most keywords + // _and_ matches all of it's conditions. + // If we only have a patial keyword match, we ignore it (all keywords must be matched) + + const NpcResponse* bestMatch = NULL; + int32_t bestKeywordCount = 0; + + // Cache some info + int64_t money = -1; + + // First loop we try with current topic + int32_t currentTopic = npcState->topic; + // If we get no matches, we (hypothetically) change it to 0 and see if we get any matches + int32_t loopCount = 1; + + while (loopCount <= 2) { + if (loopCount == 2) { + // If it didn't work with 0 before, it won't work now + if (currentTopic == 0) { + break; + } else { + currentTopic = 0; // Change to 0 and try again + } + } + + loopCount++; + + for (ResponseList::const_iterator it = list.begin(); it != list.end(); ++it) { + NpcResponse* iresponse = *it; + uint32_t params = iresponse->getParams(); + + if (eventType != EVENT_NONE && iresponse->getEventType() != eventType) { + continue; + } + + if (eventType == EVENT_NONE && iresponse->getEventType() != EVENT_NONE) { + continue; + } + + if (iresponse->getEventType() == EVENT_NONE || iresponse->getFocusState() != -1) { + if (npcState->focusState != 1 && iresponse->getFocusState() != 1) { + continue; //We are idle, and this response does not activate the npc. + } + + if (npcState->focusState == 1 && iresponse->getFocusState() == 1) { + continue; //We are not idle, and this response would activate us again. + } + } + + if (iresponse->getTopic() != -1 && currentTopic != iresponse->getTopic()) { + continue; //Not right topic + } + + // If we got a topic already, we must have response with the same topic + if (currentTopic != 0 && iresponse->getTopic() != currentTopic) { + continue; + } + + if (hasBitSet(RESPOND_MALE, params)) { + if (!player->isMale()) { + continue; + } + } + + if (hasBitSet(RESPOND_FEMALE, params)) { + if (!player->isFemale()) { + continue; + } + } + + if (hasBitSet(RESPOND_PZBLOCK, params)) { + if (!player->isPzLocked()) { + continue; + } + } + + if (hasBitSet(RESPOND_PREMIUM, params)) { + if (!player->isPremium()) { + continue; + } + } + + // This is an ugly, restrictive hack + // vocations shouldn't be hardcoded + if (hasBitSet(RESPOND_DRUID, params)) { + if (player->getVocationId() != VOCATION_DRUID && player->getVocationId() != VOCATION_ELDERDRUID) { + continue; + } + } + + if (hasBitSet(RESPOND_KNIGHT, params)) { + if (player->getVocationId() != VOCATION_KNIGHT && player->getVocationId() != VOCATION_ELITEKNIGHT) { + continue; + } + } + + if (hasBitSet(RESPOND_PALADIN, params)) { + if (player->getVocationId() != VOCATION_PALADIN && player->getVocationId() != VOCATION_ROYALPALADIN) { + continue; + } + } + + if (hasBitSet(RESPOND_SORCERER, params)) { + if (player->getVocationId() != VOCATION_SORCERER && player->getVocationId() != VOCATION_MASTERSORCERER) { + continue; + } + } + + if (hasBitSet(RESPOND_PROMOTED, params)) { + if (player->getVocationId() == VOCATION_NONE || + player->getVocationId() == VOCATION_SORCERER || + player->getVocationId() == VOCATION_DRUID || + player->getVocationId() == VOCATION_KNIGHT || + player->getVocationId() == VOCATION_PALADIN) { + continue; + } + } + + if (hasBitSet(RESPOND_LOWLEVEL, params)) { + if (iresponse->getLevel() > 0) { + if ((int32_t)player->getLevel() >= iresponse->getLevel()) { + continue; + } + } else { + if ((int32_t)player->getLevel() >= npcState->level) { + continue; + } + } + } + + if (hasBitSet(RESPOND_HIGHLEVEL, params)) { + if (iresponse->getLevel() > 0) { + if ((int32_t)player->getLevel() < iresponse->getLevel()) { + continue; + } + } else if ((int32_t)player->getLevel() < npcState->level) { + continue; + } + } + + if (hasBitSet(RESPOND_KNOWSPELL, params)) { + if (!player->hasLearnedInstantSpell(npcState->spellName)) { + continue; + } + } + + if (hasBitSet(RESPOND_CANNOTLEARNSPELL, params)) { + Spell* spell = g_spells->getInstantSpellByName(npcState->spellName); + + if (!spell) { + std::cout << "[WARNING]: Non-existant spell in cannotlearnspell tag" << std::endl; + } else { + if (player->getLevel() >= spell->getLevel() && + player->getMagicLevel() >= spell->getMagicLevel() && + (spell->isPremium() ? player->isPremium() : true)) { + continue; + } + } + } + + if (iresponse->getCondition() != CONDITION_NONE) { + if (!player->hasCondition(iresponse->getCondition())) { + continue; + } + } + + if (iresponse->getHealth() != -1) { + if (player->getHealth() >= iresponse->getHealth()) { + continue; + } + } + + if (iresponse->getKnowSpell() != "") { + std::string spellName = iresponse->getKnowSpell(); + + if (spellName == "|SPELL|") { + spellName = npcState->spellName; + } + + if (!player->hasLearnedInstantSpell(spellName)) { + continue; + } + } + + if (iresponse->scriptVars.b1) { + if (!npcState->scriptVars.b1) { + continue; + } + } + + if (iresponse->scriptVars.b2) { + if (!npcState->scriptVars.b2) { + continue; + } + } + + if (iresponse->scriptVars.b3) { + if (!npcState->scriptVars.b3) { + continue; + } + } + + if (iresponse->scriptVars.n1 != -1) { + if (npcState->scriptVars.n1 != iresponse->scriptVars.n1) { + continue; + } + } + + if (iresponse->scriptVars.n2 != -1) { + if (npcState->scriptVars.n2 != iresponse->scriptVars.n2) { + continue; + } + } + + if (iresponse->scriptVars.n3 != -1) { + if (npcState->scriptVars.n3 != iresponse->scriptVars.n3) { + continue; + } + } + + bool storageMatch = false; + + for (StorageConditions::const_iterator iter = iresponse->prop.storageConditions.begin(); iter != iresponse->prop.storageConditions.end(); ++iter) { + const StorageCondition& cs = *iter; + + int32_t playerStorageValue = -1; + + if (!player->getStorageValue(cs.id, playerStorageValue)) { + playerStorageValue = -1; + } + + switch (cs.op) { + case STORAGE_LESS: { + if (playerStorageValue >= cs.value) { + storageMatch = true; + } + + break; + } + + case STORAGE_LESSOREQUAL: { + if (playerStorageValue > cs.value) { + storageMatch = true; + } + + break; + } + + case STORAGE_EQUAL: { + if (playerStorageValue != cs.value) { + storageMatch = true; + } + + break; + } + + case STORAGE_NOTEQUAL: { + if (playerStorageValue == cs.value) { + storageMatch = true; + } + + break; + } + + case STORAGE_GREATEROREQUAL: { + if (playerStorageValue < cs.value) { + storageMatch = true; + } + + break; + } + + case STORAGE_GREATER: { + if (playerStorageValue <= cs.value) { + storageMatch = true; + } + + break; + } + + default: + break; + } + + if (storageMatch) { + break; + } + } + + if (storageMatch) { + continue; + } + + if (hasBitSet(RESPOND_LOWMONEY, params)) { + if (money == -1) { + money = g_game.getMoney(player); + } + + if (money >= (npcState->price * npcState->amount)) { + continue; + } + } + + if (hasBitSet(RESPOND_ENOUGHMONEY, params)) { + if (money == -1) { + money = g_game.getMoney(player); + } + + if (money < (npcState->price * npcState->amount)) { + continue; + } + } + + if (hasBitSet(RESPOND_LOWAMOUNT, params) || hasBitSet(RESPOND_NOAMOUNT, params) || hasBitSet(RESPOND_ENOUGHAMOUNT, params)) { + int32_t itemCount = player->__getItemTypeCount(npcState->itemId, npcState->subType); + + if (hasBitSet(RESPOND_ENOUGHAMOUNT, params)) { + if (itemCount < npcState->amount) { + continue; + } + } else if (itemCount >= npcState->amount) { + continue; + /* + if(hasBitSet(RESPOND_LOWAMOUNT, params)) + { + if(npcState->amount == 1) + continue; + } + + if(hasBitSet(RESPOND_NOAMOUNT, params)) + { + if(npcState->amount > 1) + continue; + } + */ + } + } + + if (iresponse->getHaveItemID() != 0) { + int32_t itemCount = player->__getItemTypeCount(iresponse->getHaveItemID()); + + if (itemCount == 0) { + continue; + } + } + + if (iresponse->getDontHaveItemID() != 0) { + int32_t itemCount = player->__getItemTypeCount(iresponse->getDontHaveItemID()); + + if (itemCount > 0) { + continue; + } + } + + if (iresponse->getEventType() != EVENT_NONE) { + switch (iresponse->getEventType()) { + case EVENT_IDLE: { + if ((*it)->isSingleEvent() && hasUsedIdleReply) { + continue; + } + + if ((*it)->getTime() != 0) { + //state idle (each state has its own idle) + uint32_t time = (*it)->getTime() * 1000; + + if ((OTSYS_TIME() - npcState->lastResponseTime) < time) { + //not enough time elapsed + continue; + } + } else { + continue; + } + + break; + } + + default: + break; + } + } + + if (iresponse->getEventType() == EVENT_NONE || iresponse->getEventType() == EVENT_BUSY) { + // Check keywords + if (!text.empty() && !iresponse->getInputList() .empty()) { + int32_t matches = matchKeywords(*it, wordList, exactMatch); + + if (matches > bestKeywordCount) { + bestKeywordCount = matches; + bestMatch = iresponse; + } + } else if (bestKeywordCount == 0) { + bestMatch = iresponse; + } + } else { + // First match is always the best + return iresponse; + } + } + + if (bestMatch) { + return bestMatch; + } + + // If we got no match yet, try again with other topic + } + + return NULL; +} + +int32_t Npc::matchKeywords(NpcResponse* response, std::vector wordList, bool exactMatch) +{ + int32_t bestMatchCount = 0; + + if (wordList.empty()) { + return 0; + } + + const std::list& inputList = response->getInputList(); + + for (std::list::const_iterator it = inputList.begin(); it != inputList.end(); ++it) { + int32_t matchCount = 0; + std::vector::iterator lastWordMatchIter = wordList.begin(); + std::string keywords = (*it); + std::vector keywordList = explodeString(keywords, ";"); + + for (std::vector::iterator keyIter = keywordList.begin(); keyIter != keywordList.end(); ++keyIter) { + if (!exactMatch && (*keyIter) == "|*|") { + //Match anything. + } else if ((*keyIter) == "|amount|") { + if (lastWordMatchIter == wordList.end()) { + continue; + } + + //TODO: Should iterate through each word until a number or a new keyword is found. + int32_t amount = atoi(lastWordMatchIter->c_str()); + + if (amount > 0) { + if (amount <= 500) { + response->setAmount(amount); + } else { + response->setAmount(500); + } + } else { + response->setAmount(1); + continue; + } + } else { + bool fullMatch = false; + + if (!keyIter->empty()) { + if ((*keyIter)[keyIter->size() - 1] == '$') { + fullMatch = true; + } + } + + std::vector::iterator wordIter = wordList.end(); + + for (wordIter = lastWordMatchIter; wordIter != wordList.end(); ++wordIter) { + if (fullMatch) { + if (*wordIter == keyIter->substr(0, keyIter->size() - 1)) { + break; + } + } else if (wordIter->find(*keyIter, 0) == 0) { + break; + } + } + + if (wordIter == wordList.end()) { + continue; + } + + lastWordMatchIter = wordIter + 1; + } + + ++matchCount; + } + + if ((size_t)matchCount == keywordList.size() && matchCount > bestMatchCount) { + bestMatchCount = matchCount; + } + } + + return bestMatchCount; +} + +const NpcResponse* Npc::getResponse(const Player* player, NpcState* npcState, + const std::string& text, bool checkSubResponse) +{ + if (checkSubResponse && npcState->subResponse) { + //Check previous response chain first + const ResponseList& list = npcState->subResponse->getResponseList(); + const NpcResponse* response = getResponse(list, player, npcState, text); + + if (response) { + return response; + } + } + + return getResponse(responseList, player, npcState, text); +} + +const NpcResponse* Npc::getResponse(const Player* player, NpcEvent_t eventType) +{ + std::vector result; + + for (ResponseList::const_iterator it = responseList.begin(); it != responseList.end(); ++it) { + if ((*it)->getEventType() == eventType) { + result.push_back(*it); + } + } + + if (result.empty()) { + return NULL; + } + + return result[random_range(0, result.size() - 1)]; +} + +const NpcResponse* Npc::getResponse(const Player* player, NpcState* npcState, + NpcEvent_t eventType, const std::string& text, bool checkSubResponse) +{ + if (eventType == EVENT_NONE) { + return NULL; + } + + if (checkSubResponse && npcState->subResponse) { + //Check previous response chain first + const ResponseList& list = npcState->subResponse->getResponseList(); + const NpcResponse* response = getResponse(list, player, npcState, text, true, eventType); + + if (response) { + return response; + } + } + + return getResponse(responseList, player, npcState, text, true, eventType); +} + +const NpcResponse* Npc::getResponse(const Player* player, NpcState* npcState, + NpcEvent_t eventType, bool checkSubResponse) +{ + if (eventType == EVENT_NONE) { + return NULL; + } + + if (checkSubResponse && npcState->subResponse) { + //Check previous response chain first + const ResponseList& list = npcState->subResponse->getResponseList(); + const NpcResponse* response = getResponse(list, player, npcState, "", true, eventType); + + if (response) { + return response; + } + } + + return getResponse(responseList, player, npcState, "", true, eventType); +} + +std::string Npc::formatResponse(Creature* creature, const NpcState* npcState, const NpcResponse* response) const +{ + std::string responseString = response->getText(); + + replaceString(responseString, "|PRICE|", std::to_string(npcState->price * npcState->amount)); + replaceString(responseString, "|AMOUNT|", std::to_string(npcState->amount)); + replaceString(responseString, "|LEVEL|", std::to_string(npcState->level)); + + replaceString(responseString, "|N1|", std::to_string(npcState->scriptVars.n1)); + replaceString(responseString, "|N2|", std::to_string(npcState->scriptVars.n2)); + replaceString(responseString, "|N3|", std::to_string(npcState->scriptVars.n3)); + + replaceString(responseString, "|S1|", npcState->scriptVars.s1); + replaceString(responseString, "|S2|", npcState->scriptVars.s2); + replaceString(responseString, "|S3|", npcState->scriptVars.s3); + + replaceString(responseString, "|SPELLNAME|", npcState->spellName); + + if (npcState->spellName != "") { + Spell* spell = g_spells->getInstantSpellByName(npcState->spellName); + + if (spell) { + replaceString(responseString, "|SPELLMAGLEVEL|", std::to_string(spell->getMagicLevel())); + replaceString(responseString, "|SPELLLEVEL|", std::to_string(spell->getLevel())); + } + } + + if (npcState->itemId != -1) { + const ItemType& it = Item::items[npcState->itemId]; + + if (npcState->amount <= 1) { + std::ostringstream ss; + ss << it.article + " " + it.name; + replaceString(responseString, "|ITEMNAME|", ss.str()); + } else { + replaceString(responseString, "|ITEMNAME|", it.getPluralName()); + } + } + + replaceString(responseString, "|NAME|", creature->getName()); + replaceString(responseString, "|NPCNAME|", getName()); + return responseString; +} + +void Npc::addShopPlayer(Player* player) +{ + ShopPlayerList::iterator it = std::find(shopPlayerList.begin(), shopPlayerList.end(), player); + + if (it == shopPlayerList.end()) { + shopPlayerList.push_back(player); + } +} + +void Npc::removeShopPlayer(const Player* player) +{ + if (shopPlayerList.empty()) { + return; + } + + ShopPlayerList::iterator it = std::find(shopPlayerList.begin(), shopPlayerList.end(), player); + + if (it != shopPlayerList.end()) { + shopPlayerList.erase(it); + } +} + +void Npc::closeAllShopWindows() +{ + for (ShopPlayerList::iterator it = shopPlayerList.begin(); it != shopPlayerList.end();) { + Player* player = *(it); + it = shopPlayerList.erase(it); + player->closeShopWindow(); + } + + shopPlayerList.clear(); +} + +NpcScriptInterface* Npc::getScriptInterface() +{ + return m_scriptInterface; +} + +NpcScriptInterface::NpcScriptInterface() : + LuaScriptInterface("Npc interface") +{ + m_libLoaded = false; + initState(); +} + +NpcScriptInterface::~NpcScriptInterface() +{ + // +} + +bool NpcScriptInterface::initState() +{ + return LuaScriptInterface::initState(); +} + +bool NpcScriptInterface::closeState() +{ + m_libLoaded = false; + return LuaScriptInterface::closeState(); +} + +bool NpcScriptInterface::loadNpcLib(const std::string& file) +{ + if (m_libLoaded) { + return true; + } + + if (loadFile(file) == -1) { + std::cout << "Warning: [NpcScriptInterface::loadNpcLib] Can not load " << file << std::endl; + return false; + } + + m_libLoaded = true; + return true; +} + +void NpcScriptInterface::registerFunctions() +{ + LuaScriptInterface::registerFunctions(); + + //npc exclusive functions + lua_register(m_luaState, "selfSay", NpcScriptInterface::luaActionSay); + lua_register(m_luaState, "selfMove", NpcScriptInterface::luaActionMove); + lua_register(m_luaState, "selfMoveTo", NpcScriptInterface::luaActionMoveTo); + lua_register(m_luaState, "selfTurn", NpcScriptInterface::luaActionTurn); + lua_register(m_luaState, "selfFollow", NpcScriptInterface::luaActionFollow); + lua_register(m_luaState, "selfGetPosition", NpcScriptInterface::luaSelfGetPos); + lua_register(m_luaState, "creatureGetName", NpcScriptInterface::luaCreatureGetName); + lua_register(m_luaState, "creatureGetName2", NpcScriptInterface::luaCreatureGetName2); + lua_register(m_luaState, "creatureGetPosition", NpcScriptInterface::luaCreatureGetPos); + lua_register(m_luaState, "getDistanceTo", NpcScriptInterface::luagetDistanceTo); + lua_register(m_luaState, "doNpcSetCreatureFocus", NpcScriptInterface::luaSetNpcFocus); + lua_register(m_luaState, "getNpcCid", NpcScriptInterface::luaGetNpcCid); + lua_register(m_luaState, "getNpcPos", NpcScriptInterface::luaGetNpcPos); + lua_register(m_luaState, "getNpcState", NpcScriptInterface::luaGetNpcState); + lua_register(m_luaState, "setNpcState", NpcScriptInterface::luaSetNpcState); + lua_register(m_luaState, "getNpcName", NpcScriptInterface::luaGetNpcName); + lua_register(m_luaState, "getNpcParameter", NpcScriptInterface::luaGetNpcParameter); + lua_register(m_luaState, "openShopWindow", NpcScriptInterface::luaOpenShopWindow); + lua_register(m_luaState, "closeShopWindow", NpcScriptInterface::luaCloseShopWindow); + lua_register(m_luaState, "doSellItem", NpcScriptInterface::luaDoSellItem); +} + +int32_t NpcScriptInterface::luaCreatureGetName2(lua_State* L) +{ + //creatureGetName2(name) - returns creature id + popString(L); + reportErrorFunc("Deprecated function."); + lua_pushnil(L); + return 1; +} + +int32_t NpcScriptInterface::luaCreatureGetName(lua_State* L) +{ + //creatureGetName(cid) + popNumber(L); + reportErrorFunc("Deprecated function. Use getCreatureName"); + lua_pushstring(L, ""); + return 1; +} + +int32_t NpcScriptInterface::luaCreatureGetPos(lua_State* L) +{ + //creatureGetPosition(cid) + popNumber(L); + reportErrorFunc("Deprecated function. Use getCreaturePosition"); + lua_pushnil(L); + lua_pushnil(L); + lua_pushnil(L); + return 3; +} + +int32_t NpcScriptInterface::luaSelfGetPos(lua_State* L) +{ + //selfGetPosition() + ScriptEnvironment* env = getScriptEnv(); + Npc* npc = env->getNpc(); + + if (npc) { + Position pos = npc->getPosition(); + pushPosition(L, pos); + } else { + lua_pushnil(L); + } + + return 1; +} + +int32_t NpcScriptInterface::luaActionSay(lua_State* L) +{ + //selfSay(words [[, target], publicize]) + // publicize defaults to true if there is no target, false otherwise + uint32_t parameters = lua_gettop(L); + uint32_t target = 0; + bool publicize = true; + + if (parameters >= 3) { + publicize = (popNumber(L) == 1); + } + + if (parameters >= 2) { + target = popNumber(L); + + if (target != 0) { + publicize = false; + } + } + + std::string text = popString(L); + + ScriptEnvironment* env = getScriptEnv(); + + Npc* npc = env->getNpc(); + Player* player = env->getPlayerByUID(target); + + if (npc) { + if (publicize) { + npc->doSay(text); + } else { + npc->doSayToPlayer(player, text); + } + } + + return 0; +} + +int32_t NpcScriptInterface::luaActionMove(lua_State* L) +{ + //selfMove(direction) + Direction dir = (Direction)popNumber(L); + ScriptEnvironment* env = getScriptEnv(); + + Npc* npc = env->getNpc(); + + if (npc) { + npc->doMove(dir); + } + + return 0; +} + +int32_t NpcScriptInterface::luaActionMoveTo(lua_State* L) +{ + //selfMoveTo(x,y,z) + Position target; + target.z = (int)popNumber(L); + target.y = (int)popNumber(L); + target.x = (int)popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + Npc* npc = env->getNpc(); + + if (npc) { + npc->doMoveTo(target); + } + + return 0; +} + +int32_t NpcScriptInterface::luaActionTurn(lua_State* L) +{ + //selfTurn(direction) + Direction dir = (Direction)popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Npc* npc = env->getNpc(); + + if (npc) { + npc->doTurn(dir); + } + + return 0; +} + +int32_t NpcScriptInterface::luaActionFollow(lua_State* L) +{ + //selfFollow(cid) + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Player* player = env->getPlayerByUID(cid); + + if (cid != 0 && !player) { + lua_pushboolean(L, false); + return 1; + } + + Npc* npc = env->getNpc(); + + if (!npc) { + lua_pushboolean(L, false); + return 1; + } + + bool result = npc->setFollowCreature(player, true); + lua_pushboolean(L, result); + return 1; +} + +int32_t NpcScriptInterface::luagetDistanceTo(lua_State* L) +{ + //getDistanceTo(uid) + uint32_t uid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Npc* npc = env->getNpc(); + Thing* thing = env->getThingByUID(uid); + + if (thing && npc) { + Position thing_pos = thing->getPosition(); + Position npc_pos = npc->getPosition(); + + if (npc_pos.z != thing_pos.z) { + lua_pushnumber(L, -1); + } else { + int32_t dist = std::max(std::abs(npc_pos.x - thing_pos.x), std::abs(npc_pos.y - thing_pos.y)); + lua_pushnumber(L, dist); + } + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_THING_NOT_FOUND)); + lua_pushnil(L); + } + + return 1; +} + +int32_t NpcScriptInterface::luaSetNpcFocus(lua_State* L) +{ + //doNpcSetCreatureFocus(cid) + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Npc* npc = env->getNpc(); + + if (npc) { + Creature* creature = env->getCreatureByUID(cid); + + if (creature) { + npc->hasScriptedFocus = true; + } else { + npc->hasScriptedFocus = false; + } + + npc->setCreatureFocus(creature); + } + + return 0; +} + +int32_t NpcScriptInterface::luaGetNpcPos(lua_State* L) +{ + //getNpcPos() + ScriptEnvironment* env = getScriptEnv(); + + Position pos(0, 0, 0); + uint32_t stackpos = 0; + + Npc* npc = env->getNpc(); + + if (npc) { + pos = npc->getPosition(); + stackpos = npc->getParent()->__getIndexOfThing(npc); + } + + pushPosition(L, pos, stackpos); + return 1; +} + +int32_t NpcScriptInterface::luaGetNpcState(lua_State* L) +{ + //getNpcState(cid) + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + const Player* player = env->getPlayerByUID(cid); + + if (!player) { + lua_pushnil(L); + return 1; + } + + Npc* npc = env->getNpc(); + + if (!npc) { + lua_pushnil(L); + return 1; + } + + NpcState* state = npc->getState(player); + NpcScriptInterface::pushState(L, state); + return 1; +} + +int32_t NpcScriptInterface::luaSetNpcState(lua_State* L) +{ + //setNpcState(state, cid) + uint32_t cid = popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + const Player* player = env->getPlayerByUID(cid); + + if (!player) { + lua_pushnil(L); + return 1; + } + + Npc* npc = env->getNpc(); + + if (!npc) { + lua_pushboolean(L, false); + return 1; + } + + NpcState* state = npc->getState(player); + NpcScriptInterface::popState(L, state); + lua_pushboolean(L, true); + return 1; +} + +int32_t NpcScriptInterface::luaGetNpcCid(lua_State* L) +{ + //getNpcCid() + ScriptEnvironment* env = getScriptEnv(); + + Npc* npc = env->getNpc(); + + if (npc) { + uint32_t cid = env->addThing(npc); + lua_pushnumber(L, cid); + } else { + lua_pushnil(L); + } + + return 1; +} + +int32_t NpcScriptInterface::luaGetNpcName(lua_State* L) +{ + //getNpcName() + ScriptEnvironment* env = getScriptEnv(); + + Npc* npc = env->getNpc(); + + if (npc) { + lua_pushstring(L, npc->getName().c_str()); + } else { + lua_pushstring(L, ""); + } + + return 1; +} + +int32_t NpcScriptInterface::luaGetNpcParameter(lua_State* L) +{ + //getNpcParameter(paramKey) + std::string paramKey = popString(L); + + ScriptEnvironment* env = getScriptEnv(); + + Npc* npc = env->getNpc(); + + if (npc) { + Npc::ParametersMap::iterator it = npc->m_parameters.find(paramKey); + + if (it != npc->m_parameters.end()) { + lua_pushstring(L, it->second.c_str()); + } else { + lua_pushnil(L); + } + } else { + lua_pushnil(L); + } + + return 1; +} + +void NpcScriptInterface::pushState(lua_State* L, NpcState* state) +{ + lua_newtable(L); + setField(L, "price", state->price); + setField(L, "amount", state->amount); + setField(L, "itemid", state->itemId); + setField(L, "subtype", state->subType); + setField(L, "subType", state->subType); + setFieldBool(L, "ignore", state->ignore); + setFieldBool(L, "ignorecapacity", state->ignore); + setFieldBool(L, "ignoreequipped", state->ignore); + setFieldBool(L, "inbackpacks", state->inBackpacks); + setField(L, "topic", state->topic); + setField(L, "level", state->level); + setField(L, "spellname", state->spellName); + setField(L, "listname", state->listName); + setField(L, "listpname", state->listPluralName); + setFieldBool(L, "isidle", state->focusState != 1); + + setField(L, "n1", state->scriptVars.n1); + setField(L, "n2", state->scriptVars.n2); + setField(L, "n3", state->scriptVars.n3); + + setFieldBool(L, "b1", state->scriptVars.b1); + setFieldBool(L, "b2", state->scriptVars.b2); + setFieldBool(L, "b3", state->scriptVars.b3); + + setField(L, "s1", state->scriptVars.s1); + setField(L, "s2", state->scriptVars.s2); + setField(L, "s3", state->scriptVars.s3); +} + +void NpcScriptInterface::popState(lua_State* L, NpcState* &state) +{ + state->price = getField(L, "price"); + state->amount = getField(L, "amount"); + state->itemId = getField(L, "itemid"); + state->subType = getField(L, "subtype"); + + if (state->subType == 0) { + state->subType = getField(L, "subType"); + } + + state->ignore = getFieldBool(L, "ignore") || getFieldBool(L, "ignorecapacity") || getFieldBool(L, "ignoreequipped"); + state->inBackpacks = getFieldBool(L, "inbackpacks"); + + state->topic = std::max(getField(L, "topic"), (int32_t)0); + state->level = getField(L, "level"); + state->spellName = getFieldString(L, "spellname"); + state->listName = getFieldString(L, "listname"); + state->listPluralName = getFieldString(L, "listpname"); + bool isIdle = getFieldBool(L, "isidle") || getFieldBool(L, "isIdle"); + + if (isIdle) { + state->focusState = 0; + } + + state->scriptVars.n1 = getField(L, "n1"); + state->scriptVars.n2 = getField(L, "n2"); + state->scriptVars.n3 = getField(L, "n3"); + + state->scriptVars.b1 = getFieldBool(L, "b1"); + state->scriptVars.b2 = getFieldBool(L, "b2"); + state->scriptVars.n3 = getFieldBool(L, "b3"); + + state->scriptVars.s1 = getFieldString(L, "s1"); + state->scriptVars.s2 = getFieldString(L, "s2"); + state->scriptVars.s3 = getFieldString(L, "s3"); +} + +int32_t NpcScriptInterface::luaOpenShopWindow(lua_State* L) +{ + //openShopWindow(cid, items, onBuy callback, onSell callback) + int32_t buyCallback = -1; + int32_t sellCallback = -1; + std::list items; + Player* player = NULL; + + ScriptEnvironment* env = getScriptEnv(); + Npc* npc = env->getNpc(); + + if (lua_isfunction(L, -1) == 0) { + lua_pop(L, 1); // skip it - use default value + } else { + sellCallback = popCallback(L); + } + + if (lua_isfunction(L, -1) == 0) { + lua_pop(L, 1); // skip it - use default value + } else { + buyCallback = popCallback(L); + } + + if (lua_istable(L, -1) == 0) { + reportError(__FUNCTION__, "item list is not a table."); + lua_pushboolean(L, false); + return 1; + } + + // first key + lua_pushnil(L); + + while (lua_next(L, -2) != 0) { + ShopInfo item; + item.itemId = getField(L, "id"); + item.subType = getField(L, "subType"); + + if (item.subType == 0) { + item.subType = getField(L, "subtype"); + } + + item.buyPrice = getField(L, "buy"); + item.sellPrice = getField(L, "sell"); + item.realName = getFieldString(L, "name"); + items.push_back(item); + + lua_pop(L, 1); + } + + lua_pop(L, 1); + + player = env->getPlayerByUID(popNumber(L)); + + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + //Close any eventual other shop window currently open. + player->closeShopWindow(false); + + if (!npc) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + npc->addShopPlayer(player); + player->setShopOwner(npc, buyCallback, sellCallback); + player->openShopWindow(npc, items); + + lua_pushboolean(L, true); + return 1; +} + +int32_t NpcScriptInterface::luaCloseShopWindow(lua_State* L) +{ + //closeShopWindow(cid) + ScriptEnvironment* env = getScriptEnv(); + + Player* player = env->getPlayerByUID(popNumber(L)); + + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + Npc* npc = env->getNpc(); + + if (!npc) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + int32_t buyCallback; + int32_t sellCallback; + + Npc* merchant = player->getShopOwner(buyCallback, sellCallback); + + //Check if we actually have a shop window with this player. + if (merchant == npc) { + player->sendCloseShop(); + + if (buyCallback != -1) { + luaL_unref(L, LUA_REGISTRYINDEX, buyCallback); + } + + if (sellCallback != -1) { + luaL_unref(L, LUA_REGISTRYINDEX, sellCallback); + } + + player->setShopOwner(NULL, -1, -1); + npc->removeShopPlayer(player); + } + + lua_pushboolean(L, true); + return 1; +} + +int32_t NpcScriptInterface::luaDoSellItem(lua_State* L) +{ + //doSellItem(cid, itemid, amount, subtype, actionid, canDropOnMap) + int32_t parameters = lua_gettop(L); + + bool canDropOnMap = true; + + if (parameters > 5) { + canDropOnMap = (popNumber(L) == 1); + } + + uint32_t actionId = 0; + + if (parameters > 4) { + actionId = popNumber(L); + } + + uint32_t subType = 1; + + if (parameters > 3) { + int32_t n = popNumber(L); + + if (n != -1) { + subType = n; + } + } + + uint32_t amount = (uint32_t)popNumber(L); + uint32_t itemId = (uint32_t)popNumber(L); + + ScriptEnvironment* env = getScriptEnv(); + + Player* player = env->getPlayerByUID(popNumber(L)); + + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushboolean(L, false); + return 1; + } + + uint32_t sellCount = 0; + const ItemType& it = Item::items[itemId]; + + if (it.stackable) { + while (amount > 0) { + int32_t stackCount = std::min(100, amount); + Item* item = Item::CreateItem(it.id, stackCount); + + if (item && actionId != 0) { + item->setActionId(actionId); + } + + if (g_game.internalPlayerAddItem(player, item, canDropOnMap) != RET_NOERROR) { + delete item; + lua_pushnumber(L, sellCount); + return 1; + } + + amount = amount - stackCount; + sellCount += stackCount; + } + } else { + for (uint32_t i = 0; i < amount; ++i) { + Item* item = Item::CreateItem(it.id, subType); + + if (item && actionId != 0) { + item->setActionId(actionId); + } + + if (g_game.internalPlayerAddItem(player, item, canDropOnMap) != RET_NOERROR) { + delete item; + lua_pushnumber(L, sellCount); + return 1; + } + + ++sellCount; + } + } + + lua_pushnumber(L, sellCount); + return 1; +} + +NpcEventsHandler::NpcEventsHandler(Npc* npc) +{ + m_npc = npc; + m_loaded = false; +} + +NpcEventsHandler::~NpcEventsHandler() +{ + // +} + +bool NpcEventsHandler::isLoaded() const +{ + return m_loaded; +} + +NpcScript::NpcScript(const std::string& file, Npc* npc) : + NpcEventsHandler(npc) +{ + m_scriptInterface = npc->getScriptInterface(); + + if (m_scriptInterface->reserveScriptEnv()) { + m_scriptInterface->getScriptEnv()->setNpc(npc); + + if (m_scriptInterface->loadFile(file, npc) == -1) { + std::cout << "[Warning - NpcScript::NpcScript] Can not load script: " << file << std::endl; + std::cout << m_scriptInterface->getLastLuaError() << std::endl; + m_loaded = false; + m_scriptInterface->releaseScriptEnv(); + return; + } + + m_scriptInterface->releaseScriptEnv(); + } + + m_onCreatureSay = m_scriptInterface->getEvent("onCreatureSay"); + m_onCreatureDisappear = m_scriptInterface->getEvent("onCreatureDisappear"); + m_onCreatureAppear = m_scriptInterface->getEvent("onCreatureAppear"); + m_onCreatureMove = m_scriptInterface->getEvent("onCreatureMove"); + m_onPlayerCloseChannel = m_scriptInterface->getEvent("onPlayerCloseChannel"); + m_onPlayerEndTrade = m_scriptInterface->getEvent("onPlayerEndTrade"); + m_onThink = m_scriptInterface->getEvent("onThink"); + m_loaded = true; +} + +NpcScript::~NpcScript() +{ + // +} + +void NpcScript::onCreatureAppear(const Creature* creature) +{ + if (m_onCreatureAppear == -1) { + return; + } + + //onCreatureAppear(creature) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + lua_State* L = m_scriptInterface->getLuaState(); + + env->setScriptId(m_onCreatureAppear, m_scriptInterface); + env->setRealPos(m_npc->getPosition()); + env->setNpc(m_npc); + + uint32_t cid = env->addThing(const_cast(creature)); + + m_scriptInterface->pushFunction(m_onCreatureAppear); + lua_pushnumber(L, cid); + m_scriptInterface->callFunction(1); + m_scriptInterface->releaseScriptEnv(); + } else { + std::cout << "[Error - NpcScript::onCreatureAppear] NPC Name: " << m_npc->getName() << " - Call stack overflow" << std::endl; + } +} + +void NpcScript::onCreatureDisappear(const Creature* creature) +{ + if (m_onCreatureDisappear == -1) { + return; + } + + //onCreatureDisappear(id) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + lua_State* L = m_scriptInterface->getLuaState(); + + env->setScriptId(m_onCreatureDisappear, m_scriptInterface); + env->setRealPos(m_npc->getPosition()); + env->setNpc(m_npc); + + uint32_t cid = env->addThing(const_cast(creature)); + + m_scriptInterface->pushFunction(m_onCreatureDisappear); + lua_pushnumber(L, cid); + m_scriptInterface->callFunction(1); + m_scriptInterface->releaseScriptEnv(); + } else { + std::cout << "[Error - NpcScript::onCreatureDisappear] NPC Name: " << m_npc->getName() << " - Call stack overflow" << std::endl; + } +} + +void NpcScript::onCreatureMove(const Creature* creature, const Position& oldPos, const Position& newPos) +{ + if (m_onCreatureMove == -1) { + return; + } + + //onCreatureMove(creature, oldPos, newPos) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + lua_State* L = m_scriptInterface->getLuaState(); + + env->setScriptId(m_onCreatureMove, m_scriptInterface); + env->setRealPos(m_npc->getPosition()); + env->setNpc(m_npc); + + uint32_t cid = env->addThing(const_cast(creature)); + + m_scriptInterface->pushFunction(m_onCreatureMove); + lua_pushnumber(L, cid); + LuaScriptInterface::pushPosition(L, oldPos, 0); + LuaScriptInterface::pushPosition(L, newPos, 0); + m_scriptInterface->callFunction(3); + m_scriptInterface->releaseScriptEnv(); + } else { + std::cout << "[Error - NpcScript::onCreatureMove] NPC Name: " << m_npc->getName() << " - Call stack overflow" << std::endl; + } +} + +void NpcScript::onCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, Position* pos/* = NULL*/) +{ + if (m_onCreatureSay == -1) { + return; + } + + //onCreatureSay(cid, type, msg) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + env->setScriptId(m_onCreatureSay, m_scriptInterface); + env->setRealPos(m_npc->getPosition()); + env->setNpc(m_npc); + + uint32_t cid = env->addThing(const_cast(creature)); + + lua_State* L = m_scriptInterface->getLuaState(); + m_scriptInterface->pushFunction(m_onCreatureSay); + lua_pushnumber(L, cid); + lua_pushnumber(L, type); + lua_pushstring(L, text.c_str()); + m_scriptInterface->callFunction(3); + m_scriptInterface->releaseScriptEnv(); + } else { + std::cout << "[Error - NpcScript::onCreatureSay] NPC Name: " << m_npc->getName() << " - Call stack overflow" << std::endl; + } +} + +void NpcScript::onPlayerTrade(const Player* player, int32_t callback, uint16_t itemid, + uint8_t count, uint8_t amount, bool ignore, bool inBackpacks) +{ + if (callback == -1) { + return; + } + + //"onBuy"(cid, itemid, count, amount, ignore, inbackpacks) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + env->setScriptId(-1, m_scriptInterface); + env->setRealPos(m_npc->getPosition()); + env->setNpc(m_npc); + + uint32_t cid = env->addThing(const_cast(player)); + lua_State* L = m_scriptInterface->getLuaState(); + LuaScriptInterface::pushCallback(L, callback); + lua_pushnumber(L, cid); + lua_pushnumber(L, itemid); + lua_pushnumber(L, count); + lua_pushnumber(L, amount); + lua_pushboolean(L, ignore); + lua_pushboolean(L, inBackpacks); + m_scriptInterface->callFunction(6); + m_scriptInterface->releaseScriptEnv(); + } else { + std::cout << "[Error - NpcScript::onPlayerTrade] NPC Name: " << m_npc->getName() << " - Call stack overflow" << std::endl; + } +} + +void NpcScript::onPlayerCloseChannel(const Player* player) +{ + if (m_onPlayerCloseChannel == -1) { + return; + } + + //onPlayerCloseChannel(cid) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + env->setScriptId(m_onPlayerCloseChannel, m_scriptInterface); + env->setRealPos(m_npc->getPosition()); + env->setNpc(m_npc); + + uint32_t cid = env->addThing(const_cast(player)); + + lua_State* L = m_scriptInterface->getLuaState(); + m_scriptInterface->pushFunction(m_onPlayerCloseChannel); + lua_pushnumber(L, cid); + m_scriptInterface->callFunction(1); + m_scriptInterface->releaseScriptEnv(); + } else { + std::cout << "[Error - NpcScript::onPlayerCloseChannel] NPC Name: " << m_npc->getName() << " - Call stack overflow" << std::endl; + } +} + +void NpcScript::onPlayerEndTrade(const Player* player) +{ + if (m_onPlayerCloseChannel == -1) { + return; + } + + //onPlayerEndTrade(cid) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + env->setScriptId(m_onPlayerCloseChannel, m_scriptInterface); + env->setRealPos(m_npc->getPosition()); + env->setNpc(m_npc); + + uint32_t cid = env->addThing(const_cast(player)); + + lua_State* L = m_scriptInterface->getLuaState(); + m_scriptInterface->pushFunction(m_onPlayerEndTrade); + lua_pushnumber(L, cid); + m_scriptInterface->callFunction(1); + m_scriptInterface->releaseScriptEnv(); + } else { + std::cout << "[Error - NpcScript::onPlayerEndTrade] NPC Name: " << m_npc->getName() << " - Call stack overflow" << std::endl; + } +} + +void NpcScript::onThink() +{ + if (m_onThink == -1) { + return; + } + + //onThink() + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + env->setScriptId(m_onThink, m_scriptInterface); + env->setRealPos(m_npc->getPosition()); + env->setNpc(m_npc); + + m_scriptInterface->pushFunction(m_onThink); + m_scriptInterface->callFunction(0); + m_scriptInterface->releaseScriptEnv(); + } else { + std::cout << "[Error - NpcScript::onThink] NPC Name: " << m_npc->getName() << " - Call stack overflow" << std::endl; + } +} diff --git a/src/npc.h b/src/npc.h new file mode 100644 index 0000000000..9ded27c32b --- /dev/null +++ b/src/npc.h @@ -0,0 +1,673 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_NPC_H__ +#define __OTSERV_NPC_H__ + +#include "creature.h" +#include "luascript.h" +#include "templates.h" + +class Npc; +class Player; +class NpcResponse; +struct NpcState; + +typedef std::list NpcList; +class Npcs +{ + public: + Npcs() {} + ~Npcs() {} + void reload(); +}; + +class NpcScriptInterface : public LuaScriptInterface +{ + public: + NpcScriptInterface(); + virtual ~NpcScriptInterface(); + + bool loadNpcLib(const std::string& file); + + static void pushState(lua_State* L, NpcState* state); + static void popState(lua_State* L, NpcState* &state); + + protected: + virtual void registerFunctions(); + + static int32_t luaActionSay(lua_State* L); + static int32_t luaActionMove(lua_State* L); + static int32_t luaActionMoveTo(lua_State* L); + static int32_t luaActionTurn(lua_State* L); + static int32_t luaActionFollow(lua_State* L); + static int32_t luaCreatureGetName(lua_State* L); + static int32_t luaCreatureGetName2(lua_State* L); + static int32_t luaCreatureGetPos(lua_State* L); + static int32_t luaSelfGetPos(lua_State* L); + static int32_t luagetDistanceTo(lua_State* L); + static int32_t luaSetNpcFocus(lua_State* L); + static int32_t luaGetNpcCid(lua_State* L); + static int32_t luaGetNpcPos(lua_State* L); + static int32_t luaGetNpcState(lua_State* L); + static int32_t luaSetNpcState(lua_State* L); + static int32_t luaGetNpcName(lua_State* L); + static int32_t luaGetNpcParameter(lua_State* L); + static int32_t luaOpenShopWindow(lua_State* L); + static int32_t luaCloseShopWindow(lua_State* L); + static int32_t luaDoSellItem(lua_State* L); + + private: + virtual bool initState(); + virtual bool closeState(); + + bool m_libLoaded; +}; + +class NpcEventsHandler +{ + public: + NpcEventsHandler(Npc* npc); + virtual ~NpcEventsHandler(); + + virtual void onCreatureAppear(const Creature* creature) {} + virtual void onCreatureDisappear(const Creature* creature) {} + virtual void onCreatureMove(const Creature* creature, const Position& oldPos, const Position& newPos) {} + virtual void onCreatureSay(const Creature* creature, SpeakClasses, const std::string& text, Position* pos = NULL) {} + virtual void onPlayerTrade(const Player* player, int32_t callback, uint16_t itemid, + uint8_t count, uint8_t amount, bool ignore = false, bool inBackpacks = false) {} + virtual void onPlayerCloseChannel(const Player* player) {} + virtual void onPlayerEndTrade(const Player* player) {} + virtual void onThink() {} + + bool isLoaded() const; + + protected: + Npc* m_npc; + bool m_loaded; +}; + +class NpcScript : public NpcEventsHandler +{ + public: + NpcScript(const std::string& file, Npc* npc); + NpcScript(Npc* npc); + virtual ~NpcScript(); + + virtual void onCreatureAppear(const Creature* creature); + virtual void onCreatureDisappear(const Creature* creature); + virtual void onCreatureMove(const Creature* creature, const Position& oldPos, const Position& newPos); + virtual void onCreatureSay(const Creature* creature, SpeakClasses, const std::string& text, Position* pos = NULL); + virtual void onPlayerTrade(const Player* player, int32_t callback, uint16_t itemid, + uint8_t count, uint8_t amount, bool ignore, bool inBackpacks); + virtual void onPlayerCloseChannel(const Player* player); + virtual void onPlayerEndTrade(const Player* player); + virtual void onThink(); + + private: + NpcScriptInterface* m_scriptInterface; + + int32_t m_onCreatureAppear; + int32_t m_onCreatureDisappear; + int32_t m_onCreatureMove; + int32_t m_onCreatureSay; + int32_t m_onPlayerCloseChannel; + int32_t m_onPlayerEndTrade; + int32_t m_onThink; +}; + +enum RespondParam_t { + RESPOND_DEFAULT = 0x0000, + RESPOND_MALE = 0x0001, + RESPOND_FEMALE = 0x0002, + RESPOND_PZBLOCK = 0x0004, + RESPOND_LOWMONEY = 0x0008, + RESPOND_NOAMOUNT = 0x0010, + RESPOND_LOWAMOUNT = 0x0020, + RESPOND_PREMIUM = 0x0040, + RESPOND_DRUID = 0x0080, + RESPOND_KNIGHT = 0x0100, + RESPOND_PALADIN = 0x0200, + RESPOND_SORCERER = 0x0400, + RESPOND_LOWLEVEL = 0x0800, + RESPOND_ENOUGHMONEY = 0x1000, + RESPOND_ENOUGHAMOUNT = 0x2000, + RESPOND_HIGHLEVEL = 0x4000, + RESPOND_KNOWSPELL = 0x8000, + RESPOND_CANNOTLEARNSPELL = 0x10000, + RESPOND_PROMOTED = 0x20000, + RESPOND_NOTTOPIC = 0x40000 +}; + +enum ResponseType_t { + RESPONSE_DEFAULT, + RESPONSE_SCRIPT +}; + +enum InteractType_t { + INTERACT_NONE, + INTERACT_TEXT, + INTERACT_EVENT +}; + +enum ReponseActionParam_t { + ACTION_NONE, + ACTION_SETTOPIC, + ACTION_SETLEVEL, + ACTION_SETPRICE, + ACTION_SETBUYPRICE, + ACTION_SETSELLPRICE, + ACTION_TAKEMONEY, + ACTION_GIVEMONEY, + ACTION_SELLITEM, + ACTION_BUYITEM, + ACTION_GIVEITEM, + ACTION_TAKEITEM, + ACTION_SETAMOUNT, + ACTION_SETITEM, + ACTION_SETSUBTYPE, + ACTION_SETEFFECT, + ACTION_SETSPELL, + ACTION_SETLISTNAME, + ACTION_SETLISTPNAME, + ACTION_TEACHSPELL, + ACTION_SETSTORAGE, + ACTION_SETTELEPORT, + ACTION_SCRIPT, + ACTION_SCRIPTPARAM, + ACTION_ADDQUEUE, + ACTION_SETIDLE +}; + +enum StorageComparision_t { + STORAGE_LESS, + STORAGE_LESSOREQUAL, + STORAGE_EQUAL, + STORAGE_NOTEQUAL, + STORAGE_GREATEROREQUAL, + STORAGE_GREATER +}; + +struct StorageCondition { + int32_t id; + int32_t value; + StorageComparision_t op; +}; + +typedef std::vector StorageConditions; + +enum NpcEvent_t { + EVENT_NONE, + EVENT_BUSY, + EVENT_THINK, + EVENT_IDLE, + EVENT_PLAYER_ENTER, + EVENT_PLAYER_MOVE, + EVENT_PLAYER_LEAVE, + EVENT_PLAYER_SHOPSELL, + EVENT_PLAYER_SHOPBUY, + EVENT_PLAYER_SHOPCLOSE + + /* + EVENT_CREATURE_ENTER, + EVENT_CREATURE_MOVE, + EVENT_CREATURE_LEAVE, + */ +}; + +enum ShopEvent_t { + SHOPEVENT_SELL, + SHOPEVENT_BUY, + SHOPEVENT_CLOSE +}; + +struct ResponseAction { + public: + ResponseAction() { + actionType = ACTION_NONE; + key = 0; + intValue = 0; + strValue = ""; + pos.x = 0; + pos.y = 0; + pos.z = 0; + } + + ReponseActionParam_t actionType; + int32_t key; + int32_t intValue; + std::string strValue; + Position pos; +}; + +struct ScriptVars { + ScriptVars() { + n1 = -1; + n2 = -1; + n3 = -1; + b1 = false; + b2 = false; + b3 = false; + s1 = ""; + s2 = ""; + s3 = ""; + } + + int32_t n1; + int32_t n2; + int32_t n3; + bool b1; + bool b2; + bool b3; + std::string s1; + std::string s2; + std::string s3; +}; + +struct ListItem { + ListItem() { + itemId = 0; + subType = -1; + sellPrice = 0; + buyPrice = 0; + keywords = ""; + name = ""; + pluralName = ""; + } + + int32_t sellPrice; + int32_t buyPrice; + int32_t itemId; + int32_t subType; + std::string keywords; + std::string name; + std::string pluralName; +}; + + +typedef std::list ActionList; +typedef std::map ResponseScriptMap; +typedef std::list ResponseList; + +class NpcResponse +{ + public: + struct ResponseProperties { + ResponseProperties() { + topic = -1; + focusStatus = -1; + eventType = EVENT_NONE; + params = 0; + publicize = true; + inputList.clear(); + haveItemId = 0; + dontHaveItemId = 0; + level = 0; + storageConditions.clear(); + itemList.clear(); + time = 0; + singleEvent = false; + + responseType = RESPONSE_DEFAULT; + output = ""; + knowSpell = ""; + actionList.clear(); + condition = CONDITION_NONE; + health = -1; + amount = -1; + } + + //interact specific + int32_t topic; + int32_t focusStatus; + NpcEvent_t eventType; + uint32_t params; + bool publicize; + std::list inputList; + uint16_t haveItemId; + uint16_t dontHaveItemId; + uint32_t level; + StorageConditions storageConditions; + std::list itemList; + uint32_t time; + bool singleEvent; + + //response specific + ResponseType_t responseType; + std::string output; + std::string knowSpell; + ActionList actionList; + ConditionType_t condition; + int32_t health; + int32_t amount; + }; + + NpcResponse(const ResponseProperties& _prop, + ResponseList _subResponseList, + ScriptVars _scriptVars) { + prop = _prop; + subResponseList = _subResponseList; + scriptVars = _scriptVars; + } + + NpcResponse(NpcResponse& rhs) { + prop = rhs.prop; + scriptVars = rhs.scriptVars; + + for (ResponseList::iterator it = rhs.subResponseList.begin(); it != rhs.subResponseList.end(); ++it) { + NpcResponse* response = new NpcResponse(*(*it)); + subResponseList.push_back(response); + } + } + + ~NpcResponse() { + for (ResponseList::iterator it = subResponseList.begin(); it != subResponseList.end(); ++it) { + delete *it; + } + + subResponseList.clear(); + } + + uint32_t getParams() const { + return prop.params; + } + std::string getInputText() const { + return (prop.inputList.empty() ? "" : *prop.inputList.begin()); + } + int32_t getTopic() const { + return prop.topic; + } + int32_t getFocusState() const { + return prop.focusStatus; + } + int32_t getHaveItemID() const { + return prop.haveItemId; + } + int32_t getDontHaveItemID() const { + return prop.dontHaveItemId; + } + ConditionType_t getCondition() const { + return prop.condition; + } + int32_t getHealth() const { + return prop.health; + } + int32_t getLevel() const { + return prop.level; + } + ResponseType_t getResponseType() const { + return prop.responseType; + } + NpcEvent_t getEventType() const { + return prop.eventType; + } + const std::string& getKnowSpell() const { + return prop.knowSpell; + } + const std::string& getText() const { + return prop.output; + } + int32_t getAmount() const { + return prop.amount; + } + void setAmount(int32_t _amount) { + prop.amount = _amount; + } + bool publicize() const { + return prop.publicize; + } + uint32_t getTime() const { + return prop.time; + } + uint32_t isSingleEvent() const { + return prop.singleEvent; + } + + std::string formatResponseString(Creature* creature) const; + void addAction(ResponseAction action) { + prop.actionList.push_back(action); + } + const std::list& getInputList() const { + return prop.inputList; + } + + void setResponseList(ResponseList _list) { + subResponseList.insert(subResponseList.end(), _list.begin(), _list.end()); + } + const ResponseList& getResponseList() const { + return subResponseList; + } + + ActionList::const_iterator getFirstAction() const { + return prop.actionList.begin(); + } + ActionList::const_iterator getEndAction() const { + return prop.actionList.end(); + } + + ResponseProperties prop; + ResponseList subResponseList; + ScriptVars scriptVars; +}; + +struct NpcState { + uint32_t playerId; + int32_t topic; + int32_t focusState; + bool isQueued; + int32_t price; + int32_t sellPrice; + int32_t buyPrice; + int32_t amount; + int32_t itemId; + int32_t subType; + bool ignore; + bool inBackpacks; + std::string spellName; + std::string listName; + std::string listPluralName; + int32_t level; + std::string respondToText; + const NpcResponse* lastResponse; + const NpcResponse* subResponse; + uint64_t lastResponseTime; + + //script variables + ScriptVars scriptVars; + + //Do not forget to update pushState/popState if you add more variables +}; + +class Npc : public Creature +{ + public: +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + static uint32_t npcCount; +#endif + + virtual ~Npc(); + + virtual Npc* getNpc() { + return this; + } + virtual const Npc* getNpc() const { + return this; + } + + virtual bool isPushable() const { + return walkTicks > 0; + } + + virtual uint32_t idRange() { + return 0x80000000; + } + static AutoList listNpc; + void removeList() { + listNpc.removeList(getID()); + } + void addList() { + listNpc.addList(this); + } + + static Npc* createNpc(const std::string& name); + + virtual bool canSee(const Position& pos) const; + + bool load(); + void reload(); + + virtual const std::string& getName() const { + return name; + } + virtual const std::string& getNameDescription() const { + return name; + } + + virtual CreatureType_t getType() const { + return CREATURETYPE_NPC; + } + + void doSay(const std::string& text); + void doSayToPlayer(Player* player, const std::string& text); + + void doMove(Direction dir); + void doTurn(Direction dir); + void doMoveTo(Position pos); + bool isLoaded() const { + return loaded; + } + virtual void setMasterPos(const Position& pos, uint32_t radius = 1) { + masterPos = pos; + + if (masterRadius == -1) { + masterRadius = radius; + } + } + + void onPlayerCloseChannel(const Player* player); + void onPlayerTrade(Player* player, ShopEvent_t type, int32_t callback, uint16_t itemId, + uint8_t count, uint8_t amount, bool ignore = false, bool inBackpacks = false); + void onPlayerEndTrade(Player* player, int32_t buyCallback, int32_t sellCallback); + + void turnToCreature(Creature* creature); + void setCreatureFocus(Creature* creature); + + NpcScriptInterface* getScriptInterface(); + + protected: + Npc(const std::string& _name); + + virtual void onCreatureAppear(const Creature* creature, bool isLogin); + virtual void onCreatureDisappear(const Creature* creature, uint32_t stackpos, bool isLogout); + virtual void onCreatureMove(const Creature* creature, const Tile* newTile, const Position& newPos, + const Tile* oldTile, const Position& oldPos, bool teleport); + + virtual void onCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, Position* pos = NULL); + virtual void onThink(uint32_t interval); + virtual std::string getDescription(int32_t lookDistance) const; + + bool isImmune(CombatType_t type) const { + return true; + } + bool isImmune(ConditionType_t type) const { + return true; + } + virtual bool isAttackable() const { + return attackable; + } + virtual bool getNextStep(Direction& dir, uint32_t& flags); + + bool canWalkTo(const Position& fromPos, Direction dir); + bool getRandomStep(Direction& dir); + + void reset(); + bool loadFromXml(const std::string& name); + + const NpcResponse* getResponse(const ResponseList& list, const Player* player, + NpcState* npcState, const std::string& text, + bool exactMatch = false, NpcEvent_t eventType = EVENT_NONE); + const NpcResponse* getResponse(const Player* player, NpcState* npcState, const std::string& text, bool checkLastResponse); + const NpcResponse* getResponse(const Player* player, NpcEvent_t eventType); + const NpcResponse* getResponse(const Player* player, NpcState* npcState, + NpcEvent_t eventType, const std::string& text, bool checkLastResponse); + const NpcResponse* getResponse(const Player* player, NpcState* npcState, + NpcEvent_t eventType, bool checkLastResponse); + + int32_t matchKeywords(NpcResponse* response, std::vector wordList, bool exactMatch); + + void processResponse(Player* player, NpcState* npcState, const NpcResponse* response, bool delayResponse = false); + void executeResponse(Player* player, NpcState* npcState, const NpcResponse* response); + + std::string formatResponse(Creature* creature, const NpcState* npcState, const NpcResponse* response) const; + + void onPlayerEnter(Player* player, NpcState* state); + void onPlayerLeave(Player* player, NpcState* state); + + typedef std::map ParametersMap; + ParametersMap m_parameters; + + uint32_t loadParams(xmlNodePtr node); + StorageCondition loadStorageCondition(xmlNodePtr node); + ResponseList loadInteraction(xmlNodePtr node); + + NpcState* getState(const Player* player, bool makeNew = true); + + void addShopPlayer(Player* player); + void removeShopPlayer(const Player* player); + void closeAllShopWindows(); + uint32_t getListItemPrice(uint16_t itemId, ShopEvent_t type); + + std::string name; + std::string m_filename; + uint32_t walkTicks; + bool floorChange; + bool attackable; + bool isIdle; + bool hasUsedIdleReply; + bool hasBusyReply; + bool hasScriptedFocus; + int32_t talkRadius; + uint32_t idleTimeout; + uint64_t lastResponseTime; + bool defaultPublic; + int32_t focusCreature; + + typedef std::list ShopPlayerList; + ShopPlayerList shopPlayerList; + + typedef std::map > ItemListMap; + ItemListMap itemListMap; + + ResponseScriptMap responseScriptMap; + ResponseList responseList; + + typedef std::list StateList; + StateList stateList; + NpcEventsHandler* m_npcEventHandler; + + typedef std::list QueueList; + QueueList queueList; + bool loaded; + + static NpcScriptInterface* m_scriptInterface; + + friend class Npcs; + friend class NpcScriptInterface; +}; + +#endif diff --git a/src/otpch.cpp b/src/otpch.cpp new file mode 100644 index 0000000000..8036ca5b03 --- /dev/null +++ b/src/otpch.cpp @@ -0,0 +1,19 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" \ No newline at end of file diff --git a/src/otpch.h b/src/otpch.h new file mode 100644 index 0000000000..ff261e12a0 --- /dev/null +++ b/src/otpch.h @@ -0,0 +1,56 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifdef __OTPCH_H__ +#error "Precompiled header should only be included once." +#endif + +#define __OTPCH_H__ + +//#undef __USE_OTPCH__ + +// Definitions should be global. +#include "definitions.h" + +#if defined __WINDOWS__ || defined WIN32 +#include +#endif + +//libxml +#include +#include +#include +//boost +#include +#include +#include +#include +#include +#include +#include + +//std +//#include +//#include +//#include +//#include + +//otserv +#include "configmanager.h" +#include "thing.h" + diff --git a/src/otserv.cpp b/src/otserv.cpp new file mode 100644 index 0000000000..98da62719e --- /dev/null +++ b/src/otserv.cpp @@ -0,0 +1,443 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "definitions.h" +#include +#include "server.h" + +#include +#include +#include +#include +#include + +#include "otsystem.h" +#include "networkmessage.h" +#include "protocolgame.h" + +#include +#include +#include "game.h" + +#include "iologindata.h" +#include "iomarket.h" + +#if !defined(__WINDOWS__) +#include // for sigemptyset() +#endif + +#include "monsters.h" +#include "commands.h" +#include "outfit.h" +#include "vocation.h" +#include "scriptmanager.h" +#include "configmanager.h" + +#include "tools.h" +#include "ban.h" +#include "rsa.h" + +#include "protocolgame.h" +#include "protocolold.h" +#include "protocollogin.h" +#include "status.h" +#include "admin.h" +#include "globalevent.h" +#include "mounts.h" +#include "house.h" + +#include "databasemanager.h" + +Dispatcher g_dispatcher; +Scheduler g_scheduler; + +IPList serverIPs; + +extern AdminProtocolConfig* g_adminConfig; +Ban g_bans; +Game g_game; +Commands commands; +Npcs g_npcs; +ConfigManager g_config; +Monsters g_monsters; +Vocations g_vocations; +RSA g_RSA; + +OTSYS_THREAD_LOCKVAR g_loaderLock; +OTSYS_THREAD_SIGNALVAR g_loaderSignal; + +#include "networkmessage.h" + +void startupErrorMessage(const std::string& errorStr) +{ + std::cout << "> ERROR: " << errorStr << std::endl; + getchar(); + exit(-1); +} + +void mainLoader(int argc, char* argv[], ServiceManager* servicer); + +void badAllocationHandler() +{ + // Use functions that only use stack allocation + puts("Allocation failed, server out of memory.\nDecrease the size of your map or compile in 64 bits mode.\n"); + getchar(); + exit(-1); +} + +int main(int argc, char* argv[]) +{ + // Setup bad allocation handler + std::set_new_handler(badAllocationHandler); + +#ifndef WIN32 + // ignore sigpipe... + struct sigaction sigh; + sigh.sa_handler = SIG_IGN; + sigh.sa_flags = 0; + sigemptyset(&sigh.sa_mask); + sigaction(SIGPIPE, &sigh, NULL); +#endif + + OTSYS_THREAD_LOCKVARINIT(g_loaderLock); + OTSYS_THREAD_SIGNALVARINIT(g_loaderSignal); + + ServiceManager servicer; + + g_dispatcher.start(); + g_scheduler.start(); + + g_dispatcher.addTask(createTask(boost::bind(mainLoader, argc, argv, &servicer))); + + OTSYS_THREAD_LOCK(g_loaderLock, "main()"); + OTSYS_THREAD_WAITSIGNAL(g_loaderSignal, g_loaderLock); + OTSYS_SLEEP(1000); + + if (servicer.is_running()) { + std::cout << ">> " << g_config.getString(ConfigManager::SERVER_NAME) << " Server Online!" << std::endl << std::endl; + servicer.run(); + g_scheduler.join(); + g_dispatcher.join(); + } else { + std::cout << ">> No services running. The server is NOT online." << std::endl; + } + + return 0; +} + +void mainLoader(int argc, char* argv[], ServiceManager* services) +{ + //dispatcher thread + g_game.setGameState(GAME_STATE_STARTUP); + + srand((unsigned int)OTSYS_TIME()); +#ifdef WIN32 + SetConsoleTitle(STATUS_SERVER_NAME); +#endif + std::cout << STATUS_SERVER_NAME << " - Version " << STATUS_SERVER_VERSION << std::endl; + std::cout << "Compilied on " << __DATE__ << " " << __TIME__ << " for arch "; + +#if defined(__amd64__) || defined(_M_X64) + std::cout << "x64" << std::endl; +#elif defined(__i386__) || defined(_M_IX86) || defined(_X86_) + std::cout << "x86" << std::endl; +#else + std::cout << "unk" << std::endl; +#endif + + std::cout << std::endl; + + std::cout << "A server developed by " << STATUS_SERVER_DEVELOPERS << std::endl; + std::cout << "Visit our forum for updates, support, and resources: http://otland.net/." << std::endl; + std::cout << std::endl; + + // read global config + std::cout << ">> Loading config" << std::endl; + + if (!g_config.loadFile("config.lua")) { + startupErrorMessage("Unable to load config.lua!"); + return; + } + +#ifdef WIN32 + std::string defaultPriority = asLowerCaseString(g_config.getString(ConfigManager::DEFAULT_PRIORITY)); + + if (defaultPriority == "realtime") { + SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS); + } else if (defaultPriority == "high") { + SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); + } else if (defaultPriority == "higher") { + SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); + } + + std::ostringstream mutexName; + mutexName << "forgottenserver_" << g_config.getNumber(ConfigManager::LOGIN_PORT); + CreateMutex(NULL, FALSE, mutexName.str().c_str()); + + if (GetLastError() == ERROR_ALREADY_EXISTS) { + startupErrorMessage("Another instance of The Forgotten Server is already running with the same login port, please shut it down first or change ports for this one."); + } + +#endif + + //set RSA key + const char* p("14299623962416399520070177382898895550795403345466153217470516082934737582776038882967213386204600674145392845853859217990626450972452084065728686565928113"); + const char* q("7630979195970404721891201847792002125535401292779123937207447574596692788513647179235335529307251350570728407373705564708871762033017096809910315212884101"); + const char* d("46730330223584118622160180015036832148732986808519344675210555262940258739805766860224610646919605860206328024326703361630109888417839241959507572247284807035235569619173792292786907845791904955103601652822519121908367187885509270025388641700821735345222087940578381210879116823013776808975766851829020659073"); + g_RSA.setKey(p, q, d); + + std::cout << ">> Loading database driver..." << std::flush; + Database* db = Database::getInstance(); + + if (!db->isConnected()) { + startupErrorMessage("Failed to connect to database, please read doc/MYSQL_HELP for more information."); + return; + } + + std::cout << " MySQL " << db->getClientVersion() << std::endl; + + // run database manager + std::cout << ">> Running database manager" << std::endl; + DatabaseManager* dbManager = DatabaseManager::getInstance(); + + if (!dbManager->isDatabaseSetup()) { + startupErrorMessage("The database you have specified in config.lua is empty, please import the schema to the database."); + return; + } + + for (uint32_t version = dbManager->updateDatabase(); version != 0; version = dbManager->updateDatabase()) { + std::cout << "> Database has been updated to version " << version << "." << std::endl; + } + + dbManager->checkTriggers(); + dbManager->checkEncryption(); + + if (g_config.getBoolean(ConfigManager::OPTIMIZE_DATABASE) && !dbManager->optimizeTables()) { + std::cout << "> No tables were optimized." << std::endl; + } + + //load vocations + std::cout << ">> Loading vocations" << std::endl; + + if (!g_vocations.loadFromXml()) { + startupErrorMessage("Unable to load vocations!"); + } + + //load commands + std::cout << ">> Loading commands" << std::endl; + + if (!commands.loadFromXml()) { + startupErrorMessage("Unable to load commands!"); + } + + // load item data + std::cout << ">> Loading items" << std::endl; + + if (Item::items.loadFromOtb("data/items/items.otb")) { + startupErrorMessage("Unable to load items (OTB)!"); + } + + if (!Item::items.loadFromXml()) { + startupErrorMessage("Unable to load items (XML)!"); + } + + std::cout << ">> Loading script systems" << std::endl; + + if (!ScriptingManager::getInstance()->loadScriptSystems()) { + startupErrorMessage("Failed to load script systems"); + } + + std::cout << ">> Loading monsters" << std::endl; + + if (!g_monsters.loadFromXml()) { + startupErrorMessage("Unable to load monsters!"); + } + + std::cout << ">> Loading outfits" << std::endl; + Outfits* outfits = Outfits::getInstance(); + + if (!outfits->loadFromXml()) { + startupErrorMessage("Unable to load outfits!"); + } + + g_adminConfig = new AdminProtocolConfig(); + std::cout << ">> Loading admin protocol config" << std::endl; + + if (!g_adminConfig->loadXMLConfig()) { + startupErrorMessage("Unable to load admin protocol config!"); + } + + std::cout << ">> Loading experience stages" << std::endl; + + if (!g_game.loadExperienceStages()) { + startupErrorMessage("Unable to load experience stages!"); + } + + std::string passwordType = asLowerCaseString(g_config.getString(ConfigManager::PASSWORDTYPE)); + + if (passwordType == "md5") { + g_config.setNumber(ConfigManager::PASSWORD_TYPE, PASSWORD_TYPE_MD5); + std::cout << ">> Using MD5 passwords" << std::endl; + } else if (passwordType == "sha1") { + g_config.setNumber(ConfigManager::PASSWORD_TYPE, PASSWORD_TYPE_SHA1); + std::cout << ">> Using SHA1 passwords" << std::endl; + } else { + g_config.setNumber(ConfigManager::PASSWORD_TYPE, PASSWORD_TYPE_PLAIN); + std::cout << ">> Using plaintext passwords" << std::endl; + } + + std::cout << ">> Checking world type... "; + std::string worldType = asLowerCaseString(g_config.getString(ConfigManager::WORLD_TYPE)); + + if (worldType == "pvp") { + g_game.setWorldType(WORLD_TYPE_PVP); + } else if (worldType == "no-pvp") { + g_game.setWorldType(WORLD_TYPE_NO_PVP); + } else if (worldType == "pvp-enforced") { + g_game.setWorldType(WORLD_TYPE_PVP_ENFORCED); + } else { + std::cout << std::endl; + + std::ostringstream ss; + ss << "> ERROR: Unknown world type: " << g_config.getString(ConfigManager::WORLD_TYPE) << ", valid world types are: pvp, no-pvp and pvp-enforced."; + startupErrorMessage(ss.str()); + } + + std::cout << asUpperCaseString(worldType) << std::endl; + + std::cout << ">> Loading map" << std::endl; + + if (!g_game.loadMap(g_config.getString(ConfigManager::MAP_NAME))) { + startupErrorMessage("Failed to load map"); + } + + std::cout << ">> Initializing gamestate" << std::endl; + g_game.setGameState(GAME_STATE_INIT); + + // Tibia protocols + services->add(g_config.getNumber(ConfigManager::GAME_PORT)); + services->add(g_config.getNumber(ConfigManager::LOGIN_PORT)); + + // OT protocols + services->add(g_config.getNumber(ConfigManager::ADMIN_PORT)); + services->add(g_config.getNumber(ConfigManager::STATUS_PORT)); + + // Legacy protocols + services->add(g_config.getNumber(ConfigManager::LOGIN_PORT)); + services->add(g_config.getNumber(ConfigManager::LOGIN_PORT)); + + int32_t autoSaveEachMinutes = g_config.getNumber(ConfigManager::AUTO_SAVE_EACH_MINUTES); + + if (autoSaveEachMinutes > 0) { + g_scheduler.addEvent(createSchedulerTask(autoSaveEachMinutes * 1000 * 60, boost::bind(&Game::autoSave, &g_game))); + } + + if (g_config.getBoolean(ConfigManager::SERVERSAVE_ENABLED)) { + int32_t serverSaveHour = g_config.getNumber(ConfigManager::SERVERSAVE_H); + + if (serverSaveHour >= 0 && serverSaveHour <= 24) { + time_t timeNow = time(NULL); + tm* timeinfo = localtime(&timeNow); + + if (serverSaveHour == 0) { + serverSaveHour = 23; + } else { + serverSaveHour--; + } + + timeinfo->tm_hour = serverSaveHour; + timeinfo->tm_min = 55; + timeinfo->tm_sec = 0; + time_t difference = (time_t)difftime(mktime(timeinfo), timeNow); + + if (difference < 0) { + difference += 86400; + } + + g_scheduler.addEvent(createSchedulerTask(difference * 1000, boost::bind(&Game::prepareServerSave, &g_game))); + } + } + + Houses::getInstance().payHouses(); + IOLoginData::getInstance()->updateHouseOwners(); + g_npcs.reload(); + + if (g_config.getBoolean(ConfigManager::MARKET_ENABLED)) { + g_game.checkExpiredMarketOffers(); + IOMarket::getInstance()->updateStatistics(); + } + + std::cout << ">> Loaded all modules, server starting up..." << std::endl; + + std::pair IpNetMask; + IpNetMask.first = inet_addr("127.0.0.1"); + IpNetMask.second = 0xFFFFFFFF; + serverIPs.push_back(IpNetMask); + + char szHostName[128]; + if (gethostname(szHostName, 128) == 0) { + hostent* he = gethostbyname(szHostName); + + if (he) { + unsigned char** addr = (unsigned char**)he->h_addr_list; + + while (addr[0] != NULL) { + IpNetMask.first = *(uint32_t*)(*addr); + IpNetMask.second = 0xFFFFFFFF; + serverIPs.push_back(IpNetMask); + addr++; + } + } + } + + std::string ip; + ip = g_config.getString(ConfigManager::IP); + + uint32_t resolvedIp = inet_addr(ip.c_str()); + + if (resolvedIp == INADDR_NONE) { + struct hostent* he = gethostbyname(ip.c_str()); + + if (he != 0) { + resolvedIp = *(uint32_t*)he->h_addr; + } else { + std::ostringstream ss; + ss << "ERROR: Cannot resolve " << ip << "!" << std::endl; + startupErrorMessage(ss.str()); + } + } + + IpNetMask.first = resolvedIp; + IpNetMask.second = 0; + serverIPs.push_back(IpNetMask); + +#if !defined(WIN32) && !defined(__ROOT_PERMISSION__) + + if (getuid() == 0 || geteuid() == 0) { + std::cout << "> WARNING: " << STATUS_SERVER_NAME << " has been executed as root user, it is recommended to execute is as a normal user." << std::endl; + } + +#endif + + IOLoginData::getInstance()->resetOnlineStatus(); + g_game.start(services); + g_game.setGameState(GAME_STATE_NORMAL); + OTSYS_THREAD_SIGNAL_SEND(g_loaderSignal); +} diff --git a/src/otsystem.h b/src/otsystem.h new file mode 100644 index 0000000000..80a7389c0a --- /dev/null +++ b/src/otsystem.h @@ -0,0 +1,189 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_OTTHREAD_H__ +#define __OTSERV_OTTHREAD_H__ + +#include "logger.h" + +#include +#include +#include + +typedef std::vector< std::pair > IPList; + +#ifdef WIN32 +#ifdef __WIN_LOW_FRAG_HEAP__ +#define _WIN32_WINNT 0x0501 +#endif +#include +#include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include + +#include + +#define OTSYS_CREATE_THREAD(a, b) _beginthread(a, 0, b) + +#define OTSYS_THREAD_LOCKVAR CRITICAL_SECTION + +#define OTSYS_THREAD_LOCKVARINIT(a) InitializeCriticalSection(&a); +#define OTSYS_THREAD_LOCK(a, b) EnterCriticalSection(&a); +#define OTSYS_THREAD_UNLOCK(a, b) LeaveCriticalSection(&a); +#define OTSYS_THREAD_UNLOCK_PTR(a, b) LeaveCriticalSection(a); + +#define OTSYS_THREAD_TIMEOUT WAIT_TIMEOUT +#define OTSYS_THREAD_SIGNALVARINIT(a) a = CreateEvent(NULL, FALSE, FALSE, NULL) +#define OTSYS_THREAD_SIGNAL_SEND(a) SetEvent(a); + +typedef HANDLE OTSYS_THREAD_SIGNALVAR; + +inline int64_t OTSYS_TIME() +{ + _timeb t; + _ftime(&t); + return int64_t(t.millitm) + int64_t(t.time) * 1000; +} + +inline int OTSYS_THREAD_WAITSIGNAL(OTSYS_THREAD_SIGNALVAR& signal, OTSYS_THREAD_LOCKVAR& lock) +{ + //LeaveCriticalSection(&lock); + OTSYS_THREAD_UNLOCK(lock, "OTSYS_THREAD_WAITSIGNAL"); + WaitForSingleObject(signal, INFINITE); + //EnterCriticalSection(&lock); + OTSYS_THREAD_LOCK(lock, "OTSYS_THREAD_WAITSIGNAL"); + + return -0x4711; +} + +inline void OTSYS_SLEEP(uint32_t t) +{ + Sleep(t); +} + +inline int OTSYS_THREAD_WAITSIGNAL_TIMED(OTSYS_THREAD_SIGNALVAR& signal, OTSYS_THREAD_LOCKVAR& lock, int64_t cycle) +{ + int64_t tout64 = (cycle - OTSYS_TIME()); + + DWORD tout = 0; + + if (tout64 > 0) { + tout = (DWORD)(tout64); + } + + OTSYS_THREAD_UNLOCK(lock, "OTSYS_THREAD_WAITSIGNAL_TIMED"); + int ret = WaitForSingleObject(signal, tout); + OTSYS_THREAD_LOCK(lock, "OTSYS_THREAD_WAITSIGNAL_TIMED"); + + return ret; +} + +#else + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PTHREAD_MUTEX_RECURSIVE_NP PTHREAD_MUTEX_RECURSIVE + +inline void OTSYS_CREATE_THREAD(void * (*a)(void*), void* b) +{ + pthread_attr_t attr; + pthread_t id; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_create(&id, &attr, a, b); +} + +typedef pthread_mutex_t OTSYS_THREAD_LOCKVAR; + +inline void OTSYS_THREAD_LOCKVARINIT(OTSYS_THREAD_LOCKVAR& l) +{ + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP); + pthread_mutex_init(&l, &attr); +} + +#define OTSYS_THREAD_LOCK(a, b) pthread_mutex_lock(&a); +#define OTSYS_THREAD_UNLOCK(a, b) pthread_mutex_unlock(&a); +#define OTSYS_THREAD_UNLOCK_PTR(a, b) pthread_mutex_unlock(a); +#define OTSYS_THREAD_TIMEOUT ETIMEDOUT +#define OTSYS_THREAD_SIGNALVARINIT(a) pthread_cond_init(&a, NULL); +#define OTSYS_THREAD_SIGNAL_SEND(a) pthread_cond_signal(&a); + +typedef pthread_cond_t OTSYS_THREAD_SIGNALVAR; + +inline void OTSYS_SLEEP(int t) +{ + timespec tv; + tv.tv_sec = t / 1000; + tv.tv_nsec = (t % 1000) * 1000000; + nanosleep(&tv, NULL); +} + +inline int64_t OTSYS_TIME() +{ + timeb t; + ftime(&t); + return ((int64_t)t.millitm) + ((int64_t)t.time) * 1000; +} + +inline int OTSYS_THREAD_WAITSIGNAL(OTSYS_THREAD_SIGNALVAR& signal, OTSYS_THREAD_LOCKVAR& lock) +{ + return pthread_cond_wait(&signal, &lock); +} + +inline int OTSYS_THREAD_WAITSIGNAL_TIMED(OTSYS_THREAD_SIGNALVAR& signal, OTSYS_THREAD_LOCKVAR& lock, int64_t cycle) +{ + timespec tv; + tv.tv_sec = (int64_t)(cycle / 1000); + // tv_nsec is in nanoseconds while we only store microseconds... + tv.tv_nsec = (int64_t)(cycle % 1000) * 1000000; + return pthread_cond_timedwait(&signal, &lock, &tv); +} + +#endif + +class OTSYS_THREAD_LOCK_CLASS +{ + public: + inline OTSYS_THREAD_LOCK_CLASS(OTSYS_THREAD_LOCKVAR& a) { + mutex = &a; + OTSYS_THREAD_LOCK(a, NULL) + } + + inline ~OTSYS_THREAD_LOCK_CLASS() { + OTSYS_THREAD_UNLOCK_PTR(mutex, NULL) + } + + OTSYS_THREAD_LOCKVAR* mutex; +}; + +#endif // #ifndef __OTSYSTEM_H__ diff --git a/src/outfit.cpp b/src/outfit.cpp new file mode 100644 index 0000000000..d29c1ceddd --- /dev/null +++ b/src/outfit.cpp @@ -0,0 +1,217 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "definitions.h" +#include "outfit.h" +#include +#include +#include "creature.h" +#include "player.h" +#include "tools.h" + +OutfitList::OutfitList() +{ + // +} + +OutfitList::~OutfitList() +{ + for (OutfitListType::iterator it = m_list.begin(), end = m_list.end(); it != end; ++it) { + delete *it; + } + + m_list.clear(); +} + +void OutfitList::addOutfit(const Outfit& outfit) +{ + for (OutfitListType::iterator it = m_list.begin(), end = m_list.end(); it != end; ++it) { + if ((*it)->looktype == outfit.looktype) { + (*it)->addons = (*it)->addons | outfit.addons; + return; + } + } + + //adding a new outfit + Outfit* new_outfit = new Outfit; + new_outfit->looktype = outfit.looktype; + new_outfit->addons = outfit.addons; + new_outfit->premium = outfit.premium; + m_list.push_back(new_outfit); +} + +bool OutfitList::remOutfit(const Outfit& outfit) +{ + OutfitListType::iterator it; + + for (it = m_list.begin(); it != m_list.end(); ++it) { + if ((*it)->looktype == outfit.looktype) { + if (outfit.addons == 0xFF) { + delete *it; + m_list.erase(it); + } else { + (*it)->addons = (*it)->addons & (~outfit.addons); + } + + return true; + } + } + + return false; +} + +bool OutfitList::isInList(uint32_t looktype, uint32_t addons, bool playerPremium, int32_t playerSex) const +{ + OutfitListType::const_iterator it, it_; + const OutfitListType& global_outfits = Outfits::getInstance()->getOutfits(playerSex); + + for (it = global_outfits.begin(); it != global_outfits.end(); ++it) { + if ((*it)->looktype == looktype) { + for (it_ = m_list.begin(); it_ != m_list.end(); ++it_) { + if ((*it_)->looktype == looktype) { + if (((*it_)->addons & addons) == addons) { + if (((*it)->premium && playerPremium) || !(*it)->premium) { + return true; + } + } + + return false; + } + } + + return false; + } + } + + return false; +} + +Outfits::Outfits() +{ + Outfit outfit; + //build default outfit lists + outfit.addons = 0; + outfit.premium = false; + + for (int32_t i = PLAYER_FEMALE_1; i <= PLAYER_FEMALE_7; i++) { + outfit.looktype = i; + m_female_list.addOutfit(outfit); + } + + for (int32_t i = PLAYER_MALE_1; i <= PLAYER_MALE_7; i++) { + outfit.looktype = i; + m_male_list.addOutfit(outfit); + } + + m_list.resize(10, NULL); +} + +Outfits::~Outfits() +{ + for (OutfitsListVector::iterator it = m_list.begin(), end = m_list.end(); it != end; ++it) { + delete *it; + } + + m_list.clear(); +} + +bool Outfits::loadFromXml() +{ + std::string filename = "data/XML/outfits.xml"; + + xmlDocPtr doc = xmlParseFile(filename.c_str()); + + if (doc) { + xmlNodePtr root, p; + root = xmlDocGetRootElement(doc); + + if (xmlStrcmp(root->name, (const xmlChar*)"outfits") != 0) { + xmlFreeDoc(doc); + std::cout << "Warning: outfits.xml not found, using defaults." << std::endl; + return true; + } + + p = root->children; + + while (p) { + int32_t intVal; + + if (xmlStrcmp(p->name, (const xmlChar*)"outfit") == 0) { + if (readXMLInteger(p, "type", intVal)) { + if (intVal > 9 || intVal < 0) { + std::cout << "Warning: No valid outfit type " << intVal << std::endl; + } else { + OutfitList* list; + + if (m_list[intVal] != NULL) { + list = m_list[intVal]; + } else { + list = new OutfitList; + m_list[intVal] = list; + } + + Outfit outfit; + std::string outfitName; + bool outfitEnabled = true; + + readXMLString(p, "name", outfitName); + + if (readXMLInteger(p, "looktype", intVal)) { + outfit.looktype = intVal; + + if (readXMLInteger(p, "addons", intVal)) { + outfit.addons = intVal; + } else { + outfit.addons = 0; + } + + if (readXMLInteger(p, "premium", intVal)) { + outfit.premium = (intVal == 1); + } else { + outfit.premium = false; + } + + if (readXMLInteger(p, "enabled", intVal)) { + outfitEnabled = (intVal == 1); + } + + outfitNamesMap[outfit.looktype] = outfitName; + + if (outfitEnabled) { + //This way you can add names for outfits without adding them to default list + list->addOutfit(outfit); + } + } else { + std::cout << "[Warning] Missing looktype on outfit: " << outfitName << std::endl; + } + } + } else { + std::cout << "Missing outfit type." << std::endl; + } + } + + p = p->next; + } + + xmlFreeDoc(doc); + } + + return true; +} diff --git a/src/outfit.h b/src/outfit.h new file mode 100644 index 0000000000..efa40705a8 --- /dev/null +++ b/src/outfit.h @@ -0,0 +1,103 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_OUTFIT_H__ +#define __OTSERV_OUTFIT_H__ + +#include +#include +#include +#include +#include "enums.h" + +struct Outfit { + uint32_t looktype; + uint32_t addons; + bool premium; +}; + +typedef std::list OutfitListType; + +class OutfitList +{ + public: + OutfitList(); + ~OutfitList(); + + void addOutfit(const Outfit& outfit); + bool remOutfit(const Outfit& outfit); + const OutfitListType& getOutfits() const { + return m_list; + } + bool isInList(uint32_t looktype, uint32_t addons, bool playerPremium, int32_t playerSex) const; + + private: + OutfitListType m_list; +}; + +class Outfits +{ + public: + ~Outfits(); + + static Outfits* getInstance() { + static Outfits instance; + return &instance; + } + + bool loadFromXml(); + const OutfitListType& getOutfits(uint32_t type) { + return getOutfitList(type).getOutfits(); + } + + const OutfitList& getOutfitList(uint32_t type) { + if (type < m_list.size()) { + return *m_list[type]; + } else { + if (type == PLAYERSEX_FEMALE) { + return m_female_list; + } else { + return m_male_list; + } + } + } + + const std::string& getOutfitName(uint32_t looktype) const { + std::map::const_iterator it; + it = outfitNamesMap.find(looktype); + + if (it != outfitNamesMap.end()) { + return it->second; + } else { + static const std::string d = "Outfit"; + return d; + } + } + + private: + Outfits(); + typedef std::vector OutfitsListVector; + OutfitsListVector m_list; + + std::map outfitNamesMap; + + OutfitList m_female_list; + OutfitList m_male_list; +}; + +#endif diff --git a/src/outputmessage.cpp b/src/outputmessage.cpp new file mode 100644 index 0000000000..9cbce8280e --- /dev/null +++ b/src/outputmessage.cpp @@ -0,0 +1,213 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "outputmessage.h" +#include "protocol.h" +#include "scheduler.h" + +extern Dispatcher g_dispatcher; + +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ +uint32_t OutputMessagePool::OutputMessagePoolCount = OUTPUT_POOL_SIZE; +#endif + +OutputMessage::OutputMessage() +{ + freeMessage(); +} + +//*********** OutputMessagePool ****************// + +OutputMessagePool::OutputMessagePool() +{ + for (uint32_t i = 0; i < OUTPUT_POOL_SIZE; ++i) { + OutputMessage* msg = new OutputMessage(); + m_outputMessages.push_back(msg); + } + + m_frameTime = OTSYS_TIME(); +} + +void OutputMessagePool::startExecutionFrame() +{ + //boost::recursive_mutex::scoped_lock lockClass(m_outputPoolLock); + m_frameTime = OTSYS_TIME(); + m_isOpen = true; +} + +OutputMessagePool::~OutputMessagePool() +{ + InternalOutputMessageList::iterator it; + + for (it = m_outputMessages.begin(); it != m_outputMessages.end(); ++it) { + delete *it; + } + + m_outputMessages.clear(); +} + +void OutputMessagePool::send(OutputMessage_ptr msg) +{ + m_outputPoolLock.lock(); + OutputMessage::OutputMessageState state = msg->getState(); + m_outputPoolLock.unlock(); + + if (state == OutputMessage::STATE_ALLOCATED_NO_AUTOSEND) { + Connection_ptr connection = msg->getConnection(); + + if (connection && !connection->send(msg)) { + // Send only fails when connection is closing (or in error state) + // This call will free the message + msg->getProtocol()->onSendMessage(msg); + } + } +} + +void OutputMessagePool::sendAll() +{ + boost::recursive_mutex::scoped_lock lockClass(m_outputPoolLock); + + const int64_t dropTime = m_frameTime - 10000; + const int64_t frameTime = m_frameTime - 10; + + for (OutputMessageMessageList::const_iterator it = m_toAddQueue.begin(), end = m_toAddQueue.end(); it != end; ++it) { + OutputMessage_ptr omsg = *it; + const int64_t msgFrame = omsg->getFrame(); + + if (msgFrame >= dropTime) { + omsg->setState(OutputMessage::STATE_ALLOCATED); + + if (frameTime > msgFrame) { + m_autoSendOutputMessages.push_front(omsg); + } else { + m_autoSendOutputMessages.push_back(omsg); + } + } else { + //drop messages that are older than 10 seconds + omsg->getProtocol()->onSendMessage(omsg); + } + } + + m_toAddQueue.clear(); + + for (OutputMessageMessageList::iterator it = m_autoSendOutputMessages.begin(), end = m_autoSendOutputMessages.end(); it != end; it = m_autoSendOutputMessages.erase(it)) { + OutputMessage_ptr omsg = *it; + + if (frameTime <= omsg->getFrame()) { + break; + } + + Connection_ptr connection = omsg->getConnection(); + + if (connection && !connection->send(omsg)) { + // Send only fails when connection is closing (or in error state) + // This call will free the message + omsg->getProtocol()->onSendMessage(omsg); + } + } +} + +void OutputMessagePool::releaseMessage(OutputMessage* msg) +{ + g_dispatcher.addTask( + createTask(boost::bind(&OutputMessagePool::internalReleaseMessage, this, msg))); +} + +void OutputMessagePool::internalReleaseMessage(OutputMessage* msg) +{ + if (msg->getProtocol()) { + msg->getProtocol()->unRef(); + } else { + std::cout << "No protocol found." << std::endl; + } + + if (msg->getConnection()) { + msg->getConnection()->unRef(); + } else { + std::cout << "No connection found." << std::endl; + } + + msg->freeMessage(); + + m_outputPoolLock.lock(); + m_outputMessages.push_back(msg); + m_outputPoolLock.unlock(); +} + +OutputMessage_ptr OutputMessagePool::getOutputMessage(Protocol* protocol, bool autosend /*= true*/) +{ + if (!m_isOpen) { + return OutputMessage_ptr(); + } + + boost::recursive_mutex::scoped_lock lockClass(m_outputPoolLock); + + if (protocol->getConnection() == NULL) { + return OutputMessage_ptr(); + } + + if (m_outputMessages.empty()) { + OutputMessage* msg = new OutputMessage(); + m_outputMessages.push_back(msg); + +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + OutputMessagePoolCount++; +#endif + } + + OutputMessage_ptr outputmessage; + outputmessage.reset(m_outputMessages.back(), + boost::bind(&OutputMessagePool::releaseMessage, this, _1)); + + m_outputMessages.pop_back(); + + configureOutputMessage(outputmessage, protocol, autosend); + return outputmessage; +} + +void OutputMessagePool::configureOutputMessage(OutputMessage_ptr msg, Protocol* protocol, bool autosend) +{ + msg->Reset(); + + if (autosend) { + msg->setState(OutputMessage::STATE_ALLOCATED); + m_autoSendOutputMessages.push_back(msg); + } else { + msg->setState(OutputMessage::STATE_ALLOCATED_NO_AUTOSEND); + } + + Connection_ptr connection = protocol->getConnection(); + assert(connection != NULL); + + msg->setProtocol(protocol); + protocol->addRef(); + + msg->setConnection(connection); + connection->addRef(); + + msg->setFrame(m_frameTime); +} + +void OutputMessagePool::addToAutoSend(OutputMessage_ptr msg) +{ + m_outputPoolLock.lock(); + m_toAddQueue.push_back(msg); + m_outputPoolLock.unlock(); +} diff --git a/src/outputmessage.h b/src/outputmessage.h new file mode 100644 index 0000000000..ec8dd864fe --- /dev/null +++ b/src/outputmessage.h @@ -0,0 +1,214 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_OUTPUT_MESSAGE_H__ +#define __OTSERV_OUTPUT_MESSAGE_H__ + +#include "networkmessage.h" +#include "connection.h" +#include +#include +#include +#include "tools.h" + +#include + +#include + +class Protocol; + +#define OUTPUT_POOL_SIZE 100 + +class OutputMessage : public NetworkMessage, boost::noncopyable +{ + private: + OutputMessage(); + + public: + ~OutputMessage() {} + + char* getOutputBuffer() { + return (char*)&m_MsgBuf[m_outputBufferStart]; + } + + void writeMessageLength() { + add_header((uint16_t)(m_MsgSize)); + } + + void addCryptoHeader(bool addChecksum) { + if (addChecksum) { + add_header((uint32_t)(adlerChecksum((uint8_t*)(m_MsgBuf + m_outputBufferStart), m_MsgSize))); + } + + add_header((uint16_t)(m_MsgSize)); + } + + enum OutputMessageState { + STATE_FREE, + STATE_ALLOCATED, + STATE_ALLOCATED_NO_AUTOSEND, + STATE_WAITING + }; + + Protocol* getProtocol() { + return m_protocol; + } + Connection_ptr getConnection() { + return m_connection; + } + int64_t getFrame() const { + return m_frame; + } + + inline void append(const NetworkMessage& msg) { + int32_t msgLen = msg.getMessageLength(); + memcpy(m_MsgBuf + m_ReadPos, msg.getBuffer() + 8, msgLen); + m_MsgSize += msgLen; + m_ReadPos += msgLen; + } + + inline void append(OutputMessage_ptr msg) { + int32_t msgLen = msg->getMessageLength(); + memcpy(m_MsgBuf + m_ReadPos, msg->getBuffer() + 8, msgLen); + m_MsgSize += msgLen; + m_ReadPos += msgLen; + } + + void setFrame(int64_t frame) { + m_frame = frame; + } + + protected: + template + inline void add_header(T add) { + if ((int32_t)m_outputBufferStart - (int32_t)sizeof(T) < 0) { + std::cout << "Error: [OutputMessage::add_header] m_outputBufferStart(" << m_outputBufferStart << + ") < " << sizeof(T) << std::endl; + return; + } + + m_outputBufferStart = m_outputBufferStart - sizeof(T); + *(T*)(m_MsgBuf + m_outputBufferStart) = add; + //added header size to the message size + m_MsgSize = m_MsgSize + sizeof(T); + } + + void freeMessage() { + setConnection(Connection_ptr()); + setProtocol(NULL); + m_frame = 0; + //allocate enough size for headers + //2 bytes for unencrypted message size + //4 bytes for checksum + //2 bytes for encrypted message size + m_outputBufferStart = 8; + + //setState have to be the last one + setState(OutputMessage::STATE_FREE); + } + + friend class OutputMessagePool; + + void setProtocol(Protocol* protocol) { + m_protocol = protocol; + } + void setConnection(Connection_ptr connection) { + m_connection = connection; + } + + void setState(OutputMessageState state) { + m_state = state; + } + OutputMessageState getState() const { + return m_state; + } + + Protocol* m_protocol; + Connection_ptr m_connection; + + uint32_t m_outputBufferStart; + int64_t m_frame; + + OutputMessageState m_state; +}; + +typedef boost::shared_ptr OutputMessage_ptr; + +class OutputMessagePool +{ + private: + OutputMessagePool(); + + public: + ~OutputMessagePool(); + + static OutputMessagePool* getInstance() { + static OutputMessagePool instance; + return &instance; + } + +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + static uint32_t OutputMessagePoolCount; +#endif + + void send(OutputMessage_ptr msg); + void sendAll(); + void stop() { + m_isOpen = false; + } + OutputMessage_ptr getOutputMessage(Protocol* protocol, bool autosend = true); + void startExecutionFrame(); + + int64_t getFrameTime() const { + return m_frameTime; + } + +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + size_t getTotalMessageCount() const { + return OutputMessagePoolCount; + } +#else + size_t getTotalMessageCount() const { + return m_allOutputMessages.size(); + } +#endif + size_t getAvailableMessageCount() const { + return m_outputMessages.size(); + } + size_t getAutoMessageCount() const { + return m_autoSendOutputMessages.size(); + } + void addToAutoSend(OutputMessage_ptr msg); + + protected: + void configureOutputMessage(OutputMessage_ptr msg, Protocol* protocol, bool autosend); + void releaseMessage(OutputMessage* msg); + void internalReleaseMessage(OutputMessage* msg); + + typedef std::list InternalOutputMessageList; + typedef std::list OutputMessageMessageList; + + InternalOutputMessageList m_outputMessages; + InternalOutputMessageList m_allOutputMessages; + OutputMessageMessageList m_autoSendOutputMessages; + OutputMessageMessageList m_toAddQueue; + boost::recursive_mutex m_outputPoolLock; + int64_t m_frameTime; + bool m_isOpen; +}; +#endif diff --git a/src/party.cpp b/src/party.cpp new file mode 100644 index 0000000000..f00a8ec2ea --- /dev/null +++ b/src/party.cpp @@ -0,0 +1,537 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "party.h" +#include "player.h" +#include "game.h" +#include "chat.h" + +extern Game g_game; +extern Chat g_chat; + +Party::Party(Player* _leader) +{ + sharedExpActive = false; + sharedExpEnabled = false; + + if (_leader) { + setLeader(_leader); + _leader->setParty(this); + } +} + +Party::~Party() +{ + // +} + +void Party::disband() +{ + leader->setParty(NULL); + leader->sendClosePrivate(CHANNEL_PARTY); + g_game.updatePlayerShield(leader); + g_game.updatePlayerHelpers(leader); + leader->sendCreatureSkull(leader); + leader->sendTextMessage(MSG_INFO_DESCR, "Your party has been disbanded."); + + for (PlayerVector::iterator it = inviteList.begin(); it != inviteList.end(); ++it) { + (*it)->removePartyInvitation(this); + } + + inviteList.clear(); + + for (PlayerVector::iterator it = memberList.begin(); it != memberList.end(); ++it) { + Player* player = *it; + player->setParty(NULL); + player->sendClosePrivate(CHANNEL_PARTY); + player->sendTextMessage(MSG_INFO_DESCR, "Your party has been disbanded."); + } + + for (PlayerVector::iterator it = memberList.begin(); it != memberList.end(); ++it) { + Player* player = *it; + g_game.updatePlayerShield(player); + + for (PlayerVector::iterator it2 = memberList.begin(); it2 != memberList.end(); ++it2) { + (*it2)->sendCreatureSkull(player); + } + + player->sendCreatureSkull(leader); + leader->sendCreatureSkull(player); + g_game.updatePlayerHelpers(player); + } + + memberList.clear(); + + setLeader(NULL); + delete this; +} + +bool Party::leaveParty(Player* player) +{ + if (!player) { + return false; + } + + if (player->getParty() != this && leader != player) { + return false; + } + + bool missingLeader = false; + + if (leader == player) { + if (!memberList.empty()) { + if (memberList.size() == 1 && inviteList.empty()) { + missingLeader = true; + } else { + passPartyLeadership(memberList.front()); + } + } else { + missingLeader = true; + } + } + + //since we already passed the leadership, we remove the player from the list + PlayerVector::iterator it = std::find(memberList.begin(), memberList.end(), player); + + if (it != memberList.end()) { + memberList.erase(it); + } + + player->setParty(NULL); + player->sendClosePrivate(CHANNEL_PARTY); + g_game.updatePlayerShield(player); + g_game.updatePlayerHelpers(player); + + for (PlayerVector::const_iterator m_it = memberList.begin(); m_it != memberList.end(); ++m_it) { + (*m_it)->sendCreatureSkull(player); + player->sendPlayerPartyIcons(*m_it); + g_game.updatePlayerHelpers(*m_it); + } + + leader->sendCreatureSkull(player); + player->sendCreatureSkull(player); + player->sendPlayerPartyIcons(leader); + + player->sendTextMessage(MSG_INFO_DESCR, "You have left the party."); + + updateSharedExperience(); + clearPlayerPoints(player); + + std::ostringstream ss; + ss << player->getName() << " has left the party."; + broadcastPartyMessage(MSG_INFO_DESCR, ss.str()); + + if (missingLeader || disbandParty()) { + disband(); + } + + return true; +} + +bool Party::passPartyLeadership(Player* player) +{ + if (!player || leader == player || player->getParty() != this) { + return false; + } + + //Remove it before to broadcast the message correctly + PlayerVector::iterator it = std::find(memberList.begin(), memberList.end(), player); + + if (it != memberList.end()) { + memberList.erase(it); + } + + std::ostringstream ss; + ss << player->getName() << " is now the leader of the party."; + broadcastPartyMessage(MSG_INFO_DESCR, ss.str(), true); + + Player* oldLeader = leader; + setLeader(player); + + memberList.insert(memberList.begin(), oldLeader); + + updateSharedExperience(); + + for (PlayerVector::const_iterator m_it = memberList.begin(); m_it != memberList.end(); ++m_it) { + (*m_it)->sendCreatureShield(oldLeader); + (*m_it)->sendCreatureShield(leader); + } + + for (PlayerVector::iterator i_it = inviteList.begin(); i_it != inviteList.end(); ++i_it) { + (*i_it)->sendCreatureShield(oldLeader); + (*i_it)->sendCreatureShield(leader); + } + + leader->sendCreatureShield(oldLeader); + leader->sendCreatureShield(leader); + + player->sendTextMessage(MSG_INFO_DESCR, "You are now the leader of the party."); + return true; +} + +bool Party::joinParty(Player* player) +{ + if (!player || player->isRemoved()) { + return false; + } + + PlayerVector::iterator it = std::find(inviteList.begin(), inviteList.end(), player); + + if (it == inviteList.end()) { + return false; + } + + inviteList.erase(it); + + std::ostringstream ss; + ss << player->getName() << " has joined the party."; + broadcastPartyMessage(MSG_INFO_DESCR, ss.str()); + + player->setParty(this); + + g_game.updatePlayerShield(player); + + for (PlayerVector::iterator m_it = memberList.begin(); m_it != memberList.end(); ++m_it) { + (*m_it)->sendCreatureSkull(player); + player->sendPlayerPartyIcons(*m_it); + } + + player->sendCreatureSkull(player); + leader->sendCreatureSkull(player); + player->sendPlayerPartyIcons(leader); + + memberList.push_back(player); + + g_game.updatePlayerHelpers(player); + + player->removePartyInvitation(this); + updateSharedExperience(); + + const std::string& leaderName = leader->getName(); + ss.str(""); + ss << "You have joined " << leaderName << "'" << (leaderName[leaderName.length() - 1] == 's' ? "" : "s") << + " party. Open the party channel to communicate with your companions."; + player->sendTextMessage(MSG_INFO_DESCR, ss.str()); + return true; +} + +bool Party::removeInvite(Player* player) +{ + if (!player || player->isRemoved()) { + return false; + } + + PlayerVector::iterator it = std::find(inviteList.begin(), inviteList.end(), player); + + if (it == inviteList.end()) { + return false; + } + + inviteList.erase(it); + + leader->sendCreatureShield(player); + player->sendCreatureShield(leader); + + player->removePartyInvitation(this); + + if (disbandParty()) { + disband(); + } else { + for (PlayerVector::iterator it = memberList.begin(); it != memberList.end(); ++it) { + g_game.updatePlayerHelpers(*it); + } + + g_game.updatePlayerHelpers(leader); + } + + return true; +} + +void Party::revokeInvitation(Player* player) +{ + std::ostringstream ss; + ss << leader->getName() << " has revoked " << (leader->getSex() == PLAYERSEX_FEMALE ? "her" : "his") << " invitation."; + player->sendTextMessage(MSG_INFO_DESCR, ss.str()); + + ss.str(""); + ss << "Invitation for " << player->getName() << " has been revoked."; + leader->sendTextMessage(MSG_INFO_DESCR, ss.str()); + + removeInvite(player); +} + +bool Party::invitePlayer(Player* player) +{ + if (!player || player->isRemoved()) { + return false; + } + + if (isPlayerInvited(player)) { + return false; + } + + std::ostringstream ss; + ss << player->getName() << " has been invited."; + + if (memberList.empty() && inviteList.empty()) { + ss << " Open the party channel to communicate with your members."; + g_game.updatePlayerShield(leader); + leader->sendCreatureSkull(leader); + } + + leader->sendTextMessage(MSG_INFO_DESCR, ss.str()); + + inviteList.push_back(player); + + for (PlayerVector::iterator it = memberList.begin(); it != memberList.end(); ++it) { + g_game.updatePlayerHelpers(*it); + } + + g_game.updatePlayerHelpers(leader); + + leader->sendCreatureShield(player); + player->sendCreatureShield(leader); + + player->addPartyInvitation(this); + + ss.str(""); + ss << leader->getName() << " has invited you to " << (leader->getSex() == PLAYERSEX_FEMALE ? "her" : "his") << " party."; + player->sendTextMessage(MSG_INFO_DESCR, ss.str()); + return true; +} + +bool Party::isPlayerInvited(const Player* player) const +{ + PlayerVector::const_iterator it = std::find(inviteList.begin(), inviteList.end(), player); + return it != inviteList.end(); +} + +void Party::updateAllPartyIcons() +{ + for (PlayerVector::iterator it = memberList.begin(); it != memberList.end(); ++it) { + for (PlayerVector::iterator it2 = memberList.begin(); it2 != memberList.end(); ++it2) { + (*it)->sendCreatureShield(*it2); + } + + (*it)->sendCreatureShield(leader); + leader->sendCreatureShield(*it); + } + + leader->sendCreatureShield(leader); +} + +void Party::broadcastPartyMessage(MessageClasses msgClass, const std::string& msg, bool sendToInvitations /*= false*/) +{ + for (PlayerVector::const_iterator it = memberList.begin(); it != memberList.end(); ++it) { + (*it)->sendTextMessage(msgClass, msg); + } + + leader->sendTextMessage(msgClass, msg); + + if (sendToInvitations) { + for (PlayerVector::const_iterator it = inviteList.begin(); it != inviteList.end(); ++it) { + (*it)->sendTextMessage(msgClass, msg); + } + } +} + +void Party::broadcastPartyLoot(const std::string& loot) +{ + leader->sendTextMessage(MSG_INFO_DESCR, loot); + + for (PlayerVector::iterator it = memberList.begin(); it != memberList.end(); ++it) { + (*it)->sendTextMessage(MSG_INFO_DESCR, loot); + } +} + +void Party::updateSharedExperience() +{ + if (sharedExpActive) { + bool result = canEnableSharedExperience(); + + if (result != sharedExpEnabled) { + sharedExpEnabled = result; + updateAllPartyIcons(); + } + } +} + +bool Party::setSharedExperience(Player* player, bool _sharedExpActive) +{ + if (!player || leader != player) { + return false; + } + + if (sharedExpActive == _sharedExpActive) { + return true; + } + + sharedExpActive = _sharedExpActive; + + if (sharedExpActive) { + sharedExpEnabled = canEnableSharedExperience(); + + if (sharedExpEnabled) { + leader->sendTextMessage(MSG_INFO_DESCR, "Shared Experience is now active."); + } else { + leader->sendTextMessage(MSG_INFO_DESCR, "Shared Experience has been activated, but some members of your party are inactive."); + } + } else { + leader->sendTextMessage(MSG_INFO_DESCR, "Shared Experience has been deactivated."); + } + + updateAllPartyIcons(); + return true; +} + +void Party::shareExperience(uint64_t experience) +{ + uint32_t shareExperience = (uint64_t)std::ceil((((double)experience / (memberList.size() + 1)) + ((double)experience * 0.05))); + + for (PlayerVector::iterator it = memberList.begin(); it != memberList.end(); ++it) { + (*it)->onGainSharedExperience(shareExperience); + } + + leader->onGainSharedExperience(shareExperience); +} + +bool Party::canUseSharedExperience(const Player* player) const +{ + if (memberList.empty()) { + return false; + } + + uint32_t highestLevel = leader->getLevel(); + + for (PlayerVector::const_iterator it = memberList.begin(); it != memberList.end(); ++it) { + if ((*it)->getLevel() > highestLevel) { + highestLevel = (*it)->getLevel(); + } + } + + uint32_t minLevel = (int32_t)std::ceil(((float)(highestLevel) * 2) / 3); + + if (player->getLevel() < minLevel) { + return false; + } + + const Position& leaderPos = leader->getPosition(); + + const Position& memberPos = player->getPosition(); + + if (!Position::areInRange<30, 30, 1>(leaderPos, memberPos)) { + return false; + } + + if (!player->hasFlag(PlayerFlag_NotGainInFight)) { + //check if the player has healed/attacked anything recently + CountMap::const_iterator it = pointMap.find(player->getID()); + + if (it == pointMap.end()) { + return false; + } + + uint64_t timeDiff = OTSYS_TIME() - it->second.ticks; + + if (timeDiff > (uint64_t)g_game.getInFightTicks()) { + return false; + } + } + + return true; +} + +bool Party::canEnableSharedExperience() +{ + if (!canUseSharedExperience(leader)) { + return false; + } + + for (PlayerVector::iterator it = memberList.begin(); it != memberList.end(); ++it) { + if (!canUseSharedExperience(*it)) { + return false; + } + } + + return true; +} + +void Party::addPlayerHealedMember(Player* player, uint32_t points) +{ + if (!player->hasFlag(PlayerFlag_NotGainInFight)) { + if (points > 0) { + CountMap::iterator it = pointMap.find(player->getID()); + + if (it == pointMap.end()) { + CountBlock_t cb; + cb.ticks = OTSYS_TIME(); + cb.totalHeal = points; + cb.totalDamage = 0; + pointMap[player->getID()] = cb; + } else { + it->second.totalHeal += points; + it->second.ticks = OTSYS_TIME(); + } + + updateSharedExperience(); + } + } +} + +void Party::addPlayerDamageMonster(Player* player, uint32_t points) +{ + if (!player->hasFlag(PlayerFlag_NotGainInFight)) { + if (points > 0) { + CountMap::iterator it = pointMap.find(player->getID()); + + if (it == pointMap.end()) { + CountBlock_t cb; + cb.ticks = OTSYS_TIME(); + cb.totalDamage = points; + cb.totalHeal = 0; + pointMap[player->getID()] = cb; + } else { + it->second.totalDamage += points; + it->second.ticks = OTSYS_TIME(); + } + + updateSharedExperience(); + } + } +} + +void Party::clearPlayerPoints(Player* player) +{ + CountMap::iterator it = pointMap.find(player->getID()); + + if (it != pointMap.end()) { + pointMap.erase(it); + updateSharedExperience(); + } +} + +bool Party::canOpenCorpse(uint32_t ownerId) +{ + if (Player* player = g_game.getPlayerByID(ownerId)) { + return leader->getID() == ownerId || player->getParty() == this; + } + + return false; +} diff --git a/src/party.h b/src/party.h new file mode 100644 index 0000000000..2b99f031ee --- /dev/null +++ b/src/party.h @@ -0,0 +1,107 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __PARTY_H__ +#define __PARTY_H__ + +#include "player.h" +#include "monsters.h" + +class Player; +class Party; + +typedef std::vector PlayerVector; + +class Party +{ + public: + Party(Player* _leader); + virtual ~Party(); + + Player* getLeader() const { + return leader; + } + void setLeader(Player* _leader) { + leader = _leader; + } + PlayerVector& getMembers() { + return memberList; + } + const PlayerVector& getInvitees() const { + return inviteList; + } + size_t getMemberCount() const { + return memberList.size(); + } + size_t getInvitationCount() const { + return inviteList.size(); + } + + void disband(); + bool invitePlayer(Player* player); + bool joinParty(Player* player); + void revokeInvitation(Player* player); + bool passPartyLeadership(Player* player); + bool leaveParty(Player* player); + + bool removeInvite(Player* player); + + bool isPlayerInvited(const Player* player) const; + void updateAllPartyIcons(); + void broadcastPartyMessage(MessageClasses msgClass, const std::string& msg, bool sendToInvitations = false); + void broadcastPartyLoot(const std::string& loot); + bool disbandParty() { + return (memberList.empty() && inviteList.empty()); + } + bool canOpenCorpse(uint32_t ownerId); + + void shareExperience(uint64_t experience); + bool setSharedExperience(Player* player, bool _sharedExpActive); + bool isSharedExperienceActive() const { + return sharedExpActive; + } + bool isSharedExperienceEnabled() const { + return sharedExpEnabled; + } + bool canUseSharedExperience(const Player* player) const; + void updateSharedExperience(); + + void addPlayerHealedMember(Player* player, uint32_t points); + void addPlayerDamageMonster(Player* player, uint32_t points); + void clearPlayerPoints(Player* player); + + protected: + bool sharedExpActive; + bool sharedExpEnabled; + + Player* leader; + PlayerVector memberList; + PlayerVector inviteList; + + struct CountBlock_t { + int32_t totalHeal; + int32_t totalDamage; + int64_t ticks; + }; + typedef std::map CountMap; + CountMap pointMap; + + bool canEnableSharedExperience(); +}; + +#endif diff --git a/src/player.cpp b/src/player.cpp new file mode 100644 index 0000000000..8fc8df4ea7 --- /dev/null +++ b/src/player.cpp @@ -0,0 +1,5245 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "definitions.h" + +#include +#include +#include +#include +#include + +#include "player.h" +#include "iologindata.h" +#include "chat.h" +#include "house.h" +#include "combat.h" +#include "movement.h" +#include "weapons.h" +#include "town.h" +#include "ban.h" +#include "configmanager.h" +#include "creatureevent.h" +#include "status.h" +#include "beds.h" +#include "mounts.h" +#include "quests.h" +#include "outputmessage.h" + +extern ConfigManager g_config; +extern Game g_game; +extern Chat g_chat; +extern Vocations g_vocations; +extern MoveEvents* g_moveEvents; +extern Weapons* g_weapons; +extern CreatureEvents* g_creatureEvents; + +AutoList Player::listPlayer; +MuteCountMap Player::muteCountMap; +int32_t Player::maxMessageBuffer; + +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ +uint32_t Player::playerCount = 0; +#endif + +Player::Player(const std::string& _name, ProtocolGame* p) : + Creature() +{ + client = p; + isConnecting = false; + + if (client) { + client->setPlayer(this); + } + + depotChange = false; + accountNumber = 0; + name = _name; + setVocation(0); + capacity = 400.00; + mana = 0; + manaMax = 0; + manaSpent = 0; + soul = 0; + soulMax = 100; + guildLevel = 0; + guild = NULL; + + level = 1; + levelPercent = 0; + magLevelPercent = 0; + magLevel = 0; + experience = 0; + + damageImmunities = 0; + conditionImmunities = 0; + conditionSuppressions = 0; + accessLevel = false; + groupName = ""; + groupId = 0; + lastLoginSaved = 0; + lastLogout = 0; + lastIP = 0; + lastPing = OTSYS_TIME(); + lastPong = lastPing; + MessageBufferTicks = 0; + MessageBufferCount = 0; + nextAction = 0; + + windowTextId = 0; + writeItem = NULL; + maxWriteLen = 0; + + editHouse = NULL; + editListId = 0; + + shopOwner = NULL; + purchaseCallback = -1; + saleCallback = -1; + + pzLocked = false; + bloodHitCount = 0; + shieldBlockCount = 0; + lastAttackBlockType = BLOCK_NONE; + addAttackSkillPoint = false; + lastAttack = 0; + shootRange = 1; + + blessings = 0; + + mayNotMove = false; + + marketDepotId = -1; + lastDepotId = -1; + + chaseMode = CHASEMODE_STANDSTILL; + fightMode = FIGHTMODE_ATTACK; + + bedItem = NULL; + + tradePartner = NULL; + tradeState = TRADE_NONE; + tradeItem = NULL; + + walkTask = NULL; + walkTaskEvent = 0; + actionTaskEvent = 0; + nextStepEvent = 0; + + lastFailedFollow = 0; + lastWalkthroughAttempt = 0; + lastToggleMount = 0; + + for (int32_t i = 0; i < 11; i++) { + inventory[i] = NULL; + inventoryAbilities[i] = false; + } + + for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + skills[i][SKILL_LEVEL] = 10; + skills[i][SKILL_TRIES] = 0; + skills[i][SKILL_PERCENT] = 0; + } + + for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + varSkills[i] = 0; + } + + for (int32_t i = STAT_FIRST; i <= STAT_LAST; ++i) { + varStats[i] = 0; + } + + maxDepotLimit = 1000; + maxVipLimit = 20; + groupFlags = 0; + + sex = PLAYERSEX_FEMALE; + vocationId = 0; + + town = 0; + + accountType = ACCOUNT_TYPE_NORMAL; + premiumDays = 0; + + idleTime = 0; + + skullTicks = 0; + skull = SKULL_NONE; + setParty(NULL); + + groupId = 0; + + bankBalance = 0; + + inbox = new Inbox(ITEM_INBOX); + inbox->useThing2(); + + offlineTrainingSkill = -1; + offlineTrainingTime = 0; + lastStatsTrainingTime = 0; + + ghostMode = false; + requestedOutfit = false; + lagging = false; + moveItemsBuffer = 0; + lastMoveItemTime = 0; + + staminaMinutes = 2520; + nextUseStaminaTime = 0; + + lastQuestlogUpdate = 0; + + castingProtocol = NULL; + castPassword = ""; + +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + playerCount++; +#endif +} + +Player::~Player() +{ + for (int i = 0; i < 11; ++i) { + if (inventory[i]) { + inventory[i]->setParent(NULL); + inventory[i]->releaseThing2(); + inventory[i] = NULL; + inventoryAbilities[i] = false; + } + } + + for (DepotLockerMap::iterator it = depotLockerMap.begin(), end = depotLockerMap.end(); it != end; ++it) { + it->second->removeInbox(inbox); + it->second->releaseThing2(); + } + + inbox->releaseThing2(); + + //std::cout << "Player destructor " << this << std::endl; + + setWriteItem(NULL); + setEditHouse(NULL); + +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + playerCount--; +#endif +} + +void Player::setVocation(uint32_t vocId) +{ + vocationId = vocId; + vocation = g_vocations.getVocation(vocId); + + Condition* condition = getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT); + + if (condition) { + condition->setParam(CONDITIONPARAM_HEALTHGAIN, vocation->getHealthGainAmount()); + condition->setParam(CONDITIONPARAM_HEALTHTICKS, vocation->getHealthGainTicks() * 1000); + condition->setParam(CONDITIONPARAM_MANAGAIN, vocation->getManaGainAmount()); + condition->setParam(CONDITIONPARAM_MANATICKS, vocation->getManaGainTicks() * 1000); + } + + soulMax = vocation->getSoulMax(); +} + +bool Player::isPushable() const +{ + if (hasFlag(PlayerFlag_CannotBePushed)) { + return false; + } + + return Creature::isPushable(); +} + +std::string Player::getDescription(int32_t lookDistance) const +{ + std::ostringstream s; + + if (lookDistance == -1) { + s << "yourself."; + + if (accessLevel) { + s << " You are " << groupName << "."; + } else if (vocationId != VOCATION_NONE) { + s << " You are " << vocation->getVocDescription() << "."; + } else { + s << " You have no vocation."; + } + } else { + s << name; + + if (!accessLevel) { + s << " (Level " << level << ")"; + } + + s << "."; + + if (sex == PLAYERSEX_FEMALE) { + s << " She"; + } else { + s << " He"; + } + + if (accessLevel) { + s << " is " << groupName << "."; + } else if (vocationId != VOCATION_NONE) { + s << " is " << vocation->getVocDescription() << "."; + } else { + s << " has no vocation."; + } + } + + if (party) { + if (lookDistance == -1) { + s << " Your party has "; + } else if (sex == PLAYERSEX_FEMALE) { + s << " She is in a party with "; + } else { + s << " He is in a party with "; + } + + size_t memberCount = party->getMemberCount() + 1; + + if (memberCount == 1) { + s << "1 member and "; + } else { + s << memberCount << " members and "; + } + + size_t invitationCount = party->getInvitationCount(); + + if (invitationCount == 1) { + s << "1 pending invitation."; + } else { + s << invitationCount << " pending invitations."; + } + } + + if (guild) { + GuildRank* rank = guild->getRankByLevel(guildLevel); + + if (rank) { + if (lookDistance == -1) { + s << " You are "; + } else if (sex == PLAYERSEX_FEMALE) { + s << " She is "; + } else { + s << " He is "; + } + + s << rank->name << " of the " << guild->getName(); + + if (!guildNick.empty()) { + s << " (" << guildNick << ")"; + } + + size_t memberCount = guild->getMemberCount(); + + if (memberCount == 1) { + s << ", which has 1 member, " << guild->getMembersOnlineCount() << " of them online."; + } else { + s << ", which has " << memberCount << " members, " << guild->getMembersOnlineCount() << " of them online."; + } + } + } + + return s.str(); +} + +Item* Player::getInventoryItem(slots_t slot) const +{ + if (slot < SLOT_FIRST || slot >= SLOT_LAST) { + return NULL; + } + + return inventory[slot]; +} + +void Player::setConditionSuppressions(uint32_t conditions, bool remove) +{ + if (!remove) { + conditionSuppressions |= conditions; + } else { + conditionSuppressions &= ~conditions; + } +} + +Item* Player::getWeapon(bool ignoreAmmo /*= false*/) +{ + for (uint32_t slot = SLOT_RIGHT; slot <= SLOT_LEFT; slot++) { + Item* item = getInventoryItem((slots_t)slot); + + if (!item) { + continue; + } + + switch (item->getWeaponType()) { + case WEAPON_SWORD: + case WEAPON_AXE: + case WEAPON_CLUB: + case WEAPON_WAND: { + const Weapon* weapon = g_weapons->getWeapon(item); + + if (weapon) { + return item; + } + + break; + } + + case WEAPON_DIST: { + if (!ignoreAmmo && item->getAmmoType() != AMMO_NONE) { + Item* ammoItem = getInventoryItem(SLOT_AMMO); + + if (ammoItem && ammoItem->getAmmoType() == item->getAmmoType()) { + const Weapon* weapon = g_weapons->getWeapon(ammoItem); + + if (weapon) { + shootRange = item->getShootRange(); + return ammoItem; + } + } + } else { + const Weapon* weapon = g_weapons->getWeapon(item); + + if (weapon) { + shootRange = item->getShootRange(); + return item; + } + } + + break; + } + + default: + break; + } + } + + return NULL; +} + +WeaponType_t Player::getWeaponType() +{ + Item* item = getWeapon(); + + if (!item) { + return WEAPON_NONE; + } + + return item->getWeaponType(); +} + +int32_t Player::getWeaponSkill(const Item* item) const +{ + if (!item) { + return getSkill(SKILL_FIST, SKILL_LEVEL); + } + + WeaponType_t weaponType = item->getWeaponType(); + int32_t attackSkill; + + switch (weaponType) { + case WEAPON_SWORD: { + attackSkill = getSkill(SKILL_SWORD, SKILL_LEVEL); + break; + } + + case WEAPON_CLUB: { + attackSkill = getSkill(SKILL_CLUB, SKILL_LEVEL); + break; + } + + case WEAPON_AXE: { + attackSkill = getSkill(SKILL_AXE, SKILL_LEVEL); + break; + } + + case WEAPON_DIST: { + attackSkill = getSkill(SKILL_DIST, SKILL_LEVEL); + break; + } + + default: { + attackSkill = 0; + break; + } + } + + return attackSkill; +} + +int32_t Player::getArmor() const +{ + int32_t armor = 0; + + if (getInventoryItem(SLOT_HEAD)) { + armor += getInventoryItem(SLOT_HEAD)->getArmor(); + } + + if (getInventoryItem(SLOT_NECKLACE)) { + armor += getInventoryItem(SLOT_NECKLACE)->getArmor(); + } + + if (getInventoryItem(SLOT_ARMOR)) { + armor += getInventoryItem(SLOT_ARMOR)->getArmor(); + } + + if (getInventoryItem(SLOT_LEGS)) { + armor += getInventoryItem(SLOT_LEGS)->getArmor(); + } + + if (getInventoryItem(SLOT_FEET)) { + armor += getInventoryItem(SLOT_FEET)->getArmor(); + } + + if (getInventoryItem(SLOT_RING)) { + armor += getInventoryItem(SLOT_RING)->getArmor(); + } + + return int32_t(armor * vocation->armorMultipler); +} + +void Player::getShieldAndWeapon(const Item* &shield, const Item* &weapon) const +{ + Item* item; + shield = NULL; + weapon = NULL; + + for (uint32_t slot = SLOT_RIGHT; slot <= SLOT_LEFT; slot++) { + item = getInventoryItem((slots_t)slot); + + if (item) { + switch (item->getWeaponType()) { + case WEAPON_NONE: + break; + + case WEAPON_SHIELD: { + if (!shield || (shield && item->getDefense() > shield->getDefense())) { + shield = item; + } + + break; + } + + default: { // weapons that are not shields + weapon = item; + break; + } + } + } + } + + return; +} + +int32_t Player::getDefense() const +{ + int32_t baseDefense = 5; + int32_t defenseValue = 0; + int32_t defenseSkill = 0; + int32_t extraDefense = 0; + float defenseFactor = getDefenseFactor(); + const Item* weapon = NULL; + const Item* shield = NULL; + getShieldAndWeapon(shield, weapon); + + if (weapon) { + defenseValue = baseDefense + weapon->getDefense(); + extraDefense = weapon->getExtraDefense(); + defenseSkill = getWeaponSkill(weapon); + } + + if (shield && shield->getDefense() >= defenseValue) { + defenseValue = baseDefense + shield->getDefense() + extraDefense; + defenseSkill = getSkill(SKILL_SHIELD, SKILL_LEVEL); + } + + defenseValue = int32_t(defenseValue * vocation->defenseMultipler); + + if (defenseSkill == 0) { + return 0; + } + + return ((int32_t)std::ceil(((float)(defenseSkill * (defenseValue * 0.015)) + (defenseValue * 0.1)) * defenseFactor)); +} + +float Player::getAttackFactor() const +{ + switch (fightMode) { + case FIGHTMODE_ATTACK: + return 1.0f; + + case FIGHTMODE_BALANCED: + return 1.2f; + + case FIGHTMODE_DEFENSE: + return 2.0f; + + default: + return 1.0f; + } +} + +float Player::getDefenseFactor() const +{ + switch (fightMode) { + case FIGHTMODE_ATTACK: + return 1.0f; + + case FIGHTMODE_BALANCED: + return 1.2f; + + case FIGHTMODE_DEFENSE: { + if ((OTSYS_TIME() - lastAttack) < getAttackSpeed()) { + return 1.0f; + } + + return 2.0f; + } + + default: + return 1.0f; + } +} + +uint32_t Player::getClientIcons() const +{ + uint32_t icons = 0; + + for (ConditionList::const_iterator it = conditions.begin(); it != conditions.end(); ++it) { + if (!isSuppress((*it)->getType())) { + icons |= (*it)->getIcons(); + } + } + + if (pzLocked) { + icons |= ICON_REDSWORDS; + } + + if (_tile->hasFlag(TILESTATE_PROTECTIONZONE)) { + icons |= ICON_PIGEON; + + // Don't show ICON_SWORDS if player is in protection zone. + if (hasBitSet(ICON_SWORDS, icons)) { + icons &= ~ICON_SWORDS; + } + } + + if (!getCondition(CONDITION_REGENERATION)) { + icons |= ICON_HUNGRY; + } + + // Tibia client debugs with 10 or more icons + // so let's prevent that from happening. + std::bitset<20> icon_bitset((uint64_t)icons); + + for (size_t i = 0, size = icon_bitset.size(); i < size; ++i) { + if (icon_bitset.count() < 10) { + break; + } + + if (icon_bitset[i]) { + icon_bitset.reset(i); + } + } + + return icon_bitset.to_ulong(); +} + +void Player::updateInventoryWeight() +{ + if (!hasFlag(PlayerFlag_HasInfiniteCapacity)) { + inventoryWeight = 0.00; + + for (int i = SLOT_FIRST; i < SLOT_LAST; ++i) { + Item* item = getInventoryItem((slots_t)i); + + if (item) { + inventoryWeight += item->getWeight(); + } + } + } +} + +int32_t Player::getPlayerInfo(playerinfo_t playerinfo) const +{ + switch (playerinfo) { + case PLAYERINFO_LEVEL: + return level; + case PLAYERINFO_LEVELPERCENT: + return levelPercent; + case PLAYERINFO_MAGICLEVEL: + return std::max(0, magLevel + varStats[STAT_MAGICPOINTS]); + case PLAYERINFO_MAGICLEVELPERCENT: + return magLevelPercent; + case PLAYERINFO_HEALTH: + return health; + case PLAYERINFO_MAXHEALTH: + return std::max(1, healthMax + varStats[STAT_MAXHITPOINTS]); + case PLAYERINFO_MANA: + return mana; + case PLAYERINFO_MAXMANA: + return std::max(0, manaMax + varStats[STAT_MAXMANAPOINTS]); + case PLAYERINFO_SOUL: + return std::max(0, soul + varStats[STAT_SOULPOINTS]); + default: + return 0; + } +} + +int32_t Player::getSkill(skills_t skilltype, skillsid_t skillinfo) const +{ + int32_t n = skills[skilltype][skillinfo]; + + if (skillinfo == SKILL_LEVEL) { + n += varSkills[skilltype]; + } + + return std::max(0, n); +} + +void Player::addSkillAdvance(skills_t skill, uint32_t count) +{ + if (count == 0) { + return; + } + + uint64_t currReqTries = vocation->getReqSkillTries(skill, skills[skill][SKILL_LEVEL]); + uint64_t nextReqTries = vocation->getReqSkillTries(skill, skills[skill][SKILL_LEVEL] + 1); + + if (currReqTries >= nextReqTries) { + //player has reached max skill + return; + } + + bool sendUpdateSkills = false; + count *= g_config.getNumber(ConfigManager::RATE_SKILL); + + while ((skills[skill][SKILL_TRIES] + count) >= nextReqTries) { + count -= nextReqTries - skills[skill][SKILL_TRIES]; + skills[skill][SKILL_LEVEL]++; + skills[skill][SKILL_TRIES] = 0; + skills[skill][SKILL_PERCENT] = 0; + + std::ostringstream ss; + ss << "You advanced to " << getSkillName(skill) << " level " << skills[skill][SKILL_LEVEL] << "."; + sendTextMessage(MSG_EVENT_ADVANCE, ss.str()); + + g_creatureEvents->playerAdvance(this, skill, (skills[skill][SKILL_LEVEL] - 1), skills[skill][SKILL_LEVEL]); + + sendUpdateSkills = true; + currReqTries = nextReqTries; + nextReqTries = vocation->getReqSkillTries(skill, skills[skill][SKILL_LEVEL] + 1); + + if (currReqTries >= nextReqTries) { + count = 0; + break; + } + } + + skills[skill][SKILL_TRIES] += count; + + uint32_t newPercent; + + if (nextReqTries > currReqTries) { + newPercent = Player::getPercentLevel(skills[skill][SKILL_TRIES], nextReqTries); + } else { + newPercent = 0; + } + + if (skills[skill][SKILL_PERCENT] != newPercent) { + skills[skill][SKILL_PERCENT] = newPercent; + sendUpdateSkills = true; + } + + if (sendUpdateSkills) { + sendSkills(); + } +} + +void Player::setVarStats(stats_t stat, int32_t modifier) +{ + varStats[stat] += modifier; + + switch (stat) { + case STAT_MAXHITPOINTS: { + if (getHealth() > getMaxHealth()) { + Creature::changeHealth(getMaxHealth() - getHealth()); + } else { + g_game.addCreatureHealth(this); + } + + break; + } + + case STAT_MAXMANAPOINTS: { + if (getMana() > getMaxMana()) { + Creature::changeMana(getMaxMana() - getMana()); + } + + break; + } + + default: { + break; + } + } +} + +int32_t Player::getDefaultStats(stats_t stat) +{ + switch (stat) { + case STAT_MAXHITPOINTS: + return getMaxHealth() - getVarStats(STAT_MAXHITPOINTS); + case STAT_MAXMANAPOINTS: + return getMaxMana() - getVarStats(STAT_MAXMANAPOINTS); + case STAT_SOULPOINTS: + return getPlayerInfo(PLAYERINFO_SOUL) - getVarStats(STAT_SOULPOINTS); + case STAT_MAGICPOINTS: + return getBaseMagicLevel(); + default: + return 0; + } +} + +void Player::addContainer(uint8_t cid, Container* container) +{ + if (cid > 0xF) { + return; + } + + if (container->getID() == 460) { + container->useThing2(); + } + + ContainerMap::iterator it = openContainers.find(cid); + + if (it != openContainers.end()) { + Container* oldContainer = it->second.container; + + if (oldContainer->getID() == 460) { + oldContainer->releaseThing2(); + } + + it->second.container = container; + it->second.index = 0; + } else { + OpenContainer openContainer; + openContainer.container = container; + openContainer.index = 0; + openContainers[cid] = openContainer; + } +} + +void Player::closeContainer(uint8_t cid) +{ + ContainerMap::iterator it = openContainers.find(cid); + + if (it == openContainers.end()) { + return; + } + + OpenContainer openContainer = it->second; + Container* container = openContainer.container; + openContainers.erase(it); + + if (container && container->getID() == 460) { + container->releaseThing2(); + } +} + +void Player::setContainerIndex(uint8_t cid, uint16_t index) +{ + ContainerMap::iterator it = openContainers.find(cid); + + if (it == openContainers.end()) { + return; + } + + it->second.index = index; +} + +Container* Player::getContainer(uint8_t cid) +{ + ContainerMap::const_iterator it = openContainers.find(cid); + + if (it == openContainers.end()) { + return NULL; + } + + return it->second.container; +} + +int8_t Player::getContainerID(const Container* container) const +{ + for (ContainerMap::const_iterator it = openContainers.begin(), end = openContainers.end(); it != end; ++it) { + if (it->second.container == container) { + return it->first; + } + } + + return -1; +} + +uint16_t Player::getContainerIndex(uint8_t cid) const +{ + ContainerMap::const_iterator it = openContainers.find(cid); + + if (it == openContainers.end()) { + return 0; + } + + return it->second.index; +} + +bool Player::canOpenCorpse(uint32_t ownerId) +{ + return getID() == ownerId || (party && party->canOpenCorpse(ownerId)); +} + +uint16_t Player::getLookCorpse() const +{ + if (sex != 0) { + return ITEM_MALE_CORPSE; + } else { + return ITEM_FEMALE_CORPSE; + } +} + +uint16_t Player::getDropPercent() const +{ + uint16_t dropPercent; + std::bitset<5> bitset(blessings); + + switch (bitset.count()) { + case 1: + dropPercent = 70; + break; + + case 2: + dropPercent = 45; + break; + + case 3: + dropPercent = 25; + break; + + case 4: + dropPercent = 10; + break; + + case 5: + dropPercent = 0; + break; + + default: + dropPercent = 100; + break; + } + + return dropPercent; +} + +void Player::dropLoot(Container* corpse) +{ + if (corpse && lootDrop && vocationId != VOCATION_NONE) { + Skulls_t playerSkull = getSkull(); + + if (playerSkull != SKULL_NONE || level >= 100) { + if (inventory[SLOT_NECKLACE] && inventory[SLOT_NECKLACE]->getID() == ITEM_AMULETOFLOSS && playerSkull != SKULL_RED && playerSkull != SKULL_BLACK) { + Player* lastHitPlayer; + + if (_lastHitCreature) { + lastHitPlayer = _lastHitCreature->getPlayer(); + + if (!lastHitPlayer) { + Creature* lastHitMaster = _lastHitCreature->getMaster(); + + if (lastHitMaster) { + lastHitPlayer = lastHitMaster->getPlayer(); + } + } + } else { + lastHitPlayer = NULL; + } + + if (!lastHitPlayer || blessings < 32) { + g_game.internalRemoveItem(inventory[SLOT_NECKLACE], 1); + } + } else { + for (int32_t i = SLOT_FIRST; i < SLOT_LAST; ++i) { + Item* item = inventory[i]; + + if (item) { + if (playerSkull == SKULL_RED || playerSkull == SKULL_BLACK || random_range(1, (item->getContainer() ? 100 : 1000)) <= getDropPercent()) { + g_game.internalMoveItem(this, corpse, INDEX_WHEREEVER, item, item->getItemCount(), 0); + sendInventoryItem((slots_t)i, NULL); + } + } + } + } + } + } + + if (!inventory[SLOT_BACKPACK]) { + __internalAddThing(SLOT_BACKPACK, Item::CreateItem(1988)); + } +} + +void Player::addStorageValue(const uint32_t key, const int32_t value, const bool isLogin/* = false*/) +{ + if (IS_IN_KEYRANGE(key, RESERVED_RANGE)) { + if (IS_IN_KEYRANGE(key, OUTFITS_RANGE)) { + Outfit outfit; + outfit.looktype = value >> 16; + outfit.addons = value & 0xFF; + + if (outfit.addons > 3) { + std::cout << "Warning: No valid addons value key:" << key << " value: " << (int32_t)value << " player: " << getName() << std::endl; + } else { + m_playerOutfits.addOutfit(outfit); + } + + return; + } else if (IS_IN_KEYRANGE(key, MOUNTS_RANGE)) { + // do nothing + } else { + std::cout << "Warning: unknown reserved key: " << key << " player: " << getName() << std::endl; + return; + } + } + + if (value != -1) { + int32_t oldValue; + getStorageValue(key, oldValue); + + storageMap[key] = value; + + int64_t currentFrameTime = OutputMessagePool::getInstance()->getFrameTime(); + + if (!isLogin && lastQuestlogUpdate != currentFrameTime && Quests::getInstance()->isQuestStorage(key, value, oldValue)) { + lastQuestlogUpdate = currentFrameTime; + sendTextMessage(MSG_EVENT_ADVANCE, "Your questlog has been updated."); + } + } else { + storageMap.erase(key); + } +} + +bool Player::getStorageValue(const uint32_t key, int32_t& value) const +{ + StorageMap::const_iterator it = storageMap.find(key); + + if (it == storageMap.end()) { + value = -1; + return false; + } + + value = it->second; + return true; +} + +bool Player::canSee(const Position& pos) const +{ + if (client) { + return client->canSee(pos); + } + + return false; +} + +bool Player::canSeeCreature(const Creature* creature) const +{ + if (creature == this) { + return true; + } + + if (creature->isInGhostMode() && !accessLevel) { + return false; + } + + if (creature->isInvisible() && !creature->getPlayer() && !canSeeInvisibility()) { + return false; + } + + return true; +} + +bool Player::canWalkthrough(const Creature* creature) const +{ + if (accessLevel || creature->isInGhostMode()) { + return true; + } + + const Player* player = creature->getPlayer(); + + if (!player) { + return false; + } + + const Tile* playerTile = player->getTile(); + + if (playerTile && playerTile->hasFlag(TILESTATE_PROTECTIONZONE)) { + Item* playerTileGround = playerTile->ground; + + if (playerTileGround && playerTileGround->hasWalkStack()) { + Player* thisPlayer = const_cast(this); + + if ((OTSYS_TIME() - lastWalkthroughAttempt) > 2000) { + thisPlayer->setLastWalkthroughAttempt(OTSYS_TIME()); + return false; + } + + if (creature->getPosition() != lastWalkthroughPosition) { + thisPlayer->setLastWalkthroughPosition(creature->getPosition()); + return false; + } + + thisPlayer->setLastWalkthroughPosition(creature->getPosition()); + return true; + } + } + + return false; +} + +bool Player::canWalkthroughEx(const Creature* creature) const +{ + if (accessLevel) { + return true; + } + + const Player* player = creature->getPlayer(); + + if (!player) { + return false; + } + + const Tile* playerTile = player->getTile(); + + return playerTile && playerTile->hasFlag(TILESTATE_PROTECTIONZONE); +} + +void Player::onReceiveMail() +{ + if (isNearDepotBox()) { + sendTextMessage(MSG_EVENT_ADVANCE, "New mail has arrived."); + } +} + +bool Player::isNearDepotBox() +{ + Position pos = getPosition(); + + for (int32_t cx = -1; cx <= 1; ++cx) { + for (int32_t cy = -1; cy <= 1; ++cy) { + Tile* tile = g_game.getTile(pos.x + cx, pos.y + cy, pos.z); + + if (!tile) { + continue; + } + + if (tile->hasFlag(TILESTATE_DEPOT)) { + return true; + } + } + } + + return false; +} + +DepotChest* Player::getDepotChest(uint32_t depotId, bool autoCreate) +{ + DepotMap::iterator it = depotChests.find(depotId); + + if (it != depotChests.end()) { + return it->second; + } + + if (!autoCreate) { + return NULL; + } + + DepotChest* depotChest = new DepotChest(ITEM_DEPOT); + depotChest->useThing2(); + depotChest->setMaxDepotLimit(maxDepotLimit); + depotChests[depotId] = depotChest; + return depotChest; +} + +DepotLocker* Player::getDepotLocker(uint32_t depotId) +{ + DepotLockerMap::iterator it = depotLockerMap.find(depotId); + + if (it != depotLockerMap.end()) { + inbox->setParent(it->second); + return it->second; + } + + DepotLocker* depotLocker = new DepotLocker(ITEM_LOCKER1); + depotLocker->setDepotId(depotId); + depotLocker->__internalAddThing(Item::CreateItem(ITEM_MARKET)); + depotLocker->__internalAddThing(inbox); + depotLocker->__internalAddThing(getDepotChest(depotId, true)); + depotLockerMap[depotId] = depotLocker; + return depotLocker; +} + +void Player::sendCancelMessage(ReturnValue message) const +{ + switch (message) { + case RET_DESTINATIONOUTOFREACH: + sendCancel("Destination is out of reach."); + break; + + case RET_NOTMOVEABLE: + sendCancel("You cannot move this object."); + break; + + case RET_DROPTWOHANDEDITEM: + sendCancel("Drop the double-handed object first."); + break; + + case RET_BOTHHANDSNEEDTOBEFREE: + sendCancel("Both hands need to be free."); + break; + + case RET_CANNOTBEDRESSED: + sendCancel("You cannot dress this object there."); + break; + + case RET_PUTTHISOBJECTINYOURHAND: + sendCancel("Put this object in your hand."); + break; + + case RET_PUTTHISOBJECTINBOTHHANDS: + sendCancel("Put this object in both hands."); + break; + + case RET_CANONLYUSEONEWEAPON: + sendCancel("You may only use one weapon."); + break; + + case RET_TOOFARAWAY: + sendCancel("Too far away."); + break; + + case RET_FIRSTGODOWNSTAIRS: + sendCancel("First go downstairs."); + break; + + case RET_FIRSTGOUPSTAIRS: + sendCancel("First go upstairs."); + break; + + case RET_NOTENOUGHCAPACITY: + sendCancel("This object is too heavy for you to carry."); + break; + + case RET_CONTAINERNOTENOUGHROOM: + sendCancel("You cannot put more objects in this container."); + break; + + case RET_NEEDEXCHANGE: + case RET_NOTENOUGHROOM: + sendCancel("There is not enough room."); + break; + + case RET_CANNOTPICKUP: + sendCancel("You cannot take this object."); + break; + + case RET_CANNOTTHROW: + sendCancel("You cannot throw there."); + break; + + case RET_THEREISNOWAY: + sendCancel("There is no way."); + break; + + case RET_THISISIMPOSSIBLE: + sendCancel("This is impossible."); + break; + + case RET_PLAYERISPZLOCKED: + sendCancel("You can not enter a protection zone after attacking another player."); + break; + + case RET_PLAYERISNOTINVITED: + sendCancel("You are not invited."); + break; + + case RET_CREATUREDOESNOTEXIST: + sendCancel("Creature does not exist."); + break; + + case RET_DEPOTISFULL: + sendCancel("You cannot put more items in this depot."); + break; + + case RET_CANNOTUSETHISOBJECT: + sendCancel("You cannot use this object."); + break; + + case RET_PLAYERWITHTHISNAMEISNOTONLINE: + sendCancel("A player with this name is not online."); + break; + + case RET_NOTREQUIREDLEVELTOUSERUNE: + sendCancel("You do not have the required magic level to use this rune."); + break; + + case RET_YOUAREALREADYTRADING: + sendCancel("You are already trading."); + break; + + case RET_THISPLAYERISALREADYTRADING: + sendCancel("This player is already trading."); + break; + + case RET_YOUMAYNOTLOGOUTDURINGAFIGHT: + sendCancel("You may not logout during or immediately after a fight!"); + break; + + case RET_DIRECTPLAYERSHOOT: + sendCancel("You are not allowed to shoot directly on players."); + break; + + case RET_NOTENOUGHLEVEL: + sendCancel("You do not have enough level."); + break; + + case RET_NOTENOUGHMAGICLEVEL: + sendCancel("You do not have enough magic level."); + break; + + case RET_NOTENOUGHMANA: + sendCancel("You do not have enough mana."); + break; + + case RET_NOTENOUGHSOUL: + sendCancel("You do not have enough soul."); + break; + + case RET_YOUAREEXHAUSTED: + sendCancel("You are exhausted."); + break; + + case RET_CANONLYUSETHISRUNEONCREATURES: + sendCancel("You can only use this rune on creatures."); + break; + + case RET_PLAYERISNOTREACHABLE: + sendCancel("Player is not reachable."); + break; + + case RET_CREATUREISNOTREACHABLE: + sendCancel("Creature is not reachable."); + break; + + case RET_ACTIONNOTPERMITTEDINPROTECTIONZONE: + sendCancel("This action is not permitted in a protection zone."); + break; + + case RET_YOUMAYNOTATTACKTHISPLAYER: + sendCancel("You may not attack this player."); + break; + + case RET_YOUMAYNOTATTACKTHISCREATURE: + sendCancel("You may not attack this creature."); + break; + + case RET_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE: + sendCancel("You may not attack a person in a protection zone."); + break; + + case RET_YOUMAYNOTATTACKAPERSONWHILEINPROTECTIONZONE: + sendCancel("You may not attack a person while you are in a protection zone."); + break; + + case RET_YOUCANONLYUSEITONCREATURES: + sendCancel("You can only use it on creatures."); + break; + + case RET_TURNSECUREMODETOATTACKUNMARKEDPLAYERS: + sendCancel("Turn secure mode off if you really want to attack unmarked players."); + break; + + case RET_YOUNEEDPREMIUMACCOUNT: + sendCancel("You need a premium account."); + break; + + case RET_YOUNEEDTOLEARNTHISSPELL: + sendCancel("You need to learn this spell first."); + break; + + case RET_YOURVOCATIONCANNOTUSETHISSPELL: + sendCancel("Your vocation cannot use this spell."); + break; + + case RET_YOUNEEDAWEAPONTOUSETHISSPELL: + sendCancel("You need to equip a weapon to use this spell."); + break; + + case RET_PLAYERISPZLOCKEDLEAVEPVPZONE: + sendCancel("You can not leave a pvp zone after attacking another player."); + break; + + case RET_PLAYERISPZLOCKEDENTERPVPZONE: + sendCancel("You can not enter a pvp zone after attacking another player."); + break; + + case RET_ACTIONNOTPERMITTEDINANOPVPZONE: + sendCancel("This action is not permitted in a non pvp zone."); + break; + + case RET_YOUCANNOTLOGOUTHERE: + sendCancel("You can not logout here."); + break; + + case RET_YOUNEEDAMAGICITEMTOCASTSPELL: + sendCancel("You need a magic item to cast this spell."); + break; + + case RET_CANNOTCONJUREITEMHERE: + sendCancel("You cannot conjure items here."); + break; + + case RET_YOUNEEDTOSPLITYOURSPEARS: + sendCancel("You need to split your spears first."); + break; + + case RET_NAMEISTOOAMBIGIOUS: + sendCancel("Name is too ambigious."); + break; + + case RET_CANONLYUSEONESHIELD: + sendCancel("You may use only one shield."); + break; + + case RET_NOPARTYMEMBERSINRANGE: + sendCancel("No party members in range."); + break; + + case RET_YOUARENOTTHEOWNER: + sendCancel("You are not the owner."); + break; + + case RET_NOTPOSSIBLE: + default: + sendCancel("Sorry, not possible."); + break; + } +} + +void Player::sendStats() +{ + if (client) { + client->sendStats(); + lastStatsTrainingTime = getOfflineTrainingTime() / 60 / 1000; + } +} + +void Player::sendPing() +{ + int64_t timeNow = OTSYS_TIME(); + + if ((timeNow - lastPing) >= 5000) { + lastPing = timeNow; + + if (client) { + client->sendPing(); + } + } + + int64_t noPongTime = timeNow - lastPong; + + if (!lagging && noPongTime >= 7000) { + lagging = true; + } + + if (noPongTime >= 60000 && canLogout()) { + if (!client) { + g_creatureEvents->playerLogout(this); + g_game.removeCreature(this, true); + } else { + client->logout(true, true); + } + } +} + +Item* Player::getWriteItem(uint32_t& _windowTextId, uint16_t& _maxWriteLen) +{ + _windowTextId = windowTextId; + _maxWriteLen = maxWriteLen; + return writeItem; +} + +void Player::setWriteItem(Item* item, uint16_t _maxWriteLen /*= 0*/) +{ + windowTextId++; + + if (writeItem) { + writeItem->releaseThing2(); + } + + if (item) { + writeItem = item; + maxWriteLen = _maxWriteLen; + writeItem->useThing2(); + } else { + writeItem = NULL; + maxWriteLen = 0; + } +} + +House* Player::getEditHouse(uint32_t& _windowTextId, uint32_t& _listId) +{ + _windowTextId = windowTextId; + _listId = editListId; + return editHouse; +} + +void Player::setEditHouse(House* house, uint32_t listId /*= 0*/) +{ + windowTextId++; + editHouse = house; + editListId = listId; +} + +void Player::sendHouseWindow(House* house, uint32_t listId) const +{ + if (client) { + std::string text; + + if (house->getAccessList(listId, text)) { + client->sendHouseWindow(windowTextId, house, listId, text); + } + } +} + +//container +void Player::sendAddContainerItem(const Container* container, const Item* item) +{ + if (!client) { + return; + } + + for (ContainerMap::const_iterator it = openContainers.begin(), end = openContainers.end(); it != end; ++it) { + const OpenContainer& openContainer = it->second; + + if (openContainer.container != container) { + continue; + } + + uint16_t slot = openContainer.index; + + if (container->getID() == 460) { + uint16_t containerSize = container->size() - 1; + uint16_t pageEnd = openContainer.index + container->capacity(); + + if (containerSize > pageEnd) { + slot = pageEnd; + item = container->getItem(pageEnd); + } else { + slot = containerSize; + } + } else if (openContainer.index >= container->capacity()) { + item = container->getItem(openContainer.index - 1); + } + + client->sendAddContainerItem(it->first, slot, item); + } +} + +void Player::sendUpdateContainerItem(const Container* container, uint16_t slot, const Item* oldItem, const Item* newItem) +{ + if (!client) { + return; + } + + for (ContainerMap::const_iterator it = openContainers.begin(), end = openContainers.end(); it != end; ++it) { + const OpenContainer& openContainer = it->second; + + if (openContainer.container != container) { + continue; + } + + uint16_t pageEnd = openContainer.index + container->capacity(); + + if (slot >= openContainer.index && slot < pageEnd) { + client->sendUpdateContainerItem(it->first, slot, newItem); + } + } +} + +void Player::sendRemoveContainerItem(const Container* container, uint16_t slot, const Item* lastItem) +{ + if (!client) { + return; + } + + + for (ContainerMap::iterator it = openContainers.begin(), end = openContainers.end(); it != end; ++it) { + OpenContainer& openContainer = it->second; + + if (openContainer.container != container) { + continue; + } + + uint16_t& firstIndex = openContainer.index; + + if (firstIndex > 0 && firstIndex >= container->size() - 1) { + firstIndex -= container->capacity(); + sendContainer(it->first, container, false, firstIndex); + } + + client->sendRemoveContainerItem(it->first, std::max(slot, firstIndex), container->getItem(container->capacity() + firstIndex)); + } +} + +void Player::onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem, + const ItemType& oldType, const Item* newItem, const ItemType& newType) +{ + Creature::onUpdateTileItem(tile, pos, oldItem, oldType, newItem, newType); + + if (oldItem != newItem) { + onRemoveTileItem(tile, pos, oldType, oldItem); + } + + if (tradeState != TRADE_TRANSFER) { + if (tradeItem && oldItem == tradeItem) { + g_game.internalCloseTrade(this); + } + } +} + +void Player::onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType, + const Item* item) +{ + Creature::onRemoveTileItem(tile, pos, iType, item); + + if (tradeState != TRADE_TRANSFER) { + checkTradeState(item); + + if (tradeItem) { + const Container* container = item->getContainer(); + + if (container && container->isHoldingItem(tradeItem)) { + g_game.internalCloseTrade(this); + } + } + } +} + +void Player::onCreatureAppear(const Creature* creature, bool isLogin) +{ + Creature::onCreatureAppear(creature, isLogin); + + if (isLogin && creature == this) { + Item* item; + + for (int32_t slot = SLOT_FIRST; slot < SLOT_LAST; ++slot) { + if ((item = getInventoryItem((slots_t)slot))) { + item->__startDecaying(); + g_moveEvents->onPlayerEquip(this, item, (slots_t)slot, false); + } + } + + if (!storedConditionList.empty()) { + for (ConditionList::const_iterator it = storedConditionList.begin(); it != storedConditionList.end(); ++it) { + addCondition(*it); + } + + storedConditionList.clear(); + } + + BedItem* bed = Beds::getInstance().getBedBySleeper(getGUID()); + + if (bed) { + bed->wakeUp(this); + } + + std::cout << name << " has logged in." << std::endl; + + if (guild) { + guild->addMember(this); + } + + g_game.checkPlayersRecord(); + IOLoginData::getInstance()->updateOnlineStatus(guid, true); + } +} + +void Player::onAttackedCreatureDisappear(bool isLogout) +{ + sendCancelTarget(); + + if (!isLogout) { + sendTextMessage(MSG_STATUS_SMALL, "Target lost."); + } +} + +void Player::onFollowCreatureDisappear(bool isLogout) +{ + sendCancelTarget(); + + if (!isLogout) { + sendTextMessage(MSG_STATUS_SMALL, "Target lost."); + } +} + +void Player::onChangeZone(ZoneType_t zone) +{ + if (zone == ZONE_PROTECTION) { + if (attackedCreature && !hasFlag(PlayerFlag_IgnoreProtectionZone)) { + setAttackedCreature(NULL); + onAttackedCreatureDisappear(false); + } + + if (!accessLevel && isMounted()) { + dismount(); + g_game.internalCreatureChangeOutfit(this, defaultOutfit); + } + } + + g_game.updateCreatureWalkthrough(this); + sendIcons(); +} + +void Player::onAttackedCreatureChangeZone(ZoneType_t zone) +{ + if (zone == ZONE_PROTECTION) { + if (!hasFlag(PlayerFlag_IgnoreProtectionZone)) { + setAttackedCreature(NULL); + onAttackedCreatureDisappear(false); + } + } else if (zone == ZONE_NOPVP) { + if (attackedCreature->getPlayer()) { + if (!hasFlag(PlayerFlag_IgnoreProtectionZone)) { + setAttackedCreature(NULL); + onAttackedCreatureDisappear(false); + } + } + } else if (zone == ZONE_NORMAL) { + //attackedCreature can leave a pvp zone if not pzlocked + if (g_game.getWorldType() == WORLD_TYPE_NO_PVP) { + if (attackedCreature->getPlayer()) { + setAttackedCreature(NULL); + onAttackedCreatureDisappear(false); + } + } + } +} + +void Player::onCreatureDisappear(const Creature* creature, uint32_t stackpos, bool isLogout) +{ + Creature::onCreatureDisappear(creature, stackpos, isLogout); + + if (creature == this) { + if (isLogout) { + loginPosition = getPosition(); + } + + lastLogout = time(NULL); + + if (eventWalk != 0) { + setFollowCreature(NULL); + } + + if (tradePartner) { + g_game.internalCloseTrade(this); + } + + closeShopWindow(); + + clearPartyInvitations(); + + if (party) { + party->leaveParty(this); + } + + g_chat.removeUserFromAllChannels(this); + + std::cout << getName() << " has logged out." << std::endl; + + if (guild) { + guild->removeMember(this); + } + + IOLoginData::getInstance()->updateOnlineStatus(guid, false); + + bool saved = false; + + for (uint32_t tries = 0; tries < 3; ++tries) { + if (IOLoginData::getInstance()->savePlayer(this)) { + saved = true; + break; + } + } + + if (!saved) { + std::cout << "Error while saving player: " << getName() << std::endl; + } + } +} + +void Player::openShopWindow(Npc* npc, const std::list& shop) +{ + shopItemList = shop; + sendShop(npc); + sendSaleItemList(); +} + +void Player::closeShopWindow(bool sendCloseShopWindow /*= true*/) +{ + //unreference callbacks + int32_t onBuy; + int32_t onSell; + + Npc* npc = getShopOwner(onBuy, onSell); + + if (npc) { + setShopOwner(NULL, -1, -1); + npc->onPlayerEndTrade(this, onBuy, onSell); + + if (sendCloseShopWindow) { + sendCloseShop(); + } + } + + shopItemList.clear(); +} + +void Player::onWalk(Direction& dir) +{ + Creature::onWalk(dir); + setNextActionTask(NULL); + setNextAction(OTSYS_TIME() + getStepDuration(dir)); +} + +void Player::onCreatureMove(const Creature* creature, const Tile* newTile, const Position& newPos, + const Tile* oldTile, const Position& oldPos, bool teleport) +{ + Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos, teleport); + + if (hasFollowPath && (creature == followCreature || (creature == this && followCreature))) { + isUpdatingPath = false; + g_dispatcher.addTask(createTask(boost::bind(&Game::updateCreatureWalk, &g_game, getID()))); + } + + if (creature != this) { + return; + } + + if (tradeState != TRADE_TRANSFER) { + //check if we should close trade + if (tradeItem) { + if (!Position::areInRange<1, 1, 0>(tradeItem->getPosition(), getPosition())) { + g_game.internalCloseTrade(this); + } + } + + if (tradePartner) { + if (!Position::areInRange<2, 2, 0>(tradePartner->getPosition(), getPosition())) { + g_game.internalCloseTrade(this); + } + } + } + + // close modal windows + if (modalWindows.size() > 0) { + sendTextMessage(MSG_EVENT_ADVANCE, "Offline training aborted."); + modalWindows.clear(); + } + + // leave market + if (marketDepotId != -1) { + marketDepotId = -1; + } + + if (getParty()) { + getParty()->updateSharedExperience(); + } + + if (teleport || oldPos.z != newPos.z) { + int32_t ticks = g_config.getNumber(ConfigManager::STAIRHOP_DELAY); + + if (ticks > 0) { + addCombatExhaust(ticks); + addWeaponExhaust(ticks); + + if (Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_PACIFIED, ticks, 0)) { + addCondition(condition); + } + } + } +} + +//container +void Player::onAddContainerItem(const Container* container, const Item* item) +{ + checkTradeState(item); +} + +void Player::onUpdateContainerItem(const Container* container, uint16_t slot, + const Item* oldItem, const ItemType& oldType, const Item* newItem, const ItemType& newType) +{ + if (oldItem != newItem) { + onRemoveContainerItem(container, slot, oldItem); + } + + if (tradeState != TRADE_TRANSFER) { + checkTradeState(oldItem); + } +} + +void Player::onRemoveContainerItem(const Container* container, uint16_t slot, const Item* item) +{ + if (tradeState != TRADE_TRANSFER) { + checkTradeState(item); + + if (tradeItem) { + if (tradeItem->getParent() != container && container->isHoldingItem(tradeItem)) { + g_game.internalCloseTrade(this); + } + } + } +} + +void Player::onCloseContainer(const Container* container) +{ + if (!client) { + return; + } + + for (ContainerMap::const_iterator it = openContainers.begin(), end = openContainers.end(); it != end; ++it) { + if (it->second.container == container) { + client->sendCloseContainer(it->first); + } + } +} + +void Player::onSendContainer(const Container* container) +{ + if (!client) { + return; + } + + bool hasParent = container->hasParent(client->getVersion()); + + for (ContainerMap::const_iterator it = openContainers.begin(), end = openContainers.end(); it != end; ++it) { + const OpenContainer& openContainer = it->second; + + if (openContainer.container == container) { + client->sendContainer(it->first, container, hasParent, openContainer.index); + } + } +} + +//inventory +void Player::onAddInventoryItem(slots_t slot, Item* item) +{ + // +} + +void Player::onUpdateInventoryItem(slots_t slot, Item* oldItem, const ItemType& oldType, + Item* newItem, const ItemType& newType) +{ + if (oldItem != newItem) { + onRemoveInventoryItem(slot, oldItem); + } + + if (tradeState != TRADE_TRANSFER) { + checkTradeState(oldItem); + } +} + +void Player::onRemoveInventoryItem(slots_t slot, Item* item) +{ + if (tradeState != TRADE_TRANSFER) { + checkTradeState(item); + + if (tradeItem) { + const Container* container = item->getContainer(); + + if (container && container->isHoldingItem(tradeItem)) { + g_game.internalCloseTrade(this); + } + } + } +} + +void Player::checkTradeState(const Item* item) +{ + if (tradeItem && tradeState != TRADE_TRANSFER) { + if (tradeItem == item) { + g_game.internalCloseTrade(this); + } else { + const Container* container = dynamic_cast(item->getParent()); + + while (container != NULL) { + if (container == tradeItem) { + g_game.internalCloseTrade(this); + break; + } + + container = dynamic_cast(container->getParent()); + } + } + } +} + +void Player::setNextWalkActionTask(SchedulerTask* task) +{ + if (walkTaskEvent != 0) { + g_scheduler.stopEvent(walkTaskEvent); + walkTaskEvent = 0; + } + + delete walkTask; + walkTask = task; +} + +void Player::setNextWalkTask(SchedulerTask* task) +{ + if (nextStepEvent != 0) { + g_scheduler.stopEvent(nextStepEvent); + nextStepEvent = 0; + } + + if (task) { + nextStepEvent = g_scheduler.addEvent(task); + resetIdleTime(); + } +} + +void Player::setNextActionTask(SchedulerTask* task) +{ + if (actionTaskEvent != 0) { + g_scheduler.stopEvent(actionTaskEvent); + actionTaskEvent = 0; + } + + if (task) { + actionTaskEvent = g_scheduler.addEvent(task); + resetIdleTime(); + } +} + +uint32_t Player::getNextActionTime() const +{ + int64_t time = nextAction - OTSYS_TIME(); + + if (time < SCHEDULER_MINTICKS) { + return SCHEDULER_MINTICKS; + } + + return time; +} + +void Player::onThink(uint32_t interval) +{ + Creature::onThink(interval); + + sendPing(); + + MessageBufferTicks += interval; + + if (MessageBufferTicks >= 1500) { + MessageBufferTicks = 0; + addMessageBuffer(); + } + + if (!getTile()->hasFlag(TILESTATE_NOLOGOUT) && !mayNotMove && !isAccessPlayer()) { + idleTime += interval; + + if (idleTime > (g_config.getNumber(ConfigManager::KICK_AFTER_MINUTES) * 60000) + 60000) { + kickPlayer(true); + } else if (client && idleTime == 60000 * g_config.getNumber(ConfigManager::KICK_AFTER_MINUTES)) { + std::ostringstream ss; + ss << "You have been idle for " << g_config.getNumber(ConfigManager::KICK_AFTER_MINUTES) << " minutes. You will be disconnected in one minute if you are still idle then."; + client->sendTextMessage(MSG_STATUS_WARNING, ss.str()); + } + } + + if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { + checkSkullTicks(interval); + } + + if (moveItemsBuffer > 0) { + if (moveItemsBuffer > 6) { + moveItemsBuffer -= 6; + } else { + moveItemsBuffer = 0; + } + } + + addOfflineTrainingTime(interval); + + if (lastStatsTrainingTime != getOfflineTrainingTime() / 60 / 1000) { + sendStats(); + } +} + +uint32_t Player::isMuted() +{ + if (hasFlag(PlayerFlag_CannotBeMuted)) { + return 0; + } + + int32_t muteTicks = 0; + + for (ConditionList::iterator it = conditions.begin(); it != conditions.end(); ++it) { + if ((*it)->getType() == CONDITION_MUTED && (*it)->getTicks() > muteTicks) { + muteTicks = (*it)->getTicks(); + } + } + + return ((uint32_t)muteTicks / 1000); +} + +void Player::addMessageBuffer() +{ + if (MessageBufferCount > 0 && Player::maxMessageBuffer != 0 && !hasFlag(PlayerFlag_CannotBeMuted)) { + MessageBufferCount--; + } +} + +void Player::removeMessageBuffer() +{ + if (!hasFlag(PlayerFlag_CannotBeMuted) && MessageBufferCount <= Player::maxMessageBuffer + 1 && Player::maxMessageBuffer != 0) { + MessageBufferCount++; + + if (MessageBufferCount > Player::maxMessageBuffer) { + uint32_t muteCount = 1; + MuteCountMap::iterator it = muteCountMap.find(getGUID()); + + if (it != muteCountMap.end()) { + muteCount = it->second; + } + + uint32_t muteTime = 5 * muteCount * muteCount; + muteCountMap[getGUID()] = muteCount + 1; + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_MUTED, muteTime * 1000, 0); + addCondition(condition); + + std::ostringstream ss; + ss << "You are muted for " << muteTime << " seconds."; + sendTextMessage(MSG_STATUS_SMALL, ss.str()); + } + } +} + +void Player::drainHealth(Creature* attacker, CombatType_t combatType, int32_t damage) +{ + Creature::drainHealth(attacker, combatType, damage); + sendStats(); +} + +void Player::drainMana(Creature* attacker, int32_t manaLoss) +{ + Creature::drainMana(attacker, manaLoss); + sendStats(); +} + +void Player::addManaSpent(uint64_t amount, bool withMultiplier /*= true*/) +{ + if (amount == 0 || hasFlag(PlayerFlag_NotGainMana)) { + return; + } + + uint64_t currReqMana = vocation->getReqMana(magLevel); + uint64_t nextReqMana = vocation->getReqMana(magLevel + 1); + + if (currReqMana >= nextReqMana) { + //player has reached max magic level + return; + } + + if (withMultiplier) { + amount *= g_config.getNumber(ConfigManager::RATE_MAGIC); + } + + bool sendUpdateStats = false; + + while ((manaSpent + amount) >= nextReqMana) { + amount -= nextReqMana - manaSpent; + + magLevel++; + manaSpent = 0; + + std::ostringstream ss; + ss << "You advanced to magic level " << magLevel << "."; + sendTextMessage(MSG_EVENT_ADVANCE, ss.str()); + + g_creatureEvents->playerAdvance(this, SKILL__MAGLEVEL, magLevel - 1, magLevel); + + sendUpdateStats = true; + currReqMana = nextReqMana; + nextReqMana = vocation->getReqMana(magLevel + 1); + + if (currReqMana >= nextReqMana) { + amount = 0; + return; + } + } + + manaSpent += amount; + + uint32_t newPercent; + + if (nextReqMana > currReqMana) { + newPercent = Player::getPercentLevel(manaSpent, nextReqMana); + } else { + newPercent = 0; + } + + if (newPercent != magLevelPercent) { + magLevelPercent = newPercent; + sendUpdateStats = true; + } + + if (sendUpdateStats) { + sendStats(); + } +} + +void Player::addExperience(uint64_t exp, bool useMult/* = false*/, bool sendText/* = false*/, bool applyStaminaChange/* = false*/) +{ + int32_t newLevel = level; + + uint64_t nextLevelExp = Player::getExpForLevel(newLevel + 1); + + if (Player::getExpForLevel(newLevel) >= nextLevelExp) { + //player has reached max level + levelPercent = 0; + sendStats(); + return; + } + + if (useMult) { + exp *= g_game.getExperienceStage(level); + } + + if (applyStaminaChange && g_config.getBoolean(ConfigManager::STAMINA_SYSTEM)) { + if (staminaMinutes > 2400) { + if (isPremium()) { + exp *= 1.5; + } + } else if (staminaMinutes <= 840) { + exp *= 0.5; + } + } + + experience += exp; + + if (sendText) { + const Position& targetPos = getPosition(); + + std::ostringstream ss; + ss << "You gained " << exp << " experience points."; + sendExperienceMessage(MSG_EXPERIENCE, ss.str(), targetPos, exp, TEXTCOLOR_WHITE_EXP); + + std::ostringstream ssExp; + ssExp << getNameDescription() << " gained " << exp << " experience points."; + std::string strExp = ssExp.str(); + + SpectatorVec list; + g_game.getSpectators(list, targetPos, false, true); + + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + Player* tmpPlayer = (*it)->getPlayer(); + + if (tmpPlayer != this) { + tmpPlayer->sendExperienceMessage(MSG_EXPERIENCE_OTHERS, strExp, targetPos, exp, TEXTCOLOR_WHITE_EXP); + } + } + } + + while (experience >= nextLevelExp) { + ++newLevel; + healthMax += vocation->getHPGain(); + health += vocation->getHPGain(); + manaMax += vocation->getManaGain(); + mana += vocation->getManaGain(); + capacity += vocation->getCapGain(); + nextLevelExp = Player::getExpForLevel(newLevel + 1); + + if (Player::getExpForLevel(newLevel) >= nextLevelExp) { + //player has reached max level + break; + } + } + + int32_t prevLevel = level; + + if (prevLevel != newLevel) { + level = newLevel; + + health = healthMax; + mana = manaMax; + + updateBaseSpeed(); + + int32_t newSpeed = getBaseSpeed(); + setBaseSpeed(newSpeed); + + g_game.changeSpeed(this, 0); + g_game.addCreatureHealth(this); + + if (getParty()) { + getParty()->updateSharedExperience(); + } + + g_creatureEvents->playerAdvance(this, SKILL__LEVEL, prevLevel, newLevel); + + std::ostringstream ss; + ss << "You advanced from Level " << prevLevel << " to Level " << newLevel << "."; + sendTextMessage(MSG_EVENT_ADVANCE, ss.str()); + } + + uint64_t currLevelExp = Player::getExpForLevel(level); + nextLevelExp = Player::getExpForLevel(level + 1); + + if (nextLevelExp > currLevelExp) { + uint32_t newPercent = Player::getPercentLevel(experience - currLevelExp, Player::getExpForLevel(level + 1) - currLevelExp); + levelPercent = newPercent; + } else { + levelPercent = 0; + } + + sendStats(); +} + +uint32_t Player::getPercentLevel(uint64_t count, uint64_t nextLevelCount) +{ + if (nextLevelCount == 0) { + return 0; + } + + uint32_t result = (count * 100) / nextLevelCount; + + if (result > 100) { + return 0; + } + + return result; +} + +void Player::onBlockHit(BlockType_t blockType) +{ + if (shieldBlockCount > 0) { + --shieldBlockCount; + + if (hasShield()) { + addSkillAdvance(SKILL_SHIELD, 1); + } + } +} + +void Player::onAttackedCreatureBlockHit(Creature* target, BlockType_t blockType) +{ + Creature::onAttackedCreatureBlockHit(target, blockType); + + lastAttackBlockType = blockType; + + switch (blockType) { + case BLOCK_NONE: { + addAttackSkillPoint = true; + bloodHitCount = 30; + shieldBlockCount = 30; + break; + } + + case BLOCK_DEFENSE: + case BLOCK_ARMOR: { + //need to draw blood every 30 hits + if (bloodHitCount > 0) { + addAttackSkillPoint = true; + --bloodHitCount; + } else { + addAttackSkillPoint = false; + } + + break; + } + + default: { + addAttackSkillPoint = false; + break; + } + } +} + +bool Player::hasShield() const +{ + bool result = false; + Item* item; + + item = getInventoryItem(SLOT_LEFT); + + if (item && item->getWeaponType() == WEAPON_SHIELD) { + result = true; + } + + item = getInventoryItem(SLOT_RIGHT); + + if (item && item->getWeaponType() == WEAPON_SHIELD) { + result = true; + } + + return result; +} + +BlockType_t Player::blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, + bool checkDefense /* = false*/, bool checkArmor /* = false*/) +{ + BlockType_t blockType = Creature::blockHit(attacker, combatType, damage, checkDefense, checkArmor); + + if (attacker) { + sendCreatureSquare(attacker, SQ_COLOR_BLACK); + } + + if (blockType != BLOCK_NONE) { + return blockType; + } + + if (damage > 0) { + for (int32_t slot = SLOT_FIRST; slot < SLOT_LAST; ++slot) { + if (!isItemAbilityEnabled((slots_t)slot)) { + continue; + } + + Item* item = getInventoryItem((slots_t)slot); + + if (!item) { + continue; + } + + const ItemType& it = Item::items[item->getID()]; + + if (it.abilities) { + const int16_t& absorbPercent = it.abilities->absorbPercent[combatTypeToIndex(combatType)]; + + if (absorbPercent != 0) { + damage -= std::ceil(damage * (absorbPercent / 100.)); + + if (item->hasCharges()) { + g_game.transformItem(item, item->getID(), std::max(0, item->getCharges() - 1)); + } + } + } + } + + if (damage <= 0) { + damage = 0; + blockType = BLOCK_DEFENSE; + } + } + + return blockType; +} + +uint32_t Player::getIP() const +{ + if (client) { + return client->getIP(); + } + + return 0; +} + +void Player::death() +{ + loginPosition = masterPos; + + if (skillLoss) { + uint8_t unfairFightReduction = 100; + + if (_lastHitCreature) { + Player* lastHitPlayer = _lastHitCreature->getPlayer(); + + if (!lastHitPlayer) { + Creature* lastHitMaster = _lastHitCreature->getMaster(); + + if (lastHitMaster) { + lastHitPlayer = lastHitMaster->getPlayer(); + } + } + + if (lastHitPlayer) { + uint32_t sumLevels = 0; + for (CountMap::const_iterator it = damageMap.begin(), end = damageMap.end(); it != end; ++it) { + CountBlock_t cb = it->second; + if ((OTSYS_TIME() - cb.ticks) <= g_game.getInFightTicks()) { + Player* damageDealer = g_game.getPlayerByID(it->first); + if (damageDealer) { + sumLevels += damageDealer->getLevel(); + } + } + } + + if (sumLevels > level) { + double reduce = level / (double)sumLevels; + unfairFightReduction = std::max(20, std::floor((reduce * 100) + 0.5)); + } + } + } + + //Magic level loss + uint64_t sumMana = 0; + uint64_t lostMana = 0; + + //sum up all the mana + for (uint32_t i = 1; i <= magLevel; ++i) { + sumMana += vocation->getReqMana(i); + } + + sumMana += manaSpent; + + double deathLossPercent = getLostPercent() * (unfairFightReduction / 100.); + + lostMana = (uint64_t)(sumMana * deathLossPercent); + + while (lostMana > manaSpent && magLevel > 0) { + lostMana -= manaSpent; + manaSpent = vocation->getReqMana(magLevel); + magLevel--; + } + + manaSpent -= lostMana; + uint64_t nextReqMana = vocation->getReqMana(magLevel + 1); + + if (nextReqMana > vocation->getReqMana(magLevel)) { + magLevelPercent = Player::getPercentLevel(manaSpent, nextReqMana); + } else { + magLevelPercent = 0; + } + + //Skill loss + for (int16_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { //for each skill + uint32_t sumSkillTries = 0; + + for (uint32_t c = 11; c <= skills[i][SKILL_LEVEL]; ++c) { //sum up all required tries for all skill levels + sumSkillTries += vocation->getReqSkillTries(i, c); + } + + sumSkillTries += skills[i][SKILL_TRIES]; + + uint32_t lostSkillTries = (uint32_t)(sumSkillTries * deathLossPercent); + + while (lostSkillTries > skills[i][SKILL_TRIES]) { + lostSkillTries -= skills[i][SKILL_TRIES]; + + if (skills[i][SKILL_LEVEL] <= 10) { + skills[i][SKILL_LEVEL] = 10; + skills[i][SKILL_TRIES] = 0; + lostSkillTries = 0; + break; + } + + skills[i][SKILL_TRIES] = vocation->getReqSkillTries(i, skills[i][SKILL_LEVEL]); + skills[i][SKILL_LEVEL]--; + } + + skills[i][SKILL_TRIES] = std::max(0, skills[i][SKILL_TRIES] - lostSkillTries); + skills[i][SKILL_PERCENT] = Player::getPercentLevel(skills[i][SKILL_TRIES], vocation->getReqSkillTries(i, skills[i][SKILL_LEVEL])); + } + + // + + //Level loss + uint32_t oldLevel = level; + + if (vocationId == VOCATION_NONE || level > 7) { + experience -= (uint64_t)(experience * deathLossPercent); + } + + while (level > 1 && experience < Player::getExpForLevel(level)) { + --level; + healthMax = std::max(0, healthMax - vocation->getHPGain()); + manaMax = std::max(0, manaMax - vocation->getManaGain()); + capacity = std::max(0.00, capacity - vocation->getCapGain()); + } + + if (oldLevel != level) { + std::ostringstream ss; + ss << "You were downgraded from Level " << oldLevel << " to Level " << level << "."; + sendTextMessage(MSG_EVENT_ADVANCE, ss.str()); + } + + uint64_t currLevelExp = Player::getExpForLevel(level); + uint64_t nextLevelExp = Player::getExpForLevel(level + 1); + + if (nextLevelExp > currLevelExp) { + levelPercent = Player::getPercentLevel(experience - currLevelExp, nextLevelExp - currLevelExp); + } else { + levelPercent = 0; + } + + std::bitset<6> bitset(blessings); + + if (bitset[5]) { + Player* lastHitPlayer; + + if (_lastHitCreature) { + lastHitPlayer = _lastHitCreature->getPlayer(); + + if (!lastHitPlayer) { + Creature* lastHitMaster = _lastHitCreature->getMaster(); + + if (lastHitMaster) { + lastHitPlayer = lastHitMaster->getPlayer(); + } + } + } else { + lastHitPlayer = NULL; + } + + if (lastHitPlayer) { + bitset.reset(5); + blessings = bitset.to_ulong(); + } else { + blessings = 32; + } + } else { + blessings = 0; + } + + sendStats(); + sendSkills(); + sendReLoginWindow(unfairFightReduction); + + if (getSkull() == SKULL_BLACK) { + health = 40; + mana = 0; + } else { + health = healthMax; + mana = manaMax; + } + + for (ConditionList::iterator it = conditions.begin(); it != conditions.end();) { + Condition* condition = *it; + + if (condition->isPersistent()) { + it = conditions.erase(it); + + condition->endCondition(this, CONDITIONEND_DEATH); + onEndCondition(condition->getType()); + delete condition; + } else { + ++it; + } + } + } else { + setLossSkill(true); + + for (ConditionList::iterator it = conditions.begin(); it != conditions.end();) { + Condition* condition = *it; + + if (condition->isPersistent()) { + it = conditions.erase(it); + + condition->endCondition(this, CONDITIONEND_DEATH); + onEndCondition(condition->getType()); + delete condition; + } else { + ++it; + } + } + + health = healthMax; + g_game.internalTeleport(this, getTemplePosition(), true); + g_game.addCreatureHealth(this); + onThink(EVENT_CREATURE_THINK_INTERVAL); + onIdleStatus(); + sendStats(); + } +} + +bool Player::dropCorpse() +{ + if (getZone() == ZONE_PVP) { + setDropLoot(true); + return false; + } + + return Creature::dropCorpse(); +} + +Item* Player::getCorpse() +{ + Item* corpse = Creature::getCorpse(); + + if (corpse && corpse->getContainer()) { + Creature* lastHitCreature_ = NULL; + Creature* mostDamageCreature = NULL; + std::ostringstream ss; + + if (getKillers(&lastHitCreature_, &mostDamageCreature) && lastHitCreature_) { + ss << "You recognize " << getNameDescription() << ". " << (getSex() == PLAYERSEX_FEMALE ? "She" : "He") << " was killed by " << lastHitCreature_->getNameDescription() << "."; + } else { + ss << "You recognize " << getNameDescription() << "."; + } + + corpse->setSpecialDescription(ss.str()); + } + + return corpse; +} + +void Player::addWeaponExhaust(uint32_t ticks) +{ + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST_WEAPON, ticks, 0); + addCondition(condition); +} + +void Player::addCombatExhaust(uint32_t ticks) +{ + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST_COMBAT, ticks, 0); + addCondition(condition); +} + +void Player::addHealExhaust(uint32_t ticks) +{ + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST_HEAL, ticks, 0); + addCondition(condition); +} + +void Player::addInFightTicks(bool pzlock /*= false*/) +{ + if (pzlock) { + pzLocked = true; + } + + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_INFIGHT, g_game.getInFightTicks(), 0); + addCondition(condition); +} + +void Player::addDefaultRegeneration(uint32_t addTicks) +{ + Condition* condition = getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT); + + if (condition) { + condition->setTicks(condition->getTicks() + addTicks); + } else { + condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_REGENERATION, addTicks, 0); + condition->setParam(CONDITIONPARAM_HEALTHGAIN, vocation->getHealthGainAmount()); + condition->setParam(CONDITIONPARAM_HEALTHTICKS, vocation->getHealthGainTicks() * 1000); + condition->setParam(CONDITIONPARAM_MANAGAIN, vocation->getManaGainAmount()); + condition->setParam(CONDITIONPARAM_MANATICKS, vocation->getManaGainTicks() * 1000); + addCondition(condition); + } +} + +void Player::removeList() +{ + g_game.removePlayerFromNameMap(this); + listPlayer.removeList(getID()); + + for (AutoList::listiterator it = Player::listPlayer.list.begin(); it != Player::listPlayer.list.end(); ++it) { + it->second->notifyStatusChange(this, VIPSTATUS_OFFLINE); + } + + Status::getInstance()->removePlayer(); +} + +void Player::addList() +{ + g_game.addPlayerToNameMap(this); + + for (AutoList::listiterator it = Player::listPlayer.list.begin(); it != Player::listPlayer.list.end(); ++it) { + it->second->notifyStatusChange(this, VIPSTATUS_ONLINE); + } + + listPlayer.addList(this); + + Status::getInstance()->addPlayer(); +} + +void Player::kickPlayer(bool displayEffect) +{ + if (client) { + client->logout(displayEffect, true); + } else { + g_creatureEvents->playerLogout(this); + g_game.removeCreature(this); + } +} + +void Player::notifyStatusChange(Player* loginPlayer, VipStatus_t status) +{ + if (!client) { + return; + } + + VIPListSet::iterator it = VIPList.find(loginPlayer->getGUID()); + + if (it == VIPList.end()) { + return; + } + + client->sendUpdatedVIPStatus(loginPlayer->getGUID(), status); + + if (status == VIPSTATUS_ONLINE) { + client->sendTextMessage(MSG_STATUS_SMALL, (loginPlayer->getName() + " has logged in.")); + } else if (status == VIPSTATUS_OFFLINE) { + client->sendTextMessage(MSG_STATUS_SMALL, (loginPlayer->getName() + " has logged out.")); + } +} + +bool Player::removeVIP(uint32_t _guid) +{ + if (VIPList.erase(_guid) == 0) { + return false; + } + + IOLoginData::getInstance()->removeVIPEntry(accountNumber, _guid); + return true; +} + +bool Player::addVIP(uint32_t _guid, std::string& name, VipStatus_t status) +{ + if (guid == _guid) { + sendTextMessage(MSG_STATUS_SMALL, "You cannot add yourself."); + return false; + } + + if (VIPList.size() > maxVipLimit || VIPList.size() == 200) { // max number of buddies is 200 in 9.53 + sendTextMessage(MSG_STATUS_SMALL, "You cannot add more buddies."); + return false; + } + + VIPListSet::iterator it = VIPList.find(_guid); + + if (it != VIPList.end()) { + sendTextMessage(MSG_STATUS_SMALL, "This player is already in your list."); + return false; + } + + VIPList.insert(_guid); + + IOLoginData::getInstance()->addVIPEntry(accountNumber, _guid, "", 0, false); + + if (client) { + client->sendVIP(_guid, name, "", 0, false, status); + } + + return true; +} + +bool Player::addVIPInternal(uint32_t _guid) +{ + if (guid == _guid) { + return false; + } + + if (VIPList.size() > maxVipLimit || VIPList.size() == 200) { // max number of buddies is 200 in 9.53 + return false; + } + + VIPListSet::iterator it = VIPList.find(_guid); + + if (it != VIPList.end()) { + return false; + } + + VIPList.insert(_guid); + return true; +} + +bool Player::editVIP(uint32_t _guid, const std::string& description, uint32_t icon, bool notify) +{ + VIPListSet::iterator it = VIPList.find(_guid); + + if (it == VIPList.end()) { + return false; // player is not in VIP + } + + IOLoginData::getInstance()->editVIPEntry(accountNumber, _guid, description, icon, notify); + return true; +} + +//close container and its child containers +void Player::autoCloseContainers(const Container* container) +{ + std::vector closeList; + + for (ContainerMap::const_iterator it = openContainers.begin(), end = openContainers.end(); it != end; ++it) { + Container* tmpContainer = it->second.container; + + while (tmpContainer) { + if (tmpContainer->isRemoved() || tmpContainer == container) { + closeList.push_back(it->first); + break; + } + + tmpContainer = dynamic_cast(tmpContainer->getParent()); + } + } + + for (std::vector::const_iterator it = closeList.begin(); it != closeList.end(); ++it) { + closeContainer(*it); + + if (client) { + client->sendCloseContainer(*it); + } + } +} + +bool Player::hasCapacity(const Item* item, uint32_t count) const +{ + if (hasFlag(PlayerFlag_CannotPickupItem)) { + return false; + } + + if (hasFlag(PlayerFlag_HasInfiniteCapacity) || item->getTopParent() == this) { + return true; + } + + double itemWeight = 0; + + if (item->isStackable()) { + itemWeight = Item::items[item->getID()].weight * count; + } else { + itemWeight = item->getWeight(); + } + + return itemWeight <= getFreeCapacity(); +} + +ReturnValue Player::__queryAdd(int32_t index, const Thing* thing, uint32_t count, uint32_t flags, Creature* actor/* = NULL*/) const +{ + const Item* item = thing->getItem(); + + if (item == NULL) { + return RET_NOTPOSSIBLE; + } + + bool childIsOwner = hasBitSet(FLAG_CHILDISOWNER, flags); + bool skipLimit = hasBitSet(FLAG_NOLIMIT, flags); + + if (childIsOwner) { + //a child container is querying the player, just check if enough capacity + if (skipLimit || hasCapacity(item, count)) { + return RET_NOERROR; + } + + return RET_NOTENOUGHCAPACITY; + } + + if (!item->isPickupable()) { + return RET_CANNOTPICKUP; + } + + ReturnValue ret = RET_NOERROR; + const int32_t& slotPosition = item->getSlotPosition(); + + if ((slotPosition & SLOTP_HEAD) || (slotPosition & SLOTP_NECKLACE) || + (slotPosition & SLOTP_BACKPACK) || (slotPosition & SLOTP_ARMOR) || + (slotPosition & SLOTP_LEGS) || (slotPosition & SLOTP_FEET) || + (slotPosition & SLOTP_RING)) { + ret = RET_CANNOTBEDRESSED; + } else if (slotPosition & SLOTP_TWO_HAND) { + ret = RET_PUTTHISOBJECTINBOTHHANDS; + } else if ((slotPosition & SLOTP_RIGHT) || (slotPosition & SLOTP_LEFT)) { + ret = RET_PUTTHISOBJECTINYOURHAND; + } + + switch (index) { + case SLOT_HEAD: { + if (slotPosition & SLOTP_HEAD) { + ret = RET_NOERROR; + } + + break; + } + + case SLOT_NECKLACE: { + if (slotPosition & SLOTP_NECKLACE) { + ret = RET_NOERROR; + } + + break; + } + + case SLOT_BACKPACK: { + if (slotPosition & SLOTP_BACKPACK) { + ret = RET_NOERROR; + } + + break; + } + + case SLOT_ARMOR: { + if (slotPosition & SLOTP_ARMOR) { + ret = RET_NOERROR; + } + + break; + } + + case SLOT_RIGHT: { + if (slotPosition & SLOTP_RIGHT) { + //check if we already carry an item in the other hand + if (slotPosition & SLOTP_TWO_HAND) { + if (inventory[SLOT_LEFT] && inventory[SLOT_LEFT] != item) { + ret = RET_BOTHHANDSNEEDTOBEFREE; + } else { + ret = RET_NOERROR; + } + } else if (inventory[SLOT_LEFT]) { + const Item* leftItem = inventory[SLOT_LEFT]; + WeaponType_t type = item->getWeaponType(), leftType = leftItem->getWeaponType(); + + if (leftItem->getSlotPosition() & SLOTP_TWO_HAND) { + ret = RET_DROPTWOHANDEDITEM; + } else if (item == leftItem && count == item->getItemCount()) { + ret = RET_NOERROR; + } else if (leftType == WEAPON_SHIELD && type == WEAPON_SHIELD) { + ret = RET_CANONLYUSEONESHIELD; + } else if (!leftItem->isWeapon() || !item->isWeapon() || + leftType == WEAPON_SHIELD || leftType == WEAPON_AMMO + || type == WEAPON_SHIELD || type == WEAPON_AMMO) { + ret = RET_NOERROR; + } else { + ret = RET_CANONLYUSEONEWEAPON; + } + } else { + ret = RET_NOERROR; + } + } + + break; + } + + case SLOT_LEFT: { + if (slotPosition & SLOTP_LEFT) { + //check if we already carry an item in the other hand + if (slotPosition & SLOTP_TWO_HAND) { + if (inventory[SLOT_RIGHT] && inventory[SLOT_RIGHT] != item) { + ret = RET_BOTHHANDSNEEDTOBEFREE; + } else { + ret = RET_NOERROR; + } + } else if (inventory[SLOT_RIGHT]) { + const Item* rightItem = inventory[SLOT_RIGHT]; + WeaponType_t type = item->getWeaponType(), rightType = rightItem->getWeaponType(); + + if (rightItem->getSlotPosition() & SLOTP_TWO_HAND) { + ret = RET_DROPTWOHANDEDITEM; + } else if (item == rightItem && count == item->getItemCount()) { + ret = RET_NOERROR; + } else if (rightType == WEAPON_SHIELD && type == WEAPON_SHIELD) { + ret = RET_CANONLYUSEONESHIELD; + } else if (!rightItem->isWeapon() || !item->isWeapon() || + rightType == WEAPON_SHIELD || rightType == WEAPON_AMMO + || type == WEAPON_SHIELD || type == WEAPON_AMMO) { + ret = RET_NOERROR; + } else { + ret = RET_CANONLYUSEONEWEAPON; + } + } else { + ret = RET_NOERROR; + } + } + + break; + } + + case SLOT_LEGS: { + if (slotPosition & SLOTP_LEGS) { + ret = RET_NOERROR; + } + + break; + } + + case SLOT_FEET: { + if (slotPosition & SLOTP_FEET) { + ret = RET_NOERROR; + } + + break; + } + + case SLOT_RING: { + if (slotPosition & SLOTP_RING) { + ret = RET_NOERROR; + } + + break; + } + + case SLOT_AMMO: { + if (slotPosition & SLOTP_AMMO) { + ret = RET_NOERROR; + } + + break; + } + + case SLOT_WHEREEVER: + case -1: + ret = RET_NOTENOUGHROOM; + break; + + default: + ret = RET_NOTPOSSIBLE; + break; + } + + if (ret == RET_NOERROR || ret == RET_NOTENOUGHROOM) { + //need an exchange with source? + if (getInventoryItem((slots_t)index) != NULL && (!getInventoryItem((slots_t)index)->isStackable() + || getInventoryItem((slots_t)index)->getID() != item->getID())) { + return RET_NEEDEXCHANGE; + } + + if (!g_moveEvents->onPlayerEquip(const_cast(this), const_cast(item), (slots_t)index, true)) { + return RET_CANNOTBEDRESSED; + } + + //check if enough capacity + if (!hasCapacity(item, count)) { + return RET_NOTENOUGHCAPACITY; + } + } + + return ret; +} + +ReturnValue Player::__queryMaxCount(int32_t index, const Thing* thing, uint32_t count, uint32_t& maxQueryCount, + uint32_t flags) const +{ + const Item* item = thing->getItem(); + + if (item == NULL) { + maxQueryCount = 0; + return RET_NOTPOSSIBLE; + } + + if (index == INDEX_WHEREEVER) { + uint32_t n = 0; + + for (int slotIndex = SLOT_FIRST; slotIndex < SLOT_LAST; ++slotIndex) { + Item* inventoryItem = inventory[slotIndex]; + + if (inventoryItem) { + if (Container* subContainer = inventoryItem->getContainer()) { + uint32_t queryCount = 0; + subContainer->__queryMaxCount(INDEX_WHEREEVER, item, item->getItemCount(), queryCount, flags); + n += queryCount; + + //iterate through all items, including sub-containers (deep search) + for (ContainerIterator cit = subContainer->begin(); cit != subContainer->end(); ++cit) { + if (Container* tmpContainer = (*cit)->getContainer()) { + queryCount = 0; + tmpContainer->__queryMaxCount(INDEX_WHEREEVER, item, item->getItemCount(), queryCount, flags); + n += queryCount; + } + } + } else if (inventoryItem->isStackable() && item->getID() == inventoryItem->getID() && inventoryItem->getItemCount() < 100) { + uint32_t remainder = (100 - inventoryItem->getItemCount()); + + if (__queryAdd(slotIndex, item, remainder, flags) == RET_NOERROR) { + n += remainder; + } + } + } else if (__queryAdd(slotIndex, item, item->getItemCount(), flags) == RET_NOERROR) { //empty slot + if (item->isStackable()) { + n += 100; + } else { + n += 1; + } + } + } + + maxQueryCount = n; + } else { + const Thing* destThing = __getThing(index); + const Item* destItem = NULL; + + if (destThing) { + destItem = destThing->getItem(); + } + + if (destItem) { + if (destItem->isStackable() && item->getID() == destItem->getID() && destItem->getItemCount() < 100) { + maxQueryCount = 100 - destItem->getItemCount(); + } else { + maxQueryCount = 0; + } + } else if (__queryAdd(index, item, count, flags) == RET_NOERROR) { //empty slot + if (item->isStackable()) { + maxQueryCount = 100; + } else { + maxQueryCount = 1; + } + + return RET_NOERROR; + } + } + + if (maxQueryCount < count) { + return RET_NOTENOUGHROOM; + } else { + return RET_NOERROR; + } +} + +ReturnValue Player::__queryRemove(const Thing* thing, uint32_t count, uint32_t flags) const +{ + int32_t index = __getIndexOfThing(thing); + + if (index == -1) { + return RET_NOTPOSSIBLE; + } + + const Item* item = thing->getItem(); + + if (item == NULL) { + return RET_NOTPOSSIBLE; + } + + if (count == 0 || (item->isStackable() && count > item->getItemCount())) { + return RET_NOTPOSSIBLE; + } + + if (item->isNotMoveable() && !hasBitSet(FLAG_IGNORENOTMOVEABLE, flags)) { + return RET_NOTMOVEABLE; + } + + return RET_NOERROR; +} + +Cylinder* Player::__queryDestination(int32_t& index, const Thing* thing, Item** destItem, + uint32_t& flags) +{ + if (index == 0 /*drop to capacity window*/ || index == INDEX_WHEREEVER) { + *destItem = NULL; + + const Item* item = thing->getItem(); + + if (item == NULL) { + return this; + } + + bool autoStack = !((flags & FLAG_IGNOREAUTOSTACK) == FLAG_IGNOREAUTOSTACK); + + std::list containerList; + + for (uint32_t slotIndex = SLOT_FIRST; slotIndex < SLOT_LAST; ++slotIndex) { + Item* inventoryItem = inventory[slotIndex]; + + if (inventoryItem) { + if (inventoryItem == tradeItem) { + continue; + } + + if (inventoryItem == item) { + continue; + } + + if (autoStack && item->isStackable()) { + //try find an already existing item to stack with + if (__queryAdd(slotIndex, item, item->getItemCount(), 0) == RET_NOERROR) { + if (inventoryItem->getID() == item->getID() && inventoryItem->getItemCount() < 100) { + index = slotIndex; + *destItem = inventoryItem; + return this; + } + } + + if (Container* subContainer = inventoryItem->getContainer()) { + containerList.push_back(subContainer); + } + } else if (Container* subContainer = inventoryItem->getContainer()) { + if (subContainer->__queryAdd(INDEX_WHEREEVER, item, item->getItemCount(), flags) == RET_NOERROR) { + index = INDEX_WHEREEVER; + *destItem = NULL; + return subContainer; + } + + containerList.push_back(subContainer); + } + } else if (__queryAdd(slotIndex, item, item->getItemCount(), flags) == RET_NOERROR) { //empty slot + index = slotIndex; + *destItem = NULL; + return this; + } + } + + while (!containerList.empty()) { + Container* tmpContainer = containerList.front(); + containerList.pop_front(); + + if (!(autoStack && item->isStackable())) { + //we need to find first empty container as fast as we can for non-stackable items + uint32_t n = tmpContainer->capacity() - tmpContainer->size(); + + while (n) { + if (tmpContainer->__queryAdd(tmpContainer->capacity() - n, item, item->getItemCount(), flags) == RET_NOERROR) { + index = tmpContainer->capacity() - n; + *destItem = NULL; + return tmpContainer; + } + + n--; + } + + for (ItemDeque::const_iterator it = tmpContainer->getItems(), end = tmpContainer->getEnd(); it != end; ++it) { + if (Container* subContainer = (*it)->getContainer()) { + containerList.push_back(subContainer); + } + } + + continue; + } + + uint32_t n = 0; + + for (ItemDeque::const_iterator it = tmpContainer->getItems(), end = tmpContainer->getEnd(); it != end; ++it) { + Item* tmpItem = *it; + + if (tmpItem == tradeItem) { + continue; + } + + if (tmpItem == item) { + continue; + } + + //try find an already existing item to stack with + if (tmpItem != item && tmpItem->getID() == item->getID() && tmpItem->getItemCount() < 100) { + index = n; + *destItem = tmpItem; + return tmpContainer; + } + + if (Container* subContainer = tmpItem->getContainer()) { + containerList.push_back(subContainer); + } + + n++; + } + + if (n < tmpContainer->capacity() && tmpContainer->__queryAdd(n, item, item->getItemCount(), flags) == RET_NOERROR) { + index = n; + *destItem = NULL; + return tmpContainer; + } + } + + return this; + } + + Thing* destThing = __getThing(index); + + if (destThing) { + *destItem = destThing->getItem(); + } + + Cylinder* subCylinder = dynamic_cast(destThing); + + if (subCylinder) { + index = INDEX_WHEREEVER; + *destItem = NULL; + return subCylinder; + } else { + return this; + } +} + +void Player::__addThing(Thing* thing) +{ + __addThing(0, thing); +} + +void Player::__addThing(int32_t index, Thing* thing) +{ + if (index < 0 || index > 11) { + return /*RET_NOTPOSSIBLE*/; + } + + if (index == 0) { + return /*RET_NOTENOUGHROOM*/; + } + + Item* item = thing->getItem(); + + if (item == NULL) { + return /*RET_NOTPOSSIBLE*/; + } + + item->setParent(this); + inventory[index] = item; + + //send to client + sendInventoryItem((slots_t)index, item); + + //event methods + onAddInventoryItem((slots_t)index, item); +} + +void Player::__updateThing(Thing* thing, uint16_t itemId, uint32_t count) +{ + int32_t index = __getIndexOfThing(thing); + + if (index == -1) { + return /*RET_NOTPOSSIBLE*/; + } + + Item* item = thing->getItem(); + + if (item == NULL) { + return /*RET_NOTPOSSIBLE*/; + } + + const ItemType& oldType = Item::items[item->getID()]; + + const ItemType& newType = Item::items[itemId]; + + item->setID(itemId); + + item->setSubType(count); + + //send to client + sendInventoryItem((slots_t)index, item); + + //event methods + onUpdateInventoryItem((slots_t)index, item, oldType, item, newType); +} + +void Player::__replaceThing(uint32_t index, Thing* thing) +{ + if (index > 11) { + return /*RET_NOTPOSSIBLE*/; + } + + Item* oldItem = getInventoryItem((slots_t)index); + + if (!oldItem) { + return /*RET_NOTPOSSIBLE*/; + } + + Item* item = thing->getItem(); + + if (!item) { + return /*RET_NOTPOSSIBLE*/; + } + + const ItemType& oldType = Item::items[oldItem->getID()]; + + const ItemType& newType = Item::items[item->getID()]; + + //send to client + sendInventoryItem((slots_t)index, item); + + //event methods + onUpdateInventoryItem((slots_t)index, oldItem, oldType, item, newType); + + item->setParent(this); + + inventory[index] = item; +} + +void Player::__removeThing(Thing* thing, uint32_t count) +{ + Item* item = thing->getItem(); + + if (!item) { + return /*RET_NOTPOSSIBLE*/; + } + + int32_t index = __getIndexOfThing(thing); + + if (index == -1) { + return /*RET_NOTPOSSIBLE*/; + } + + if (item->isStackable()) { + if (count == item->getItemCount()) { + //send change to client + sendInventoryItem((slots_t)index, NULL); + + //event methods + onRemoveInventoryItem((slots_t)index, item); + + item->setParent(NULL); + inventory[index] = NULL; + } else { + uint8_t newCount = (uint8_t)std::max(0, item->getItemCount() - count); + item->setItemCount(newCount); + + const ItemType& it = Item::items[item->getID()]; + + //send change to client + sendInventoryItem((slots_t)index, item); + + //event methods + onUpdateInventoryItem((slots_t)index, item, it, item, it); + } + } else { + //send change to client + sendInventoryItem((slots_t)index, NULL); + + //event methods + onRemoveInventoryItem((slots_t)index, item); + + item->setParent(NULL); + inventory[index] = NULL; + } +} + +int32_t Player::__getIndexOfThing(const Thing* thing) const +{ + for (int i = SLOT_FIRST; i < SLOT_LAST; ++i) { + if (inventory[i] == thing) { + return i; + } + } + + return -1; +} + +int32_t Player::__getFirstIndex() const +{ + return SLOT_FIRST; +} + +int32_t Player::__getLastIndex() const +{ + return SLOT_LAST; +} + +uint32_t Player::__getItemTypeCount(uint16_t itemId, int32_t subType /*= -1*/) const +{ + uint32_t count = 0; + + for (int i = SLOT_FIRST; i < SLOT_LAST; i++) { + Item* item = inventory[i]; + + if (!item) { + continue; + } + + if (item->getID() == itemId) { + count += Item::countByType(item, subType); + } else if (Container* container = item->getContainer()) { + for (ContainerIterator it = container->begin(), end = container->end(); it != end; ++it) { + if ((*it)->getID() == itemId) { + count += Item::countByType(*it, subType); + } + } + } + } + + return count; +} + +std::map& Player::__getAllItemTypeCount(std::map& countMap) const +{ + for (int i = SLOT_FIRST; i < SLOT_LAST; i++) { + Item* item = inventory[i]; + + if (!item) { + continue; + } + + countMap[item->getID()] += Item::countByType(item, -1); + + if (Container* container = item->getContainer()) { + for (ContainerIterator it = container->begin(), end = container->end(); it != end; ++it) { + countMap[(*it)->getID()] += Item::countByType(*it, -1); + } + } + } + + return countMap; +} + +Thing* Player::__getThing(uint32_t index) const +{ + if (index >= SLOT_FIRST && index < SLOT_LAST) { + return inventory[index]; + } + + return NULL; +} + +void Player::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) +{ + if (link == LINK_OWNER) { + //calling movement scripts + g_moveEvents->onPlayerEquip(this, thing->getItem(), (slots_t)index, false); + } + + bool requireListUpdate = true; + + if (link == LINK_OWNER || link == LINK_TOPPARENT) { + const Item* i = (oldParent ? oldParent->getItem() : NULL); + + // Check if we owned the old container too, so we don't need to do anything, + // as the list was updated in postRemoveNotification + assert(i ? i->getContainer() != NULL : true); + + if (i) { + requireListUpdate = i->getContainer()->getHoldingPlayer() != this; + } else { + requireListUpdate = oldParent != this; + } + + updateInventoryWeight(); + updateItemsLight(); + sendStats(); + } + + if (const Item* item = thing->getItem()) { + if (const Container* container = item->getContainer()) { + onSendContainer(container); + } + + if (shopOwner && requireListUpdate) { + updateSaleShopList(item->getID()); + } + } else if (const Creature* creature = thing->getCreature()) { + if (creature == this) { + //check containers + std::vector containers; + + for (ContainerMap::const_iterator it = openContainers.begin(), end = openContainers.end(); it != end; ++it) { + Container* container = it->second.container; + + if (!Position::areInRange<1, 1, 0>(container->getPosition(), getPosition())) { + containers.push_back(container); + } + } + + for (std::vector::const_iterator it = containers.begin(); it != containers.end(); ++it) { + autoCloseContainers(*it); + } + } + } +} + +void Player::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, bool isCompleteRemoval, cylinderlink_t link /*= LINK_OWNER*/) +{ + if (link == LINK_OWNER) { + //calling movement scripts + g_moveEvents->onPlayerDeEquip(this, thing->getItem(), (slots_t)index, isCompleteRemoval); + } + + bool requireListUpdate = true; + + if (link == LINK_OWNER || link == LINK_TOPPARENT) { + const Item* i = (newParent ? newParent->getItem() : NULL); + + // Check if we owned the old container too, so we don't need to do anything, + // as the list was updated in postRemoveNotification + assert(i ? i->getContainer() != NULL : true); + + if (i) { + requireListUpdate = i->getContainer()->getHoldingPlayer() != this; + } else { + requireListUpdate = newParent != this; + } + + updateInventoryWeight(); + updateItemsLight(); + sendStats(); + } + + if (const Item* item = thing->getItem()) { + if (const Container* container = item->getContainer()) { + if (container->isRemoved() || !Position::areInRange<1, 1, 0>(getPosition(), container->getPosition())) { + autoCloseContainers(container); + } else if (container->getTopParent() == this) { + onSendContainer(container); + } else if (const Container* topContainer = dynamic_cast(container->getTopParent())) { + if (const DepotChest* depotChest = dynamic_cast(topContainer)) { + bool isOwner = false; + + for (DepotMap::iterator it = depotChests.begin(); it != depotChests.end(); ++it) { + if (it->second == depotChest) { + isOwner = true; + onSendContainer(container); + } + } + + if (!isOwner) { + autoCloseContainers(container); + } + } else { + onSendContainer(container); + } + } else { + autoCloseContainers(container); + } + } + + if (shopOwner && requireListUpdate) { + updateSaleShopList(item->getID()); + } + } +} + +void Player::updateSaleShopList(uint32_t itemId) +{ + for (std::list::const_iterator it = shopItemList.begin(); it != shopItemList.end(); ++it) { + if (it->itemId == itemId) { + if (client) { + client->sendSaleItemList(shopItemList); + } + + break; + } + } +} + +bool Player::hasShopItemForSale(uint32_t itemId, uint8_t subType) +{ + for (std::list::const_iterator it = shopItemList.begin(); it != shopItemList.end(); ++it) { + if (it->itemId == itemId && it->buyPrice > 0) { + const ItemType& iit = Item::items[itemId]; + + if (iit.isFluidContainer() || iit.isSplash()) { + if (it->subType == subType) { + return true; + } + } else { + return true; + } + } + } + + return false; +} + +void Player::__internalAddThing(Thing* thing) +{ + __internalAddThing(0, thing); +} + +void Player::__internalAddThing(uint32_t index, Thing* thing) +{ + Item* item = thing->getItem(); + + if (!item) { + return; + } + + //index == 0 means we should equip this item at the most appropiate slot + if (index == 0) { + return; + } + + if (index > 0 && index < 11) { + if (inventory[index]) { + return; + } + + inventory[index] = item; + item->setParent(this); + } +} + +bool Player::setFollowCreature(Creature* creature, bool fullPathSearch /*= false*/) +{ + if (!Creature::setFollowCreature(creature, fullPathSearch)) { + setFollowCreature(NULL); + setAttackedCreature(NULL); + + sendCancelMessage(RET_THEREISNOWAY); + sendCancelTarget(); + stopWalk(); + return false; + } + + return true; +} + +bool Player::setAttackedCreature(Creature* creature) +{ + if (!Creature::setAttackedCreature(creature)) { + sendCancelTarget(); + return false; + } + + if (chaseMode == CHASEMODE_FOLLOW && creature) { + if (followCreature != creature) { + //chase opponent + setFollowCreature(creature); + } + } else { + setFollowCreature(NULL); + } + + if (creature) { + g_dispatcher.addTask(createTask( + boost::bind(&Game::checkCreatureAttack, &g_game, getID()))); + } + + return true; +} + +void Player::goToFollowCreature() +{ + if (!walkTask) { + if ((OTSYS_TIME() - lastFailedFollow) < 2000) { + return; + } + + Creature::goToFollowCreature(); + + if (followCreature && !hasFollowPath) { + lastFailedFollow = OTSYS_TIME(); + } + } +} + +void Player::getPathSearchParams(const Creature* creature, FindPathParams& fpp) const +{ + Creature::getPathSearchParams(creature, fpp); + fpp.fullPathSearch = true; +} + +void Player::onAttacking(uint32_t interval) +{ + Creature::onAttacking(interval); +} + +void Player::doAttacking(uint32_t interval) +{ + if (lastAttack == 0) { + lastAttack = OTSYS_TIME() - getAttackSpeed() - 1; + } + + if (hasCondition(CONDITION_PACIFIED)) { + return; + } + + if ((OTSYS_TIME() - lastAttack) >= getAttackSpeed()) { + Item* tool = getWeapon(); + const Weapon* weapon = g_weapons->getWeapon(tool); + + bool result = false; + + if (weapon) { + if (!weapon->interruptSwing()) { + result = weapon->useWeapon(this, tool, attackedCreature); + } else if (!canDoAction()) { + uint32_t delay = getNextActionTime(); + SchedulerTask* task = createSchedulerTask(delay, boost::bind(&Game::checkCreatureAttack, + &g_game, getID())); + setNextActionTask(task); + } else if (!hasCondition(CONDITION_EXHAUST_COMBAT) || !weapon->hasExhaustion()) { + result = weapon->useWeapon(this, tool, attackedCreature); + } + } else { + result = Weapon::useFist(this, attackedCreature); + } + + if (result) { + lastAttack = OTSYS_TIME(); + } + } +} + +uint64_t Player::getGainedExperience(Creature* attacker) const +{ + if (g_config.getBoolean(ConfigManager::EXPERIENCE_FROM_PLAYERS)) { + Player* attackerPlayer = attacker->getPlayer(); + + if (attackerPlayer && attackerPlayer != this && skillLoss && std::abs((int32_t)(attackerPlayer->getLevel() - level)) <= g_config.getNumber(ConfigManager::EXP_FROM_PLAYERS_LEVEL_RANGE)) { + return std::max(0, std::floor(getLostExperience() * getDamageRatio(attacker) * 0.75)); + } + } + + return 0; +} + +void Player::onFollowCreature(const Creature* creature) +{ + if (!creature) { + stopWalk(); + } +} + +void Player::setChaseMode(chaseMode_t mode) +{ + chaseMode_t prevChaseMode = chaseMode; + chaseMode = mode; + + if (prevChaseMode != chaseMode) { + if (chaseMode == CHASEMODE_FOLLOW) { + if (!followCreature && attackedCreature) { + //chase opponent + setFollowCreature(attackedCreature); + } + } else if (attackedCreature) { + setFollowCreature(NULL); + cancelNextWalk = true; + } + } +} + +void Player::setFightMode(fightMode_t mode) +{ + fightMode = mode; +} + +void Player::onWalkAborted() +{ + setNextWalkActionTask(NULL); + sendCancelWalk(); +} + +void Player::onWalkComplete() +{ + if (walkTask) { + walkTaskEvent = g_scheduler.addEvent(walkTask); + walkTask = NULL; + } +} + +void Player::stopWalk() +{ + cancelNextWalk = true; +} + +void Player::getCreatureLight(LightInfo& light) const +{ + if (internalLight.level > itemsLight.level) { + light = internalLight; + } else { + light = itemsLight; + } +} + +void Player::updateItemsLight(bool internal /*=false*/) +{ + LightInfo maxLight; + LightInfo curLight; + + for (int32_t i = SLOT_FIRST; i < SLOT_LAST; ++i) { + Item* item = getInventoryItem((slots_t)i); + + if (item) { + item->getLight(curLight); + + if (curLight.level > maxLight.level) { + maxLight = curLight; + } + } + } + + if (itemsLight.level != maxLight.level || itemsLight.color != maxLight.color) { + itemsLight = maxLight; + + if (!internal) { + g_game.changeLight(this); + } + } +} + +void Player::onAddCondition(ConditionType_t type) +{ + Creature::onAddCondition(type); + + if (type == CONDITION_OUTFIT && isMounted()) { + dismount(); + } + + sendIcons(); +} + +void Player::onAddCombatCondition(ConditionType_t type) +{ + switch (type) { + case CONDITION_POISON: + sendTextMessage(MSG_STATUS_DEFAULT, "You are poisoned."); + break; + + case CONDITION_DROWN: + sendTextMessage(MSG_STATUS_DEFAULT, "You are drowning."); + break; + + case CONDITION_PARALYZE: + sendTextMessage(MSG_STATUS_DEFAULT, "You are paralyzed."); + break; + + case CONDITION_DRUNK: + sendTextMessage(MSG_STATUS_DEFAULT, "You are drunk."); + break; + + case CONDITION_CURSED: + sendTextMessage(MSG_STATUS_DEFAULT, "You are cursed."); + break; + + case CONDITION_FREEZING: + sendTextMessage(MSG_STATUS_DEFAULT, "You are freezing."); + break; + + case CONDITION_DAZZLED: + sendTextMessage(MSG_STATUS_DEFAULT, "You are dazzled."); + break; + + case CONDITION_BLEEDING: + sendTextMessage(MSG_STATUS_DEFAULT, "You are bleeding."); + break; + + default: + break; + } +} + +void Player::onEndCondition(ConditionType_t type) +{ + Creature::onEndCondition(type); + + if (type == CONDITION_INFIGHT) { + onIdleStatus(); + pzLocked = false; + clearAttacked(); + + if (getSkull() != SKULL_RED && getSkull() != SKULL_BLACK) { + setSkull(SKULL_NONE); + g_game.updatePlayerSkull(this); + } + } + + sendIcons(); +} + +void Player::onCombatRemoveCondition(const Creature* attacker, Condition* condition) +{ + //Creature::onCombatRemoveCondition(attacker, condition); + bool remove = true; + + if (condition->getId() > 0) { + remove = false; + + //Means the condition is from an item, id == slot + if (g_game.getWorldType() == WORLD_TYPE_PVP_ENFORCED) { + Item* item = getInventoryItem((slots_t)condition->getId()); + + if (item) { + //25% chance to destroy the item + if (25 >= random_range(0, 100)) { + g_game.internalRemoveItem(item); + } + } + } + } + + if (remove) { + if (!canDoAction()) { + int32_t delay = getNextActionTime(); + delay -= (delay % EVENT_CREATURE_THINK_INTERVAL); + + if (delay < 0) { + removeCondition(condition); + } else { + condition->setTicks(delay); + } + } else { + removeCondition(condition); + } + } +} + +void Player::onAttackedCreature(Creature* target) +{ + Creature::onAttackedCreature(target); + + if (hasFlag(PlayerFlag_NotGainInFight)) { + return; + } + + if (target == this) { + addInFightTicks(); + return; + } + + Player* targetPlayer = target->getPlayer(); + + if (targetPlayer && !isPartner(targetPlayer) && !isGuildMate(targetPlayer)) { + if (!pzLocked && g_game.getWorldType() == WORLD_TYPE_PVP_ENFORCED) { + pzLocked = true; + sendIcons(); + } + + if (getSkull() == SKULL_NONE && getSkullClient(targetPlayer) == SKULL_YELLOW) { + addAttacked(targetPlayer); + targetPlayer->sendCreatureSkull(this); + } else if (!targetPlayer->hasAttacked(this)) { + if (!pzLocked && g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { + pzLocked = true; + sendIcons(); + } + + if (!Combat::isInPvpZone(this, targetPlayer) && !isInWar(targetPlayer)) { + addAttacked(targetPlayer); + + if (targetPlayer->getSkull() == SKULL_NONE && getSkull() == SKULL_NONE) { + setSkull(SKULL_WHITE); + g_game.updatePlayerSkull(this); + } + + if (getSkull() == SKULL_NONE) { + targetPlayer->sendCreatureSkull(this); + } + } + } + } + + addInFightTicks(); +} + +void Player::onAttacked() +{ + Creature::onAttacked(); + + if (!hasFlag(PlayerFlag_NotGainInFight)) { + addInFightTicks(); + } +} + +void Player::onIdleStatus() +{ + Creature::onIdleStatus(); + + if (getParty()) { + getParty()->clearPlayerPoints(this); + } +} + +void Player::onPlacedCreature() +{ + //scripting event - onLogin + if (!g_creatureEvents->playerLogin(this)) { + kickPlayer(true); + } +} + +void Player::onRemovedCreature() +{ + // +} + +void Player::onAttackedCreatureDrainHealth(Creature* target, int32_t points) +{ + Creature::onAttackedCreatureDrainHealth(target, points); + + if (target) { + if (getParty() && !Combat::isPlayerCombat(target)) { + Monster* tmpMonster = target->getMonster(); + + if (tmpMonster && tmpMonster->isHostile()) { + //We have fulfilled a requirement for shared experience + getParty()->addPlayerDamageMonster(this, points); + } + } + } +} + +void Player::onTargetCreatureGainHealth(Creature* target, int32_t points) +{ + Creature::onTargetCreatureGainHealth(target, points); + + if (target && getParty()) { + Player* tmpPlayer = NULL; + + if (target->getPlayer()) { + tmpPlayer = target->getPlayer(); + } else if (Creature* targetMaster = target->getMaster()) { + if (Player* targetMasterPlayer = targetMaster->getPlayer()) { + tmpPlayer = targetMasterPlayer; + } + } + + if (isPartner(tmpPlayer)) { + getParty()->addPlayerHealedMember(this, points); + } + } +} + +bool Player::onKilledCreature(Creature* target, bool lastHit/* = true*/) +{ + bool unjustified = false; + + if (hasFlag(PlayerFlag_NotGenerateLoot)) { + target->setDropLoot(false); + } + + Creature::onKilledCreature(target, lastHit); + + if (Player* targetPlayer = target->getPlayer()) { + if (targetPlayer && targetPlayer->getZone() == ZONE_PVP) { + targetPlayer->setDropLoot(false); + targetPlayer->setLossSkill(false); + } else if (!hasFlag(PlayerFlag_NotGainInFight) && !isPartner(targetPlayer)) { + if (!Combat::isInPvpZone(this, targetPlayer) && !targetPlayer->hasAttacked(this) && !isGuildMate(targetPlayer) && targetPlayer != this) { + if (targetPlayer->getSkull() == SKULL_NONE && !isInWar(targetPlayer)) { + unjustified = true; + addUnjustifiedDead(targetPlayer); + } + + if (lastHit && hasCondition(CONDITION_INFIGHT)) { + pzLocked = true; + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_INFIGHT, g_config.getNumber(ConfigManager::WHITE_SKULL_TIME), 0); + addCondition(condition); + } + } + } + } + + return unjustified; +} + +void Player::gainExperience(uint64_t gainExp) +{ + if (!hasFlag(PlayerFlag_NotGainExperience) && gainExp > 0) { + uint64_t oldExperience = experience; + + if (staminaMinutes == 0) { + return; + } + + addExperience(gainExp, true, true, true); + + //soul regeneration + int64_t gainedExperience = experience - oldExperience; + + if (gainedExperience >= level) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SOUL, 4 * 60 * 1000, 0); + condition->setParam(CONDITIONPARAM_SOULGAIN, 1); + condition->setParam(CONDITIONPARAM_SOULTICKS, vocation->getSoulGainTicks() * 1000); + addCondition(condition); + } + } +} + +void Player::onGainExperience(uint64_t gainExp, Creature* target) +{ + if (hasFlag(PlayerFlag_NotGainExperience)) { + return; + } + + if (target) { + if (gainExp > 0 && target->getMonster()) { + useStamina(); + } + + Party* party = getParty(); + if (!target->getPlayer() && party && party->isSharedExperienceActive() && party->isSharedExperienceEnabled()) { + party->shareExperience(gainExp); + //We will get a share of the experience through the sharing mechanism + return; + } + } + + Creature::onGainExperience(gainExp, target); + gainExperience(gainExp); +} + +void Player::onGainSharedExperience(uint64_t gainExp) +{ + Creature::onGainSharedExperience(gainExp); + gainExperience(gainExp); +} + +bool Player::isImmune(CombatType_t type) const +{ + if (hasFlag(PlayerFlag_CannotBeAttacked)) { + return true; + } + + return Creature::isImmune(type); +} + +bool Player::isImmune(ConditionType_t type) const +{ + if (hasFlag(PlayerFlag_CannotBeAttacked)) { + return true; + } + + return Creature::isImmune(type); +} + +bool Player::isAttackable() const +{ + return !hasFlag(PlayerFlag_CannotBeAttacked); +} + +void Player::changeHealth(int32_t healthChange, bool sendHealthChange/* = true*/) +{ + Creature::changeHealth(healthChange, sendHealthChange); + sendStats(); +} + +void Player::changeMana(int32_t manaChange) +{ + if (!hasFlag(PlayerFlag_HasInfiniteMana)) { + Creature::changeMana(manaChange); + } + + sendStats(); +} + +void Player::changeSoul(int32_t soulChange) +{ + if (soulChange > 0) { + soul += std::min(soulChange, soulMax - soul); + } else { + soul = std::max(0, soul + soulChange); + } + + sendStats(); +} + +const OutfitListType& Player::getPlayerOutfits() +{ + return m_playerOutfits.getOutfits(); +} + +bool Player::canWear(uint32_t _looktype, uint32_t _addons) +{ + return m_playerOutfits.isInList(_looktype, _addons, isPremium(), getSex()) || accessLevel; +} + +bool Player::canLogout() +{ + if (isConnecting) { + return false; + } + + if (getTile()->hasFlag(TILESTATE_NOLOGOUT)) { + return false; + } + + if (getTile()->hasFlag(TILESTATE_PROTECTIONZONE)) { + return true; + } + + return !isPzLocked() && !hasCondition(CONDITION_INFIGHT); +} + +void Player::genReservedStorageRange() +{ + uint32_t base_key; + //generate outfits range + base_key = PSTRG_OUTFITS_RANGE_START + 1; + + const OutfitList& global_outfits = Outfits::getInstance()->getOutfitList(sex); + + const OutfitListType& outfits = m_playerOutfits.getOutfits(); + OutfitListType::const_iterator it; + + for (it = outfits.begin(); it != outfits.end(); ++it) { + uint32_t looktype = (*it)->looktype; + uint32_t addons = (*it)->addons; + + if (!global_outfits.isInList(looktype, addons, isPremium(), getSex())) { + long value = (looktype << 16) | (addons & 0xFF); + storageMap[base_key] = value; + base_key++; + + if (base_key > PSTRG_OUTFITS_RANGE_START + PSTRG_OUTFITS_RANGE_SIZE) { + std::cout << "Warning: [Player::genReservedStorageRange()] Player " << getName() << " with more than 500 outfits!" << std::endl; + break; + } + } + } +} + +void Player::addOutfit(uint32_t _looktype, uint32_t _addons) +{ + Outfit outfit; + outfit.looktype = _looktype; + outfit.addons = _addons; + m_playerOutfits.addOutfit(outfit); +} + +bool Player::remOutfit(uint32_t _looktype, uint32_t _addons) +{ + Outfit outfit; + outfit.looktype = _looktype; + outfit.addons = _addons; + return m_playerOutfits.remOutfit(outfit); +} + +uint32_t Player::getOutfitAddons(uint32_t looktype) +{ + const OutfitListType& outfits = m_playerOutfits.getOutfits(); + + for (OutfitListType::const_iterator it = outfits.begin(); it != outfits.end(); ++it) { + if ((*it)->looktype == looktype) { + return (*it)->addons; + } + } + + return 0; +} + +void Player::setSex(PlayerSex_t newSex) +{ + sex = newSex; + Outfits* outfits = Outfits::getInstance(); + const OutfitListType& global_outfits = outfits->getOutfits(sex); + OutfitListType::const_iterator it; + Outfit outfit; + + for (it = global_outfits.begin(); it != global_outfits.end(); ++it) { + outfit.looktype = (*it)->looktype; + outfit.addons = (*it)->addons; + outfit.premium = (*it)->premium; + m_playerOutfits.addOutfit(outfit); + } +} + +Skulls_t Player::getSkull() const +{ + if (hasFlag(PlayerFlag_NotGainInFight)) { + return SKULL_NONE; + } + + return skull; +} + +Skulls_t Player::getSkullClient(const Player* player) const +{ + if (!player || g_game.getWorldType() != WORLD_TYPE_PVP) { + return SKULL_NONE; + } + + if (player->getSkull() == SKULL_NONE) { + if (isInWar(player)) { + return SKULL_GREEN; + } + + if (!player->getGuildWarList().empty() && guild == player->getGuild()) { + return SKULL_GREEN; + } + + if (player->hasAttacked(this)) { + return SKULL_YELLOW; + } + + if (isPartner(player)) { + return SKULL_GREEN; + } + } + + return player->getSkull(); +} + +bool Player::hasAttacked(const Player* attacked) const +{ + if (hasFlag(PlayerFlag_NotGainInFight) || !attacked) { + return false; + } + + return attackedSet.find(attacked->getGUID()) != attackedSet.end(); +} + +void Player::addAttacked(const Player* attacked) +{ + if (hasFlag(PlayerFlag_NotGainInFight) || !attacked || attacked == this) { + return; + } + + if (attackedSet.find(attacked->getGUID()) == attackedSet.end()) { + attackedSet.insert(attacked->getGUID()); + } +} + +void Player::clearAttacked() +{ + attackedSet.clear(); +} + +void Player::addUnjustifiedDead(const Player* attacked) +{ + if (hasFlag(PlayerFlag_NotGainInFight) || attacked == this || g_game.getWorldType() == WORLD_TYPE_PVP_ENFORCED) { + return; + } + + if (client) { + std::ostringstream ss; + ss << "Warning! The murder of " << attacked->getName() << " was not justified."; + client->sendTextMessage(MSG_EVENT_ADVANCE, ss.str()); + } + + skullTicks += g_config.getNumber(ConfigManager::FRAG_TIME); + + if (g_config.getNumber(ConfigManager::KILLS_TO_BAN) != 0 && skullTicks > (g_config.getNumber(ConfigManager::KILLS_TO_BAN) - 1) * g_config.getNumber(ConfigManager::FRAG_TIME) && !IOBan::getInstance()->isAccountBanned(accountNumber)) { + IOBan::getInstance()->addAccountBan(accountNumber, time(NULL) + (g_config.getNumber(ConfigManager::BAN_DAYS) * 86400), 20, 2, "No comment.", 0); + + uint32_t playerId = getID(); + g_game.addMagicEffect(getPosition(), NM_ME_MAGIC_POISON); + g_scheduler.addEvent(createSchedulerTask(500, + boost::bind(&Game::kickPlayer, &g_game, playerId, false))); + } else if (getSkull() != SKULL_BLACK) { + if (g_config.getNumber(ConfigManager::KILLS_TO_BLACK) != 0 && skullTicks > (g_config.getNumber(ConfigManager::KILLS_TO_BLACK) - 1) * g_config.getNumber(ConfigManager::FRAG_TIME)) { + setSkull(SKULL_BLACK); + g_game.updatePlayerSkull(this); + } else if (getSkull() != SKULL_RED && g_config.getNumber(ConfigManager::KILLS_TO_RED) != 0 && skullTicks > (g_config.getNumber(ConfigManager::KILLS_TO_RED) - 1) * g_config.getNumber(ConfigManager::FRAG_TIME)) { + setSkull(SKULL_RED); + g_game.updatePlayerSkull(this); + } + } +} + +void Player::checkSkullTicks(int32_t ticks) +{ + int32_t newTicks = skullTicks - ticks; + + if (newTicks < 0) { + skullTicks = 0; + } else { + skullTicks = newTicks; + } + + if ((skull == SKULL_RED || skull == SKULL_BLACK) && skullTicks < 1000 && !hasCondition(CONDITION_INFIGHT)) { + setSkull(SKULL_NONE); + g_game.updatePlayerSkull(this); + } +} + +bool Player::isPromoted() const +{ + int32_t promotedVocation = g_vocations.getPromotedVocation(vocationId); + return promotedVocation == 0 && vocationId != promotedVocation; +} + +double Player::getLostPercent() const +{ + std::bitset<5> bitset(blessings); + + const int32_t deathLosePercent = g_config.getNumber(ConfigManager::DEATH_LOSE_PERCENT); + + if (deathLosePercent != -1) { + int32_t lossPercent = deathLosePercent; + + if (isPromoted()) { + lossPercent -= 3; + } + + lossPercent -= (int32_t)bitset.count(); + return std::max(0, lossPercent) / (double)100; + } else { + double lossPercent; + + if (level >= 25) { + double tmpLevel = level + (levelPercent / 100.); + lossPercent = (double)((tmpLevel + 50) * 50 * ((tmpLevel * tmpLevel) - (5 * tmpLevel) + 8)) / experience; + } else { + lossPercent = 10; + } + + if (isPromoted()) { + lossPercent *= 0.7; + } + + return lossPercent * pow(0.92, (int32_t)bitset.count()) / 100; + } +} + +void Player::learnInstantSpell(const std::string& name) +{ + if (!hasLearnedInstantSpell(name)) { + learnedInstantSpellList.push_back(name); + } +} + +bool Player::hasLearnedInstantSpell(const std::string& name) const +{ + if (hasFlag(PlayerFlag_CannotUseSpells)) { + return false; + } + + if (hasFlag(PlayerFlag_IgnoreSpellCheck)) { + return true; + } + + for (LearnedInstantSpellList::const_iterator it = learnedInstantSpellList.begin(); it != learnedInstantSpellList.end(); ++it) { + if (strcasecmp(it->c_str(), name.c_str()) == 0) { + return true; + } + } + + return false; +} + +bool Player::isInvitedToGuild(uint32_t guild_id) const +{ + for (InvitedToGuildsList::const_iterator it = invitedToGuildsList.begin(); it != invitedToGuildsList.end(); ++it) { + if ((*it) == guild_id) { + return true; + } + } + + return false; +} + +bool Player::isInWar(const Player* player) const +{ + if (!player || !guild) { + return false; + } + + const Guild* playerGuild = player->getGuild(); + + if (!playerGuild) { + return false; + } + + return isInWarList(playerGuild->getId()) && player->isInWarList(guild->getId()); +} + +bool Player::isInWarList(uint32_t guildId) const +{ + for (GuildWarList::const_iterator it = guildWarList.begin(); it != guildWarList.end(); ++it) { + if (*it == guildId) { + return true; + } + } + + return false; +} + +void Player::leaveGuild() +{ + sendClosePrivate(CHANNEL_GUILD); + guild = NULL; + guildNick = ""; + guildLevel = 0; + guildWarList.clear(); +} + +bool Player::isPremium() const +{ + if (g_config.getBoolean(ConfigManager::FREE_PREMIUM) || hasFlag(PlayerFlag_IsAlwaysPremium)) { + return true; + } + + return premiumDays > 0; +} + +void Player::setPremiumDays(int32_t v) +{ + premiumDays = v; + sendBasicData(); +} + +void Player::setGuildLevel(uint8_t newGuildLevel) +{ + guildLevel = newGuildLevel; + + if (guild) { + GuildRank* rank = guild->getRankByLevel(newGuildLevel); + if (rank) { + setGuildRank(rank->name); + } + } +} + +void Player::setGroupId(int32_t newId) +{ + const PlayerGroup* group = IOLoginData::getInstance()->getPlayerGroup(newId); + + if (group) { + groupId = newId; + groupName = group->m_name; + toLowerCaseString(groupName); + setFlags(group->m_flags); + accessLevel = (group->m_access >= 1); + + if (group->m_maxdepotitems > 0) { + maxDepotLimit = group->m_maxdepotitems; + } else if (isPremium()) { + maxDepotLimit = 2000; + } + + if (group->m_maxviplist > 0) { + maxVipLimit = group->m_maxviplist; + } else if (isPremium()) { + maxVipLimit = 100; + } + } +} + +PartyShields_t Player::getPartyShield(const Player* player) const +{ + if (!player) { + return SHIELD_NONE; + } + + Party* party = getParty(); + + if (party) { + if (party->getLeader() == player) { + if (party->isSharedExperienceActive()) { + if (party->isSharedExperienceEnabled()) { + return SHIELD_YELLOW_SHAREDEXP; + } + + if (party->canUseSharedExperience(player)) { + return SHIELD_YELLOW_NOSHAREDEXP; + } + + return SHIELD_YELLOW_NOSHAREDEXP_BLINK; + } + + return SHIELD_YELLOW; + } + + if (player->getParty() == party) { + if (party->isSharedExperienceActive()) { + if (party->isSharedExperienceEnabled()) { + return SHIELD_BLUE_SHAREDEXP; + } + + if (party->canUseSharedExperience(player)) { + return SHIELD_BLUE_NOSHAREDEXP; + } + + return SHIELD_BLUE_NOSHAREDEXP_BLINK; + } + + return SHIELD_BLUE; + } + + if (isInviting(player)) { + return SHIELD_WHITEBLUE; + } + } + + if (player->isInviting(this)) { + return SHIELD_WHITEYELLOW; + } + + if (player->getParty()) { + return SHIELD_GRAY; + } + + return SHIELD_NONE; +} + +bool Player::isInviting(const Player* player) const +{ + if (!player || !getParty() || getParty()->getLeader() != this) { + return false; + } + + return getParty()->isPlayerInvited(player); +} + +bool Player::isPartner(const Player* player) const +{ + if (!player || !getParty() || !player->getParty()) { + return false; + } + + return (getParty() == player->getParty()); +} + +bool Player::isGuildMate(const Player* player) const +{ + if (!player || !guild) { + return false; + } + + return guild == player->getGuild(); +} + +void Player::sendPlayerPartyIcons(Player* player) +{ + sendCreatureShield(player); + sendCreatureSkull(player); +} + +bool Player::addPartyInvitation(Party* party) +{ + if (!party) { + return false; + } + + PartyList::iterator it = std::find(invitePartyList.begin(), invitePartyList.end(), party); + + if (it != invitePartyList.end()) { + return false; + } + + invitePartyList.push_back(party); + return true; +} + +bool Player::removePartyInvitation(Party* party) +{ + if (!party) { + return false; + } + + PartyList::iterator it = std::find(invitePartyList.begin(), invitePartyList.end(), party); + + if (it == invitePartyList.end()) { + return false; + } + + invitePartyList.erase(it); + return true; +} + +void Player::clearPartyInvitations() +{ + if (!invitePartyList.empty()) { + PartyList list; + + for (PartyList::iterator it = invitePartyList.begin(); it != invitePartyList.end(); ++it) { + list.push_back(*it); + } + + invitePartyList.clear(); + + for (PartyList::iterator it = list.begin(); it != list.end(); ++it) { + (*it)->removeInvite(this); + } + } +} + +GuildEmblems_t Player::getGuildEmblem(const Player* player) const +{ + if (!player) { + return GUILDEMBLEM_NONE; + } + + const Guild* playerGuild = player->getGuild(); + + if (!playerGuild) { + return GUILDEMBLEM_NONE; + } + + if (player->getGuildWarList().empty()) { + if (guild == playerGuild) { + return GUILDEMBLEM_MEMBER; + } else { + return GUILDEMBLEM_OTHER; + } + } else if (guild == playerGuild) { + return GUILDEMBLEM_ALLY; + } else if (isInWar(player)) { + return GUILDEMBLEM_ENEMY; + } + + return GUILDEMBLEM_NEUTRAL; +} + +uint8_t Player::getCurrentMount() const +{ + int32_t value; + + if (getStorageValue(PSTRG_MOUNTS_CURRENTMOUNT, value)) { + return value; + } + + return 0; +} + +void Player::setCurrentMount(uint8_t mount) +{ + addStorageValue(PSTRG_MOUNTS_CURRENTMOUNT, mount); +} + +bool Player::toggleMount(bool mount) +{ + if ((OTSYS_TIME() - lastToggleMount) < 3000) { + sendCancelMessage(RET_YOUAREEXHAUSTED); + return false; + } + + if (mount) { + if (isMounted()) { + return false; + } + + if (!accessLevel && _tile->hasFlag(TILESTATE_PROTECTIONZONE)) { + sendCancelMessage(RET_ACTIONNOTPERMITTEDINPROTECTIONZONE); + return false; + } + + uint8_t currentMountId = getCurrentMount(); + + if (currentMountId == 0) { + sendOutfitWindow(); + return false; + } + + Mount* currentMount = Mounts::getInstance()->getMountByID(currentMountId); + + if (!currentMount) { + return false; + } + + if (!currentMount->isTamed(this)) { + setCurrentMount(0); + sendOutfitWindow(); + return false; + } + + if (currentMount->isPremium() && !isPremium()) { + sendCancelMessage(RET_YOUNEEDPREMIUMACCOUNT); + return false; + } + + if (hasCondition(CONDITION_OUTFIT)) { + sendCancelMessage(RET_NOTPOSSIBLE); + return false; + } + + defaultOutfit.lookMount = currentMount->getClientID(); + + if (currentMount->getSpeed() != 0) { + g_game.changeSpeed(this, currentMount->getSpeed()); + } + } else { + if (!isMounted()) { + return false; + } + + dismount(); + } + + g_game.internalCreatureChangeOutfit(this, defaultOutfit); + lastToggleMount = OTSYS_TIME(); + return true; +} + +bool Player::tameMount(uint8_t mountId) +{ + if (!Mounts::getInstance()->getMountByID(mountId)) { + return false; + } + + mountId--; + int key = PSTRG_MOUNTS_RANGE_START + (mountId / 31); + int32_t value = 0; + + if (getStorageValue(key, value)) { + value |= (1 << (mountId % 31)); + } else { + value = (1 << (mountId % 31)); + } + + addStorageValue(key, value); + return true; +} + +bool Player::untameMount(uint8_t mountId) +{ + if (!Mounts::getInstance()->getMountByID(mountId)) { + return false; + } + + mountId--; + int key = PSTRG_MOUNTS_RANGE_START + (mountId / 31); + int32_t value = 0; + + if (!getStorageValue(key, value)) { + return true; + } + + value &= ~(1 << (mountId % 31)); + addStorageValue(key, value); + + if (getCurrentMount() == (mountId + 1)) { + if (isMounted()) { + dismount(); + g_game.internalCreatureChangeOutfit(this, defaultOutfit); + } + + setCurrentMount(0); + } + + return true; +} + +void Player::dismount() +{ + Mount* mount = Mounts::getInstance()->getMountByID(getCurrentMount()); + + if (mount && mount->getSpeed() > 0) { + g_game.changeSpeed(this, -mount->getSpeed()); + } + + defaultOutfit.lookMount = 0; +} + +bool Player::addOfflineTrainingTries(skills_t skill, int32_t tries) +{ + if (tries <= 0 || skill == SKILL__LEVEL) { + return false; + } + + bool sendUpdate = false; + uint32_t oldSkillValue, newSkillValue; + long double oldPercentToNextLevel, newPercentToNextLevel; + + if (skill == SKILL__MAGLEVEL) { + uint64_t currReqMana = vocation->getReqMana(magLevel); + uint64_t nextReqMana = vocation->getReqMana(magLevel + 1); + + if (currReqMana >= nextReqMana) { + return false; + } + + oldSkillValue = magLevel; + oldPercentToNextLevel = (long double)(manaSpent * 100) / nextReqMana; + + tries *= g_config.getNumber(ConfigManager::RATE_MAGIC); + uint32_t currMagLevel = magLevel; + + while ((manaSpent + tries) >= nextReqMana) { + tries -= nextReqMana - manaSpent; + + magLevel++; + manaSpent = 0; + + g_creatureEvents->playerAdvance(this, SKILL__MAGLEVEL, magLevel - 1, magLevel); + + sendUpdate = true; + currReqMana = nextReqMana; + nextReqMana = vocation->getReqMana(magLevel + 1); + + if (currReqMana >= nextReqMana) { + tries = 0; + break; + } + } + + manaSpent += tries; + + if (magLevel != currMagLevel) { + std::ostringstream ss; + ss << "You advanced to magic level " << magLevel << "."; + sendTextMessage(MSG_EVENT_ADVANCE, ss.str()); + } + + uint32_t newPercent; + + if (nextReqMana > currReqMana) { + newPercent = Player::getPercentLevel(manaSpent, nextReqMana); + newPercentToNextLevel = (long double)(manaSpent * 100) / nextReqMana; + } else { + newPercent = 0; + newPercentToNextLevel = 0; + } + + if (newPercent != magLevelPercent) { + magLevelPercent = newPercent; + sendUpdate = true; + } + + newSkillValue = magLevel; + } else { + uint64_t currReqTries = vocation->getReqSkillTries(skill, skills[skill][SKILL_LEVEL]); + uint64_t nextReqTries = vocation->getReqSkillTries(skill, skills[skill][SKILL_LEVEL] + 1); + + if (currReqTries >= nextReqTries) { + return false; + } + + oldSkillValue = skills[skill][SKILL_LEVEL]; + oldPercentToNextLevel = (long double)(skills[skill][SKILL_TRIES] * 100) / nextReqTries; + + tries *= g_config.getNumber(ConfigManager::RATE_SKILL); + uint32_t currSkillLevel = skills[skill][SKILL_LEVEL]; + + while ((skills[skill][SKILL_TRIES] + tries) >= nextReqTries) { + tries -= nextReqTries - skills[skill][SKILL_TRIES]; + + skills[skill][SKILL_LEVEL]++; + skills[skill][SKILL_TRIES] = 0; + skills[skill][SKILL_PERCENT] = 0; + + g_creatureEvents->playerAdvance(this, skill, (skills[skill][SKILL_LEVEL] - 1), skills[skill][SKILL_LEVEL]); + + sendUpdate = true; + currReqTries = nextReqTries; + nextReqTries = vocation->getReqSkillTries(skill, skills[skill][SKILL_LEVEL] + 1); + + if (currReqTries >= nextReqTries) { + tries = 0; + break; + } + } + + skills[skill][SKILL_TRIES] += tries; + + if (currSkillLevel != skills[skill][SKILL_LEVEL]) { + std::ostringstream ss; + ss << "You advanced to " << getSkillName(skill) << " level " << skills[skill][SKILL_LEVEL] << "."; + sendTextMessage(MSG_EVENT_ADVANCE, ss.str()); + } + + uint32_t newPercent; + + if (nextReqTries > currReqTries) { + newPercent = Player::getPercentLevel(skills[skill][SKILL_TRIES], nextReqTries); + newPercentToNextLevel = (long double)(skills[skill][SKILL_TRIES] * 100) / nextReqTries; + } else { + newPercent = 0; + newPercentToNextLevel = 0; + } + + if (skills[skill][SKILL_PERCENT] != newPercent) { + skills[skill][SKILL_PERCENT] = newPercent; + sendUpdate = true; + } + + newSkillValue = skills[skill][SKILL_LEVEL]; + } + + std::ostringstream ss; + ss << std::fixed << std::setprecision(2) << "Your " << ucwords(getSkillName(skill)) << " skill changed from level " << oldSkillValue << " (with " << oldPercentToNextLevel << "% progress towards level " << (oldSkillValue + 1) << ") to level " << newSkillValue << " (with " << newPercentToNextLevel << "% progress towards level " << (newSkillValue + 1) << ")"; + sendTextMessage(MSG_EVENT_ADVANCE, ss.str()); + return sendUpdate; +} + +bool Player::hasModalWindowOpen(uint32_t modalWindowId) const +{ + return find(modalWindows.begin(), modalWindows.end(), modalWindowId) != modalWindows.end(); +} + +void Player::onModalWindowHandled(uint32_t modalWindowId) +{ + modalWindows.remove(modalWindowId); +} + +void Player::sendModalWindow(const ModalWindow& modalWindow) +{ + if (!client) { + return; + } + + modalWindows.push_back(modalWindow.getID()); + client->sendModalWindow(modalWindow); +} + +void Player::clearModalWindows() +{ + modalWindows.clear(); +} + +void Player::regenerateStamina(int32_t offlineTime) +{ + if (!g_config.getBoolean(ConfigManager::STAMINA_SYSTEM)) { + return; + } + + offlineTime -= 600; + + if (offlineTime < 180) { + return; + } + + int16_t regainStaminaMinutes = offlineTime / 180; + int16_t maxNormalStaminaRegen = 2400 - std::min(2400, staminaMinutes); + + if (regainStaminaMinutes > maxNormalStaminaRegen) { + int16_t happyHourStaminaRegen = (offlineTime - (maxNormalStaminaRegen * 180)) / 600; + staminaMinutes = std::min(2520, std::max(2400, staminaMinutes) + happyHourStaminaRegen); + } else { + staminaMinutes += regainStaminaMinutes; + } +} + +void Player::useStamina() +{ + if (!g_config.getBoolean(ConfigManager::STAMINA_SYSTEM) || staminaMinutes == 0) { + return; + } + + time_t currentTime = time(NULL); + + if (currentTime > nextUseStaminaTime) { + time_t timePassed = currentTime - nextUseStaminaTime; + + if (timePassed > 60) { + if (staminaMinutes > 2) { + staminaMinutes -= 2; + } else { + staminaMinutes = 0; + } + + nextUseStaminaTime = currentTime + 120; + } else { + --staminaMinutes; + nextUseStaminaTime = currentTime + 60; + } + + sendStats(); + } +} + +uint16_t Player::getHelpers() const +{ + uint16_t helpers; + + if (guild && party) { + OTSERV_HASH_SET helperSet; + + const PlayerVector& guildMembers = guild->getMembersOnline(); + helperSet.insert(guildMembers.begin(), guildMembers.end()); + + const PlayerVector& partyMembers = party->getMembers(); + helperSet.insert(partyMembers.begin(), partyMembers.end()); + + const PlayerVector& partyInvitees = party->getInvitees(); + helperSet.insert(partyInvitees.begin(), partyInvitees.end()); + + helperSet.insert(party->getLeader()); + + helpers = helperSet.size(); + } else if (guild) { + helpers = guild->getMembersOnlineCount(); + } else if (party) { + helpers = party->getMemberCount() + party->getInvitationCount() + 1; + } else { + helpers = 0; + } + + return helpers; +} + +void Player::sendClosePrivate(uint16_t channelId) +{ + if (channelId == CHANNEL_GUILD || channelId == CHANNEL_PARTY) { + g_chat.removeUserFromChannel(this, channelId); + } + + if (client) { + client->sendClosePrivate(channelId); + } +} diff --git a/src/player.h b/src/player.h new file mode 100644 index 0000000000..8c0007ea6a --- /dev/null +++ b/src/player.h @@ -0,0 +1,1459 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_PLAYER_H__ +#define __OTSERV_PLAYER_H__ + +#include "otsystem.h" +#include "creature.h" +#include "container.h" +#include "cylinder.h" +#include "outfit.h" +#include "enums.h" +#include "vocation.h" +#include "protocolgame.h" +#include "ioguild.h" +#include "party.h" +#include "inbox.h" +#include "depotchest.h" +#include "depotlocker.h" +#include "guild.h" + +#include +#include +#include + +class House; +class NetworkMessage; +class Weapon; +class ProtocolGame; +class Npc; +class Party; +class SchedulerTask; +class Bed; +class Guild; + +enum skillsid_t { + SKILL_LEVEL = 0, + SKILL_TRIES = 1, + SKILL_PERCENT = 2 +}; + +enum playerinfo_t { + PLAYERINFO_LEVEL, + PLAYERINFO_LEVELPERCENT, + PLAYERINFO_HEALTH, + PLAYERINFO_MAXHEALTH, + PLAYERINFO_MANA, + PLAYERINFO_MAXMANA, + PLAYERINFO_MAGICLEVEL, + PLAYERINFO_MAGICLEVELPERCENT, + PLAYERINFO_SOUL, +}; + +enum freeslot_t { + SLOT_TYPE_NONE, + SLOT_TYPE_INVENTORY, + SLOT_TYPE_CONTAINER +}; + +enum chaseMode_t { + CHASEMODE_STANDSTILL, + CHASEMODE_FOLLOW, +}; + +enum fightMode_t { + FIGHTMODE_ATTACK, + FIGHTMODE_BALANCED, + FIGHTMODE_DEFENSE +}; + +enum secureMode_t { + SECUREMODE_ON, + SECUREMODE_OFF +}; + +enum tradestate_t { + TRADE_NONE, + TRADE_INITIATED, + TRADE_ACCEPT, + TRADE_ACKNOWLEDGE, + TRADE_TRANSFER +}; + +struct VIPEntry { + uint32_t guid; + std::string name; + std::string description; + uint32_t icon; + bool notify; +}; + +struct OpenContainer { + Container* container; + uint16_t index; +}; + +typedef std::map ContainerMap; +typedef std::map DepotMap; +typedef std::map DepotLockerMap; +typedef std::map StorageMap; +typedef OTSERV_HASH_SET VIPListSet; +typedef std::map MuteCountMap; +typedef std::list LearnedInstantSpellList; +typedef std::list InvitedToGuildsList; +typedef std::list PartyList; +typedef std::vector GuildWarList; + +#define PLAYER_MAX_SPEED 1500 +#define PLAYER_MIN_SPEED 10 + +class Player : public Creature, public Cylinder +{ + public: +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + static uint32_t playerCount; +#endif + Player(const std::string& name, ProtocolGame* p); + virtual ~Player(); + + virtual Player* getPlayer() { + return this; + } + virtual const Player* getPlayer() const { + return this; + } + + static MuteCountMap muteCountMap; + static int32_t maxMessageBuffer; + + virtual const std::string& getName() const { + return name; + } + virtual const std::string& getNameDescription() const { + return name; + } + virtual std::string getDescription(int32_t lookDistance) const; + + virtual CreatureType_t getType() const { + return CREATURETYPE_PLAYER; + } + + uint8_t getCurrentMount() const; + void setCurrentMount(uint8_t mountId); + bool isMounted() const { + return defaultOutfit.lookMount != 0; + } + bool toggleMount(bool mount); + bool tameMount(uint8_t mountId); + bool untameMount(uint8_t mountId); + void dismount(); + + void setDepotChange(bool b) { + depotChange = b; + } + + void sendFYIBox(const std::string& message) { + if (client) { + client->sendFYIBox(message); + } + } + + void setGUID(uint32_t _guid) { + guid = _guid; + } + uint32_t getGUID() const { + return guid; + } + virtual uint32_t idRange() { + return 0x10000000; + } + virtual bool canSeeInvisibility() const { + return hasFlag(PlayerFlag_CanSenseInvisibility) || accessLevel; + } + static AutoList listPlayer; + void removeList(); + void addList(); + void kickPlayer(bool displayEffect); + + static uint64_t getExpForLevel(int32_t lv) { + lv--; + return ((50ULL * lv * lv * lv) - (150ULL * lv * lv) + (400ULL * lv)) / 3ULL; + } + + void incrementMoveItemsBuffer() { + moveItemsBuffer++; + } + void incrementMoveItemsBuffer(int32_t modifier) { + moveItemsBuffer += modifier; + } + uint32_t getMoveItemsBuffer() { + return moveItemsBuffer; + } + int64_t getLastMoveItemTime() { + return lastMoveItemTime; + } + void updateLastMoveItemTime() { + lastMoveItemTime = OTSYS_TIME(); + } + + uint16_t getStaminaMinutes() const { + return staminaMinutes; + } + void regenerateStamina(int32_t offlineTime); + void useStamina(); + + bool addOfflineTrainingTries(skills_t skill, int32_t tries); + + void addOfflineTrainingTime(int32_t addTime) { + offlineTrainingTime = std::min(12 * 3600 * 1000, offlineTrainingTime + addTime); + } + void removeOfflineTrainingTime(int32_t removeTime) { + offlineTrainingTime = std::max(0, offlineTrainingTime - removeTime); + } + int32_t getOfflineTrainingTime() const { + return offlineTrainingTime; + } + + int32_t getOfflineTrainingSkill() const { + return offlineTrainingSkill; + } + void setOfflineTrainingSkill(int32_t skill) { + offlineTrainingSkill = skill; + } + + uint64_t getBankBalance() const { + return bankBalance; + } + void setBankBalance(uint64_t balance) { + bankBalance = balance; + } + + Guild* getGuild() const { + return guild; + } + void setGuild(Guild* guild) { + this->guild = guild; + } + + int8_t getGuildLevel() const { + return guildLevel; + } + void setGuildLevel(uint8_t newGuildLevel); + + bool isGuildMate(const Player* player) const; + + void setGuildRank(const std::string& rank) { + guildRank = rank; + } + const std::string& getGuildNick() const { + return guildNick; + } + void setGuildNick(const std::string& nick) { + guildNick = nick; + } + + bool isInWar(const Player* player) const; + bool isInWarList(uint32_t guild_id) const; + bool isInvitedToGuild(uint32_t guild_id) const; + void leaveGuild(); + + void setLastWalkthroughAttempt(int64_t walkthroughAttempt) { + lastWalkthroughAttempt = walkthroughAttempt; + } + void setLastWalkthroughPosition(Position walkthroughPosition) { + lastWalkthroughPosition = walkthroughPosition; + } + + bool hasRequestedOutfit() const { + return requestedOutfit; + } + void hasRequestedOutfit(bool newValue) { + requestedOutfit = newValue; + } + + Inbox* getInbox() const { + return inbox; + } + const DepotMap& getDepotChests() const { + return depotChests; + } + + uint32_t getClientIcons() const; + + const GuildWarList& getGuildWarList() const { + return guildWarList; + } + void setGuildWarList(GuildWarList _guildWarList) { + guildWarList = _guildWarList; + } + + Vocation* getVocation() const { + return vocation; + } + + OperatingSystem_t getOperatingSystem() const { + return operatingSystem; + } + void setOperatingSystem(OperatingSystem_t clientos) { + operatingSystem = clientos; + } + + uint16_t getProtocolVersion() const { + if (!client) { + return 0; + } + + return client->getVersion(); + } + + secureMode_t getSecureMode() const { + return secureMode; + } + + void setParty(Party* _party) { + party = _party; + } + Party* getParty() const { + return party; + } + PartyShields_t getPartyShield(const Player* player) const; + bool isInviting(const Player* player) const; + bool isPartner(const Player* player) const; + void sendPlayerPartyIcons(Player* player); + bool addPartyInvitation(Party* party); + bool removePartyInvitation(Party* party); + void clearPartyInvitations(); + + GuildEmblems_t getGuildEmblem(const Player* player) const; + + uint64_t getSpentMana() const { + return manaSpent; + } + + bool isLagging() const { + return lagging; + } + void setLagging(bool v) { + lagging = v; + } + + void setFlags(uint64_t flags) { + groupFlags = flags; + } + bool hasFlag(PlayerFlags value) const { + return (0 != (groupFlags & ((uint64_t)1 << value))); + } + + BedItem* getBedItem() { + return bedItem; + } + void setBedItem(BedItem* b) { + bedItem = b; + } + + void addBlessing(int16_t blessing) { + blessings += blessing; + } + bool hasBlessing(int16_t value) const { + return (0 != (blessings & ((int16_t)1 << value))); + } + + bool isFemale() const { + return sex == PLAYERSEX_FEMALE; + } + bool isMale() const { + return sex == PLAYERSEX_MALE; + } + + bool isOffline() const { + return (getID() == 0); + } + void disconnect() { + if (client) { + client->disconnect(); + } + } + uint32_t getIP() const; + + void addContainer(uint8_t cid, Container* container); + void closeContainer(uint8_t cid); + void setContainerIndex(uint8_t cid, uint16_t index); + + Container* getContainer(uint8_t cid); + int8_t getContainerID(const Container* container) const; + uint16_t getContainerIndex(uint8_t cid) const; + + bool canOpenCorpse(uint32_t ownerId); + + void addStorageValue(const uint32_t key, const int32_t value, const bool isLogin = false); + bool getStorageValue(const uint32_t key, int32_t& value) const; + void genReservedStorageRange(); + + inline StorageMap::const_iterator getStorageIteratorBegin() const { + return storageMap.begin(); + } + inline StorageMap::const_iterator getStorageIteratorEnd() const { + return storageMap.end(); + } + + ProtocolGame* getCastingProtocol() const { + return castingProtocol; + } + void setCastingProtocol(ProtocolGame* castingProtocol) { + this->castingProtocol = castingProtocol; + } + + const std::string& getCastingPassword() const { + return castPassword; + } + + void setGroupId(int32_t newId); + int32_t getGroupId() const { + return groupId; + } + + void setMarketDepotId(int16_t newId) { + marketDepotId = newId; + } + int16_t getMarketDepotId() const { + return marketDepotId; + } + + void setLastDepotId(int16_t newId) { + lastDepotId = newId; + } + int16_t getLastDepotId() const { + return lastDepotId; + } + + void resetIdleTime() { + idleTime = 0; + } + bool getNoMove() const { + return mayNotMove; + } + + bool isInGhostMode() const { + return ghostMode; + } + void switchGhostMode() { + ghostMode = !ghostMode; + } + + uint32_t getAccount() const { + return accountNumber; + } + AccountType_t getAccountType() const { + return accountType; + } + uint32_t getLevel() const { + return level; + } + int32_t getMagicLevel() const { + return getPlayerInfo(PLAYERINFO_MAGICLEVEL); + } + uint32_t getBaseMagicLevel() const { + return magLevel; + } + bool isAccessPlayer() const { + return accessLevel; + } + bool isPremium() const; + void setPremiumDays(int32_t v); + + uint16_t getHelpers() const; + + void setVocation(uint32_t vocId); + uint32_t getVocationId() const { + return vocationId; + } + + PlayerSex_t getSex() const { + return sex; + } + void setSex(PlayerSex_t); + int32_t getPlayerInfo(playerinfo_t playerinfo) const; + uint64_t getExperience() const { + return experience; + } + + time_t getLastLoginSaved() const { + return lastLoginSaved; + } + + time_t getLastLogout() const { + return lastLogout; + } + + const Position& getLoginPosition() const { + return loginPosition; + } + const Position& getTemplePosition() const { + return masterPos; + } + uint32_t getTown() const { + return town; + } + void setTown(uint32_t _town) { + town = _town; + } + + void clearModalWindows(); + bool hasModalWindowOpen(uint32_t modalWindowId) const; + void onModalWindowHandled(uint32_t modalWindowId); + + virtual bool isPushable() const; + virtual int32_t getThrowRange() const { + return 1; + } + uint32_t isMuted(); + void addMessageBuffer(); + void removeMessageBuffer(); + + double getCapacity() const { + if (hasFlag(PlayerFlag_CannotPickupItem)) { + return 0.00; + } else if (hasFlag(PlayerFlag_HasInfiniteCapacity)) { + return 10000.00; + } else { + return capacity; + } + } + + double getFreeCapacity() const { + if (hasFlag(PlayerFlag_CannotPickupItem)) { + return 0.00; + } else if (hasFlag(PlayerFlag_HasInfiniteCapacity)) { + return 10000.00; + } else { + return std::max(0.00, capacity - inventoryWeight); + } + } + + virtual int32_t getMaxHealth() const { + return getPlayerInfo(PLAYERINFO_MAXHEALTH); + } + virtual int32_t getMaxMana() const { + return getPlayerInfo(PLAYERINFO_MAXMANA); + } + + Item* getInventoryItem(slots_t slot) const; + + bool isItemAbilityEnabled(slots_t slot) const { + return inventoryAbilities[slot]; + } + void setItemAbility(slots_t slot, bool enabled) { + inventoryAbilities[slot] = enabled; + } + + int32_t getBaseSkill(skills_t skill) const { + return skills[skill][SKILL_LEVEL]; + } + + int32_t getVarSkill(skills_t skill) const { + return varSkills[skill]; + } + void setVarSkill(skills_t skill, int32_t modifier) { + varSkills[skill] += modifier; + } + + int32_t getVarStats(stats_t stat) const { + return varStats[stat]; + } + void setVarStats(stats_t stat, int32_t modifier); + int32_t getDefaultStats(stats_t stat); + + void setConditionSuppressions(uint32_t conditions, bool remove); + + DepotChest* getDepotChest(uint32_t depotId, bool autoCreate); + DepotLocker* getDepotLocker(uint32_t depotId); + void onReceiveMail(); + bool isNearDepotBox(); + + virtual bool canSee(const Position& pos) const; + virtual bool canSeeCreature(const Creature* creature) const; + + bool canWalkthrough(const Creature* creature) const; + bool canWalkthroughEx(const Creature* creature) const; + + virtual RaceType_t getRace() const { + return RACE_BLOOD; + } + + //safe-trade functions + void setTradeState(tradestate_t state) { + tradeState = state; + } + tradestate_t getTradeState() { + return tradeState; + } + Item* getTradeItem() { + return tradeItem; + } + + //shop functions + void setShopOwner(Npc* owner, int32_t onBuy, int32_t onSell) { + shopOwner = owner; + purchaseCallback = onBuy; + saleCallback = onSell; + } + + Npc* getShopOwner(int32_t& onBuy, int32_t& onSell) { + onBuy = purchaseCallback; + onSell = saleCallback; + return shopOwner; + } + + const Npc* getShopOwner(int32_t& onBuy, int32_t& onSell) const { + onBuy = purchaseCallback; + onSell = saleCallback; + return shopOwner; + } + + //V.I.P. functions + void notifyStatusChange(Player* player, VipStatus_t status); + bool removeVIP(uint32_t guid); + bool addVIP(uint32_t guid, std::string& name, VipStatus_t status); + bool addVIPInternal(uint32_t guid); + bool editVIP(uint32_t _guid, const std::string& description, uint32_t icon, bool notify); + + //follow functions + virtual bool setFollowCreature(Creature* creature, bool fullPathSearch = false); + virtual void goToFollowCreature(); + + //follow events + virtual void onFollowCreature(const Creature* creature); + + //walk events + virtual void onWalk(Direction& dir); + virtual void onWalkAborted(); + virtual void onWalkComplete(); + + void stopWalk(); + void openShopWindow(Npc* npc, const std::list& shop); + void closeShopWindow(bool sendCloseShopWindow = true); + void updateSaleShopList(uint32_t itemId); + bool hasShopItemForSale(uint32_t itemId, uint8_t subType); + + void setChaseMode(chaseMode_t mode); + void setFightMode(fightMode_t mode); + void setSecureMode(secureMode_t mode) { + secureMode = mode; + } + + //combat functions + virtual bool setAttackedCreature(Creature* creature); + bool isImmune(CombatType_t type) const; + bool isImmune(ConditionType_t type) const; + bool hasShield() const; + virtual bool isAttackable() const; + + virtual void changeHealth(int32_t healthChange, bool sendHealthChange = true); + virtual void changeMana(int32_t manaChange); + void changeSoul(int32_t soulChange); + + bool isPzLocked() const { + return pzLocked; + } + virtual BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, + bool checkDefense = false, bool checkArmor = false); + virtual void doAttacking(uint32_t interval); + virtual bool hasExtraSwing() { + return lastAttack > 0 && ((OTSYS_TIME() - lastAttack) >= getAttackSpeed()); + } + int32_t getShootRange() const { + return shootRange; + } + + int32_t getSkill(skills_t skilltype, skillsid_t skillinfo) const; + bool getAddAttackSkill() const { + return addAttackSkillPoint; + } + BlockType_t getLastAttackBlockType() const { + return lastAttackBlockType; + } + + Item* getWeapon(bool ignoreAmmo = false); + virtual WeaponType_t getWeaponType(); + int32_t getWeaponSkill(const Item* item) const; + void getShieldAndWeapon(const Item* &shield, const Item* &weapon) const; + + virtual void drainHealth(Creature* attacker, CombatType_t combatType, int32_t damage); + virtual void drainMana(Creature* attacker, int32_t manaLoss); + void addManaSpent(uint64_t amount, bool withMultiplier = true); + void addSkillAdvance(skills_t skill, uint32_t count); + + virtual int32_t getArmor() const; + virtual int32_t getDefense() const; + virtual float getAttackFactor() const; + virtual float getDefenseFactor() const; + + void addWeaponExhaust(uint32_t ticks); + void addCombatExhaust(uint32_t ticks); + void addHealExhaust(uint32_t ticks); + void addInFightTicks(bool pzlock = false); + void addDefaultRegeneration(uint32_t addTicks); + + virtual uint64_t getGainedExperience(Creature* attacker) const; + + //combat event functions + virtual void onAddCondition(ConditionType_t type); + virtual void onAddCombatCondition(ConditionType_t type); + virtual void onEndCondition(ConditionType_t type); + virtual void onCombatRemoveCondition(const Creature* attacker, Condition* condition); + virtual void onAttackedCreature(Creature* target); + virtual void onAttacked(); + virtual void onAttackedCreatureDrainHealth(Creature* target, int32_t points); + virtual void onTargetCreatureGainHealth(Creature* target, int32_t points); + virtual bool onKilledCreature(Creature* target, bool lastHit = true); + virtual void onGainExperience(uint64_t gainExp, Creature* target); + virtual void onGainSharedExperience(uint64_t gainExp); + virtual void onAttackedCreatureBlockHit(Creature* target, BlockType_t blockType); + virtual void onBlockHit(BlockType_t blockType); + virtual void onChangeZone(ZoneType_t zone); + virtual void onAttackedCreatureChangeZone(ZoneType_t zone); + virtual void onIdleStatus(); + virtual void onPlacedCreature(); + virtual void onRemovedCreature(); + + virtual void getCreatureLight(LightInfo& light) const; + + Skulls_t getSkull() const; + Skulls_t getSkullClient(const Player* player) const; + + bool hasAttacked(const Player* attacked) const; + void addAttacked(const Player* attacked); + void clearAttacked(); + void addUnjustifiedDead(const Player* attacked); + void setSkull(Skulls_t newSkull) { + skull = newSkull; + } + void sendCreatureSkull(const Creature* creature) const { + if (client) { + client->sendCreatureSkull(creature); + } + } + void checkSkullTicks(int32_t ticks); + + const OutfitListType& getPlayerOutfits(); + bool canWear(uint32_t _looktype, uint32_t _addons); + void addOutfit(uint32_t _looktype, uint32_t _addons); + bool remOutfit(uint32_t _looktype, uint32_t _addons); + uint32_t getOutfitAddons(uint32_t looktype); + bool canLogout(); + + //tile + //send methods + void sendAddTileItem(const Tile* tile, const Position& pos, const Item* item) { + if (client) { + client->sendAddTileItem(tile, pos, tile->getClientIndexOfThing(this, item), item); + } + } + void sendUpdateTileItem(const Tile* tile, const Position& pos, const Item* olditem, const Item* newitem) { + if (client) { + client->sendUpdateTileItem(tile, pos, tile->getClientIndexOfThing(this, newitem), newitem); + } + } + void sendRemoveTileItem(const Tile* tile, const Position& pos, uint32_t stackpos, const Item* item) { + if (client) { + client->sendRemoveTileItem(tile, pos, stackpos); + } + } + void sendUpdateTile(const Tile* tile, const Position& pos) { + if (client) { + client->sendUpdateTile(tile, pos); + } + } + + void sendChannelMessage(const std::string& author, const std::string& text, SpeakClasses type, uint16_t channel) { + if (client) { + client->sendChannelMessage(author, text, type, channel); + } + } + void sendChannelEvent(uint16_t channelId, const std::string& playerName, ChannelEvent_t channelEvent) { + if (client) { + client->sendChannelEvent(channelId, playerName, channelEvent); + } + } + void sendCreatureAppear(const Creature* creature, const Position& pos, bool isLogin) { + if (client) { + client->sendAddCreature(creature, pos, creature->getTile()->getClientIndexOfThing(this, creature), isLogin); + } + } + void sendCreatureDisappear(const Creature* creature, uint32_t stackpos, bool isLogout) { + if (client) { + client->sendRemoveCreature(creature, creature->getPosition(), stackpos, isLogout); + } + } + void sendCreatureMove(const Creature* creature, const Tile* newTile, const Position& newPos, + const Tile* oldTile, const Position& oldPos, uint32_t oldStackPos, bool teleport) { + if (client) { + client->sendMoveCreature(creature, newTile, newPos, newTile->getClientIndexOfThing(this, creature), oldTile, oldPos, oldStackPos, teleport); + } + } + + void sendCreatureTurn(const Creature* creature) { + if (client) { + client->sendCreatureTurn(creature, creature->getTile()->getClientIndexOfThing(this, creature)); + } + } + void sendCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, Position* pos = NULL) { + if (client) { + client->sendCreatureSay(creature, type, text, pos); + } + } + void sendCreatureSquare(const Creature* creature, SquareColor_t color) { + if (client) { + client->sendCreatureSquare(creature, color); + } + } + void sendCreatureChangeOutfit(const Creature* creature, const Outfit_t& outfit) { + if (client) { + client->sendCreatureOutfit(creature, outfit); + } + } + void sendCreatureChangeVisible(const Creature* creature, bool visible) { + if (client) { + if (visible) { + client->sendCreatureOutfit(creature, creature->getCurrentOutfit()); + } else { + client->sendCreatureInvisible(creature); + } + } + } + void sendCreatureLight(const Creature* creature) { + if (client) { + client->sendCreatureLight(creature); + } + } + void sendCreatureWalkthrough(const Creature* creature, bool walkthrough) { + if (client) { + client->sendCreatureWalkthrough(creature, walkthrough); + } + } + void sendCreatureShield(const Creature* creature) { + if (client) { + client->sendCreatureShield(creature); + } + } + void sendCreatureType(uint32_t creatureId, uint8_t creatureType) { + if (client) { + client->sendCreatureType(creatureId, creatureType); + } + } + void sendCreatureHelpers(uint32_t creatureId, uint16_t helpers) { + if (client) { + client->sendCreatureHelpers(creatureId, helpers); + } + } + void sendSpellCooldown(uint8_t spellId, uint32_t time) { + if (client) { + client->sendSpellCooldown(spellId, time); + } + } + void sendSpellGroupCooldown(SpellGroup_t groupId, uint32_t time) { + if (client) { + client->sendSpellGroupCooldown(groupId, time); + } + } + + void sendDamageMessage(MessageClasses mclass, const std::string& message, const Position& pos, + uint32_t primaryDamage = 0, TextColor_t primaryColor = TEXTCOLOR_NONE, + uint32_t secondaryDamage = 0, TextColor_t secondaryColor = TEXTCOLOR_NONE) { + if (client) { + client->sendDamageMessage(mclass, message, pos, primaryDamage, primaryColor, secondaryDamage, secondaryColor); + } + } + void sendHealMessage(MessageClasses mclass, const std::string& message, const Position& pos, uint32_t heal, TextColor_t color) { + if (client) { + client->sendHealMessage(mclass, message, pos, heal, color); + } + } + void sendExperienceMessage(MessageClasses mclass, const std::string& message, const Position& pos, uint32_t exp, TextColor_t color) { + if (client) { + client->sendExperienceMessage(mclass, message, pos, exp, color); + } + } + void sendModalWindow(const ModalWindow& modalWindow); + + //container + void sendAddContainerItem(const Container* container, const Item* item); + void sendUpdateContainerItem(const Container* container, uint16_t slot, const Item* oldItem, const Item* newItem); + void sendRemoveContainerItem(const Container* container, uint16_t slot, const Item* lastItem); + void sendContainer(uint8_t cid, const Container* container, bool hasParent, uint16_t firstIndex) { + if (client) { + client->sendContainer(cid, container, hasParent, firstIndex); + } + } + + //inventory + void sendInventoryItem(slots_t slot, const Item* item) { + if (client) { + client->sendInventoryItem(slot, item); + } + } + + //event methods + virtual void onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem, + const ItemType& oldType, const Item* newItem, const ItemType& newType); + virtual void onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType, + const Item* item); + + virtual void onCreatureAppear(const Creature* creature, bool isLogin); + virtual void onCreatureDisappear(const Creature* creature, uint32_t stackpos, bool isLogout); + virtual void onCreatureMove(const Creature* creature, const Tile* newTile, const Position& newPos, + const Tile* oldTile, const Position& oldPos, bool teleport); + + virtual void onAttackedCreatureDisappear(bool isLogout); + virtual void onFollowCreatureDisappear(bool isLogout); + + //container + void onAddContainerItem(const Container* container, const Item* item); + void onUpdateContainerItem(const Container* container, uint16_t slot, + const Item* oldItem, const ItemType& oldType, const Item* newItem, const ItemType& newType); + void onRemoveContainerItem(const Container* container, uint16_t slot, const Item* item); + + void onCloseContainer(const Container* container); + void onSendContainer(const Container* container); + void autoCloseContainers(const Container* container); + + //inventory + void onAddInventoryItem(slots_t slot, Item* item); + void onUpdateInventoryItem(slots_t slot, Item* oldItem, const ItemType& oldType, + Item* newItem, const ItemType& newType); + void onRemoveInventoryItem(slots_t slot, Item* item); + + void sendCancel(const std::string& msg) const { + if (client) { + client->sendCancel(msg); + } + } + void sendCancelMessage(ReturnValue message) const; + void sendCancelTarget() const { + if (client) { + client->sendCancelTarget(); + } + } + void sendCancelWalk() const { + if (client) { + client->sendCancelWalk(); + } + } + void sendChangeSpeed(const Creature* creature, uint32_t newSpeed) const { + if (client) { + client->sendChangeSpeed(creature, newSpeed); + } + } + void sendCreatureHealth(const Creature* creature) const { + if (client) { + client->sendCreatureHealth(creature); + } + } + void sendDistanceShoot(const Position& from, const Position& to, unsigned char type) const { + if (client) { + client->sendDistanceShoot(from, to, type); + } + } + void sendHouseWindow(House* house, uint32_t listId) const; + void sendCreatePrivateChannel(uint16_t channelId, const std::string& channelName) { + if (client) { + client->sendCreatePrivateChannel(channelId, channelName); + } + } + void sendClosePrivate(uint16_t channelId); + void sendIcons() const { + if (client) { + client->sendIcons(getClientIcons()); + } + } + void sendMagicEffect(const Position& pos, uint8_t type) const { + if (client) { + client->sendMagicEffect(pos, type); + } + } + void sendPing(); + void sendPingBack() const { + if (client) { + client->sendPingBack(); + } + } + void sendStats(); + void sendBasicData() const { + if (client) { + client->sendBasicData(); + } + } + void sendSkills() const { + if (client) { + client->sendSkills(); + } + } + void sendTextMessage(MessageClasses mclass, const std::string& message, Position* pos = NULL, uint32_t value = 0, TextColor_t color = TEXTCOLOR_NONE) const { + if (client) { + client->sendTextMessage(mclass, message, pos, value, color); + } + } + void sendReLoginWindow(uint8_t unfairFightReduction) const { + if (client) { + client->sendReLoginWindow(unfairFightReduction); + } + } + void sendTextWindow(Item* item, uint16_t maxlen, bool canWrite) const { + if (client) { + client->sendTextWindow(windowTextId, item, maxlen, canWrite); + } + } + void sendTextWindow(uint32_t itemId, const std::string& text) const { + if (client) { + client->sendTextWindow(windowTextId, itemId, text); + } + } + void sendToChannel(Creature* creature, SpeakClasses type, const std::string& text, uint16_t channelId) const { + if (client) { + client->sendToChannel(creature, type, text, channelId); + } + } + void sendShop(Npc* npc) const { + if (client) { + client->sendShop(npc, shopItemList); + } + } + void sendSaleItemList() const { + if (client) { + client->sendSaleItemList(shopItemList); + } + } + void sendCloseShop() const { + if (client) { + client->sendCloseShop(); + } + } + void sendMarketEnter(uint32_t depotId) const { + if (client) { + client->sendMarketEnter(depotId); + } + } + void sendMarketLeave() { + marketDepotId = -1; + + if (client) { + client->sendMarketLeave(); + } + } + void sendMarketBrowseItem(uint16_t itemId, const MarketOfferList& buyOffers, const MarketOfferList& sellOffers) const { + if (client) { + client->sendMarketBrowseItem(itemId, buyOffers, sellOffers); + } + } + void sendMarketBrowseOwnOffers(const MarketOfferList& buyOffers, const MarketOfferList& sellOffers) const { + if (client) { + client->sendMarketBrowseOwnOffers(buyOffers, sellOffers); + } + } + void sendMarketBrowseOwnHistory(const HistoryMarketOfferList& buyOffers, const HistoryMarketOfferList& sellOffers) const { + if (client) { + client->sendMarketBrowseOwnHistory(buyOffers, sellOffers); + } + } + void sendMarketDetail(uint16_t itemId) const { + if (client) { + client->sendMarketDetail(itemId); + } + } + void sendMarketAcceptOffer(MarketOfferEx offer) const { + if (client) { + client->sendMarketAcceptOffer(offer); + } + } + void sendMarketCancelOffer(MarketOfferEx offer) const { + if (client) { + client->sendMarketCancelOffer(offer); + } + } + void sendTradeItemRequest(const Player* player, const Item* item, bool ack) const { + if (client) { + client->sendTradeItemRequest(player, item, ack); + } + } + void sendTradeClose() const { + if (client) { + client->sendCloseTrade(); + } + } + void sendWorldLight(LightInfo& lightInfo) { + if (client) { + client->sendWorldLight(lightInfo); + } + } + void sendChannelsDialog() { + if (client) { + client->sendChannelsDialog(); + } + } + void sendOpenPrivateChannel(const std::string& receiver) { + if (client) { + client->sendOpenPrivateChannel(receiver); + } + } + void sendOutfitWindow() { + if (client) { + client->sendOutfitWindow(); + } + } + void sendCloseContainer(uint8_t cid) { + if (client) { + client->sendCloseContainer(cid); + } + } + void sendChannel(uint16_t channelId, const std::string& channelName) { + if (client) { + client->sendChannel(channelId, channelName); + } + } + void sendTutorial(uint8_t tutorialId) { + if (client) { + client->sendTutorial(tutorialId); + } + } + void sendAddMarker(const Position& pos, uint8_t markType, const std::string& desc) { + if (client) { + client->sendAddMarker(pos, markType, desc); + } + } + void sendQuestLog() { + if (client) { + client->sendQuestLog(); + } + } + void sendQuestLine(const Quest* quest) { + if (client) { + client->sendQuestLine(quest); + } + } + void sendEnterWorld() { + if (client) { + client->sendEnterWorld(); + } + } + + void receivePing() { + lastPong = OTSYS_TIME(); + } + + virtual void onThink(uint32_t interval); + virtual void onAttacking(uint32_t interval); + + virtual void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER); + virtual void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, bool isCompleteRemoval, cylinderlink_t link = LINK_OWNER); + + void setNextAction(int64_t time) { + if (time > nextAction) { + nextAction = time; + } + } + bool canDoAction() const { + return nextAction <= OTSYS_TIME(); + } + uint32_t getNextActionTime() const; + + Item* getWriteItem(uint32_t& _windowTextId, uint16_t& _maxWriteLen); + void setWriteItem(Item* item, uint16_t _maxWriteLen = 0); + + House* getEditHouse(uint32_t& _windowTextId, uint32_t& _listId); + void setEditHouse(House* house, uint32_t listId = 0); + + void learnInstantSpell(const std::string& name); + bool hasLearnedInstantSpell(const std::string& name) const; + + VIPListSet VIPList; + uint32_t maxVipLimit; + + InvitedToGuildsList invitedToGuildsList; + GuildWarList guildWarList; + + ContainerMap openContainers; + + //depots + DepotMap depotChests; + DepotLockerMap depotLockerMap; + uint32_t maxDepotLimit; + + protected: + void checkTradeState(const Item* item); + bool hasCapacity(const Item* item, uint32_t count) const; + + void gainExperience(uint64_t exp); + void addExperience(uint64_t exp, bool useMult = false, bool sendText = false, bool applyStaminaChange = false); + + void updateInventoryWeight(); + void postUpdateGoods(uint32_t itemId); + + void setNextWalkActionTask(SchedulerTask* task); + void setNextWalkTask(SchedulerTask* task); + void setNextActionTask(SchedulerTask* task); + + void death(); + virtual bool dropCorpse(); + virtual Item* getCorpse(); + + //cylinder implementations + virtual ReturnValue __queryAdd(int32_t index, const Thing* thing, uint32_t count, + uint32_t flags, Creature* actor = NULL) const; + virtual ReturnValue __queryMaxCount(int32_t index, const Thing* thing, uint32_t count, uint32_t& maxQueryCount, + uint32_t flags) const; + virtual ReturnValue __queryRemove(const Thing* thing, uint32_t count, uint32_t flags) const; + virtual Cylinder* __queryDestination(int32_t& index, const Thing* thing, Item** destItem, + uint32_t& flags); + + virtual void __addThing(Thing* thing); + virtual void __addThing(int32_t index, Thing* thing); + + virtual void __updateThing(Thing* thing, uint16_t itemId, uint32_t count); + virtual void __replaceThing(uint32_t index, Thing* thing); + + virtual void __removeThing(Thing* thing, uint32_t count); + + virtual int32_t __getIndexOfThing(const Thing* thing) const; + virtual int32_t __getFirstIndex() const; + virtual int32_t __getLastIndex() const; + virtual uint32_t __getItemTypeCount(uint16_t itemId, int32_t subType = -1) const; + virtual std::map& __getAllItemTypeCount(std::map& countMap) const; + virtual Thing* __getThing(uint32_t index) const; + + virtual void __internalAddThing(Thing* thing); + virtual void __internalAddThing(uint32_t index, Thing* thing); + + ProtocolGame* client; + + Party* party; + PartyList invitePartyList; + + uint32_t level; + uint32_t levelPercent; + uint32_t magLevel; + uint32_t magLevelPercent; + bool accessLevel; + AccountType_t accountType; + int32_t premiumDays; + uint64_t experience; + uint32_t damageImmunities; + uint32_t conditionImmunities; + uint32_t conditionSuppressions; + uint32_t condition; + uint64_t manaSpent; + int32_t vocationId; + Vocation* vocation; + PlayerSex_t sex; + int32_t soul, soulMax; + uint64_t groupFlags; + int16_t blessings; + uint32_t MessageBufferTicks; + int32_t MessageBufferCount; + uint32_t actionTaskEvent; + uint32_t nextStepEvent; + uint32_t walkTaskEvent; + SchedulerTask* walkTask; + int64_t lastFailedFollow; + + std::string groupName; + int32_t idleTime; + int32_t groupId; + OperatingSystem_t operatingSystem; + bool ghostMode; + bool depotChange; + bool lagging; + + std::list modalWindows; + + int32_t offlineTrainingSkill; + int32_t offlineTrainingTime; + + int16_t marketDepotId; + int16_t lastDepotId; + + bool mayNotMove; + bool requestedOutfit; + + double inventoryWeight; + double capacity; + + int64_t lastPing; + int64_t lastPong; + + int64_t nextAction; + + bool pzLocked; + bool isConnecting; + int32_t bloodHitCount; + int32_t shieldBlockCount; + BlockType_t lastAttackBlockType; + bool addAttackSkillPoint; + uint64_t lastAttack; + int32_t shootRange; + + chaseMode_t chaseMode; + fightMode_t fightMode; + secureMode_t secureMode; + + time_t lastLoginSaved; + time_t lastLogout; + Position loginPosition; + uint32_t lastIP; + + //account variables + uint32_t accountNumber; + std::string password; + + //inventory variables + Item* inventory[SLOT_LAST]; + bool inventoryAbilities[SLOT_LAST]; + + //player advances variables + uint32_t skills[SKILL_LAST + 1][3]; + + //extra skill modifiers + int32_t varSkills[SKILL_LAST + 1]; + + //extra stat modifiers + int32_t varStats[STAT_LAST + 1]; + + LearnedInstantSpellList learnedInstantSpellList; + ConditionList storedConditionList; + + //trade variables + Player* tradePartner; + tradestate_t tradeState; + Item* tradeItem; + + //shop variables + Npc* shopOwner; + int32_t purchaseCallback; + int32_t saleCallback; + std::list shopItemList; + + std::map goodsMap; + + Inbox* inbox; + + std::string name; + std::string nameDescription; + uint32_t guid; + + uint32_t town; + + uint64_t bankBalance; + + uint16_t lastStatsTrainingTime; + + Position lastWalkthroughPosition; + int64_t lastWalkthroughAttempt; + int64_t lastToggleMount; + + //guild variables + Guild* guild; + std::string guildRank; + std::string guildNick; + uint8_t guildLevel; + + StorageMap storageMap; + int64_t lastQuestlogUpdate; + + LightInfo itemsLight; + + OutfitList m_playerOutfits; + + BedItem* bedItem; + + ProtocolGame* castingProtocol; + std::string castPassword; + + //stamina + uint16_t staminaMinutes; + time_t nextUseStaminaTime; + + //read/write storage data + uint32_t windowTextId; + Item* writeItem; + uint16_t maxWriteLen; + House* editHouse; + uint32_t editListId; + + uint32_t moveItemsBuffer; + int64_t lastMoveItemTime; + + int64_t skullTicks; + Skulls_t skull; + typedef OTSERV_HASH_SET AttackedSet; + AttackedSet attackedSet; + + void updateItemsLight(bool internal = false); + virtual int32_t getStepSpeed() const { + if (getSpeed() > PLAYER_MAX_SPEED) { + return PLAYER_MAX_SPEED; + } else if (getSpeed() < PLAYER_MIN_SPEED) { + return PLAYER_MIN_SPEED; + } + + return getSpeed(); + } + void updateBaseSpeed() { + if (!hasFlag(PlayerFlag_SetMaxSpeed)) { + baseSpeed = vocation->getBaseSpeed() + (2 * (level - 1)); + } else { + baseSpeed = PLAYER_MAX_SPEED; + } + } + + bool isPromoted() const; + + uint32_t getAttackSpeed() const { + return vocation->getAttackSpeed(); + } + + uint16_t getDropPercent() const; + + static uint32_t getPercentLevel(uint64_t count, uint64_t nextLevelCount); + double getLostPercent() const; + virtual uint64_t getLostExperience() const { + return skillLoss ? uint64_t(experience * getLostPercent()) : 0; + } + virtual void dropLoot(Container* corpse); + virtual uint32_t getDamageImmunities() const { + return damageImmunities; + } + virtual uint32_t getConditionImmunities() const { + return conditionImmunities; + } + virtual uint32_t getConditionSuppressions() const { + return conditionSuppressions; + } + virtual uint16_t getLookCorpse() const; + virtual void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const; + + friend class Game; + friend class Npc; + friend class LuaScriptInterface; + friend class Commands; + friend class Map; + friend class Actions; + friend class IOLoginData; + friend class ProtocolGame; +}; + +#endif diff --git a/src/position.cpp b/src/position.cpp new file mode 100644 index 0000000000..a240479e9f --- /dev/null +++ b/src/position.cpp @@ -0,0 +1,73 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "position.h" +#include + +std::ostream& operator<<(std::ostream& os, const Position& pos) +{ + os << "( " << std::setw(5) << std::setfill('0') << pos.x; + os << " / " << std::setw(5) << std::setfill('0') << pos.y; + os << " / " << std::setw(3) << std::setfill('0') << pos.z; + os << " )"; + return os; +} + +std::ostream& operator<<(std::ostream& os, const Direction& dir) +{ + switch (dir) { + case NORTH: + os << "North"; + break; + + case EAST: + os << "East"; + break; + + case WEST: + os << "West"; + break; + + case SOUTH: + os << "South"; + break; + + case SOUTHWEST: + os << "South-West"; + break; + + case SOUTHEAST: + os << "South-East"; + break; + + case NORTHWEST: + os << "North-West"; + break; + + case NORTHEAST: + os << "North-East"; + break; + + default: + break; + } + + return os; +} diff --git a/src/position.h b/src/position.h new file mode 100644 index 0000000000..a9551a4823 --- /dev/null +++ b/src/position.h @@ -0,0 +1,151 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_POS_H +#define __OTSERV_POS_H + +#include "definitions.h" + +#include +#include +#include + +enum Direction { + NORTH = 0, + EAST = 1, + SOUTH = 2, + WEST = 3, + SOUTHWEST = 4, + SOUTHEAST = 5, + NORTHWEST = 6, + NORTHEAST = 7, + SOUTH_ALT = 8, + EAST_ALT = 9, + NODIR = 10 +}; + +class Position +{ + public: + Position() : x(0), y(0), z(0) {} + ~Position() {} + + template + inline static bool areInRange(const Position& p1, const Position& p2) { + if (std::abs(float(p1.x - p2.x)) > deltax || std::abs(float(p1.y - p2.y)) > deltay || std::abs(float(p1.z - p2.z)) > deltaz) { + return false; + } + + return true; + } + + template + inline static bool areInRange(const Position& p1, const Position& p2) { + if (std::abs(float(p1.x - p2.x)) > deltax || std::abs(float(p1.y - p2.y)) > deltay) { + return false; + } + + return true; + } + + Position(int32_t _x, int32_t _y, int32_t _z) + : x(_x), y(_y), z(_z) {} + + int32_t x, y, z; + + bool operator<(const Position& p) const { + if (z < p.z) { + return true; + } + + if (z > p.z) { + return false; + } + + if (y < p.y) { + return true; + } + + if (y > p.y) { + return false; + } + + if (x < p.x) { + return true; + } + + if (x > p.x) { + return false; + } + + return false; + } + + bool operator>(const Position& p) const { + return ! (*this < p); + } + + bool operator==(const Position& p) const { + return p.x == x && p.y == y && p.z == z; + } + + bool operator!=(const Position& p) const { + return p.x != x || p.y != y || p.z != z; + } + + Position operator-(const Position& p1) { + return Position(x - p1.x, y - p1.y, z - p1.z); + } +}; + +std::ostream& operator<<(std::ostream&, const Position&); +std::ostream& operator<<(std::ostream&, const Direction&); + +class PositionEx : public Position +{ + public: + PositionEx() {} + ~PositionEx() {} + + PositionEx(int32_t _x, int32_t _y, int32_t _z, int32_t _stackpos) + : Position(_x, _y, _z), stackpos(_stackpos) {} + + PositionEx(int32_t _x, int32_t _y, int32_t _z) + : Position(_x, _y, _z), stackpos(0) {} + + PositionEx(const Position& p) + : Position(p.x, p.y, p.z), stackpos(0) {} + + PositionEx(const PositionEx& p) + : Position(p.x, p.y, p.z), stackpos(p.stackpos) {} + + PositionEx(const Position& p, int32_t _stackpos) + : Position(p.x, p.y, p.z), stackpos(_stackpos) {} + + int32_t stackpos; + + bool operator==(const PositionEx& p) const { + return p.x == x && p.y == y && p.z == z && p.stackpos == stackpos; + } + + bool operator!=(const PositionEx& p) const { + return p.x != x || p.y != y || p.z != z || p.stackpos != stackpos; + } +}; + +#endif diff --git a/src/protocol.cpp b/src/protocol.cpp new file mode 100644 index 0000000000..724bb02a14 --- /dev/null +++ b/src/protocol.cpp @@ -0,0 +1,202 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "definitions.h" + +#if defined WIN32 +#include +#endif + +#include "protocol.h" +#include "scheduler.h" +#include "connection.h" +#include "outputmessage.h" +#include "rsa.h" + +extern RSA g_RSA; + +void Protocol::onSendMessage(OutputMessage_ptr msg) +{ + if (!m_rawMessages) { + msg->writeMessageLength(); + + if (m_encryptionEnabled) { + XTEA_encrypt(*msg); + msg->addCryptoHeader(m_checksumEnabled); + } + } + + if (msg == m_outputBuffer) { + m_outputBuffer.reset(); + } +} + +void Protocol::onRecvMessage(NetworkMessage& msg) +{ + if (m_encryptionEnabled && !XTEA_decrypt(msg)) { + return; + } + + parsePacket(msg); +} + +OutputMessage_ptr Protocol::getOutputBuffer(int32_t size) +{ + if (m_outputBuffer && NetworkMessage::max_body_length >= m_outputBuffer->getMessageLength() + size) { + return m_outputBuffer; + } else if (m_connection) { + m_outputBuffer = OutputMessagePool::getInstance()->getOutputMessage(this); + return m_outputBuffer; + } + + return OutputMessage_ptr(); +} + +void Protocol::releaseProtocol() +{ + if (m_refCount > 0) { + //Reschedule it and try again. + g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, + boost::bind(&Protocol::releaseProtocol, this))); + } else { + deleteProtocolTask(); + } +} + +void Protocol::deleteProtocolTask() +{ + //dispather thread + assert(m_refCount == 0); + setConnection(Connection_ptr()); + + delete this; +} + +void Protocol::XTEA_encrypt(OutputMessage& msg) +{ + const uint32_t delta = 0x61C88647; + + uint32_t k[4]; + k[0] = m_key[0]; + k[1] = m_key[1]; + k[2] = m_key[2]; + k[3] = m_key[3]; + + //add bytes until reach 8 multiple + int32_t paddingBytes = msg.getMessageLength() % 8; + + if (paddingBytes != 0) { + uint32_t n = 8 - paddingBytes; + msg.AddPaddingBytes(n); + } + + uint32_t* buffer = (uint32_t*)msg.getOutputBuffer(); + const int32_t messageLength = msg.getMessageLength() / 4; + int32_t read_pos = 0; + + while (read_pos < messageLength) { + uint32_t v0 = buffer[read_pos], v1 = buffer[read_pos + 1]; + uint32_t sum = 0; + + for (int32_t i = 0; i < 32; ++i) { + v0 += ((v1 << 4 ^ v1 >> 5) + v1) ^ (sum + k[sum & 3]); + sum -= delta; + v1 += ((v0 << 4 ^ v0 >> 5) + v0) ^ (sum + k[sum >> 11 & 3]); + } + + buffer[read_pos++] = v0; + buffer[read_pos++] = v1; + } +} + +bool Protocol::XTEA_decrypt(NetworkMessage& msg) +{ + if ((msg.getMessageLength() - 6) % 8 != 0) { + return false; + } + + const uint32_t delta = 0x61C88647; + + const int32_t messageLength = (msg.getMessageLength() - 6) / 4; + + // + uint32_t k[4]; + + k[0] = m_key[0]; + + k[1] = m_key[1]; + + k[2] = m_key[2]; + + k[3] = m_key[3]; + + uint32_t* buffer = (uint32_t*)(msg.getBuffer() + msg.getReadPos()); + + int32_t read_pos = 0; + + while (read_pos < messageLength) { + uint32_t v0 = buffer[read_pos], v1 = buffer[read_pos + 1]; + uint32_t sum = 0xC6EF3720; + + for (int32_t i = 0; i < 32; ++i) { + v1 -= ((v0 << 4 ^ v0 >> 5) + v0) ^ (sum + k[sum >> 11 & 3]); + sum += delta; + v0 -= ((v1 << 4 ^ v1 >> 5) + v1) ^ (sum + k[sum & 3]); + } + + buffer[read_pos++] = v0; + buffer[read_pos++] = v1; + } + + // + + int tmp = msg.GetU16(); + + if (tmp > msg.getMessageLength() - 8) { + return false; + } + + msg.setMessageLength(tmp); + return true; +} + +bool Protocol::RSA_decrypt(NetworkMessage& msg) +{ + return RSA_decrypt(&g_RSA, msg); +} + +bool Protocol::RSA_decrypt(RSA* rsa, NetworkMessage& msg) +{ + if (msg.getMessageLength() - msg.getReadPos() != 128) { + return false; + } + + rsa->decrypt((char*)(msg.getBuffer() + msg.getReadPos()), 128); + return msg.GetByte() == 0; +} + +uint32_t Protocol::getIP() const +{ + if (getConnection()) { + return getConnection()->getIP(); + } + + return 0; +} diff --git a/src/protocol.h b/src/protocol.h new file mode 100644 index 0000000000..d26176d9e1 --- /dev/null +++ b/src/protocol.h @@ -0,0 +1,123 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_PROTOCOL_H__ +#define __OTSERV_PROTOCOL_H__ + +#include +#include + +class NetworkMessage; +class OutputMessage; +class Connection; +typedef boost::shared_ptr OutputMessage_ptr; +typedef boost::shared_ptr Connection_ptr; +class RSA; + +class Protocol : boost::noncopyable +{ + public: + Protocol(Connection_ptr connection) { + m_connection = connection; + m_encryptionEnabled = false; + m_checksumEnabled = true; + m_rawMessages = false; + m_key[0] = 0; + m_key[1] = 0; + m_key[2] = 0; + m_key[3] = 0; + m_refCount = 0; + } + + virtual ~Protocol() {} + + virtual int32_t getProtocolId() { + return 0x00; + } + + virtual void parsePacket(NetworkMessage& msg) {} + + virtual void onSendMessage(OutputMessage_ptr msg); + void onRecvMessage(NetworkMessage& msg); + virtual void onRecvFirstMessage(NetworkMessage& msg) = 0; + virtual void onConnect() {} + + Connection_ptr getConnection() { + return m_connection; + } + const Connection_ptr getConnection() const { + return m_connection; + } + void setConnection(Connection_ptr connection) { + m_connection = connection; + } + + uint32_t getIP() const; + + int32_t addRef() { + return ++m_refCount; + } + int32_t unRef() { + return --m_refCount; + } + + //Use this function for autosend messages only + OutputMessage_ptr getOutputBuffer(int32_t size); + + protected: + void enableXTEAEncryption() { + m_encryptionEnabled = true; + } + void disableXTEAEncryption() { + m_encryptionEnabled = false; + } + void setXTEAKey(const uint32_t* key) { + memcpy(m_key, key, sizeof(uint32_t) * 4); + } + void enableChecksum() { + m_checksumEnabled = true; + } + void disableChecksum() { + m_checksumEnabled = false; + } + + void XTEA_encrypt(OutputMessage& msg); + bool XTEA_decrypt(NetworkMessage& msg); + bool RSA_decrypt(NetworkMessage& msg); + bool RSA_decrypt(RSA* rsa, NetworkMessage& msg); + + void setRawMessages(bool value) { + m_rawMessages = value; + } + + virtual void releaseProtocol(); + virtual void deleteProtocolTask(); + friend class Connection; + + OutputMessage_ptr m_outputBuffer; + + private: + Connection_ptr m_connection; + bool m_encryptionEnabled; + bool m_checksumEnabled; + bool m_rawMessages; + uint32_t m_key[4]; + uint32_t m_refCount; +}; + +#endif diff --git a/src/protocolgame.cpp b/src/protocolgame.cpp new file mode 100644 index 0000000000..b34984f902 --- /dev/null +++ b/src/protocolgame.cpp @@ -0,0 +1,3609 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "protocolgame.h" + +#include "networkmessage.h" +#include "outputmessage.h" + +#include "items.h" + +#include "tile.h" +#include "player.h" +#include "chat.h" + +#include "configmanager.h" +#include "actions.h" +#include "game.h" +#include "iologindata.h" +#include "iomarket.h" +#include "house.h" +#include "waitlist.h" +#include "quests.h" +#include "mounts.h" +#include "ban.h" +#include "connection.h" +#include "creatureevent.h" + +#include +#include +#include +#include +#include +#include + +#include + +extern Game g_game; +extern ConfigManager g_config; +extern Actions actions; +extern Ban g_bans; +extern CreatureEvents* g_creatureEvents; +Chat g_chat; + +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ +uint32_t ProtocolGame::protocolGameCount = 0; +#endif + +// Helping templates to add dispatcher tasks +template +void ProtocolGame::addGameTaskInternal(bool droppable, uint32_t delay, const FunctionType& func) +{ + if (droppable) { + g_dispatcher.addTask(createTask(delay, func)); + } else { + g_dispatcher.addTask(createTask(func)); + } +} + +ProtocolGame::ProtocolGame(Connection_ptr connection) : + Protocol(connection) +{ + player = NULL; + m_debugAssertSent = false; + m_acceptPackets = false; + eventConnect = 0; + version = CLIENT_VERSION_MIN; +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + protocolGameCount++; +#endif +} + +ProtocolGame::~ProtocolGame() +{ + player = NULL; +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + protocolGameCount--; +#endif +} + +void ProtocolGame::setPlayer(Player* p) +{ + player = p; +} + +void ProtocolGame::releaseProtocol() +{ + //dispatcher thread + if (player && player->client == this) { + player->client = NULL; + } + Protocol::releaseProtocol(); +} + +void ProtocolGame::deleteProtocolTask() +{ + //dispatcher thread + if (player) { + g_game.FreeThing(player); + player = NULL; + } + + Protocol::deleteProtocolTask(); +} + +bool ProtocolGame::login(const std::string& name, uint32_t accnumber, OperatingSystem_t operatingSystem, bool gamemasterLogin) +{ + //dispatcher thread + Player* _player = g_game.getPlayerByName(name); + + if (!_player || g_config.getBoolean(ConfigManager::ALLOW_CLONES)) { + player = new Player(name, this); + + player->useThing2(); + player->setID(); + + if (IOBan::getInstance()->isPlayerNamelocked(name) && accnumber > 1) { + disconnectClient(0x14, "Your character has been namelocked.\nLogin on the website to change your name."); + return false; + } + + if (!IOLoginData::getInstance()->loadPlayer(player, name, true)) { + disconnectClient(0x14, "Your character could not be loaded."); + return false; + } + + /* + if(gamemasterLogin && player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER) + { + disconnectClient(0x14, "You are not a gamemaster!"); + return false; + } + */ + + if (g_game.getGameState() == GAME_STATE_CLOSING && !player->hasFlag(PlayerFlag_CanAlwaysLogin)) { + disconnectClient(0x14, "The game is just going down.\nPlease try again later."); + return false; + } + + if (g_game.getGameState() == GAME_STATE_CLOSED && !player->hasFlag(PlayerFlag_CanAlwaysLogin)) { + disconnectClient(0x14, "Server is currently closed. Please try again later."); + return false; + } + + if (g_config.getBoolean(ConfigManager::ONE_PLAYER_ON_ACCOUNT) && player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER && g_game.getPlayerByAccount(player->getAccount())) { + disconnectClient(0x14, "You may only login with one character\nof your account at the same time."); + return false; + } + + if (IOLoginData::getInstance()->isPendingDeletion(player->getName())) { + disconnectClient(0x14, "Your character is pending deletion,\nyou can restore your character at the website."); + return false; + } + + if (!player->hasFlag(PlayerFlag_CannotBeBanned)) { + uint32_t bannedBy = 0, banTime = 0; + int32_t reason = 0, action = 0; + std::string comment = ""; + bool deletion = false; + + if (IOBan::getInstance()->getBanInformation(accnumber, bannedBy, banTime, reason, action, comment, deletion)) { + uint64_t timeNow = time(NULL); + + if ((deletion && banTime != 0) || banTime > timeNow) { + std::string bannedByName; + + if (bannedBy == 0) { + bannedByName = (deletion ? "Automatic deletion" : "Automatic banishment"); + } else { + IOLoginData::getInstance()->getNameByGuid(bannedBy, bannedByName); + } + + std::ostringstream ss; + ss << "Your account has been " << (deletion ? "deleted" : "banished") << " by:\n" << bannedByName << ", for the following reason:\n" << getReason(reason) << ".\nThe action taken was:\n" << getAction(action, false) << ".\nThe comment given was:\n" << comment << "\nYour " << (deletion ? "account was deleted on" : "banishment will be lifted at") << ":\n" << formatDateShort(banTime) << "."; + disconnectClient(0x14, ss.str().c_str()); + return false; + } + } + } + + if (!WaitingList::getInstance()->clientLogin(player)) { + int32_t currentSlot = WaitingList::getInstance()->getClientSlot(player); + int32_t retryTime = WaitingList::getTime(currentSlot); + std::ostringstream ss; + + ss << "Too many players online.\n" << "You are at place " + << currentSlot << " on the waiting list."; + + OutputMessage_ptr output = OutputMessagePool::getInstance()->getOutputMessage(this, false); + + if (output) { + output->AddByte(0x16); + output->AddString(ss.str()); + output->AddByte(retryTime); + OutputMessagePool::getInstance()->send(output); + } + + getConnection()->closeConnection(); + return false; + } + + if (!IOLoginData::getInstance()->loadPlayer(player, name)) { + disconnectClient(0x14, "Your character could not be loaded."); + return false; + } + + player->setOperatingSystem((OperatingSystem_t)operatingSystem); + + if (!g_game.placeCreature(player, player->getLoginPosition())) { + if (!g_game.placeCreature(player, player->getTemplePosition(), false, true)) { + disconnectClient(0x14, "Temple position is wrong. Contact the administrator."); + return false; + } + } + + player->lastIP = player->getIP(); + player->lastLoginSaved = std::max(time(NULL), player->lastLoginSaved + 1); + m_acceptPackets = true; + return true; + } else { + if (eventConnect != 0 || !g_config.getBoolean(ConfigManager::REPLACE_KICK_ON_LOGIN)) { + //Already trying to connect + disconnectClient(0x14, "You are already logged in."); + return false; + } + + if (_player->client) { + _player->disconnect(); + _player->isConnecting = true; + + addRef(); + eventConnect = g_scheduler.addEvent( + createSchedulerTask(1000, boost::bind(&ProtocolGame::connect, this, _player->getID(), operatingSystem))); + return true; + } + + addRef(); + return connect(_player->getID(), operatingSystem); + } + + return false; +} + +bool ProtocolGame::connect(uint32_t playerId, OperatingSystem_t operatingSystem) +{ + unRef(); + eventConnect = 0; + + Player* _player = g_game.getPlayerByID(playerId); + + if (!_player || _player->isRemoved() || _player->client) { + disconnectClient(0x14, "You are already logged in."); + return false; + } + + player = _player; + player->useThing2(); + + g_chat.removeUserFromAllChannels(player); + player->clearModalWindows(); + player->setOperatingSystem((OperatingSystem_t)operatingSystem); + player->isConnecting = false; + + player->client = this; + sendAddCreature(player, player->getPosition(), player->getTile()->__getIndexOfThing(player), false); + player->lastIP = player->getIP(); + player->lastLoginSaved = std::max(time(NULL), player->lastLoginSaved + 1); + m_acceptPackets = true; + return true; +} + +bool ProtocolGame::logout(bool displayEffect, bool forced) +{ + //dispatcher thread + if (!player) { + return false; + } + + if (!player->isRemoved()) { + if (forced) { + g_creatureEvents->playerLogout(player); + } else { + bool flag = (player->getAccountType() == ACCOUNT_TYPE_GOD); + + if (player->getTile()->hasFlag(TILESTATE_NOLOGOUT) && !flag) { + player->sendCancelMessage(RET_YOUCANNOTLOGOUTHERE); + return false; + } + + if (!player->getTile()->hasFlag(TILESTATE_PROTECTIONZONE) && player->hasCondition(CONDITION_INFIGHT) && !flag) { + player->sendCancelMessage(RET_YOUMAYNOTLOGOUTDURINGAFIGHT); + return false; + } + + //scripting event - onLogout + if (!g_creatureEvents->playerLogout(player) && !flag) { + //Let the script handle the error message + return false; + } + } + } + + if (player->isRemoved() || player->getHealth() <= 0) { + displayEffect = false; + } + + if (displayEffect) { + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + } + + if (Connection_ptr connection = getConnection()) { + connection->closeConnection(); + } + + return g_game.removeCreature(player); +} + +bool ProtocolGame::parseFirstPacket(NetworkMessage& msg) +{ + if (g_game.getGameState() == GAME_STATE_SHUTDOWN) { + getConnection()->closeConnection(); + return false; + } + + OperatingSystem_t operatingSystem = (OperatingSystem_t)msg.GetU16(); + version = msg.GetU16(); + + msg.SkipBytes(5); // U32 clientVersion, U8 clientType + + if (!RSA_decrypt(msg)) { + getConnection()->closeConnection(); + return false; + } + + uint32_t key[4]; + key[0] = msg.GetU32(); + key[1] = msg.GetU32(); + key[2] = msg.GetU32(); + key[3] = msg.GetU32(); + enableXTEAEncryption(); + setXTEAKey(key); + + bool gamemasterFlag = msg.GetByte() != 0; + std::string accountName = msg.GetString(); + std::string name = msg.GetString(); + std::string password = msg.GetString(); + + msg.SkipBytes(4); // challenge timestamp + msg.SkipBytes(1); // challenge random + + if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) { + disconnectClient(0x14, "Only clients with protocol " CLIENT_VERSION_STR " allowed!"); + return false; + } + + if (accountName.empty()) { + disconnectClient(0x14, "You must enter your account name."); + return false; + } + + if (g_game.getGameState() == GAME_STATE_STARTUP || g_game.getServerSaveMessage(0)) { + disconnectClient(0x14, "Gameworld is starting up. Please wait."); + return false; + } + + if (g_game.getGameState() == GAME_STATE_MAINTAIN) { + disconnectClient(0x14, "Gameworld is under maintenance. Please re-connect in a while."); + return false; + } + + if (g_bans.isIpDisabled(getIP())) { + disconnectClient(0x14, "Too many connections attempts from this IP. Try again later."); + return false; + } + + if (IOBan::getInstance()->isIpBanished(getIP())) { + disconnectClient(0x14, "Your IP is banished!"); + return false; + } + + if (!IOLoginData::getInstance()->playerExists(name)) { + disconnectClient(0x14, "Player not found."); + return false; + } + + std::string acc_pass; + uint32_t accnumber; + bool gotPassword = IOLoginData::getInstance()->getPassword(accountName, name, acc_pass, accnumber); + if (!gotPassword || !passwordTest(password, acc_pass)) { + g_bans.addLoginAttempt(getIP(), false); + disconnectClient(0x14, "Account name or password is not correct."); + return false; + } + + g_bans.addLoginAttempt(getIP(), true); + + g_dispatcher.addTask( + createTask(boost::bind(&ProtocolGame::login, this, name, accnumber, operatingSystem, gamemasterFlag))); + + return true; +} + +void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg) +{ + parseFirstPacket(msg); +} + +void ProtocolGame::onConnect() +{ + OutputMessage_ptr output = OutputMessagePool::getInstance()->getOutputMessage(this, false); + + if (output) { + // Adler checksum + output->AddByte(0xEF); + output->AddByte(0x00); + output->AddByte(0x82); + output->AddByte(0x02); + + // Packet length + output->AddByte(0x06); + output->AddByte(0x00); + + // Packet type + output->AddByte(0x1F); + + // challenge timestamp + output->AddU32(0x00004101); + + // challenge random + output->AddByte(0x87); + + OutputMessagePool::getInstance()->send(output); + } +} + +void ProtocolGame::disconnectClient(uint8_t error, const char* message) +{ + OutputMessage_ptr output = OutputMessagePool::getInstance()->getOutputMessage(this, false); + + if (output) { + output->AddByte(error); + output->AddString(message); + OutputMessagePool::getInstance()->send(output); + } + + disconnect(); +} + +void ProtocolGame::disconnect() +{ + if (getConnection()) { + getConnection()->closeConnection(); + } +} + +void ProtocolGame::writeToOutputBuffer(const NetworkMessage& msg) +{ + OutputMessage_ptr out = getOutputBuffer(msg.getMessageLength()); + + if (out) { + out->append(msg); + } +} + +void ProtocolGame::parsePacket(NetworkMessage& msg) +{ + if (!m_acceptPackets || g_game.getGameState() == GAME_STATE_SHUTDOWN || msg.getMessageLength() <= 0) { + return; + } + + uint8_t recvbyte = msg.GetByte(); + + if (!player) { + if (recvbyte == 0x0F) { + disconnect(); + } + + return; + } else if (player->isLagging()) { + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerRemoveLagging, player->getID()); + } + + //a dead player can not performs actions + if (player->isRemoved() || player->getHealth() <= 0) { + if (recvbyte == 0x0F) { + disconnect(); + return; + } + + if (recvbyte != 0x14) { + return; + } + } + + switch (recvbyte) { + case 0x14: parseLogout(msg); break; + case 0x1D: parseReceivePingBack(msg); break; + case 0x1E: parseReceivePing(msg); break; + case 0x64: parseAutoWalk(msg); break; + case 0x65: parseMove(msg, NORTH); break; + case 0x66: parseMove(msg, EAST); break; + case 0x67: parseMove(msg, SOUTH); break; + case 0x68: parseMove(msg, WEST); break; + case 0x69: addGameTask(&Game::playerStopAutoWalk, player->getID()); break; + case 0x6A: parseMove(msg, NORTHEAST); break; + case 0x6B: parseMove(msg, SOUTHEAST); break; + case 0x6C: parseMove(msg, SOUTHWEST); break; + case 0x6D: parseMove(msg, NORTHWEST); break; + case 0x6F: parseTurn(msg, NORTH); break; + case 0x70: parseTurn(msg, EAST); break; + case 0x71: parseTurn(msg, SOUTH); break; + case 0x72: parseTurn(msg, WEST); break; + case 0x78: parseThrow(msg); break; + case 0x79: parseLookInShop(msg); break; + case 0x7A: parsePlayerPurchase(msg); break; + case 0x7B: parsePlayerSale(msg); break; + case 0x7C: parseCloseShop(msg); break; + case 0x7D: parseRequestTrade(msg); break; + case 0x7E: parseLookInTrade(msg); break; + case 0x7F: parseAcceptTrade(msg); break; + case 0x80: parseCloseTrade(); break; + case 0x82: parseUseItem(msg); break; + case 0x83: parseUseItemEx(msg); break; + case 0x84: parseUseWithCreature(msg); break; + case 0x85: parseRotateItem(msg); break; + case 0x87: parseCloseContainer(msg); break; + case 0x88: parseUpArrowContainer(msg); break; + case 0x89: parseTextWindow(msg); break; + case 0x8A: parseHouseWindow(msg); break; + case 0x8C: parseLookAt(msg); break; + case 0x8D: parseLookInBattleList(msg); break; + case 0x8E: parseJoinAggression(msg); break; + case 0x96: parseSay(msg); break; + case 0x97: parseGetChannels(msg); break; + case 0x98: parseOpenChannel(msg); break; + case 0x99: parseCloseChannel(msg); break; + case 0x9A: parseOpenPrivateChannel(msg); break; + case 0x9E: parseCloseNpc(msg); break; + case 0xA0: parseFightModes(msg); break; + case 0xA1: parseAttack(msg); break; + case 0xA2: parseFollow(msg); break; + case 0xA3: parseInviteToParty(msg); break; + case 0xA4: parseJoinParty(msg); break; + case 0xA5: parseRevokePartyInvite(msg); break; + case 0xA6: parsePassPartyLeadership(msg); break; + case 0xA7: parseLeaveParty(msg); break; + case 0xA8: parseEnableSharedPartyExperience(msg); break; + case 0xAA: parseCreatePrivateChannel(msg); break; + case 0xAB: parseChannelInvite(msg); break; + case 0xAC: parseChannelExclude(msg); break; + case 0xBE: parseCancelMove(msg); break; + case 0xC9: parseUpdateTile(msg); break; + case 0xCA: parseUpdateContainer(msg); break; + case 0xCB: parseBrowseField(msg); break; + case 0xCC: parseSeekInContainer(msg); break; + case 0xD2: parseRequestOutfit(msg); break; + case 0xD3: parseSetOutfit(msg); break; + case 0xD4: parseToggleMount(msg); break; + case 0xDC: parseAddVip(msg); break; + case 0xDD: parseRemoveVip(msg); break; + case 0xDE: parseEditVip(msg); break; + case 0xE6: parseBugReport(msg); break; + case 0xE7: parseThankYou(msg); break; + case 0xE8: parseDebugAssert(msg); break; + case 0xF0: parseQuestLog(msg); break; + case 0xF1: parseQuestLine(msg); break; + case 0xF2: parseRuleViolationReport(msg); break; + case 0xF3: parseGetObjectInfo(msg); break; + case 0xF4: parseMarketLeave(); break; + case 0xF5: parseMarketBrowse(msg); break; + case 0xF6: parseMarketCreateOffer(msg); break; + case 0xF7: parseMarketCancelOffer(msg); break; + case 0xF8: parseMarketAcceptOffer(msg); break; + case 0xF9: parseModalWindowAnswer(msg); break; + + default: + // std::cout << "Player: " << player->getName() << " sent an unknown packet header: 0x" << std::hex << (int16_t)recvbyte << std::dec << "!" << std::endl; + break; + } + + if (msg.isOverrun()) { + disconnect(); + } +} + +void ProtocolGame::GetTileDescription(const Tile* tile, NetworkMessage& msg) +{ + msg.AddU16(0x00); //environmental effects + + int32_t count = 0; + + if (tile->ground) { + msg.AddItem(tile->ground, version); + count++; + } + + ItemVector::const_iterator it; + + const TileItemVector* items = tile->getItemList(); + + if (items) { + for (it = items->getBeginTopItem(); ((it != items->getEndTopItem()) && (count < 10)); ++it) { + msg.AddItem(*it, version); + count++; + } + } + + const CreatureVector* creatures = tile->getCreatures(); + + if (creatures) { + for (CreatureVector::const_reverse_iterator cit = creatures->rbegin(); ((cit != creatures->rend()) && (count < 10)); ++cit) { + if ((*cit)->isInGhostMode() && !player->isAccessPlayer()) { + continue; + } + + bool known; + uint32_t removedKnown; + checkCreatureAsKnown((*cit)->getID(), known, removedKnown); + AddCreature(msg, *cit, known, removedKnown); + count++; + } + } + + if (items) { + for (it = items->getBeginDownItem(); ((it != items->getEndDownItem()) && (count < 10)); ++it) { + msg.AddItem(*it, version); + count++; + } + } +} + +void ProtocolGame::GetMapDescription(int32_t x, int32_t y, int32_t z, + int32_t width, int32_t height, NetworkMessage& msg) +{ + int32_t skip = -1; + int32_t startz, endz, zstep = 0; + + if (z > 7) { + startz = z - 2; + endz = std::min(MAP_MAX_LAYERS - 1, z + 2); + zstep = 1; + } else { + startz = 7; + endz = 0; + zstep = -1; + } + + for (int32_t nz = startz; nz != endz + zstep; nz += zstep) { + GetFloorDescription(msg, x, y, nz, width, height, z - nz, skip); + } + + if (skip >= 0) { + msg.AddByte(skip); + msg.AddByte(0xFF); + } +} + +void ProtocolGame::GetFloorDescription(NetworkMessage& msg, int32_t x, int32_t y, int32_t z, + int32_t width, int32_t height, int32_t offset, int32_t& skip) +{ + for (int32_t nx = 0; nx < width; nx++) { + for (int32_t ny = 0; ny < height; ny++) { + Tile* tile = g_game.getTile(x + nx + offset, y + ny + offset, z); + + if (tile) { + if (skip >= 0) { + msg.AddByte(skip); + msg.AddByte(0xFF); + } + + skip = 0; + GetTileDescription(tile, msg); + } else if (skip == 0xFE) { + msg.AddByte(0xFF); + msg.AddByte(0xFF); + } else { + ++skip; + } + } + } +} + +void ProtocolGame::checkCreatureAsKnown(uint32_t id, bool& known, uint32_t& removedKnown) +{ + std::pair::const_iterator, bool> result = knownCreatureSet.insert(id); + + if (!result.second) { + known = true; + return; + } + + known = false; + + if (knownCreatureSet.size() > 1300) { + // Look for a creature to remove + for (OTSERV_HASH_SET::iterator it = knownCreatureSet.begin(); it != knownCreatureSet.end(); ++it) { + Creature* creature = g_game.getCreatureByID(*it); + + if (!canSee(creature)) { + removedKnown = *it; + knownCreatureSet.erase(it); + return; + } + } + + // Bad situation. Let's just remove anyone. + OTSERV_HASH_SET::iterator it = knownCreatureSet.begin(); + + if (*it == id) { + ++it; + } + + removedKnown = *it; + knownCreatureSet.erase(it); + } else { + removedKnown = 0; + } +} + +bool ProtocolGame::canSee(const Creature* c) const +{ + if (!c || !player || c->isRemoved()) { + return false; + } + + if (c->isInGhostMode() && !player->isAccessPlayer()) { + return false; + } + + return canSee(c->getPosition()); +} + +bool ProtocolGame::canSee(const Position& pos) const +{ + return canSee(pos.x, pos.y, pos.z); +} + +bool ProtocolGame::canSee(int32_t x, int32_t y, int32_t z) const +{ + if (!player) { + return false; + } + + const Position& myPos = player->getPosition(); + + if (myPos.z <= 7) { + //we are on ground level or above (7 -> 0) + //view is from 7 -> 0 + if (z > 7) { + return false; + } + } else if (myPos.z >= 8) { + //we are underground (8 -> 15) + //view is +/- 2 from the floor we stand on + if (std::abs(myPos.z - z) > 2) { + return false; + } + } + + //negative offset means that the action taken place is on a lower floor than ourself + int32_t offsetz = myPos.z - z; + + if ((x >= myPos.x - 8 + offsetz) && (x <= myPos.x + 9 + offsetz) && + (y >= myPos.y - 6 + offsetz) && (y <= myPos.y + 7 + offsetz)) { + return true; + } + + return false; +} + +//********************** Parse methods *******************************// +void ProtocolGame::parseLogout(NetworkMessage& msg) +{ + g_dispatcher.addTask(createTask(boost::bind(&ProtocolGame::logout, this, true, false))); +} + +void ProtocolGame::parseCreatePrivateChannel(NetworkMessage& msg) +{ + addGameTask(&Game::playerCreatePrivateChannel, player->getID()); +} + +void ProtocolGame::parseChannelInvite(NetworkMessage& msg) +{ + const std::string name = msg.GetString(); + addGameTask(&Game::playerChannelInvite, player->getID(), name); +} + +void ProtocolGame::parseChannelExclude(NetworkMessage& msg) +{ + const std::string name = msg.GetString(); + addGameTask(&Game::playerChannelExclude, player->getID(), name); +} + +void ProtocolGame::parseGetChannels(NetworkMessage& msg) +{ + addGameTask(&Game::playerRequestChannels, player->getID()); +} + +void ProtocolGame::parseOpenChannel(NetworkMessage& msg) +{ + uint16_t channelId = msg.GetU16(); + addGameTask(&Game::playerOpenChannel, player->getID(), channelId); +} + +void ProtocolGame::parseCloseChannel(NetworkMessage& msg) +{ + uint16_t channelId = msg.GetU16(); + addGameTask(&Game::playerCloseChannel, player->getID(), channelId); +} + +void ProtocolGame::parseOpenPrivateChannel(NetworkMessage& msg) +{ + const std::string receiver = msg.GetString(); + addGameTask(&Game::playerOpenPrivateChannel, player->getID(), receiver); +} + +void ProtocolGame::parseCloseNpc(NetworkMessage& msg) +{ + addGameTask(&Game::playerCloseNpcChannel, player->getID()); +} + +void ProtocolGame::parseCancelMove(NetworkMessage& msg) +{ + addGameTask(&Game::playerCancelAttackAndFollow, player->getID()); +} + +void ProtocolGame::parseReceivePing(NetworkMessage& msg) +{ + addGameTask(&Game::playerReceivePing, player->getID()); +} + +void ProtocolGame::parseReceivePingBack(NetworkMessage& msg) +{ + addGameTask(&Game::playerReceivePingBack, player->getID()); +} + +void ProtocolGame::parseAutoWalk(NetworkMessage& msg) +{ + std::list path; + + size_t numdirs = msg.GetByte(); + + for (size_t i = 0; i < numdirs; ++i) { + uint8_t rawdir = msg.GetByte(); + + switch (rawdir) { + case 1: + path.push_back(EAST); + break; + + case 2: + path.push_back(NORTHEAST); + break; + + case 3: + path.push_back(NORTH); + break; + + case 4: + path.push_back(NORTHWEST); + break; + + case 5: + path.push_back(WEST); + break; + + case 6: + path.push_back(SOUTHWEST); + break; + + case 7: + path.push_back(SOUTH); + break; + + case 8: + path.push_back(SOUTHEAST); + break; + + default: + break; + } + } + + addGameTask(&Game::playerAutoWalk, player->getID(), path); +} + +void ProtocolGame::parseMove(NetworkMessage& msg, Direction dir) +{ + addGameTask(&Game::playerMove, player->getID(), dir); +} + +void ProtocolGame::parseTurn(NetworkMessage& msg, Direction dir) +{ + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), dir); +} + +void ProtocolGame::parseRequestOutfit(NetworkMessage& msg) +{ + addGameTask(&Game::playerRequestOutfit, player->getID()); +} + +void ProtocolGame::parseSetOutfit(NetworkMessage& msg) +{ + Outfit_t newOutfit; + newOutfit.lookType = msg.GetU16(); + newOutfit.lookHead = msg.GetByte(); + newOutfit.lookBody = msg.GetByte(); + newOutfit.lookLegs = msg.GetByte(); + newOutfit.lookFeet = msg.GetByte(); + newOutfit.lookAddons = msg.GetByte(); + newOutfit.lookMount = msg.GetU16(); + addGameTask(&Game::playerChangeOutfit, player->getID(), newOutfit); +} + +void ProtocolGame::parseToggleMount(NetworkMessage& msg) +{ + bool mount = msg.GetByte() != 0; + addGameTask(&Game::playerToggleMount, player->getID(), mount); +} + +void ProtocolGame::parseUseItem(NetworkMessage& msg) +{ + Position pos = msg.GetPosition(); + uint16_t spriteId = msg.GetSpriteId(); + uint8_t stackpos = msg.GetByte(); + uint8_t index = msg.GetByte(); + bool isHotkey = (pos.x == 0xFFFF && pos.y == 0 && pos.z == 0); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerUseItem, player->getID(), pos, stackpos, index, spriteId, isHotkey); +} + +void ProtocolGame::parseUseItemEx(NetworkMessage& msg) +{ + Position fromPos = msg.GetPosition(); + uint16_t fromSpriteId = msg.GetSpriteId(); + uint8_t fromStackPos = msg.GetByte(); + Position toPos = msg.GetPosition(); + uint16_t toSpriteId = msg.GetU16(); + uint8_t toStackPos = msg.GetByte(); + bool isHotkey = (fromPos.x == 0xFFFF && fromPos.y == 0 && fromPos.z == 0); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerUseItemEx, player->getID(), fromPos, fromStackPos, fromSpriteId, toPos, toStackPos, toSpriteId, isHotkey); +} + +void ProtocolGame::parseUseWithCreature(NetworkMessage& msg) +{ + Position fromPos = msg.GetPosition(); + uint16_t spriteId = msg.GetSpriteId(); + uint8_t fromStackPos = msg.GetByte(); + uint32_t creatureId = msg.GetU32(); + bool isHotkey = (fromPos.x == 0xFFFF && fromPos.y == 0 && fromPos.z == 0); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerUseWithCreature, player->getID(), fromPos, fromStackPos, creatureId, spriteId, isHotkey); +} + +void ProtocolGame::parseCloseContainer(NetworkMessage& msg) +{ + uint8_t cid = msg.GetByte(); + addGameTask(&Game::playerCloseContainer, player->getID(), cid); +} + +void ProtocolGame::parseUpArrowContainer(NetworkMessage& msg) +{ + uint8_t cid = msg.GetByte(); + addGameTask(&Game::playerMoveUpContainer, player->getID(), cid); +} + +void ProtocolGame::parseUpdateTile(NetworkMessage& msg) +{ + // Position pos = msg.GetPosition(); + // addGameTask(&Game::playerUpdateTile, player->getID(), pos); +} + +void ProtocolGame::parseUpdateContainer(NetworkMessage& msg) +{ + uint8_t cid = msg.GetByte(); + addGameTask(&Game::playerUpdateContainer, player->getID(), cid); +} + +void ProtocolGame::parseThrow(NetworkMessage& msg) +{ + Position fromPos = msg.GetPosition(); + uint16_t spriteId = msg.GetSpriteId(); + uint8_t fromStackpos = msg.GetByte(); + Position toPos = msg.GetPosition(); + uint8_t count = msg.GetByte(); + + if (toPos != fromPos) { + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerMoveThing, player->getID(), fromPos, spriteId, fromStackpos, toPos, count); + } +} + +void ProtocolGame::parseLookAt(NetworkMessage& msg) +{ + Position pos = msg.GetPosition(); + uint16_t spriteId = msg.GetSpriteId(); + uint8_t stackpos = msg.GetByte(); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookAt, player->getID(), pos, spriteId, stackpos); +} + +void ProtocolGame::parseLookInBattleList(NetworkMessage& msg) +{ + uint32_t creatureId = msg.GetU32(); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookInBattleList, player->getID(), creatureId); +} + +void ProtocolGame::parseJoinAggression(NetworkMessage& msg) +{ + // uint32_t creatureId = msg.GetU32(); + // addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerJoinAggression, player->getID(), creatureId); +} + +void ProtocolGame::parseSay(NetworkMessage& msg) +{ + SpeakClasses type = (SpeakClasses)msg.GetByte(); + + std::string receiver; + uint16_t channelId = 0; + + switch (type) { + case SPEAK_PRIVATE_TO: + case SPEAK_PRIVATE_RED_TO: + receiver = msg.GetString(); + break; + + case SPEAK_CHANNEL_Y: + case SPEAK_CHANNEL_R1: + channelId = msg.GetU16(); + break; + + default: + break; + } + + const std::string text = msg.GetString(); + + if (text.length() > 255) { + return; + } + + addGameTask(&Game::playerSay, player->getID(), channelId, type, receiver, text); +} + +void ProtocolGame::parseFightModes(NetworkMessage& msg) +{ + uint8_t rawFightMode = msg.GetByte(); //1 - offensive, 2 - balanced, 3 - defensive + uint8_t rawChaseMode = msg.GetByte(); // 0 - stand while fightning, 1 - chase opponent + uint8_t rawSecureMode = msg.GetByte(); // 0 - can't attack unmarked, 1 - can attack unmarked + // uint8_t rawPvpMode = msg.GetByte(); // pvp mode introduced in 10.0 + + chaseMode_t chaseMode; + + if (rawChaseMode == 1) { + chaseMode = CHASEMODE_FOLLOW; + } else { + chaseMode = CHASEMODE_STANDSTILL; + } + + fightMode_t fightMode; + + if (rawFightMode == 1) { + fightMode = FIGHTMODE_ATTACK; + } else if (rawFightMode == 2) { + fightMode = FIGHTMODE_BALANCED; + } else { + fightMode = FIGHTMODE_DEFENSE; + } + + secureMode_t secureMode; + + if (rawSecureMode == 1) { + secureMode = SECUREMODE_ON; + } else { + secureMode = SECUREMODE_OFF; + } + + addGameTask(&Game::playerSetFightModes, player->getID(), fightMode, chaseMode, secureMode); +} + +void ProtocolGame::parseAttack(NetworkMessage& msg) +{ + uint32_t creatureId = msg.GetU32(); + // msg.GetU32(); creatureId (same as above) + addGameTask(&Game::playerSetAttackedCreature, player->getID(), creatureId); +} + +void ProtocolGame::parseFollow(NetworkMessage& msg) +{ + uint32_t creatureId = msg.GetU32(); + // msg.GetU32(); creatureId (same as above) + addGameTask(&Game::playerFollowCreature, player->getID(), creatureId); +} + +void ProtocolGame::parseTextWindow(NetworkMessage& msg) +{ + uint32_t windowTextId = msg.GetU32(); + const std::string newText = msg.GetString(); + addGameTask(&Game::playerWriteItem, player->getID(), windowTextId, newText); +} + +void ProtocolGame::parseHouseWindow(NetworkMessage& msg) +{ + uint8_t doorId = msg.GetByte(); + uint32_t id = msg.GetU32(); + const std::string text = msg.GetString(); + addGameTask(&Game::playerUpdateHouseWindow, player->getID(), doorId, id, text); +} + +void ProtocolGame::parseLookInShop(NetworkMessage& msg) +{ + uint16_t id = msg.GetU16(); + uint8_t count = msg.GetByte(); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookInShop, player->getID(), id, count); +} + +void ProtocolGame::parsePlayerPurchase(NetworkMessage& msg) +{ + uint16_t id = msg.GetU16(); + uint8_t count = msg.GetByte(); + uint8_t amount = msg.GetByte(); + bool ignoreCap = msg.GetByte() != 0; + bool inBackpacks = msg.GetByte() != 0; + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerPurchaseItem, player->getID(), id, count, amount, ignoreCap, inBackpacks); +} + +void ProtocolGame::parsePlayerSale(NetworkMessage& msg) +{ + uint16_t id = msg.GetU16(); + uint8_t count = msg.GetByte(); + uint8_t amount = msg.GetByte(); + bool ignoreEquipped = msg.GetByte() != 0; + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerSellItem, player->getID(), id, count, amount, ignoreEquipped); +} + +void ProtocolGame::parseCloseShop(NetworkMessage& msg) +{ + addGameTask(&Game::playerCloseShop, player->getID()); +} + +void ProtocolGame::parseRequestTrade(NetworkMessage& msg) +{ + Position pos = msg.GetPosition(); + uint16_t spriteId = msg.GetSpriteId(); + uint8_t stackpos = msg.GetByte(); + uint32_t playerId = msg.GetU32(); + addGameTask(&Game::playerRequestTrade, player->getID(), pos, stackpos, playerId, spriteId); +} + +void ProtocolGame::parseAcceptTrade(NetworkMessage& msg) +{ + addGameTask(&Game::playerAcceptTrade, player->getID()); +} + +void ProtocolGame::parseLookInTrade(NetworkMessage& msg) +{ + bool counterOffer = (msg.GetByte() == 0x01); + uint8_t index = msg.GetByte(); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookInTrade, player->getID(), counterOffer, index); +} + +void ProtocolGame::parseCloseTrade() +{ + addGameTask(&Game::playerCloseTrade, player->getID()); +} + +void ProtocolGame::parseAddVip(NetworkMessage& msg) +{ + const std::string name = msg.GetString(); + addGameTask(&Game::playerRequestAddVip, player->getID(), name); +} + +void ProtocolGame::parseRemoveVip(NetworkMessage& msg) +{ + uint32_t guid = msg.GetU32(); + addGameTask(&Game::playerRequestRemoveVip, player->getID(), guid); +} + +void ProtocolGame::parseEditVip(NetworkMessage& msg) +{ + uint32_t guid = msg.GetU32(); + const std::string description = msg.GetString(); + uint32_t icon = std::min(10, msg.GetU32()); // 10 is max icon in 9.63 + bool notify = msg.GetByte() != 0; + addGameTask(&Game::playerRequestEditVip, player->getID(), guid, description, icon, notify); +} + +void ProtocolGame::parseRotateItem(NetworkMessage& msg) +{ + Position pos = msg.GetPosition(); + uint16_t spriteId = msg.GetSpriteId(); + uint8_t stackpos = msg.GetByte(); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerRotateItem, player->getID(), pos, stackpos, spriteId); +} + +void ProtocolGame::parseBugReport(NetworkMessage& msg) +{ + std::string bug = msg.GetString(); + addGameTask(&Game::playerReportBug, player->getID(), bug); +} + +void ProtocolGame::parseThankYou(NetworkMessage& msg) +{ + // uint32_t statementId = msg.GetU32(); + // addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::parseThankYou, player->getID(), statementId); +} + +void ProtocolGame::parseDebugAssert(NetworkMessage& msg) +{ + if (m_debugAssertSent) { + return; + } + + m_debugAssertSent = true; + + std::string assertLine = msg.GetString(); + std::string date = msg.GetString(); + std::string description = msg.GetString(); + std::string comment = msg.GetString(); + addGameTask(&Game::playerDebugAssert, player->getID(), assertLine, date, description, comment); +} + +void ProtocolGame::parseInviteToParty(NetworkMessage& msg) +{ + uint32_t targetId = msg.GetU32(); + addGameTask(&Game::playerInviteToParty, player->getID(), targetId); +} + +void ProtocolGame::parseJoinParty(NetworkMessage& msg) +{ + uint32_t targetId = msg.GetU32(); + addGameTask(&Game::playerJoinParty, player->getID(), targetId); +} + +void ProtocolGame::parseRevokePartyInvite(NetworkMessage& msg) +{ + uint32_t targetId = msg.GetU32(); + addGameTask(&Game::playerRevokePartyInvitation, player->getID(), targetId); +} + +void ProtocolGame::parsePassPartyLeadership(NetworkMessage& msg) +{ + uint32_t targetId = msg.GetU32(); + addGameTask(&Game::playerPassPartyLeadership, player->getID(), targetId); +} + +void ProtocolGame::parseLeaveParty(NetworkMessage& msg) +{ + addGameTask(&Game::playerLeaveParty, player->getID()); +} + +void ProtocolGame::parseEnableSharedPartyExperience(NetworkMessage& msg) +{ + bool sharedExpActive = msg.GetByte() == 1; + addGameTask(&Game::playerEnableSharedPartyExperience, player->getID(), sharedExpActive); +} + +void ProtocolGame::parseQuestLog(NetworkMessage& msg) +{ + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerShowQuestLog, player->getID()); +} + +void ProtocolGame::parseQuestLine(NetworkMessage& msg) +{ + uint16_t questId = msg.GetU16(); + addGameTask(&Game::playerShowQuestLine, player->getID(), questId); +} + +void ProtocolGame::parseRuleViolationReport(NetworkMessage& msg) +{ + /* TODO + ReportType_t type = msg.GetByte(); + uint8_t reason = msg.GetByte(); + std::string name = msg.GetString(); + std::string comment = msg.GetString(); + + switch(type) + { + case REPORTTYPE_STATEMENT: + { + std::string translation = msg.GetString(); + uint32_t statementId = msg.GetU32(); + break; + } + + case REPORTTYPE_NAME: + { + std::string translation = msg.GetString(); + break; + } + + default: break; + } + */ +} + +void ProtocolGame::parseGetObjectInfo(NetworkMessage& msg) +{ + // Unknown use + + // uint8_t objectCount = msg.GetByte(); + // for every object: + // msg.GetU16(); Item ID? + // msg.GetU8(); Sub type? + + // Responded to with: + // msg.AddByte(0xF4); + // msg.AddByte(objectCount); + // for every object: + // msg.AddU16(Item ID?); + // msg.AddU8(Sub type?); + // msg.AddString(Item name?); +} + +void ProtocolGame::parseMarketLeave() +{ + addGameTask(&Game::playerLeaveMarket, player->getID()); +} + +void ProtocolGame::parseMarketBrowse(NetworkMessage& msg) +{ + uint16_t browseId = msg.GetU16(); + + if (browseId == MARKETREQUEST_OWN_OFFERS) { + addGameTask(&Game::playerBrowseMarketOwnOffers, player->getID()); + } else if (browseId == MARKETREQUEST_OWN_HISTORY) { + addGameTask(&Game::playerBrowseMarketOwnHistory, player->getID()); + } else { + addGameTask(&Game::playerBrowseMarket, player->getID(), browseId); + } +} + +void ProtocolGame::parseMarketCreateOffer(NetworkMessage& msg) +{ + uint8_t type = msg.GetByte(); + uint16_t spriteId = msg.GetU16(); + uint16_t amount = msg.GetU16(); + uint32_t price = msg.GetU32(); + bool anonymous = (msg.GetByte() != 0); + addGameTask(&Game::playerCreateMarketOffer, player->getID(), type, spriteId, amount, price, anonymous); +} + +void ProtocolGame::parseMarketCancelOffer(NetworkMessage& msg) +{ + uint32_t timestamp = msg.GetU32(); + uint16_t counter = msg.GetU16(); + addGameTask(&Game::playerCancelMarketOffer, player->getID(), timestamp, counter); +} + +void ProtocolGame::parseMarketAcceptOffer(NetworkMessage& msg) +{ + uint32_t timestamp = msg.GetU32(); + uint16_t counter = msg.GetU16(); + uint16_t amount = msg.GetU16(); + addGameTask(&Game::playerAcceptMarketOffer, player->getID(), timestamp, counter, amount); +} + +void ProtocolGame::parseModalWindowAnswer(NetworkMessage& msg) +{ + uint32_t id = msg.GetU32(); + uint8_t button = msg.GetByte(); + uint8_t choice = msg.GetByte(); + addGameTask(&Game::playerAnswerModalWindow, player->getID(), id, button, choice); +} + +void ProtocolGame::parseBrowseField(NetworkMessage& msg) +{ + const Position& pos = msg.GetPosition(); + addGameTask(&Game::playerBrowseField, player->getID(), pos); +} + +void ProtocolGame::parseSeekInContainer(NetworkMessage& msg) +{ + uint8_t containerId = msg.GetByte(); + uint16_t index = msg.GetU16(); + addGameTask(&Game::playerSeekInContainer, player->getID(), containerId, index); +} + +//********************** Send methods *******************************// +void ProtocolGame::sendOpenPrivateChannel(const std::string& receiver) +{ + NetworkMessage msg; + msg.AddByte(0xAD); + msg.AddString(receiver); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendChannelEvent(uint16_t channelId, const std::string& playerName, ChannelEvent_t channelEvent) +{ + NetworkMessage msg; + msg.AddByte(0xF3); + msg.AddU16(channelId); + msg.AddString(playerName); + msg.AddByte(channelEvent); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureOutfit(const Creature* creature, const Outfit_t& outfit) +{ + if (!canSee(creature)) { + return; + } + + NetworkMessage msg; + msg.AddByte(0x8E); + msg.AddU32(creature->getID()); + + if (creature->isInGhostMode()) { + AddCreatureInvisible(msg, creature); + } else { + AddCreatureOutfit(msg, creature, outfit); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureInvisible(const Creature* creature) +{ + if (!canSee(creature)) { + return; + } + + NetworkMessage msg; + msg.AddByte(0x8E); + msg.AddU32(creature->getID()); + AddCreatureInvisible(msg, creature); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureLight(const Creature* creature) +{ + if (!canSee(creature)) { + return; + } + + NetworkMessage msg; + AddCreatureLight(msg, creature); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendWorldLight(const LightInfo& lightInfo) +{ + NetworkMessage msg; + AddWorldLight(msg, lightInfo); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureWalkthrough(const Creature* creature, bool walkthrough) +{ + if (!canSee(creature)) { + return; + } + + NetworkMessage msg; + msg.AddByte(0x92); + msg.AddU32(creature->getID()); + msg.AddByte(walkthrough ? 0x00 : 0x01); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureShield(const Creature* creature) +{ + if (!canSee(creature)) { + return; + } + + NetworkMessage msg; + msg.AddByte(0x91); + msg.AddU32(creature->getID()); + + PartyShields_t shield = player->getPartyShield(creature->getPlayer()); + + if (version < 979 && shield == SHIELD_GRAY) { + shield = SHIELD_NONE; + } + + msg.AddByte(shield); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureSkull(const Creature* creature) +{ + if (g_game.getWorldType() != WORLD_TYPE_PVP) { + return; + } + + if (!canSee(creature)) { + return; + } + + NetworkMessage msg; + msg.AddByte(0x90); + msg.AddU32(creature->getID()); + msg.AddByte(player->getSkullClient(creature->getPlayer())); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureType(uint32_t creatureId, uint8_t creatureType) +{ + if (version < 979) { + return; + } + + NetworkMessage msg; + msg.AddByte(0x95); + msg.AddU32(creatureId); + msg.AddByte(creatureType); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureHelpers(uint32_t creatureId, uint16_t helpers) +{ + if (version < 979) { + return; + } + + NetworkMessage msg; + msg.AddByte(0x94); + msg.AddU32(creatureId); + msg.AddU16(helpers); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureSquare(const Creature* creature, SquareColor_t color) +{ + if (!canSee(creature)) { + return; + } + + NetworkMessage msg; + msg.AddByte(0x86); + msg.AddU32(creature->getID()); + msg.AddByte((uint8_t)color); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendTutorial(uint8_t tutorialId) +{ + NetworkMessage msg; + msg.AddByte(0xDC); + msg.AddByte(tutorialId); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendAddMarker(const Position& pos, uint8_t markType, const std::string& desc) +{ + NetworkMessage msg; + msg.AddByte(0xDD); + msg.AddPosition(pos); + msg.AddByte(markType); + msg.AddString(desc); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendReLoginWindow(uint8_t unfairFightReduction) +{ + NetworkMessage msg; + msg.AddByte(0x28); + msg.AddByte(unfairFightReduction); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendStats() +{ + NetworkMessage msg; + AddPlayerStats(msg); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendBasicData() +{ + NetworkMessage msg; + msg.AddByte(0x9F); + msg.AddByte(player->isPremium() ? 0x01 : 0x00); + msg.AddByte(player->getVocation()->getClientId()); + /* + std::list spellIdList; + + const InstantsMap& instantsMap = g_spells->getInstantsMap(); + for(InstantsMap::const_iterator it = instantsMap.begin(), end = instantsMap.end(); it != end; ++it) + { + Spell* spell = it->second; + if(spell->getSpellId() == 0) continue; + if(spell->isInstant() && spell->isLearnable()) continue; + const VocSpellMap& vocSpellMap = spell->getVocMap(); + if(!vocSpellMap.empty() && vocSpellMap.find(player->getVocationId()) == vocSpellMap.end()) continue; + spellIdList.push_back(spell->getSpellId()); + } + + // known spells + msg.AddU16(spellIdList.size()); + for(std::list::const_iterator it = spellIdList.begin(), end = spellIdList.end(); it != end; ++it) + msg.AddByte(*it); + */ + msg.AddU16(0x00); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendTextMessage(MessageClasses mclass, const std::string& message, Position* pos/* = NULL*/, uint32_t value/* = 0*/, TextColor_t color/* = TEXTCOLOR_NONE*/) +{ + NetworkMessage msg; + + if (pos != NULL && (mclass == MSG_DAMAGE_DEALT || mclass == MSG_DAMAGE_RECEIVED || mclass == MSG_HEALED || mclass == MSG_EXPERIENCE || mclass == MSG_DAMAGE_OTHERS || mclass == MSG_HEALED_OTHERS || mclass == MSG_EXPERIENCE_OTHERS)) { + AddTextMessageEx(msg, mclass, message, *pos, value, color); + } else { + AddTextMessage(msg, mclass, message); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendClosePrivate(uint16_t channelId) +{ + NetworkMessage msg; + msg.AddByte(0xB3); + msg.AddU16(channelId); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatePrivateChannel(uint16_t channelId, const std::string& channelName) +{ + NetworkMessage msg; + msg.AddByte(0xB2); + msg.AddU16(channelId); + msg.AddString(channelName); + msg.AddU16(0x01); + msg.AddString(player->getName()); + msg.AddU16(0x00); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendChannelsDialog() +{ + NetworkMessage msg; + msg.AddByte(0xAB); + + const ChannelList& list = g_chat.getChannelList(player); + msg.AddByte(list.size()); + + for (ChannelList::const_iterator it = list.begin(); it != list.end(); ++it) { + ChatChannel* channel = *it; + msg.AddU16(channel->getId()); + msg.AddString(channel->getName()); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendChannel(uint16_t channelId, const std::string& channelName) +{ + NetworkMessage msg; + msg.AddByte(0xAC); + + msg.AddU16(channelId); + msg.AddString(channelName); + + ChatChannel* channel; + + if ((channelId == CHANNEL_GUILD || channelId == CHANNEL_PARTY || channelId == CHANNEL_PRIVATE) && (channel = g_chat.getChannel(player, channelId))) { + UsersMap channelUsers = channel->getUsers(); + msg.AddU16(channelUsers.size()); + + for (UsersMap::iterator it = channelUsers.begin(); it != channelUsers.end(); ++it) { + msg.AddString(it->second->getName()); + } + + PrivateChatChannel* privateChannel = dynamic_cast(channel); + + if (privateChannel) { + InvitedMap invitedUsers = privateChannel->getInvitedUsers(); + msg.AddU16(invitedUsers.size()); + + for (InvitedMap::iterator it = invitedUsers.begin(); it != invitedUsers.end(); ++it) { + msg.AddString(it->second->getName()); + } + } else { + msg.AddU16(0x00); + } + } else { + msg.AddU16(0x00); + msg.AddU16(0x00); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendChannelMessage(const std::string& author, const std::string& text, SpeakClasses type, uint16_t channel) +{ + NetworkMessage msg; + msg.AddByte(0xAA); + msg.AddU32(0x00); + msg.AddString(author); + msg.AddU16(0x00); + msg.AddByte(type); + msg.AddU16(channel); + msg.AddString(text); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendIcons(int32_t icons) +{ + NetworkMessage msg; + msg.AddByte(0xA2); + msg.AddU16(icons); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendContainer(uint8_t cid, const Container* container, bool hasParent, uint16_t firstIndex) +{ + NetworkMessage msg; + msg.AddByte(0x6E); + + msg.AddByte(cid); + + if (container->getID() == 460) { + msg.AddItem(1987, 1, version); + msg.AddString("Browse Field"); + } else { + msg.AddItem(container, version); + msg.AddString(container->getName()); + } + + msg.AddByte(container->capacity()); + + msg.AddByte(hasParent ? 0x01 : 0x00); + + uint32_t maxItemsToSend; + + if (version > 975) { + msg.AddByte(container->isUnlocked() ? 0x01 : 0x00); // Drag and drop + msg.AddByte(container->hasPages() ? 0x01 : 0x00); // Pagination + + msg.AddU16(container->size()); + msg.AddU16(firstIndex); + + if (container->hasPages() && firstIndex > 0) { + maxItemsToSend = std::min(container->capacity(), container->size() - firstIndex); + } else { + maxItemsToSend = container->capacity(); + } + } else { + maxItemsToSend = 0xFF; + } + + msg.AddByte(std::min(maxItemsToSend, container->size())); + + uint32_t i = 0; + + for (ItemDeque::const_iterator cit = container->getItems() + firstIndex, end = container->getEnd(); i < maxItemsToSend && cit != end; ++cit, ++i) { + msg.AddItem(*cit, version); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendShop(Npc* npc, const ShopInfoList& itemList) +{ + NetworkMessage msg; + msg.AddByte(0x7A); + msg.AddString(npc->getName()); + msg.AddU16(std::min(0xFFFF, itemList.size())); + + uint32_t i = 0; + + for (ShopInfoList::const_iterator it = itemList.begin(), end = itemList.end(); i < 0xFFFF && it != end; ++it, ++i) { + AddShopItem(msg, *it); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCloseShop() +{ + NetworkMessage msg; + msg.AddByte(0x7C); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendSaleItemList(const std::list& shop) +{ + NetworkMessage msg; + msg.AddByte(0x7B); + msg.AddU64(g_game.getMoney(player)); + + std::map saleMap; + + if (shop.size() <= 5) { + // For very small shops it's not worth it to create the complete map + for (std::list::const_iterator it = shop.begin(); it != shop.end(); ++it) { + const ShopInfo& sInfo = *it; + + if (sInfo.sellPrice > 0) { + int8_t subtype = -1; + const ItemType& it = Item::items[sInfo.itemId]; + + if (it.hasSubType() && !it.stackable) { + subtype = (sInfo.subType == 0 ? -1 : sInfo.subType); + } + + uint32_t count = player->__getItemTypeCount(sInfo.itemId, subtype); + + if (count > 0) { + saleMap[sInfo.itemId] = count; + } + } + } + } else { + // Large shop, it's better to get a cached map of all item counts and use it + // We need a temporary map since the finished map should only contain items + // available in the shop + std::map tempSaleMap; + player->__getAllItemTypeCount(tempSaleMap); + + // We must still check manually for the special items that require subtype matches + // (That is, fluids such as potions etc., actually these items are very few since + // health potions now use their own ID) + for (std::list::const_iterator it = shop.begin(); it != shop.end(); ++it) { + const ShopInfo& sInfo = *it; + + if (sInfo.sellPrice > 0) { + int8_t subtype = -1; + const ItemType& it = Item::items[sInfo.itemId]; + + if (it.hasSubType() && !it.stackable) { + subtype = (sInfo.subType == 0 ? -1 : sInfo.subType); + } + + if (subtype != -1) { + uint32_t count = subtype; + + if (!it.isFluidContainer() && !it.isSplash()) { + count = player->__getItemTypeCount(sInfo.itemId, subtype); // This shop item requires extra checks + } + + if (count > 0) { + saleMap[sInfo.itemId] = count; + } + } else { + std::map::const_iterator findIt = tempSaleMap.find(sInfo.itemId); + + if (findIt != tempSaleMap.end() && findIt->second > 0) { + saleMap[sInfo.itemId] = findIt->second; + } + } + } + } + } + + msg.AddByte(std::min(0xFF, saleMap.size())); + + uint32_t i = 0; + + for (std::map::const_iterator it = saleMap.begin(), end = saleMap.end(); i < 0xFF && it != end; ++it, ++i) { + msg.AddItemId(it->first, version); + msg.AddByte(std::min(0xFF, it->second)); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendMarketEnter(uint32_t depotId) +{ + NetworkMessage msg; + msg.AddByte(0xF6); + + msg.AddU64(player->getBankBalance()); + msg.AddByte(std::min(0xFF, IOMarket::getInstance()->getPlayerOfferCount(player->getGUID()))); + + DepotChest* depotChest = player->getDepotChest(depotId, false); + + if (!depotChest) { + msg.AddU16(0x00); + writeToOutputBuffer(msg); + return; + } + + player->setMarketDepotId(depotId); + + std::map depotItems; + std::list containerList; + containerList.push_back(depotChest); + containerList.push_back(player->getInbox()); + + do { + Container* container = containerList.front(); + containerList.pop_front(); + + for (ItemDeque::const_iterator it = container->getItems(), end = container->getEnd(); it != end; ++it) { + Item* item = *it; + + Container* c = item->getContainer(); + + if (c && !c->empty()) { + containerList.push_back(c); + continue; + } + + const ItemType& itemType = Item::items[item->getID()]; + + if (!itemType.ware) { + continue; + } + + if (!itemType.isRune() && item->getCharges() != itemType.charges) { + continue; + } + + if (item->getDuration() != itemType.decayTime) { + continue; + } + + depotItems[item->getID()] += Item::countByType(item, -1); + } + } while (!containerList.empty()); + + msg.AddU16(std::min(0xFFFF, depotItems.size())); + + uint16_t i = 0; + + for (std::map::const_iterator it = depotItems.begin(), end = depotItems.end(); it != end && i < 0xFFFF; ++it, ++i) { + msg.AddItemId(it->first, version); + msg.AddU16(std::min(0xFFFF, it->second)); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendMarketLeave() +{ + NetworkMessage msg; + msg.AddByte(0xF7); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendMarketBrowseItem(uint16_t itemId, const MarketOfferList& buyOffers, const MarketOfferList& sellOffers) +{ + NetworkMessage msg; + + msg.AddByte(0xF9); + msg.AddItemId(itemId, version); + + msg.AddU32(buyOffers.size()); + + for (MarketOfferList::const_iterator it = buyOffers.begin(), end = buyOffers.end(); it != end; ++it) { + msg.AddU32(it->timestamp); + msg.AddU16(it->counter); + msg.AddU16(it->amount); + msg.AddU32(it->price); + msg.AddString(it->playerName); + } + + msg.AddU32(sellOffers.size()); + + for (MarketOfferList::const_iterator it = sellOffers.begin(), end = sellOffers.end(); it != end; ++it) { + msg.AddU32(it->timestamp); + msg.AddU16(it->counter); + msg.AddU16(it->amount); + msg.AddU32(it->price); + msg.AddString(it->playerName); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendMarketAcceptOffer(MarketOfferEx offer) +{ + NetworkMessage msg; + msg.AddByte(0xF9); + msg.AddItemId(offer.itemId, version); + + if (offer.type == MARKETACTION_BUY) { + msg.AddU32(0x01); + msg.AddU32(offer.timestamp); + msg.AddU16(offer.counter); + msg.AddU16(offer.amount); + msg.AddU32(offer.price); + msg.AddString(offer.playerName); + msg.AddU32(0x00); + } else { + msg.AddU32(0x00); + msg.AddU32(0x01); + msg.AddU32(offer.timestamp); + msg.AddU16(offer.counter); + msg.AddU16(offer.amount); + msg.AddU32(offer.price); + msg.AddString(offer.playerName); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendMarketBrowseOwnOffers(const MarketOfferList& buyOffers, const MarketOfferList& sellOffers) +{ + NetworkMessage msg; + msg.AddByte(0xF9); + msg.AddU16(MARKETREQUEST_OWN_OFFERS); + + msg.AddU32(buyOffers.size()); + + for (MarketOfferList::const_iterator it = buyOffers.begin(), end = buyOffers.end(); it != end; ++it) { + msg.AddU32(it->timestamp); + msg.AddU16(it->counter); + msg.AddItemId(it->itemId, version); + msg.AddU16(it->amount); + msg.AddU32(it->price); + } + + msg.AddU32(sellOffers.size()); + + for (MarketOfferList::const_iterator it = sellOffers.begin(), end = sellOffers.end(); it != end; ++it) { + msg.AddU32(it->timestamp); + msg.AddU16(it->counter); + msg.AddItemId(it->itemId, version); + msg.AddU16(it->amount); + msg.AddU32(it->price); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendMarketCancelOffer(MarketOfferEx offer) +{ + NetworkMessage msg; + msg.AddByte(0xF9); + msg.AddU16(MARKETREQUEST_OWN_OFFERS); + + if (offer.type == MARKETACTION_BUY) { + msg.AddU32(0x01); + msg.AddU32(offer.timestamp); + msg.AddU16(offer.counter); + msg.AddItemId(offer.itemId, version); + msg.AddU16(offer.amount); + msg.AddU32(offer.price); + msg.AddU32(0x00); + } else { + msg.AddU32(0x00); + msg.AddU32(0x01); + msg.AddU32(offer.timestamp); + msg.AddU16(offer.counter); + msg.AddItemId(offer.itemId, version); + msg.AddU16(offer.amount); + msg.AddU32(offer.price); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendMarketBrowseOwnHistory(const HistoryMarketOfferList& buyOffers, const HistoryMarketOfferList& sellOffers) +{ + NetworkMessage msg; + msg.AddByte(0xF9); + msg.AddU16(MARKETREQUEST_OWN_HISTORY); + + std::map counterMap; + + uint32_t i = 0; + msg.AddU32(std::min(800, buyOffers.size())); + + for (HistoryMarketOfferList::const_iterator it = buyOffers.begin(), end = buyOffers.end(); it != end && i < 800; ++it, ++i) { + msg.AddU32(it->timestamp); + msg.AddU16(counterMap[it->timestamp]++); + msg.AddItemId(it->itemId, version); + msg.AddU16(it->amount); + msg.AddU32(it->price); + msg.AddByte(it->state); + } + + counterMap.clear(); + i = 0; + + msg.AddU32(std::min(800, sellOffers.size())); + + for (HistoryMarketOfferList::const_iterator it = sellOffers.begin(), end = sellOffers.end(); it != end && i < 800; ++it, ++i) { + msg.AddU32(it->timestamp); + msg.AddU16(counterMap[it->timestamp]++); + msg.AddItemId(it->itemId, version); + msg.AddU16(it->amount); + msg.AddU32(it->price); + msg.AddByte(it->state); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendMarketDetail(uint16_t itemId) +{ + NetworkMessage msg; + msg.AddByte(0xF8); + msg.AddItemId(itemId, version); + + const ItemType& it = Item::items[itemId]; + + if (it.armor != 0) { + msg.AddString(std::to_string(it.armor)); + } else { + msg.AddU16(0x00); + } + + if (it.attack != 0) { + // TODO: chance to hit, range + // example: + // "attack +x, chance to hit +y%, z fields" + if (it.abilities && it.abilities->elementType != COMBAT_NONE && it.decayTo > 0) { + std::ostringstream ss; + ss << it.attack << " physical +" << it.abilities->elementDamage << " " << getCombatName(it.abilities->elementType); + msg.AddString(ss.str()); + } else { + msg.AddString(std::to_string(it.attack)); + } + } else { + msg.AddU16(0x00); + } + + if (it.isContainer()) { + msg.AddString(std::to_string(it.maxItems)); + } else { + msg.AddU16(0x00); + } + + if (it.defense != 0) { + if (it.extraDefense != 0) { + std::ostringstream ss; + ss << it.defense << " " << std::showpos << it.extraDefense << std::noshowpos; + msg.AddString(ss.str()); + } else { + msg.AddString(std::to_string(it.defense)); + } + } else { + msg.AddU16(0x00); + } + + if (!it.description.empty()) { + std::string descr = it.description; + + if (descr[descr.length() - 1] == '.') { + descr.erase(descr.length() - 1); + } + + msg.AddString(descr); + } else { + msg.AddU16(0x00); + } + + if (it.decayTime != 0) { + std::ostringstream ss; + ss << it.decayTime << " seconds"; + msg.AddString(ss.str()); + } else { + msg.AddU16(0x00); + } + + if (it.abilities) { + std::ostringstream ss; + bool separator = false; + + for (uint32_t i = (COMBAT_FIRST + 1); i <= COMBAT_COUNT; ++i) { + if (!it.abilities->absorbPercent[i]) { + continue; + } + + if (separator) { + ss << ", "; + } else { + separator = true; + } + + ss << getCombatName(indexToCombatType(i)) << " " << std::showpos << it.abilities->absorbPercent[i] << std::noshowpos << "%"; + } + + msg.AddString(ss.str()); + } else { + msg.AddU16(0x00); + } + + if (it.minReqLevel != 0) { + msg.AddString(std::to_string(it.minReqLevel)); + } else { + msg.AddU16(0x00); + } + + if (it.minReqMagicLevel != 0) { + msg.AddString(std::to_string(it.minReqMagicLevel)); + } else { + msg.AddU16(0x00); + } + + msg.AddString(it.vocationString); + + msg.AddString(it.runeSpellName); + + if (it.abilities) { + std::ostringstream ss; + bool separator = false; + + for (uint16_t i = SKILL_FIRST; i <= SKILL_LAST; i++) { + if (!it.abilities->skills[i]) { + continue; + } + + if (separator) { + ss << ", "; + } else { + separator = true; + } + + ss << getSkillName(i) << " " << std::showpos << it.abilities->skills[i] << std::noshowpos; + } + + if (it.abilities->stats[STAT_MAGICPOINTS] != 0) { + if (separator) { + ss << ", "; + } else { + separator = true; + } + + ss << "magic level " << std::showpos << it.abilities->stats[STAT_MAGICPOINTS] << std::noshowpos; + } + + if (it.abilities->speed != 0) { + if (separator) { + ss << ", "; + } + + ss << "speed" << " " << std::showpos << (int32_t)(it.abilities->speed / 2) << std::noshowpos; + } + + msg.AddString(ss.str()); + } else { + msg.AddU16(0x00); + } + + if (it.charges != 0) { + msg.AddString(std::to_string(it.charges)); + } else { + msg.AddU16(0x00); + } + + std::string weaponName = getWeaponName(it.weaponType); + + if (it.slotPosition & SLOTP_TWO_HAND) { + if (!weaponName.empty()) { + weaponName += ", two-handed"; + } else { + weaponName = "two-handed"; + } + } + + msg.AddString(weaponName); + + if (it.weight > 0) { + std::ostringstream ss; + ss << std::fixed << std::setprecision(2) << it.weight << " oz"; + msg.AddString(ss.str()); + } else { + msg.AddU16(0x00); + } + + MarketStatistics* statistics = IOMarket::getInstance()->getPurchaseStatistics(itemId); + + if (statistics) { + msg.AddByte(0x01); + msg.AddU32(statistics->numTransactions); + msg.AddU32(std::min(0xFFFFFFFF, statistics->totalPrice)); + msg.AddU32(statistics->highestPrice); + msg.AddU32(statistics->lowestPrice); + } else { + msg.AddByte(0x00); + } + + statistics = IOMarket::getInstance()->getSaleStatistics(itemId); + + if (statistics) { + msg.AddByte(0x01); + msg.AddU32(statistics->numTransactions); + msg.AddU32(std::min(0xFFFFFFFF, statistics->totalPrice)); + msg.AddU32(statistics->highestPrice); + msg.AddU32(statistics->lowestPrice); + } else { + msg.AddByte(0x00); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendQuestLog() +{ + NetworkMessage msg; + msg.AddByte(0xF0); + msg.AddU16(Quests::getInstance()->getQuestsCount(player)); + + for (QuestsList::const_iterator it = Quests::getInstance()->getFirstQuest(), end = Quests::getInstance()->getLastQuest(); it != end; ++it) { + Quest* quest = *it; + + if (quest->isStarted(player)) { + msg.AddU16(quest->getID()); + msg.AddString(quest->getName()); + msg.AddByte(quest->isCompleted(player)); + } + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendQuestLine(const Quest* quest) +{ + NetworkMessage msg; + msg.AddByte(0xF1); + msg.AddU16(quest->getID()); + msg.AddByte(quest->getMissionsCount(player)); + + for (MissionsList::const_iterator it = quest->getFirstMission(), end = quest->getLastMission(); it != end; ++it) { + Mission* mission = *it; + + if (mission->isStarted(player)) { + msg.AddString(mission->getName(player)); + msg.AddString(mission->getDescription(player)); + } + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendTradeItemRequest(const Player* player, const Item* item, bool ack) +{ + NetworkMessage msg; + + if (ack) { + msg.AddByte(0x7D); + } else { + msg.AddByte(0x7E); + } + + msg.AddString(player->getName()); + + if (const Container* tradeContainer = item->getContainer()) { + std::list listContainer; + ItemDeque::const_iterator it; + Container* tmpContainer = NULL; + + listContainer.push_back(tradeContainer); + + std::list listItem; + listItem.push_back(tradeContainer); + + while (!listContainer.empty()) { + const Container* container = listContainer.front(); + listContainer.pop_front(); + + for (it = container->getItems(); it != container->getEnd(); ++it) { + if ((tmpContainer = (*it)->getContainer())) { + listContainer.push_back(tmpContainer); + } + + listItem.push_back(*it); + } + } + + msg.AddByte(listItem.size()); + + while (!listItem.empty()) { + const Item* item = listItem.front(); + listItem.pop_front(); + msg.AddItem(item, version); + } + } else { + msg.AddByte(0x01); + msg.AddItem(item, version); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCloseTrade() +{ + NetworkMessage msg; + msg.AddByte(0x7F); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCloseContainer(uint8_t cid) +{ + NetworkMessage msg; + msg.AddByte(0x6F); + msg.AddByte(cid); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureTurn(const Creature* creature, uint32_t stackPos) +{ + if (stackPos >= 10 || !canSee(creature)) { + return; + } + + NetworkMessage msg; + msg.AddByte(0x6B); + msg.AddPosition(creature->getPosition()); + msg.AddByte(stackPos); + msg.AddU16(0x63); + msg.AddU32(creature->getID()); + msg.AddByte(creature->getDirection()); + msg.AddByte(player->canWalkthroughEx(creature) ? 0x00 : 0x01); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, Position* pos/* = NULL*/) +{ + NetworkMessage msg; + AddCreatureSpeak(msg, creature, type, text, 0, pos); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendToChannel(const Creature* creature, SpeakClasses type, const std::string& text, uint16_t channelId) +{ + NetworkMessage msg; + AddCreatureSpeak(msg, creature, type, text, channelId); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCancel(const std::string& message) +{ + NetworkMessage msg; + AddTextMessage(msg, MSG_STATUS_SMALL, message); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCancelTarget() +{ + NetworkMessage msg; + msg.AddByte(0xA3); + msg.AddU32(0x00); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendChangeSpeed(const Creature* creature, uint32_t speed) +{ + NetworkMessage msg; + msg.AddByte(0x8F); + msg.AddU32(creature->getID()); + msg.AddU16(speed / 2); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCancelWalk() +{ + NetworkMessage msg; + msg.AddByte(0xB5); + msg.AddByte(player->getDirection()); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendSkills() +{ + NetworkMessage msg; + AddPlayerSkills(msg); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendPing() +{ + NetworkMessage msg; + msg.AddByte(0x1D); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendPingBack() +{ + NetworkMessage msg; + msg.AddByte(0x1E); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendDistanceShoot(const Position& from, const Position& to, uint8_t type) +{ + NetworkMessage msg; + AddDistanceShoot(msg, from, to, type); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendMagicEffect(const Position& pos, uint8_t type) +{ + if (!canSee(pos)) { + return; + } + + NetworkMessage msg; + AddMagicEffect(msg, pos, type); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureHealth(const Creature* creature) +{ + NetworkMessage msg; + AddCreatureHealth(msg, creature); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendFYIBox(const std::string& message) +{ + NetworkMessage msg; + msg.AddByte(0x15); + msg.AddString(message); + writeToOutputBuffer(msg); +} + +//tile +void ProtocolGame::sendMapDescription(const Position& pos) +{ + NetworkMessage msg; + msg.AddByte(0x64); + msg.AddPosition(player->getPosition()); + GetMapDescription(pos.x - 8, pos.y - 6, pos.z, 18, 14, msg); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendAddTileItem(const Tile* tile, const Position& pos, uint32_t stackpos, const Item* item) +{ + if (!canSee(pos)) { + return; + } + + NetworkMessage msg; + AddTileItem(msg, pos, stackpos, item); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendUpdateTileItem(const Tile* tile, const Position& pos, uint32_t stackpos, const Item* item) +{ + if (!canSee(pos)) { + return; + } + + NetworkMessage msg; + UpdateTileItem(msg, pos, stackpos, item); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendRemoveTileItem(const Tile* tile, const Position& pos, uint32_t stackpos) +{ + if (!canSee(pos)) { + return; + } + + NetworkMessage msg; + RemoveTileItem(msg, pos, stackpos); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendUpdateTile(const Tile* tile, const Position& pos) +{ + if (!canSee(pos)) { + return; + } + + NetworkMessage msg; + msg.AddByte(0x69); + msg.AddPosition(pos); + + if (tile) { + GetTileDescription(tile, msg); + msg.AddByte(0x00); + msg.AddByte(0xFF); + } else { + msg.AddByte(0x01); + msg.AddByte(0xFF); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendPendingStateEntered() +{ + NetworkMessage msg; + msg.AddByte(0x0A); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendEnterWorld() +{ + NetworkMessage msg; + msg.AddByte(0x0F); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendAddCreature(const Creature* creature, const Position& pos, uint32_t stackpos, bool isLogin) +{ + if (!canSee(pos)) { + return; + } + + NetworkMessage msg; + + if (creature != player) { + AddTileCreature(msg, pos, stackpos, creature); + writeToOutputBuffer(msg); + + if (isLogin) { + sendMagicEffect(pos, NM_ME_TELEPORT); + } + + return; + } + + msg.AddByte(0x17); + + msg.AddU32(player->getID()); + msg.AddU16(0x32); // beat duration (50) + + msg.AddDouble(Creature::speedA, 3); + msg.AddDouble(Creature::speedB, 3); + msg.AddDouble(Creature::speedC, 3); + + // can report bugs? + if (player->getAccountType() >= ACCOUNT_TYPE_TUTOR) { + msg.AddByte(0x01); + } else { + msg.AddByte(0x00); + } + + writeToOutputBuffer(msg); + + sendPendingStateEntered(); + sendEnterWorld(); + sendMapDescription(pos); + + if (isLogin) { + sendMagicEffect(pos, NM_ME_TELEPORT); + } + + sendInventoryItem(SLOT_HEAD, player->getInventoryItem(SLOT_HEAD)); + sendInventoryItem(SLOT_NECKLACE, player->getInventoryItem(SLOT_NECKLACE)); + sendInventoryItem(SLOT_BACKPACK, player->getInventoryItem(SLOT_BACKPACK)); + sendInventoryItem(SLOT_ARMOR, player->getInventoryItem(SLOT_ARMOR)); + sendInventoryItem(SLOT_RIGHT, player->getInventoryItem(SLOT_RIGHT)); + sendInventoryItem(SLOT_LEFT, player->getInventoryItem(SLOT_LEFT)); + sendInventoryItem(SLOT_LEGS, player->getInventoryItem(SLOT_LEGS)); + sendInventoryItem(SLOT_FEET, player->getInventoryItem(SLOT_FEET)); + sendInventoryItem(SLOT_RING, player->getInventoryItem(SLOT_RING)); + sendInventoryItem(SLOT_AMMO, player->getInventoryItem(SLOT_AMMO)); + + sendStats(); + sendSkills(); + + //gameworld light-settings + LightInfo lightInfo; + g_game.getWorldLightInfo(lightInfo); + sendWorldLight(lightInfo); + + //player light level + sendCreatureLight(creature); + + if (isLogin) { + // TODO: Move to onLogin script + std::string loginStr = g_config.getString(ConfigManager::LOGIN_MSG); + + if (player->getLastLoginSaved() <= 0) { + loginStr += " Please choose your outfit."; + sendOutfitWindow(); + } else { + if (!loginStr.empty()) { + sendTextMessage(MSG_STATUS_DEFAULT, loginStr.c_str()); + } + + time_t lastLogin = player->getLastLoginSaved(); + + std::ostringstream ss; + ss << "Your last visit was on " << ctime(&lastLogin); + loginStr = ss.str(); + loginStr.pop_back(); + loginStr += "."; + } + + sendTextMessage(MSG_STATUS_DEFAULT, loginStr); + } + + const std::list& vipEntries = IOLoginData::getInstance()->getVIPEntries(player->getAccount()); + + if (player->isAccessPlayer()) { + for (std::list::const_iterator it = vipEntries.begin(), end = vipEntries.end(); it != end; ++it) { + const VIPEntry& entry = (*it); + + VipStatus_t vipStatus; + + Player* vipPlayer = g_game.getPlayerByGUID(entry.guid); + + if (!vipPlayer) { + vipStatus = VIPSTATUS_OFFLINE; + } + /* + else if(vipPlayer->isPending) + vipStatus = VIPSTATUS_PENDING; + */ + else { + vipStatus = VIPSTATUS_ONLINE; + } + + sendVIP(entry.guid, entry.name, entry.description, entry.icon, entry.notify, vipStatus); + } + } else { + for (std::list::const_iterator it = vipEntries.begin(), end = vipEntries.end(); it != end; ++it) { + const VIPEntry& entry = (*it); + + VipStatus_t vipStatus; + + Player* vipPlayer = g_game.getPlayerByGUID(entry.guid); + + if (!vipPlayer || vipPlayer->isInGhostMode()) { + vipStatus = VIPSTATUS_OFFLINE; + } + /* + else if(vipPlayer->isPending) + vipStatus = VIPSTATUS_PENDING; + */ + else { + vipStatus = VIPSTATUS_ONLINE; + } + + sendVIP(entry.guid, entry.name, entry.description, entry.icon, entry.notify, vipStatus); + } + } + + sendBasicData(); + player->sendIcons(); +} + +void ProtocolGame::sendRemoveCreature(const Creature* creature, const Position& pos, uint32_t stackpos, bool isLogout) +{ + if (creature->isInGhostMode() && !player->isAccessPlayer()) { + return; + } + + if (!canSee(pos)) { + return; + } + + NetworkMessage msg; + RemoveTileItem(msg, pos, stackpos); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendMoveCreature(const Creature* creature, const Tile* newTile, const Position& newPos, + uint32_t newStackPos, const Tile* oldTile, const Position& oldPos, uint32_t oldStackPos, bool teleport) +{ + if (creature == player) { + if (teleport || oldStackPos >= 10) { + NetworkMessage msg; + RemoveTileItem(msg, oldPos, oldStackPos); + writeToOutputBuffer(msg); + sendMapDescription(newPos); + } else { + NetworkMessage msg; + + if (oldPos.z == 7 && newPos.z >= 8) { + RemoveTileItem(msg, oldPos, oldStackPos); + } else { + msg.AddByte(0x6D); + msg.AddPosition(oldPos); + msg.AddByte(oldStackPos); + msg.AddPosition(newPos); + } + + if (newPos.z > oldPos.z) { + MoveDownCreature(msg, creature, newPos, oldPos, oldStackPos); + } else if (newPos.z < oldPos.z) { + MoveUpCreature(msg, creature, newPos, oldPos, oldStackPos); + } + + if (oldPos.y > newPos.y) { // north, for old x + msg.AddByte(0x65); + GetMapDescription(oldPos.x - 8, newPos.y - 6, newPos.z, 18, 1, msg); + } else if (oldPos.y < newPos.y) { // south, for old x + msg.AddByte(0x67); + GetMapDescription(oldPos.x - 8, newPos.y + 7, newPos.z, 18, 1, msg); + } + + if (oldPos.x < newPos.x) { // east, [with new y] + msg.AddByte(0x66); + GetMapDescription(newPos.x + 9, newPos.y - 6, newPos.z, 1, 14, msg); + } else if (oldPos.x > newPos.x) { // west, [with new y] + msg.AddByte(0x68); + GetMapDescription(newPos.x - 8, newPos.y - 6, newPos.z, 1, 14, msg); + } + + writeToOutputBuffer(msg); + } + } else if (canSee(oldPos) && canSee(creature->getPosition())) { + if (teleport || (oldPos.z == 7 && newPos.z >= 8) || oldStackPos >= 10) { + sendRemoveCreature(creature, oldPos, oldStackPos, false); + sendAddCreature(creature, newPos, newStackPos, false); + } else { + NetworkMessage msg; + msg.AddByte(0x6D); + msg.AddPosition(oldPos); + msg.AddByte(oldStackPos); + msg.AddPosition(creature->getPosition()); + writeToOutputBuffer(msg); + } + } else if (canSee(oldPos)) { + sendRemoveCreature(creature, oldPos, oldStackPos, false); + } else if (canSee(creature->getPosition())) { + sendAddCreature(creature, newPos, newStackPos, false); + } +} + +void ProtocolGame::sendInventoryItem(slots_t slot, const Item* item) +{ + NetworkMessage msg; + + if (item) { + msg.AddByte(0x78); + msg.AddByte(slot); + msg.AddItem(item, version); + } else { + msg.AddByte(0x79); + msg.AddByte(slot); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendAddContainerItem(uint8_t cid, uint16_t slot, const Item* item) +{ + NetworkMessage msg; + msg.AddByte(0x70); + msg.AddByte(cid); + + if (version > 975) { + msg.AddU16(slot); + } + + msg.AddItem(item, version); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendUpdateContainerItem(uint8_t cid, uint16_t slot, const Item* item) +{ + NetworkMessage msg; + msg.AddByte(0x71); + msg.AddByte(cid); + + if (version > 975) { + msg.AddU16(slot); + } else { + msg.AddByte(std::min(0xFF, slot)); + } + + msg.AddItem(item, version); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendRemoveContainerItem(uint8_t cid, uint16_t slot, const Item* lastItem) +{ + NetworkMessage msg; + msg.AddByte(0x72); + msg.AddByte(cid); + + if (version > 975) { + msg.AddU16(slot); + + if (lastItem) { + msg.AddItem(lastItem, version); + } else { + msg.AddU16(0x00); + } + } else { + msg.AddByte(std::min(0xFF, slot)); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendTextWindow(uint32_t windowTextId, Item* item, uint16_t maxlen, bool canWrite) +{ + NetworkMessage msg; + msg.AddByte(0x96); + msg.AddU32(windowTextId); + msg.AddItem(item, version); + + if (canWrite) { + msg.AddU16(maxlen); + msg.AddString(item->getText()); + } else { + msg.AddU16(item->getText().size()); + msg.AddString(item->getText()); + } + + const std::string& writer = item->getWriter(); + + if (writer.size()) { + msg.AddString(writer); + } else { + msg.AddU16(0x00); + } + + time_t writtenDate = item->getDate(); + + if (writtenDate > 0) { + msg.AddString(formatDateShort(writtenDate)); + } else { + msg.AddU16(0x00); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendTextWindow(uint32_t windowTextId, uint32_t itemId, const std::string& text) +{ + NetworkMessage msg; + msg.AddByte(0x96); + msg.AddU32(windowTextId); + msg.AddItem(itemId, 1, version); + msg.AddU16(text.size()); + msg.AddString(text); + msg.AddU16(0x00); + msg.AddU16(0x00); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendHouseWindow(uint32_t windowTextId, House* _house, + uint32_t listId, const std::string& text) +{ + NetworkMessage msg; + msg.AddByte(0x97); + msg.AddByte(0x00); + msg.AddU32(windowTextId); + msg.AddString(text); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendOutfitWindow() +{ + NetworkMessage msg; + msg.AddByte(0xC8); + + Outfit_t currentOutfit = player->getDefaultOutfit(); + Mount* currentMount = Mounts::getInstance()->getMountByID(player->getCurrentMount()); + + if (currentMount) { + currentOutfit.lookMount = currentMount->getClientID(); + } + + AddCreatureOutfit(msg, player, currentOutfit); + + const OutfitListType& globalOutfits = Outfits::getInstance()->getOutfits(player->getSex()); + std::list outfits; + + if (player->isAccessPlayer()) { + Outfit _outfit; + _outfit.looktype = 75; + _outfit.addons = 3; + outfits.push_back(_outfit); + + for (OutfitListType::const_iterator it = globalOutfits.begin(), end = globalOutfits.end(); it != end; ++it) { + Outfit outfit; + outfit.looktype = (*it)->looktype; + outfit.addons = 3; + outfits.push_back(outfit); + + if (outfits.size() >= 50) { // Tibia client doesn't allow more than 50 outfits + break; + } + } + } else if (player->isPremium()) { + for (OutfitListType::const_iterator it = globalOutfits.begin(), end = globalOutfits.end(); it != end; ++it) { + Outfit outfit; + outfit.looktype = (*it)->looktype; + outfit.addons = player->getOutfitAddons((*it)->looktype); + outfits.push_back(outfit); + + if (outfits.size() >= 50) { // Read the previous comment + break; + } + } + } else { + for (OutfitListType::const_iterator it = globalOutfits.begin(), end = globalOutfits.end(); it != end; ++it) { + if ((*it)->premium) { + continue; + } + + Outfit outfit; + outfit.looktype = (*it)->looktype; + outfit.addons = player->getOutfitAddons((*it)->looktype); + outfits.push_back(outfit); + + if (outfits.size() >= 50) { // Read the previous comment + break; + } + } + } + + msg.AddByte(outfits.size()); + + for (std::list::const_iterator it = outfits.begin(), end = outfits.end(); it != end; ++it) { + msg.AddU16(it->looktype); + msg.AddString(Outfits::getInstance()->getOutfitName(it->looktype)); + msg.AddByte(it->addons); + } + + MountsList mounts; + + for (MountsList::const_iterator it = Mounts::getInstance()->getFirstMount(), + end = Mounts::getInstance()->getLastMount(); it != end; ++it) { + if ((*it)->isTamed(player)) { + mounts.push_back(*it); + } + } + + msg.AddByte(mounts.size()); + + for (MountsList::const_iterator it = mounts.begin(), end = mounts.end(); it != end; ++it) { + msg.AddU16((*it)->getClientID()); + msg.AddString((*it)->getName()); + } + + player->hasRequestedOutfit(true); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendUpdatedVIPStatus(uint32_t guid, VipStatus_t newStatus) +{ + NetworkMessage msg; + msg.AddByte(0xD3); + msg.AddU32(guid); + msg.AddByte(newStatus); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendVIP(uint32_t guid, const std::string& name, const std::string& description, uint32_t icon, bool notify, VipStatus_t status) +{ + NetworkMessage msg; + msg.AddByte(0xD2); + msg.AddU32(guid); + msg.AddString(name); + msg.AddString(description); + msg.AddU32(std::min(10, icon)); + msg.AddByte(notify ? 0x01 : 0x00); + msg.AddByte(status); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendSpellCooldown(uint8_t spellId, uint32_t time) +{ + if (spellId == 0 || spellId > 160) { // TODO: define max spells somewhere + return; + } + + NetworkMessage msg; + msg.AddByte(0xA4); + msg.AddByte(spellId); + msg.AddU32(time); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendSpellGroupCooldown(SpellGroup_t groupId, uint32_t time) +{ + NetworkMessage msg; + msg.AddByte(0xA5); + msg.AddByte(groupId); + msg.AddU32(time); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendDamageMessage(MessageClasses mclass, const std::string& message, const Position& pos, + uint32_t primaryDamage/* = 0*/, TextColor_t primaryColor/* = TEXTCOLOR_NONE*/, + uint32_t secondaryDamage/* = 0*/, TextColor_t secondaryColor/* = TEXTCOLOR_NONE*/) +{ + NetworkMessage msg; + msg.AddByte(0xB4); + msg.AddByte(mclass); + msg.AddPosition(pos); + msg.AddU32(primaryDamage); + msg.AddByte(primaryColor); + msg.AddU32(secondaryDamage); + msg.AddByte(secondaryColor); + msg.AddString(message); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendHealMessage(MessageClasses mclass, const std::string& message, const Position& pos, uint32_t heal, TextColor_t color) +{ + NetworkMessage msg; + msg.AddByte(0xB4); + msg.AddByte(mclass); + msg.AddPosition(pos); + msg.AddU32(heal); + msg.AddByte(color); + msg.AddString(message); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendExperienceMessage(MessageClasses mclass, const std::string& message, const Position& pos, uint32_t exp, TextColor_t color) +{ + NetworkMessage msg; + msg.AddByte(0xB4); + msg.AddByte(mclass); + msg.AddPosition(pos); + msg.AddU32(exp); + msg.AddByte(color); + msg.AddString(message); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendModalWindow(const ModalWindow& modalWindow) +{ + NetworkMessage msg; + msg.AddByte(0xFA); + + msg.AddU32(modalWindow.getID()); + msg.AddString(modalWindow.getTitle()); + msg.AddString(modalWindow.getMessage()); + + msg.AddByte(modalWindow.getButtonCount()); + + for (ModalWindowChoiceList::const_iterator it = modalWindow.getButtons().begin(), end = modalWindow.getButtons().end(); it != end; ++it) { + msg.AddString(it->first); + msg.AddByte(it->second); + } + + msg.AddByte(modalWindow.getChoiceCount()); + + for (ModalWindowChoiceList::const_iterator it = modalWindow.getChoices().begin(), end = modalWindow.getChoices().end(); it != end; ++it) { + msg.AddString(it->first); + msg.AddByte(it->second); + } + + msg.AddByte(modalWindow.getDefaultEscapeButton()); + msg.AddByte(modalWindow.getDefaultEnterButton()); + msg.AddByte(modalWindow.hasPriority() ? 0x01 : 0x00); + + writeToOutputBuffer(msg); +} + +////////////// Add common messages +void ProtocolGame::AddTextMessage(NetworkMessage& msg, MessageClasses mclass, const std::string& message) +{ + msg.AddByte(0xB4); + msg.AddByte(mclass); + msg.AddString(message); +} + +void ProtocolGame::AddTextMessageEx(NetworkMessage& msg, MessageClasses mclass, const std::string& message, const Position& pos, uint32_t value, TextColor_t color) +{ + msg.AddByte(0xB4); + msg.AddByte(mclass); + msg.AddPosition(pos); + msg.AddU32(value); + msg.AddByte(color); + msg.AddString(message); +} + +void ProtocolGame::AddMagicEffect(NetworkMessage& msg, const Position& pos, uint8_t type) +{ + msg.AddByte(0x83); + msg.AddPosition(pos); + msg.AddByte(type + 1); +} + +void ProtocolGame::AddDistanceShoot(NetworkMessage& msg, const Position& from, const Position& to, + uint8_t type) +{ + msg.AddByte(0x85); + msg.AddPosition(from); + msg.AddPosition(to); + msg.AddByte(type + 1); +} + +void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bool known, uint32_t remove) +{ + CreatureType_t creatureType = creature->getType(); + + const Player* otherPlayer = creature->getPlayer(); + + if (known) { + msg.AddU16(0x62); + msg.AddU32(creature->getID()); + } else { + msg.AddU16(0x61); + msg.AddU32(remove); + msg.AddU32(creature->getID()); + msg.AddByte(creatureType); + msg.AddString(creature->getName()); + } + + if (creature->isHealthHidden()) { + msg.AddByte(0x00); + } else { + msg.AddByte((int32_t)std::ceil(((float)creature->getHealth()) * 100 / std::max(creature->getMaxHealth(), 1))); + } + + msg.AddByte((uint8_t)creature->getDirection()); + + if (!creature->isInvisible() && !creature->isInGhostMode()) { + AddCreatureOutfit(msg, creature, creature->getCurrentOutfit()); + } else { + AddCreatureInvisible(msg, creature); + } + + LightInfo lightInfo; + creature->getCreatureLight(lightInfo); + msg.AddByte(player->isAccessPlayer() ? 0xFF : lightInfo.level); + msg.AddByte(lightInfo.color); + + msg.AddU16(creature->getStepSpeed() / 2); + + msg.AddByte(player->getSkullClient(otherPlayer)); + + PartyShields_t shield = player->getPartyShield(otherPlayer); + + if (version < 979 && shield == SHIELD_GRAY) { + shield = SHIELD_NONE; + } + + msg.AddByte(shield); + + if (!known) { + uint8_t emblem = player->getGuildEmblem(otherPlayer); + + if (version < 979 && emblem > GUILDEMBLEM_NEUTRAL) { + emblem = GUILDEMBLEM_NONE; + } + + msg.AddByte(emblem); + } + + if (version >= 979) { + if (creatureType == CREATURETYPE_MONSTER) { + const Creature* master = creature->getMaster(); + + if (master) { + const Player* masterPlayer = master->getPlayer(); + + if (masterPlayer) { + if (masterPlayer == player) { + creatureType = CREATURETYPE_SUMMON_OWN; + } else { + creatureType = CREATURETYPE_SUMMON_OTHERS; + } + } + } + } + + msg.AddByte(creatureType); // Type (for summons) + + msg.AddByte(0xFF); // MARK_UNMARKED + + if (otherPlayer) { + msg.AddU16(otherPlayer->getHelpers()); + } else { + msg.AddU16(0x00); + } + } + + msg.AddByte(player->canWalkthroughEx(creature) ? 0x00 : 0x01); +} + +void ProtocolGame::AddPlayerStats(NetworkMessage& msg) +{ + msg.AddByte(0xA0); + + msg.AddU16(player->getHealth()); + msg.AddU16(player->getPlayerInfo(PLAYERINFO_MAXHEALTH)); + + msg.AddU32(uint32_t(player->getFreeCapacity() * 100)); + msg.AddU32(uint32_t(player->getCapacity() * 100)); + + msg.AddU64(player->getExperience()); + + msg.AddU16(player->getPlayerInfo(PLAYERINFO_LEVEL)); + msg.AddByte(player->getPlayerInfo(PLAYERINFO_LEVELPERCENT)); + + msg.AddU16(player->getMana()); + msg.AddU16(player->getPlayerInfo(PLAYERINFO_MAXMANA)); + + msg.AddByte(player->getMagicLevel()); + msg.AddByte(player->getBaseMagicLevel()); + msg.AddByte(player->getPlayerInfo(PLAYERINFO_MAGICLEVELPERCENT)); + + msg.AddByte(player->getPlayerInfo(PLAYERINFO_SOUL)); + + msg.AddU16(player->getStaminaMinutes()); + + msg.AddU16(player->getBaseSpeed() / 2); + + Condition* condition = player->getCondition(CONDITION_REGENERATION); + msg.AddU16(condition ? condition->getTicks() / 1000 : 0x00); + + msg.AddU16(player->getOfflineTrainingTime() / 60 / 1000); +} + +void ProtocolGame::AddPlayerSkills(NetworkMessage& msg) +{ + msg.AddByte(0xA1); + + msg.AddByte(std::min(0xFF, player->getSkill(SKILL_FIST, SKILL_LEVEL))); + msg.AddByte(player->getBaseSkill(SKILL_FIST)); + msg.AddByte(player->getSkill(SKILL_FIST, SKILL_PERCENT)); + + msg.AddByte(std::min(0xFF, player->getSkill(SKILL_CLUB, SKILL_LEVEL))); + msg.AddByte(player->getBaseSkill(SKILL_CLUB)); + msg.AddByte(player->getSkill(SKILL_CLUB, SKILL_PERCENT)); + + msg.AddByte(std::min(0xFF, player->getSkill(SKILL_SWORD, SKILL_LEVEL))); + msg.AddByte(player->getBaseSkill(SKILL_SWORD)); + msg.AddByte(player->getSkill(SKILL_SWORD, SKILL_PERCENT)); + + msg.AddByte(std::min(0xFF, player->getSkill(SKILL_AXE, SKILL_LEVEL))); + msg.AddByte(player->getBaseSkill(SKILL_AXE)); + msg.AddByte(player->getSkill(SKILL_AXE, SKILL_PERCENT)); + + msg.AddByte(std::min(0xFF, player->getSkill(SKILL_DIST, SKILL_LEVEL))); + msg.AddByte(player->getBaseSkill(SKILL_DIST)); + msg.AddByte(player->getSkill(SKILL_DIST, SKILL_PERCENT)); + + msg.AddByte(std::min(0xFF, player->getSkill(SKILL_SHIELD, SKILL_LEVEL))); + msg.AddByte(player->getBaseSkill(SKILL_SHIELD)); + msg.AddByte(player->getSkill(SKILL_SHIELD, SKILL_PERCENT)); + + msg.AddByte(std::min(0xFF, player->getSkill(SKILL_FISH, SKILL_LEVEL))); + msg.AddByte(player->getBaseSkill(SKILL_FISH)); + msg.AddByte(player->getSkill(SKILL_FISH, SKILL_PERCENT)); +} + +void ProtocolGame::AddCreatureSpeak(NetworkMessage& msg, const Creature* creature, SpeakClasses type, + std::string text, uint16_t channelId, Position* pos/* = NULL*/) +{ + if (!creature) { + return; + } + + msg.AddByte(0xAA); + + static uint32_t statementId = 0; + msg.AddU32(++statementId); // statement id + + if (type == SPEAK_CHANNEL_R2) { + msg.AddU16(0x00); + type = SPEAK_CHANNEL_R1; + } else { + msg.AddString(creature->getName()); + } + + //Add level only for players + if (const Player* speaker = creature->getPlayer()) { + msg.AddU16(speaker->getPlayerInfo(PLAYERINFO_LEVEL)); + } else { + msg.AddU16(0x00); + } + + msg.AddByte(type); + + switch (type) { + case SPEAK_SAY: + case SPEAK_WHISPER: + case SPEAK_YELL: + case SPEAK_MONSTER_SAY: + case SPEAK_MONSTER_YELL: + case SPEAK_PRIVATE_NP: { + if (pos) { + msg.AddPosition(*pos); + } else { + msg.AddPosition(creature->getPosition()); + } + + break; + } + + case SPEAK_CHANNEL_Y: + case SPEAK_CHANNEL_R1: + case SPEAK_CHANNEL_O: + msg.AddU16(channelId); + break; + + default: + break; + } + + msg.AddString(text); +} + +void ProtocolGame::AddCreatureHealth(NetworkMessage& msg, const Creature* creature) +{ + msg.AddByte(0x8C); + msg.AddU32(creature->getID()); + + if (creature->isHealthHidden()) { + msg.AddByte(0x00); + } else { + msg.AddByte((int32_t)std::ceil(((float)creature->getHealth()) * 100 / std::max(creature->getMaxHealth(), 1))); + } +} + +void ProtocolGame::AddCreatureInvisible(NetworkMessage& msg, const Creature* creature) +{ + if (player->canSeeInvisibility() && !creature->isInGhostMode()) { + AddCreatureOutfit(msg, creature, creature->getCurrentOutfit()); + } else { + msg.AddU16(0x00); + msg.AddU16(0x00); + msg.AddU16(0x00); + } +} + +void ProtocolGame::AddCreatureOutfit(NetworkMessage& msg, const Creature* creature, const Outfit_t& outfit) +{ + msg.AddU16(outfit.lookType); + + if (outfit.lookType != 0) { + msg.AddByte(outfit.lookHead); + msg.AddByte(outfit.lookBody); + msg.AddByte(outfit.lookLegs); + msg.AddByte(outfit.lookFeet); + msg.AddByte(outfit.lookAddons); + } else { + msg.AddItemId(outfit.lookTypeEx, version); + } + + msg.AddU16(outfit.lookMount); +} + +void ProtocolGame::AddWorldLight(NetworkMessage& msg, const LightInfo& lightInfo) +{ + msg.AddByte(0x82); + msg.AddByte((player->isAccessPlayer() ? 0xFF : lightInfo.level)); + msg.AddByte(lightInfo.color); +} + +void ProtocolGame::AddCreatureLight(NetworkMessage& msg, const Creature* creature) +{ + LightInfo lightInfo; + creature->getCreatureLight(lightInfo); + + msg.AddByte(0x8D); + msg.AddU32(creature->getID()); + msg.AddByte((player->isAccessPlayer() ? 0xFF : lightInfo.level)); + msg.AddByte(lightInfo.color); +} + +//tile +void ProtocolGame::AddTileItem(NetworkMessage& msg, const Position& pos, uint32_t stackpos, const Item* item) +{ + if (stackpos >= 10) { + return; + } + + msg.AddByte(0x6A); + msg.AddPosition(pos); + msg.AddByte(stackpos); + msg.AddItem(item, version); +} + +void ProtocolGame::AddTileCreature(NetworkMessage& msg, const Position& pos, uint32_t stackpos, + const Creature* creature) +{ + if (stackpos >= 10) { + return; + } + + msg.AddByte(0x6A); + msg.AddPosition(pos); + msg.AddByte(stackpos); + + bool known; + uint32_t removedKnown; + checkCreatureAsKnown(creature->getID(), known, removedKnown); + AddCreature(msg, creature, known, removedKnown); +} + +void ProtocolGame::UpdateTileItem(NetworkMessage& msg, const Position& pos, uint32_t stackpos, const Item* item) +{ + if (stackpos >= 10) { + return; + } + + msg.AddByte(0x6B); + msg.AddPosition(pos); + msg.AddByte(stackpos); + msg.AddItem(item, version); +} + +void ProtocolGame::RemoveTileItem(NetworkMessage& msg, const Position& pos, uint32_t stackpos) +{ + if (stackpos >= 10) { + return; + } + + msg.AddByte(0x6C); + msg.AddPosition(pos); + msg.AddByte(stackpos); +} + +void ProtocolGame::MoveUpCreature(NetworkMessage& msg, const Creature* creature, + const Position& newPos, const Position& oldPos, uint32_t oldStackPos) +{ + if (creature != player) { + return; + } + + //floor change up + msg.AddByte(0xBE); + + //going to surface + if (newPos.z == 7) { + int32_t skip = -1; + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 5, 18, 14, 3, skip); //(floor 7 and 6 already set) + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 4, 18, 14, 4, skip); + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 3, 18, 14, 5, skip); + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 2, 18, 14, 6, skip); + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 1, 18, 14, 7, skip); + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 0, 18, 14, 8, skip); + + if (skip >= 0) { + msg.AddByte(skip); + msg.AddByte(0xFF); + } + } + //underground, going one floor up (still underground) + else if (newPos.z > 7) { + int32_t skip = -1; + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, oldPos.z - 3, 18, 14, 3, skip); + + if (skip >= 0) { + msg.AddByte(skip); + msg.AddByte(0xFF); + } + } + + //moving up a floor up makes us out of sync + //west + msg.AddByte(0x68); + GetMapDescription(oldPos.x - 8, oldPos.y + 1 - 6, newPos.z, 1, 14, msg); + + //north + msg.AddByte(0x65); + GetMapDescription(oldPos.x - 8, oldPos.y - 6, newPos.z, 18, 1, msg); +} + +void ProtocolGame::MoveDownCreature(NetworkMessage& msg, const Creature* creature, + const Position& newPos, const Position& oldPos, uint32_t oldStackPos) +{ + if (creature != player) { + return; + } + + //floor change down + msg.AddByte(0xBF); + + //going from surface to underground + if (newPos.z == 8) { + int32_t skip = -1; + + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, newPos.z, 18, 14, -1, skip); + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, newPos.z + 1, 18, 14, -2, skip); + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, newPos.z + 2, 18, 14, -3, skip); + + if (skip >= 0) { + msg.AddByte(skip); + msg.AddByte(0xFF); + } + } + //going further down + else if (newPos.z > oldPos.z && newPos.z > 8 && newPos.z < 14) { + int32_t skip = -1; + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, newPos.z + 2, 18, 14, -3, skip); + + if (skip >= 0) { + msg.AddByte(skip); + msg.AddByte(0xFF); + } + } + + //moving down a floor makes us out of sync + //east + msg.AddByte(0x66); + GetMapDescription(oldPos.x + 9, oldPos.y - 1 - 6, newPos.z, 1, 14, msg); + + //south + msg.AddByte(0x67); + GetMapDescription(oldPos.x - 8, oldPos.y + 7, newPos.z, 18, 1, msg); +} + +void ProtocolGame::AddShopItem(NetworkMessage& msg, const ShopInfo& item) +{ + const ItemType& it = Item::items[item.itemId]; + msg.AddU16(it.clientId); + + if (it.stackable || it.isRune()) { + msg.AddByte(item.subType); + } else if (it.isSplash() || it.isFluidContainer()) { + msg.AddByte(serverFluidToClient(item.subType)); + } else { + msg.AddByte(0x00); + } + + msg.AddString(item.realName); + msg.AddU32(uint32_t(it.weight * 100)); + msg.AddU32(item.buyPrice); + msg.AddU32(item.sellPrice); +} diff --git a/src/protocolgame.h b/src/protocolgame.h new file mode 100644 index 0000000000..f4b41faa31 --- /dev/null +++ b/src/protocolgame.h @@ -0,0 +1,370 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_PROTOCOLGAME_H__ +#define __OTSERV_PROTOCOLGAME_H__ + +#include +#include "protocol.h" +#include "enums.h" +#include "creature.h" +#include "modalwindow.h" + +enum connectResult_t { + CONNECT_SUCCESS = 1, + CONNECT_TOMANYPLAYERS = 2, + CONNECT_MASTERPOSERROR = 3, + CONNECT_INTERNALERROR = 4 +}; + +class NetworkMessage; +class Player; +class Game; +class House; +class Container; +class Tile; +class Connection; +class Quest; + +class ProtocolGame : public Protocol +{ + public: + // static protocol information + enum {server_sends_first = true}; + enum {protocol_identifier = 0}; // Not required as we send first + enum {use_checksum = true}; + static const char* protocol_name() { + return "gameworld protocol"; + } + +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + static uint32_t protocolGameCount; +#endif + ProtocolGame(Connection_ptr connection); + virtual ~ProtocolGame(); + + virtual int32_t getProtocolId() { + return 0x0A; + } + + bool login(const std::string& name, uint32_t accnumber, OperatingSystem_t operatingSystem, bool gamemasterLogin); + bool logout(bool displayEffect, bool forced); + + void setPlayer(Player* p); + + uint16_t getVersion() const { + return version; + } + + const OTSERV_HASH_SET& getKnownCreatures() const { + return knownCreatureSet; + } + + private: + OTSERV_HASH_SET knownCreatureSet; + + bool connect(uint32_t playerId, OperatingSystem_t operatingSystem); + void disconnect(); + void disconnectClient(uint8_t error, const char* message); + void writeToOutputBuffer(const NetworkMessage& msg); + + virtual void releaseProtocol(); + virtual void deleteProtocolTask(); + + void checkCreatureAsKnown(uint32_t id, bool& known, uint32_t& removedKnown); + + bool canSee(int32_t x, int32_t y, int32_t z) const; + bool canSee(const Creature*) const; + bool canSee(const Position& pos) const; + + // we have all the parse methods + virtual void parsePacket(NetworkMessage& msg); + virtual void onRecvFirstMessage(NetworkMessage& msg); + virtual void onConnect(); + bool parseFirstPacket(NetworkMessage& msg); + + //Parse methods + void parseLogout(NetworkMessage& msg); + void parseCancelMove(NetworkMessage& msg); + + void parseReceivePing(NetworkMessage& msg); + void parseReceivePingBack(NetworkMessage& msg); + void parseAutoWalk(NetworkMessage& msg); + void parseMove(NetworkMessage& msg, Direction dir); + void parseTurn(NetworkMessage& msg, Direction dir); + + void parseRequestOutfit(NetworkMessage& msg); + void parseSetOutfit(NetworkMessage& msg); + void parseSay(NetworkMessage& msg); + void parseLookAt(NetworkMessage& msg); + void parseLookInBattleList(NetworkMessage& msg); + void parseJoinAggression(NetworkMessage& msg); + void parseFightModes(NetworkMessage& msg); + void parseAttack(NetworkMessage& msg); + void parseFollow(NetworkMessage& msg); + + void parseBugReport(NetworkMessage& msg); + void parseThankYou(NetworkMessage& msg); + void parseDebugAssert(NetworkMessage& msg); + + void parseThrow(NetworkMessage& msg); + void parseUseItemEx(NetworkMessage& msg); + void parseUseWithCreature(NetworkMessage& msg); + void parseUseItem(NetworkMessage& msg); + void parseCloseContainer(NetworkMessage& msg); + void parseUpArrowContainer(NetworkMessage& msg); + void parseUpdateTile(NetworkMessage& msg); + void parseUpdateContainer(NetworkMessage& msg); + void parseTextWindow(NetworkMessage& msg); + void parseHouseWindow(NetworkMessage& msg); + + void parseLookInShop(NetworkMessage& msg); + void parsePlayerPurchase(NetworkMessage& msg); + void parsePlayerSale(NetworkMessage& msg); + void parseCloseShop(NetworkMessage& msg); + + void parseQuestLog(NetworkMessage& msg); + void parseQuestLine(NetworkMessage& msg); + + void parseInviteToParty(NetworkMessage& msg); + void parseJoinParty(NetworkMessage& msg); + void parseRevokePartyInvite(NetworkMessage& msg); + void parsePassPartyLeadership(NetworkMessage& msg); + void parseLeaveParty(NetworkMessage& msg); + void parseEnableSharedPartyExperience(NetworkMessage& msg); + + void parseToggleMount(NetworkMessage& msg); + + void parseRuleViolationReport(NetworkMessage& msg); + + void parseModalWindowAnswer(NetworkMessage& msg); + + void parseBrowseField(NetworkMessage& msg); + void parseSeekInContainer(NetworkMessage& msg); + + //trade methods + void parseRequestTrade(NetworkMessage& msg); + void parseLookInTrade(NetworkMessage& msg); + void parseAcceptTrade(NetworkMessage& msg); + void parseCloseTrade(); + + // + void parseGetObjectInfo(NetworkMessage& msg); + + //market methods + void parseMarketLeave(); + void parseMarketBrowse(NetworkMessage& msg); + void parseMarketCreateOffer(NetworkMessage& msg); + void parseMarketCancelOffer(NetworkMessage& msg); + void parseMarketAcceptOffer(NetworkMessage& msg); + + //VIP methods + void parseAddVip(NetworkMessage& msg); + void parseRemoveVip(NetworkMessage& msg); + void parseEditVip(NetworkMessage& msg); + + void parseRotateItem(NetworkMessage& msg); + + //Channel tabs + void parseCreatePrivateChannel(NetworkMessage& msg); + void parseChannelInvite(NetworkMessage& msg); + void parseChannelExclude(NetworkMessage& msg); + void parseGetChannels(NetworkMessage& msg); + void parseOpenChannel(NetworkMessage& msg); + void parseOpenPrivateChannel(NetworkMessage& msg); + void parseCloseChannel(NetworkMessage& msg); + void parseCloseNpc(NetworkMessage& msg); + + //Send functions + void sendChannelMessage(const std::string& author, const std::string& text, SpeakClasses type, uint16_t channel); + void sendChannelEvent(uint16_t channelId, const std::string& playerName, ChannelEvent_t channelEvent); + void sendClosePrivate(uint16_t channelId); + void sendCreatePrivateChannel(uint16_t channelId, const std::string& channelName); + void sendChannelsDialog(); + void sendChannel(uint16_t channelId, const std::string& channelName); + void sendOpenPrivateChannel(const std::string& receiver); + void sendToChannel(const Creature* creature, SpeakClasses type, const std::string& text, uint16_t channelId); + void sendIcons(int32_t icons); + void sendFYIBox(const std::string& message); + + void sendDistanceShoot(const Position& from, const Position& to, uint8_t type); + void sendMagicEffect(const Position& pos, uint8_t type); + void sendCreatureHealth(const Creature* creature); + void sendSkills(); + void sendPing(); + void sendPingBack(); + void sendCreatureTurn(const Creature* creature, uint32_t stackpos); + void sendCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, Position* pos = NULL); + + void sendQuestLog(); + void sendQuestLine(const Quest* quest); + + void sendCancel(const std::string& message); + void sendCancelWalk(); + void sendChangeSpeed(const Creature* creature, uint32_t speed); + void sendCancelTarget(); + void sendCreatureVisible(const Creature* creature, bool visible); + void sendCreatureOutfit(const Creature* creature, const Outfit_t& outfit); + void sendCreatureInvisible(const Creature* creature); + void sendStats(); + void sendBasicData(); + void sendTextMessage(MessageClasses mclass, const std::string& message, Position* pos = NULL, uint32_t exp = 0, TextColor_t color = TEXTCOLOR_NONE); + void sendReLoginWindow(uint8_t unfairFightReduction); + + void sendTutorial(uint8_t tutorialId); + void sendAddMarker(const Position& pos, uint8_t markType, const std::string& desc); + + void sendCreatureWalkthrough(const Creature* creature, bool walkthrough); + void sendCreatureShield(const Creature* creature); + void sendCreatureSkull(const Creature* creature); + void sendCreatureType(uint32_t creatureId, uint8_t creatureType); + void sendCreatureHelpers(uint32_t creatureId, uint16_t helpers); + + void sendShop(Npc* npc, const ShopInfoList& itemList); + void sendCloseShop(); + void sendSaleItemList(const std::list& shop); + void sendMarketEnter(uint32_t depotId); + void sendMarketLeave(); + void sendMarketBrowseItem(uint16_t itemId, const MarketOfferList& buyOffers, const MarketOfferList& sellOffers); + void sendMarketAcceptOffer(MarketOfferEx offer); + void sendMarketBrowseOwnOffers(const MarketOfferList& buyOffers, const MarketOfferList& sellOffers); + void sendMarketCancelOffer(MarketOfferEx offer); + void sendMarketBrowseOwnHistory(const HistoryMarketOfferList& buyOffers, const HistoryMarketOfferList& sellOffers); + void sendMarketDetail(uint16_t itemId); + void sendTradeItemRequest(const Player* player, const Item* item, bool ack); + void sendCloseTrade(); + + void sendTextWindow(uint32_t windowTextId, Item* item, uint16_t maxlen, bool canWrite); + void sendTextWindow(uint32_t windowTextId, uint32_t itemId, const std::string& text); + void sendHouseWindow(uint32_t windowTextId, House* house, uint32_t listId, const std::string& text); + void sendOutfitWindow(); + + void sendUpdatedVIPStatus(uint32_t guid, VipStatus_t newStatus); + void sendVIP(uint32_t guid, const std::string& name, const std::string& description, uint32_t icon, bool notify, VipStatus_t status); + + void sendPendingStateEntered(); + void sendEnterWorld(); + + void sendCreatureLight(const Creature* creature); + void sendWorldLight(const LightInfo& lightInfo); + + void sendCreatureSquare(const Creature* creature, SquareColor_t color); + + void sendSpellCooldown(uint8_t spellId, uint32_t time); + void sendSpellGroupCooldown(SpellGroup_t groupId, uint32_t time); + + //tiles + void sendMapDescription(const Position& pos); + + void sendAddTileItem(const Tile* tile, const Position& pos, uint32_t stackpos, const Item* item); + void sendUpdateTileItem(const Tile* tile, const Position& pos, uint32_t stackpos, const Item* item); + void sendRemoveTileItem(const Tile* tile, const Position& pos, uint32_t stackpos); + void sendUpdateTile(const Tile* tile, const Position& pos); + + void sendAddCreature(const Creature* creature, const Position& pos, uint32_t stackpos, bool isLogin); + void sendRemoveCreature(const Creature* creature, const Position& pos, uint32_t stackpos, bool isLogout); + void sendMoveCreature(const Creature* creature, const Tile* newTile, const Position& newPos, uint32_t newStackPos, + const Tile* oldTile, const Position& oldPos, uint32_t oldStackPos, bool teleport); + + //containers + void sendAddContainerItem(uint8_t cid, uint16_t slot, const Item* item); + void sendUpdateContainerItem(uint8_t cid, uint16_t slot, const Item* item); + void sendRemoveContainerItem(uint8_t cid, uint16_t slot, const Item* lastItem); + + void sendContainer(uint8_t cid, const Container* container, bool hasParent, uint16_t firstIndex); + void sendCloseContainer(uint8_t cid); + + //inventory + void sendInventoryItem(slots_t slot, const Item* item); + + //messages + void sendDamageMessage(MessageClasses mclass, const std::string& message, const Position& pos, + uint32_t primaryDamage = 0, TextColor_t primaryColor = TEXTCOLOR_NONE, + uint32_t secondaryDamage = 0, TextColor_t secondaryColor = TEXTCOLOR_NONE); + void sendHealMessage(MessageClasses mclass, const std::string& message, const Position& pos, uint32_t heal, TextColor_t color); + void sendExperienceMessage(MessageClasses mclass, const std::string& message, const Position& pos, uint32_t exp, TextColor_t color); + void sendModalWindow(const ModalWindow& modalWindow); + + //Help functions + + // translate a tile to clientreadable format + void GetTileDescription(const Tile* tile, NetworkMessage& msg); + + // translate a floor to clientreadable format + void GetFloorDescription(NetworkMessage& msg, int32_t x, int32_t y, int32_t z, + int32_t width, int32_t height, int32_t offset, int32_t& skip); + + // translate a map area to clientreadable format + void GetMapDescription(int32_t x, int32_t y, int32_t z, + int32_t width, int32_t height, NetworkMessage& msg); + + void AddTextMessage(NetworkMessage& msg, MessageClasses mclass, const std::string& message); + void AddTextMessageEx(NetworkMessage& msg, MessageClasses mclass, const std::string& message, const Position& pos, uint32_t value, TextColor_t color); + void AddMagicEffect(NetworkMessage& msg, const Position& pos, uint8_t type); + void AddDistanceShoot(NetworkMessage& msg, const Position& from, const Position& to, uint8_t type); + void AddCreature(NetworkMessage& msg, const Creature* creature, bool known, uint32_t remove); + void AddPlayerStats(NetworkMessage& msg); + void AddCreatureSpeak(NetworkMessage& msg, const Creature* creature, SpeakClasses type, + std::string text, uint16_t channelId, Position* pos = NULL); + void AddCreatureHealth(NetworkMessage& msg, const Creature* creature); + void AddCreatureOutfit(NetworkMessage& msg, const Creature* creature, const Outfit_t& outfit); + void AddCreatureInvisible(NetworkMessage& msg, const Creature* creature); + void AddPlayerSkills(NetworkMessage& msg); + void AddWorldLight(NetworkMessage& msg, const LightInfo& lightInfo); + void AddCreatureLight(NetworkMessage& msg, const Creature* creature); + + //tiles + void AddTileItem(NetworkMessage& msg, const Position& pos, uint32_t stackpos, const Item* item); + void AddTileCreature(NetworkMessage& msg, const Position& pos, uint32_t stackpos, const Creature* creature); + void UpdateTileItem(NetworkMessage& msg, const Position& pos, uint32_t stackpos, const Item* item); + void RemoveTileItem(NetworkMessage& msg, const Position& pos, uint32_t stackpos); + + void MoveUpCreature(NetworkMessage& msg, const Creature* creature, + const Position& newPos, const Position& oldPos, uint32_t oldStackPos); + void MoveDownCreature(NetworkMessage& msg, const Creature* creature, + const Position& newPos, const Position& oldPos, uint32_t oldStackPos); + + //container + void AddContainerItem(NetworkMessage& msg, uint8_t cid, const Item* item); + void UpdateContainerItem(NetworkMessage& msg, uint8_t cid, uint16_t slot, const Item* item); + void RemoveContainerItem(NetworkMessage& msg, uint8_t cid, uint16_t slot); + + //inventory + void SetInventoryItem(NetworkMessage& msg, slots_t slot, const Item* item); + + //shop + void AddShopItem(NetworkMessage& msg, const ShopInfo& item); + + friend class Player; + + // Helper so we don't need to bind every time +#define addGameTask(f, ...) addGameTaskInternal(false, 0, boost::bind(f, &g_game, __VA_ARGS__)) +#define addGameTaskTimed(delay, f, ...) addGameTaskInternal(true, delay, boost::bind(f, &g_game, __VA_ARGS__)) + + template + void addGameTaskInternal(bool droppable, uint32_t delay, const FunctionType&); + + Player* player; + + uint32_t eventConnect; + uint16_t version; + bool m_debugAssertSent; + bool m_acceptPackets; +}; + +#endif diff --git a/src/protocollogin.cpp b/src/protocollogin.cpp new file mode 100644 index 0000000000..3e44091de9 --- /dev/null +++ b/src/protocollogin.cpp @@ -0,0 +1,202 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "protocollogin.h" + +#include "outputmessage.h" +#include "connection.h" +#include "rsa.h" + +#include "configmanager.h" +#include "tools.h" +#include "iologindata.h" +#include "ban.h" +#include +#include "game.h" + +extern ConfigManager g_config; +extern IPList serverIPs; +extern Ban g_bans; +extern Game g_game; + +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ +uint32_t ProtocolLogin::protocolLoginCount = 0; +#endif + +void ProtocolLogin::disconnectClient(uint8_t error, const char* message) +{ + OutputMessage_ptr output = OutputMessagePool::getInstance()->getOutputMessage(this, false); + + if (output) { + output->AddByte(error); + output->AddString(message); + OutputMessagePool::getInstance()->send(output); + } + + getConnection()->closeConnection(); +} + +bool ProtocolLogin::parseFirstPacket(NetworkMessage& msg) +{ + if (g_game.getGameState() == GAME_STATE_SHUTDOWN) { + getConnection()->closeConnection(); + return false; + } + + uint32_t clientip = getConnection()->getIP(); + + /*uint16_t clientos = */ + msg.GetU16(); + uint16_t version = msg.GetU16(); + + if (version >= 971) { + msg.SkipBytes(17); + } else { + msg.SkipBytes(12); + } + + /* + * Skipped bytes: + * 4 bytes: protocolVersion (only 971+) + * 12 bytes: dat, spr, pic signatures (4 bytes each) + * 1 byte: 0 (only 971+) + */ + + if (version <= 760) { + disconnectClient(0x0A, "Only clients with protocol " CLIENT_VERSION_STR " allowed!"); + return false; + } + + if (!RSA_decrypt(msg)) { + getConnection()->closeConnection(); + return false; + } + + uint32_t key[4]; + key[0] = msg.GetU32(); + key[1] = msg.GetU32(); + key[2] = msg.GetU32(); + key[3] = msg.GetU32(); + enableXTEAEncryption(); + setXTEAKey(key); + + std::string accountName = msg.GetString(); + std::string password = msg.GetString(); + + if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) { + disconnectClient(0x0A, "Only clients with protocol " CLIENT_VERSION_STR " allowed!\nIt can be downloaded from our website."); + return false; + } + + if (g_game.getGameState() == GAME_STATE_STARTUP) { + disconnectClient(0x0A, "Gameworld is starting up. Please wait."); + return false; + } + + if (g_game.getGameState() == GAME_STATE_MAINTAIN) { + disconnectClient(0x0A, "Gameworld is under maintenance. Please re-connect in a while."); + return false; + } + + if (g_bans.isIpDisabled(clientip)) { + disconnectClient(0x0A, "Too many connections attempts from this IP. Try again later."); + return false; + } + + if (IOBan::getInstance()->isIpBanished(clientip)) { + disconnectClient(0x0A, "Your IP is banished!"); + return false; + } + + uint32_t serverip = serverIPs[0].first; + for (uint32_t i = 0; i < serverIPs.size(); i++) { + if ((serverIPs[i].first & serverIPs[i].second) == (clientip & serverIPs[i].second)) { + serverip = serverIPs[i].first; + break; + } + } + + if (accountName.empty()) { + g_bans.addLoginAttempt(clientip, false); + disconnectClient(0x0A, "Invalid account name."); + return false; + } + + Account account = IOLoginData::getInstance()->loadAccount(accountName); + if (account.id == 0 || !passwordTest(password, account.password)) { + g_bans.addLoginAttempt(clientip, false); + disconnectClient(0x0A, "Account name or password is not correct."); + return false; + } + + g_bans.addLoginAttempt(clientip, true); + + OutputMessage_ptr output = OutputMessagePool::getInstance()->getOutputMessage(this, false); + if (output) { + //Update premium days + g_game.updatePremium(account); + + //Add MOTD + output->AddByte(0x14); + + std::ostringstream ss; + ss << g_game.getMotdNum() << "\n" << g_config.getString(ConfigManager::MOTD); + output->AddString(ss.str()); + + //Add char list + output->AddByte(0x64); + output->AddByte((uint8_t)account.charList.size()); + + for (std::list::iterator it = account.charList.begin(), end = account.charList.end(); it != end; ++it) { + output->AddString(*it); + + if (g_config.getBoolean(ConfigManager::ON_OR_OFF_CHARLIST)) { + if (g_game.getPlayerByName(*it)) { + output->AddString("Online"); + } else { + output->AddString("Offline"); + } + } else { + output->AddString(g_config.getString(ConfigManager::SERVER_NAME)); + } + + output->AddU32(serverip); + output->AddU16(g_config.getNumber(ConfigManager::GAME_PORT)); + output->AddByte(0x00); + } + + //Add premium days + if (g_config.getBoolean(ConfigManager::FREE_PREMIUM)) { + output->AddU16(0xFFFF); //client displays free premium + } else { + output->AddU16(account.premiumDays); + } + + OutputMessagePool::getInstance()->send(output); + } + + getConnection()->closeConnection(); + return true; +} + +void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) +{ + parseFirstPacket(msg); +} diff --git a/src/protocollogin.h b/src/protocollogin.h new file mode 100644 index 0000000000..4d540d8dc8 --- /dev/null +++ b/src/protocollogin.h @@ -0,0 +1,64 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_PROTOCOL_LOGIN_H__ +#define __OTSERV_PROTOCOL_LOGIN_H__ + +#include "protocol.h" + +class NetworkMessage; +class OutputMessage; + +class ProtocolLogin : public Protocol +{ + public: + // static protocol information + enum {server_sends_first = false}; + enum {protocol_identifier = 0x01}; + enum {use_checksum = true}; + static const char* protocol_name() { + return "login protocol"; + } + +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + static uint32_t protocolLoginCount; +#endif + ProtocolLogin(Connection_ptr connection) : Protocol(connection) { +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + protocolLoginCount++; +#endif + } + virtual ~ProtocolLogin() { +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + protocolLoginCount--; +#endif + } + + virtual int32_t getProtocolId() { + return 0x01; + } + + virtual void onRecvFirstMessage(NetworkMessage& msg); + + protected: + void disconnectClient(uint8_t error, const char* message); + + bool parseFirstPacket(NetworkMessage& msg); +}; + +#endif diff --git a/src/protocolold.cpp b/src/protocolold.cpp new file mode 100644 index 0000000000..8de6064214 --- /dev/null +++ b/src/protocolold.cpp @@ -0,0 +1,86 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "protocolold.h" +#include "outputmessage.h" +#include "connection.h" + +#include "rsa.h" +#include "game.h" + +extern Game g_game; + +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ +uint32_t ProtocolOld::protocolOldCount = 0; +#endif + +void ProtocolOld::disconnectClient(uint8_t error, const char* message) +{ + OutputMessage_ptr output = OutputMessagePool::getInstance()->getOutputMessage(this, false); + + if (output) { + output->AddByte(error); + output->AddString(message); + OutputMessagePool::getInstance()->send(output); + } + + getConnection()->closeConnection(); +} + +bool ProtocolOld::parseFirstPacket(NetworkMessage& msg) +{ + if (g_game.getGameState() == GAME_STATE_SHUTDOWN) { + getConnection()->closeConnection(); + return false; + } + + /*uint16_t clientOS =*/ msg.GetU16(); + uint16_t version = msg.GetU16(); + msg.SkipBytes(12); + + if (version <= 760) { + disconnectClient(0x0A, "Only clients with protocol " CLIENT_VERSION_STR " allowed!"); + } + + if (!RSA_decrypt(msg)) { + getConnection()->closeConnection(); + return false; + } + + uint32_t key[4]; + key[0] = msg.GetU32(); + key[1] = msg.GetU32(); + key[2] = msg.GetU32(); + key[3] = msg.GetU32(); + enableXTEAEncryption(); + setXTEAKey(key); + + if (version <= 822) { + disableChecksum(); + } + + disconnectClient(0x0A, "Only clients with protocol " CLIENT_VERSION_STR " allowed!"); + return false; +} + +void ProtocolOld::onRecvFirstMessage(NetworkMessage& msg) +{ + parseFirstPacket(msg); +} diff --git a/src/protocolold.h b/src/protocolold.h new file mode 100644 index 0000000000..69db503f89 --- /dev/null +++ b/src/protocolold.h @@ -0,0 +1,80 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_PROTOCOL_OLD_H__ +#define __OTSERV_PROTOCOL_OLD_H__ + +#include "protocol.h" + +class NetworkMessage; +class OutputMessage; + +class ProtocolOld : public Protocol +{ + public: + // static protocol information + enum {server_sends_first = false}; + enum {use_checksum = false}; + +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + static uint32_t protocolOldCount; +#endif + + ProtocolOld(Connection_ptr connection) : Protocol(connection) { +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + protocolOldCount++; +#endif + } + + virtual ~ProtocolOld() { +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + protocolOldCount--; +#endif + } + + virtual void onRecvFirstMessage(NetworkMessage& msg); + + protected: + void disconnectClient(uint8_t error, const char* message); + + bool parseFirstPacket(NetworkMessage& msg); +}; + +class ProtocolOldLogin : public ProtocolOld +{ + public: + enum {protocol_identifier = 0x01}; + static const char* protocol_name() { + return "old login protocol"; + } + + ProtocolOldLogin(Connection_ptr connection) : ProtocolOld(connection) {} +}; + +class ProtocolOldGame : public ProtocolOld +{ + public: + enum {protocol_identifier = 0x0A}; + static const char* protocol_name() { + return "old gameworld protocol"; + } + + ProtocolOldGame(Connection_ptr connection) : ProtocolOld(connection) {} +}; + +#endif diff --git a/src/quests.cpp b/src/quests.cpp new file mode 100644 index 0000000000..a0a22104a0 --- /dev/null +++ b/src/quests.cpp @@ -0,0 +1,388 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include + +#include "quests.h" +#include "tools.h" + +MissionState::MissionState(const std::string& _description, int32_t _missionID) +{ + description = _description; + missionID = _missionID; +} + +Mission::Mission(const std::string& _missionName, int32_t _storageID, int32_t _startValue, int32_t _endValue, bool _ignoreEndValue) +{ + missionName = _missionName; + endValue = _endValue; + ignoreEndValue = _ignoreEndValue; + startValue = _startValue; + storageID = _storageID; + mainState = NULL; +} + +Mission::~Mission() +{ + for (uint32_t it = 0; it != state.size(); it++) { + delete state[it]; + } + + state.clear(); +} + +std::string Mission::getDescription(Player* player) +{ + int32_t value; + player->getStorageValue(storageID, value); + + if (mainState) { + std::string desc = mainState->getMissionDescription(); + replaceString(desc, "|STATE|", std::to_string(value)); + replaceString(desc, "\\n", "\n"); + return desc; + } + + int32_t current = endValue; + + if (ignoreEndValue) { + while (current >= startValue) { + if (value >= current) { + StateList::const_iterator sit = state.find(current); + + if (sit != state.end()) { + return sit->second->getMissionDescription(); + } + } + + current--; + } + } else { + while (current >= startValue) { + if (value == current) { + StateList::const_iterator sit = state.find(current); + + if (sit != state.end()) { + return sit->second->getMissionDescription(); + } + } + + current--; + } + } + + return "An error has occurred, please contact a gamemaster."; +} + +bool Mission::isStarted(Player* player) const +{ + if (!player) { + return false; + } + + int32_t value; + + if (!player->getStorageValue(storageID, value)) { + return false; + } + + if (value < startValue) { + return false; + } + + if (!ignoreEndValue && value > endValue) { + return false; + } + + return true; +} + +bool Mission::isCompleted(Player* player) const +{ + if (!player) { + return false; + } + + int32_t value; + + if (!player->getStorageValue(storageID, value)) { + return false; + } + + if (ignoreEndValue) { + return value >= endValue; + } + + return value == endValue; +} + +std::string Mission::getName(Player* player) +{ + if (isCompleted(player)) { + return missionName + " (completed)"; + } + + return missionName; +} + +Quest::Quest(const std::string& _name, uint16_t _id, int32_t _startStorageID, int32_t _startStorageValue) +{ + name = _name; + id = _id; + startStorageID = _startStorageID; + startStorageValue = _startStorageValue; +} + +Quest::~Quest() +{ + for (MissionsList::iterator it = missions.begin(), end = missions.end(); it != end; ++it) { + delete (*it); + } + + missions.clear(); +} + +uint16_t Quest::getMissionsCount(Player* player) const +{ + uint16_t count = 0; + + for (MissionsList::const_iterator it = missions.begin(), end = missions.end(); it != end; ++it) { + if ((*it)->isStarted(player)) { + count++; + } + } + + return count; +} + +bool Quest::isCompleted(Player* player) +{ + for (MissionsList::const_iterator it = missions.begin(), end = missions.end(); it != end; ++it) { + if (!(*it)->isCompleted(player)) { + return false; + } + } + + return true; +} + +bool Quest::isStarted(Player* player) const +{ + if (!player) { + return false; + } + + int32_t value; + + if (!player->getStorageValue(startStorageID, value) || value < startStorageValue) { + return false; + } + + return true; +} + +Quests::Quests() +{ + // +} + +Quests::~Quests() +{ + for (QuestsList::iterator it = quests.begin(), end = quests.end(); it != end; ++it) { + delete (*it); + } + + quests.clear(); +} + +bool Quests::reload() +{ + for (QuestsList::iterator it = quests.begin(), end = quests.end(); it != end; ++it) { + delete (*it); + } + + quests.clear(); + return loadFromXml(); +} + +bool Quests::loadFromXml() +{ + xmlDocPtr doc = xmlParseFile("data/XML/quests.xml"); + + if (doc) { + xmlNodePtr root, p; + root = xmlDocGetRootElement(doc); + + if (xmlStrcmp(root->name, (const xmlChar*)"quests") == 0) { + int32_t intValue; + std::string strValue; + uint16_t id = 0; + p = root->children; + + while (p) { + if (xmlStrcmp(p->name, (const xmlChar*)"quest") == 0) { + std::string name; + int32_t startStorageID = 0, startStorageValue = 0; + + if (readXMLString(p, "name", strValue)) { + name = strValue; + } + + if (readXMLInteger(p, "startstorageid", intValue)) { + startStorageID = intValue; + } + + if (readXMLInteger(p, "startstoragevalue", intValue)) { + startStorageValue = intValue; + } + + Quest* quest = new Quest(name, id, startStorageID, startStorageValue); + xmlNodePtr tmpNode = p->children; + + while (tmpNode) { + if (xmlStrcmp(tmpNode->name, (const xmlChar*)"mission") == 0) { + std::string missionName, missionState; + int32_t storageID = 0, startValue = 0, endValue = 0; + bool ignoreEndValue; + + if (readXMLString(tmpNode, "name", strValue)) { + missionName = strValue; + } + + if (readXMLInteger(tmpNode, "storageid", intValue)) { + storageID = intValue; + } + + if (readXMLInteger(tmpNode, "startvalue", intValue)) { + startValue = intValue; + } + + if (readXMLInteger(tmpNode, "endvalue", intValue)) { + endValue = intValue; + } + + if (readXMLInteger(tmpNode, "ignoreendvalue", intValue)) { + ignoreEndValue = (intValue != 0); + } else { + ignoreEndValue = false; + } + + if (readXMLString(tmpNode, "description", strValue)) { + missionState = strValue; + } + + Mission* mission = new Mission(missionName, storageID, startValue, endValue, ignoreEndValue); + + if (missionState.empty()) { + xmlNodePtr tmpNode2 = tmpNode->children; + + while (tmpNode2) { + if (xmlStrcmp(tmpNode2->name, (const xmlChar*)"missionstate") == 0) { + std::string description; + int32_t missionID = 0; + + if (readXMLInteger(tmpNode2, "id", intValue)) { + missionID = intValue; + } + + if (readXMLString(tmpNode2, "description", strValue)) { + description = strValue; + } + + mission->state[missionID] = new MissionState(description, missionID); + } + + tmpNode2 = tmpNode2->next; + } + } else { + mission->mainState = new MissionState(missionState, 0); + } + + quest->addMission(mission); + } + + tmpNode = tmpNode->next; + } + + quests.push_back(quest); + } + + id++; + p = p->next; + } + } + + xmlFreeDoc(doc); + return true; + } + + return false; +} + +Quest* Quests::getQuestByID(uint16_t id) +{ + for (QuestsList::iterator it = quests.begin(), end = quests.end(); it != end; ++it) { + if ((*it)->getID() == id) { + return (*it); + } + } + + return NULL; +} + +uint16_t Quests::getQuestsCount(Player* player) +{ + uint16_t count = 0; + + for (QuestsList::const_iterator it = quests.begin(), end = quests.end(); it != end; ++it) { + if ((*it)->isStarted(player)) { + count++; + } + } + + return count; +} + +bool Quests::isQuestStorage(const uint32_t key, const int32_t value, const int32_t oldValue) +{ + for (QuestsList::const_iterator it = quests.begin(), end = quests.end(); it != end; ++it) { + Quest* quest = *it; + + if (quest->getStartStorageId() == key && quest->getStartStorageValue() == value) { + return true; + } + + for (MissionsList::const_iterator m_it = quest->getFirstMission(), m_end = quest->getLastMission(); m_it != m_end; ++m_it) { + Mission* mission = *m_it; + + if (mission->mainState) { + if (mission->getStorageId() == key && value >= mission->getStartStorageValue() && value <= mission->getEndStorageValue() && (oldValue < mission->getStartStorageValue() || oldValue > mission->getEndStorageValue())) { + return true; + } + } else { + if (mission->getStorageId() == key && value >= mission->getStartStorageValue() && value <= mission->getEndStorageValue()) { + return true; + } + } + } + } + + return false; +} diff --git a/src/quests.h b/src/quests.h new file mode 100644 index 0000000000..e0f9e98dfd --- /dev/null +++ b/src/quests.h @@ -0,0 +1,154 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef _QUESTS_H_ +#define _QUESTS_H_ + +#include +#include +#include "player.h" +#include "networkmessage.h" + +class MissionState; +class Mission; +class Quest; + +typedef std::map StateList; +typedef std::list MissionsList; +typedef std::list QuestsList; + +class MissionState +{ + public: + MissionState(const std::string& _description, int32_t _missionID); + + int32_t getMissionID() const { + return missionID; + } + std::string getMissionDescription() const { + return description; + } + + private: + std::string description; + int32_t missionID; +}; + +class Mission +{ + public: + Mission(const std::string& _missionName, int32_t _storageID, int32_t _startValue, int32_t _endValue, bool _ignoreEndValue); + ~Mission(); + bool isCompleted(Player* player) const; + bool isStarted(Player* player) const; + std::string getName(Player* player); + std::string getDescription(Player* player); + + uint32_t getStorageId() const { + return storageID; + } + int32_t getStartStorageValue() const { + return startValue; + } + int32_t getEndStorageValue() const { + return endValue; + } + + MissionState* mainState; + StateList state; + + private: + std::string missionName; + uint32_t storageID; + int32_t startValue, endValue; + bool ignoreEndValue; +}; + +class Quest +{ + public: + Quest(const std::string& _name, uint16_t _id, int32_t _startStorageID, int32_t _startStorageValue); + ~Quest(); + + bool isCompleted(Player* player); + bool isStarted(Player* player) const; + uint16_t getID() const { + return id; + } + std::string getName() const { + return name; + } + uint16_t getMissionsCount(Player* player) const; + + uint32_t getStartStorageId() const { + return startStorageID; + } + int32_t getStartStorageValue() const { + return startStorageValue; + } + + void addMission(Mission* mission) { + missions.push_back(mission); + } + + MissionsList::const_iterator getFirstMission() const { + return missions.begin(); + } + MissionsList::const_iterator getLastMission() const { + return missions.end(); + } + + private: + std::string name; + + uint32_t startStorageID; + int32_t startStorageValue; + uint16_t id; + + MissionsList missions; +}; + +class Quests +{ + public: + Quests(); + ~Quests(); + + static Quests* getInstance() { + static Quests instance; + return &instance; + } + + QuestsList::const_iterator getFirstQuest() const { + return quests.begin(); + } + QuestsList::const_iterator getLastQuest() const { + return quests.end(); + } + + bool loadFromXml(); + Quest* getQuestByID(uint16_t id); + bool isQuestStorage(const uint32_t key, const int32_t value, const int32_t oldValue); + uint16_t getQuestsCount(Player* player); + bool reload(); + + private: + QuestsList quests; +}; + +#endif diff --git a/src/raids.cpp b/src/raids.cpp new file mode 100644 index 0000000000..5ace765d6b --- /dev/null +++ b/src/raids.cpp @@ -0,0 +1,714 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "raids.h" + +#include "game.h" +#include "player.h" +#include "configmanager.h" + +#include + +extern Game g_game; +extern ConfigManager g_config; + +Raids::Raids() + : m_scriptInterface("Raid Interface") +{ + loaded = false; + started = false; + running = NULL; + lastRaidEnd = 0; + checkRaidsEvent = 0; + + m_scriptInterface.initState(); +} + +Raids::~Raids() +{ + clear(); +} + +bool Raids::loadFromXml() +{ + if (isLoaded()) { + return true; + } + + xmlDocPtr doc = xmlParseFile("data/raids/raids.xml"); + + if (doc) { + xmlNodePtr root, raidNode; + root = xmlDocGetRootElement(doc); + + if (xmlStrcmp(root->name, (const xmlChar*)"raids") != 0) { + std::cout << "[Error] Raids: Wrong root node." << std::endl; + xmlFreeDoc(doc); + return false; + } + + int intValue; + std::string strValue; + raidNode = root->children; + + while (raidNode) { + if (xmlStrcmp(raidNode->name, (const xmlChar*)"raid") == 0) { + std::string name, file; + uint32_t interval, margin; + + if (readXMLString(raidNode, "name", strValue)) { + name = strValue; + } else { + std::cout << "[Error] Raids: name tag missing for raid." << std::endl; + raidNode = raidNode->next; + continue; + } + + if (readXMLString(raidNode, "file", strValue)) { + file = strValue; + } else { + std::ostringstream ss; + ss << "raids/" << name << ".xml"; + file = ss.str(); + std::cout << "[Warning] Raids: file tag missing for raid " << name << ". Using default: " << file << std::endl; + } + + //interval2 is the average interval between + // 2 executions of the raid in minutes + if (readXMLInteger(raidNode, "interval2", intValue) && intValue > 0) { + interval = intValue * 60; + } else { + std::cout << "[Error] Raids: interval2 tag missing or divided by 0 for raid " << name << std::endl; + raidNode = raidNode->next; + continue; + } + + if (readXMLInteger(raidNode, "margin", intValue)) { + margin = intValue * 60 * 1000; + } else { + std::cout << "[Warning] Raids: margin tag missing for raid " << name << std::endl; + margin = 0; + } + + Raid* newRaid = new Raid(name, interval, margin); + + if (!newRaid) { + xmlFreeDoc(doc); + return false; + } + + bool ret = newRaid->loadFromXml("data/raids/" + file); + + if (!ret) { + std::cout << "[Error] Raids: failed to load raid " << name << std::endl; + delete newRaid; + } else { + raidList.push_back(newRaid); + } + } + + raidNode = raidNode->next; + } + + xmlFreeDoc(doc); + + } else { + std::cout << "[Error] Raids: Could not load data/raids/raids.xml" << std::endl; + return false; + } + + loaded = true; + return true; +} + +#define MAX_RAND_RANGE 10000000 + +bool Raids::startup() +{ + if (!isLoaded() || isStarted()) { + return false; + } + + setLastRaidEnd(OTSYS_TIME()); + + checkRaidsEvent = g_scheduler.addEvent(createSchedulerTask(CHECK_RAIDS_INTERVAL * 1000, boost::bind(&Raids::checkRaids, this))); + + started = true; + return started; +} + +void Raids::checkRaids() +{ + if (!getRunning()) { + uint64_t now = OTSYS_TIME(); + + for (RaidList::iterator it = raidList.begin(); it != raidList.end(); ++it) { + if (now >= (getLastRaidEnd() + (*it)->getMargin())) { + if (MAX_RAND_RANGE * CHECK_RAIDS_INTERVAL / (*it)->getInterval() >= (uint32_t)random_range(0, MAX_RAND_RANGE)) { + setRunning(*it); + (*it)->startRaid(); + raidList.erase(it); + break; + } + } + } + } + + checkRaidsEvent = g_scheduler.addEvent(createSchedulerTask(CHECK_RAIDS_INTERVAL * 1000, boost::bind(&Raids::checkRaids, this))); +} + +void Raids::clear() +{ + g_scheduler.stopEvent(checkRaidsEvent); + checkRaidsEvent = 0; + + for (RaidList::const_iterator it = raidList.begin(), end = raidList.end(); it != end; ++it) { + delete *it; + } + + raidList.clear(); + + loaded = false; + started = false; + running = NULL; + lastRaidEnd = 0; + + m_scriptInterface.reInitState(); +} + +bool Raids::reload() +{ + clear(); + return loadFromXml(); +} + +Raid* Raids::getRaidByName(const std::string& name) +{ + for (RaidList::iterator it = raidList.begin(), end = raidList.end(); it != end; ++it) { + if (strcasecmp((*it)->getName().c_str(), name.c_str()) == 0) { + return (*it); + } + } + + return NULL; +} + +Raid::Raid(const std::string& _name, uint32_t _interval, uint32_t _marginTime) +{ + loaded = false; + name = _name; + interval = _interval; + nextEvent = 0; + state = RAIDSTATE_IDLE; + margin = _marginTime; + nextEventEvent = 0; +} + +Raid::~Raid() +{ + stopEvents(); + + for (RaidEventVector::iterator it = raidEvents.begin(), end = raidEvents.end(); it != end; ++it) { + delete (*it); + } + + raidEvents.clear(); +} + +bool Raid::loadFromXml(const std::string& _filename) +{ + if (isLoaded()) { + return true; + } + + xmlDocPtr doc = xmlParseFile(_filename.c_str()); + + if (doc) { + xmlNodePtr root, eventNode; + root = xmlDocGetRootElement(doc); + + if (xmlStrcmp(root->name, (const xmlChar*)"raid") != 0) { + std::cout << "[Error] Raids: Wrong root node." << std::endl; + xmlFreeDoc(doc); + return false; + } + + eventNode = root->children; + + while (eventNode) { + RaidEvent* event; + + if (xmlStrcmp(eventNode->name, (const xmlChar*)"announce") == 0) { + event = new AnnounceEvent(); + } else if (xmlStrcmp(eventNode->name, (const xmlChar*)"singlespawn") == 0) { + event = new SingleSpawnEvent(); + } else if (xmlStrcmp(eventNode->name, (const xmlChar*)"areaspawn") == 0) { + event = new AreaSpawnEvent(); + } else if (xmlStrcmp(eventNode->name, (const xmlChar*)"script") == 0) { + event = new ScriptEvent(&Raids::getInstance()->getScriptInterface()); + } else { + eventNode = eventNode->next; + continue; + } + + if (event->configureRaidEvent(eventNode)) { + raidEvents.push_back(event); + } else { + std::cout << "Raids: Error in file(" << _filename << ") eventNode: " << eventNode->name << std::endl; + delete event; + } + + eventNode = eventNode->next; + } + + //sort by delay time + std::sort(raidEvents.begin(), raidEvents.end(), RaidEvent::compareEvents); + + xmlFreeDoc(doc); + } else { + std::cout << "[Error] Raid: Could not load " << _filename << "!" << std::endl; + return false; + } + + loaded = true; + return true; +} + +void Raid::startRaid() +{ + RaidEvent* raidEvent = getNextRaidEvent(); + + if (raidEvent) { + state = RAIDSTATE_EXECUTING; + nextEventEvent = g_scheduler.addEvent(createSchedulerTask(raidEvent->getDelay(), boost::bind(&Raid::executeRaidEvent, this, raidEvent))); + } +} + +void Raid::executeRaidEvent(RaidEvent* raidEvent) +{ + if (raidEvent->executeEvent()) { + nextEvent++; + RaidEvent* newRaidEvent = getNextRaidEvent(); + + if (newRaidEvent) { + uint32_t ticks = (uint32_t)std::max(RAID_MINTICKS, newRaidEvent->getDelay() - raidEvent->getDelay()); + nextEventEvent = g_scheduler.addEvent(createSchedulerTask(ticks, boost::bind(&Raid::executeRaidEvent, this, newRaidEvent))); + } else { + resetRaid(); + } + } else { + resetRaid(); + } +} + +void Raid::resetRaid() +{ + nextEvent = 0; + state = RAIDSTATE_IDLE; + Raids::getInstance()->setRunning(NULL); + Raids::getInstance()->setLastRaidEnd(OTSYS_TIME()); +} + +void Raid::stopEvents() +{ + if (nextEventEvent != 0) { + g_scheduler.stopEvent(nextEventEvent); + nextEventEvent = 0; + } +} + +RaidEvent* Raid::getNextRaidEvent() +{ + if (nextEvent < raidEvents.size()) { + return raidEvents[nextEvent]; + } else { + return NULL; + } +} + +void Raid::addEvent(RaidEvent* event) +{ + raidEvents.push_back(event); +} + +bool RaidEvent::configureRaidEvent(xmlNodePtr eventNode) +{ + int32_t intValue; + + if (readXMLInteger(eventNode, "delay", intValue)) { + m_delay = intValue; + + if (m_delay < RAID_MINTICKS) { + m_delay = RAID_MINTICKS; + } + + return true; + } else { + std::cout << "[Error] Raid: delay tag missing." << std::endl; + return false; + } +} + +bool AnnounceEvent::configureRaidEvent(xmlNodePtr eventNode) +{ + if (!RaidEvent::configureRaidEvent(eventNode)) { + return false; + } + + std::string strValue; + + if (readXMLString(eventNode, "message", strValue)) { + m_message = strValue; + } else { + std::cout << "[Error] Raid: message tag missing for announce event." << std::endl; + return false; + } + + if (readXMLString(eventNode, "type", strValue)) { + std::string tmpStrValue = asLowerCaseString(strValue); + + if (tmpStrValue == "warning") { + m_messageType = MSG_STATUS_WARNING; + } else if (tmpStrValue == "event") { + m_messageType = MSG_EVENT_ADVANCE; + } else if (tmpStrValue == "default") { + m_messageType = MSG_EVENT_DEFAULT; + } else if (tmpStrValue == "description") { + m_messageType = MSG_INFO_DESCR; + } else if (tmpStrValue == "smallstatus") { + m_messageType = MSG_STATUS_SMALL; + } else if (tmpStrValue == "blueconsole") { + m_messageType = MSG_STATUS_CONSOLE_BLUE; + } else if (tmpStrValue == "redconsole") { + m_messageType = MSG_STATUS_CONSOLE_RED; + } else { + std::cout << "[Notice] Raid: Unknown type tag missing for announce event. Using default: " << (int32_t)m_messageType << std::endl; + } + } else { + m_messageType = MSG_EVENT_ADVANCE; + std::cout << "[Notice] Raid: type tag missing for announce event. Using default: " << (int32_t)m_messageType << std::endl; + } + + return true; +} + +bool AnnounceEvent::executeEvent() +{ + g_game.broadcastMessage(m_message, m_messageType); + return true; +} + +bool SingleSpawnEvent::configureRaidEvent(xmlNodePtr eventNode) +{ + if (!RaidEvent::configureRaidEvent(eventNode)) { + return false; + } + + std::string strValue; + int32_t intValue; + + if (readXMLString(eventNode, "name", strValue)) { + m_monsterName = strValue; + } else { + std::cout << "[Error] Raid: name tag missing for singlespawn event." << std::endl; + return false; + } + + if (readXMLInteger(eventNode, "x", intValue)) { + m_position.x = intValue; + } else { + std::cout << "[Error] Raid: x tag missing for singlespawn event." << std::endl; + return false; + } + + if (readXMLInteger(eventNode, "y", intValue)) { + m_position.y = intValue; + } else { + std::cout << "[Error] Raid: y tag missing for singlespawn event." << std::endl; + return false; + } + + if (readXMLInteger(eventNode, "z", intValue)) { + m_position.z = intValue; + } else { + std::cout << "[Error] Raid: z tag missing for singlespawn event." << std::endl; + return false; + } + + return true; +} + +bool SingleSpawnEvent::executeEvent() +{ + Monster* monster = Monster::createMonster(m_monsterName); + + if (!monster) { + std::cout << "[Error] Raids: Cant create monster " << m_monsterName << std::endl; + return false; + } + + if (!g_game.placeCreature(monster, m_position, false, true)) { + delete monster; + std::cout << "[Error] Raids: Cant place monster " << m_monsterName << std::endl; + return false; + } + + return true; +} + +bool AreaSpawnEvent::configureRaidEvent(xmlNodePtr eventNode) +{ + if (!RaidEvent::configureRaidEvent(eventNode)) { + return false; + } + + std::string strValue; + int32_t intValue; + + if (readXMLInteger(eventNode, "radius", intValue)) { + int32_t radius = intValue; + Position centerPos; + + if (readXMLInteger(eventNode, "centerx", intValue)) { + centerPos.x = intValue; + } else { + std::cout << "[Error] Raid: centerx tag missing for areaspawn event." << std::endl; + return false; + } + + if (readXMLInteger(eventNode, "centery", intValue)) { + centerPos.y = intValue; + } else { + std::cout << "[Error] Raid: centery tag missing for areaspawn event." << std::endl; + return false; + } + + if (readXMLInteger(eventNode, "centerz", intValue)) { + centerPos.z = intValue; + } else { + std::cout << "[Error] Raid: centerz tag missing for areaspawn event." << std::endl; + return false; + } + + m_fromPos.x = centerPos.x - radius; + m_fromPos.y = centerPos.y - radius; + m_fromPos.z = centerPos.z; + + m_toPos.x = centerPos.x + radius; + m_toPos.y = centerPos.y + radius; + m_toPos.z = centerPos.z; + } else { + if (readXMLInteger(eventNode, "fromx", intValue)) { + m_fromPos.x = intValue; + } else { + std::cout << "[Error] Raid: fromx tag missing for areaspawn event." << std::endl; + return false; + } + + if (readXMLInteger(eventNode, "fromy", intValue)) { + m_fromPos.y = intValue; + } else { + std::cout << "[Error] Raid: fromy tag missing for areaspawn event." << std::endl; + return false; + } + + if (readXMLInteger(eventNode, "fromz", intValue)) { + m_fromPos.z = intValue; + } else { + std::cout << "[Error] Raid: fromz tag missing for areaspawn event." << std::endl; + return false; + } + + if (readXMLInteger(eventNode, "tox", intValue)) { + m_toPos.x = intValue; + } else { + std::cout << "[Error] Raid: tox tag missing for areaspawn event." << std::endl; + return false; + } + + if (readXMLInteger(eventNode, "toy", intValue)) { + m_toPos.y = intValue; + } else { + std::cout << "[Error] Raid: toy tag missing for areaspawn event." << std::endl; + return false; + } + + if (readXMLInteger(eventNode, "toz", intValue)) { + m_toPos.z = intValue; + } else { + std::cout << "[Error] Raid: toz tag missing for areaspawn event." << std::endl; + return false; + } + } + + xmlNodePtr monsterNode = eventNode->children; + + while (monsterNode) { + if (xmlStrcmp(monsterNode->name, (const xmlChar*)"monster") == 0) { + std::string name; + int32_t minAmount = 0; + int32_t maxAmount = 0; + + if (readXMLString(monsterNode, "name", strValue)) { + name = strValue; + } else { + std::cout << "[Error] Raid: name tag missing for monster node." << std::endl; + return false; + } + + if (readXMLInteger(monsterNode, "minamount", intValue)) { + minAmount = intValue; + } + + if (readXMLInteger(monsterNode, "maxamount", intValue)) { + maxAmount = intValue; + } + + if (maxAmount == 0 && minAmount == 0) { + if (readXMLInteger(monsterNode, "amount", intValue)) { + maxAmount = intValue; + minAmount = intValue; + } else { + std::cout << "[Error] Raid: amount tag missing for monster node." << std::endl; + return false; + } + } + + addMonster(name, minAmount, maxAmount); + } + + monsterNode = monsterNode->next; + } + + return true; +} + +AreaSpawnEvent::~AreaSpawnEvent() +{ + for (MonsterSpawnList::iterator it = m_spawnList.begin(), end = m_spawnList.end(); it != end; ++it) { + delete (*it); + } + + m_spawnList.clear(); +} + +void AreaSpawnEvent::addMonster(MonsterSpawn* monsterSpawn) +{ + m_spawnList.push_back(monsterSpawn); +} + +void AreaSpawnEvent::addMonster(const std::string& monsterName, uint32_t minAmount, uint32_t maxAmount) +{ + MonsterSpawn* monsterSpawn = new MonsterSpawn(); + monsterSpawn->name = monsterName; + monsterSpawn->minAmount = minAmount; + monsterSpawn->maxAmount = maxAmount; + addMonster(monsterSpawn); +} + +bool AreaSpawnEvent::executeEvent() +{ + for (MonsterSpawnList::iterator it = m_spawnList.begin(), end = m_spawnList.end(); it != end; ++it) { + MonsterSpawn* spawn = (*it); + + uint32_t amount = random_range(spawn->minAmount, spawn->maxAmount); + + for (uint32_t i = 0; i < amount; ++i) { + Monster* monster = Monster::createMonster(spawn->name); + + if (!monster) { + std::cout << "[Error] Raids: Cant create monster " << spawn->name << std::endl; + return false; + } + + bool success = false; + + for (int32_t tries = 0; tries < MAXIMUM_TRIES_PER_MONSTER; tries++) { + Position pos; + pos.x = random_range(m_fromPos.x, m_toPos.x); + pos.y = random_range(m_fromPos.y, m_toPos.y); + pos.z = random_range(m_fromPos.z, m_toPos.z); + + Tile* tile = g_game.getMap()->getTile(pos); + + if (tile && !tile->isMoveableBlocking() && !tile->hasFlag(TILESTATE_PROTECTIONZONE) && tile->getTopCreature() == NULL && g_game.placeCreature(monster, pos, false, true)) { + success = true; + break; + } + } + + if (!success) { + delete monster; + } + } + } + + return true; +} + +ScriptEvent::ScriptEvent(LuaScriptInterface* _interface) : + Event(_interface) +{ +} + +bool ScriptEvent::configureRaidEvent(xmlNodePtr eventNode) +{ + if (!RaidEvent::configureRaidEvent(eventNode)) { + return false; + } + + std::string str; + + if (readXMLString(eventNode, "script", str)) { + if (!loadScript("data/raids/scripts/" + str)) { + std::cout << "Error: [ScriptEvent::configureRaidEvent] Can not load raid script." << std::endl; + return false; + } + } else { + std::cout << "Error: [ScriptEvent::configureRaidEvent] No script file found for raid" << std::endl; + return false; + } + + return true; +} + +std::string ScriptEvent::getScriptEventName() +{ + return "onRaid"; +} + +bool ScriptEvent::executeEvent() +{ + //onRaid() + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + env->setScriptId(m_scriptId, m_scriptInterface); + + m_scriptInterface->pushFunction(m_scriptId); + + bool result = m_scriptInterface->callFunction(0); + m_scriptInterface->releaseScriptEnv(); + return result; + } else { + std::cout << "[Error] Call stack overflow. ScriptEvent::executeEvent" << std::endl; + return 0; + } +} diff --git a/src/raids.h b/src/raids.h new file mode 100644 index 0000000000..b1784ec307 --- /dev/null +++ b/src/raids.h @@ -0,0 +1,253 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_RAIDS_H__ +#define __OTSERV_RAIDS_H__ + +#include +#include +#include + +#include "definitions.h" +#include "const.h" +#include "position.h" +#include "baseevents.h" + +#include +#include + +enum RaidState_t { + RAIDSTATE_IDLE = 0, + RAIDSTATE_EXECUTING +}; + +struct MonsterSpawn { + std::string name; + uint32_t minAmount; + uint32_t maxAmount; +}; + +//How many times it will try to find a tile to add the monster to before giving up +#define MAXIMUM_TRIES_PER_MONSTER 10 +#define CHECK_RAIDS_INTERVAL 60 +#define RAID_MINTICKS 1000 + +class Raid; +class RaidEvent; + +typedef std::list RaidList; +typedef std::vector RaidEventVector; +typedef std::list MonsterSpawnList; + +class Raids +{ + public: + static Raids* getInstance() { + static Raids instance; + return &instance; + } + + ~Raids(); + + bool loadFromXml(); + bool startup(); + + void clear(); + bool reload(); + + bool isLoaded() const { + return loaded; + } + bool isStarted() const { + return started; + } + + Raid* getRunning() { + return running; + } + void setRunning(Raid* newRunning) { + running = newRunning; + } + + Raid* getRaidByName(const std::string& name); + + uint64_t getLastRaidEnd() const { + return lastRaidEnd; + } + void setLastRaidEnd(uint64_t newLastRaidEnd) { + lastRaidEnd = newLastRaidEnd; + } + + void checkRaids(); + + LuaScriptInterface& getScriptInterface() { + return m_scriptInterface; + } + + private: + Raids(); + RaidList raidList; + bool loaded, started; + Raid* running; + uint64_t lastRaidEnd; + uint32_t checkRaidsEvent; + + LuaScriptInterface m_scriptInterface; +}; + +class Raid +{ + public: + Raid(const std::string& _name, uint32_t _interval, uint32_t _marginTime); + ~Raid(); + + bool loadFromXml(const std::string& _filename); + + void startRaid(); + + void executeRaidEvent(RaidEvent* raidEvent); + void resetRaid(); + + RaidEvent* getNextRaidEvent(); + void setState(RaidState_t newState) { + state = newState; + } + std::string getName() const { + return name; + } + + void addEvent(RaidEvent* event); + + bool isLoaded() const { + return loaded; + } + uint64_t getMargin() const { + return margin; + } + uint32_t getInterval() const { + return interval; + } + + void stopEvents(); + + private: + RaidEventVector raidEvents; + std::string name; + uint32_t interval; + uint32_t nextEvent; + uint64_t margin; + RaidState_t state; + uint32_t nextEventEvent; + bool loaded; +}; + +class RaidEvent +{ + public: + RaidEvent() {} + virtual ~RaidEvent() {} + + virtual bool configureRaidEvent(xmlNodePtr eventNode); + + virtual bool executeEvent() { + return false; + } + uint32_t getDelay() const { + return m_delay; + } + void setDelay(uint32_t newDelay) { + m_delay = newDelay; + } + + static bool compareEvents(const RaidEvent* lhs, const RaidEvent* rhs) { + return lhs->getDelay() < rhs->getDelay(); + } + + private: + uint32_t m_delay; +}; + +class AnnounceEvent : public RaidEvent +{ + public: + AnnounceEvent() { + m_messageType = MSG_EVENT_ADVANCE; + } + virtual ~AnnounceEvent() {} + + virtual bool configureRaidEvent(xmlNodePtr eventNode); + + virtual bool executeEvent(); + + private: + std::string m_message; + MessageClasses m_messageType; +}; + +class SingleSpawnEvent : public RaidEvent +{ + public: + SingleSpawnEvent() {} + virtual ~SingleSpawnEvent() {} + + virtual bool configureRaidEvent(xmlNodePtr eventNode); + + virtual bool executeEvent(); + + private: + std::string m_monsterName; + Position m_position; +}; + +class AreaSpawnEvent : public RaidEvent +{ + public: + AreaSpawnEvent() {} + virtual ~AreaSpawnEvent(); + + virtual bool configureRaidEvent(xmlNodePtr eventNode); + + void addMonster(MonsterSpawn* monsterSpawn); + void addMonster(const std::string& monsterName, uint32_t minAmount, uint32_t maxAmount); + + virtual bool executeEvent(); + + private: + MonsterSpawnList m_spawnList; + Position m_fromPos, m_toPos; +}; + +class ScriptEvent : public RaidEvent, public Event +{ + public: + ScriptEvent(LuaScriptInterface* _interface); + ScriptEvent(const ScriptEvent* copy); + ~ScriptEvent() {} + + virtual bool configureRaidEvent(xmlNodePtr eventNode); + virtual bool configureEvent(xmlNodePtr p) { + return false; + } + + bool executeEvent(); + + protected: + virtual std::string getScriptEventName(); +}; + +#endif diff --git a/src/rsa.cpp b/src/rsa.cpp new file mode 100644 index 0000000000..b4e5c10522 --- /dev/null +++ b/src/rsa.cpp @@ -0,0 +1,141 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include + +#include "rsa.h" + +RSA::RSA() +{ + OTSYS_THREAD_LOCKVARINIT(rsaLock); + m_keySet = false; + mpz_init2(m_p, 1024); + mpz_init2(m_q, 1024); + mpz_init2(m_d, 1024); + mpz_init2(m_u, 1024); + mpz_init2(m_dp, 1024); + mpz_init2(m_dq, 1024); + mpz_init2(m_mod, 1024); +} + +RSA::~RSA() +{ + mpz_clear(m_p); + mpz_clear(m_q); + mpz_clear(m_d); + mpz_clear(m_u); + mpz_clear(m_dp); + mpz_clear(m_dq); + mpz_clear(m_mod); +} + +bool RSA::setKey(const std::string& file) +{ + //loads p,q and d from a file + FILE* f = fopen(file.c_str(), "r"); + + if (!f) { + return false; + } + + char p[512], q[512], d[512]; + delete fgets(p, 512, f); + delete fgets(q, 512, f); + delete fgets(d, 512, f); + setKey(p, q, d); + return true; +} + +void RSA::setKey(const char* p, const char* q, const char* d) +{ + OTSYS_THREAD_LOCK_CLASS lockClass(rsaLock); + + mpz_set_str(m_p, p, 10); + mpz_set_str(m_q, q, 10); + mpz_set_str(m_d, d, 10); + + mpz_t pm1, qm1; + mpz_init2(pm1, 520); + mpz_init2(qm1, 520); + + mpz_sub_ui(pm1, m_p, 1); + mpz_sub_ui(qm1, m_q, 1); + mpz_invert(m_u, m_p, m_q); + mpz_mod(m_dp, m_d, pm1); + mpz_mod(m_dq, m_d, qm1); + + mpz_mul(m_mod, m_p, m_q); + + mpz_clear(pm1); + mpz_clear(qm1); +} + +void RSA::decrypt(char* msg, int32_t size) +{ + OTSYS_THREAD_LOCK_CLASS lockClass(rsaLock); + + mpz_t c, v1, v2, u2, tmp; + mpz_init2(c, 1024); + mpz_init2(v1, 1024); + mpz_init2(v2, 1024); + mpz_init2(u2, 1024); + mpz_init2(tmp, 1024); + + mpz_import(c, 128, 1, 1, 0, 0, msg); + + mpz_mod(tmp, c, m_p); + mpz_powm(v1, tmp, m_dp, m_p); + mpz_mod(tmp, c, m_q); + mpz_powm(v2, tmp, m_dq, m_q); + mpz_sub(u2, v2, v1); + mpz_mul(tmp, u2, m_u); + mpz_mod(u2, tmp, m_q); + + if (mpz_cmp_si(u2, 0) < 0) { + mpz_add(tmp, u2, m_q); + mpz_set(u2, tmp); + } + + mpz_mul(tmp, u2, m_p); + mpz_set_ui(c, 0); + mpz_add(c, v1, tmp); + + size_t count = (mpz_sizeinbase(c, 2) + 7) / 8; + memset(msg, 0, 128 - count); + mpz_export(&msg[128 - count], NULL, 1, 1, 0, 0, c); + + mpz_clear(c); + mpz_clear(v1); + mpz_clear(v2); + mpz_clear(u2); + mpz_clear(tmp); +} + +int32_t RSA::getKeySize() +{ + return (mpz_sizeinbase(m_mod, 2) + 7) / 8; +} + +void RSA::getPublicKey(char* buffer) +{ + size_t count = (mpz_sizeinbase(m_mod, 2) + 7) / 8; + memset(buffer, 0, 128 - count); + mpz_export(&buffer[128 - count], NULL, 1, 1, 0, 0, m_mod); +} diff --git a/src/rsa.h b/src/rsa.h new file mode 100644 index 0000000000..79b6892585 --- /dev/null +++ b/src/rsa.h @@ -0,0 +1,47 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_RSA_H__ +#define __OTSERV_RSA_H__ + +#include "otsystem.h" + +#include "gmp.h" + +class RSA +{ + public: + RSA(); + ~RSA(); + void setKey(const char* p, const char* q, const char* d); + bool setKey(const std::string& file); + void decrypt(char* msg, int32_t size); + + int32_t getKeySize(); + void getPublicKey(char* buffer); + + protected: + bool m_keySet; + + OTSYS_THREAD_LOCKVAR rsaLock; + + //use only GMP + mpz_t m_p, m_q, m_u, m_d, m_dp, m_dq, m_mod; +}; + +#endif diff --git a/src/scheduler.cpp b/src/scheduler.cpp new file mode 100644 index 0000000000..7a171fe0c9 --- /dev/null +++ b/src/scheduler.cpp @@ -0,0 +1,178 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include +#include "scheduler.h" + +Scheduler::Scheduler() +{ + m_lastEventId = 0; + m_threadState = STATE_TERMINATED; +} + +void Scheduler::start() +{ + m_threadState = STATE_RUNNING; + m_thread = boost::thread(boost::bind(&Scheduler::schedulerThread, (void*)this)); +} + +void Scheduler::schedulerThread(void* p) +{ + Scheduler* scheduler = (Scheduler*)p; + + // NOTE: second argument defer_lock is to prevent from immediate locking + boost::unique_lock eventLockUnique(scheduler->m_eventLock, boost::defer_lock); + + while (scheduler->m_threadState != STATE_TERMINATED) { + SchedulerTask* task = NULL; + bool runTask = false; + bool ret = true; + + // check if there are events waiting... + eventLockUnique.lock(); + + if (scheduler->m_eventList.empty()) { + scheduler->m_eventSignal.wait(eventLockUnique); + } else { + ret = scheduler->m_eventSignal.timed_wait(eventLockUnique, scheduler->m_eventList.top()->getCycle()); + } + + // the mutex is locked again now... + if (!ret && (scheduler->m_threadState != STATE_TERMINATED)) { + // ok we had a timeout, so there has to be an event we have to execute... + task = scheduler->m_eventList.top(); + scheduler->m_eventList.pop(); + + // check if the event was stopped + EventIdSet::iterator it = scheduler->m_eventIds.find(task->getEventId()); + + if (it != scheduler->m_eventIds.end()) { + // was not stopped so we should run it + runTask = true; + scheduler->m_eventIds.erase(it); + } + } + + eventLockUnique.unlock(); + + // add task to dispatcher + if (task) { + // if it was not stopped + if (runTask) { + // Expiration has another meaning for dispatcher tasks, reset it + task->setDontExpire(); + g_dispatcher.addTask(task); + } else { + // was stopped, have to be deleted here + delete task; + } + } + } +} + +uint32_t Scheduler::addEvent(SchedulerTask* task) +{ + bool do_signal = false; + m_eventLock.lock(); + + if (Scheduler::m_threadState == Scheduler::STATE_RUNNING) { + // check if the event has a valid id + if (task->getEventId() == 0) { + // if not generate one + if (m_lastEventId >= 0xFFFFFFFF) { + m_lastEventId = 0; + } + + ++m_lastEventId; + task->setEventId(m_lastEventId); + } + + // insert the eventid in the list of active events + m_eventIds.insert(task->getEventId()); + + // add the event to the queue + m_eventList.push(task); + + // if the list was empty or this event is the top in the list + // we have to signal it + do_signal = (task == m_eventList.top()); + } else { + m_eventLock.unlock(); + delete task; + task = NULL; + return 0; + } + + m_eventLock.unlock(); + + if (do_signal) { + m_eventSignal.notify_one(); + } + + return task->getEventId(); +} + +bool Scheduler::stopEvent(uint32_t eventid) +{ + if (eventid == 0) { + return false; + } + + m_eventLock.lock(); + + // search the event id.. + EventIdSet::iterator it = m_eventIds.find(eventid); + + if (it == m_eventIds.end()) { + m_eventLock.unlock(); + return false; + } + + m_eventIds.erase(it); + m_eventLock.unlock(); + return true; +} + +void Scheduler::stop() +{ + m_eventLock.lock(); + m_threadState = Scheduler::STATE_CLOSING; + m_eventLock.unlock(); +} + +void Scheduler::shutdown() +{ + m_eventLock.lock(); + m_threadState = Scheduler::STATE_TERMINATED; + + //this list should already be empty + while (!m_eventList.empty()) { + delete m_eventList.top(); + m_eventList.pop(); + } + + m_eventIds.clear(); + m_eventLock.unlock(); +} + +void Scheduler::join() +{ + m_thread.join(); +} diff --git a/src/scheduler.h b/src/scheduler.h new file mode 100644 index 0000000000..f4f876cdfe --- /dev/null +++ b/src/scheduler.h @@ -0,0 +1,114 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_SCHEDULER_H__ +#define __OTSERV_SCHEDULER_H__ + +#include "otsystem.h" +#include "tasks.h" +#include +#include +#include +#include + +#define SCHEDULER_MINTICKS 50 + +class SchedulerTask : public Task +{ + public: + ~SchedulerTask() {} + + void setEventId(uint32_t eventid) { + m_eventid = eventid; + } + uint32_t getEventId() const { + return m_eventid; + } + + boost::system_time getCycle() const { + return m_expiration; + } + + bool operator<(const SchedulerTask& other) const { + return getCycle() > other.getCycle(); + } + + protected: + SchedulerTask(uint32_t delay, const boost::function& f) : Task(delay, f) { + m_eventid = 0; + } + + uint32_t m_eventid; + + friend SchedulerTask* createSchedulerTask(uint32_t, const boost::function&); +}; + +inline SchedulerTask* createSchedulerTask(uint32_t delay, const boost::function& f) +{ + if (delay < SCHEDULER_MINTICKS) { + delay = SCHEDULER_MINTICKS; + } + + return new SchedulerTask(delay, f); +} + +class lessSchedTask : public std::binary_function +{ + public: + bool operator()(SchedulerTask*& t1, SchedulerTask*& t2) { + return (*t1) < (*t2); + } +}; + +class Scheduler +{ + public: + Scheduler(); + ~Scheduler() {} + + uint32_t addEvent(SchedulerTask* task); + bool stopEvent(uint32_t eventId); + + void start(); + void stop(); + void shutdown(); + void join(); + + enum SchedulerState { + STATE_RUNNING, + STATE_CLOSING, + STATE_TERMINATED + }; + + protected: + static void schedulerThread(void* p); + + boost::thread m_thread; + boost::mutex m_eventLock; + boost::condition_variable m_eventSignal; + + uint32_t m_lastEventId; + std::priority_queue, lessSchedTask > m_eventList; + typedef OTSERV_HASH_SET EventIdSet; + EventIdSet m_eventIds; + SchedulerState m_threadState; +}; + +extern Scheduler g_scheduler; + +#endif diff --git a/src/scriptmanager.cpp b/src/scriptmanager.cpp new file mode 100644 index 0000000000..6322410245 --- /dev/null +++ b/src/scriptmanager.cpp @@ -0,0 +1,110 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "scriptmanager.h" +#include "luascript.h" + +#include +#include + +#include "actions.h" +#include "talkaction.h" +#include "spells.h" +#include "movement.h" +#include "weapons.h" +#include "creatureevent.h" +#include "globalevent.h" + +Actions* g_actions = NULL; +CreatureEvents* g_creatureEvents = NULL; +GlobalEvents* g_globalEvents = NULL; +Spells* g_spells = NULL; +TalkActions* g_talkActions = NULL; +MoveEvents* g_moveEvents = NULL; +Weapons* g_weapons = NULL; + +ScriptingManager* ScriptingManager::_instance = NULL; + +ScriptingManager::ScriptingManager() +{ + g_weapons = new Weapons(); + g_spells = new Spells(); + g_actions = new Actions(); + g_talkActions = new TalkActions(); + g_moveEvents = new MoveEvents(); + g_creatureEvents = new CreatureEvents(); + g_globalEvents = new GlobalEvents(); +} + +ScriptingManager::~ScriptingManager() +{ + // +} + +ScriptingManager* ScriptingManager::getInstance() +{ + if (_instance == NULL) { + _instance = new ScriptingManager(); + } + + return _instance; +} + +bool ScriptingManager::loadScriptSystems() +{ + if (!g_weapons->loadFromXml()) { + std::cout << "> ERROR: Unable to load Weapons!" << std::endl; + return false; + } + + g_weapons->loadDefaults(); + + if (!g_spells->loadFromXml()) { + std::cout << "> ERROR: Unable to load Spells!" << std::endl; + return false; + } + + if (!g_actions->loadFromXml()) { + std::cout << "> ERROR: Unable to load Actions!" << std::endl; + return false; + } + + if (!g_talkActions->loadFromXml()) { + std::cout << "> ERROR: Unable to load TalkActions!" << std::endl; + return false; + } + + if (!g_moveEvents->loadFromXml()) { + std::cout << "> ERROR: Unable to load MoveEvents!" << std::endl; + return false; + } + + if (!g_creatureEvents->loadFromXml()) { + std::cout << "> ERROR: Unable to load CreatureEvents!" << std::endl; + return false; + } + + if (!g_globalEvents->loadFromXml()) { + std::cout << "> ERROR: Unable to load GlobalEVents!" << std::endl; + return false; + } + + return true; +} diff --git a/src/scriptmanager.h b/src/scriptmanager.h new file mode 100644 index 0000000000..f7963d5312 --- /dev/null +++ b/src/scriptmanager.h @@ -0,0 +1,35 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __SCRIPTMANAGER_H__ +#define __SCRIPTMANAGER_H__ + +class ScriptingManager +{ + public: + ScriptingManager(); + ~ScriptingManager(); + static ScriptingManager* getInstance(); + + bool loadScriptSystems(); + + private: + static ScriptingManager* _instance; +}; + +#endif diff --git a/src/server.cpp b/src/server.cpp new file mode 100644 index 0000000000..2f7dad793b --- /dev/null +++ b/src/server.cpp @@ -0,0 +1,269 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "outputmessage.h" +#include "server.h" +#include "connection.h" +#include "scheduler.h" +#include "configmanager.h" +#include "ban.h" + +extern ConfigManager g_config; +extern Ban g_bans; + +ServiceManager::ServiceManager() + : m_io_service(), death_timer(m_io_service), running(false) +{ + // +} + +ServiceManager::~ServiceManager() +{ + stop(); +} + +std::list ServiceManager::get_ports() const +{ + std::list ports; + + for (std::map::const_iterator it = m_acceptors.begin(); + it != m_acceptors.end(); ++it) { + ports.push_back(it->first); + } + + ports.unique(); + return ports; +} + +void ServiceManager::die() +{ + m_io_service.stop(); +} + +void ServiceManager::run() +{ + assert(!running); + running = true; + m_io_service.run(); +} + +void ServiceManager::stop() +{ + if (!running) { + return; + } + + running = false; + + for (std::map::iterator it = m_acceptors.begin(); + it != m_acceptors.end(); ++it) { + try { + m_io_service.post(boost::bind(&ServicePort::onStopServer, it->second)); + } catch (boost::system::system_error& e) { + std::cout << "[ServiceManager::stop] Network Error: " << e.what() << std::endl; + } + } + + m_acceptors.clear(); + + OutputMessagePool::getInstance()->stop(); + + death_timer.expires_from_now(boost::posix_time::seconds(3)); + death_timer.async_wait(boost::bind(&ServiceManager::die, this)); +} + +ServicePort::ServicePort(boost::asio::io_service& io_service) : + m_io_service(io_service), + m_acceptor(NULL), + m_serverPort(0), + m_pendingStart(false) +{ + // +} + +ServicePort::~ServicePort() +{ + close(); +} + +bool ServicePort::is_single_socket() const +{ + return m_services.size() && m_services.front()->is_single_socket(); +} + +std::string ServicePort::get_protocol_names() const +{ + if (m_services.empty()) { + return ""; + } + + std::string str = m_services.front()->get_protocol_name(); + + for (uint32_t i = 1; i < m_services.size(); ++i) { + str += ", "; + str += m_services[i]->get_protocol_name(); + } + + return str; +} + +void ServicePort::accept() +{ + if (!m_acceptor) { + return; + } + + boost::asio::ip::tcp::socket* socket = new boost::asio::ip::tcp::socket(m_io_service); + m_acceptor->async_accept(*socket, + boost::bind(&ServicePort::onAccept, this, socket, + boost::asio::placeholders::error)); +} + +void ServicePort::onAccept(boost::asio::ip::tcp::socket* socket, const boost::system::error_code& error) +{ + if (!error) { + if (m_services.empty()) { + return; + } + + boost::system::error_code error; + const boost::asio::ip::tcp::endpoint endpoint = socket->remote_endpoint(error); + uint32_t remote_ip = 0; + + if (!error) { + remote_ip = htonl(endpoint.address().to_v4().to_ulong()); + } + + if (remote_ip != 0 && g_bans.acceptConnection(remote_ip)) { + Connection_ptr connection = ConnectionManager::getInstance()->createConnection(socket, m_io_service, shared_from_this()); + Service_ptr service = m_services.front(); + + if (service->is_single_socket()) { + connection->acceptConnection(service->make_protocol(connection)); + } else { + connection->acceptConnection(); + } + } else if (socket->is_open()) { + boost::system::error_code error; + socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, error); + socket->close(error); + delete socket; + } + + accept(); + } else if (error != boost::asio::error::operation_aborted) { + if (!m_pendingStart) { + close(); + m_pendingStart = true; + g_scheduler.addEvent(createSchedulerTask(15000, + boost::bind(&ServicePort::openAcceptor, boost::weak_ptr(shared_from_this()), m_serverPort))); + } + } +} + +Protocol* ServicePort::make_protocol(bool checksummed, NetworkMessage& msg) const +{ + uint8_t protocolID = msg.GetByte(); + + for (std::vector::const_iterator service_iter = m_services.begin(); service_iter != m_services.end(); ++service_iter) { + Service_ptr service = *service_iter; + + if (protocolID != service->get_protocol_identifier()) { + continue; + } + + if ((checksummed && service->is_checksummed()) || !service->is_checksummed()) { + return service->make_protocol(Connection_ptr()); + } + } + + return NULL; +} + +void ServicePort::onStopServer() +{ + close(); +} + +void ServicePort::openAcceptor(boost::weak_ptr weak_service, uint16_t port) +{ + if (weak_service.expired()) { + return; + } + + if (ServicePort_ptr service = weak_service.lock()) { + service->open(port); + } +} + +void ServicePort::open(uint16_t port) +{ + close(); + + m_serverPort = port; + m_pendingStart = false; + + try { + if (g_config.getBoolean(ConfigManager::BIND_ONLY_GLOBAL_ADDRESS)) { + m_acceptor = new boost::asio::ip::tcp::acceptor(m_io_service, boost::asio::ip::tcp::endpoint( + boost::asio::ip::address(boost::asio::ip::address_v4::from_string(g_config.getString(ConfigManager::IP))), m_serverPort)); + } else { + m_acceptor = new boost::asio::ip::tcp::acceptor(m_io_service, boost::asio::ip::tcp::endpoint( + boost::asio::ip::address(boost::asio::ip::address_v4(INADDR_ANY)), m_serverPort)); + } + + m_acceptor->set_option(boost::asio::ip::tcp::no_delay(true)); + + accept(); + } catch (boost::system::system_error& e) { + std::cout << "[ServicePort::open] Error: " << e.what() << std::endl; + + m_pendingStart = true; + g_scheduler.addEvent(createSchedulerTask(15000, + boost::bind(&ServicePort::openAcceptor, boost::weak_ptr(shared_from_this()), port))); + } +} + +void ServicePort::close() +{ + if (m_acceptor) { + if (m_acceptor->is_open()) { + boost::system::error_code error; + m_acceptor->close(error); + } + + delete m_acceptor; + m_acceptor = NULL; + } +} + +bool ServicePort::add_service(Service_ptr new_svc) +{ + for (std::vector::const_iterator svc_iter = m_services.begin(); svc_iter != m_services.end(); ++svc_iter) { + Service_ptr svc = *svc_iter; + + if (svc->is_single_socket()) { + return false; + } + } + + m_services.push_back(new_svc); + return true; +} diff --git a/src/server.h b/src/server.h new file mode 100644 index 0000000000..d03033fab8 --- /dev/null +++ b/src/server.h @@ -0,0 +1,167 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_SERVER_H__ +#define __OTSERV_SERVER_H__ + +#include "definitions.h" +#include +#include +#include +#include +#include +#include + +class Connection; +typedef boost::shared_ptr Connection_ptr; +class Protocol; +class NetworkMessage; + +class ServiceBase; +class ServicePort; + +typedef boost::shared_ptr Service_ptr; +typedef boost::shared_ptr ServicePort_ptr; + +class ServiceBase : boost::noncopyable +{ + public: + virtual ~ServiceBase() {} // Redundant, but stifles compiler warnings + + virtual bool is_single_socket() const = 0; + virtual bool is_checksummed() const = 0; + virtual uint8_t get_protocol_identifier() const = 0; + virtual const char* get_protocol_name() const = 0; + + virtual Protocol* make_protocol(Connection_ptr c) const = 0; +}; + +template +class Service : public ServiceBase +{ + public: + bool is_single_socket() const { + return ProtocolType::server_sends_first; + } + bool is_checksummed() const { + return ProtocolType::use_checksum; + } + uint8_t get_protocol_identifier() const { + return ProtocolType::protocol_identifier; + } + const char* get_protocol_name() const { + return ProtocolType::protocol_name(); + } + + Protocol* make_protocol(Connection_ptr c) const { + return new ProtocolType(c); + } +}; + +class ServicePort : boost::noncopyable, public boost::enable_shared_from_this +{ + public: + ServicePort(boost::asio::io_service& io_service); + ~ServicePort(); + + static void openAcceptor(boost::weak_ptr weak_service, uint16_t port); + void open(uint16_t port); + void close(); + bool is_single_socket() const; + std::string get_protocol_names() const; + + bool add_service(Service_ptr); + Protocol* make_protocol(bool checksummed, NetworkMessage& msg) const; + + void onStopServer(); + void onAccept(boost::asio::ip::tcp::socket* socket, const boost::system::error_code& error); + + protected: + void accept(); + + boost::asio::io_service& m_io_service; + boost::asio::ip::tcp::acceptor* m_acceptor; + std::vector m_services; + + uint16_t m_serverPort; + bool m_pendingStart; +}; + +typedef boost::shared_ptr ServicePort_ptr; +class ServiceManager : boost::noncopyable +{ + ServiceManager(const ServiceManager&); + public: + ServiceManager(); + ~ServiceManager(); + + void run(); + void stop(); + + bool okay(); + + template + bool add(uint16_t port); + + bool is_running() const { + return m_acceptors.empty() == false; + } + std::list get_ports() const; + + protected: + void die(); + + std::map m_acceptors; + + boost::asio::io_service m_io_service; + boost::asio::deadline_timer death_timer; + bool running; +}; + +template +bool ServiceManager::add(uint16_t port) +{ + if (port == 0) { + std::cout << "ERROR: No port provided for service " << ProtocolType::protocol_name() << ". Service disabled." << std::endl; + return false; + } + + ServicePort_ptr service_port; + + std::map::iterator finder = + m_acceptors.find(port); + + if (finder == m_acceptors.end()) { + service_port.reset(new ServicePort(m_io_service)); + service_port->open(port); + m_acceptors[port] = service_port; + } else { + service_port = finder->second; + + if (service_port->is_single_socket() || ProtocolType::server_sends_first) { + std::cout << "ERROR: " << ProtocolType::protocol_name() << + " and " << service_port->get_protocol_names() << + " cannot use the same port " << port << "." << std::endl; + return false; + } + } + + return service_port->add_service(Service_ptr(new Service())); +}; + +#endif diff --git a/src/sha1.cpp b/src/sha1.cpp new file mode 100644 index 0000000000..7f86dcdd0e --- /dev/null +++ b/src/sha1.cpp @@ -0,0 +1,569 @@ +/* + * sha1.cpp + * + * Copyright (C) 1998 + * Paul E. Jones + * All Rights Reserved. + * + ***************************************************************************** + * $Id: sha1.cpp,v 1.9 2004/03/27 18:02:20 paulej Exp $ + ***************************************************************************** + * + * Description: + * This class implements the Secure Hashing Standard as defined + * in FIPS PUB 180-1 published April 17, 1995. + * + * The Secure Hashing Standard, which uses the Secure Hashing + * Algorithm (SHA), produces a 160-bit message digest for a + * given data stream. In theory, it is highly improbable that + * two messages will produce the same message digest. Therefore, + * this algorithm can serve as a means of providing a "fingerprint" + * for a message. + * + * Portability Issues: + * SHA-1 is defined in terms of 32-bit "words". This code was + * written with the expectation that the processor has at least + * a 32-bit machine word size. If the machine word size is larger, + * the code should still function properly. One caveat to that + * is that the input functions taking characters and character arrays + * assume that only 8 bits of information are stored in each character. + * + * Caveats: + * SHA-1 is designed to work with messages less than 2^64 bits long. + * Although SHA-1 allows a message digest to be generated for + * messages of any number of bits less than 2^64, this implementation + * only works with messages with a length that is a multiple of 8 + * bits. + * + */ + +#include "otpch.h" + +#include "sha1.h" + +/* + * SHA1 + * + * Description: + * This is the constructor for the sha1 class. + * + * Parameters: + * None. + * + * Returns: + * Nothing. + * + * Comments: + * + */ +SHA1::SHA1() +{ + Reset(); +} + +/* + * ~SHA1 + * + * Description: + * This is the destructor for the sha1 class + * + * Parameters: + * None. + * + * Returns: + * Nothing. + * + * Comments: + * + */ +SHA1::~SHA1() +{ + // The destructor does nothing +} + +/* + * Reset + * + * Description: + * This function will initialize the sha1 class member variables + * in preparation for computing a new message digest. + * + * Parameters: + * None. + * + * Returns: + * Nothing. + * + * Comments: + * + */ +void SHA1::Reset() +{ + Length_Low = 0; + Length_High = 0; + Message_Block_Index = 0; + + H[0] = 0x67452301; + H[1] = 0xEFCDAB89; + H[2] = 0x98BADCFE; + H[3] = 0x10325476; + H[4] = 0xC3D2E1F0; + + Computed = false; + Corrupted = false; +} + +/* + * Result + * + * Description: + * This function will return the 160-bit message digest into the + * array provided. + * + * Parameters: + * message_digest_array: [out] + * This is an array of five unsigned integers which will be filled + * with the message digest that has been computed. + * + * Returns: + * True if successful, false if it failed. + * + * Comments: + * + */ +bool SHA1::Result(unsigned* message_digest_array) +{ + int32_t i; + + if (Corrupted) { + return false; + } + + if (!Computed) { + PadMessage(); + Computed = true; + } + + for (i = 0; i < 5; i++) { + message_digest_array[i] = H[i]; + } + + return true; +} + +/* + * Input + * + * Description: + * This function accepts an array of octets as the next portion of + * the message. + * + * Parameters: + * message_array: [in] + * An array of characters representing the next portion of the + * message. + * + * Returns: + * Nothing. + * + * Comments: + * + */ +void SHA1::Input(const unsigned char* message_array, unsigned length) +{ + if (!length) { + return; + } + + if (Computed || Corrupted) { + Corrupted = true; + return; + } + + while (length-- && !Corrupted) { + Message_Block[Message_Block_Index++] = (*message_array & 0xFF); + + Length_Low += 8; + Length_Low &= 0xFFFFFFFF; // Force it to 32 bits + + if (Length_Low == 0) { + Length_High++; + Length_High &= 0xFFFFFFFF; // Force it to 32 bits + + if (Length_High == 0) { + Corrupted = true; // Message is too long + } + } + + if (Message_Block_Index == 64) { + ProcessMessageBlock(); + } + + message_array++; + } +} + +/* + * Input + * + * Description: + * This function accepts an array of octets as the next portion of + * the message. + * + * Parameters: + * message_array: [in] + * An array of characters representing the next portion of the + * message. + * length: [in] + * The length of the message_array + * + * Returns: + * Nothing. + * + * Comments: + * + */ +void SHA1::Input(const char* message_array, unsigned length) +{ + Input((unsigned char*) message_array, length); +} + +/* + * Input + * + * Description: + * This function accepts a single octets as the next message element. + * + * Parameters: + * message_element: [in] + * The next octet in the message. + * + * Returns: + * Nothing. + * + * Comments: + * + */ +void SHA1::Input(unsigned char message_element) +{ + Input(&message_element, 1); +} + +/* + * Input + * + * Description: + * This function accepts a single octet as the next message element. + * + * Parameters: + * message_element: [in] + * The next octet in the message. + * + * Returns: + * Nothing. + * + * Comments: + * + */ +void SHA1::Input(char message_element) +{ + Input((unsigned char*) &message_element, 1); +} + +/* + * operator<< + * + * Description: + * This operator makes it convenient to provide character strings to + * the SHA1 object for processing. + * + * Parameters: + * message_array: [in] + * The character array to take as input. + * + * Returns: + * A reference to the SHA1 object. + * + * Comments: + * Each character is assumed to hold 8 bits of information. + * + */ +SHA1& SHA1::operator<<(const char* message_array) +{ + const char* p = message_array; + + while (*p) { + Input(*p); + p++; + } + + return *this; +} + +/* + * operator<< + * + * Description: + * This operator makes it convenient to provide character strings to + * the SHA1 object for processing. + * + * Parameters: + * message_array: [in] + * The character array to take as input. + * + * Returns: + * A reference to the SHA1 object. + * + * Comments: + * Each character is assumed to hold 8 bits of information. + * + */ +SHA1& SHA1::operator<<(const unsigned char* message_array) +{ + const unsigned char* p = message_array; + + while (*p) { + Input(*p); + p++; + } + + return *this; +} + +/* + * operator<< + * + * Description: + * This function provides the next octet in the message. + * + * Parameters: + * message_element: [in] + * The next octet in the message + * + * Returns: + * A reference to the SHA1 object. + * + * Comments: + * The character is assumed to hold 8 bits of information. + * + */ +SHA1& SHA1::operator<<(const char message_element) +{ + Input((unsigned char*) &message_element, 1); + + return *this; +} + +/* + * operator<< + * + * Description: + * This function provides the next octet in the message. + * + * Parameters: + * message_element: [in] + * The next octet in the message + * + * Returns: + * A reference to the SHA1 object. + * + * Comments: + * The character is assumed to hold 8 bits of information. + * + */ +SHA1& SHA1::operator<<(const unsigned char message_element) +{ + Input(&message_element, 1); + + return *this; +} + +/* + * ProcessMessageBlock + * + * Description: + * This function will process the next 512 bits of the message + * stored in the Message_Block array. + * + * Parameters: + * None. + * + * Returns: + * Nothing. + * + * Comments: + * Many of the variable names in this function, especially the single + * character names, were used because those were the names used + * in the publication. + * + */ +void SHA1::ProcessMessageBlock() +{ + const unsigned K[] = { // Constants defined for SHA-1 + 0x5A827999, + 0x6ED9EBA1, + 0x8F1BBCDC, + 0xCA62C1D6 + }; + + int t; // Loop counter + unsigned temp; // Temporary word value + unsigned W[80]; // Word sequence + unsigned A, B, C, D, E; // Word buffers + + /* + * Initialize the first 16 words in the array W + */ + for (t = 0; t < 16; t++) { + W[t] = ((unsigned) Message_Block[t * 4]) << 24; + W[t] |= ((unsigned) Message_Block[t * 4 + 1]) << 16; + W[t] |= ((unsigned) Message_Block[t * 4 + 2]) << 8; + W[t] |= ((unsigned) Message_Block[t * 4 + 3]); + } + + for (t = 16; t < 80; t++) { + W[t] = CircularShift(1, W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16]); + } + + A = H[0]; + B = H[1]; + C = H[2]; + D = H[3]; + E = H[4]; + + for (t = 0; t < 20; t++) { + temp = CircularShift(5, A) + ((B & C) | ((~B) & D)) + E + W[t] + K[0]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = CircularShift(30, B); + B = A; + A = temp; + } + + for (t = 20; t < 40; t++) { + temp = CircularShift(5, A) + (B ^ C ^ D) + E + W[t] + K[1]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = CircularShift(30, B); + B = A; + A = temp; + } + + for (t = 40; t < 60; t++) { + temp = CircularShift(5, A) + + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = CircularShift(30, B); + B = A; + A = temp; + } + + for (t = 60; t < 80; t++) { + temp = CircularShift(5, A) + (B ^ C ^ D) + E + W[t] + K[3]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = CircularShift(30, B); + B = A; + A = temp; + } + + H[0] = (H[0] + A) & 0xFFFFFFFF; + H[1] = (H[1] + B) & 0xFFFFFFFF; + H[2] = (H[2] + C) & 0xFFFFFFFF; + H[3] = (H[3] + D) & 0xFFFFFFFF; + H[4] = (H[4] + E) & 0xFFFFFFFF; + + Message_Block_Index = 0; +} + +/* + * PadMessage + * + * Description: + * According to the standard, the message must be padded to an even + * 512 bits. The first padding bit must be a '1'. The last 64 bits + * represent the length of the original message. All bits in between + * should be 0. This function will pad the message according to those + * rules by filling the message_block array accordingly. It will also + * call ProcessMessageBlock() appropriately. When it returns, it + * can be assumed that the message digest has been computed. + * + * Parameters: + * None. + * + * Returns: + * Nothing. + * + * Comments: + * + */ +void SHA1::PadMessage() +{ + /* + * Check to see if the current message block is too small to hold + * the initial padding bits and length. If so, we will pad the + * block, process it, and then continue padding into a second block. + */ + if (Message_Block_Index > 55) { + Message_Block[Message_Block_Index++] = 0x80; + + while (Message_Block_Index < 64) { + Message_Block[Message_Block_Index++] = 0; + } + + ProcessMessageBlock(); + + while (Message_Block_Index < 56) { + Message_Block[Message_Block_Index++] = 0; + } + } else { + Message_Block[Message_Block_Index++] = 0x80; + + while (Message_Block_Index < 56) { + Message_Block[Message_Block_Index++] = 0; + } + } + + /* + * Store the message length as the last 8 octets + */ + Message_Block[56] = (Length_High >> 24) & 0xFF; + Message_Block[57] = (Length_High >> 16) & 0xFF; + Message_Block[58] = (Length_High >> 8) & 0xFF; + Message_Block[59] = (Length_High) & 0xFF; + Message_Block[60] = (Length_Low >> 24) & 0xFF; + Message_Block[61] = (Length_Low >> 16) & 0xFF; + Message_Block[62] = (Length_Low >> 8) & 0xFF; + Message_Block[63] = (Length_Low) & 0xFF; + + ProcessMessageBlock(); +} + + +/* + * CircularShift + * + * Description: + * This member function will perform a circular shifting operation. + * + * Parameters: + * bits: [in] + * The number of bits to shift (1-31) + * word: [in] + * The value to shift (assumes a 32-bit integer) + * + * Returns: + * The shifted value. + * + * Comments: + * + */ +unsigned SHA1::CircularShift(int bits, unsigned word) const +{ + return ((word << bits) & 0xFFFFFFFF) | ((word & 0xFFFFFFFF) >> (32 - bits)); +} diff --git a/src/sha1.h b/src/sha1.h new file mode 100644 index 0000000000..cfe49e915b --- /dev/null +++ b/src/sha1.h @@ -0,0 +1,84 @@ +/* + * sha1.h + * + * Copyright (C) 1998 + * Paul E. Jones + * All Rights Reserved. + * + ***************************************************************************** + * $Id: sha1.h,v 1.6 2004/03/27 18:02:26 paulej Exp $ + ***************************************************************************** + * + * Description: + * This class implements the Secure Hashing Standard as defined + * in FIPS PUB 180-1 published April 17, 1995. + * + * Many of the variable names in this class, especially the single + * character names, were used because those were the names used + * in the publication. + * + * Please read the file sha1.cpp for more information. + * + */ + +#ifndef __SHA1__ +#define __SHA1__ + +class SHA1 +{ + public: + SHA1(); + virtual ~SHA1(); + + /* + * Re-initialize the class + */ + void Reset(); + + /* + * Returns the message digest + */ + bool Result(unsigned* message_digest_array); + + /* + * Provide input to SHA1 + */ + void Input(const unsigned char* message_array, unsigned length); + void Input(const char* message_array, unsigned length); + void Input(unsigned char message_element); + void Input(char message_element); + SHA1& operator<<(const char* message_array); + SHA1& operator<<(const unsigned char* message_array); + SHA1& operator<<(const char message_element); + SHA1& operator<<(const unsigned char message_element); + + private: + /* + * Process the next 512 bits of the message + */ + void ProcessMessageBlock(); + + /* + * Pads the current message block to 512 bits + */ + void PadMessage(); + + /* + * Performs a circular left shift operation + */ + inline unsigned CircularShift(int bits, unsigned word) const; + + unsigned H[5]; // Message digest buffers + + unsigned Length_Low; // Message length in bits + unsigned Length_High; // Message length in bits + + unsigned char Message_Block[64]; // 512-bit message blocks + int Message_Block_Index; // Index into message block array + + bool Computed; // Is the digest computed? + bool Corrupted; // Is the message digest corruped? + +}; + +#endif diff --git a/src/spawn.cpp b/src/spawn.cpp new file mode 100644 index 0000000000..7778c9e14b --- /dev/null +++ b/src/spawn.cpp @@ -0,0 +1,473 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "spawn.h" +#include "game.h" +#include "player.h" +#include "npc.h" +#include "tools.h" +#include "configmanager.h" + +#include +#include + +extern ConfigManager g_config; +extern Monsters g_monsters; +extern Game g_game; + +#define MINSPAWN_INTERVAL 1000 +#define DEFAULTSPAWN_INTERVAL 60000 + +Spawns::Spawns() +{ + loaded = false; + started = false; + filename = ""; +} + +Spawns::~Spawns() +{ + clear(); +} + +bool Spawns::loadFromXml(const std::string& _filename) +{ + if (isLoaded()) { + return true; + } + + filename = _filename; + + xmlDocPtr doc = xmlParseFile(filename.c_str()); + + if (doc) { + xmlNodePtr root, spawnNode; + root = xmlDocGetRootElement(doc); + + if (xmlStrcmp(root->name, (const xmlChar*)"spawns") != 0) { + xmlFreeDoc(doc); + return false; + } + + int32_t intValue; + std::string strValue; + + spawnNode = root->children; + + while (spawnNode) { + if (xmlStrcmp(spawnNode->name, (const xmlChar*)"spawn") == 0) { + Position centerPos; + int32_t radius = -1; + + if (readXMLInteger(spawnNode, "centerx", intValue)) { + centerPos.x = intValue; + } else { + xmlFreeDoc(doc); + return false; + } + + if (readXMLInteger(spawnNode, "centery", intValue)) { + centerPos.y = intValue; + } else { + xmlFreeDoc(doc); + return false; + } + + if (readXMLInteger(spawnNode, "centerz", intValue)) { + centerPos.z = intValue; + } else { + xmlFreeDoc(doc); + return false; + } + + if (readXMLInteger(spawnNode, "radius", intValue)) { + radius = intValue; + } else { + xmlFreeDoc(doc); + return false; + } + + Spawn* spawn = new Spawn(centerPos, radius); + spawnList.push_back(spawn); + + xmlNodePtr tmpNode = spawnNode->children; + + while (tmpNode) { + if (xmlStrcmp(tmpNode->name, (const xmlChar*)"monster") == 0) { + std::string name = ""; + Position pos = centerPos; + Direction dir = NORTH; + uint32_t interval = 0; + + if (readXMLString(tmpNode, "name", strValue)) { + name = strValue; + } else { + tmpNode = tmpNode->next; + continue; + } + + if (readXMLInteger(tmpNode, "direction", intValue)) { + switch (intValue) { + case 0: + dir = NORTH; + break; + case 1: + dir = EAST; + break; + case 2: + dir = SOUTH; + break; + case 3: + dir = WEST; + break; + } + } + + if (readXMLInteger(tmpNode, "x", intValue)) { + pos.x += intValue; + } else { + tmpNode = tmpNode->next; + continue; + } + + if (readXMLInteger(tmpNode, "y", intValue)) { + pos.y += intValue; + } else { + tmpNode = tmpNode->next; + continue; + } + + if (readXMLInteger(tmpNode, "spawntime", intValue) || readXMLInteger(tmpNode, "interval", intValue)) { + interval = intValue * 1000; + } else { + tmpNode = tmpNode->next; + continue; + } + + if (interval > MINSPAWN_INTERVAL) { + spawn->addMonster(name, pos, dir, interval); + } else { + std::cout << "[Warning] Spawns::loadFromXml " << name << " " << pos << " spawntime can not be less than " << MINSPAWN_INTERVAL / 1000 << " seconds." << std::endl; + } + } else if (xmlStrcmp(tmpNode->name, (const xmlChar*)"npc") == 0) { + Direction direction = NORTH; + std::string name = ""; + Position placePos = centerPos; + + if (readXMLString(tmpNode, "name", strValue)) { + name = strValue; + } else { + tmpNode = tmpNode->next; + continue; + } + + if (readXMLInteger(tmpNode, "direction", intValue)) { + switch (intValue) { + case 0: + direction = NORTH; + break; + case 1: + direction = EAST; + break; + case 2: + direction = SOUTH; + break; + case 3: + direction = WEST; + break; + } + } + + if (readXMLInteger(tmpNode, "x", intValue)) { + placePos.x += intValue; + } else { + tmpNode = tmpNode->next; + continue; + } + + if (readXMLInteger(tmpNode, "y", intValue)) { + placePos.y += intValue; + } else { + tmpNode = tmpNode->next; + continue; + } + + Npc* npc = Npc::createNpc(name); + + if (!npc) { + tmpNode = tmpNode->next; + continue; + } + + npc->setDirection(direction); + npc->setMasterPos(placePos, radius); + npcList.push_back(npc); + } + + tmpNode = tmpNode->next; + } + } + + spawnNode = spawnNode->next; + } + + xmlFreeDoc(doc); + loaded = true; + return true; + } + + return false; +} + +void Spawns::startup() +{ + if (!isLoaded() || isStarted()) { + return; + } + + for (NpcList::iterator it = npcList.begin(); it != npcList.end(); ++it) { + g_game.placeCreature((*it), (*it)->getMasterPos(), false, true); + } + + npcList.clear(); + + for (SpawnList::iterator it = spawnList.begin(); it != spawnList.end(); ++it) { + (*it)->startup(); + } + + started = true; +} + +void Spawns::clear() +{ + for (SpawnList::iterator it = spawnList.begin(); it != spawnList.end(); ++it) { + delete (*it); + } + + spawnList.clear(); + + loaded = false; + started = false; + filename = ""; +} + +bool Spawns::isInZone(const Position& centerPos, int32_t radius, const Position& pos) +{ + if (radius == -1) { + return true; + } + + return ((pos.x >= centerPos.x - radius) && (pos.x <= centerPos.x + radius) && + (pos.y >= centerPos.y - radius) && (pos.y <= centerPos.y + radius)); +} + +void Spawn::startSpawnCheck() +{ + if (checkSpawnEvent == 0) { + checkSpawnEvent = g_scheduler.addEvent(createSchedulerTask(getInterval(), boost::bind(&Spawn::checkSpawn, this))); + } +} + +Spawn::Spawn(const Position& _pos, int32_t _radius) +{ + centerPos = _pos; + radius = _radius; + interval = DEFAULTSPAWN_INTERVAL; + checkSpawnEvent = 0; +} + +Spawn::~Spawn() +{ + for (SpawnedMap::iterator it = spawnedMap.begin(); it != spawnedMap.end(); ++it) { + Monster* monster = it->second; + monster->setSpawn(NULL); + monster->releaseThing2(); + } + + spawnedMap.clear(); + spawnMap.clear(); + + stopEvent(); +} + +bool Spawn::findPlayer(const Position& pos) +{ + SpectatorVec list; + g_game.getSpectators(list, pos, false, true); + + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + if (!(*it)->getPlayer()->hasFlag(PlayerFlag_IgnoredByMonsters)) { + return true; + } + } + + return false; +} + +bool Spawn::isInSpawnZone(const Position& pos) +{ + return Spawns::getInstance()->isInZone(centerPos, radius, pos); +} + +bool Spawn::spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& pos, Direction dir, bool startup /*= false*/) +{ + Monster* monster = Monster::createMonster(mType); + + if (!monster) { + return false; + } + + if (startup) { + //No need to send out events to the surrounding since there is no one out there to listen! + if (!g_game.internalPlaceCreature(monster, pos, true)) { + delete monster; + return false; + } + } else { + if (!g_game.placeCreature(monster, pos, false, true)) { + delete monster; + return false; + } + } + + monster->setDirection(dir); + monster->setSpawn(this); + monster->setMasterPos(pos, radius); + monster->useThing2(); + + spawnedMap.insert(spawned_pair(spawnId, monster)); + spawnMap[spawnId].lastSpawn = OTSYS_TIME(); + return true; +} + +void Spawn::startup() +{ + for (SpawnMap::iterator it = spawnMap.begin(); it != spawnMap.end(); ++it) { + uint32_t spawnId = it->first; + spawnBlock_t& sb = it->second; + + spawnMonster(spawnId, sb.mType, sb.pos, sb.direction, true); + } +} + +void Spawn::checkSpawn() +{ + checkSpawnEvent = 0; + + cleanup(); + + uint32_t spawnCount = 0; + uint32_t spawnId; + + for (SpawnMap::iterator it = spawnMap.begin(); it != spawnMap.end(); ++it) { + spawnId = it->first; + spawnBlock_t& sb = it->second; + + if (spawnedMap.count(spawnId) == 0) { + if (OTSYS_TIME() >= sb.lastSpawn + sb.interval) { + if (findPlayer(sb.pos)) { + sb.lastSpawn = OTSYS_TIME(); + continue; + } + + spawnMonster(spawnId, sb.mType, sb.pos, sb.direction); + + ++spawnCount; + + if (spawnCount >= (uint32_t)g_config.getNumber(ConfigManager::RATE_SPAWN)) { + break; + } + } + } + } + + if (spawnedMap.size() < spawnMap.size()) { + checkSpawnEvent = g_scheduler.addEvent(createSchedulerTask(getInterval(), boost::bind(&Spawn::checkSpawn, this))); + } +} + +void Spawn::cleanup() +{ + Monster* monster; + uint32_t spawnId; + + for (SpawnedMap::iterator it = spawnedMap.begin(); it != spawnedMap.end();) { + spawnId = it->first; + monster = it->second; + + if (monster->isRemoved()) { + if (spawnId != 0) { + spawnMap[spawnId].lastSpawn = OTSYS_TIME(); + } + + monster->releaseThing2(); + spawnedMap.erase(it++); + } else if (!isInSpawnZone(monster->getPosition()) && spawnId != 0) { + spawnedMap.insert(spawned_pair(0, monster)); + spawnedMap.erase(it++); + } else { + ++it; + } + } +} + +bool Spawn::addMonster(const std::string& _name, const Position& _pos, Direction _dir, uint32_t _interval) +{ + MonsterType* mType = g_monsters.getMonsterType(_name); + + if (!mType) { + std::cout << "[Spawn::addMonster] Can not find " << _name << std::endl; + return false; + } + + if (_interval < interval) { + interval = _interval; + } + + spawnBlock_t sb; + sb.mType = mType; + sb.pos = _pos; + sb.direction = _dir; + sb.interval = _interval; + sb.lastSpawn = 0; + + uint32_t spawnId = (int32_t)spawnMap.size() + 1; + spawnMap[spawnId] = sb; + return true; +} + +void Spawn::removeMonster(Monster* monster) +{ + for (SpawnedMap::iterator it = spawnedMap.begin(); it != spawnedMap.end(); ++it) { + if (it->second == monster) { + monster->releaseThing2(); + spawnedMap.erase(it); + break; + } + } +} + +void Spawn::stopEvent() +{ + if (checkSpawnEvent != 0) { + g_scheduler.stopEvent(checkSpawnEvent); + checkSpawnEvent = 0; + } +} diff --git a/src/spawn.h b/src/spawn.h new file mode 100644 index 0000000000..2cb8419122 --- /dev/null +++ b/src/spawn.h @@ -0,0 +1,118 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_SPAWN_H__ +#define __OTSERV_SPAWN_H__ + +#include "tile.h" +#include "position.h" +#include "monster.h" +#include "templates.h" + +#include +#include + +class Spawn; +typedef std::list SpawnList; + +class Spawns +{ + private: + Spawns(); + + public: + static Spawns* getInstance() { + static Spawns instance; + return &instance; + } + + bool isInZone(const Position& centerPos, int32_t radius, const Position& pos); + + ~Spawns(); + + bool loadFromXml(const std::string& _filename); + void startup(); + void clear(); + + bool isLoaded() const { + return loaded; + } + bool isStarted() const { + return started; + } + + private: + typedef std::list NpcList; + NpcList npcList; + SpawnList spawnList; + bool loaded, started; + std::string filename; +}; + +struct spawnBlock_t { + MonsterType* mType; + Direction direction; + Position pos; + uint32_t interval; + int64_t lastSpawn; +}; + +class Spawn +{ + public: + Spawn(const Position& _pos, int32_t _radius); + ~Spawn(); + + bool addMonster(const std::string& _name, const Position& _pos, Direction _dir, uint32_t _interval); + void removeMonster(Monster* monster); + + uint32_t getInterval() const { + return interval; + } + void startup(); + + void startSpawnCheck(); + void stopEvent(); + + bool isInSpawnZone(const Position& pos); + void cleanup(); + + private: + Position centerPos; + int32_t radius; + int32_t despawnRange; + int32_t despawnRadius; + + //map of creatures in the spawn + typedef std::map SpawnMap; + SpawnMap spawnMap; + + //map of the spawned creatures + typedef std::multimap > SpawnedMap; + typedef SpawnedMap::value_type spawned_pair; + SpawnedMap spawnedMap; + + uint32_t interval; + uint32_t checkSpawnEvent; + + bool findPlayer(const Position& pos); + bool spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& pos, Direction dir, bool startup = false); + void checkSpawn(); +}; + +#endif diff --git a/src/spells.cpp b/src/spells.cpp new file mode 100644 index 0000000000..92ed73fa2f --- /dev/null +++ b/src/spells.cpp @@ -0,0 +1,2330 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "definitions.h" +#include "tools.h" +#include "house.h" +#include "housetile.h" +#include "spells.h" +#include "combat.h" +#include "commands.h" +#include "monsters.h" +#include "configmanager.h" +#include "const.h" + +#include +#include + +extern Game g_game; +extern Spells* g_spells; +extern Monsters g_monsters; +extern Vocations g_vocations; +extern ConfigManager g_config; + +Spells::Spells(): + m_scriptInterface("Spell Interface") +{ + m_scriptInterface.initState(); +} + +Spells::~Spells() +{ + clear(); +} + +TalkActionResult_t Spells::playerSaySpell(Player* player, SpeakClasses type, std::string& words) +{ + std::string str_words = words; + + //strip trailing spaces + trimString(str_words); + + InstantSpell* instantSpell = getInstantSpell(str_words); + + if (!instantSpell) { + return TALKACTION_CONTINUE; + } + + std::string param = ""; + + if (instantSpell->getHasParam()) { + size_t spellLen = instantSpell->getWords().length(); + size_t paramLen = str_words.length() - spellLen; + std::string paramText = str_words.substr(spellLen, paramLen); + + if (!paramText.empty() && paramText[0] == ' ') { + size_t loc1 = paramText.find('"', 1); + + if (loc1 != std::string::npos) { + size_t loc2 = paramText.find('"', loc1 + 1); + + if (loc2 == std::string::npos) { + loc2 = paramText.length(); + } else if (paramText.find_last_not_of(' ') != loc2) { + return TALKACTION_CONTINUE; + } + + param = paramText.substr(loc1 + 1, loc2 - loc1 - 1); + } else { + trimString(paramText); + loc1 = paramText.find(' ', 0); + + if (loc1 == std::string::npos) { + param = paramText; + } else { + return TALKACTION_CONTINUE; + } + } + } + } + + if (instantSpell->playerCastInstant(player, param)) { + words = instantSpell->getWords(); + + if (instantSpell->getHasParam() && !param.empty()) { + words += " \"" + param + "\""; + } + + return TALKACTION_BREAK; + } + + return TALKACTION_FAILED; +} + +void Spells::clear() +{ + RunesMap::iterator it; + + for (it = runes.begin(); it != runes.end(); ++it) { + delete it->second; + } + + runes.clear(); + + InstantsMap::iterator it2; + + for (it2 = instants.begin(); it2 != instants.end(); ++it2) { + delete it2->second; + } + + instants.clear(); +} + +LuaScriptInterface& Spells::getScriptInterface() +{ + return m_scriptInterface; +} + +std::string Spells::getScriptBaseName() +{ + return "spells"; +} + +Event* Spells::getEvent(const std::string& nodeName) +{ + std::string tmpNodeName = asLowerCaseString(nodeName); + + if (tmpNodeName == "rune") { + return new RuneSpell(&m_scriptInterface); + } else if (tmpNodeName == "instant") { + return new InstantSpell(&m_scriptInterface); + } else if (tmpNodeName == "conjure") { + return new ConjureSpell(&m_scriptInterface); + } + + return NULL; +} + +bool Spells::registerEvent(Event* event, xmlNodePtr p) +{ + InstantSpell* instant = dynamic_cast(event); + RuneSpell* rune = dynamic_cast(event); + + if (instant) { + if (instants.find(instant->getWords()) != instants.end()) { + std::cout << "[Warning - Spells::registerEvent] Duplicate registered instant spell with words: " << instant->getWords() << std::endl; + return false; + } + + instants[instant->getWords()] = instant; + return true; + } else if (rune) { + if (runes.find(rune->getRuneItemId()) != runes.end()) { + std::cout << "[Warning - Spells::registerEvent] Duplicate registered rune with id: " << rune->getRuneItemId() << std::endl; + return false; + } + + runes[rune->getRuneItemId()] = rune; + return true; + } + + return false; +} + +Spell* Spells::getSpellByName(const std::string& name) +{ + Spell* spell; + + if ((spell = getRuneSpellByName(name))) { + return spell; + } + + if ((spell = getInstantSpellByName(name))) { + return spell; + } + + return NULL; +} + +RuneSpell* Spells::getRuneSpell(uint32_t id) +{ + RunesMap::iterator it = runes.find(id); + + if (it != runes.end()) { + return it->second; + } + + return NULL; +} + +RuneSpell* Spells::getRuneSpellByName(const std::string& name) +{ + for (RunesMap::iterator it = runes.begin(); it != runes.end(); ++it) { + if (strcasecmp(it->second->getName().c_str(), name.c_str()) == 0) { + return it->second; + } + } + + return NULL; +} + +InstantSpell* Spells::getInstantSpell(const std::string& words) +{ + InstantSpell* result = NULL; + + for (InstantsMap::iterator it = instants.begin(); it != instants.end(); ++it) { + InstantSpell* instantSpell = it->second; + + const std::string& instantSpellWords = instantSpell->getWords(); + size_t spellLen = instantSpellWords.length(); + + if (strncasecmp(instantSpellWords.c_str(), words.c_str(), spellLen) == 0) { + if (!result || spellLen > result->getWords().length()) { + result = instantSpell; + } + } + } + + if (result) { + const std::string& resultWords = result->getWords(); + + if (words.length() > resultWords.length()) { + if (!result->getHasParam()) { + return NULL; + } + + size_t spellLen = resultWords.length(); + size_t paramLen = words.length() - spellLen; + + if (paramLen < 2 || words[spellLen] != ' ') { + return NULL; + } + } + + return result; + } + + return NULL; +} + +uint32_t Spells::getInstantSpellCount(const Player* player) +{ + uint32_t count = 0; + + for (InstantsMap::iterator it = instants.begin(); it != instants.end(); ++it) { + InstantSpell* instantSpell = it->second; + + if (instantSpell->canCast(player)) { + ++count; + } + } + + return count; +} + +InstantSpell* Spells::getInstantSpellByIndex(const Player* player, uint32_t index) +{ + uint32_t count = 0; + + for (InstantsMap::iterator it = instants.begin(); it != instants.end(); ++it) { + InstantSpell* instantSpell = it->second; + + if (instantSpell->canCast(player)) { + if (count == index) { + return instantSpell; + } + + ++count; + } + } + + return NULL; +} + +InstantSpell* Spells::getInstantSpellByName(const std::string& name) +{ + for (InstantsMap::iterator it = instants.begin(); it != instants.end(); ++it) { + if (strcasecmp(it->second->getName().c_str(), name.c_str()) == 0) { + return it->second; + } + } + + return NULL; +} + +Position Spells::getCasterPosition(Creature* creature, Direction dir) +{ + Position pos = creature->getPosition(); + pos = getNextPosition(dir, pos); + return pos; +} + +CombatSpell::CombatSpell(Combat* _combat, bool _needTarget, bool _needDirection) : + Event(&g_spells->getScriptInterface()) +{ + combat = _combat; + needTarget = _needTarget; + needDirection = _needDirection; +} + +CombatSpell::~CombatSpell() +{ + delete combat; +} + +bool CombatSpell::loadScriptCombat() +{ + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + combat = env->getCombatObject(env->getLastCombatId()); + + env->resetCallback(); + m_scriptInterface->releaseScriptEnv(); + } + + return (combat != NULL); +} + +bool CombatSpell::castSpell(Creature* creature) +{ + if (m_scripted) { + LuaVariant var; + var.type = VARIANT_POSITION; + + if (needDirection) { + var.pos = Spells::getCasterPosition(creature, creature->getDirection()); + } else { + var.pos = creature->getPosition(); + } + + return executeCastSpell(creature, var); + } + + Position pos; + + if (needDirection) { + pos = Spells::getCasterPosition(creature, creature->getDirection()); + } else { + pos = creature->getPosition(); + } + + combat->doCombat(creature, pos); + return true; +} + +bool CombatSpell::castSpell(Creature* creature, Creature* target) +{ + if (m_scripted) { + LuaVariant var; + + if (combat->hasArea()) { + var.type = VARIANT_POSITION; + + if (needTarget) { + var.pos = target->getPosition(); + } else if (needDirection) { + var.pos = Spells::getCasterPosition(creature, creature->getDirection()); + } else { + var.pos = creature->getPosition(); + } + } else { + var.type = VARIANT_NUMBER; + var.number = target->getID(); + } + + return executeCastSpell(creature, var); + } + + if (combat->hasArea()) { + if (needTarget) { + combat->doCombat(creature, target->getPosition()); + } else { + return castSpell(creature); + } + } else { + combat->doCombat(creature, target); + } + + return true; +} + +bool CombatSpell::executeCastSpell(Creature* creature, const LuaVariant& var) +{ + //onCastSpell(cid, var) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + env->setScriptId(m_scriptId, m_scriptInterface); + env->setRealPos(creature->getPosition()); + + lua_State* L = m_scriptInterface->getLuaState(); + + uint32_t cid = env->addThing(creature); + + m_scriptInterface->pushFunction(m_scriptId); + lua_pushnumber(L, cid); + m_scriptInterface->pushVariant(L, var); + + bool result = m_scriptInterface->callFunction(2); + m_scriptInterface->releaseScriptEnv(); + + return result; + } else { + std::cout << "[Error - ComatSpell::executeCastSpell] Call stack overflow." << std::endl; + return false; + } +} + +Spell::Spell() +{ + spellId = 0; + level = 0; + magLevel = 0; + mana = 0; + manaPercent = 0; + soul = 0; + range = -1; + cooldown = 1000; + needTarget = false; + needWeapon = false; + selfTarget = false; + blockingSolid = false; + blockingCreature = false; + premium = false; + enabled = true; + isAggressive = true; + learnable = false; + group = SPELLGROUP_NONE; + groupCooldown = 1000; + secondaryGroup = SPELLGROUP_NONE; + secondaryGroupCooldown = 0; +} + +bool Spell::configureSpell(xmlNodePtr p) +{ + int32_t intValue; + std::string strValue; + + if (readXMLString(p, "name", strValue)) { + name = strValue; + const char* reservedList[] = { + "melee", + "physical", + "poison", + "fire", + "energy", + "drown", + "lifedrain", + "manadrain", + "healing", + "speed", + "outfit", + "invisible", + "drunk", + "firefield", + "poisonfield", + "energyfield", + "firecondition", + "poisoncondition", + "energycondition", + "drowncondition", + "freezecondition", + "cursecondition", + "dazzlecondition" + }; + + for (uint32_t i = 0; i < sizeof(reservedList) / sizeof(const char*); ++i) { + if (strcasecmp(reservedList[i], name.c_str()) == 0) { + std::cout << "[Error - Spell::configureSpell] Spell is using a reserved name: " << reservedList[i] << std::endl; + return false; + } + } + } else { + std::cout << "[Error - Spell::configureSpell] Spell without name." << std::endl; + return false; + } + + if (readXMLInteger(p, "spellid", intValue) || readXMLInteger(p, "icon", intValue)) { + spellId = intValue; + } + + if (readXMLString(p, "group", strValue)) { + std::string tmpStr = asLowerCaseString(strValue); + + if (tmpStr == "none" || tmpStr == "0") { + group = SPELLGROUP_NONE; + } else if (tmpStr == "attack" || tmpStr == "1") { + group = SPELLGROUP_ATTACK; + } else if (tmpStr == "healing" || tmpStr == "2") { + group = SPELLGROUP_HEALING; + } else if (tmpStr == "support" || tmpStr == "3") { + group = SPELLGROUP_SUPPORT; + } else if (tmpStr == "special" || tmpStr == "4") { + group = SPELLGROUP_SPECIAL; + } else { + std::cout << "[Warning - Spell::configureSpell] Unknown group: " << strValue << std::endl; + } + } + + if (readXMLInteger(p, "groupcooldown", intValue)) { + groupCooldown = intValue; + } + + if (readXMLString(p, "secondarygroup", strValue)) { + std::string tmpStr = asLowerCaseString(strValue); + + if (tmpStr == "none" || tmpStr == "0") { + secondaryGroup = SPELLGROUP_NONE; + } else if (tmpStr == "attack" || tmpStr == "1") { + secondaryGroup = SPELLGROUP_ATTACK; + } else if (tmpStr == "healing" || tmpStr == "2") { + secondaryGroup = SPELLGROUP_HEALING; + } else if (tmpStr == "support" || tmpStr == "3") { + secondaryGroup = SPELLGROUP_SUPPORT; + } else if (tmpStr == "special" || tmpStr == "4") { + secondaryGroup = SPELLGROUP_SPECIAL; + } else { + std::cout << "[Warning - Spell::configureSpell] Unknown secondarygroup: " << strValue << std::endl; + } + } + + if (readXMLInteger(p, "secondarygroupcooldown", intValue)) { + secondaryGroupCooldown = intValue; + } + + if (readXMLInteger(p, "lvl", intValue)) { + level = intValue; + } + + if (readXMLInteger(p, "maglv", intValue)) { + magLevel = intValue; + } + + if (readXMLInteger(p, "mana", intValue)) { + mana = intValue; + } + + if (readXMLInteger(p, "manapercent", intValue)) { + manaPercent = intValue; + } + + if (readXMLInteger(p, "soul", intValue)) { + soul = intValue; + } + + if (readXMLInteger(p, "exhaustion", intValue) || readXMLInteger(p, "cooldown", intValue)) { + cooldown = intValue; + } + + if (readXMLInteger(p, "prem", intValue)) { + premium = (intValue == 1); + } + + if (readXMLInteger(p, "enabled", intValue)) { + enabled = (intValue == 1); + } + + if (readXMLInteger(p, "needtarget", intValue)) { + needTarget = (intValue == 1); + } + + if (readXMLInteger(p, "needweapon", intValue)) { + needWeapon = (intValue == 1); + } + + if (readXMLInteger(p, "selftarget", intValue)) { + selfTarget = (intValue == 1); + } + + if (readXMLInteger(p, "needlearn", intValue)) { + learnable = (intValue == 1); + } + + if (readXMLInteger(p, "range", intValue)) { + range = intValue; + } + + if (readXMLInteger(p, "blocking", intValue)) { + blockingSolid = (intValue == 1); + blockingCreature = (intValue == 1); + } + + if (readXMLString(p, "blocktype", strValue)) { + std::string tmpStrValue = asLowerCaseString(strValue); + + if (tmpStrValue == "all") { + blockingSolid = true; + blockingCreature = true; + } else if (tmpStrValue == "solid") { + blockingSolid = true; + } else if (tmpStrValue == "creature") { + blockingCreature = true; + } else { + std::cout << "[Warning - Spell::configureSpell] Blocktype \"" << strValue << "\" does not exist." << std::endl; + } + } + + if (readXMLString(p, "aggressive", strValue)) { + isAggressive = booleanString(strValue); + } + + if (readXMLString(p, "groups", strValue)) { + std::vector split = explodeString(strValue, ",", 2); + std::vector::iterator it = split.begin(); + + if (it != split.end()) { + std::string tmpStr = asLowerCaseString(*it); + + if (tmpStr == "none" || tmpStr == "0") { + group = SPELLGROUP_NONE; + } else if (tmpStr == "attack" || tmpStr == "1") { + group = SPELLGROUP_ATTACK; + } else if (tmpStr == "healing" || tmpStr == "2") { + group = SPELLGROUP_HEALING; + } else if (tmpStr == "support" || tmpStr == "3") { + group = SPELLGROUP_SUPPORT; + } else if (tmpStr == "special" || tmpStr == "4") { + group = SPELLGROUP_SPECIAL; + } else { + std::cout << "[Warning - Spell::configureSpell] Unknown groups (primary): " << strValue << std::endl; + } + } + + ++it; + + if (it != split.end()) { + std::string tmpStr = asLowerCaseString(*it); + + if (tmpStr == "none" || tmpStr == "0") { + secondaryGroup = SPELLGROUP_NONE; + } else if (tmpStr == "attack" || tmpStr == "1") { + secondaryGroup = SPELLGROUP_ATTACK; + } else if (tmpStr == "healing" || tmpStr == "2") { + secondaryGroup = SPELLGROUP_HEALING; + } else if (tmpStr == "support" || tmpStr == "3") { + secondaryGroup = SPELLGROUP_SUPPORT; + } else if (tmpStr == "special" || tmpStr == "4") { + secondaryGroup = SPELLGROUP_SPECIAL; + } else { + std::cout << "[Warning - Spell::configureSpell] Unknown groups (secondary): " << strValue << std::endl; + } + } + } + + if (readXMLString(p, "groupexhaustions", strValue)) { + std::vector split = explodeString(strValue, ",", 2); + std::vector::iterator it = split.begin(); + + if (it != split.end()) { + groupCooldown = atoi(it->c_str()); + } + + ++it; + + if (it != split.end()) { + secondaryGroupCooldown = atoi(it->c_str()); + } + } + + if (group == SPELLGROUP_NONE) { + group = (isAggressive ? SPELLGROUP_ATTACK : SPELLGROUP_HEALING); + } + + xmlNodePtr vocationNode = p->children; + + while (vocationNode) { + if (xmlStrcmp(vocationNode->name, (const xmlChar*)"vocation") == 0) { + if (readXMLString(vocationNode, "name", strValue)) { + int32_t vocationId = g_vocations.getVocationId(strValue); + + if (vocationId != -1) { + vocSpellMap[vocationId] = true; + int32_t promotedVocation = g_vocations.getPromotedVocation(vocationId); + + if (promotedVocation != 0) { + vocSpellMap[promotedVocation] = true; + } + } else { + std::cout << "[Warning - Spell::configureSpell] Wrong vocation name: " << strValue << std::endl; + } + } + } + + vocationNode = vocationNode->next; + } + + return true; +} + +bool Spell::playerSpellCheck(Player* player, bool ignoreExhaust/* = false*/) const +{ + if (player->hasFlag(PlayerFlag_CannotUseSpells)) { + return false; + } + + if (player->hasFlag(PlayerFlag_IgnoreSpellCheck)) { + return true; + } + + if (!enabled) { + return false; + } + + if (isAggressive && !player->hasFlag(PlayerFlag_IgnoreProtectionZone) && player->getZone() == ZONE_PROTECTION) { + player->sendCancelMessage(RET_ACTIONNOTPERMITTEDINPROTECTIONZONE); + return false; + } + + if (player->hasCondition(CONDITION_SPELLGROUPCOOLDOWN, group) || player->hasCondition(CONDITION_SPELLCOOLDOWN, spellId)) { + player->sendCancelMessage(RET_YOUAREEXHAUSTED); + + if (isInstant()) { + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + } + + return false; + } + + if ((int32_t)player->getLevel() < level) { + player->sendCancelMessage(RET_NOTENOUGHLEVEL); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + if ((int32_t)player->getMagicLevel() < magLevel) { + player->sendCancelMessage(RET_NOTENOUGHMAGICLEVEL); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + if (player->getMana() < getManaCost(player) && !player->hasFlag(PlayerFlag_HasInfiniteMana)) { + player->sendCancelMessage(RET_NOTENOUGHMANA); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + if (player->getPlayerInfo(PLAYERINFO_SOUL) < soul && !player->hasFlag(PlayerFlag_HasInfiniteSoul)) { + player->sendCancelMessage(RET_NOTENOUGHSOUL); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + if (isInstant() && isLearnable()) { + if (!player->hasLearnedInstantSpell(getName())) { + player->sendCancelMessage(RET_YOUNEEDTOLEARNTHISSPELL); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + } else if (!vocSpellMap.empty() && vocSpellMap.find(player->getVocationId()) == vocSpellMap.end()) { + player->sendCancelMessage(RET_YOURVOCATIONCANNOTUSETHISSPELL); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + if (needWeapon) { + switch (player->getWeaponType()) { + case WEAPON_SWORD: + case WEAPON_CLUB: + case WEAPON_AXE: + break; + + default: { + player->sendCancelMessage(RET_YOUNEEDAWEAPONTOUSETHISSPELL); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + } + } + + if (isPremium() && !player->isPremium()) { + player->sendCancelMessage(RET_YOUNEEDPREMIUMACCOUNT); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + return true; +} + +bool Spell::playerInstantSpellCheck(Player* player, const Position& toPos) +{ + if (toPos.x != 0xFFFF) { + const Position& playerPos = player->getPosition(); + + if (playerPos.z > toPos.z) { + player->sendCancelMessage(RET_FIRSTGOUPSTAIRS); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } else if (playerPos.z < toPos.z) { + player->sendCancelMessage(RET_FIRSTGODOWNSTAIRS); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } else { + Tile* tile = g_game.getTile(toPos.x, toPos.y, toPos.z); + + if (!tile) { + tile = new StaticTile(toPos.x, toPos.y, toPos.z); + g_game.setTile(tile); + } + + ReturnValue ret; + + if ((ret = Combat::canDoCombat(player, tile, isAggressive)) != RET_NOERROR) { + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + if (blockingCreature && tile->getBottomVisibleCreature(player) != NULL) { + player->sendCancelMessage(RET_NOTENOUGHROOM); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + if (blockingSolid && tile->hasProperty(BLOCKSOLID)) { + player->sendCancelMessage(RET_NOTENOUGHROOM); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + } + } + + return true; +} + +bool Spell::playerRuneSpellCheck(Player* player, const Position& toPos) +{ + if (!playerSpellCheck(player)) { + return false; + } + + if (toPos.x != 0xFFFF) { + const Position& playerPos = player->getPosition(); + + if (playerPos.z > toPos.z) { + player->sendCancelMessage(RET_FIRSTGOUPSTAIRS); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } else if (playerPos.z < toPos.z) { + player->sendCancelMessage(RET_FIRSTGODOWNSTAIRS); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } else { + Tile* tile = g_game.getTile(toPos.x, toPos.y, toPos.z); + + if (!tile) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + if (range != -1) { + if (!g_game.canThrowObjectTo(playerPos, toPos, true, range, range)) { + player->sendCancelMessage(RET_DESTINATIONOUTOFREACH); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + } + + ReturnValue ret; + + if ((ret = Combat::canDoCombat(player, tile, isAggressive)) != RET_NOERROR) { + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + const Creature* topVisibleCreature = tile->getBottomVisibleCreature(player); + + if (blockingCreature && topVisibleCreature) { + player->sendCancelMessage(RET_NOTENOUGHROOM); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } else if (blockingSolid && tile->hasProperty(BLOCKSOLID)) { + player->sendCancelMessage(RET_NOTENOUGHROOM); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + if (needTarget && !topVisibleCreature) { + player->sendCancelMessage(RET_CANONLYUSETHISRUNEONCREATURES); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + if (isAggressive && needTarget && player->getSecureMode() == SECUREMODE_ON && topVisibleCreature) { + const Player* targetPlayer = topVisibleCreature->getPlayer(); + + if (targetPlayer && targetPlayer != player && player->getSkullClient(targetPlayer) == SKULL_NONE && !Combat::isInPvpZone(player, targetPlayer)) { + player->sendCancelMessage(RET_TURNSECUREMODETOATTACKUNMARKEDPLAYERS); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + } + } + } + + return true; +} + +void Spell::postCastSpell(Player* player, bool finishedCast /*= true*/, bool payCost /*= true*/) const +{ + if (finishedCast) { + if (!player->hasFlag(PlayerFlag_HasNoExhaustion)) { + if (cooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, cooldown, 0, false, spellId); + player->addCondition(condition); + } + + if (groupCooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, groupCooldown, 0, false, group); + player->addCondition(condition); + } + + if (secondaryGroupCooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, secondaryGroupCooldown, 0, false, secondaryGroup); + player->addCondition(condition); + } + } + + if (!player->hasFlag(PlayerFlag_NotGainInFight)) { + if (isAggressive) { + player->addInFightTicks(); + } + } + } + + if (payCost) { + int32_t manaCost = getManaCost(player); + int32_t soulCost = getSoulCost(); + postCastSpell(player, (uint32_t)manaCost, (uint32_t)soulCost); + } +} + +void Spell::postCastSpell(Player* player, uint32_t manaCost, uint32_t soulCost) const +{ + if (manaCost > 0) { + player->addManaSpent(manaCost); + player->changeMana(-(int32_t)manaCost); + } + + if (!player->hasFlag(PlayerFlag_HasInfiniteSoul)) { + if (soulCost > 0) { + player->changeSoul(-(int32_t)soulCost); + } + } +} + +int32_t Spell::getManaCost(const Player* player) const +{ + if (mana != 0) { + return mana; + } + + if (manaPercent != 0) { + int32_t maxMana = player->getMaxMana(); + int32_t manaCost = (maxMana * manaPercent) / 100; + return manaCost; + } + + return 0; +} + +int32_t Spell::getSoulCost() const +{ + return soul; +} + +ReturnValue Spell::CreateIllusion(Creature* creature, const Outfit_t& outfit, int32_t time) +{ + ConditionOutfit* outfitCondition = new ConditionOutfit(CONDITIONID_COMBAT, CONDITION_OUTFIT, time); + + if (!outfitCondition) { + return RET_NOTPOSSIBLE; + } + + outfitCondition->addOutfit(outfit); + creature->addCondition(outfitCondition); + + return RET_NOERROR; +} + +ReturnValue Spell::CreateIllusion(Creature* creature, const std::string& name, int32_t time) +{ + uint32_t mId = g_monsters.getIdByName(name); + + if (mId == 0) { + return RET_CREATUREDOESNOTEXIST; + } + + const MonsterType* mType = g_monsters.getMonsterType(mId); + + if (mType == NULL) { + return RET_CREATUREDOESNOTEXIST; + } + + Player* player = creature->getPlayer(); + + if (player && !player->hasFlag(PlayerFlag_CanIllusionAll)) { + if (!mType->isIllusionable) { + return RET_NOTPOSSIBLE; + } + } + + return CreateIllusion(creature, mType->outfit, time); +} + +ReturnValue Spell::CreateIllusion(Creature* creature, uint32_t itemId, int32_t time) +{ + const ItemType& it = Item::items[itemId]; + + if (it.id == 0) { + return RET_NOTPOSSIBLE; + } + + Outfit_t outfit; + outfit.lookTypeEx = itemId; + + return CreateIllusion(creature, outfit, time); +} + +InstantSpell::InstantSpell(LuaScriptInterface* _interface) : + TalkAction(_interface) +{ + needDirection = false; + hasParam = false; + hasPlayerNameParam = false; + checkLineOfSight = true; + casterTargetOrDirection = false; + function = NULL; +} + +InstantSpell::~InstantSpell() +{ + // +} + +std::string InstantSpell::getScriptEventName() +{ + return "onCastSpell"; +} + +bool InstantSpell::configureEvent(xmlNodePtr p) +{ + if (!Spell::configureSpell(p)) { + return false; + } + + if (!TalkAction::configureEvent(p)) { + return false; + } + + int32_t intValue; + + if (readXMLInteger(p, "params", intValue)) { + if (intValue == 1) { + hasParam = true; + } + } + + if (readXMLInteger(p, "playernameparam", intValue)) { + hasPlayerNameParam = (intValue > 0); + } + + if (readXMLInteger(p, "direction", intValue)) { + needDirection = (intValue == 1); + } else if (readXMLInteger(p, "casterTargetOrDirection", intValue)) { + casterTargetOrDirection = (intValue == 1); + } + + if (readXMLInteger(p, "blockwalls", intValue)) { + checkLineOfSight = (intValue == 1); + } + + return true; +} + +bool InstantSpell::loadFunction(const std::string& functionName) +{ + std::string tmpFunctionName = asLowerCaseString(functionName); + + if (tmpFunctionName == "edithouseguest") { + isAggressive = false; + function = HouseGuestList; + } else if (tmpFunctionName == "edithousesubowner") { + isAggressive = false; + function = HouseSubOwnerList; + } else if (tmpFunctionName == "edithousedoor") { + isAggressive = false; + function = HouseDoorList; + } else if (tmpFunctionName == "housekick") { + isAggressive = false; + function = HouseKick; + } else if (tmpFunctionName == "searchplayer") { + isAggressive = false; + function = SearchPlayer; + } else if (tmpFunctionName == "levitate") { + isAggressive = false; + function = Levitate; + } else if (tmpFunctionName == "illusion") { + isAggressive = false; + function = Illusion; + } else if (tmpFunctionName == "summonmonster") { + function = SummonMonster; + } else { + std::cout << "[Warning - InstantSpell::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl; + return false; + } + + m_scripted = false; + return true; +} + +bool InstantSpell::playerCastInstant(Player* player, std::string& param) +{ + if (!playerSpellCheck(player)) { + return false; + } + + LuaVariant var; + + if (selfTarget) { + var.type = VARIANT_NUMBER; + var.number = player->getID(); + } else if (needTarget || casterTargetOrDirection) { + Creature* target = NULL; + bool useDirection = false; + + if (hasParam) { + Player* playerTarget = NULL; + ReturnValue ret = g_game.getPlayerByNameWildcard(param, playerTarget); + + if (playerTarget && playerTarget->isAccessPlayer() && !player->isAccessPlayer()) { + playerTarget = NULL; + } + + target = playerTarget; + + if (!target || target->getHealth() <= 0) { + if (!casterTargetOrDirection) { + if (cooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, cooldown, 0, false, spellId); + player->addCondition(condition); + } + + if (groupCooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, groupCooldown, 0, false, group); + player->addCondition(condition); + } + + if (secondaryGroupCooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, secondaryGroupCooldown, 0, false, secondaryGroup); + player->addCondition(condition); + } + + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + useDirection = true; + } + + param = playerTarget->getName(); + } else { + target = player->getAttackedCreature(); + + if (!target || target->getHealth() <= 0) { + if (!casterTargetOrDirection) { + player->sendCancelMessage(RET_YOUCANONLYUSEITONCREATURES); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + useDirection = true; + } + } + + if (!useDirection) { + if (!canThrowSpell(player, target)) { + player->sendCancelMessage(RET_CREATUREISNOTREACHABLE); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + var.type = VARIANT_NUMBER; + var.number = target->getID(); + } else { + var.type = VARIANT_POSITION; + var.pos = Spells::getCasterPosition(player, player->getDirection()); + + if (!playerInstantSpellCheck(player, var.pos)) { + return false; + } + } + } else if (hasParam) { + var.type = VARIANT_STRING; + + if (getHasPlayerNameParam()) { + Player* playerTarget = NULL; + ReturnValue ret = g_game.getPlayerByNameWildcard(param, playerTarget); + + if (ret != RET_NOERROR) { + if (cooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, cooldown, 0, false, spellId); + player->addCondition(condition); + } + + if (groupCooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, groupCooldown, 0, false, group); + player->addCondition(condition); + } + + if (secondaryGroupCooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, secondaryGroupCooldown, 0, false, secondaryGroup); + player->addCondition(condition); + } + + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + if (playerTarget && (!playerTarget->isAccessPlayer() || player->isAccessPlayer())) { + param = playerTarget->getName(); + } + } + + var.text = param; + } else { + var.type = VARIANT_POSITION; + + if (needDirection) { + var.pos = Spells::getCasterPosition(player, player->getDirection()); + } else { + var.pos = player->getPosition(); + } + + if (!playerInstantSpellCheck(player, var.pos)) { + return false; + } + } + + bool result = internalCastSpell(player, var); + + if (result) { + Spell::postCastSpell(player); + } + + return result; +} + +bool InstantSpell::canThrowSpell(const Creature* creature, const Creature* target) const +{ + const Position& fromPos = creature->getPosition(); + const Position& toPos = target->getPosition(); + + if (fromPos.z != toPos.z || + (range == -1 && !g_game.canThrowObjectTo(fromPos, toPos, checkLineOfSight)) || + (range != -1 && !g_game.canThrowObjectTo(fromPos, toPos, checkLineOfSight, range, range))) { + return false; + } + + return true; +} + +bool InstantSpell::castSpell(Creature* creature) +{ + LuaVariant var; + + if (casterTargetOrDirection) { + Creature* target = creature->getAttackedCreature(); + + if (target && target->getHealth() > 0) { + if (!canThrowSpell(creature, target)) { + return false; + } + + var.type = VARIANT_NUMBER; + var.number = target->getID(); + return internalCastSpell(creature, var); + } + + return false; + } else if (needDirection) { + var.type = VARIANT_POSITION; + var.pos = Spells::getCasterPosition(creature, creature->getDirection()); + } else { + var.type = VARIANT_POSITION; + var.pos = creature->getPosition(); + } + + return internalCastSpell(creature, var); +} + +bool InstantSpell::castSpell(Creature* creature, Creature* target) +{ + if (needTarget) { + LuaVariant var; + var.type = VARIANT_NUMBER; + var.number = target->getID(); + return internalCastSpell(creature, var); + } else { + return castSpell(creature); + } +} + +bool InstantSpell::internalCastSpell(Creature* creature, const LuaVariant& var) +{ + if (m_scripted) { + return executeCastSpell(creature, var); + } else if (function) { + return function(this, creature, var.text); + } + + return false; +} + +bool InstantSpell::executeCastSpell(Creature* creature, const LuaVariant& var) +{ + //onCastSpell(cid, var) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + env->setScriptId(m_scriptId, m_scriptInterface); + env->setRealPos(creature->getPosition()); + + lua_State* L = m_scriptInterface->getLuaState(); + + uint32_t cid = env->addThing(creature); + + m_scriptInterface->pushFunction(m_scriptId); + lua_pushnumber(L, cid); + m_scriptInterface->pushVariant(L, var); + + bool result = m_scriptInterface->callFunction(2); + m_scriptInterface->releaseScriptEnv(); + + return result; + } else { + std::cout << "[Error - InstantSpell::executeCastSpell] Call stack overflow." << std::endl; + return false; + } +} + +House* InstantSpell::getHouseFromPos(Creature* creature) +{ + if (creature) { + Player* player = creature->getPlayer(); + + if (player) { + HouseTile* houseTile = dynamic_cast(player->getTile()); + + if (houseTile) { + House* house = houseTile->getHouse(); + + if (house) { + return house; + } + } + } + } + + return NULL; +} + +bool InstantSpell::HouseGuestList(const InstantSpell* spell, Creature* creature, const std::string& param) +{ + House* house = getHouseFromPos(creature); + + if (!house) { + return false; + } + + Player* player = creature->getPlayer(); + + if (house->canEditAccessList(GUEST_LIST, player)) { + player->setEditHouse(house, GUEST_LIST); + player->sendHouseWindow(house, GUEST_LIST); + } else { + player->sendCancelMessage(RET_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + } + + return true; +} + +bool InstantSpell::HouseSubOwnerList(const InstantSpell* spell, Creature* creature, const std::string& param) +{ + House* house = getHouseFromPos(creature); + + if (!house) { + return false; + } + + Player* player = creature->getPlayer(); + + if (house->canEditAccessList(SUBOWNER_LIST, player)) { + player->setEditHouse(house, SUBOWNER_LIST); + player->sendHouseWindow(house, SUBOWNER_LIST); + } else { + player->sendCancelMessage(RET_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + } + + return true; +} + +bool InstantSpell::HouseDoorList(const InstantSpell* spell, Creature* creature, const std::string& param) +{ + House* house = getHouseFromPos(creature); + + if (!house) { + return false; + } + + Player* player = creature->getPlayer(); + Position pos = Spells::getCasterPosition(player, player->getDirection()); + Door* door = house->getDoorByPosition(pos); + + if (door && house->canEditAccessList(door->getDoorId(), player)) { + player->setEditHouse(house, door->getDoorId()); + player->sendHouseWindow(house, door->getDoorId()); + return true; + } else { + player->sendCancelMessage(RET_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + } + + return true; +} + +bool InstantSpell::HouseKick(const InstantSpell* spell, Creature* creature, const std::string& param) +{ + Player* player = creature->getPlayer(); + Player* targetPlayer = g_game.getPlayerByName(param); + + if (!targetPlayer) { + targetPlayer = player; + } + + House* house = getHouseFromPos(targetPlayer); + + if (!house) { + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + player->sendCancelMessage(RET_NOTPOSSIBLE); + return false; + } + + if (!house->kickPlayer(player, targetPlayer->getName())) { + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + player->sendCancelMessage(RET_NOTPOSSIBLE); + return false; + } + + return true; +} + +bool InstantSpell::SearchPlayer(const InstantSpell* spell, Creature* creature, const std::string& param) +{ + //a. From 1 to 4 sq's [Person] is standing next to you. + //b. From 5 to 100 sq's [Person] is to the south, north, east, west. + //c. From 101 to 274 sq's [Person] is far to the south, north, east, west. + //d. From 275 to infinite sq's [Person] is very far to the south, north, east, west. + //e. South-west, s-e, n-w, n-e (corner coordinates): this phrase appears if the player you're looking for has moved five squares in any direction from the south, north, east or west. + //f. Lower level to the (direction): this phrase applies if the person you're looking for is from 1-25 squares up/down the actual floor you're in. + //g. Higher level to the (direction): this phrase applies if the person you're looking for is from 1-25 squares up/down the actual floor you're in. + + Player* player = creature->getPlayer(); + + if (!player) { + return false; + } + + enum distance_t { + DISTANCE_BESIDE, + DISTANCE_CLOSE, + DISTANCE_FAR, + DISTANCE_VERYFAR + }; + + enum direction_t { + DIR_N, DIR_S, DIR_E, DIR_W, + DIR_NE, DIR_NW, DIR_SE, DIR_SW + }; + + enum level_t { + LEVEL_HIGHER, + LEVEL_LOWER, + LEVEL_SAME + }; + + Player* playerExiva = g_game.getPlayerByName(param); + + if (!playerExiva) { + return false; + } + + if (playerExiva->isAccessPlayer() && !player->isAccessPlayer()) { + player->sendCancelMessage(RET_PLAYERWITHTHISNAMEISNOTONLINE); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + const Position lookPos = player->getPosition(); + + const Position searchPos = playerExiva->getPosition(); + + int32_t dx = lookPos.x - searchPos.x; + + int32_t dy = lookPos.y - searchPos.y; + + int32_t dz = lookPos.z - searchPos.z; + + distance_t distance; + + direction_t direction; + + level_t level; + + //getting floor + if (dz > 0) { + level = LEVEL_HIGHER; + } else if (dz < 0) { + level = LEVEL_LOWER; + } else { + level = LEVEL_SAME; + } + + //getting distance + if (std::abs(dx) < 4 && std::abs(dy) < 4) { + distance = DISTANCE_BESIDE; + } else { + int32_t distance2 = dx * dx + dy * dy; + + if (distance2 < 10000) { + distance = DISTANCE_CLOSE; + } else if (distance2 < 75076) { + distance = DISTANCE_FAR; + } else { + distance = DISTANCE_VERYFAR; + } + } + + //getting direction + float tan; + + if (dx != 0) { + tan = (float)dy / (float)dx; + } else { + tan = 10.; + } + + if (std::abs(tan) < 0.4142) { + if (dx > 0) { + direction = DIR_W; + } else { + direction = DIR_E; + } + } else if (std::abs(tan) < 2.4142) { + if (tan > 0) { + if (dy > 0) { + direction = DIR_NW; + } else { + direction = DIR_SE; + } + } else { + if (dx > 0) { + direction = DIR_SW; + } else { + direction = DIR_NE; + } + } + } else { + if (dy > 0) { + direction = DIR_N; + } else { + direction = DIR_S; + } + } + + std::ostringstream ss; + ss << playerExiva->getName() << " "; + + if (distance == DISTANCE_BESIDE) { + if (level == LEVEL_SAME) { + ss << "is standing next to you"; + } else if (level == LEVEL_HIGHER) { + ss << "is above you"; + } else if (level == LEVEL_LOWER) { + ss << "is below you"; + } + } else { + switch (distance) { + case DISTANCE_CLOSE: + + if (level == LEVEL_SAME) { + ss << "is to the"; + } else if (level == LEVEL_HIGHER) { + ss << "is on a higher level to the"; + } else if (level == LEVEL_LOWER) { + ss << "is on a lower level to the"; + } + + break; + case DISTANCE_FAR: + ss << "is far to the"; + break; + case DISTANCE_VERYFAR: + ss << "is very far to the"; + break; + default: + break; + } + + ss << " "; + + switch (direction) { + case DIR_N: + ss << "north"; + break; + case DIR_S: + ss << "south"; + break; + case DIR_E: + ss << "east"; + break; + case DIR_W: + ss << "west"; + break; + case DIR_NE: + ss << "north-east"; + break; + case DIR_NW: + ss << "north-west"; + break; + case DIR_SE: + ss << "south-east"; + break; + case DIR_SW: + ss << "south-west"; + break; + } + } + + ss << "."; + player->sendTextMessage(MSG_INFO_DESCR, ss.str().c_str()); + g_game.addMagicEffect(player->getPosition(), NM_ME_MAGIC_ENERGY); + return true; +} + +bool InstantSpell::SummonMonster(const InstantSpell* spell, Creature* creature, const std::string& param) +{ + Player* player = creature->getPlayer(); + + if (!player) { + return false; + } + + MonsterType* mType = g_monsters.getMonsterType(param); + + if (!mType) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + int32_t manaCost = mType->manaCost; + + if (!player->hasFlag(PlayerFlag_CanSummonAll)) { + if (!mType->isSummonable) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + if (player->getMana() < manaCost) { + player->sendCancelMessage(RET_NOTENOUGHMANA); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + if (player->getSummonCount() >= 2) { + player->sendCancel("You cannot summon more creatures."); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + } + + ReturnValue ret = RET_NOERROR; + Monster* monster = Monster::createMonster(param); + + if (monster) { + // Place the monster + creature->addSummon(monster); + + if (!g_game.placeCreature(monster, creature->getPosition(), true)) { + creature->removeSummon(monster); + ret = RET_NOTENOUGHROOM; + } + } else { + ret = RET_NOTPOSSIBLE; + } + + if (ret == RET_NOERROR) { + spell->postCastSpell(player, (uint32_t)manaCost, (uint32_t)spell->getSoulCost()); + g_game.addMagicEffect(player->getPosition(), NM_ME_MAGIC_ENERGY); + g_game.addMagicEffect(monster->getPosition(), NM_ME_TELEPORT); + } else { + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + } + + return (ret == RET_NOERROR); +} + +bool InstantSpell::Levitate(const InstantSpell* spell, Creature* creature, const std::string& param) +{ + Player* player = creature->getPlayer(); + + if (!player) { + return false; + } + + const Position& currentPos = creature->getPosition(); + + const Position& destPos = Spells::getCasterPosition(creature, creature->getDirection()); + + ReturnValue ret = RET_NOTPOSSIBLE; + + std::string tmpParam = asLowerCaseString(param); + + if (tmpParam == "up") { + if (currentPos.z != 8) { + Tile* tmpTile = g_game.getTile(currentPos.x, currentPos.y, currentPos.z - 1); + + if (tmpTile == NULL || (tmpTile->ground == NULL && !tmpTile->hasProperty(IMMOVABLEBLOCKSOLID))) { + tmpTile = g_game.getTile(destPos.x, destPos.y, destPos.z - 1); + + if (tmpTile && tmpTile->ground && !tmpTile->hasProperty(IMMOVABLEBLOCKSOLID) && !tmpTile->floorChange()) { + ret = g_game.internalMoveCreature(player, player->getTile(), tmpTile, FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE); + } + } + } + } else if (tmpParam == "down") { + if (currentPos.z != 7) { + Tile* tmpTile = g_game.getTile(destPos.x, destPos.y, destPos.z); + + if (tmpTile == NULL || (tmpTile->ground == NULL && !tmpTile->hasProperty(BLOCKSOLID))) { + tmpTile = g_game.getTile(destPos.x, destPos.y, destPos.z + 1); + + if (tmpTile && tmpTile->ground && !tmpTile->hasProperty(IMMOVABLEBLOCKSOLID) && !tmpTile->floorChange()) { + ret = g_game.internalMoveCreature(player, player->getTile(), tmpTile, FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE); + } + } + } + } + + if (ret == RET_NOERROR) { + g_game.addMagicEffect(player->getPosition(), NM_ME_TELEPORT); + } else { + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + } + + return (ret == RET_NOERROR); +} + +bool InstantSpell::Illusion(const InstantSpell* spell, Creature* creature, const std::string& param) +{ + Player* player = creature->getPlayer(); + + if (!player) { + return false; + } + + ReturnValue ret = CreateIllusion(creature, param, 180000); + + if (ret == RET_NOERROR) { + g_game.addMagicEffect(player->getPosition(), NM_ME_MAGIC_BLOOD); + } else { + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + } + + return (ret == RET_NOERROR); +} + +bool InstantSpell::canCast(const Player* player) const +{ + if (player->hasFlag(PlayerFlag_CannotUseSpells)) { + return false; + } + + if (player->hasFlag(PlayerFlag_IgnoreSpellCheck)) { + return true; + } + + if (isLearnable()) { + if (player->hasLearnedInstantSpell(getName())) { + return true; + } + } else { + if (vocSpellMap.empty() || vocSpellMap.find(player->getVocationId()) != vocSpellMap.end()) { + return true; + } + } + + return false; +} + + +ConjureSpell::ConjureSpell(LuaScriptInterface* _interface) : + InstantSpell(_interface) +{ + isAggressive = false; + conjureId = 0; + conjureCount = 1; + conjureReagentId = 0; +} + +ConjureSpell::~ConjureSpell() +{ + // +} + +std::string ConjureSpell::getScriptEventName() +{ + return "onCastSpell"; +} + +bool ConjureSpell::configureEvent(xmlNodePtr p) +{ + if (!InstantSpell::configureEvent(p)) { + return false; + } + + int32_t intValue; + + if (readXMLInteger(p, "conjureId", intValue)) { + conjureId = intValue; + } + + if (readXMLInteger(p, "conjureCount", intValue)) { + conjureCount = intValue; + } else if (conjureId != 0) { + //load the default charge from items.xml + const ItemType& it = Item::items[conjureId]; + + if (it.charges != 0) { + conjureCount = it.charges; + } + } + + if (readXMLInteger(p, "reagentId", intValue)) { + conjureReagentId = intValue; + } + + return true; +} + +bool ConjureSpell::loadFunction(const std::string& functionName) +{ + std::string tmpFunctionName = asLowerCaseString(functionName); + + if (tmpFunctionName == "conjureitem" || tmpFunctionName == "conjurerune") { + function = ConjureItem; + } else if (tmpFunctionName == "conjurefood") { + function = ConjureFood; + } else { + std::cout << "[Warning - ConjureSpell::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl; + return false; + } + + m_scripted = false; + return true; +} + +ReturnValue ConjureSpell::internalConjureItem(Player* player, uint32_t conjureId, uint32_t conjureCount) +{ + Item* newItem = Item::CreateItem(conjureId, conjureCount); + + if (!newItem) { + return RET_NOTPOSSIBLE; + } + + ReturnValue result = g_game.internalPlayerAddItem(player, newItem); + + if (result != RET_NOERROR) { + delete newItem; + } + + return result; +} + +ReturnValue ConjureSpell::internalConjureItem(Player* player, uint32_t conjureId, + uint32_t conjureCount, uint32_t reagentId, slots_t slot, bool test /*= false*/) +{ + if (reagentId != 0) { + Item* item = player->getInventoryItem(slot); + + if (item && item->getID() == reagentId) { + if (item->isStackable() && item->getItemCount() != 1) { + return RET_YOUNEEDTOSPLITYOURSPEARS; + } + + if (test) { + return RET_NOERROR; + } + + Item* newItem = g_game.transformItem(item, conjureId, conjureCount); + + if (newItem) { + g_game.startDecay(newItem); + } + + return RET_NOERROR; + } + } + + return RET_YOUNEEDAMAGICITEMTOCASTSPELL; +} + +bool ConjureSpell::ConjureItem(const ConjureSpell* spell, Creature* creature, const std::string& param) +{ + Player* player = creature->getPlayer(); + + if (!player) { + return false; + } + + ReturnValue result = RET_NOERROR; + + if (spell->getReagentId() != 0) { + if (!g_game.removeItemOfType(player, spell->getReagentId(), 1, -1)) { + player->sendCancelMessage(RET_YOUNEEDAMAGICITEMTOCASTSPELL); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + Item* newItem = Item::CreateItem(spell->getConjureId(), spell->getConjureCount()); + + if (!newItem) { + return false; + } + + ReturnValue ret = g_game.internalPlayerAddItem(player, newItem); + + if (ret != RET_NOERROR) { + delete newItem; + return false; + } + + spell->postCastSpell(player); + g_game.addMagicEffect(player->getPosition(), NM_ME_MAGIC_BLOOD); + return true; + } else { + if (internalConjureItem(player, spell->getConjureId(), spell->getConjureCount()) == RET_NOERROR) { + spell->postCastSpell(player); + g_game.addMagicEffect(player->getPosition(), NM_ME_MAGIC_BLOOD); + return true; + } + } + + player->sendCancelMessage(result); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; +} + +bool ConjureSpell::ConjureFood(const ConjureSpell* spell, Creature* creature, const std::string& param) +{ + Player* player = creature->getPlayer(); + + if (!player) { + return false; + } + + uint32_t foodType[8] = { + ITEM_MEAT, + ITEM_HAM, + ITEM_GRAPE, + ITEM_APPLE, + ITEM_BREAD, + ITEM_CHEESE, + ITEM_ROLL, + ITEM_BREAD + }; + + bool result = (internalConjureItem(player, foodType[random_range(0, 7)], 1) == RET_NOERROR); + + if (result) { + spell->postCastSpell(player); + g_game.addMagicEffect(player->getPosition(), NM_ME_MAGIC_POISON); + } + + return result; +} + +bool ConjureSpell::playerCastInstant(Player* player, std::string& param) +{ + if (!playerSpellCheck(player)) { + return false; + } + + if (m_scripted) { + LuaVariant var; + var.type = VARIANT_STRING; + var.text = param; + return executeCastSpell(player, var); + } else if (function) { + return function(this, player, param); + } + + return false; +} + +RuneSpell::RuneSpell(LuaScriptInterface* _interface) : + Action(_interface) +{ + hasCharges = true; + runeId = 0; + function = NULL; + + allowFarUse = true; +} + +RuneSpell::~RuneSpell() +{ + // +} + +std::string RuneSpell::getScriptEventName() +{ + return "onCastSpell"; +} + +bool RuneSpell::configureEvent(xmlNodePtr p) +{ + if (!Spell::configureSpell(p)) { + return false; + } + + if (!Action::configureEvent(p)) { + return false; + } + + int32_t intValue; + + if (readXMLInteger(p, "id", intValue)) { + runeId = intValue; + } else { + std::cout << "[Error - RuneSpell::configureSpell] Rune spell without id." << std::endl; + return false; + } + + uint32_t charges = 0; + + if (readXMLInteger(p, "charges", intValue)) { + charges = (uint32_t)intValue; + } + + hasCharges = (charges > 0); + + if (magLevel != 0 || level != 0) { + //Change information in the ItemType to get accurate description + ItemType& iType = Item::items.getItemType(runeId); + iType.runeMagLevel = magLevel; + iType.runeLevel = level; + iType.charges = charges; + } + + return true; +} + +bool RuneSpell::loadFunction(const std::string& functionName) +{ + std::string tmpFunctionName = asLowerCaseString(functionName); + + if (tmpFunctionName == "chameleon") { + function = Illusion; + } else if (tmpFunctionName == "convince") { + function = Convince; + } else { + std::cout << "[Warning - RuneSpell::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl; + return false; + } + + m_scripted = false; + return true; +} + +bool RuneSpell::Illusion(const RuneSpell* spell, Creature* creature, Item* item, + const Position& posFrom, const Position& posTo) +{ + Player* player = creature->getPlayer(); + + if (!player) { + return false; + } + + Thing* thing = g_game.internalGetThing(player, posTo, 0, 0, STACKPOS_MOVE); + + if (!thing) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + Item* illusionItem = thing->getItem(); + + if (!illusionItem || illusionItem->isNotMoveable()) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + ReturnValue ret = CreateIllusion(creature, illusionItem->getID(), 200000); + + if (ret != RET_NOERROR) { + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + g_game.addMagicEffect(player->getPosition(), NM_ME_MAGIC_BLOOD); + return true; +} + +bool RuneSpell::Convince(const RuneSpell* spell, Creature* creature, Item* item, const Position& posFrom, const Position& posTo) +{ + Player* player = creature->getPlayer(); + + if (!player) { + return false; + } + + if (!player->hasFlag(PlayerFlag_CanConvinceAll)) { + if (player->getSummonCount() >= 2) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + } + + Thing* thing = g_game.internalGetThing(player, posTo, 0, 0, STACKPOS_LOOK); + + if (!thing) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + Creature* convinceCreature = thing->getCreature(); + + if (!convinceCreature) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + int32_t manaCost = 0; + + if (convinceCreature->getMonster()) { + manaCost = convinceCreature->getMonster()->getManaCost(); + } + + if (!player->hasFlag(PlayerFlag_HasInfiniteMana) && player->getMana() < manaCost) { + player->sendCancelMessage(RET_NOTENOUGHMANA); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + if (!convinceCreature->convinceCreature(creature)) { + player->sendCancelMessage(RET_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), NM_ME_POFF); + return false; + } + + spell->postCastSpell(player, (uint32_t)manaCost, (uint32_t)spell->getSoulCost()); + g_game.updateCreatureType(convinceCreature); + g_game.addMagicEffect(player->getPosition(), NM_ME_MAGIC_BLOOD); + return true; +} + +ReturnValue RuneSpell::canExecuteAction(const Player* player, const Position& toPos) +{ + if (player->hasFlag(PlayerFlag_CannotUseSpells)) { + return RET_CANNOTUSETHISOBJECT; + } + + ReturnValue ret = Action::canExecuteAction(player, toPos); + + if (ret != RET_NOERROR) { + return ret; + } + + if (toPos.x == 0xFFFF) { + if (needTarget) { + return RET_CANONLYUSETHISRUNEONCREATURES; + } else if (!selfTarget) { + return RET_NOTENOUGHROOM; + } + } + + return RET_NOERROR; +} + +bool RuneSpell::executeUse(Player* player, Item* item, const PositionEx& posFrom, + const PositionEx& posTo, bool extendedUse, uint32_t creatureId) +{ + if (!playerRuneSpellCheck(player, posTo)) { + return false; + } + + bool result = false; + + if (m_scripted) { + LuaVariant var; + + if (needTarget) { + if (creatureId == 0) { + Tile* tileTo = g_game.getTile(posTo); + + if (tileTo) { + const Creature* creature = tileTo->getBottomVisibleCreature(player); + + if (creature) { + creatureId = creature->getID(); + } + } + } + + var.type = VARIANT_NUMBER; + var.number = creatureId; + } else { + var.type = VARIANT_POSITION; + var.pos = posTo; + } + + result = internalCastSpell(player, var); + } else { + if (function) { + result = function(this, player, item, posFrom, posTo); + } + } + + if (result) { + Spell::postCastSpell(player); + + if (hasCharges && item && g_config.getBoolean(ConfigManager::REMOVE_RUNE_CHARGES)) { + int32_t newCount = std::max(0, item->getItemCount() - 1); + g_game.transformItem(item, item->getID(), newCount); + } + } + + return result; +} + +bool RuneSpell::castSpell(Creature* creature) +{ + LuaVariant var; + var.type = VARIANT_NUMBER; + var.number = creature->getID(); + + return internalCastSpell(creature, var); +} + +bool RuneSpell::castSpell(Creature* creature, Creature* target) +{ + LuaVariant var; + var.type = VARIANT_NUMBER; + var.number = target->getID(); + + return internalCastSpell(creature, var); +} + +bool RuneSpell::internalCastSpell(Creature* creature, const LuaVariant& var) +{ + bool result = false; + + if (m_scripted) { + result = executeCastSpell(creature, var); + } else { + result = false; + } + + return result; +} + +bool RuneSpell::executeCastSpell(Creature* creature, const LuaVariant& var) +{ + //onCastSpell(cid, var) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + env->setScriptId(m_scriptId, m_scriptInterface); + env->setRealPos(creature->getPosition()); + + lua_State* L = m_scriptInterface->getLuaState(); + + uint32_t cid = env->addThing(creature); + + m_scriptInterface->pushFunction(m_scriptId); + lua_pushnumber(L, cid); + m_scriptInterface->pushVariant(L, var); + + bool result = m_scriptInterface->callFunction(2); + m_scriptInterface->releaseScriptEnv(); + + return result; + } else { + std::cout << "[Error - RuneSpell::executeCastSpell] Call stack overflow." << std::endl; + return false; + } +} diff --git a/src/spells.h b/src/spells.h new file mode 100644 index 0000000000..6799087b6b --- /dev/null +++ b/src/spells.h @@ -0,0 +1,348 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_SPELLS_H__ +#define __OTSERV_SPELLS_H__ + +#include "game.h" +#include "luascript.h" +#include "player.h" +#include "actions.h" +#include "talkaction.h" +#include "baseevents.h" + +class InstantSpell; +class ConjureSpell; +class RuneSpell; +class Spell; + +typedef std::map RunesMap; +typedef std::map InstantsMap; +typedef std::map VocSpellMap; + +class Spells : public BaseEvents +{ + public: + Spells(); + virtual ~Spells(); + + Spell* getSpellByName(const std::string& name); + RuneSpell* getRuneSpell(uint32_t id); + RuneSpell* getRuneSpellByName(const std::string& name); + + InstantSpell* getInstantSpell(const std::string& words); + InstantSpell* getInstantSpellByName(const std::string& name); + + uint32_t getInstantSpellCount(const Player* player); + InstantSpell* getInstantSpellByIndex(const Player* player, uint32_t index); + + TalkActionResult_t playerSaySpell(Player* player, SpeakClasses type, std::string& words); + + static Position getCasterPosition(Creature* creature, Direction dir); + virtual std::string getScriptBaseName(); + + const InstantsMap& getInstantsMap() const { + return instants; + } + + protected: + virtual void clear(); + virtual LuaScriptInterface& getScriptInterface(); + virtual Event* getEvent(const std::string& nodeName); + virtual bool registerEvent(Event* event, xmlNodePtr p); + + RunesMap runes; + InstantsMap instants; + + friend class CombatSpell; + LuaScriptInterface m_scriptInterface; +}; + +typedef bool (InstantSpellFunction)(const InstantSpell* spell, Creature* creature, const std::string& param); +typedef bool (ConjureSpellFunction)(const ConjureSpell* spell, Creature* creature, const std::string& param); +typedef bool (RuneSpellFunction)(const RuneSpell* spell, Creature* creature, Item* item, const Position& posFrom, const Position& posTo); + +class BaseSpell +{ + public: + BaseSpell() {} + virtual ~BaseSpell() {} + + virtual bool castSpell(Creature* creature) = 0; + virtual bool castSpell(Creature* creature, Creature* target) = 0; +}; + +class CombatSpell : public Event, public BaseSpell +{ + public: + CombatSpell(Combat* _combat, bool _needTarget, bool _needDirection); + virtual ~CombatSpell(); + + virtual bool castSpell(Creature* creature); + virtual bool castSpell(Creature* creature, Creature* target); + virtual bool configureEvent(xmlNodePtr p) { + return true; + } + + //scripting + bool executeCastSpell(Creature* creature, const LuaVariant& var); + + bool loadScriptCombat(); + Combat* getCombat() { + return combat; + } + + protected: + virtual std::string getScriptEventName() { + return "onCastSpell"; + } + + bool needDirection; + bool needTarget; + Combat* combat; +}; + +class Spell : public BaseSpell +{ + public: + Spell(); + virtual ~Spell() {} + + bool configureSpell(xmlNodePtr xmlspell); + const std::string& getName() const { + return name; + } + + void postCastSpell(Player* player, bool finishedSpell = true, bool payCost = true) const; + void postCastSpell(Player* player, uint32_t manaCost, uint32_t soulCost) const; + + uint8_t getSpellId() const { + return spellId; + } + + int32_t getManaCost(const Player* player) const; + int32_t getSoulCost() const; + uint32_t getLevel() const { + return level; + } + int32_t getMagicLevel() const { + return magLevel; + } + int32_t getMana() const { + return mana; + } + int32_t getManaPercent() const { + return manaPercent; + } + bool isPremium() const { + return premium; + } + + virtual bool isInstant() const = 0; + bool isLearnable() const { + return learnable; + } + + static ReturnValue CreateIllusion(Creature* creature, const Outfit_t& outfit, int32_t time); + static ReturnValue CreateIllusion(Creature* creature, const std::string& name, int32_t time); + static ReturnValue CreateIllusion(Creature* creature, uint32_t itemId, int32_t time); + + const VocSpellMap& getVocMap() const { + return vocSpellMap; + } + + protected: + bool playerSpellCheck(Player* player, bool ignoreExhaust = false) const; + bool playerInstantSpellCheck(Player* player, const Position& toPos); + bool playerRuneSpellCheck(Player* player, const Position& toPos); + + uint8_t spellId; + SpellGroup_t group; + uint32_t groupCooldown; + SpellGroup_t secondaryGroup; + uint32_t secondaryGroupCooldown; + + bool learnable; + bool enabled; + bool premium; + int32_t level; + int32_t magLevel; + + int32_t mana; + int32_t manaPercent; + int32_t soul; + int32_t range; + uint32_t cooldown; + bool needTarget; + bool needWeapon; + bool selfTarget; + bool blockingSolid; + bool blockingCreature; + bool isAggressive; + + VocSpellMap vocSpellMap; + + private: + std::string name; +}; + +class InstantSpell : public TalkAction, public Spell +{ + public: + InstantSpell(LuaScriptInterface* _interface); + virtual ~InstantSpell(); + + virtual bool configureEvent(xmlNodePtr p); + virtual bool loadFunction(const std::string& functionName); + + virtual bool playerCastInstant(Player* player, std::string& param); + + virtual bool castSpell(Creature* creature); + virtual bool castSpell(Creature* creature, Creature* target); + + //scripting + bool executeCastSpell(Creature* creature, const LuaVariant& var); + + virtual bool isInstant() const { + return true; + } + bool getHasParam() const { + return hasParam; + } + bool getHasPlayerNameParam() const { + return hasPlayerNameParam; + } + bool canCast(const Player* player) const; + bool canThrowSpell(const Creature* creature, const Creature* target) const; + + protected: + virtual std::string getScriptEventName(); + + static InstantSpellFunction HouseGuestList; + static InstantSpellFunction HouseSubOwnerList; + static InstantSpellFunction HouseDoorList; + static InstantSpellFunction HouseKick; + static InstantSpellFunction SearchPlayer; + static InstantSpellFunction SummonMonster; + static InstantSpellFunction Levitate; + static InstantSpellFunction Illusion; + + static House* getHouseFromPos(Creature* creature); + + bool internalCastSpell(Creature* creature, const LuaVariant& var); + + bool needDirection; + bool hasParam; + bool hasPlayerNameParam; + bool checkLineOfSight; + bool casterTargetOrDirection; + InstantSpellFunction* function; +}; + +class ConjureSpell : public InstantSpell +{ + public: + ConjureSpell(LuaScriptInterface* _interface); + virtual ~ConjureSpell(); + + virtual bool configureEvent(xmlNodePtr p); + virtual bool loadFunction(const std::string& functionName); + + virtual bool playerCastInstant(Player* player, std::string& param); + + virtual bool castSpell(Creature* creature) { + return false; + } + virtual bool castSpell(Creature* creature, Creature* target) { + return false; + } + + uint32_t getConjureId() const { + return conjureId; + } + uint32_t getConjureCount() const { + return conjureCount; + } + uint32_t getReagentId() const { + return conjureReagentId; + } + + protected: + virtual std::string getScriptEventName(); + + static ReturnValue internalConjureItem(Player* player, uint32_t conjureId, uint32_t conjureCount); + static ReturnValue internalConjureItem(Player* player, uint32_t conjureId, uint32_t conjureCount, uint32_t reagentId, slots_t slot, bool test = false); + + static ConjureSpellFunction ConjureItem; + static ConjureSpellFunction ConjureFood; + + bool internalCastSpell(Creature* creature, const LuaVariant& var); + Position getCasterPosition(Creature* creature); + + ConjureSpellFunction* function; + + uint32_t conjureId; + uint32_t conjureCount; + uint32_t conjureReagentId; +}; + +class RuneSpell : public Action, public Spell +{ + public: + RuneSpell(LuaScriptInterface* _interface); + virtual ~RuneSpell(); + + virtual bool configureEvent(xmlNodePtr p); + virtual bool loadFunction(const std::string& functionName); + + virtual ReturnValue canExecuteAction(const Player* player, const Position& toPos); + virtual bool hasOwnErrorHandler() { + return true; + } + + virtual bool executeUse(Player* player, Item* item, const PositionEx& posFrom, + const PositionEx& posTo, bool extendedUse, uint32_t creatureId); + + virtual bool castSpell(Creature* creature); + virtual bool castSpell(Creature* creature, Creature* target); + + //scripting + bool executeCastSpell(Creature* creature, const LuaVariant& var); + + virtual bool isInstant() const { + return false; + } + uint32_t getRuneItemId() { + return runeId; + } + + protected: + virtual std::string getScriptEventName(); + + static RuneSpellFunction Illusion; + static RuneSpellFunction Convince; + + bool internalCastSpell(Creature* creature, const LuaVariant& var); + + bool hasCharges; + uint32_t runeId; + + RuneSpellFunction* function; +}; + +#endif diff --git a/src/status.cpp b/src/status.cpp new file mode 100644 index 0000000000..ba9f04849d --- /dev/null +++ b/src/status.cpp @@ -0,0 +1,290 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include +#include + +#include "status.h" +#include "configmanager.h" +#include "game.h" +#include "connection.h" +#include "networkmessage.h" +#include "outputmessage.h" +#include "tools.h" + +#ifndef WIN32 +#define SOCKET_ERROR -1 +#define INVALID_SOCKET -1 +#endif + +extern ConfigManager g_config; +extern Game g_game; + +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ +uint32_t ProtocolStatus::protocolStatusCount = 0; +#endif + +enum RequestedInfo_t { + REQUEST_BASIC_SERVER_INFO = 0x01, + REQUEST_OWNER_SERVER_INFO = 0x02, + REQUEST_MISC_SERVER_INFO = 0x04, + REQUEST_PLAYERS_INFO = 0x08, + REQUEST_MAP_INFO = 0x10, + REQUEST_EXT_PLAYERS_INFO = 0x20, + REQUEST_PLAYER_STATUS_INFO = 0x40, + REQUEST_SERVER_SOFTWARE_INFO = 0x80 +}; + +std::map ProtocolStatus::ipConnectMap; + +void ProtocolStatus::onRecvFirstMessage(NetworkMessage& msg) +{ + uint32_t ip = getIP(); + + if (ip != 0x0100007F) { + std::string ipStr = convertIPToString(ip); + + if (ipStr != g_config.getString(ConfigManager::IP)) { + std::map::const_iterator it = ipConnectMap.find(ip); + + if (it != ipConnectMap.end()) { + if (OTSYS_TIME() < (it->second + g_config.getNumber(ConfigManager::STATUSQUERY_TIMEOUT))) { + getConnection()->closeConnection(); + return; + } + } + } + } + + ipConnectMap[ip] = OTSYS_TIME(); + + switch (msg.GetByte()) { + //XML info protocol + case 0xFF: { + if (msg.GetString(4) == "info") { + OutputMessage_ptr output = OutputMessagePool::getInstance()->getOutputMessage(this, false); + + if (output) { + Status* status = Status::getInstance(); + std::string str = status->getStatusString(); + output->AddBytes(str.c_str(), str.size()); + setRawMessages(true); // we dont want the size header, nor encryption + OutputMessagePool::getInstance()->send(output); + } + } + + break; + } + + //Another ServerInfo protocol + case 0x01: { + uint32_t requestedInfo = msg.GetU16(); //Only a Byte is necessary, though we could add new infos here + OutputMessage_ptr output = OutputMessagePool::getInstance()->getOutputMessage(this, false); + + if (output) { + Status* status = Status::getInstance(); + status->getInfo(requestedInfo, output, msg); + OutputMessagePool::getInstance()->send(output); + } + + break; + } + default: + break; + } + + getConnection()->closeConnection(); +} + +Status::Status() +{ + m_playersOnline = 0; + m_start = OTSYS_TIME(); +} + +void Status::addPlayer() +{ + m_playersOnline++; +} + +void Status::removePlayer() +{ + m_playersOnline--; +} + +void addXMLProperty(xmlNodePtr p, const std::string& tag, const char* val) +{ + xmlSetProp(p, (const xmlChar*)tag.c_str(), (const xmlChar*)val); +} + +void addXMLProperty(xmlNodePtr p, const std::string& tag, const std::string& val) +{ + xmlSetProp(p, (const xmlChar*)tag.c_str(), (const xmlChar*)val.c_str()); +} + +template +void addXMLProperty(xmlNodePtr p, const std::string& tag, T val) +{ + xmlSetProp(p, (const xmlChar*)tag.c_str(), (const xmlChar*)std::to_string(val).c_str()); +} + +std::string Status::getStatusString() const +{ + std::string xml; + + xmlDocPtr doc; + xmlNodePtr p, root; + + doc = xmlNewDoc((const xmlChar*)"1.0"); + doc->children = xmlNewDocNode(doc, NULL, (const xmlChar*)"tsqp", NULL); + root = doc->children; + + xmlSetProp(root, (const xmlChar*)"version", (const xmlChar*)"1.0"); + + p = xmlNewNode(NULL, (const xmlChar*)"serverinfo"); + addXMLProperty(p, "uptime", getUptime()); + addXMLProperty(p, "ip", g_config.getString(ConfigManager::IP)); + addXMLProperty(p, "servername", g_config.getString(ConfigManager::SERVER_NAME)); + addXMLProperty(p, "port", g_config.getNumber(ConfigManager::LOGIN_PORT)); + addXMLProperty(p, "location", g_config.getString(ConfigManager::LOCATION).c_str()); + addXMLProperty(p, "url", g_config.getString(ConfigManager::URL).c_str()); + addXMLProperty(p, "server", STATUS_SERVER_NAME); + addXMLProperty(p, "version", STATUS_SERVER_VERSION); + addXMLProperty(p, "client", STATUS_SERVER_PROTOCOL); + xmlAddChild(root, p); + + p = xmlNewNode(NULL, (const xmlChar*)"owner"); + addXMLProperty(p, "name", g_config.getString(ConfigManager::OWNER_NAME)); + addXMLProperty(p, "email", g_config.getString(ConfigManager::OWNER_EMAIL)); + xmlAddChild(root, p); + + p = xmlNewNode(NULL, (const xmlChar*)"players"); + addXMLProperty(p, "online", m_playersOnline); + addXMLProperty(p, "max", g_config.getNumber(ConfigManager::MAX_PLAYERS)); + addXMLProperty(p, "peak", g_game.getLastPlayersRecord()); + xmlAddChild(root, p); + + p = xmlNewNode(NULL, (const xmlChar*)"monsters"); + addXMLProperty(p, "total", g_game.getMonstersOnline()); + xmlAddChild(root, p); + + uint32_t mapWidth, mapHeight; + g_game.getMapDimensions(mapWidth, mapHeight); + + p = xmlNewNode(NULL, (const xmlChar*)"map"); + addXMLProperty(p, "name", g_config.getString(ConfigManager::MAP_NAME)); + addXMLProperty(p, "author", g_config.getString(ConfigManager::MAP_AUTHOR)); + addXMLProperty(p, "width", mapWidth); + addXMLProperty(p, "height", mapHeight); + xmlAddChild(root, p); + + xmlNewTextChild(root, NULL, (const xmlChar*)"motd", (const xmlChar*)g_config.getString(ConfigManager::MOTD).c_str()); + + xmlChar* s = NULL; + int32_t len = 0; + xmlDocDumpMemory(doc, (xmlChar**)&s, &len); + + if (s) { + xml = std::string((char*)s, len); + } else { + xml = ""; + } + + xmlFreeOTSERV(s); + xmlFreeDoc(doc); + return xml; +} + +void Status::getInfo(uint32_t requestedInfo, OutputMessage_ptr output, NetworkMessage& msg) const +{ + if (requestedInfo & REQUEST_BASIC_SERVER_INFO) { + output->AddByte(0x10); + output->AddString(g_config.getString(ConfigManager::SERVER_NAME)); + output->AddString(g_config.getString(ConfigManager::IP)); + output->AddString(std::to_string(g_config.getNumber(ConfigManager::LOGIN_PORT))); + } + + if (requestedInfo & REQUEST_OWNER_SERVER_INFO) { + output->AddByte(0x11); + output->AddString(g_config.getString(ConfigManager::OWNER_NAME)); + output->AddString(g_config.getString(ConfigManager::OWNER_EMAIL)); + } + + if (requestedInfo & REQUEST_MISC_SERVER_INFO) { + uint64_t running = getUptime(); + output->AddByte(0x12); + output->AddString(g_config.getString(ConfigManager::MOTD)); + output->AddString(g_config.getString(ConfigManager::LOCATION)); + output->AddString(g_config.getString(ConfigManager::URL)); + output->AddU32((uint32_t)(running >> 32)); + output->AddU32((uint32_t)(running)); + } + + if (requestedInfo & REQUEST_PLAYERS_INFO) { + output->AddByte(0x20); + output->AddU32(m_playersOnline); + output->AddU32(g_config.getNumber(ConfigManager::MAX_PLAYERS)); + output->AddU32(g_game.getLastPlayersRecord()); + } + + if (requestedInfo & REQUEST_MAP_INFO) { + output->AddByte(0x30); + output->AddString(g_config.getString(ConfigManager::MAP_NAME)); + output->AddString(g_config.getString(ConfigManager::MAP_AUTHOR)); + uint32_t mapWidth, mapHeight; + g_game.getMapDimensions(mapWidth, mapHeight); + output->AddU16(mapWidth); + output->AddU16(mapHeight); + } + + if (requestedInfo & REQUEST_EXT_PLAYERS_INFO) { + output->AddByte(0x21); // players info - online players list + output->AddU32(m_playersOnline); + + for (AutoList::listiterator it = Player::listPlayer.list.begin(); it != Player::listPlayer.list.end(); ++it) { + //Send the most common info + output->AddString(it->second->getName()); + output->AddU32(it->second->getLevel()); + } + } + + if (requestedInfo & REQUEST_PLAYER_STATUS_INFO) { + output->AddByte(0x22); // players info - online status info of a player + const std::string name = msg.GetString(); + + if (g_game.getPlayerByName(name) != NULL) { + output->AddByte(0x01); + } else { + output->AddByte(0x00); + } + } + + if (requestedInfo & REQUEST_SERVER_SOFTWARE_INFO) { + output->AddByte(0x23); // server software info + output->AddString(STATUS_SERVER_NAME); + output->AddString(STATUS_SERVER_VERSION); + output->AddString(STATUS_SERVER_PROTOCOL); + } +} + +uint64_t Status::getUptime() const +{ + return (OTSYS_TIME() - m_start) / 1000; +} diff --git a/src/status.h b/src/status.h new file mode 100644 index 0000000000..b676d75e82 --- /dev/null +++ b/src/status.h @@ -0,0 +1,90 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_STATUS_H +#define __OTSERV_STATUS_H + +#include +#include "definitions.h" +#include "networkmessage.h" +#include "protocol.h" + +class ProtocolStatus : public Protocol +{ + public: + // static protocol information + enum {server_sends_first = false}; + enum {protocol_identifier = 0xFF}; + enum {use_checksum = false}; + static const char* protocol_name() { + return "status protocol"; + } + +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + static uint32_t protocolStatusCount; +#endif + ProtocolStatus(Connection_ptr connection) : Protocol(connection) { +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + protocolStatusCount++; +#endif + } + virtual ~ProtocolStatus() { +#ifdef __ENABLE_SERVER_DIAGNOSTIC__ + protocolStatusCount--; +#endif + } + + virtual int32_t getProtocolId() { + return 0xFF; + } + + virtual void onRecvFirstMessage(NetworkMessage& msg); + + protected: + static std::map ipConnectMap; +}; + +class Status +{ + public: + static Status* getInstance() { + static Status status; + return &status; + } + + void addPlayer(); + void removePlayer(); + + std::string getStatusString() const; + void getInfo(uint32_t requestedInfo, OutputMessage_ptr output, NetworkMessage& msg) const; + + uint32_t getPlayersOnline() const { + return m_playersOnline; + } + + uint64_t getUptime() const; + + protected: + Status(); + + private: + uint64_t m_start; + uint32_t m_playersOnline; +}; + +#endif diff --git a/src/talkaction.cpp b/src/talkaction.cpp new file mode 100644 index 0000000000..09a656610b --- /dev/null +++ b/src/talkaction.cpp @@ -0,0 +1,181 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "creature.h" +#include "player.h" +#include "tools.h" + +#include +#include + +#include "talkaction.h" + +extern Game g_game; + +TalkActions::TalkActions() + : m_scriptInterface("TalkAction Interface") +{ + m_scriptInterface.initState(); +} + +TalkActions::~TalkActions() +{ + clear(); +} + +void TalkActions::clear() +{ + TalkActionList::iterator it = wordsMap.begin(); + + while (it != wordsMap.end()) { + delete it->second; + wordsMap.erase(it); + it = wordsMap.begin(); + } + + m_scriptInterface.reInitState(); +} + +LuaScriptInterface& TalkActions::getScriptInterface() +{ + return m_scriptInterface; +} + +std::string TalkActions::getScriptBaseName() +{ + return "talkactions"; +} + +Event* TalkActions::getEvent(const std::string& nodeName) +{ + if (asLowerCaseString(nodeName) == "talkaction") { + return new TalkAction(&m_scriptInterface); + } + + return NULL; +} + +bool TalkActions::registerEvent(Event* event, xmlNodePtr p) +{ + TalkAction* talkAction = dynamic_cast(event); + + if (!talkAction) { + return false; + } + + wordsMap.push_back(std::make_pair(talkAction->getWords(), talkAction)); + return true; +} + +TalkActionResult_t TalkActions::playerSaySpell(Player* player, SpeakClasses type, const std::string& words) +{ + if (type != SPEAK_SAY) { + return TALKACTION_CONTINUE; + } + + std::string str_words; + std::string str_param; + size_t loc = words.find('"', 0); + + if (loc != std::string::npos) { + str_words = std::string(words, 0, loc); + str_param = std::string(words, (loc + 1), words.size() - loc - 1); + } else { + str_words = words; + str_param = std::string(""); + } + + trim_left(str_words, " "); + trim_right(str_words, " "); + + TalkActionList::iterator it; + + for (it = wordsMap.begin(); it != wordsMap.end(); ++it) { + if (it->first == str_words) { + TalkAction* talkAction = it->second; + int32_t ret = talkAction->executeSay(player, str_words, str_param); + + if (ret == 1) { + return TALKACTION_CONTINUE; + } else { + return TALKACTION_BREAK; + } + } + } + + return TALKACTION_CONTINUE; +} + +TalkAction::TalkAction(LuaScriptInterface* _interface) : + Event(_interface) +{ + // +} + +TalkAction::~TalkAction() +{ + // +} + +bool TalkAction::configureEvent(xmlNodePtr p) +{ + std::string strValue; + + if (readXMLString(p, "words", strValue)) { + m_words = strValue; + } else { + std::cout << "Error: [TalkAction::configureEvent] No words for TalkAction or Spell." << std::endl; + return false; + } + + return true; +} + +std::string TalkAction::getScriptEventName() +{ + return "onSay"; +} + +int32_t TalkAction::executeSay(Creature* creature, const std::string& words, const std::string& param) +{ + //onSay(cid, words, param) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + env->setScriptId(m_scriptId, m_scriptInterface); + env->setRealPos(creature->getPosition()); + + uint32_t cid = env->addThing(creature); + + lua_State* L = m_scriptInterface->getLuaState(); + + m_scriptInterface->pushFunction(m_scriptId); + lua_pushnumber(L, cid); + lua_pushstring(L, words.c_str()); + lua_pushstring(L, param.c_str()); + + bool result = m_scriptInterface->callFunction(3); + m_scriptInterface->releaseScriptEnv(); + return result; + } else { + std::cout << "[Error - Talkaction::executeSay] Call stack overflow." << std::endl; + return 0; + } +} diff --git a/src/talkaction.h b/src/talkaction.h new file mode 100644 index 0000000000..105c5db461 --- /dev/null +++ b/src/talkaction.h @@ -0,0 +1,79 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __TALKACTION_H__ +#define __TALKACTION_H__ + +#include +#include +#include "luascript.h" +#include "baseevents.h" +#include "const.h" + +enum TalkActionResult_t { + TALKACTION_CONTINUE, + TALKACTION_BREAK, + TALKACTION_FAILED +}; + +class TalkAction; + +class TalkActions : public BaseEvents +{ + public: + TalkActions(); + virtual ~TalkActions(); + + TalkActionResult_t playerSaySpell(Player* player, SpeakClasses type, const std::string& words); + + protected: + virtual LuaScriptInterface& getScriptInterface(); + virtual std::string getScriptBaseName(); + virtual Event* getEvent(const std::string& nodeName); + virtual bool registerEvent(Event* event, xmlNodePtr p); + virtual void clear(); + + typedef std::list< std::pair > TalkActionList; + TalkActionList wordsMap; + + LuaScriptInterface m_scriptInterface; +}; + +class TalkAction : public Event +{ + public: + TalkAction(LuaScriptInterface* _interface); + virtual ~TalkAction(); + + virtual bool configureEvent(xmlNodePtr p); + + std::string getWords() const { + return m_words; + } + + //scripting + int32_t executeSay(Creature* creature, const std::string& words, const std::string& param); + // + + protected: + virtual std::string getScriptEventName(); + + std::string m_words; +}; + +#endif diff --git a/src/tasks.cpp b/src/tasks.cpp new file mode 100644 index 0000000000..388a3cc9ef --- /dev/null +++ b/src/tasks.cpp @@ -0,0 +1,151 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "tasks.h" +#include "outputmessage.h" +#include "game.h" + +extern Game g_game; + +Dispatcher::Dispatcher() +{ + m_taskList.clear(); + m_threadState = STATE_TERMINATED; +} + +void Dispatcher::start() +{ + m_threadState = STATE_RUNNING; + m_thread = boost::thread(boost::bind(&Dispatcher::dispatcherThread, (void*)this)); +} + +void Dispatcher::dispatcherThread(void* p) +{ + Dispatcher* dispatcher = (Dispatcher*)p; + + OutputMessagePool* outputPool; + + // NOTE: second argument defer_lock is to prevent from immediate locking + boost::unique_lock taskLockUnique(dispatcher->m_taskLock, boost::defer_lock); + + while (dispatcher->m_threadState != STATE_TERMINATED) { + Task* task = NULL; + + // check if there are tasks waiting + taskLockUnique.lock(); + + if (dispatcher->m_taskList.empty()) { + //if the list is empty wait for signal + dispatcher->m_taskSignal.wait(taskLockUnique); + } + + if (!dispatcher->m_taskList.empty() && (dispatcher->m_threadState != STATE_TERMINATED)) { + // take the first task + task = dispatcher->m_taskList.front(); + dispatcher->m_taskList.pop_front(); + } + + taskLockUnique.unlock(); + + // finally execute the task... + if (task) { + if (!task->hasExpired()) { + OutputMessagePool::getInstance()->startExecutionFrame(); + (*task)(); + + outputPool = OutputMessagePool::getInstance(); + + if (outputPool) { + outputPool->sendAll(); + } + + g_game.clearSpectatorCache(); + } + + delete task; + } + } +} + +void Dispatcher::addTask(Task* task, bool push_front /*= false*/) +{ + bool do_signal = false; + + m_taskLock.lock(); + + if (m_threadState == STATE_RUNNING) { + do_signal = m_taskList.empty(); + + if (push_front) { + m_taskList.push_front(task); + } else { + m_taskList.push_back(task); + } + } else { + delete task; + task = NULL; + } + + m_taskLock.unlock(); + + // send a signal if the list was empty + if (do_signal) { + m_taskSignal.notify_one(); + } +} + +void Dispatcher::flush() +{ + Task* task = NULL; + + while (!m_taskList.empty()) { + task = m_taskList.front(); + m_taskList.pop_front(); + (*task)(); + delete task; + OutputMessagePool* outputPool = OutputMessagePool::getInstance(); + + if (outputPool) { + outputPool->sendAll(); + } + + g_game.clearSpectatorCache(); + } +} + +void Dispatcher::stop() +{ + m_taskLock.lock(); + m_threadState = STATE_CLOSING; + m_taskLock.unlock(); +} + +void Dispatcher::shutdown() +{ + m_taskLock.lock(); + m_threadState = STATE_TERMINATED; + flush(); + m_taskLock.unlock(); +} + +void Dispatcher::join() +{ + m_thread.join(); +} diff --git a/src/tasks.h b/src/tasks.h new file mode 100644 index 0000000000..80c5bb6f4b --- /dev/null +++ b/src/tasks.h @@ -0,0 +1,107 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_TASKS_H__ +#define __OTSERV_TASKS_H__ + +#include +#include + +const int DISPATCHER_TASK_EXPIRATION = 2000; + +class Task +{ + public: + // DO NOT allocate this class on the stack + Task(uint32_t ms, const boost::function& f) : m_f(f) { + m_expiration = boost::get_system_time() + boost::posix_time::milliseconds(ms); + } + Task(const boost::function& f) + : m_expiration(boost::date_time::not_a_date_time), m_f(f) {} + + ~Task() {} + + void operator()() { + m_f(); + } + + void setDontExpire() { + m_expiration = boost::date_time::not_a_date_time; + } + + bool hasExpired() const { + if (m_expiration == boost::date_time::not_a_date_time) { + return false; + } + + return m_expiration < boost::get_system_time(); + } + + protected: + // Expiration has another meaning for scheduler tasks, + // then it is the time the task should be added to the + // dispatcher + boost::system_time m_expiration; + boost::function m_f; +}; + +inline Task* createTask(boost::function f) +{ + return new Task(f); +} + +inline Task* createTask(uint32_t expiration, boost::function f) +{ + return new Task(expiration, f); +} + +enum DispatcherState { + STATE_RUNNING, + STATE_CLOSING, + STATE_TERMINATED +}; + +class Dispatcher +{ + public: + Dispatcher(); + ~Dispatcher() {} + + void addTask(Task* task, bool push_front = false); + + void start(); + void stop(); + void shutdown(); + void join(); + + protected: + static void dispatcherThread(void* p); + + void flush(); + + boost::thread m_thread; + boost::mutex m_taskLock; + boost::condition_variable m_taskSignal; + + std::list m_taskList; + DispatcherState m_threadState; +}; + +extern Dispatcher g_dispatcher; + +#endif diff --git a/src/teleport.cpp b/src/teleport.cpp new file mode 100644 index 0000000000..3ac973c5c0 --- /dev/null +++ b/src/teleport.cpp @@ -0,0 +1,143 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "teleport.h" +#include "game.h" + +extern Game g_game; + +Teleport::Teleport(uint16_t _type) : Item(_type) +{ + destPos.x = 0; + destPos.y = 0; + destPos.z = 0; +} + +Teleport::~Teleport() +{ + // +} + +Attr_ReadValue Teleport::readAttr(AttrTypes_t attr, PropStream& propStream) +{ + if (ATTR_TELE_DEST == attr) { + TeleportDest* tele_dest; + + if (!propStream.GET_STRUCT(tele_dest)) { + return ATTR_READ_ERROR; + } + + setDestPos(Position(tele_dest->_x, tele_dest->_y, tele_dest->_z)); + return ATTR_READ_CONTINUE; + } else { + return Item::readAttr(attr, propStream); + } +} + +bool Teleport::serializeAttr(PropWriteStream& propWriteStream) const +{ + bool ret = Item::serializeAttr(propWriteStream); + + propWriteStream.ADD_UCHAR(ATTR_TELE_DEST); + + TeleportDest tele_dest; + + tele_dest._x = destPos.x; + tele_dest._y = destPos.y; + tele_dest._z = (uint8_t)destPos.z; + + propWriteStream.ADD_VALUE(tele_dest); + + return ret; +} + +ReturnValue Teleport::__queryAdd(int32_t index, const Thing* thing, uint32_t count, + uint32_t flags, Creature* actor/* = NULL*/) const +{ + return RET_NOTPOSSIBLE; +} + +ReturnValue Teleport::__queryMaxCount(int32_t index, const Thing* thing, uint32_t count, + uint32_t& maxQueryCount, uint32_t flags) const +{ + return RET_NOTPOSSIBLE; +} + +ReturnValue Teleport::__queryRemove(const Thing* thing, uint32_t count, uint32_t flags) const +{ + return RET_NOERROR; +} + +Cylinder* Teleport::__queryDestination(int32_t& index, const Thing* thing, Item** destItem, + uint32_t& flags) +{ + return this; +} + +void Teleport::__addThing(Thing* thing) +{ + return __addThing(0, thing); +} + +void Teleport::__addThing(int32_t index, Thing* thing) +{ + Tile* destTile = g_game.getTile(destPos.x, destPos.y, destPos.z); + + if (!destTile) { + return; + } + + if (Creature* creature = thing->getCreature()) { + Position origPos = creature->getPosition(); + g_game.internalCreatureTurn(creature, origPos.x > destPos.x ? WEST : EAST); + getTile()->moveCreature(creature, destTile); + g_game.addMagicEffect(origPos, NM_ME_TELEPORT); + g_game.addMagicEffect(destTile->getPosition(), NM_ME_TELEPORT); + } else if (Item* item = thing->getItem()) { + g_game.addMagicEffect(item->getPosition(), NM_ME_TELEPORT); + g_game.internalMoveItem(getTile(), destTile, INDEX_WHEREEVER, item, item->getItemCount(), NULL); + g_game.addMagicEffect(destTile->getPosition(), NM_ME_TELEPORT); + } +} + +void Teleport::__updateThing(Thing* thing, uint16_t itemId, uint32_t count) +{ + // +} + +void Teleport::__replaceThing(uint32_t index, Thing* thing) +{ + // +} + +void Teleport::__removeThing(Thing* thing, uint32_t count) +{ + // +} + +void Teleport::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) +{ + getParent()->postAddNotification(thing, oldParent, index, LINK_PARENT); +} + +void Teleport::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, bool isCompleteRemoval, cylinderlink_t link /*= LINK_OWNER*/) +{ + getParent()->postRemoveNotification(thing, newParent, index, isCompleteRemoval, LINK_PARENT); +} diff --git a/src/teleport.h b/src/teleport.h new file mode 100644 index 0000000000..2f35a59c59 --- /dev/null +++ b/src/teleport.h @@ -0,0 +1,72 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_TELEPORT_H__ +#define __OTSERV_TELEPORT_H__ + +#include "tile.h" + +class Teleport : public Item, public Cylinder +{ + public: + Teleport(uint16_t _type); + ~Teleport(); + + virtual Teleport* getTeleport() { + return this; + } + virtual const Teleport* getTeleport() const { + return this; + } + + //serialization + virtual Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream); + virtual bool serializeAttr(PropWriteStream& propWriteStream) const; + + void setDestPos(const Position& pos) { + destPos = pos; + } + const Position& getDestPos() const { + return destPos; + } + + //cylinder implementations + virtual ReturnValue __queryAdd(int32_t index, const Thing* thing, uint32_t count, + uint32_t flags, Creature* actor = NULL) const; + virtual ReturnValue __queryMaxCount(int32_t index, const Thing* thing, uint32_t count, + uint32_t& maxQueryCount, uint32_t flags) const; + virtual ReturnValue __queryRemove(const Thing* thing, uint32_t count, uint32_t flags) const; + virtual Cylinder* __queryDestination(int32_t& index, const Thing* thing, Item** destItem, + uint32_t& flags); + + virtual void __addThing(Thing* thing); + virtual void __addThing(int32_t index, Thing* thing); + + virtual void __updateThing(Thing* thing, uint16_t itemId, uint32_t count); + virtual void __replaceThing(uint32_t index, Thing* thing); + + virtual void __removeThing(Thing* thing, uint32_t count); + + virtual void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER); + virtual void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, bool isCompleteRemoval, cylinderlink_t link = LINK_OWNER); + + private: + Position destPos; +}; + +#endif diff --git a/src/templates.h b/src/templates.h new file mode 100644 index 0000000000..fc54ee844e --- /dev/null +++ b/src/templates.h @@ -0,0 +1,92 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __TEMPLATES_H__ +#define __TEMPLATES_H__ + +#include +#include + +#include "creature.h" +#include "otsystem.h" + +template class AutoList +{ + public: + AutoList() {} + + virtual ~AutoList() { + list.clear(); + } + + void addList(T* t) { + list[t->getID()] = t; + } + + void removeList(uint32_t _id) { + list.erase(_id); + } + + typedef std::map list_type; + + list_type list; + typedef typename list_type::iterator listiterator; +}; + +class AutoID +{ + public: + AutoID() { + OTSYS_THREAD_LOCK_CLASS lockClass(autoIDLock); + count++; + + if (count >= 0xFFFFFF) { + count = 1000; + } + + while (list.find(count) != list.end()) { + if (count >= 0xFFFFFF) { + count = 1000; + } else { + count++; + } + } + + list.insert(count); + auto_id = count; + } + + virtual ~AutoID() { + list_type::iterator it = list.find(auto_id); + + if (it != list.end()) { + list.erase(it); + } + } + + typedef OTSERV_HASH_SET list_type; + + uint32_t auto_id; + static OTSYS_THREAD_LOCKVAR autoIDLock; + + protected: + static uint32_t count; + static list_type list; +}; + +#endif diff --git a/src/thing.cpp b/src/thing.cpp new file mode 100644 index 0000000000..994e3d6c86 --- /dev/null +++ b/src/thing.cpp @@ -0,0 +1,134 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "thing.h" +#include "cylinder.h" +#include "tile.h" +#include "creature.h" +#include "item.h" +#include "player.h" + +Thing::Thing() +{ + parent = NULL; + useCount = 0; +} + + +Thing::~Thing() +{ + // + //std::cout << "thing destructor " << this << std::endl; +} + +Cylinder* Thing::getTopParent() +{ + //tile + if (getParent() == NULL) { + return dynamic_cast(this); + } + + Cylinder* aux = getParent(); + Cylinder* prevaux = dynamic_cast(this); + + while (aux->getParent() != NULL) { + prevaux = aux; + aux = aux->getParent(); + } + + if (dynamic_cast(prevaux)) { + return prevaux; + } + + return aux; +} + +const Cylinder* Thing::getTopParent() const +{ + //tile + if (getParent() == NULL) { + return dynamic_cast(this); + } + + const Cylinder* aux = getParent(); + + const Cylinder* prevaux = dynamic_cast(this); + + while (aux->getParent() != NULL) { + prevaux = aux; + aux = aux->getParent(); + } + + if (dynamic_cast(prevaux)) { + return prevaux; + } + + return aux; +} + +Tile* Thing::getTile() +{ + Cylinder* cylinder = getTopParent(); + + //get root cylinder + if (cylinder->getParent()) { + cylinder = cylinder->getParent(); + } + + return dynamic_cast(cylinder); +} + +const Tile* Thing::getTile() const +{ + const Cylinder* cylinder = getTopParent(); + + //get root cylinder + if (cylinder->getParent()) { + cylinder = cylinder->getParent(); + } + + return dynamic_cast(cylinder); +} + +const Position& Thing::getPosition() const +{ + const Tile* tile = getTile(); + + if (!tile) { + return Tile::null_tile.getTilePosition(); + } + + return tile->getTilePosition(); +} + +bool Thing::isRemoved() const +{ + if (parent == NULL) { + return true; + } + + const Cylinder* aux = getParent(); + + if (aux->isRemoved()) { + return true; + } + + return false; +} diff --git a/src/thing.h b/src/thing.h new file mode 100644 index 0000000000..5cafc4ae23 --- /dev/null +++ b/src/thing.h @@ -0,0 +1,157 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __THING_H__ +#define __THING_H__ + +#include "position.h" + +enum ReturnValue { + RET_NOERROR = 1, + RET_NOTPOSSIBLE = 2, + RET_NOTENOUGHROOM = 3, + RET_PLAYERISPZLOCKED = 4, + RET_PLAYERISNOTINVITED = 5, + RET_CANNOTTHROW = 6, + RET_THEREISNOWAY = 7, + RET_DESTINATIONOUTOFREACH = 8, + RET_CREATUREBLOCK = 9, + RET_NOTMOVEABLE = 10, + RET_DROPTWOHANDEDITEM = 11, + RET_BOTHHANDSNEEDTOBEFREE = 12, + RET_CANONLYUSEONEWEAPON = 13, + RET_NEEDEXCHANGE = 14, + RET_CANNOTBEDRESSED = 15, + RET_PUTTHISOBJECTINYOURHAND = 16, + RET_PUTTHISOBJECTINBOTHHANDS = 17, + RET_TOOFARAWAY = 18, + RET_FIRSTGODOWNSTAIRS = 19, + RET_FIRSTGOUPSTAIRS = 20, + RET_CONTAINERNOTENOUGHROOM = 21, + RET_NOTENOUGHCAPACITY = 22, + RET_CANNOTPICKUP = 23, + RET_THISISIMPOSSIBLE = 24, + RET_DEPOTISFULL = 25, + RET_CREATUREDOESNOTEXIST = 26, + RET_CANNOTUSETHISOBJECT = 27, + RET_PLAYERWITHTHISNAMEISNOTONLINE = 28, + RET_NOTREQUIREDLEVELTOUSERUNE = 29, + RET_YOUAREALREADYTRADING = 30, + RET_THISPLAYERISALREADYTRADING = 31, + RET_YOUMAYNOTLOGOUTDURINGAFIGHT = 32, + RET_DIRECTPLAYERSHOOT = 33, + RET_NOTENOUGHLEVEL = 34, + RET_NOTENOUGHMAGICLEVEL = 35, + RET_NOTENOUGHMANA = 36, + RET_NOTENOUGHSOUL = 37, + RET_YOUAREEXHAUSTED = 38, + RET_PLAYERISNOTREACHABLE = 39, + RET_CANONLYUSETHISRUNEONCREATURES = 40, + RET_ACTIONNOTPERMITTEDINPROTECTIONZONE = 41, + RET_YOUMAYNOTATTACKTHISPLAYER = 42, + RET_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE = 43, + RET_YOUMAYNOTATTACKAPERSONWHILEINPROTECTIONZONE = 44, + RET_YOUMAYNOTATTACKTHISCREATURE = 45, + RET_YOUCANONLYUSEITONCREATURES = 46, + RET_CREATUREISNOTREACHABLE = 47, + RET_TURNSECUREMODETOATTACKUNMARKEDPLAYERS = 48, + RET_YOUNEEDPREMIUMACCOUNT = 49, + RET_YOUNEEDTOLEARNTHISSPELL = 50, + RET_YOURVOCATIONCANNOTUSETHISSPELL = 51, + RET_YOUNEEDAWEAPONTOUSETHISSPELL = 52, + RET_PLAYERISPZLOCKEDLEAVEPVPZONE = 53, + RET_PLAYERISPZLOCKEDENTERPVPZONE = 54, + RET_ACTIONNOTPERMITTEDINANOPVPZONE = 55, + RET_YOUCANNOTLOGOUTHERE = 56, + RET_YOUNEEDAMAGICITEMTOCASTSPELL = 57, + RET_CANNOTCONJUREITEMHERE = 58, + RET_YOUNEEDTOSPLITYOURSPEARS = 59, + RET_NAMEISTOOAMBIGIOUS = 60, + RET_CANONLYUSEONESHIELD = 61, + RET_NOPARTYMEMBERSINRANGE = 62, + RET_YOUARENOTTHEOWNER = 63 +}; + +class Tile; +class Cylinder; +class Item; +class Creature; + +class Thing +{ + protected: + Thing(); + + public: + virtual ~Thing(); + + void useThing2() { + ++useCount; + } + void releaseThing2() { + --useCount; + + if (useCount <= 0) { + delete this; + } + } + + virtual std::string getDescription(int32_t lookDistance) const = 0; + + Cylinder* getParent() { + return parent; + } + const Cylinder* getParent() const { + return parent; + } + + virtual void setParent(Cylinder* cylinder) { + parent = cylinder; + } + + Cylinder* getTopParent(); //returns Tile/Container or a Player + const Cylinder* getTopParent() const; + + virtual Tile* getTile(); + virtual const Tile* getTile() const; + + virtual const Position& getPosition() const; + virtual int32_t getThrowRange() const = 0; + virtual bool isPushable() const = 0; + + virtual Item* getItem() { + return NULL; + } + virtual const Item* getItem() const { + return NULL; + } + virtual Creature* getCreature() { + return NULL; + } + virtual const Creature* getCreature() const { + return NULL; + } + + virtual bool isRemoved() const; + + private: + Cylinder* parent; + int32_t useCount; +}; + +#endif diff --git a/src/tile.cpp b/src/tile.cpp new file mode 100644 index 0000000000..5a61cb1f68 --- /dev/null +++ b/src/tile.cpp @@ -0,0 +1,1911 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "definitions.h" + +#include +#include + +#include "definitions.h" +#include "tile.h" +#include "game.h" +#include "player.h" +#include "creature.h" +#include "teleport.h" +#include "trashholder.h" +#include "mailbox.h" +#include "combat.h" +#include "movement.h" + +extern Game g_game; +extern MoveEvents* g_moveEvents; + +StaticTile real_null_tile(0xFFFF, 0xFFFF, 0xFFFF); +Tile& Tile::null_tile = real_null_tile; + +bool Tile::hasProperty(enum ITEMPROPERTY prop) const +{ + if (ground && ground->hasProperty(prop)) { + return true; + } + + if (const TileItemVector* items = getItemList()) { + for (ItemVector::const_iterator it = items->begin(); it != items->end(); ++it) { + if ((*it)->hasProperty(prop)) { + return true; + } + } + } + + return false; +} + +bool Tile::hasProperty(Item* exclude, enum ITEMPROPERTY prop) const +{ + assert(exclude); + + if (ground && exclude != ground && ground->hasProperty(prop)) { + return true; + } + + if (const TileItemVector* items = getItemList()) { + for (ItemVector::const_iterator it = items->begin(); it != items->end(); ++it) { + Item* item = *it; + + if (item != exclude && item->hasProperty(prop)) { + return true; + } + } + } + + return false; +} + +bool Tile::hasHeight(uint32_t n) const +{ + uint32_t height = 0; + + if (ground) { + if (ground->hasProperty(HASHEIGHT)) { + ++height; + } + + if (n == height) { + return true; + } + } + + if (const TileItemVector* items = getItemList()) { + for (ItemVector::const_iterator it = items->begin(); it != items->end(); ++it) { + if ((*it)->hasProperty(HASHEIGHT)) { + ++height; + } + + if (n == height) { + return true; + } + } + } + + return false; +} + +uint32_t Tile::getCreatureCount() const +{ + if (const CreatureVector* creatures = getCreatures()) { + return creatures->size(); + } + + return 0; +} + +uint32_t Tile::getItemCount() const +{ + if (const TileItemVector* items = getItemList()) { + return (uint32_t)items->size(); + } + + return 0; +} + +uint32_t Tile::getTopItemCount() const +{ + if (const TileItemVector* items = getItemList()) { + return items->getTopItemCount(); + } + + return 0; +} + +uint32_t Tile::getDownItemCount() const +{ + if (const TileItemVector* items = getItemList()) { + return items->getDownItemCount(); + } + + return 0; +} + +std::string Tile::getDescription(int32_t lookDistance) const +{ + std::string ret = "You dont know why, but you cant see anything!"; + return ret; +} + +Teleport* Tile::getTeleportItem() const +{ + if (!hasFlag(TILESTATE_TELEPORT)) { + return NULL; + } + + if (const TileItemVector* items = getItemList()) { + for (ItemVector::const_reverse_iterator it = items->rbegin(); it != items->rend(); ++it) { + if ((*it)->getTeleport()) { + return (*it)->getTeleport(); + } + } + } + + return NULL; +} + +MagicField* Tile::getFieldItem() const +{ + if (!hasFlag(TILESTATE_MAGICFIELD)) { + return NULL; + } + + if (ground && ground->getMagicField()) { + return ground->getMagicField(); + } + + if (const TileItemVector* items = getItemList()) { + for (ItemVector::const_reverse_iterator it = items->rbegin(); it != items->rend(); ++it) { + if ((*it)->getMagicField()) { + return (*it)->getMagicField(); + } + } + } + + return NULL; +} + +TrashHolder* Tile::getTrashHolder() const +{ + if (!hasFlag(TILESTATE_TRASHHOLDER)) { + return NULL; + } + + if (ground && ground->getTrashHolder()) { + return ground->getTrashHolder(); + } + + if (const TileItemVector* items = getItemList()) { + for (ItemVector::const_reverse_iterator it = items->rbegin(); it != items->rend(); ++it) { + if ((*it)->getTrashHolder()) { + return (*it)->getTrashHolder(); + } + } + } + + return NULL; +} + +Mailbox* Tile::getMailbox() const +{ + if (!hasFlag(TILESTATE_MAILBOX)) { + return NULL; + } + + if (ground && ground->getMailbox()) { + return ground->getMailbox(); + } + + if (const TileItemVector* items = getItemList()) { + for (ItemVector::const_reverse_iterator it = items->rbegin(); it != items->rend(); ++it) { + if ((*it)->getMailbox()) { + return (*it)->getMailbox(); + } + } + } + + return NULL; +} + +BedItem* Tile::getBedItem() const +{ + if (!hasFlag(TILESTATE_BED)) { + return NULL; + } + + if (ground && ground->getBed()) { + return ground->getBed(); + } + + if (const TileItemVector* items = getItemList()) { + for (ItemVector::const_reverse_iterator it = items->rbegin(); it != items->rend(); ++it) { + if ((*it)->getBed()) { + return (*it)->getBed(); + } + } + } + + return NULL; +} + +Creature* Tile::getTopCreature() +{ + if (CreatureVector* creatures = getCreatures()) { + if (!creatures->empty()) { + return *creatures->begin(); + } + } + + return NULL; +} + +const Creature* Tile::getTopCreature() const +{ + if (const CreatureVector* creatures = getCreatures()) { + if (!creatures->empty()) { + return *creatures->begin(); + } + } + + return NULL; +} + +const Creature* Tile::getBottomCreature() const +{ + if (const CreatureVector* creatures = getCreatures()) { + if (!creatures->empty()) { + return *creatures->rbegin(); + } + } + + return NULL; +} + +Creature* Tile::getTopVisibleCreature(const Creature* creature) +{ + if (CreatureVector* creatures = getCreatures()) { + if (creature) { + const Player* player = creature->getPlayer(); + + if (player && player->isAccessPlayer()) { + return getTopCreature(); + } + + for (CreatureVector::const_iterator it = creatures->begin(), end = creatures->end(); it != end; ++it) { + if (creature->canSeeCreature(*it)) { + return *it; + } + } + } else { + for (CreatureVector::const_iterator it = creatures->begin(), end = creatures->end(); it != end; ++it) { + if (!(*it)->isInvisible()) { + const Player* player = (*it)->getPlayer(); + + if (!player || !player->isInGhostMode()) { + return *it; + } + } + } + } + } + + return NULL; +} + +const Creature* Tile::getTopVisibleCreature(const Creature* creature) const +{ + if (const CreatureVector* creatures = getCreatures()) { + if (creature) { + const Player* player = creature->getPlayer(); + + if (player && player->isAccessPlayer()) { + return getTopCreature(); + } + + for (CreatureVector::const_iterator it = creatures->begin(), end = creatures->end(); it != end; ++it) { + if (creature->canSeeCreature(*it)) { + return *it; + } + } + } else { + for (CreatureVector::const_iterator it = creatures->begin(), end = creatures->end(); it != end; ++it) { + if (!(*it)->isInvisible()) { + const Player* player = (*it)->getPlayer(); + + if (!player || !player->isInGhostMode()) { + return *it; + } + } + } + } + } + + return NULL; +} + +const Creature* Tile::getBottomVisibleCreature(const Creature* creature) const +{ + if (const CreatureVector* creatures = getCreatures()) { + if (creature) { + const Player* player = creature->getPlayer(); + + if (player && player->isAccessPlayer()) { + return getBottomCreature(); + } + + for (CreatureVector::const_reverse_iterator it = creatures->rbegin(), end = creatures->rend(); it != end; ++it) { + if (creature->canSeeCreature(*it)) { + return *it; + } + } + } else { + for (CreatureVector::const_reverse_iterator it = creatures->rbegin(), end = creatures->rend(); it != end; ++it) { + if (!(*it)->isInvisible()) { + const Player* player = (*it)->getPlayer(); + + if (!player || !player->isInGhostMode()) { + return *it; + } + } + } + } + } + + return NULL; +} + +Item* Tile::getTopDownItem() +{ + if (TileItemVector* items = getItemList()) { + if (items->getDownItemCount() > 0) { + return *items->getBeginDownItem(); + } + } + + return NULL; +} + +Item* Tile::getTopTopItem() +{ + if (TileItemVector* items = getItemList()) { + if (items->getTopItemCount() > 0) { + return *(items->getEndTopItem() - 1); + } + } + + return NULL; +} + +Item* Tile::getItemByTopOrder(int32_t topOrder) +{ + //topOrder: + //1: borders + //2: ladders, signs, splashes + //3: doors etc + //4: creatures + if (TileItemVector* items = getItemList()) { + ItemVector::reverse_iterator itEnd = ItemVector::reverse_iterator(items->getBeginTopItem()); + + for (ItemVector::reverse_iterator it = ItemVector::reverse_iterator(items->getEndTopItem()); it != itEnd; ++it) { + if (Item::items[(*it)->getID()].alwaysOnTopOrder == topOrder) { + return (*it); + } + } + } + + return NULL; +} + +Thing* Tile::getTopVisibleThing(const Creature* creature) +{ + Thing* thing = getTopVisibleCreature(creature); + + if (thing) { + return thing; + } + + TileItemVector* items = getItemList(); + + if (items) { + for (ItemVector::iterator it = items->getBeginDownItem(); it != items->getEndDownItem(); ++it) { + const ItemType& iit = Item::items[(*it)->getID()]; + + if (!iit.lookThrough) { + return (*it); + } + } + + ItemVector::reverse_iterator itEnd = ItemVector::reverse_iterator(items->getBeginTopItem()); + + for (ItemVector::reverse_iterator it = ItemVector::reverse_iterator(items->getEndTopItem()); it != itEnd; ++it) { + const ItemType& iit = Item::items[(*it)->getID()]; + + if (!iit.lookThrough) { + return (*it); + } + } + } + + return ground; +} + +void Tile::onAddTileItem(Item* item) +{ + if (item->hasProperty(MOVEABLE)) { + Game::BrowseFieldMap::const_iterator it = g_game.browseFields.find(this); + + if (it != g_game.browseFields.end()) { + it->second->__addThingBack(item); + item->setParent(this); + } + } + + updateTileFlags(item, false); + + const Position& cylinderMapPos = getPosition(); + + const SpectatorVec& list = g_game.getSpectators(cylinderMapPos); + + SpectatorVec::const_iterator end = list.end(); + + //send to client + for (SpectatorVec::const_iterator it = list.begin(); it != end; ++it) { + if (Player* tmpPlayer = (*it)->getPlayer()) { + tmpPlayer->sendAddTileItem(this, cylinderMapPos, item); + } + } + + //event methods + for (SpectatorVec::const_iterator it = list.begin(); it != end; ++it) { + (*it)->onAddTileItem(this, cylinderMapPos, item); + } +} + +void Tile::onUpdateTileItem(Item* oldItem, const ItemType& oldType, Item* newItem, const ItemType& newType) +{ + if (newItem->hasProperty(MOVEABLE)) { + Game::BrowseFieldMap::const_iterator it = g_game.browseFields.find(this); + + if (it != g_game.browseFields.end()) { + int32_t index = it->second->__getIndexOfThing(oldItem); + + if (index != -1) { + it->second->__replaceThing(index, newItem); + newItem->setParent(this); + } + } + } + + const Position& cylinderMapPos = getPosition(); + + const SpectatorVec& list = g_game.getSpectators(cylinderMapPos); + + SpectatorVec::const_iterator end = list.end(); + + //send to client + for (SpectatorVec::const_iterator it = list.begin(); it != end; ++it) { + if (Player* tmpPlayer = (*it)->getPlayer()) { + tmpPlayer->sendUpdateTileItem(this, cylinderMapPos, oldItem, newItem); + } + } + + //event methods + for (SpectatorVec::const_iterator it = list.begin(); it != end; ++it) { + (*it)->onUpdateTileItem(this, cylinderMapPos, oldItem, oldType, newItem, newType); + } +} + +void Tile::onRemoveTileItem(const SpectatorVec& list, std::vector& oldStackPosVector, Item* item) +{ + if (item->hasProperty(MOVEABLE)) { + Game::BrowseFieldMap::const_iterator it = g_game.browseFields.find(this); + + if (it != g_game.browseFields.end()) { + it->second->__removeThing(item, item->getItemCount()); + } + } + + updateTileFlags(item, true); + + const Position& cylinderMapPos = getPosition(); + const ItemType& iType = Item::items[item->getID()]; + + SpectatorVec::const_iterator end = list.end(); + + //send to client + int32_t i = 0; + + for (SpectatorVec::const_iterator it = list.begin(); it != end; ++it) { + if (Player* tmpPlayer = (*it)->getPlayer()) { + tmpPlayer->sendRemoveTileItem(this, cylinderMapPos, oldStackPosVector[i], item); + ++i; + } + } + + //event methods + for (SpectatorVec::const_iterator it = list.begin(); it != end; ++it) { + (*it)->onRemoveTileItem(this, cylinderMapPos, iType, item); + } +} + +void Tile::onUpdateTile(const SpectatorVec& list) +{ + const Position& cylinderMapPos = getPosition(); + + //send to clients + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + (*it)->getPlayer()->sendUpdateTile(this, cylinderMapPos); + } +} + +void Tile::moveCreature(Creature* creature, Cylinder* toCylinder, bool forceTeleport/* = false*/) +{ + Tile* newTile = toCylinder->getTile(); + int32_t oldStackPos = __getIndexOfThing(creature); + + Position oldPos = getPosition(); + Position newPos = newTile->getPosition(); + + bool teleport = false; + + if (forceTeleport || !newTile->ground || !Position::areInRange<1, 1, 0>(oldPos, newPos)) { + teleport = true; + } + + SpectatorVec list; + g_game.getSpectators(list, oldPos, true); + g_game.getSpectators(list, newPos, true); + + SpectatorVec::const_iterator end = list.end(); + + std::vector oldStackPosVector; + + for (SpectatorVec::const_iterator it = list.begin(); it != end; ++it) { + if (Player* tmpPlayer = (*it)->getPlayer()) { + oldStackPosVector.push_back(getClientIndexOfThing(tmpPlayer, creature)); + } + } + + //remove the creature + __removeThing(creature, 0); + + // Switch the node ownership + if (qt_node != newTile->qt_node) { + qt_node->removeCreature(creature); + newTile->qt_node->addCreature(creature); + } + + //add the creature + newTile->__addThing(creature); + int32_t newStackPos = newTile->__getIndexOfThing(creature); + + if (!teleport) { + if (oldPos.y > newPos.y) { + creature->setDirection(NORTH); + } else if (oldPos.y < newPos.y) { + creature->setDirection(SOUTH); + } + + if (oldPos.x < newPos.x) { + creature->setDirection(EAST); + } else if (oldPos.x > newPos.x) { + creature->setDirection(WEST); + } + } + + //send to client + uint32_t i = 0; + + for (SpectatorVec::const_iterator it = list.begin(); it != end; ++it) { + if (Player* tmpPlayer = (*it)->getPlayer()) { + //Use the correct stackpos + if (!creature->isInGhostMode() || tmpPlayer->isAccessPlayer()) { + tmpPlayer->sendCreatureMove(creature, newTile, newPos, this, oldPos, oldStackPosVector[i], teleport); + } + + ++i; + } + } + + //event method + for (SpectatorVec::const_iterator it = list.begin(); it != end; ++it) { + (*it)->onCreatureMove(creature, newTile, newPos, this, oldPos, teleport); + } + + postRemoveNotification(creature, toCylinder, oldStackPos, true); + newTile->postAddNotification(creature, this, newStackPos); +} + +ReturnValue Tile::__queryAdd(int32_t index, const Thing* thing, uint32_t count, + uint32_t flags, Creature* actor/* = NULL*/) const +{ + const CreatureVector* creatures = getCreatures(); + const TileItemVector* items = getItemList(); + + if (const Creature* creature = thing->getCreature()) { + if (hasBitSet(FLAG_NOLIMIT, flags)) { + return RET_NOERROR; + } + + if (hasBitSet(FLAG_PATHFINDING, flags)) { + if (floorChange() || positionChange()) { + return RET_NOTPOSSIBLE; + } + } + + if (ground == NULL) { + return RET_NOTPOSSIBLE; + } + + if (const Monster* monster = creature->getMonster()) { + if (hasFlag(TILESTATE_PROTECTIONZONE)) { + return RET_NOTPOSSIBLE; + } + + if (floorChange() || positionChange()) { + return RET_NOTPOSSIBLE; + } + + if (monster->canPushCreatures() && !monster->isSummon()) { + if (creatures) { + Creature* creature; + + for (uint32_t i = 0; i < creatures->size(); ++i) { + creature = creatures->at(i); + + if (creature->getPlayer() && creature->getPlayer()->isInGhostMode()) { + continue; + } + + const Monster* creatureMonster = creature->getMonster(); + + if (!creatureMonster || !creature->isPushable() || + (creatureMonster->isSummon() && creatureMonster->getMaster()->getPlayer())) { + return RET_NOTPOSSIBLE; + } + } + } + } else if (creatures && !creatures->empty()) { + for (CreatureVector::const_iterator cit = creatures->begin(); cit != creatures->end(); ++cit) { + if (!(*cit)->isInGhostMode()) { + return RET_NOTENOUGHROOM; + } + } + } + + if (hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID)) { + return RET_NOTPOSSIBLE; + } + + if (hasBitSet(FLAG_PATHFINDING, flags) && hasFlag(TILESTATE_IMMOVABLENOFIELDBLOCKPATH)) { + return RET_NOTPOSSIBLE; + } + + if (hasFlag(TILESTATE_BLOCKSOLID) || (hasBitSet(FLAG_PATHFINDING, flags) && hasFlag(TILESTATE_NOFIELDBLOCKPATH))) { + if (!(monster->canPushItems() || hasBitSet(FLAG_IGNOREBLOCKITEM, flags))) { + return RET_NOTPOSSIBLE; + } + } + + MagicField* field = getFieldItem(); + + if (field && !field->isBlocking()) { + CombatType_t combatType = field->getCombatType(); + + //There is 3 options for a monster to enter a magic field + //1) Monster is immune + if (!monster->isImmune(combatType)) { + //1) Monster is "strong" enough to handle the damage + //2) Monster is already afflicated by this type of condition + if (hasBitSet(FLAG_IGNOREFIELDDAMAGE, flags)) { + if (!(monster->canPushItems() || monster->hasCondition(Combat::DamageToConditionType(combatType)))) { + return RET_NOTPOSSIBLE; + } + } else { + return RET_NOTPOSSIBLE; + } + } + } + + return RET_NOERROR; + } else if (const Player* player = creature->getPlayer()) { + if (creatures && !creatures->empty() && !hasBitSet(FLAG_IGNOREBLOCKCREATURE, flags) && !player->isAccessPlayer()) { + for (CreatureVector::const_iterator cit = creatures->begin(); cit != creatures->end(); ++cit) { + if (!player->canWalkthrough(*cit)) { + return RET_NOTPOSSIBLE; + } + } + } + + if (player->getParent() == NULL && hasFlag(TILESTATE_NOLOGOUT)) { + //player is trying to login to a "no logout" tile + return RET_NOTPOSSIBLE; + } + + if (player->getTile() && player->isPzLocked()) { + if (!player->getTile()->hasFlag(TILESTATE_PVPZONE)) { + //player is trying to enter a pvp zone while being pz-locked + if (hasFlag(TILESTATE_PVPZONE)) { + return RET_PLAYERISPZLOCKEDENTERPVPZONE; + } + } else if (!hasFlag(TILESTATE_PVPZONE)) { //player is trying to leave a pvp zone while being pz-locked + return RET_PLAYERISPZLOCKEDLEAVEPVPZONE; + } + + } + + if ((hasFlag(TILESTATE_NOPVPZONE) || hasFlag(TILESTATE_PROTECTIONZONE)) && player->isPzLocked()) { + return RET_PLAYERISPZLOCKED; + } + } else if (creatures && !creatures->empty() && !hasBitSet(FLAG_IGNOREBLOCKCREATURE, flags)) { + for (CreatureVector::const_iterator cit = creatures->begin(); cit != creatures->end(); ++cit) { + if (!(*cit)->isInGhostMode()) { + return RET_NOTENOUGHROOM; + } + } + } + + if (items) { + if (!hasBitSet(FLAG_IGNOREBLOCKITEM, flags)) { + //If the FLAG_IGNOREBLOCKITEM bit isn't set we dont have to iterate every single item + if (hasFlag(TILESTATE_BLOCKSOLID)) { + return RET_NOTENOUGHROOM; + } + } else { + //FLAG_IGNOREBLOCKITEM is set + if (ground) { + const ItemType& iiType = Item::items[ground->getID()]; + + if (iiType.blockSolid && (!iiType.moveable || ground->getUniqueId() != 0)) { + return RET_NOTPOSSIBLE; + } + } + + if (const TileItemVector* items = getItemList()) { + Item* iitem; + + for (ItemVector::const_iterator it = items->begin(); it != items->end(); ++it) { + iitem = (*it); + const ItemType& iiType = Item::items[iitem->getID()]; + + if (iiType.blockSolid && (!iiType.moveable || iitem->getUniqueId() != 0)) { + return RET_NOTPOSSIBLE; + } + } + } + } + } + } else if (const Item* item = thing->getItem()) { + if (items && items->size() >= 0xFFFF) { + return RET_NOTPOSSIBLE; + } + + if (hasBitSet(FLAG_NOLIMIT, flags)) { + return RET_NOERROR; + } + + bool itemIsHangable = item->isHangable(); + + if (ground == NULL && !itemIsHangable) { + return RET_NOTPOSSIBLE; + } + + if (creatures && !creatures->empty() && item->isBlocking() && !hasBitSet(FLAG_IGNOREBLOCKCREATURE, flags)) { + for (CreatureVector::const_iterator cit = creatures->begin(); cit != creatures->end(); ++cit) { + if (!(*cit)->isInGhostMode()) { + return RET_NOTENOUGHROOM; + } + } + } + + if (ground) { + const ItemType& iiType = Item::items[ground->getID()]; + + if (iiType.blockSolid) { + if (!iiType.allowPickupable || item->isMagicField() || item->isBlocking()) { + if (!item->isPickupable()) { + return RET_NOTENOUGHROOM; + } + + if (!iiType.hasHeight || iiType.pickupable || iiType.isBed()) { + return RET_NOTENOUGHROOM; + } + } + } + } + + if (items) { + if (itemIsHangable) { + bool hasHangable = false; + bool supportHangable = false; + + for (ItemVector::const_iterator it = items->begin(), end = items->end(); it != end; ++it) { + const ItemType& iiType = Item::items[(*it)->getID()]; + + if (iiType.isHangable) { + hasHangable = true; + } + + if (iiType.isHorizontal || iiType.isVertical) { + supportHangable = true; + } else if (iiType.blockSolid) { + if (iiType.allowPickupable && !item->isMagicField() && !item->isBlocking()) { + continue; + } + + if (!item->isPickupable()) { + return RET_NOTENOUGHROOM; + } + + if (!iiType.hasHeight || iiType.pickupable || iiType.isBed()) { + return RET_NOTENOUGHROOM; + } + } + } + + if (hasHangable && supportHangable) { + return RET_NEEDEXCHANGE; + } + } else { + for (ItemVector::const_iterator it = items->begin(), end = items->end(); it != end; ++it) { + const ItemType& iiType = Item::items[(*it)->getID()]; + + if (!iiType.blockSolid) { + continue; + } + + if (iiType.allowPickupable && !item->isMagicField() && !item->isBlocking()) { + continue; + } + + if (!item->isPickupable()) { + return RET_NOTENOUGHROOM; + } + + if (!iiType.hasHeight || iiType.pickupable || iiType.isBed()) { + return RET_NOTENOUGHROOM; + } + } + } + } + } + + return RET_NOERROR; +} + +ReturnValue Tile::__queryMaxCount(int32_t index, const Thing* thing, uint32_t count, uint32_t& maxQueryCount, + uint32_t flags) const +{ + maxQueryCount = std::max(1, count); + return RET_NOERROR; +} + +ReturnValue Tile::__queryRemove(const Thing* thing, uint32_t count, uint32_t flags) const +{ + int32_t index = __getIndexOfThing(thing); + + if (index == -1) { + return RET_NOTPOSSIBLE; + } + + const Item* item = thing->getItem(); + + if (item == NULL) { + return RET_NOTPOSSIBLE; + } + + if (count == 0 || (item->isStackable() && count > item->getItemCount())) { + return RET_NOTPOSSIBLE; + } + + if (item->isNotMoveable() && !hasBitSet(FLAG_IGNORENOTMOVEABLE, flags)) { + return RET_NOTMOVEABLE; + } + + return RET_NOERROR; +} + +Cylinder* Tile::__queryDestination(int32_t& index, const Thing* thing, Item** destItem, + uint32_t& flags) +{ + Tile* destTile = NULL; + *destItem = NULL; + + if (floorChangeDown()) { + int dx = getTilePosition().x; + int dy = getTilePosition().y; + int dz = getTilePosition().z + 1; + + Tile* southDownTile = g_game.getTile(dx, dy - 1, dz); + + if (southDownTile && southDownTile->floorChange(SOUTH_ALT)) { + dy -= 2; + destTile = g_game.getTile(dx, dy, dz); + } else { + Tile* eastDownTile = g_game.getTile(dx - 1, dy, dz); + + if (eastDownTile && eastDownTile->floorChange(EAST_ALT)) { + dx -= 2; + destTile = g_game.getTile(dx, dy, dz); + } else { + Tile* downTile = g_game.getTile(dx, dy, dz); + + if (downTile) { + if (downTile->floorChange(NORTH)) { + dy += 1; + } + + if (downTile->floorChange(SOUTH)) { + dy -= 1; + } + + if (downTile->floorChange(SOUTH_ALT)) { + dy -= 2; + } + + if (downTile->floorChange(EAST)) { + dx -= 1; + } + + if (downTile->floorChange(EAST_ALT)) { + dx -= 2; + } + + if (downTile->floorChange(WEST)) { + dx += 1; + } + + destTile = g_game.getTile(dx, dy, dz); + } + } + } + } else if (floorChange()) { + int dx = getTilePosition().x; + int dy = getTilePosition().y; + int dz = getTilePosition().z - 1; + + if (floorChange(NORTH)) { + dy -= 1; + } + + if (floorChange(SOUTH)) { + dy += 1; + } + + if (floorChange(EAST)) { + dx += 1; + } + + if (floorChange(WEST)) { + dx -= 1; + } + + if (floorChange(SOUTH_ALT)) { + dy += 2; + } + + if (floorChange(EAST_ALT)) { + dx += 2; + } + + destTile = g_game.getTile(dx, dy, dz); + } + + if (destTile == NULL) { + destTile = this; + } else { + flags |= FLAG_NOLIMIT; //Will ignore that there is blocking items/creatures + } + + if (destTile) { + Thing* destThing = destTile->getTopDownItem(); + + if (destThing) { + *destItem = destThing->getItem(); + } + } + + return destTile; +} + +void Tile::__addThing(Thing* thing) +{ + __addThing(0, thing); +} + +void Tile::__addThing(int32_t index, Thing* thing) +{ + Creature* creature = thing->getCreature(); + + if (creature) { + g_game.clearSpectatorCache(); + creature->setParent(this); + CreatureVector* creatures = makeCreatures(); + creatures->insert(creatures->begin(), creature); + ++thingCount; + } else { + Item* item = thing->getItem(); + + if (item == NULL) { + return /*RET_NOTPOSSIBLE*/; + } + + TileItemVector* items = getItemList(); + + if (items && items->size() > 0xFFFF) { + return /*RET_NOTPOSSIBLE*/; + } + + item->setParent(this); + + if (item->isGroundTile()) { + if (ground == NULL) { + ground = item; + ++thingCount; + onAddTileItem(item); + } else { + const ItemType& oldType = Item::items[ground->getID()]; + const ItemType& newType = Item::items[item->getID()]; + + int32_t oldGroundIndex = __getIndexOfThing(ground); + Item* oldGround = ground; + ground->setParent(NULL); + g_game.FreeThing(ground); + ground = item; + updateTileFlags(oldGround, true); + updateTileFlags(item, false); + onUpdateTileItem(oldGround, oldType, item, newType); + postRemoveNotification(oldGround, NULL, oldGroundIndex, true); + } + } else if (item->isAlwaysOnTop()) { + if (item->isSplash()) { + //remove old splash if exists + if (items) { + for (ItemVector::iterator it = items->getBeginTopItem(); it != items->getEndTopItem(); ++it) { + if ((*it)->isSplash()) { + int32_t oldSplashIndex = __getIndexOfThing(*it); + Item* oldSplash = *it; + __removeThing(oldSplash, 1); + oldSplash->setParent(NULL); + g_game.FreeThing(oldSplash); + postRemoveNotification(oldSplash, NULL, oldSplashIndex, true); + break; + } + } + } + } + + bool isInserted = false; + + if (items) { + for (ItemVector::iterator it = items->getBeginTopItem(); it != items->getEndTopItem(); ++it) { + //Note: this is different from internalAddThing + if (Item::items[item->getID()].alwaysOnTopOrder <= Item::items[(*it)->getID()].alwaysOnTopOrder) { + items->insert(it, item); + ++thingCount; + isInserted = true; + break; + } + } + } else { + items = makeItemList(); + } + + if (!isInserted) { + items->push_back(item); + ++thingCount; + } + + onAddTileItem(item); + } else { + if (item->isMagicField()) { + //remove old field item if exists + if (items) { + MagicField* oldField = NULL; + + for (ItemVector::iterator it = items->getBeginDownItem(); it != items->getEndDownItem(); ++it) { + if ((oldField = (*it)->getMagicField())) { + if (oldField->isReplaceable()) { + int32_t oldFieldIndex = __getIndexOfThing(*it); + __removeThing(oldField, 1); + + oldField->setParent(NULL); + g_game.FreeThing(oldField); + postRemoveNotification(oldField, NULL, oldFieldIndex, true); + break; + } else { + //This magic field cannot be replaced. + item->setParent(NULL); + g_game.FreeThing(item); + return; + } + } + } + } + } + + items = makeItemList(); + items->insert(items->getBeginDownItem(), item); + ++items->downItemCount; + ++thingCount; + onAddTileItem(item); + } + } +} + +void Tile::__updateThing(Thing* thing, uint16_t itemId, uint32_t count) +{ + int32_t index = __getIndexOfThing(thing); + + if (index == -1) { + return /*RET_NOTPOSSIBLE*/; + } + + Item* item = thing->getItem(); + + if (item == NULL) { + return /*RET_NOTPOSSIBLE*/; + } + + const ItemType& oldType = Item::items[item->getID()]; + + const ItemType& newType = Item::items[itemId]; + + updateTileFlags(item, true); + + item->setID(itemId); + + item->setSubType(count); + + updateTileFlags(item, false); + + onUpdateTileItem(item, oldType, item, newType); +} + +void Tile::__replaceThing(uint32_t index, Thing* thing) +{ + int32_t pos = index; + + Item* item = thing->getItem(); + + if (item == NULL) { + return /*RET_NOTPOSSIBLE*/; + } + + Item* oldItem = NULL; + bool isInserted = false; + + if (ground) { + if (pos == 0) { + oldItem = ground; + ground = item; + isInserted = true; + } + + --pos; + } + + TileItemVector* items = getItemList(); + + if (items && !isInserted) { + int32_t topItemSize = getTopItemCount(); + + if (pos < topItemSize) { + ItemVector::iterator it = items->getBeginTopItem(); + it += pos; + + oldItem = (*it); + it = items->erase(it); + items->insert(it, item); + isInserted = true; + } + + pos -= topItemSize; + } + + CreatureVector* creatures = getCreatures(); + + if (creatures) { + if (!isInserted && pos < (int32_t)creatures->size()) { + return /*RET_NOTPOSSIBLE*/; + } + + pos -= (uint32_t)creatures->size(); + } + + if (items && !isInserted) { + int32_t downItemSize = getDownItemCount(); + + if (pos < downItemSize) { + ItemVector::iterator it = items->begin(); + it += pos; + pos = 0; + + oldItem = (*it); + it = items->erase(it); + items->insert(it, item); + isInserted = true; + } + } + + if (isInserted) { + item->setParent(this); + + updateTileFlags(oldItem, true); + updateTileFlags(item, false); + const ItemType& oldType = Item::items[oldItem->getID()]; + const ItemType& newType = Item::items[item->getID()]; + onUpdateTileItem(oldItem, oldType, item, newType); + + oldItem->setParent(NULL); + return /*RET_NOERROR*/; + } +} + +void Tile::__removeThing(Thing* thing, uint32_t count) +{ + Creature* creature = thing->getCreature(); + + if (creature) { + CreatureVector* creatures = getCreatures(); + + if (creatures) { + CreatureVector::iterator it = std::find(creatures->begin(), creatures->end(), thing); + + if (it != creatures->end()) { + g_game.clearSpectatorCache(); + creatures->erase(it); + --thingCount; + } + } + + return; + } + + Item* item = thing->getItem(); + + if (item) { + int32_t index = __getIndexOfThing(item); + + if (index == -1) { + return; + } + + if (item == ground) { + const SpectatorVec& list = g_game.getSpectators(getPosition()); + + std::vector oldStackPosVector; + + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + if (Player* tmpPlayer = (*it)->getPlayer()) { + oldStackPosVector.push_back(getClientIndexOfThing(tmpPlayer, ground)); + } + } + + ground->setParent(NULL); + ground = NULL; + --thingCount; + onRemoveTileItem(list, oldStackPosVector, item); + return; + } + + if (item->isAlwaysOnTop()) { + TileItemVector* items = getItemList(); + + if (items) { + for (ItemVector::iterator it = items->getBeginTopItem(); it != items->getEndTopItem(); ++it) { + if (*it == item) { + const SpectatorVec& list = g_game.getSpectators(getPosition()); + + std::vector oldStackPosVector; + + for (SpectatorVec::const_iterator iit = list.begin(), iend = list.end(); iit != iend; ++iit) { + if (Player* tmpPlayer = (*iit)->getPlayer()) { + oldStackPosVector.push_back(getClientIndexOfThing(tmpPlayer, *it)); + } + } + + (*it)->setParent(NULL); + items->erase(it); + --thingCount; + onRemoveTileItem(list, oldStackPosVector, item); + return; + } + } + } + } else { + TileItemVector* items = getItemList(); + + if (items) { + for (ItemVector::iterator it = items->getBeginDownItem(); it != items->getEndDownItem(); ++it) { + if (*it == item) { + if (item->isStackable() && count != item->getItemCount()) { + uint8_t newCount = (uint8_t)std::max(0, (int32_t)(item->getItemCount() - count)); + + updateTileFlags(item, true); + item->setItemCount(newCount); + updateTileFlags(item, false); + + const ItemType& it = Item::items[item->getID()]; + onUpdateTileItem(item, it, item, it); + } else { + const SpectatorVec& list = g_game.getSpectators(getPosition()); + + std::vector oldStackPosVector; + + for (SpectatorVec::const_iterator iit = list.begin(), iend = list.end(); iit != iend; ++iit) { + if (Player* tmpPlayer = (*iit)->getPlayer()) { + oldStackPosVector.push_back(getClientIndexOfThing(tmpPlayer, *it)); + } + } + + (*it)->setParent(NULL); + items->erase(it); + --items->downItemCount; + --thingCount; + onRemoveTileItem(list, oldStackPosVector, item); + } + + return; + } + } + } + } + } +} + +int32_t Tile::__getIndexOfThing(const Thing* thing) const +{ + int n = -1; + + if (ground) { + if (ground == thing) { + return 0; + } + + ++n; + } + + const TileItemVector* items = getItemList(); + + if (items) { + if (thing->getItem()) { + for (ItemVector::const_iterator it = items->getBeginTopItem(); it != items->getEndTopItem(); ++it) { + ++n; + + if ((*it) == thing) { + return n; + } + } + } else { + n += items->getTopItemCount(); + } + } + + if (const CreatureVector* creatures = getCreatures()) { + if (thing->getCreature()) { + for (CreatureVector::const_iterator cit = creatures->begin(); cit != creatures->end(); ++cit) { + ++n; + + if ((*cit) == thing) { + return n; + } + } + } else { + n += creatures->size(); + } + } + + if (items) { + if (thing->getItem()) { + for (ItemVector::const_iterator it = items->getBeginDownItem(); it != items->getEndDownItem(); ++it) { + ++n; + + if ((*it) == thing) { + return n; + } + } + } else { + n += items->getDownItemCount(); + } + } + + return -1; +} + +int32_t Tile::getClientIndexOfThing(const Player* player, const Thing* thing) const +{ + int n = -1; + + if (ground) { + if (ground == thing) { + return 0; + } + + ++n; + } + + const TileItemVector* items = getItemList(); + + if (items) { + if (thing->getItem()) { + for (ItemVector::const_iterator it = items->getBeginTopItem(); it != items->getEndTopItem(); ++it) { + ++n; + + if ((*it) == thing) { + return n; + } + } + } else { + n += items->getTopItemCount(); + } + } + + if (const CreatureVector* creatures = getCreatures()) { + for (CreatureVector::const_reverse_iterator cit = creatures->rbegin(); cit != creatures->rend(); ++cit) { + if ((*cit) == thing || !(*cit)->isInGhostMode() || player->isAccessPlayer()) { + ++n; + } + + if ((*cit) == thing) { + return n; + } + } + } + + if (items) { + if (thing->getItem()) { + for (ItemVector::const_iterator it = items->getBeginDownItem(); it != items->getEndDownItem(); ++it) { + ++n; + + if ((*it) == thing) { + return n; + } + } + } else { + n += items->getDownItemCount(); + } + } + + return -1; +} + +int32_t Tile::__getFirstIndex() const +{ + return 0; +} + +int32_t Tile::__getLastIndex() const +{ + return getThingCount(); +} + +uint32_t Tile::__getItemTypeCount(uint16_t itemId, int32_t subType /*= -1*/) const +{ + uint32_t count = 0; + + if (ground && ground->getID() == itemId) { + count += Item::countByType(ground, subType); + } + + const TileItemVector* items = getItemList(); + + if (items) { + for (ItemVector::const_iterator it = items->begin(), end = items->end(); it != end; ++it) { + Item* item = (*it); + + if (item->getID() == itemId) { + count += Item::countByType(item, subType); + } + } + } + + return count; +} + +Thing* Tile::__getThing(uint32_t index) const +{ + if (ground) { + if (index == 0) { + return ground; + } + + --index; + } + + const TileItemVector* items = getItemList(); + + if (items) { + uint32_t topItemSize = items->getTopItemCount(); + + if (index < topItemSize) { + return items->at(items->downItemCount + index); + } + + index -= topItemSize; + } + + if (const CreatureVector* creatures = getCreatures()) { + if (index < (uint32_t)creatures->size()) { + return creatures->at(index); + } + + index -= (uint32_t)creatures->size(); + } + + if (items) { + if (index < items->getDownItemCount()) { + return items->at(index); + } + } + + return NULL; +} + +void Tile::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) +{ + const Position& cylinderMapPos = getPosition(); + + SpectatorVec list; + g_game.getSpectators(list, cylinderMapPos, true, true); + + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + (*it)->getPlayer()->postAddNotification(thing, oldParent, index, LINK_NEAR); + } + + //add a reference to this item, it may be deleted after being added (mailbox for example) + thing->useThing2(); + + if (link == LINK_OWNER) { + //calling movement scripts + Creature* creature = thing->getCreature(); + + if (creature) { + g_moveEvents->onCreatureMove(creature, this, true); + } else { + Item* item = thing->getItem(); + + if (item) { + g_moveEvents->onItemMove(item, this, true); + } + } + + if (hasFlag(TILESTATE_TELEPORT)) { + Teleport* teleport = getTeleportItem(); + + if (teleport) { + teleport->__addThing(thing); + } + } else if (hasFlag(TILESTATE_TRASHHOLDER)) { + TrashHolder* trashholder = getTrashHolder(); + + if (trashholder) { + trashholder->__addThing(thing); + } + } else if (hasFlag(TILESTATE_MAILBOX)) { + Mailbox* mailbox = getMailbox(); + + if (mailbox) { + mailbox->__addThing(thing); + } + } + } + + //release the reference to this item onces we are finished + g_game.FreeThing(thing); +} + +void Tile::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, bool isCompleteRemoval, cylinderlink_t link /*= LINK_OWNER*/) +{ + const Position& cylinderMapPos = getPosition(); + + SpectatorVec list; + g_game.getSpectators(list, cylinderMapPos, true, true); + + if (/*isCompleteRemoval &&*/ getThingCount() > 8) { + onUpdateTile(list); + } + + for (SpectatorVec::const_iterator it = list.begin(), end = list.end(); it != end; ++it) { + (*it)->getPlayer()->postRemoveNotification(thing, newParent, index, isCompleteRemoval, LINK_NEAR); + } + + //calling movement scripts + Creature* creature = thing->getCreature(); + + if (creature) { + g_moveEvents->onCreatureMove(creature, this, false); + } else { + Item* item = thing->getItem(); + + if (item) { + g_moveEvents->onItemMove(item, this, false); + } + } +} + +void Tile::__internalAddThing(Thing* thing) +{ + __internalAddThing(0, thing); +} + +void Tile::__internalAddThing(uint32_t index, Thing* thing) +{ + thing->setParent(this); + + Creature* creature = thing->getCreature(); + + if (creature) { + g_game.clearSpectatorCache(); + CreatureVector* creatures = makeCreatures(); + creatures->insert(creatures->begin(), creature); + ++thingCount; + } else { + Item* item = thing->getItem(); + + if (item == NULL) { + return; + } + + TileItemVector* items = makeItemList(); + + if (items && items->size() >= 0xFFFF) { + return /*RET_NOTPOSSIBLE*/; + } + + if (item->isGroundTile()) { + if (ground == NULL) { + ground = item; + ++thingCount; + } + } else if (item->isAlwaysOnTop()) { + bool isInserted = false; + + for (ItemVector::iterator it = items->getBeginTopItem(); it != items->getEndTopItem(); ++it) { + if (Item::items[(*it)->getID()].alwaysOnTopOrder > Item::items[item->getID()].alwaysOnTopOrder) { + items->insert(it, item); + ++thingCount; + isInserted = true; + break; + } + } + + if (!isInserted) { + items->push_back(item); + ++thingCount; + } + } else { + items->insert(items->getBeginDownItem(), item); + ++items->downItemCount; + ++thingCount; + } + + updateTileFlags(item, false); + } +} + +void Tile::updateTileFlags(Item* item, bool removing) +{ + if (!removing) { + //!removing is adding an item to the tile + if (!hasFlag(TILESTATE_FLOORCHANGE)) { + if (item->floorChangeDown()) { + setFlag(TILESTATE_FLOORCHANGE); + setFlag(TILESTATE_FLOORCHANGE_DOWN); + } + + if (item->floorChangeNorth()) { + setFlag(TILESTATE_FLOORCHANGE); + setFlag(TILESTATE_FLOORCHANGE_NORTH); + } + + if (item->floorChangeSouth()) { + setFlag(TILESTATE_FLOORCHANGE); + setFlag(TILESTATE_FLOORCHANGE_SOUTH); + } + + if (item->floorChangeEast()) { + setFlag(TILESTATE_FLOORCHANGE); + setFlag(TILESTATE_FLOORCHANGE_EAST); + } + + if (item->floorChangeWest()) { + setFlag(TILESTATE_FLOORCHANGE); + setFlag(TILESTATE_FLOORCHANGE_WEST); + } + + if (item->floorChangeSouthAlt()) { + setFlag(TILESTATE_FLOORCHANGE); + setFlag(TILESTATE_FLOORCHANGE_SOUTH_ALT); + } + + if (item->floorChangeEastAlt()) { + setFlag(TILESTATE_FLOORCHANGE); + setFlag(TILESTATE_FLOORCHANGE_EAST_ALT); + } + } + + if (item->hasProperty(IMMOVABLEBLOCKSOLID)) { + setFlag(TILESTATE_IMMOVABLEBLOCKSOLID); + } + + if (item->hasProperty(BLOCKPATH)) { + setFlag(TILESTATE_BLOCKPATH); + } + + if (item->hasProperty(NOFIELDBLOCKPATH)) { + setFlag(TILESTATE_NOFIELDBLOCKPATH); + } + + if (item->hasProperty(IMMOVABLENOFIELDBLOCKPATH)) { + setFlag(TILESTATE_IMMOVABLENOFIELDBLOCKPATH); + } + + if (item->getTeleport()) { + setFlag(TILESTATE_TELEPORT); + } + + if (item->getMagicField()) { + setFlag(TILESTATE_MAGICFIELD); + } + + if (item->getMailbox()) { + setFlag(TILESTATE_MAILBOX); + } + + if (item->getTrashHolder()) { + setFlag(TILESTATE_TRASHHOLDER); + } + + if (item->hasProperty(BLOCKSOLID)) { + setFlag(TILESTATE_BLOCKSOLID); + } + + if (item->getBed()) { + setFlag(TILESTATE_BED); + } + + if (item->getContainer() && item->getContainer()->getDepotLocker()) { + setFlag(TILESTATE_DEPOT); + } + } else { + if (item->floorChangeDown()) { + resetFlag(TILESTATE_FLOORCHANGE); + resetFlag(TILESTATE_FLOORCHANGE_DOWN); + } + + if (item->floorChangeNorth()) { + resetFlag(TILESTATE_FLOORCHANGE); + resetFlag(TILESTATE_FLOORCHANGE_NORTH); + } + + if (item->floorChangeSouth()) { + resetFlag(TILESTATE_FLOORCHANGE); + resetFlag(TILESTATE_FLOORCHANGE_SOUTH); + } + + if (item->floorChangeEast()) { + resetFlag(TILESTATE_FLOORCHANGE); + resetFlag(TILESTATE_FLOORCHANGE_EAST); + } + + if (item->floorChangeWest()) { + resetFlag(TILESTATE_FLOORCHANGE); + resetFlag(TILESTATE_FLOORCHANGE_WEST); + } + + if (item->floorChangeSouthAlt()) { + resetFlag(TILESTATE_FLOORCHANGE); + resetFlag(TILESTATE_FLOORCHANGE_SOUTH_ALT); + } + + if (item->floorChangeEastAlt()) { + resetFlag(TILESTATE_FLOORCHANGE); + resetFlag(TILESTATE_FLOORCHANGE_EAST_ALT); + } + + if (item->hasProperty(BLOCKSOLID) && !hasProperty(item, BLOCKSOLID)) { + resetFlag(TILESTATE_BLOCKSOLID); + } + + if (item->hasProperty(IMMOVABLEBLOCKSOLID) && !hasProperty(item, IMMOVABLEBLOCKSOLID)) { + resetFlag(TILESTATE_IMMOVABLEBLOCKSOLID); + } + + if (item->hasProperty(BLOCKPATH) && !hasProperty(item, BLOCKPATH)) { + resetFlag(TILESTATE_BLOCKPATH); + } + + if (item->hasProperty(NOFIELDBLOCKPATH) && !hasProperty(item, NOFIELDBLOCKPATH)) { + resetFlag(TILESTATE_NOFIELDBLOCKPATH); + } + + if (item->hasProperty(IMMOVABLEBLOCKPATH) && !hasProperty(item, IMMOVABLEBLOCKPATH)) { + resetFlag(TILESTATE_IMMOVABLEBLOCKPATH); + } + + if (item->hasProperty(IMMOVABLENOFIELDBLOCKPATH) && !hasProperty(item, IMMOVABLENOFIELDBLOCKPATH)) { + resetFlag(TILESTATE_IMMOVABLENOFIELDBLOCKPATH); + } + + if (item->getTeleport()) { + resetFlag(TILESTATE_TELEPORT); + } + + if (item->getMagicField()) { + resetFlag(TILESTATE_MAGICFIELD); + } + + if (item->getMailbox()) { + resetFlag(TILESTATE_MAILBOX); + } + + if (item->getTrashHolder()) { + resetFlag(TILESTATE_TRASHHOLDER); + } + + if (item->getBed()) { + resetFlag(TILESTATE_BED); + } + + if (item->getContainer() && item->getContainer()->getDepotLocker()) { + resetFlag(TILESTATE_DEPOT); + } + } +} + +bool Tile::isMoveableBlocking() const +{ + if (!ground || hasFlag(TILESTATE_BLOCKSOLID)) { + return true; + } + + return false; +} + +int32_t Tile::getNewCreatureStackpos(const Player* player) +{ + int32_t n = -1; + + if (ground) { + ++n; + } + + const TileItemVector* items = getItemList(); + + if (items) { + n += items->getTopItemCount(); + } + + if (const CreatureVector* creatures = getCreatures()) { + for (CreatureVector::const_reverse_iterator cit = creatures->rbegin(); cit != creatures->rend(); ++cit) { + if (!(*cit)->isInGhostMode() || player->isAccessPlayer()) { + ++n; + } + } + } + + return n + 1; +} diff --git a/src/tile.h b/src/tile.h new file mode 100644 index 0000000000..899109b20a --- /dev/null +++ b/src/tile.h @@ -0,0 +1,533 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_TILE_H__ +#define __OTSERV_TILE_H__ + +#include + +#include "cylinder.h" +#include "item.h" +#include "tools.h" + +class Creature; +class Teleport; +class TrashHolder; +class Mailbox; +class MagicField; +class QTreeLeafNode; +class BedItem; + +typedef std::vector CreatureVector; +typedef OTSERV_HASH_SET SpectatorVec; +typedef std::map > SpectatorCache; +typedef std::vector ItemVector; + +enum tileflags_t { + TILESTATE_NONE = 0, + TILESTATE_PROTECTIONZONE = 1, + TILESTATE_DEPRECATED_HOUSE = 2, + TILESTATE_NOPVPZONE = 4, + TILESTATE_NOLOGOUT = 8, + TILESTATE_PVPZONE = 16, + TILESTATE_REFRESH = 32, + + //internal usage + TILESTATE_HOUSE = 64, + TILESTATE_FLOORCHANGE = 128, + TILESTATE_FLOORCHANGE_DOWN = 256, + TILESTATE_FLOORCHANGE_NORTH = 512, + TILESTATE_FLOORCHANGE_SOUTH = 1024, + TILESTATE_FLOORCHANGE_EAST = 2048, + TILESTATE_FLOORCHANGE_WEST = 4096, + TILESTATE_TELEPORT = 8192, + TILESTATE_MAGICFIELD = 16384, + TILESTATE_MAILBOX = 32768, + TILESTATE_TRASHHOLDER = 65536, + TILESTATE_BED = 131072, + TILESTATE_DEPOT = 262144, + TILESTATE_BLOCKSOLID = 524288, + TILESTATE_BLOCKPATH = 1048576, + TILESTATE_IMMOVABLEBLOCKSOLID = 2097152, + TILESTATE_IMMOVABLEBLOCKPATH = 4194304, + TILESTATE_IMMOVABLENOFIELDBLOCKPATH = 8388608, + TILESTATE_NOFIELDBLOCKPATH = 16777216, + TILESTATE_DYNAMIC_TILE = 33554432, + TILESTATE_FLOORCHANGE_SOUTH_ALT = 67108864, + TILESTATE_FLOORCHANGE_EAST_ALT = 134217728 +}; + +enum ZoneType_t { + ZONE_PROTECTION, + ZONE_NOPVP, + ZONE_PVP, + ZONE_NOLOGOUT, + ZONE_NORMAL +}; + +class TileItemVector +{ + public: + TileItemVector() : downItemCount(0) {}; + ~TileItemVector() {}; + + ItemVector::iterator begin() { + return items.begin(); + } + ItemVector::const_iterator begin() const { + return items.begin(); + } + ItemVector::reverse_iterator rbegin() { + return items.rbegin(); + } + ItemVector::const_reverse_iterator rbegin() const { + return items.rbegin(); + } + + ItemVector::iterator end() { + return items.end(); + } + ItemVector::const_iterator end() const { + return items.end(); + } + ItemVector::reverse_iterator rend() { + return items.rend(); + } + ItemVector::const_reverse_iterator rend() const { + return items.rend(); + } + + size_t size() { + return items.size(); + } + size_t size() const { + return items.size(); + } + + ItemVector::iterator insert(ItemVector::iterator _where, Item* item) { + return items.insert(_where, item); + } + ItemVector::iterator erase(ItemVector::iterator _pos) { + return items.erase(_pos); + } + Item* at(size_t _pos) { + return items.at(_pos); + } + Item* at(size_t _pos) const { + return items.at(_pos); + } + Item* back() { + return items.back(); + } + const Item* back() const { + return items.back(); + } + void push_back(Item* item) { + return items.push_back(item); + } + + ItemVector::iterator getBeginDownItem() { + return items.begin(); + } + ItemVector::const_iterator getBeginDownItem() const { + return items.begin(); + } + ItemVector::iterator getEndDownItem() { + return items.begin() + downItemCount; + } + ItemVector::const_iterator getEndDownItem() const { + return items.begin() + downItemCount; + } + + ItemVector::iterator getBeginTopItem() { + return items.begin() + downItemCount; + } + ItemVector::const_iterator getBeginTopItem() const { + return items.begin() + downItemCount; + } + ItemVector::iterator getEndTopItem() { + return items.end(); + } + ItemVector::const_iterator getEndTopItem() const { + return items.end(); + } + + uint32_t getTopItemCount() const { + return std::distance(getBeginTopItem(), getEndTopItem() ); + } + uint32_t getDownItemCount() const { + return std::distance(getBeginDownItem(), getEndDownItem() ); + } + Item* getTopTopItem(); + Item* getTopDownItem(); + + private: + ItemVector items; + uint16_t downItemCount; + + friend class Tile; +}; + +class Tile : public Cylinder +{ + public: + static Tile& null_tile; + Tile(uint16_t x, uint16_t y, uint16_t z); + ~Tile(); + + TileItemVector* getItemList(); + const TileItemVector* getItemList() const; + TileItemVector* makeItemList(); + + CreatureVector* getCreatures(); + const CreatureVector* getCreatures() const; + CreatureVector* makeCreatures(); + + virtual int32_t getThrowRange() const { + return 0; + } + virtual bool isPushable() const { + return false; + } + + MagicField* getFieldItem() const; + Teleport* getTeleportItem() const; + TrashHolder* getTrashHolder() const; + Mailbox* getMailbox() const; + BedItem* getBedItem() const; + + Creature* getTopCreature(); + const Creature* getTopCreature() const; + const Creature* getBottomCreature() const; + Creature* getTopVisibleCreature(const Creature* creature); + const Creature* getTopVisibleCreature(const Creature* creature) const; + const Creature* getBottomVisibleCreature(const Creature* creature) const; + Item* getTopTopItem(); + Item* getTopDownItem(); + bool isMoveableBlocking() const; + Thing* getTopVisibleThing(const Creature* creature); + Item* getItemByTopOrder(int32_t topOrder); + + uint32_t getThingCount() const { + return thingCount; + } + // If these return != 0 the associated vectors are guaranteed to exists + uint32_t getCreatureCount() const; + uint32_t getItemCount() const; + uint32_t getTopItemCount() const; + uint32_t getDownItemCount() const; + + bool hasProperty(enum ITEMPROPERTY prop) const; + bool hasProperty(Item* exclude, enum ITEMPROPERTY prop) const; + + bool hasFlag(tileflags_t flag) const { + return hasBitSet(flag, m_flags); + } + void setFlag(tileflags_t flag) { + m_flags |= (uint32_t)flag; + } + void resetFlag(tileflags_t flag) { + m_flags &= ~(uint32_t)flag; + } + + bool positionChange() const { + return hasFlag(TILESTATE_TELEPORT); + } + bool floorChange() const { + return hasFlag(TILESTATE_FLOORCHANGE); + } + bool floorChangeDown() const { + return hasFlag(TILESTATE_FLOORCHANGE_DOWN); + } + bool floorChange(Direction direction) const { + switch (direction) { + case NORTH: + return hasFlag(TILESTATE_FLOORCHANGE_NORTH); + + case SOUTH: + return hasFlag(TILESTATE_FLOORCHANGE_SOUTH); + + case EAST: + return hasFlag(TILESTATE_FLOORCHANGE_EAST); + + case WEST: + return hasFlag(TILESTATE_FLOORCHANGE_WEST); + + case SOUTH_ALT: + return hasFlag(TILESTATE_FLOORCHANGE_SOUTH_ALT); + + case EAST_ALT: + return hasFlag(TILESTATE_FLOORCHANGE_EAST_ALT); + + default: + return false; + } + } + + ZoneType_t getZone() const { + if (hasFlag(TILESTATE_PROTECTIONZONE)) { + return ZONE_PROTECTION; + } else if (hasFlag(TILESTATE_NOPVPZONE)) { + return ZONE_NOPVP; + } else if (hasFlag(TILESTATE_PVPZONE)) { + return ZONE_PVP; + } else { + return ZONE_NORMAL; + } + } + + bool hasHeight(uint32_t n) const; + + virtual std::string getDescription(int32_t lookDistance) const; + + void moveCreature(Creature* creature, Cylinder* toCylinder, bool forceTeleport = false); + int32_t getClientIndexOfThing(const Player* player, const Thing* thing) const; + + int32_t getNewCreatureStackpos(const Player* player); + + //cylinder implementations + virtual ReturnValue __queryAdd(int32_t index, const Thing* thing, uint32_t count, + uint32_t flags, Creature* actor = NULL) const; + virtual ReturnValue __queryMaxCount(int32_t index, const Thing* thing, uint32_t count, + uint32_t& maxQueryCount, uint32_t flags) const; + virtual ReturnValue __queryRemove(const Thing* thing, uint32_t count, uint32_t flags) const; + virtual Cylinder* __queryDestination(int32_t& index, const Thing* thing, Item** destItem, + uint32_t& flags); + + virtual void __addThing(Thing* thing); + virtual void __addThing(int32_t index, Thing* thing); + + virtual void __updateThing(Thing* thing, uint16_t itemId, uint32_t count); + virtual void __replaceThing(uint32_t index, Thing* thing); + + virtual void __removeThing(Thing* thing, uint32_t count); + + virtual int32_t __getIndexOfThing(const Thing* thing) const; + virtual int32_t __getFirstIndex() const; + virtual int32_t __getLastIndex() const; + virtual uint32_t __getItemTypeCount(uint16_t itemId, int32_t subType = -1) const; + virtual Thing* __getThing(uint32_t index) const; + + virtual void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER); + virtual void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, bool isCompleteRemoval, cylinderlink_t link = LINK_OWNER); + + virtual void __internalAddThing(Thing* thing); + virtual void __internalAddThing(uint32_t index, Thing* thing); + + virtual const Position& getPosition() const { + return tilePos; + } + const Position& getTilePosition() const { + return tilePos; + } + + virtual bool isRemoved() const { + return false; + } + + private: + void onAddTileItem(Item* item); + void onUpdateTileItem(Item* oldItem, const ItemType& oldType, Item* newItem, const ItemType& newType); + void onRemoveTileItem(const SpectatorVec& list, std::vector& oldStackPosVector, Item* item); + void onUpdateTile(const SpectatorVec& list); + + void updateTileFlags(Item* item, bool removing); + + protected: + // Put this first for cache-coherency + bool is_dynamic() const { + return (m_flags & TILESTATE_DYNAMIC_TILE) != 0; + } + + public: + QTreeLeafNode* qt_node; + Item* ground; + + protected: + uint32_t thingCount; + Position tilePos; + uint32_t m_flags; +}; + +// Used for walkable tiles, where there is high likeliness of +// items being added/removed +class DynamicTile : public Tile +{ + // By allocating the vectors in-house, we avoid some memory fragmentation + TileItemVector items; + CreatureVector creatures; + + public: + DynamicTile(uint16_t x, uint16_t y, uint16_t z); + ~DynamicTile(); + + TileItemVector* getItemList() { + return &items; + } + const TileItemVector* getItemList() const { + return &items; + } + TileItemVector* makeItemList() { + return &items; + } + + CreatureVector* getCreatures() { + return &creatures; + } + const CreatureVector* getCreatures() const { + return &creatures; + } + CreatureVector* makeCreatures() { + return &creatures; + } +}; + +// For blocking tiles, where we very rarely actually have items +class StaticTile : public Tile +{ + // We very rarely even need the vectors, so don't keep them in memory + TileItemVector* items; + CreatureVector* creatures; + + public: + StaticTile(uint16_t x, uint16_t y, uint16_t z); + ~StaticTile(); + + TileItemVector* getItemList() { + return items; + } + const TileItemVector* getItemList() const { + return items; + } + TileItemVector* makeItemList() { + return (items) ? (items) : (items = new TileItemVector); + } + + CreatureVector* getCreatures() { + return creatures; + } + const CreatureVector* getCreatures() const { + return creatures; + } + CreatureVector* makeCreatures() { + return (creatures) ? (creatures) : (creatures = new CreatureVector); + } +}; + +inline Tile::Tile(uint16_t x, uint16_t y, uint16_t z) : + qt_node(NULL), + ground(NULL), + thingCount(0), + tilePos(x, y, z), + m_flags(0) +{ +} + +inline Tile::~Tile() +{ + delete ground; +} + +inline CreatureVector* Tile::getCreatures() +{ + if (is_dynamic()) { + return static_cast(this)->DynamicTile::getCreatures(); + } + + return static_cast(this)->StaticTile::getCreatures(); +} + +inline const CreatureVector* Tile::getCreatures() const +{ + if (is_dynamic()) { + return static_cast(this)->DynamicTile::getCreatures(); + } + + return static_cast(this)->StaticTile::getCreatures(); +} + +inline TileItemVector* Tile::getItemList() +{ + if (is_dynamic()) { + return static_cast(this)->DynamicTile::getItemList(); + } + + return static_cast(this)->StaticTile::getItemList(); +} + +inline const TileItemVector* Tile::getItemList() const +{ + if (is_dynamic()) { + return static_cast(this)->DynamicTile::getItemList(); + } + + return static_cast(this)->StaticTile::getItemList(); +} + +inline CreatureVector* Tile::makeCreatures() +{ + if (is_dynamic()) { + return static_cast(this)->DynamicTile::makeCreatures(); + } + + return static_cast(this)->StaticTile::makeCreatures(); +} + +inline TileItemVector* Tile::makeItemList() +{ + if (is_dynamic()) { + return static_cast(this)->DynamicTile::makeItemList(); + } + + return static_cast(this)->StaticTile::makeItemList(); +} + +inline StaticTile::StaticTile(uint16_t x, uint16_t y, uint16_t z) : + Tile(x, y, z), + items(NULL), + creatures(NULL) +{ +} + +inline StaticTile::~StaticTile() +{ + if (items) { + for (ItemVector::const_iterator it = items->begin(); it != items->end(); ++it) { + (*it)->releaseThing2(); + } + + delete items; + } + + delete creatures; +} + +inline DynamicTile::DynamicTile(uint16_t x, uint16_t y, uint16_t z) : + Tile(x, y, z) +{ + m_flags |= TILESTATE_DYNAMIC_TILE; +} + +inline DynamicTile::~DynamicTile() +{ + for (ItemVector::const_iterator it = items.begin(); it != items.end(); ++it) { + (*it)->releaseThing2(); + } +} + +#endif diff --git a/src/tools.cpp b/src/tools.cpp new file mode 100644 index 0000000000..cf7b754d1d --- /dev/null +++ b/src/tools.cpp @@ -0,0 +1,1247 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "definitions.h" +#include "otsystem.h" + +#include "tools.h" +#include "configmanager.h" +#include "md5.h" +#include "sha1.h" + +#include +#include + +#if defined __GNUC__ && __GNUC__ > 3 +#include +#endif + +extern ConfigManager g_config; + +std::string transformToSHA1(const std::string& plainText, bool upperCase /*= false*/) +{ + SHA1 sha1; + unsigned sha1Hash[5]; + std::ostringstream hexStream; + + sha1.Input((const unsigned char*)plainText.c_str(), plainText.length()); + sha1.Result(sha1Hash); + + hexStream.flags(std::ios::hex | std::ios::uppercase); + + for (uint32_t i = 0; i < 5; ++i) { + hexStream << std::setw(8) << std::setfill('0') << (uint32_t)sha1Hash[i]; + } + + std::string hexStr = hexStream.str(); + + if (!upperCase) { + toLowerCaseString(hexStr); + } + + return hexStr; +} + +std::string transformToMD5(const std::string& plainText, bool upperCase /*= false*/) +{ + MD5_CTX m_md5; + std::ostringstream hexStream; + + MD5Init(&m_md5, 0); + MD5Update(&m_md5, (const unsigned char*)plainText.c_str(), plainText.length()); + MD5Final(&m_md5); + + hexStream.flags(std::ios::hex | std::ios::uppercase); + + for (uint32_t i = 0; i < 16; ++i) { + hexStream << std::setw(2) << std::setfill('0') << (uint32_t)m_md5.digest[i]; + } + + std::string hexStr = hexStream.str(); + + if (!upperCase) { + toLowerCaseString(hexStr); + } + + return hexStr; +} + +bool passwordTest(const std::string& plain, std::string& hash) +{ + switch (g_config.getNumber(ConfigManager::PASSWORD_TYPE)) { + case PASSWORD_TYPE_MD5: + std::transform(hash.begin(), hash.end(), hash.begin(), upchar); + return transformToMD5(plain, true) == hash; + + case PASSWORD_TYPE_SHA1: + std::transform(hash.begin(), hash.end(), hash.begin(), upchar); + return transformToSHA1(plain, true) == hash; + + default: + return plain == hash; + } +} + +void replaceString(std::string& str, const std::string& sought, const std::string& replacement) +{ + size_t pos = 0; + size_t start = 0; + size_t soughtLen = sought.length(); + size_t replaceLen = replacement.length(); + + while ((pos = str.find(sought, start)) != std::string::npos) { + str = str.substr(0, pos) + replacement + str.substr(pos + soughtLen); + start = pos + replaceLen; + } +} + +void trim_right(std::string& source, const std::string& t) +{ + source.erase(source.find_last_not_of(t) + 1); +} + +void trim_left(std::string& source, const std::string& t) +{ + source.erase(0, source.find_first_not_of(t)); +} + +void toLowerCaseString(std::string& source) +{ + std::transform(source.begin(), source.end(), source.begin(), tolower); +} + +void toUpperCaseString(std::string& source) +{ + std::transform(source.begin(), source.end(), source.begin(), upchar); +} + +std::string asLowerCaseString(const std::string& source) +{ + std::string s = source; + toLowerCaseString(s); + return s; +} + +std::string asUpperCaseString(const std::string& source) +{ + std::string s = source; + toUpperCaseString(s); + return s; +} + +bool readXMLInteger(xmlNodePtr node, const char* tag, int32_t& value) +{ + char* nodeValue = (char*)xmlGetProp(node, (xmlChar*)tag); + + if (nodeValue) { + value = atoi(nodeValue); + xmlFreeOTSERV(nodeValue); + return true; + } + + return false; +} + +bool readXMLInteger64(xmlNodePtr node, const char* tag, uint64_t& value) +{ + char* nodeValue = (char*)xmlGetProp(node, (xmlChar*)tag); + + if (nodeValue) { + value = ATOI64(nodeValue); + xmlFreeOTSERV(nodeValue); + return true; + } + + return false; +} + +bool readXMLFloat(xmlNodePtr node, const char* tag, float& value) +{ + char* nodeValue = (char*)xmlGetProp(node, (xmlChar*)tag); + + if (nodeValue) { + value = atof(nodeValue); + xmlFreeOTSERV(nodeValue); + return true; + } + + return false; +} + +bool utf8ToLatin1(const char* intext, std::string& outtext) +{ + outtext = ""; + + if (intext == NULL) { + return false; + } + + int32_t inlen = strlen(intext); + + if (inlen == 0) { + return false; + } + + int32_t outlen = (inlen << 1) + 1; + unsigned char* outbuf = new uint8_t[outlen]; + int32_t res = UTF8Toisolat1(outbuf, &outlen, (const unsigned char*)intext, &inlen); + + if (res < 0) { + delete[] outbuf; + return false; + } + + outtext = std::string((char*)outbuf, outlen); + delete[] outbuf; + return true; +} + +bool readXMLString(xmlNodePtr node, const char* tag, std::string& value) +{ + char* nodeValue = (char*)xmlGetProp(node, (xmlChar*)tag); + + if (!nodeValue) { + return false; + } + + if (!utf8ToLatin1(nodeValue, value)) { + value = nodeValue; + } + + xmlFreeOTSERV(nodeValue); + return true; +} + +bool readXMLContentString(xmlNodePtr node, std::string& value) +{ + char* nodeValue = (char*)xmlNodeGetContent(node); + + if (!nodeValue) { + return false; + } + + if (!utf8ToLatin1(nodeValue, value)) { + value = nodeValue; + } + + xmlFreeOTSERV(nodeValue); + return true; +} + +StringVec explodeString(const std::string& inString, const std::string& separator, int32_t limit/* = -1*/) +{ + StringVec returnVector; + std::string::size_type start = 0, end = 0; + + while (--limit != -1 && (end = inString.find(separator, start)) != std::string::npos) { + returnVector.push_back(inString.substr(start, end - start)); + start = end + separator.size(); + } + + returnVector.push_back(inString.substr(start)); + return returnVector; +} + +IntegerVec vectorAtoi(StringVec stringVector) +{ + IntegerVec returnVector; + + for (std::vector::iterator it = stringVector.begin(); it != stringVector.end(); ++it) { + returnVector.push_back(atoi(it->c_str())); + } + + return returnVector; +} + +bool hasBitSet(uint32_t flag, uint32_t flags) +{ + return ((flags & flag) == flag); +} + +#define RAND_MAX24 16777216 +uint32_t rand24b() +{ + return ((rand() << 12) ^ (rand())) & (0xFFFFFF); +} + +float box_muller(float m, float s) +{ + // normal random variate generator + // mean m, standard deviation s + + float y1; + static float y2; + static int use_last = 0; + + if (use_last) { // use value from previous call + y1 = y2; + use_last = 0; + } else { + float x1, x2, w; + + do { + double r1 = (((float)(rand()) / RAND_MAX)); + double r2 = (((float)(rand()) / RAND_MAX)); + + x1 = 2.0 * r1 - 1.0; + x2 = 2.0 * r2 - 1.0; + w = x1 * x1 + x2 * x2; + } while (w >= 1.0); + + w = sqrt((-2.0 * log(w)) / w); + y1 = x1 * w; + y2 = x2 * w; + use_last = 1; + } + + return(m + y1 * s); +} + +int32_t random_range(int32_t lowest_number, int32_t highest_number, DistributionType_t type /*= DISTRO_UNIFORM*/) +{ + if (highest_number == lowest_number) { + return lowest_number; + } + + if (lowest_number > highest_number) { + int32_t nTmp = highest_number; + highest_number = lowest_number; + lowest_number = nTmp; + } + + int32_t range = highest_number - lowest_number; + + if (type == DISTRO_UNIFORM) { + int32_t r = rand24b() % (range + 1); + return lowest_number + r; + } else if (type == DISTRO_NORMAL) { + float value = box_muller(0.5, 0.25); + + if (value < 0) { + value = 0; + } else if (value > 1) { + value = 1; + } + + return lowest_number + (int32_t)((float)range * value); + } else { + float r = 1.f - sqrt((1.f * rand24b()) / RAND_MAX24); + return lowest_number + (int32_t)((float)range * r); + } +} + +// Upcase a char. +char upchar(char c) +{ +#if defined(__GNUC__) && __GNUC__ >= 3 + return toupper(c); +#else + + if ((c >= 97 && c <= 122) || (c <= -1 && c >= -32 )) { + c -= 32; + } + + return c; +#endif +} + +bool isNumber(char character) +{ + return (character >= 48 && character <= 57); +} + +std::string trimString(std::string& str) +{ + str.erase(str.find_last_not_of(" ") + 1); + return str.erase(0, str.find_first_not_of(" ")); +} + +std::string parseParams(tokenizer::iterator& it, tokenizer::iterator end) +{ + std::string tmp; + + if (it == end) { + return ""; + } + + tmp = *it; + ++it; + + if (tmp[0] == '"') { + tmp.erase(0, 1); + + while (it != end && tmp[tmp.length() - 1] != '"') { + tmp += " " + *it; + ++it; + } + + if (tmp.length() > 0 && tmp[tmp.length() - 1] == '"') { + tmp.erase(tmp.length() - 1); + } + } + + return tmp; +} + +std::string convertIPToString(uint32_t ip) +{ + char buffer[17]; + int res = sprintf(buffer, "%u.%u.%u.%u", ip & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, (ip >> 24)); + + if (res < 0) { + return ""; + } + + return buffer; +} + +std::string formatDate(time_t time) +{ + char buffer[24]; + const tm* tms = localtime(&time); + int res; + + if (tms) { + res = sprintf(buffer, "%02d/%02d/%04d %02d:%02d:%02d", tms->tm_mday, tms->tm_mon + 1, tms->tm_year + 1900, tms->tm_hour, tms->tm_min, tms->tm_sec); + } else { + res = sprintf(buffer, "UNIX Time : %d", (int32_t)time); + } + + if (res < 0) { + return ""; + } + + return buffer; +} + +std::string formatDateShort(time_t time) +{ + char buffer[24]; + const tm* tms = localtime(&time); + int res; + + if (tms) { + res = strftime(buffer, 12, "%d %b %Y", tms); + + if (res == 0) { + return ""; + } + } else { + res = sprintf(buffer, "UNIX Time : %d", (int32_t)time); + + if (res < 0) { + return ""; + } + } + + return buffer; +} + +Direction getDirection(const std::string& string) +{ + Direction direction = NORTH; + + if (string == "north" || string == "n" || string == "0") { + direction = NORTH; + } else if (string == "east" || string == "e" || string == "1") { + direction = EAST; + } else if (string == "south" || string == "s" || string == "2") { + direction = SOUTH; + } else if (string == "west" || string == "w" || string == "3") { + direction = WEST; + } else if (string == "southwest" || string == "south west" || string == "south-west" || string == "sw" || string == "4") { + direction = SOUTHWEST; + } else if (string == "southeast" || string == "south east" || string == "south-east" || string == "se" || string == "5") { + direction = SOUTHEAST; + } else if (string == "northwest" || string == "north west" || string == "north-west" || string == "nw" || string == "6") { + direction = NORTHWEST; + } else if (string == "northeast" || string == "north east" || string == "north-east" || string == "ne" || string == "7") { + direction = NORTHEAST; + } + + return direction; +} + +Position getNextPosition(Direction direction, Position pos) +{ + switch (direction) { + case NORTH: + pos.y--; + break; + + case SOUTH: + pos.y++; + break; + + case WEST: + pos.x--; + break; + + case EAST: + pos.x++; + break; + + case SOUTHWEST: + pos.x--; + pos.y++; + break; + + case NORTHWEST: + pos.x--; + pos.y--; + break; + + case NORTHEAST: + pos.x++; + pos.y--; + break; + + case SOUTHEAST: + pos.x++; + pos.y++; + break; + + default: + break; + } + + return pos; +} + +Direction getDirectionTo(const Position& from, const Position& to) +{ + Direction dir; + + int x_offset = from.x - to.x; + + if (x_offset < 0) { + dir = EAST; + x_offset = std::abs(x_offset); + } else { + dir = WEST; + } + + int y_offset = from.y - to.y; + + if (y_offset >= 0) { + if (y_offset > x_offset) { + dir = NORTH; + } else if (y_offset == x_offset) { + if (dir == EAST) { + dir = NORTHEAST; + } else { + dir = NORTHWEST; + } + } + } else { + y_offset = std::abs(y_offset); + + if (y_offset > x_offset) { + dir = SOUTH; + } else if (y_offset == x_offset) { + if (dir == EAST) { + dir = SOUTHEAST; + } else { + dir = SOUTHWEST; + } + } + } + + return dir; +} + +struct MagicEffectNames { + const char* name; + MagicEffectClasses effect; +}; + +struct ShootTypeNames { + const char* name; + ShootType_t shoot; +}; + +struct CombatTypeNames { + const char* name; + CombatType_t combat; +}; + +struct AmmoTypeNames { + const char* name; + Ammo_t ammoType; +}; + +struct AmmoActionNames { + const char* name; + AmmoAction_t ammoAction; +}; + +MagicEffectNames magicEffectNames[] = { + {"redspark", NM_ME_DRAW_BLOOD}, + {"bluebubble", NM_ME_LOSE_ENERGY}, + {"poff", NM_ME_POFF}, + {"yellowspark", NM_ME_BLOCKHIT}, + {"explosionarea", NM_ME_EXPLOSION_AREA}, + {"explosion", NM_ME_EXPLOSION_DAMAGE}, + {"firearea", NM_ME_FIRE_AREA}, + {"yellowbubble", NM_ME_YELLOW_RINGS}, + {"greenbubble", NM_ME_POISON_RINGS}, + {"blackspark", NM_ME_HIT_AREA}, + {"teleport", NM_ME_TELEPORT}, + {"energy", NM_ME_ENERGY_DAMAGE}, + {"blueshimmer", NM_ME_MAGIC_ENERGY}, + {"redshimmer", NM_ME_MAGIC_BLOOD}, + {"greenshimmer", NM_ME_MAGIC_POISON}, + {"fire", NM_ME_HITBY_FIRE}, + {"greenspark", NM_ME_POISON}, + {"mortarea", NM_ME_MORT_AREA}, + {"greennote", NM_ME_SOUND_GREEN}, + {"rednote", NM_ME_SOUND_RED}, + {"poison", NM_ME_POISON_AREA}, + {"yellownote", NM_ME_SOUND_YELLOW}, + {"purplenote", NM_ME_SOUND_PURPLE}, + {"bluenote", NM_ME_SOUND_BLUE}, + {"whitenote", NM_ME_SOUND_WHITE}, + {"bubbles", NM_ME_BUBBLES}, + {"dice", NM_ME_CRAPS}, + {"giftwraps", NM_ME_GIFT_WRAPS}, + {"yellowfirework", NM_ME_FIREWORK_YELLOW}, + {"redfirework", NM_ME_FIREWORK_RED}, + {"bluefirework", NM_ME_FIREWORK_BLUE}, + {"stun", NM_ME_STUN}, + {"sleep", NM_ME_SLEEP}, + {"watercreature", NM_ME_WATERCREATURE}, + {"groundshaker", NM_ME_GROUNDSHAKER}, + {"hearts", NM_ME_HEARTS}, + {"fireattack", NM_ME_FIREATTACK}, + {"energyarea", NM_ME_ENERGY_AREA}, + {"smallclouds", NM_ME_SMALLCLOUDS}, + {"holydamage", NM_ME_HOLYDAMAGE}, + {"bigclouds", NM_ME_BIGCLOUDS}, + {"icearea", NM_ME_ICEAREA}, + {"icetornado", NM_ME_ICETORNADO}, + {"iceattack", NM_ME_ICEATTACK}, + {"stones", NM_ME_STONES}, + {"smallplants", NM_ME_SMALLPLANTS}, + {"carniphila", NM_ME_CARNIPHILA}, + {"purpleenergy", NM_ME_PURPLEENERGY}, + {"yellowenergy", NM_ME_YELLOWENERGY}, + {"holyarea", NM_ME_HOLYAREA}, + {"bigplants", NM_ME_BIGPLANTS}, + {"cake", NM_ME_CAKE}, + {"giantice", NM_ME_GIANTICE}, + {"watersplash", NM_ME_WATERSPLASH}, + {"plantattack", NM_ME_PLANTATTACK}, + {"tutorialarrow", NM_ME_TUTORIALARROW}, + {"tutorialsquare", NM_ME_TUTORIALSQUARE}, + {"mirrorhorizontal", NM_ME_MIRRORHORIZONTAL}, + {"mirrorvertical", NM_ME_MIRRORVERTICAL}, + {"skullhorizontal", NM_ME_SKULLHORIZONTAL}, + {"skullvertical", NM_ME_SKULLVERTICAL}, + {"assassin", NM_ME_ASSASSIN}, + {"stepshorizontal", NM_ME_STEPSHORIZONTAL}, + {"bloodysteps", NM_ME_BLOODYSTEPS}, + {"stepsvertical", NM_ME_STEPSVERTICAL}, + {"yalaharighost", NM_ME_YALAHARIGHOST}, + {"bats", NM_ME_BATS}, + {"smoke", NM_ME_SMOKE}, + {"insects", NM_ME_INSECTS}, + {"dragonhead", NM_ME_DRAGONHEAD}, + {"orcshaman", NM_ME_ORCSHAMAN}, + {"orcshamanfire", NM_ME_ORCSHAMAN_FIRE}, + {"thunder", NM_ME_THUNDER}, + {"ferumbras", NM_ME_FERUMBRAS}, + {"confettihorizontal", NM_ME_CONFETTI_HORIZONTAL}, + {"confettivertical", NM_ME_CONFETTI_VERTICAL}, + {"blacksmoke", NM_ME_BLACKSMOKE} +}; + +ShootTypeNames shootTypeNames[] = { + {"spear", NM_SHOOT_SPEAR}, + {"bolt", NM_SHOOT_BOLT}, + {"arrow", NM_SHOOT_ARROW}, + {"fire", NM_SHOOT_FIRE}, + {"energy", NM_SHOOT_ENERGY}, + {"poisonarrow", NM_SHOOT_POISONARROW}, + {"burstarrow", NM_SHOOT_BURSTARROW}, + {"throwingstar", NM_SHOOT_THROWINGSTAR}, + {"throwingknife", NM_SHOOT_THROWINGKNIFE}, + {"smallstone", NM_SHOOT_SMALLSTONE}, + {"death", NM_SHOOT_DEATH}, + {"largerock", NM_SHOOT_LARGEROCK}, + {"snowball", NM_SHOOT_SNOWBALL}, + {"powerbolt", NM_SHOOT_POWERBOLT}, + {"poison", NM_SHOOT_POISONFIELD}, + {"infernalbolt", NM_SHOOT_INFERNALBOLT}, + {"huntingspear", NM_SHOOT_HUNTINGSPEAR}, + {"enchantedspear", NM_SHOOT_ENCHANTEDSPEAR}, + {"redstar", NM_SHOOT_REDSTAR}, + {"greenstar", NM_SHOOT_GREENSTAR}, + {"royalspear", NM_SHOOT_ROYALSPEAR}, + {"sniperarrow", NM_SHOOT_SNIPERARROW}, + {"onyxarrow", NM_SHOOT_ONYXARROW}, + {"piercingbolt", NM_SHOOT_PIERCINGBOLT}, + {"whirlwindsword", NM_SHOOT_WHIRLWINDSWORD}, + {"whirlwindaxe", NM_SHOOT_WHIRLWINDAXE}, + {"whirlwindclub", NM_SHOOT_WHIRLWINDCLUB}, + {"etherealspear", NM_SHOOT_ETHEREALSPEAR}, + {"ice", NM_SHOOT_ICE}, + {"earth", NM_SHOOT_EARTH}, + {"holy", NM_SHOOT_HOLY}, + {"suddendeath", NM_SHOOT_SUDDENDEATH}, + {"flasharrow", NM_SHOOT_FLASHARROW}, + {"flammingarrow", NM_SHOOT_FLAMMINGARROW}, + {"shiverarrow", NM_SHOOT_SHIVERARROW}, + {"energyball", NM_SHOOT_ENERGYBALL}, + {"smallice", NM_SHOOT_SMALLICE}, + {"smallholy", NM_SHOOT_SMALLHOLY}, + {"smallearth", NM_SHOOT_SMALLEARTH}, + {"eartharrow", NM_SHOOT_EARTHARROW}, + {"explosion", NM_SHOOT_EXPLOSION}, + {"cake", NM_SHOOT_CAKE}, + {"tarsalarrow", NM_SHOOT_TARSALARROW}, + {"vortexbolt", NM_SHOOT_VORTEXBOLT}, + {"prismaticbolt", NM_SHOOT_PRISMATICBOLT}, + {"crystallinearrow", NM_SHOOT_CRYSTALLINEARROW}, + {"drillbolt", NM_SHOOT_DRILLBOLT}, + {"envenomedarrow", NM_SHOOT_ENVENOMEDARROW} +}; + +CombatTypeNames combatTypeNames[] = { + {"physical", COMBAT_PHYSICALDAMAGE}, + {"energy", COMBAT_ENERGYDAMAGE}, + {"earth", COMBAT_EARTHDAMAGE}, + {"fire", COMBAT_FIREDAMAGE}, + {"undefined", COMBAT_UNDEFINEDDAMAGE}, + {"lifedrain", COMBAT_LIFEDRAIN}, + {"manadrain", COMBAT_MANADRAIN}, + {"healing", COMBAT_HEALING}, + {"drown", COMBAT_DROWNDAMAGE}, + {"ice", COMBAT_ICEDAMAGE}, + {"holy", COMBAT_HOLYDAMAGE}, + {"death", COMBAT_DEATHDAMAGE} +}; + +AmmoTypeNames ammoTypeNames[] = { + {"spear", AMMO_SPEAR}, + {"bolt", AMMO_BOLT}, + {"arrow", AMMO_ARROW}, + {"poisonarrow", AMMO_ARROW}, + {"burstarrow", AMMO_ARROW}, + {"throwingstar", AMMO_THROWINGSTAR}, + {"throwingknife", AMMO_THROWINGKNIFE}, + {"smallstone", AMMO_STONE}, + {"largerock", AMMO_STONE}, + {"snowball", AMMO_SNOWBALL}, + {"powerbolt", AMMO_BOLT}, + {"infernalbolt", AMMO_BOLT}, + {"huntingspear", AMMO_SPEAR}, + {"enchantedspear", AMMO_SPEAR}, + {"royalspear", AMMO_SPEAR}, + {"sniperarrow", AMMO_ARROW}, + {"onyxarrow", AMMO_ARROW}, + {"piercingbolt", AMMO_BOLT}, + {"etherealspear", AMMO_SPEAR}, + {"flasharrow", AMMO_ARROW}, + {"flammingarrow", AMMO_ARROW}, + {"shiverarrow", AMMO_ARROW}, + {"eartharrow", AMMO_ARROW} +}; + +AmmoActionNames ammoActionNames[] = { + {"move", AMMOACTION_MOVE}, + {"moveback", AMMOACTION_MOVEBACK}, + {"removecharge", AMMOACTION_REMOVECHARGE}, + {"removecount", AMMOACTION_REMOVECOUNT} +}; + +MagicEffectClasses getMagicEffect(const std::string& strValue) +{ + for (uint32_t i = 0; i < sizeof(magicEffectNames) / sizeof(MagicEffectNames); ++i) { + if (strcasecmp(strValue.c_str(), magicEffectNames[i].name) == 0) { + return magicEffectNames[i].effect; + } + } + + return NM_ME_UNK; +} + +ShootType_t getShootType(const std::string& strValue) +{ + for (uint32_t i = 0; i < sizeof(shootTypeNames) / sizeof(ShootTypeNames); ++i) { + if (strcasecmp(strValue.c_str(), shootTypeNames[i].name) == 0) { + return shootTypeNames[i].shoot; + } + } + + return NM_SHOOT_UNK; +} + +CombatType_t getCombatType(const std::string& strValue) +{ + for (uint32_t i = 0; i < sizeof(combatTypeNames) / sizeof(CombatTypeNames); ++i) { + if (strcasecmp(strValue.c_str(), combatTypeNames[i].name) == 0) { + return combatTypeNames[i].combat; + } + } + + return COMBAT_NONE; +} + +std::string getCombatName(CombatType_t combatType) +{ + for (uint32_t i = 0; i < sizeof(combatTypeNames) / sizeof(CombatTypeNames); ++i) { + if (combatTypeNames[i].combat == combatType) { + return combatTypeNames[i].name; + } + } + + return "unknown"; +} + +Ammo_t getAmmoType(const std::string& strValue) +{ + for (uint32_t i = 0; i < sizeof(ammoTypeNames) / sizeof(AmmoTypeNames); ++i) { + if (strcasecmp(strValue.c_str(), ammoTypeNames[i].name) == 0) { + return ammoTypeNames[i].ammoType; + } + } + + return AMMO_NONE; +} + +AmmoAction_t getAmmoAction(const std::string& strValue) +{ + for (uint32_t i = 0; i < sizeof(ammoActionNames) / sizeof(AmmoActionNames); ++i) { + if (strcasecmp(strValue.c_str(), ammoActionNames[i].name) == 0) { + return ammoActionNames[i].ammoAction; + } + } + + return AMMOACTION_NONE; +} + +std::string getSkillName(uint16_t skillid) +{ + switch (skillid) { + case SKILL_FIST: + return "fist fighting"; + + case SKILL_CLUB: + return "club fighting"; + + case SKILL_SWORD: + return "sword fighting"; + + case SKILL_AXE: + return "axe fighting"; + + case SKILL_DIST: + return "distance fighting"; + + case SKILL_SHIELD: + return "shielding"; + + case SKILL_FISH: + return "fishing"; + + case SKILL__MAGLEVEL: + return "magic level"; + + case SKILL__LEVEL: + return "level"; + + default: + return "unknown"; + } +} + +skills_t getSkillId(const std::string& param) +{ + if (param == "fist") { + return SKILL_FIST; + } else if (param == "club") { + return SKILL_CLUB; + } else if (param == "sword") { + return SKILL_SWORD; + } else if (param == "axe") { + return SKILL_AXE; + } else if (param == "distance" || param == "dist") { + return SKILL_DIST; + } else if (param == "shielding" || param == "shield") { + return SKILL_SHIELD; + } else if (param == "fishing" || param == "fish") { + return SKILL_FISH; + } else { + return SKILL_FIST; + } +} + +int32_t reasonStringToInt(std::string reason) +{ + reason = asLowerCaseString(reason); + + if (reason == "offensive name") { + return 0; + } else if (reason == "invalid name format") { + return 1; + } else if (reason == "unsuitable name") { + return 2; + } else if (reason == "name inciting rule violation") { + return 3; + } else if (reason == "offensive statement") { + return 4; + } else if (reason == "spamming") { + return 5; + } else if (reason == "illegal advertising") { + return 6; + } else if (reason == "off-topic public statement") { + return 7; + } else if (reason == "non-english public statement") { + return 8; + } else if (reason == "inciting rule violation") { + return 9; + } else if (reason == "bug abuse") { + return 10; + } else if (reason == "game weakness abuse") { + return 11; + } else if (reason == "using unofficial software to play") { + return 12; + } else if (reason == "hacking") { + return 13; + } else if (reason == "multi-clienting") { + return 14; + } else if (reason == "account trading or sharing") { + return 15; + } else if (reason == "threatening gamemaster") { + return 16; + } else if (reason == "pretending to have influence on rule enforcement") { + return 17; + } else if (reason == "false report to gamemaster") { + return 18; + } else if (reason == "destructive behaviour") { + return 19; + } else if (reason == "excessive unjustified player killing") { + return 20; + } else if (reason == "spoiling auction") { + return 21; + } else { + return -1; + } +} + +int32_t actionStringToInt(std::string action) +{ + action = asLowerCaseString(action); + + if (action == "notation") { + return 0; + } else if (action == "name report" || action == "namelock") { + return 1; + } else if (action == "ban" || action == "banishment") { + return 2; + } else if (action == "namelock + ban" || action == "namelock + banishment" || action == "name report + ban" || action == "name report + banishment") { + return 3; + } else if (action == "ban + final warning" || action == "banishment + final warning") { + return 4; + } else if (action == "namelock + ban + final warning" || action == "namelock + banishment + final warning" || action == "name report + ban + final warning" || action == "name report + banishment + final warning") { + return 5; + } else if (action == "statement report") { + return 6; + } else if (action == "delete" || action == "deletion") { + return 7; + } else { + return -1; + } +} + +std::string getReason(int32_t reasonId) +{ + switch (reasonId) { + case 0: + return "Offensive Name"; + case 1: + return "Invalid Name Format"; + case 2: + return "Unsuitable Name"; + case 3: + return "Name Inciting Rule Violation"; + case 4: + return "Offensive Statement"; + case 5: + return "Spamming"; + case 6: + return "Illegal Advertising"; + case 7: + return "Off-Topic Public Statement"; + case 8: + return "Non-English Public Statement"; + case 9: + return "Inciting Rule Violation"; + case 10: + return "Bug Abuse"; + case 11: + return "Game Weakness Abuse"; + case 12: + return "Using Unofficial Software to Play"; + case 13: + return "Hacking"; + case 14: + return "Multi-Clienting"; + case 15: + return "Account Trading or Sharing"; + case 16: + return "Threatening Gamemaster"; + case 17: + return "Pretending to Have Influence on Rule Enforcement"; + case 18: + return "False Report to Gamemaster"; + case 19: + return "Destructive Behaviour"; + case 20: + return "Excessive Unjustified Player Killing"; + case 21: + return "Spoiling Auction"; + default: + return "Unknown Reason"; + } +} + +std::string getAction(int32_t actionId, bool IPBanishment) +{ + std::string action; + + switch (actionId) { + case 0: + action = "Notation"; + break; + case 1: + action = "Name Report"; + break; + case 2: + action = "Banishment"; + break; + case 3: + action = "Name Report + Banishment"; + break; + case 4: + action = "Banishment + Final Warning"; + break; + case 5: + action = "Name Report + Banishment + Final Warning"; + break; + case 6: + action = "Statement Report"; + break; + default: + action = "Deletion"; + break; + } + + if (IPBanishment) { + action += " + IP Banishment"; + } + + return action; +} + +bool dirExists(const char* name) +{ +#ifdef _MSC_VER + return _access(name, 0) == 0; +#else + return access(name, F_OK) == 0; +#endif +} + +bool createDir(const std::string& dirName) +{ + return +#ifdef _MSC_VER + _mkdir(dirName.c_str()) +#else + mkdir(dirName.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) +#endif + == 0; +} + +uint32_t adlerChecksum(uint8_t* data, size_t length) +{ + if (length > NETWORKMESSAGE_MAXSIZE) { + return 0; + } + + const uint16_t adler = 65521; + + uint32_t a = 1, b = 0; + + while (length > 0) { + size_t tmp = length > 5552 ? 5552 : length; + length -= tmp; + + do { + a += *data++; + b += a; + } while (--tmp); + + a %= adler; + b %= adler; + } + + return (b << 16) | a; +} + +std::string ucfirst(std::string str) +{ + for (uint32_t i = 0; i < str.length(); ++i) { + if (str[i] != ' ') { + str[i] = upchar(str[i]); + break; + } + } + + return str; +} + +std::string ucwords(std::string str) +{ + uint32_t strLength = str.length(); + + if (strLength == 0) { + return str; + } + + str[0] = upchar(str[0]); + + for (uint32_t i = 1; i < strLength; ++i) { + if (str[i - 1] == ' ') { + str[i] = upchar(str[i]); + } + } + + return str; +} + +bool booleanString(const std::string& str) +{ + const std::string& lowerStr = asLowerCaseString(str); + return (lowerStr == "yes" || lowerStr == "true" || lowerStr == "y" || atoi(lowerStr.c_str()) > 0); +} + +std::string getWeaponName(WeaponType_t weaponType) +{ + switch (weaponType) { + case WEAPON_SWORD: + return "sword"; + case WEAPON_CLUB: + return "club"; + case WEAPON_AXE: + return "axe"; + case WEAPON_DIST: + return "distance"; + case WEAPON_WAND: + return "wand"; + case WEAPON_AMMO: + return "ammunition"; + default: + return ""; + } +} + +uint32_t combatTypeToIndex(CombatType_t combatType) +{ + switch (combatType) { + case COMBAT_NONE: + return 0; + case COMBAT_PHYSICALDAMAGE: + return 1; + case COMBAT_ENERGYDAMAGE: + return 2; + case COMBAT_EARTHDAMAGE: + return 3; + case COMBAT_FIREDAMAGE: + return 4; + case COMBAT_UNDEFINEDDAMAGE: + return 5; + case COMBAT_LIFEDRAIN: + return 6; + case COMBAT_MANADRAIN: + return 7; + case COMBAT_HEALING: + return 8; + case COMBAT_DROWNDAMAGE: + return 9; + case COMBAT_ICEDAMAGE: + return 10; + case COMBAT_HOLYDAMAGE: + return 11; + case COMBAT_DEATHDAMAGE: + return 12; + default: + return 0; + } +} + +CombatType_t indexToCombatType(uint32_t v) +{ + if (v == 0) { + return COMBAT_FIRST; + } + + return (CombatType_t)(1 << (v - 1)); +} + +uint8_t serverFluidToClient(uint8_t serverFluid) +{ + uint8_t size = sizeof(clientToServerFluidMap) / sizeof(int8_t); + + for (uint8_t i = 0; i < size; ++i) { + if (clientToServerFluidMap[i] == serverFluid) { + return i; + } + } + + return 0; +} + +uint8_t clientFluidToServer(uint8_t clientFluid) +{ + uint8_t size = sizeof(clientToServerFluidMap) / sizeof(int8_t); + + if (clientFluid >= size) { + return 0; + } + + return clientToServerFluidMap[clientFluid]; +} + +std::string getFirstLine(const std::string& str) +{ + std::string firstLine = ""; + + for (uint32_t i = 0, strLength = str.length(); i < strLength; ++i) { + if (str[i] == '\n') { + break; + } + + firstLine += str[i]; + } + + return firstLine; +} diff --git a/src/tools.h b/src/tools.h new file mode 100644 index 0000000000..7c25bd0fc7 --- /dev/null +++ b/src/tools.h @@ -0,0 +1,122 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_TOOLS_H__ +#define __OTSERV_TOOLS_H__ + +#include "definitions.h" +#include "position.h" +#include "const.h" +#include "enums.h" + +#include +#include + +#include + +#include +typedef boost::tokenizer > tokenizer; + +enum DistributionType_t { + DISTRO_UNIFORM, + DISTRO_SQUARE, + DISTRO_NORMAL +}; + +extern std::string transformToMD5(const std::string& plainText, bool upperCase = false); +extern std::string transformToSHA1(const std::string& plainText, bool upperCase = false); +extern bool passwordTest(const std::string& plain, std::string& hash); + +extern void replaceString(std::string& str, const std::string& sought, const std::string& replacement); +extern void trim_right(std::string& source, const std::string& t); +extern void trim_left(std::string& source, const std::string& t); +extern void toLowerCaseString(std::string& source); +extern void toUpperCaseString(std::string& source); +extern std::string asLowerCaseString(const std::string& source); +extern std::string asUpperCaseString(const std::string& source); + +extern bool utf8ToLatin1(const char* intext, std::string& outtext); +extern bool readXMLInteger(xmlNodePtr node, const char* tag, int& value); +#if (defined __WINDOWS__ || defined WIN32) && !defined __GNUC__ +extern bool readXMLInteger(xmlNodePtr node, const char* tag, int32_t& value); +#endif +extern bool readXMLInteger64(xmlNodePtr node, const char* tag, uint64_t& value); +extern bool readXMLFloat(xmlNodePtr node, const char* tag, float& value); +extern bool readXMLString(xmlNodePtr node, const char* tag, std::string& value); +extern bool readXMLContentString(xmlNodePtr node, std::string& value); + +typedef std::vector StringVec; +typedef std::vector IntegerVec; + +extern StringVec explodeString(const std::string& inString, const std::string& separator, int32_t limit = -1); +extern IntegerVec vectorAtoi(std::vector stringVector); +extern bool hasBitSet(uint32_t flag, uint32_t flags); + +extern bool isNumber(char character); + +extern int random_range(int lowest_number, int highest_number, DistributionType_t type = DISTRO_UNIFORM); + +extern Direction getDirection(const std::string& string); +extern Position getNextPosition(Direction direction, Position pos); +extern Direction getDirectionTo(const Position& from, const Position& to); + +extern char upchar(char c); + +extern std::string getFirstLine(const std::string& str); + +extern std::string parseParams(tokenizer::iterator& it, tokenizer::iterator end); + +extern std::string formatDate(time_t time); +extern std::string formatDateShort(time_t time); +extern std::string convertIPToString(uint32_t ip); + +extern std::string trimString(std::string& str); + +extern MagicEffectClasses getMagicEffect(const std::string& strValue); +extern ShootType_t getShootType(const std::string& strValue); +extern Ammo_t getAmmoType(const std::string& strValue); +extern AmmoAction_t getAmmoAction(const std::string& strValue); +extern CombatType_t getCombatType(const std::string& strValue); +extern std::string getCombatName(CombatType_t combatType); + +extern std::string getSkillName(uint16_t skillid); +extern skills_t getSkillId(const std::string& param); + +extern int32_t actionStringToInt(std::string action); +extern int32_t reasonStringToInt(std::string reason); +extern std::string getReason(int32_t reasonId); +extern std::string getAction(int32_t actionId, bool IPBanishment); + +extern bool dirExists(const char* name); +extern bool createDir(const std::string&); + +extern uint32_t adlerChecksum(uint8_t* data, size_t len); + +extern std::string ucfirst(std::string str); +extern std::string ucwords(std::string str); +extern bool booleanString(const std::string& str); + +extern std::string getWeaponName(WeaponType_t weaponType); + +extern uint32_t combatTypeToIndex(CombatType_t combatType); +extern CombatType_t indexToCombatType(uint32_t v); + +extern uint8_t serverFluidToClient(uint8_t serverFluid); +extern uint8_t clientFluidToServer(uint8_t clientFluid); + +#endif diff --git a/src/town.h b/src/town.h new file mode 100644 index 0000000000..a895835e82 --- /dev/null +++ b/src/town.h @@ -0,0 +1,118 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __TOWN_H__ +#define __TOWN_H__ + +#include +#include +#include + +#include "position.h" +#include "definitions.h" + +class Town +{ + public: + Town(uint32_t _townid) + : townid(_townid) {} + + ~Town() {} + + const Position& getTemplePosition() const { + return posTemple; + } + const std::string& getName() const { + return townName; + } + + void setTemplePos(const Position& pos) { + posTemple = pos; + } + void setName(const std::string& _townName) { + townName = _townName; + } + uint32_t getTownID() const { + return townid; + } + + private: + uint32_t townid; + std::string townName; + Position posTemple; +}; + +typedef std::map TownMap; + +class Towns +{ + public: + ~Towns() { + for (TownMap::iterator it = townMap.begin(); it != townMap.end(); ++it) { + delete it->second; + } + } + + static Towns& getInstance() { + static Towns singleton; + return singleton; + } + + bool addTown(uint32_t _townid, Town* town) { + TownMap::iterator it = townMap.find(_townid); + + if (it != townMap.end()) { + return false; + } + + townMap[_townid] = town; + return true; + } + + Town* getTown(std::string& townname) { + for (TownMap::iterator it = townMap.begin(); it != townMap.end(); ++it) { + if (strcasecmp(it->second->getName().c_str(), townname.c_str()) == 0) { + return it->second; + } + } + + return NULL; + } + + Town* getTown(uint32_t _townid) { + TownMap::iterator it = townMap.find(_townid); + + if (it != townMap.end()) { + return it->second; + } + + return NULL; + } + + TownMap::const_iterator getFirstTown() const { + return townMap.begin(); + } + TownMap::const_iterator getLastTown() const { + return townMap.end(); + } + + private: + TownMap townMap; +}; + +#endif diff --git a/src/trashholder.cpp b/src/trashholder.cpp new file mode 100644 index 0000000000..7c5befe361 --- /dev/null +++ b/src/trashholder.cpp @@ -0,0 +1,127 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "trashholder.h" +#include "game.h" + +extern Game g_game; + +TrashHolder::TrashHolder(uint16_t _type, MagicEffectClasses _effect /*= NM_ME_NONE*/) : Item(_type) +{ + effect = _effect; +} + +TrashHolder::~TrashHolder() +{ + // +} + +ReturnValue TrashHolder::__queryAdd(int32_t index, const Thing* thing, uint32_t count, + uint32_t flags, Creature* actor/* = NULL*/) const +{ + return RET_NOERROR; +} + +ReturnValue TrashHolder::__queryMaxCount(int32_t index, const Thing* thing, uint32_t count, + uint32_t& maxQueryCount, uint32_t flags) const +{ + maxQueryCount = std::max(1, count); + return RET_NOERROR; +} + +ReturnValue TrashHolder::__queryRemove(const Thing* thing, uint32_t count, uint32_t flags) const +{ + return RET_NOTPOSSIBLE; +} + +Cylinder* TrashHolder::__queryDestination(int32_t& index, const Thing* thing, Item** destItem, + uint32_t& flags) +{ + return this; +} + +void TrashHolder::__addThing(Thing* thing) +{ + return __addThing(0, thing); +} + +void TrashHolder::__addThing(int32_t index, Thing* thing) +{ + Item* item = thing->getItem(); + + if (!item) { + return; + } + + if (item == this || !item->hasProperty(MOVEABLE)) { + return; + } + + if (item->isHangable() && isGroundTile()) { + Cylinder* parent = getParent(); + + if (parent) { + Tile* tile = dynamic_cast(parent); + + if (tile) { + if (const TileItemVector* items = tile->getItemList()) { + for (ItemVector::const_iterator it = items->begin(); it != items->end(); ++it) { + const ItemType& iiType = Item::items[(*it)->getID()]; + + if (iiType.isHorizontal || iiType.isVertical) { + return; + } + } + } + } + } + } + + g_game.internalRemoveItem(item); + + if (effect != NM_ME_NONE) { + g_game.addMagicEffect(getPosition(), effect); + } +} + +void TrashHolder::__updateThing(Thing* thing, uint16_t itemId, uint32_t count) +{ + // +} + +void TrashHolder::__replaceThing(uint32_t index, Thing* thing) +{ + // +} + +void TrashHolder::__removeThing(Thing* thing, uint32_t count) +{ + // +} + +void TrashHolder::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) +{ + getParent()->postAddNotification(thing, oldParent, index, LINK_PARENT); +} + +void TrashHolder::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, bool isCompleteRemoval, cylinderlink_t link /*= LINK_OWNER*/) +{ + getParent()->postRemoveNotification(thing, newParent, index, isCompleteRemoval, LINK_PARENT); +} diff --git a/src/trashholder.h b/src/trashholder.h new file mode 100644 index 0000000000..c2ac077d3c --- /dev/null +++ b/src/trashholder.h @@ -0,0 +1,63 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __TRASHHOLDER_H__ +#define __TRASHHOLDER_H__ + +#include "item.h" +#include "cylinder.h" +#include "const.h" + +class TrashHolder : public Item, public Cylinder +{ + public: + TrashHolder(uint16_t _type, MagicEffectClasses _effect = NM_ME_NONE); + ~TrashHolder(); + + virtual TrashHolder* getTrashHolder() { + return this; + } + virtual const TrashHolder* getTrashHolder() const { + return this; + } + + //cylinder implementations + virtual ReturnValue __queryAdd(int32_t index, const Thing* thing, uint32_t count, + uint32_t flags, Creature* actor = NULL) const; + virtual ReturnValue __queryMaxCount(int32_t index, const Thing* thing, uint32_t count, + uint32_t& maxQueryCount, uint32_t flags) const; + virtual ReturnValue __queryRemove(const Thing* thing, uint32_t count, uint32_t flags) const; + virtual Cylinder* __queryDestination(int32_t& index, const Thing* thing, Item** destItem, + uint32_t& flags); + + virtual void __addThing(Thing* thing); + virtual void __addThing(int32_t index, Thing* thing); + + virtual void __updateThing(Thing* thing, uint16_t itemId, uint32_t count); + virtual void __replaceThing(uint32_t index, Thing* thing); + + virtual void __removeThing(Thing* thing, uint32_t count); + + virtual void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER); + virtual void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, bool isCompleteRemoval, cylinderlink_t link = LINK_OWNER); + + private: + MagicEffectClasses effect; +}; + +#endif diff --git a/src/vocation.cpp b/src/vocation.cpp new file mode 100644 index 0000000000..ee903d59a4 --- /dev/null +++ b/src/vocation.cpp @@ -0,0 +1,308 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "definitions.h" +#include "vocation.h" +#include +#include +#include +#include + +#include "tools.h" + +Vocations::Vocations() +{ + // +} + +Vocations::~Vocations() +{ + for (VocationsMap::iterator it = vocationsMap.begin(); it != vocationsMap.end(); ++it) { + delete it->second; + } + + vocationsMap.clear(); +} + +bool Vocations::loadFromXml() +{ + std::string filename = "data/XML/vocations.xml"; + + xmlDocPtr doc = xmlParseFile(filename.c_str()); + + if (doc) { + xmlNodePtr root, p; + root = xmlDocGetRootElement(doc); + + if (xmlStrcmp(root->name, (const xmlChar*)"vocations") != 0) { + xmlFreeDoc(doc); + return false; + } + + p = root->children; + + while (p) { + std::string str; + int32_t intVal; + + if (xmlStrcmp(p->name, (const xmlChar*)"vocation") == 0) { + Vocation* voc = new Vocation(); + uint32_t voc_id; + xmlNodePtr configNode; + + if (readXMLInteger(p, "id", intVal)) { + float floatVal; + + voc_id = intVal; + + if (readXMLString(p, "name", str)) { + voc->name = str; + } + + if (readXMLInteger(p, "clientid", intVal)) { + voc->clientId = intVal; + } + + if (readXMLString(p, "description", str)) { + voc->description = str; + } + + if (readXMLInteger(p, "gaincap", intVal)) { + voc->gainCap = intVal; + } + + if (readXMLInteger(p, "gainhp", intVal)) { + voc->gainHP = intVal; + } + + if (readXMLInteger(p, "gainmana", intVal)) { + voc->gainMana = intVal; + } + + if (readXMLInteger(p, "gainhpticks", intVal)) { + voc->gainHealthTicks = intVal; + } + + if (readXMLInteger(p, "gainhpamount", intVal)) { + voc->gainHealthAmount = intVal; + } + + if (readXMLInteger(p, "gainmanaticks", intVal)) { + voc->gainManaTicks = intVal; + } + + if (readXMLInteger(p, "gainmanaamount", intVal)) { + voc->gainManaAmount = intVal; + } + + if (readXMLFloat(p, "manamultiplier", floatVal)) { + voc->manaMultiplier = floatVal; + } + + if (readXMLInteger(p, "attackspeed", intVal)) { + voc->attackSpeed = intVal; + } + + if (readXMLInteger(p, "basespeed", intVal)) { + voc->baseSpeed = intVal; + } + + if (readXMLInteger(p, "soulmax", intVal)) { + voc->soulMax = intVal; + } + + if (readXMLInteger(p, "gainsoulticks", intVal)) { + voc->gainSoulTicks = intVal; + } + + if (readXMLInteger(p, "fromvoc", intVal)) { + voc->fromVocation = intVal; + } + + configNode = p->children; + + while (configNode) { + if (xmlStrcmp(configNode->name, (const xmlChar*)"skill") == 0) { + uint32_t skill_id; + + if (readXMLInteger(configNode, "id", intVal)) { + skill_id = intVal; + + if (skill_id > SKILL_LAST) { + std::cout << "No valid skill id. " << skill_id << std::endl; + } else { + if (readXMLFloat(configNode, "multiplier", floatVal)) { + voc->skillMultipliers[skill_id] = floatVal; + } + } + } else { + std::cout << "Missing skill id." << std::endl; + } + } else if (xmlStrcmp(configNode->name, (const xmlChar*)"formula") == 0) { + if (readXMLFloat(configNode, "meleeDamage", floatVal)) { + voc->meleeDamageMultipler = floatVal; + } + + if (readXMLFloat(configNode, "distDamage", floatVal)) { + voc->distDamageMultipler = floatVal; + } + + if (readXMLFloat(configNode, "defense", floatVal)) { + voc->defenseMultipler = floatVal; + } + + if (readXMLFloat(configNode, "armor", floatVal)) { + voc->armorMultipler = floatVal; + } + } + + configNode = configNode->next; + } + + vocationsMap[voc_id] = voc; + } else { + std::cout << "Missing vocation id." << std::endl; + } + } + + p = p->next; + } + + xmlFreeDoc(doc); + } + + return true; +} + +Vocation* Vocations::getVocation(uint32_t id) +{ + VocationsMap::iterator it = vocationsMap.find(id); + + if (it == vocationsMap.end()) { + std::cout << "Warning: [Vocations::getVocation] Vocation " << id << " not found." << std::endl; + return &def_voc; + } + + return it->second; +} + +int32_t Vocations::getVocationId(const std::string& name) +{ + for (VocationsMap::iterator it = vocationsMap.begin(); it != vocationsMap.end(); ++it) { + if (strcasecmp(it->second->name.c_str(), name.c_str()) == 0) { + return it->first; + } + } + + return -1; +} + +int32_t Vocations::getPromotedVocation(uint32_t vocationId) +{ + for (VocationsMap::iterator it = vocationsMap.begin(); it != vocationsMap.end(); ++it) { + if (it->second->fromVocation == vocationId && it->first != vocationId) { + return it->first; + } + } + + return 0; +} + +uint32_t Vocation::skillBase[SKILL_LAST + 1] = {50, 50, 50, 50, 30, 100, 20}; + +Vocation::Vocation() +{ + name = "none"; + description = ""; + gainHealthTicks = 6; + gainHealthAmount = 1; + gainManaTicks = 6; + gainManaAmount = 1; + gainSoulTicks = 120; + soulMax = 100; + + clientId = 0; + fromVocation = 0; + + gainCap = 5; + gainMana = 5; + gainHP = 5; + attackSpeed = 1500; + baseSpeed = 220; + manaMultiplier = 4.0; + meleeDamageMultipler = 1.0; + distDamageMultipler = 1.0; + defenseMultipler = 1.0; + armorMultipler = 1.0; + skillMultipliers[0] = 1.5f; + skillMultipliers[1] = 2.0f; + skillMultipliers[2] = 2.0f; + skillMultipliers[3] = 2.0f; + skillMultipliers[4] = 2.0f; + skillMultipliers[5] = 1.5f; + skillMultipliers[6] = 1.1f; +} + +Vocation::~Vocation() +{ + cacheMana.clear(); + + for (int32_t i = SKILL_FIRST; i < SKILL_LAST; ++i) { + cacheSkill[i].clear(); + } +} + +uint32_t Vocation::getReqSkillTries(int32_t skill, int32_t level) +{ + if (skill < SKILL_FIRST || skill > SKILL_LAST) { + return 0; + } + + skillCacheMap& skillMap = cacheSkill[skill]; + skillCacheMap::iterator it = skillMap.find(level); + + if (it != cacheSkill[skill].end()) { + return it->second; + } + + uint32_t tries = (uint32_t)(skillBase[skill] * pow((float)skillMultipliers[skill], (float)(level - 11))); + skillMap[level] = tries; + return tries; +} + +uint64_t Vocation::getReqMana(uint32_t magLevel) +{ + manaCacheMap::iterator it = cacheMana.find(magLevel); + + if (it != cacheMana.end()) { + return it->second; + } + + uint64_t reqMana = (uint64_t)(400 * pow(manaMultiplier, (int) magLevel - 1)); + + if (reqMana % 20 < 10) { + reqMana = reqMana - (reqMana % 20); + } else { + reqMana = reqMana - (reqMana % 20) + 20; + } + + cacheMana[magLevel] = reqMana; + return reqMana; +} diff --git a/src/vocation.h b/src/vocation.h new file mode 100644 index 0000000000..53cec19225 --- /dev/null +++ b/src/vocation.h @@ -0,0 +1,148 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_VOCATION_H__ +#define __OTSERV_VOCATION_H__ + +#include "enums.h" +#include +#include +#include "item.h" + +class Vocation +{ + public: + ~Vocation(); + const std::string& getVocName() const { + return name; + } + const std::string& getVocDescription() const { + return description; + } + uint32_t getReqSkillTries(int32_t skill, int32_t level); + uint64_t getReqMana(uint32_t magLevel); + + uint8_t getClientId() const { + return clientId; + } + + uint32_t getHPGain() const { + return gainHP; + } + uint32_t getManaGain() const { + return gainMana; + } + uint32_t getCapGain() const { + return gainCap; + } + + uint32_t getManaGainTicks() const { + return gainManaTicks; + } + uint32_t getManaGainAmount() const { + return gainManaAmount; + } + uint32_t getHealthGainTicks() const { + return gainHealthTicks; + } + uint32_t getHealthGainAmount() const { + return gainHealthAmount; + } + + uint16_t getSoulMax() const { + return std::min(soulMax, 255); + } + uint16_t getSoulGainTicks() const { + return gainSoulTicks; + } + + uint32_t getAttackSpeed() const { + return attackSpeed; + } + uint32_t getBaseSpeed() const { + return baseSpeed; + } + + uint32_t getFromVocation() const { + return fromVocation; + } + + float meleeDamageMultipler, distDamageMultipler, defenseMultipler, armorMultipler; + + protected: + friend class Vocations; + Vocation(); + + uint8_t clientId; + + std::string name; + std::string description; + + uint32_t gainHealthTicks; + uint32_t gainHealthAmount; + uint32_t gainManaTicks; + uint32_t gainManaAmount; + uint32_t gainCap; + uint32_t gainMana; + uint32_t gainHP; + + uint16_t gainSoulTicks; + uint16_t soulMax; + + uint32_t fromVocation; + + uint32_t attackSpeed; + uint32_t baseSpeed; + + static uint32_t skillBase[SKILL_LAST + 1]; + float skillMultipliers[SKILL_LAST + 1]; + float manaMultiplier; + + typedef std::map manaCacheMap; + manaCacheMap cacheMana; + + typedef std::map skillCacheMap; + skillCacheMap cacheSkill[SKILL_LAST + 1]; +}; + +typedef std::map VocationsMap; + +class Vocations +{ + public: + Vocations(); + ~Vocations(); + + bool loadFromXml(); + Vocation* getVocation(uint32_t id); + int32_t getVocationId(const std::string& name); + int32_t getPromotedVocation(uint32_t vocationId); + + VocationsMap::iterator getFirstVocation() { + return vocationsMap.begin(); + } + VocationsMap::iterator getLastVocation() { + return vocationsMap.end(); + } + + private: + VocationsMap vocationsMap; + Vocation def_voc; +}; + +#endif diff --git a/src/waitlist.cpp b/src/waitlist.cpp new file mode 100644 index 0000000000..7d152bfa34 --- /dev/null +++ b/src/waitlist.cpp @@ -0,0 +1,155 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include +#include + +#include "configmanager.h" +#include "waitlist.h" +#include "status.h" + +extern ConfigManager g_config; + +WaitingList::WaitingList() +{ + // +} + +WaitingList::~WaitingList() +{ + waitList.clear(); +} + +WaitListIterator WaitingList::findClient(const Player* player, uint32_t& slot) +{ + slot = 1; + + for (WaitListIterator it = waitList.begin(); it != waitList.end(); ++it) { + Wait* wait = *it; + + if (wait->acc == player->getAccount() && wait->ip == player->getIP() && wait->name == player->getName()) { + return it; + } + + ++slot; + } + + return waitList.end(); +} + +int32_t WaitingList::getTime(int32_t slot) +{ + if (slot < 5) { + return 5; + } else if (slot < 10) { + return 10; + } else if (slot < 20) { + return 20; + } else if (slot < 50) { + return 60; + } else { + return 120; + } +} + +int32_t WaitingList::getTimeOut(int32_t slot) +{ + //timeout is set to 15 seconds longer than expected retry attempt + return getTime(slot) + 15; +} + +bool WaitingList::clientLogin(const Player* player) +{ + if (player->hasFlag(PlayerFlag_CanAlwaysLogin) || player->getAccountType() >= ACCOUNT_TYPE_GAMEMASTER) { + return true; + } + + if (waitList.empty() && Status::getInstance()->getPlayersOnline() < (uint32_t)g_config.getNumber(ConfigManager::MAX_PLAYERS)) { + //no waiting list and enough room + return true; + } + + cleanUpList(); + + uint32_t slot; + WaitListIterator it = findClient(player, slot); + + if (it != waitList.end()) { + if ((Status::getInstance()->getPlayersOnline() + slot) <= (uint32_t)g_config.getNumber(ConfigManager::MAX_PLAYERS)) { + //should be able to login now + delete *it; + waitList.erase(it); + return true; + } else { + //let them wait a bit longer + (*it)->timeout = OTSYS_TIME() + (getTimeOut(slot) * 1000); + return false; + } + } + + Wait* wait = new Wait(); + + if (player->isPremium()) { + slot = 1; + + for (WaitListIterator it = waitList.begin(); it != waitList.end(); ++it) { + if (!(*it)->premium) { + waitList.insert(it, wait); + break; + } + + ++slot; + } + } else { + waitList.push_back(wait); + slot = (uint32_t)waitList.size(); + } + + wait->name = player->getName(); + wait->acc = player->getAccount(); + wait->ip = player->getIP(); + wait->premium = player->isPremium(); + wait->timeout = OTSYS_TIME() + (getTimeOut(slot) * 1000); + return false; +} + +int32_t WaitingList::getClientSlot(const Player* player) +{ + uint32_t slot; + WaitListIterator it = findClient(player, slot); + + if (it != waitList.end()) { + return slot; + } + + return -1; +} + +void WaitingList::cleanUpList() +{ + for (WaitListIterator it = waitList.begin(); it != waitList.end();) { + if ((*it)->timeout - OTSYS_TIME() <= 0) { + delete *it; + waitList.erase(it++); + } else { + ++it; + } + } +} diff --git a/src/waitlist.h b/src/waitlist.h new file mode 100644 index 0000000000..4a922e2d7d --- /dev/null +++ b/src/waitlist.h @@ -0,0 +1,60 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __WAITLIST_H__ +#define __WAITLIST_H__ + +#include "game.h" +#include "networkmessage.h" + +struct Wait { + uint32_t acc; + uint32_t ip; + std::string name; + bool premium; + int64_t timeout; +}; + +typedef std::list WaitList; +typedef WaitList::iterator WaitListIterator; + +class WaitingList +{ + public: + WaitingList(); + ~WaitingList(); + + static WaitingList* getInstance() { + static WaitingList waitingList; + return &waitingList; + } + + bool clientLogin(const Player* player); + int32_t getClientSlot(const Player* player); + static int32_t getTime(int32_t slot); + + protected: + WaitList priorityWaitList; + WaitList waitList; + + int32_t getTimeOut(int32_t slot); + WaitListIterator findClient(const Player* player, uint32_t& slot); + void cleanUpList(); +}; + +#endif diff --git a/src/waypoints.h b/src/waypoints.h new file mode 100644 index 0000000000..11d061860e --- /dev/null +++ b/src/waypoints.h @@ -0,0 +1,65 @@ +//////////////////////////////////////////////////////////////////////// +// OpenTibia - an opensource roleplaying game +//////////////////////////////////////////////////////////////////////// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +//////////////////////////////////////////////////////////////////////// + +#ifndef __WAYPOINTS__ +#define __WAYPOINTS__ +#include "otsystem.h" + +class Waypoint +{ + public: + Waypoint(const std::string& _name, const Position& _pos): + name(_name), pos(_pos) {} + + std::string name; + Position pos; +}; + +typedef boost::shared_ptr WaypointPtr; +typedef std::map WaypointMap; + +class Waypoints +{ + public: + // Does not require either constructor nor destructor + inline void addWaypoint(WaypointPtr waypoint); + WaypointPtr getWaypointByName(const std::string& name) const; + const WaypointMap& getWaypointsMap() const { + return waypoints; + } + + protected: + WaypointMap waypoints; +}; + + +inline void Waypoints::addWaypoint(WaypointPtr waypoint) +{ + waypoints[waypoint->name] = waypoint; +} + +inline WaypointPtr Waypoints::getWaypointByName(const std::string& name) const +{ + WaypointMap::const_iterator it = waypoints.find(name); + + if (it != waypoints.end()) { + return it->second; + } + + return WaypointPtr(); +} +#endif diff --git a/src/weapons.cpp b/src/weapons.cpp new file mode 100644 index 0000000000..126c4148a1 --- /dev/null +++ b/src/weapons.cpp @@ -0,0 +1,1192 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include "definitions.h" +#include "weapons.h" +#include "combat.h" +#include "tools.h" +#include "configmanager.h" + +#include +#include + +extern Game g_game; +extern Vocations g_vocations; +extern ConfigManager g_config; +extern Weapons* g_weapons; + +Weapons::Weapons(): + m_scriptInterface("Weapon Interface") +{ + m_scriptInterface.initState(); +} + +Weapons::~Weapons() +{ + clear(); +} + +const Weapon* Weapons::getWeapon(const Item* item) const +{ + if (!item) { + return NULL; + } + + WeaponMap::const_iterator it = weapons.find(item->getID()); + + if (it != weapons.end()) { + return it->second; + } + + return NULL; +} + +void Weapons::clear() +{ + WeaponMap::iterator it; + + for (it = weapons.begin(); it != weapons.end(); ++it) { + delete it->second; + } + + weapons.clear(); +} + +LuaScriptInterface& Weapons::getScriptInterface() +{ + return m_scriptInterface; +} + +std::string Weapons::getScriptBaseName() +{ + return "weapons"; +} + +bool Weapons::loadDefaults() +{ + for (uint32_t i = 0; i < Item::items.size(); ++i) { + const ItemType* it = Item::items.getElement(i); + + if (!it || weapons.find(it->id) != weapons.end()) { + continue; + } + + if (it->weaponType != WEAPON_NONE) { + switch (it->weaponType) { + case WEAPON_AXE: + case WEAPON_SWORD: + case WEAPON_CLUB: { + WeaponMelee* weapon = new WeaponMelee(&m_scriptInterface); + weapon->configureWeapon(*it); + weapons[it->id] = weapon; + break; + } + + case WEAPON_AMMO: + case WEAPON_DIST: { + if (it->weaponType == WEAPON_DIST && it->ammoType != AMMO_NONE) { + continue; + } + + WeaponDistance* weapon = new WeaponDistance(&m_scriptInterface); + weapon->configureWeapon(*it); + weapons[it->id] = weapon; + break; + } + default: + break; + } + } + } + + return true; +} + +Event* Weapons::getEvent(const std::string& nodeName) +{ + std::string tmpNodeName = asLowerCaseString(nodeName); + + if (tmpNodeName == "melee") { + return new WeaponMelee(&m_scriptInterface); + } else if (tmpNodeName == "distance") { + return new WeaponDistance(&m_scriptInterface); + } else if (tmpNodeName == "wand" || tmpNodeName == "rod") { + return new WeaponWand(&m_scriptInterface); + } + + return NULL; +} + +bool Weapons::registerEvent(Event* event, xmlNodePtr p) +{ + Weapon* weapon = dynamic_cast(event); + + if (weapon) { + if (weapons.find(weapon->getID()) != weapons.end()) { + std::cout << "[Warning - Weapons::registerEvent] Duplicate registered item with id: " << weapon->getID() << std::endl; + return false; + } + + weapons[weapon->getID()] = weapon; + return true; + } + + return false; +} + +//monsters +int32_t Weapons::getMaxMeleeDamage(int32_t attackSkill, int32_t attackValue) +{ + return ((int32_t)ceil((attackSkill * (attackValue * 0.05)) + (attackValue * 0.5))); +} + +//players +int32_t Weapons::getMaxWeaponDamage(uint32_t level, int32_t attackSkill, int32_t attackValue, float attackFactor) +{ + return (int32_t)ceil((2 * (attackValue * (attackSkill + 5.8) / 25 + (level - 1) / 10.)) / attackFactor); +} + +Weapon::Weapon(LuaScriptInterface* _interface) : + Event(_interface) +{ + m_scripted = false; + id = 0; + level = 0; + magLevel = 0; + mana = 0; + manaPercent = 0; + soul = 0; + exhaustion = 0; + premium = false; + enabled = true; + wieldUnproperly = false; + range = 1; + ammoAction = AMMOACTION_NONE; +} + +Weapon::~Weapon() +{ + // +} + +void Weapon::setCombatParam(const CombatParams& _params) +{ + params = _params; +} + +bool Weapon::configureEvent(xmlNodePtr p) +{ + int32_t intValue; + std::string strValue; + + if (readXMLInteger(p, "id", intValue)) { + id = intValue; + } else { + std::cout << "Error: [Weapon::configureEvent] Weapon without id." << std::endl; + return false; + } + + if (readXMLInteger(p, "lvl", intValue) || readXMLInteger(p, "level", intValue)) { + level = intValue; + } + + if (readXMLInteger(p, "maglv", intValue) || readXMLInteger(p, "maglevel", intValue)) { + magLevel = intValue; + } + + if (readXMLInteger(p, "mana", intValue)) { + mana = intValue; + } + + if (readXMLInteger(p, "manapercent", intValue)) { + manaPercent = intValue; + } + + if (readXMLInteger(p, "soul", intValue)) { + soul = intValue; + } + + if (readXMLInteger(p, "exhaustion", intValue)) { + exhaustion = intValue; + } + + if (readXMLInteger(p, "prem", intValue)) { + premium = (intValue == 1); + } + + if (readXMLInteger(p, "enabled", intValue)) { + enabled = (intValue == 1); + } + + if (readXMLInteger(p, "unproperly", intValue)) { + wieldUnproperly = (intValue == 1); + } + + if (readXMLString(p, "ammo", strValue)) { + std::cout << "Warning: ammo is not longer used in weapons.xml." << std::endl; + } + + typedef std::list STRING_LIST; + STRING_LIST vocStringList; + xmlNodePtr vocationNode = p->children; + + while (vocationNode) { + if (xmlStrcmp(vocationNode->name, (const xmlChar*)"vocation") == 0) { + if (readXMLString(vocationNode, "name", strValue)) { + int32_t vocationId = g_vocations.getVocationId(strValue); + + if (vocationId != -1) { + vocWeaponMap[vocationId] = true; + int32_t promotedVocation = g_vocations.getPromotedVocation(vocationId); + + if (promotedVocation != 0) { + vocWeaponMap[promotedVocation] = true; + } + + readXMLInteger(vocationNode, "showInDescription", intValue); + + if (intValue != 0) { + toLowerCaseString(strValue); + vocStringList.push_back(strValue); + } + } + } + } + + vocationNode = vocationNode->next; + } + + range = Item::items[id].shootRange; + + std::string vocationString; + + if (!vocStringList.empty()) { + for (STRING_LIST::iterator it = vocStringList.begin(); it != vocStringList.end(); ++it) { + if (*it != vocStringList.front()) { + if (*it != vocStringList.back()) { + vocationString += ", "; + } else { + vocationString += " and "; + } + } + + vocationString += *it; + vocationString += "s"; + } + } + + uint32_t wieldInfo = 0; + + if (getReqLevel() > 0) { + wieldInfo |= WIELDINFO_LEVEL; + } + + if (getReqMagLv() > 0) { + wieldInfo |= WIELDINFO_MAGLV; + } + + if (!vocationString.empty()) { + wieldInfo |= WIELDINFO_VOCREQ; + } + + if (isPremium()) { + wieldInfo |= WIELDINFO_PREMIUM; + } + + if (wieldInfo != 0) { + ItemType& it = Item::items.getItemType(id); + it.wieldInfo = wieldInfo; + it.vocationString = vocationString; + it.minReqLevel = getReqLevel(); + it.minReqMagicLevel = getReqMagLv(); + } + + if (configureWeapon(Item::items[getID()])) { + return true; + } + + return false; +} + +bool Weapon::loadFunction(const std::string& functionName) +{ + std::string tmpFunctionName = asLowerCaseString(functionName); + + if (tmpFunctionName == "internalloadweapon" || tmpFunctionName == "default") { + if (configureWeapon(Item::items[getID()])) { + return true; + } + } else if (tmpFunctionName == "script") { + m_scripted = true; + } + + return false; +} + +bool Weapon::configureWeapon(const ItemType& it) +{ + id = it.id; + return true; +} + +std::string Weapon::getScriptEventName() +{ + return "onUseWeapon"; +} + +int32_t Weapon::playerWeaponCheck(Player* player, Creature* target) const +{ + const Position& playerPos = player->getPosition(); + const Position& targetPos = target->getPosition(); + + if (playerPos.z != targetPos.z) { + return 0; + } + + int32_t trueRange; + const ItemType& it = Item::items[getID()]; + + if (it.weaponType == WEAPON_AMMO) { + trueRange = player->getShootRange(); + } else { + trueRange = range; + } + + if (std::max(std::abs(playerPos.x - targetPos.x), std::abs(playerPos.y - targetPos.y)) > trueRange) { + return 0; + } + + if (!player->hasFlag(PlayerFlag_IgnoreWeaponCheck)) { + if (!enabled) { + return 0; + } + + if (player->getMana() < getManaCost(player)) { + return 0; + } + + if (player->getPlayerInfo(PLAYERINFO_SOUL) < soul) { + return 0; + } + + if (isPremium() && !player->isPremium()) { + return 0; + } + + if (!vocWeaponMap.empty()) { + if (vocWeaponMap.find(player->getVocationId()) == vocWeaponMap.end()) { + return 0; + } + } + + int32_t damageModifier = 100; + + if (player->getLevel() < getReqLevel()) { + damageModifier = (isWieldedUnproperly() ? damageModifier / 2 : 0); + } + + if (player->getMagicLevel() < getReqMagLv()) { + damageModifier = (isWieldedUnproperly() ? damageModifier / 2 : 0); + } + + return damageModifier; + } + + return 100; +} + +bool Weapon::useWeapon(Player* player, Item* item, Creature* target) const +{ + int32_t damageModifier = playerWeaponCheck(player, target); + + if (damageModifier == 0) { + return false; + } + + return internalUseWeapon(player, item, target, damageModifier); +} + +bool Weapon::useFist(Player* player, Creature* target) +{ + const Position& playerPos = player->getPosition(); + const Position& targetPos = target->getPosition(); + + if (Position::areInRange<1, 1>(playerPos, targetPos)) { + float attackFactor = player->getAttackFactor(); + int32_t attackSkill = player->getSkill(SKILL_FIST, SKILL_LEVEL); + int32_t attackValue = 7; + + int32_t maxDamage = Weapons::getMaxWeaponDamage(player->getLevel(), attackSkill, attackValue, attackFactor); + + if (random_range(1, 100) <= g_config.getNumber(ConfigManager::CRITICAL_HIT_CHANCE)) { + maxDamage <<= 1; + } + + int32_t damage = -random_range(0, maxDamage, DISTRO_NORMAL); + + CombatParams params; + params.combatType = COMBAT_PHYSICALDAMAGE; + params.blockedByArmor = true; + params.blockedByShield = true; + Combat::doCombatHealth(player, target, damage, damage, params); + + if (!player->hasFlag(PlayerFlag_NotGainSkill) && player->getAddAttackSkill()) { + player->addSkillAdvance(SKILL_FIST, 1); + } + + return true; + } + + return false; +} + +bool Weapon::internalUseWeapon(Player* player, Item* item, Creature* target, int32_t damageModifier) const +{ + if (m_scripted) { + LuaVariant var; + var.type = VARIANT_NUMBER; + var.number = target->getID(); + executeUseWeapon(player, var); + } else { + int32_t damage = (getWeaponDamage(player, target, item) * damageModifier) / 100; + Combat::doCombatHealth(player, target, damage, damage, params); + } + + onUsedAmmo(player, item, target->getTile()); + onUsedWeapon(player, item, target->getTile()); + return true; +} + +bool Weapon::internalUseWeapon(Player* player, Item* item, Tile* tile) const +{ + if (m_scripted) { + LuaVariant var; + var.type = VARIANT_TARGETPOSITION; + var.pos = tile->getPosition(); + executeUseWeapon(player, var); + } else { + Combat::postCombatEffects(player, tile->getPosition(), params); + g_game.addMagicEffect(tile->getPosition(), NM_ME_POFF); + } + + onUsedAmmo(player, item, tile); + onUsedWeapon(player, item, tile); + return true; +} + +void Weapon::onUsedWeapon(Player* player, Item* item, Tile* destTile) const +{ + if (!player->hasFlag(PlayerFlag_NotGainSkill)) { + skills_t skillType; + uint32_t skillPoint = 0; + + if (getSkillType(player, item, skillType, skillPoint)) { + player->addSkillAdvance(skillType, skillPoint); + } + } + + if (!player->hasFlag(PlayerFlag_HasNoExhaustion) && exhaustion > 0) { + player->addWeaponExhaust(exhaustion); + } + + int32_t manaCost = getManaCost(player); + + if (manaCost > 0) { + player->addManaSpent(manaCost); + player->changeMana(-manaCost); + } + + if (!player->hasFlag(PlayerFlag_HasInfiniteSoul) && soul > 0) { + player->changeSoul(-soul); + } +} + +void Weapon::onUsedAmmo(Player* player, Item* item, Tile* destTile) const +{ + if (!g_config.getBoolean(ConfigManager::REMOVE_AMMO)) { + return; + } + + if (ammoAction == AMMOACTION_REMOVECOUNT) { + uint16_t newCount = item->getItemCount(); + + if (newCount > 0) { + newCount--; + } + + g_game.transformItem(item, item->getID(), newCount); + } else if (ammoAction == AMMOACTION_REMOVECHARGE) { + uint16_t newCharge = item->getCharges(); + + if (newCharge > 0) { + newCharge--; + } + + g_game.transformItem(item, item->getID(), newCharge); + } else if (ammoAction == AMMOACTION_MOVE) { + g_game.internalMoveItem(item->getParent(), destTile, INDEX_WHEREEVER, item, 1, NULL, FLAG_NOLIMIT); + } else if (ammoAction == AMMOACTION_MOVEBACK) { + /* do nothing */ + } else if (item->hasCharges()) { + uint16_t newCharge = item->getCharges(); + + if (newCharge > 0) { + newCharge--; + } + + g_game.transformItem(item, item->getID(), newCharge); + } +} + +int32_t Weapon::getManaCost(const Player* player) const +{ + if (mana != 0) { + return mana; + } + + if (manaPercent == 0) { + return 0; + } + + return (player->getMaxMana() * manaPercent) / 100; +} + +bool Weapon::executeUseWeapon(Player* player, const LuaVariant& var) const +{ + //onUseWeapon(cid, var) + if (m_scriptInterface->reserveScriptEnv()) { + ScriptEnvironment* env = m_scriptInterface->getScriptEnv(); + + env->setScriptId(m_scriptId, m_scriptInterface); + env->setRealPos(player->getPosition()); + + lua_State* L = m_scriptInterface->getLuaState(); + + uint32_t cid = env->addThing(player); + + m_scriptInterface->pushFunction(m_scriptId); + lua_pushnumber(L, cid); + m_scriptInterface->pushVariant(L, var); + + bool result = m_scriptInterface->callFunction(2); + m_scriptInterface->releaseScriptEnv(); + + return result; + } else { + std::cout << "[Error] Call stack overflow. Weapon::executeUseWeapon" << std::endl; + return false; + } +} + +WeaponMelee::WeaponMelee(LuaScriptInterface* _interface) : + Weapon(_interface) +{ + params.blockedByArmor = true; + params.blockedByShield = true; + params.combatType = COMBAT_PHYSICALDAMAGE; + elementType = COMBAT_NONE; + elementDamage = 0; +} + +bool WeaponMelee::configureEvent(xmlNodePtr p) +{ + if (!Weapon::configureEvent(p)) { + return false; + } + + return true; +} + +bool WeaponMelee::configureWeapon(const ItemType& it) +{ + if (it.abilities) { + elementType = it.abilities->elementType; + elementDamage = it.abilities->elementDamage; + } + + return Weapon::configureWeapon(it); +} + +bool WeaponMelee::useWeapon(Player* player, Item* item, Creature* target) const +{ + int32_t damageModifier = playerWeaponCheck(player, target); + + if (damageModifier == 0) { + return false; + } + + if (elementDamage != 0) { + int32_t damage = getElementDamage(player, item); + CombatParams eParams; + eParams.combatType = elementType; + eParams.isAggressive = true; + eParams.useCharges = true; + Combat::doCombatHealth(player, target, damage, damage, eParams); + } + + return internalUseWeapon(player, item, target, damageModifier); +} + +void WeaponMelee::onUsedWeapon(Player* player, Item* item, Tile* destTile) const +{ + Weapon::onUsedWeapon(player, item, destTile); +} + +void WeaponMelee::onUsedAmmo(Player* player, Item* item, Tile* destTile) const +{ + Weapon::onUsedAmmo(player, item, destTile); +} + +bool WeaponMelee::getSkillType(const Player* player, const Item* item, + skills_t& skill, uint32_t& skillpoint) const +{ + skillpoint = 0; + + if (player->getAddAttackSkill() && player->getLastAttackBlockType() != BLOCK_IMMUNITY) { + skillpoint = 1; + } + + WeaponType_t weaponType = item->getWeaponType(); + + switch (weaponType) { + case WEAPON_SWORD: { + skill = SKILL_SWORD; + return true; + } + + case WEAPON_CLUB: { + skill = SKILL_CLUB; + return true; + } + + case WEAPON_AXE: { + skill = SKILL_AXE; + return true; + } + + default: + break; + } + + return false; +} + +int32_t WeaponMelee::getElementDamage(const Player* player, const Item* item) const +{ + int32_t attackSkill = player->getWeaponSkill(item); + int32_t attackValue = std::max(0, elementDamage); + float attackFactor = player->getAttackFactor(); + + int32_t maxValue = Weapons::getMaxWeaponDamage(player->getLevel(), attackSkill, attackValue, attackFactor); + + if (random_range(1, 100) <= g_config.getNumber(ConfigManager::CRITICAL_HIT_CHANCE)) { + maxValue <<= 1; + } + + maxValue = int32_t(maxValue * player->getVocation()->meleeDamageMultipler); + return -random_range(0, maxValue, DISTRO_NORMAL); +} + +int32_t WeaponMelee::getWeaponDamage(const Player* player, const Creature* target, const Item* item, bool maxDamage /*= false*/) const +{ + int32_t attackSkill = player->getWeaponSkill(item); + int32_t attackValue = std::max(0, item->getAttack()); + float attackFactor = player->getAttackFactor(); + + int32_t maxValue = Weapons::getMaxWeaponDamage(player->getLevel(), attackSkill, attackValue, attackFactor); + + if (random_range(1, 100) <= g_config.getNumber(ConfigManager::CRITICAL_HIT_CHANCE)) { + maxValue <<= 1; + } + + maxValue = int32_t(maxValue * player->getVocation()->meleeDamageMultipler); + + if (maxDamage) { + return -maxValue; + } + + return -random_range(0, maxValue, DISTRO_NORMAL); +} + +WeaponDistance::WeaponDistance(LuaScriptInterface* _interface) : + Weapon(_interface) +{ + hitChance = 0; + maxHitChance = 0; + breakChance = 0; + ammuAttackValue = 0; + params.blockedByArmor = true; + params.combatType = COMBAT_PHYSICALDAMAGE; + elementType = COMBAT_NONE; + elementDamage = 0; +} + +bool WeaponDistance::configureEvent(xmlNodePtr p) +{ + if (!Weapon::configureEvent(p)) { + return false; + } + + const ItemType& it = Item::items[id]; + + //default values + if (it.ammoType != AMMO_NONE) { + //hit chance on two-handed weapons is limited to 90% + maxHitChance = 90; + } else { + //one-handed is set to 75% + maxHitChance = 75; + } + + if (it.hitChance != 0) { + hitChance = it.hitChance; + } + + if (it.maxHitChance != -1) { + maxHitChance = it.maxHitChance; + } + + if (it.breakChance != -1) { + breakChance = it.breakChance; + } + + if (it.ammoAction != AMMOACTION_NONE) { + ammoAction = it.ammoAction; + } + + int32_t intValue; + + if (readXMLInteger(p, "hitChance", intValue)) { + std::cout << "Warning: hitChance is not longer used in weapons.xml." << std::endl; + } + + if (readXMLInteger(p, "breakChance", intValue)) { + std::cout << "Warning: breakChance is not longer used in weapons.xml." << std::endl; + } + + return true; +} + +bool WeaponDistance::configureWeapon(const ItemType& it) +{ + //default values + if (it.ammoType != AMMO_NONE) { + //hit chance on two-handed weapons is limited to 90% + maxHitChance = 90; + } else { + //one-handed is set to 75% + maxHitChance = 75; + } + + params.distanceEffect = it.shootType; + range = it.shootRange; + ammuAttackValue = it.attack; + + if (it.hitChance != 0) { + hitChance = it.hitChance; + } + + if (it.maxHitChance > 0) { + maxHitChance = it.maxHitChance; + } + + if (it.breakChance > 0) { + breakChance = it.breakChance; + } + + if (it.ammoAction != AMMOACTION_NONE) { + ammoAction = it.ammoAction; + } + + if (it.abilities) { + elementType = it.abilities->elementType; + elementDamage = it.abilities->elementDamage; + } + + return Weapon::configureWeapon(it); +} + +int32_t WeaponDistance::playerWeaponCheck(Player* player, Creature* target) const +{ + Item* bow = player->getWeapon(true); + + if (bow && bow->getWeaponType() == WEAPON_DIST && bow->getID() != id) { + const Weapon* weap = g_weapons->getWeapon(bow); + + if (weap) { + return weap->playerWeaponCheck(player, target); + } + } + + return Weapon::playerWeaponCheck(player, target); +} + +bool WeaponDistance::useWeapon(Player* player, Item* item, Creature* target) const +{ + int32_t damageModifier = playerWeaponCheck(player, target); + + if (damageModifier == 0) { + return false; + } + + int32_t chance; + + if (hitChance == 0) { + //hit chance is based on distance to target and distance skill + uint32_t skill = player->getSkill(SKILL_DIST, SKILL_LEVEL); + const Position& playerPos = player->getPosition(); + const Position& targetPos = target->getPosition(); + uint32_t distance = std::max(std::abs(playerPos.x - targetPos.x), std::abs(playerPos.y - targetPos.y)); + + if (maxHitChance == 75) { + //chance for one-handed weapons + switch (distance) { + case 1: + chance = std::min(skill, 74) + 1; + break; + case 2: + chance = (uint32_t)((float)2.4 * std::min(skill, 28)) + 8; + break; + case 3: + chance = (uint32_t)((float)1.55 * std::min(skill, 45)) + 6; + break; + case 4: + chance = (uint32_t)((float)1.25 * std::min(skill, 58)) + 3; + break; + case 5: + chance = (uint32_t)((float)std::min(skill, 74)) + 1; + break; + case 6: + chance = (uint32_t)((float)0.8 * std::min(skill, 90)) + 3; + break; + case 7: + chance = (uint32_t)((float)0.7 * std::min(skill, 104)) + 2; + break; + default: + chance = hitChance; + break; + } + } else if (maxHitChance == 90) { + //formula for two-handed weapons + switch (distance) { + case 1: + chance = (uint32_t)((float)1.2 * std::min(skill, 74)) + 1; + break; + case 2: + chance = (uint32_t)((float)3.2 * std::min(skill, 28)); + break; + case 3: + chance = (uint32_t)((float)2.0 * std::min(skill, 45)); + break; + case 4: + chance = (uint32_t)((float)1.55 * std::min(skill, 58)); + break; + case 5: + chance = (uint32_t)((float)1.2 * std::min(skill, 74)) + 1; + break; + case 6: + chance = (uint32_t)((float)1.0 * std::min(skill, 90)); + break; + case 7: + chance = (uint32_t)((float)1.0 * std::min(skill, 90)); + break; + default: + chance = hitChance; + break; + } + } else if (maxHitChance == 100) { + switch (distance) { + case 1: + chance = (uint32_t)((float)1.35 * std::min(skill, 73)) + 1; + break; + case 2: + chance = (uint32_t)((float)3.2 * std::min(skill, 30)) + 4; + break; + case 3: + chance = (uint32_t)((float)2.05 * std::min(skill, 48)) + 2; + break; + case 4: + chance = (uint32_t)((float)1.5 * std::min(skill, 65)) + 2; + break; + case 5: + chance = (uint32_t)((float)1.35 * std::min(skill, 73)) + 1; + break; + case 6: + chance = (uint32_t)((float)1.2 * std::min(skill, 87)) - 4; + break; + case 7: + chance = (uint32_t)((float)1.1 * std::min(skill, 90)) + 1; + break; + default: + chance = hitChance; + break; + } + } else { + chance = maxHitChance; + } + } else { + chance = hitChance; + } + + if (item->getWeaponType() == WEAPON_AMMO) { + Item* bow = player->getWeapon(true); + + if (bow && bow->getHitChance() != 0) { + chance += bow->getHitChance(); + } + } + + if (chance >= random_range(1, 100)) { + if (elementDamage != 0) { + int32_t damage = getElementDamage(player, target, item); + CombatParams eParams; + eParams.combatType = elementType; + eParams.isAggressive = true; + eParams.useCharges = true; + Combat::doCombatHealth(player, target, damage, damage, eParams); + } + + Weapon::internalUseWeapon(player, item, target, damageModifier); + } else { + //miss target + Tile* destTile = target->getTile(); + + if (!Position::areInRange<1, 1, 0>(player->getPosition(), target->getPosition())) { + typedef std::vector > RelPosList; + RelPosList destList; + destList.push_back(std::make_pair(-1, -1)); + destList.push_back(std::make_pair(-1, 0)); + destList.push_back(std::make_pair(-1, 1)); + destList.push_back(std::make_pair(0, -1)); + destList.push_back(std::make_pair(0, 0)); + destList.push_back(std::make_pair(0, 1)); + destList.push_back(std::make_pair(1, -1)); + destList.push_back(std::make_pair(1, 0)); + destList.push_back(std::make_pair(1, 1)); + + std::random_shuffle(destList.begin(), destList.end()); + + Position destPos = target->getPosition(); + Tile* tmpTile = NULL; + + for (RelPosList::iterator it = destList.begin(); it != destList.end(); ++it) { + tmpTile = g_game.getTile(destPos.x + it->first, destPos.y + it->second, destPos.z); + + // Blocking tiles or tiles without ground ain't valid targets for spears + if (tmpTile && !tmpTile->hasProperty(IMMOVABLEBLOCKSOLID) && tmpTile->ground != NULL) { + destTile = tmpTile; + break; + } + } + } + + Weapon::internalUseWeapon(player, item, destTile); + } + + return true; +} + +void WeaponDistance::onUsedWeapon(Player* player, Item* item, Tile* destTile) const +{ + Weapon::onUsedWeapon(player, item, destTile); +} + +void WeaponDistance::onUsedAmmo(Player* player, Item* item, Tile* destTile) const +{ + if (ammoAction == AMMOACTION_MOVEBACK && breakChance > 0 && random_range(1, 100) <= breakChance) { + uint16_t newCount = item->getItemCount(); + + if (newCount > 0) { + newCount--; + } + + g_game.transformItem(item, item->getID(), newCount); + } else { + Weapon::onUsedAmmo(player, item, destTile); + } +} + +int32_t WeaponDistance::getElementDamage(const Player* player, const Creature* target, const Item* item) const +{ + int32_t attackValue = elementDamage; + + if (item->getWeaponType() == WEAPON_AMMO) { + Item* bow = const_cast(player)->getWeapon(true); + + if (bow) { + attackValue += bow->getAttack(); + } + } + + int32_t attackSkill = player->getSkill(SKILL_DIST, SKILL_LEVEL); + float attackFactor = player->getAttackFactor(); + + int32_t maxValue = Weapons::getMaxWeaponDamage(player->getLevel(), attackSkill, attackValue, attackFactor); + + if (random_range(1, 100) <= g_config.getNumber(ConfigManager::CRITICAL_HIT_CHANCE)) { + maxValue <<= 1; + } + + maxValue = int32_t(maxValue * player->getVocation()->distDamageMultipler); + + int32_t minValue = 0; + + if (target) { + if (target->getPlayer()) { + minValue = (int32_t)std::ceil(player->getLevel() * 0.1); + } else { + minValue = (int32_t)std::ceil(player->getLevel() * 0.2); + } + } + + return -random_range(minValue, maxValue, DISTRO_NORMAL); +} + +int32_t WeaponDistance::getWeaponDamage(const Player* player, const Creature* target, const Item* item, bool maxDamage /*= false*/) const +{ + int32_t attackValue = ammuAttackValue; + + if (item->getWeaponType() == WEAPON_AMMO) { + Item* bow = const_cast(player)->getWeapon(true); + + if (bow) { + attackValue += bow->getAttack(); + } + } + + int32_t attackSkill = player->getSkill(SKILL_DIST, SKILL_LEVEL); + float attackFactor = player->getAttackFactor(); + + int32_t maxValue = Weapons::getMaxWeaponDamage(player->getLevel(), attackSkill, attackValue, attackFactor); + + if (random_range(1, 100) <= g_config.getNumber(ConfigManager::CRITICAL_HIT_CHANCE)) { + maxValue <<= 1; + } + + maxValue = int32_t(maxValue * player->getVocation()->distDamageMultipler); + + if (maxDamage) { + return -maxValue; + } + + int32_t minValue = 0; + + if (target) { + if (target->getPlayer()) { + minValue = (int32_t)std::ceil(player->getLevel() * 0.1); + } else { + minValue = (int32_t)std::ceil(player->getLevel() * 0.2); + } + } + + return -random_range(minValue, maxValue, DISTRO_NORMAL); +} + +bool WeaponDistance::getSkillType(const Player* player, const Item* item, + skills_t& skill, uint32_t& skillpoint) const +{ + skill = SKILL_DIST; + skillpoint = 0; + + if (player->getAddAttackSkill()) { + switch (player->getLastAttackBlockType()) { + case BLOCK_NONE: { + skillpoint = 2; + break; + } + + case BLOCK_DEFENSE: + case BLOCK_ARMOR: { + skillpoint = 1; + break; + } + + default: + skillpoint = 0; + break; + } + } + + return true; +} + +WeaponWand::WeaponWand(LuaScriptInterface* _interface) : + Weapon(_interface) +{ + minChange = 0; + maxChange = 0; +} + +bool WeaponWand::configureEvent(xmlNodePtr p) +{ + if (!Weapon::configureEvent(p)) { + return false; + } + + int32_t intValue; + std::string strValue; + + if (readXMLInteger(p, "min", intValue)) { + minChange = intValue; + } + + if (readXMLInteger(p, "max", intValue)) { + maxChange = intValue; + } + + if (readXMLString(p, "type", strValue)) { + std::string tmpStrValue = asLowerCaseString(strValue); + + if (tmpStrValue == "earth") { + params.combatType = COMBAT_EARTHDAMAGE; + } else if (tmpStrValue == "ice") { + params.combatType = COMBAT_ICEDAMAGE; + } else if (tmpStrValue == "energy") { + params.combatType = COMBAT_ENERGYDAMAGE; + } else if (tmpStrValue == "fire") { + params.combatType = COMBAT_FIREDAMAGE; + } else if (tmpStrValue == "death") { + params.combatType = COMBAT_DEATHDAMAGE; + } else if (tmpStrValue == "holy") { + params.combatType = COMBAT_HOLYDAMAGE; + } else { + std::cout << "[Warning - WeaponWand::configureEvent] Type \"" << strValue << "\" does not exist." << std::endl; + } + } + + return true; +} + +bool WeaponWand::configureWeapon(const ItemType& it) +{ + range = it.shootRange; + params.distanceEffect = it.shootType; + + return Weapon::configureWeapon(it); +} + +int32_t WeaponWand::getWeaponDamage(const Player* player, const Creature* target, const Item* item, bool maxDamage /*= false*/) const +{ + if (maxDamage) { + return -maxChange; + } + + return random_range(-minChange, -maxChange, DISTRO_NORMAL); +} diff --git a/src/weapons.h b/src/weapons.h new file mode 100644 index 0000000000..a40e9fd8b1 --- /dev/null +++ b/src/weapons.h @@ -0,0 +1,210 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __OTSERV_WEAPONS_H__ +#define __OTSERV_WEAPONS_H__ + +#include "game.h" +#include "luascript.h" +#include "player.h" +#include "actions.h" +#include "talkaction.h" +#include "baseevents.h" +#include "combat.h" +#include "const.h" + +class Weapon; +class WeaponMelee; +class WeaponDistance; +class WeaponWand; + +class Weapons : public BaseEvents +{ + public: + Weapons(); + virtual ~Weapons(); + + bool loadDefaults(); + const Weapon* getWeapon(const Item* item) const; + + static int32_t getMaxMeleeDamage(int32_t attackSkill, int32_t attackValue); + static int32_t getMaxWeaponDamage(uint32_t level, int32_t attackSkill, int32_t attackValue, float attackFactor); + + protected: + virtual void clear(); + virtual LuaScriptInterface& getScriptInterface(); + virtual std::string getScriptBaseName(); + virtual Event* getEvent(const std::string& nodeName); + virtual bool registerEvent(Event* event, xmlNodePtr p); + + typedef std::map WeaponMap; + WeaponMap weapons; + + LuaScriptInterface m_scriptInterface; +}; + +class Weapon : public Event +{ + public: + Weapon(LuaScriptInterface* _interface); + virtual ~Weapon(); + + virtual bool configureEvent(xmlNodePtr p); + virtual bool loadFunction(const std::string& functionName); + virtual bool configureWeapon(const ItemType& it); + virtual bool interruptSwing() const { + return false; + } + + virtual int32_t playerWeaponCheck(Player* player, Creature* target) const; + static bool useFist(Player* player, Creature* target); + virtual bool useWeapon(Player* player, Item* item, Creature* target) const; + + void setCombatParam(const CombatParams& _params); + + uint16_t getID() const { + return id; + } + virtual int32_t getWeaponDamage(const Player* player, const Creature* target, const Item* item, bool maxDamage = false) const = 0; + + uint32_t getReqLevel() const { + return level; + } + int32_t getReqMagLv() const { + return magLevel; + } + bool hasExhaustion() const { + return exhaustion != 0; + } + bool isPremium() const { + return premium; + } + bool isWieldedUnproperly() const { + return wieldUnproperly; + } + + protected: + virtual std::string getScriptEventName(); + + bool executeUseWeapon(Player* player, const LuaVariant& var) const; + bool internalUseWeapon(Player* player, Item* item, Creature* target, int32_t damageModifier) const; + bool internalUseWeapon(Player* player, Item* item, Tile* tile) const; + + virtual void onUsedWeapon(Player* player, Item* item, Tile* destTile) const; + virtual void onUsedAmmo(Player* player, Item* item, Tile* destTile) const; + virtual bool getSkillType(const Player* player, const Item* item, skills_t& skill, uint32_t& skillpoint) const { + return false; + } + + int32_t getManaCost(const Player* player) const; + + uint16_t id; + bool enabled; + bool premium; + uint32_t exhaustion; + bool wieldUnproperly; + int32_t level; + int32_t magLevel; + int32_t mana; + int32_t manaPercent; + int32_t soul; + int32_t range; + AmmoAction_t ammoAction; + CombatParams params; + + private: + typedef std::map VocWeaponMap; + VocWeaponMap vocWeaponMap; +}; + +class WeaponMelee : public Weapon +{ + public: + WeaponMelee(LuaScriptInterface* _interface); + virtual ~WeaponMelee() {} + + virtual bool configureEvent(xmlNodePtr p); + virtual bool configureWeapon(const ItemType& it); + + virtual bool useWeapon(Player* player, Item* item, Creature* target) const; + virtual int32_t getWeaponDamage(const Player* player, const Creature* target, const Item* item, bool maxDamage = false) const; + + int32_t getElementDamage(const Player* player, const Item* item) const; + + protected: + virtual void onUsedWeapon(Player* player, Item* item, Tile* destTile) const; + virtual void onUsedAmmo(Player* player, Item* item, Tile* destTile) const; + virtual bool getSkillType(const Player* player, const Item* item, skills_t& skill, uint32_t& skillpoint) const; + + CombatType_t elementType; + int16_t elementDamage; +}; + +class WeaponDistance : public Weapon +{ + public: + WeaponDistance(LuaScriptInterface* _interface); + virtual ~WeaponDistance() {} + + virtual bool configureEvent(xmlNodePtr p); + virtual bool configureWeapon(const ItemType& it); + virtual bool interruptSwing() const { + return true; + } + + virtual int32_t playerWeaponCheck(Player* player, Creature* target) const; + virtual bool useWeapon(Player* player, Item* item, Creature* target) const; + virtual int32_t getWeaponDamage(const Player* player, const Creature* target, const Item* item, bool maxDamage = false) const; + + int32_t getElementDamage(const Player* player, const Creature* target, const Item* item) const; + + protected: + virtual void onUsedWeapon(Player* player, Item* item, Tile* destTile) const; + virtual void onUsedAmmo(Player* player, Item* item, Tile* destTile) const; + virtual bool getSkillType(const Player* player, const Item* item, skills_t& skill, uint32_t& skillpoint) const; + + int32_t hitChance; + int32_t maxHitChance; + int32_t breakChance; + int32_t ammuAttackValue; + + CombatType_t elementType; + int16_t elementDamage; +}; + +class WeaponWand : public Weapon +{ + public: + WeaponWand(LuaScriptInterface* _interface); + virtual ~WeaponWand() {} + + virtual bool configureEvent(xmlNodePtr p); + virtual bool configureWeapon(const ItemType& it); + + virtual int32_t getWeaponDamage(const Player* player, const Creature* target, const Item* item, bool maxDamage = false) const; + + protected: + virtual bool getSkillType(const Player* player, const Item* item, skills_t& skill, uint32_t& skillpoint) const { + return false; + } + + int32_t minChange; + int32_t maxChange; +}; + +#endif diff --git a/src/wildcardtree.cpp b/src/wildcardtree.cpp new file mode 100644 index 0000000000..e0a781c74f --- /dev/null +++ b/src/wildcardtree.cpp @@ -0,0 +1,138 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#include "otpch.h" + +#include + +#include "wildcardtree.h" + +WildcardTreeNode::WildcardTreeNode(bool breakpoint) +{ + this->breakpoint = breakpoint; +} + +WildcardTreeNode::~WildcardTreeNode() +{ + for (std::map::const_iterator it = children.begin(), end = children.end(); it != end; ++it) { + delete it->second; + } +} + +WildcardTreeNode* WildcardTreeNode::getChild(char ch) const +{ + std::map::const_iterator it = children.find(ch); + + if (it == children.end()) { + return NULL; + } + + return it->second; +} + +WildcardTreeNode* WildcardTreeNode::addChild(char ch, bool breakpoint) +{ + WildcardTreeNode* child = getChild(ch); + + if (child) { + if (breakpoint && !child->breakpoint) { + child->breakpoint = true; + } + } else { + child = new WildcardTreeNode(breakpoint); + children[ch] = child; + } + + return child; +} + +void WildcardTreeNode::insert(const std::string& str) +{ + WildcardTreeNode* cur = this; + + size_t length = str.length() - 1; + + for (size_t pos = 0; pos < length; ++pos) { + cur = cur->addChild(str[pos], false); + } + + cur->addChild(str[length], true); +} + +void WildcardTreeNode::remove(const std::string& str) +{ + WildcardTreeNode* cur = this; + + std::stack path; + path.push(cur); + size_t len = str.length(); + + for (size_t pos = 0; pos < len; ++pos) { + cur = cur->getChild(str[pos]); + path.push(cur); + } + + cur->breakpoint = false; + + do { + cur = path.top(); + path.pop(); + + if (!cur->children.empty() || cur->breakpoint || path.empty()) { + break; + } + + cur = path.top(); + + std::map::iterator it = cur->children.find(str[--len]); + + if (it != cur->children.end()) { + delete it->second; + cur->children.erase(it); + } + } while (true); +} + +ReturnValue WildcardTreeNode::findOne(const std::string& query, std::string& result) const +{ + const WildcardTreeNode* cur = this; + + for (size_t pos = 0; pos < query.length(); ++pos) { + cur = cur->getChild(query[pos]); + + if (!cur) { + return RET_PLAYERWITHTHISNAMEISNOTONLINE; + } + } + + result = query; + + do { + size_t size = cur->children.size(); + + if (size == 0) { + return RET_NOERROR; + } else if (size > 1 || cur->breakpoint) { + return RET_NAMEISTOOAMBIGIOUS; + } + + std::map::const_iterator it = cur->children.begin(); + result += it->first; + cur = it->second; + } while (true); +} diff --git a/src/wildcardtree.h b/src/wildcardtree.h new file mode 100644 index 0000000000..e9b84f64b4 --- /dev/null +++ b/src/wildcardtree.h @@ -0,0 +1,46 @@ +////////////////////////////////////////////////////////////////////// +// The Forgotten Server - a server application for the MMORPG Tibia +////////////////////////////////////////////////////////////////////// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +////////////////////////////////////////////////////////////////////// + +#ifndef __WILDCARD_TREE_H__ +#define __WILDCARD_TREE_H__ + +#include +#include + +#include "thing.h" + +class WildcardTreeNode +{ + public: + WildcardTreeNode(bool breakpoint); + ~WildcardTreeNode(); + + WildcardTreeNode* getChild(char ch) const; + WildcardTreeNode* addChild(char ch, bool breakpoint); + + void insert(const std::string& str); + void remove(const std::string& str); + + ReturnValue findOne(const std::string& query, std::string& result) const; + + private: + std::map children; + bool breakpoint; +}; + +#endif diff --git a/vc12/arch32.props b/vc12/arch32.props new file mode 100644 index 0000000000..d2adc41ef5 --- /dev/null +++ b/vc12/arch32.props @@ -0,0 +1,13 @@ + + + + + + + + $(TFS_LIBS) + true + + + + \ No newline at end of file diff --git a/vc12/arch64.props b/vc12/arch64.props new file mode 100644 index 0000000000..6c086a54a8 --- /dev/null +++ b/vc12/arch64.props @@ -0,0 +1,12 @@ + + + + + + + + $(TFS_LIBS64) + + + + \ No newline at end of file diff --git a/vc12/debug.props b/vc12/debug.props new file mode 100644 index 0000000000..322ee8f6c3 --- /dev/null +++ b/vc12/debug.props @@ -0,0 +1,17 @@ + + + + + + true + + + + false + false + EnableFastChecks + + + + + \ No newline at end of file diff --git a/vc12/release.props b/vc12/release.props new file mode 100644 index 0000000000..3cd0e2ef82 --- /dev/null +++ b/vc12/release.props @@ -0,0 +1,17 @@ + + + + + + false + + + + Full + + + UseLinkTimeCodeGeneration + + + + \ No newline at end of file diff --git a/vc12/settings.props b/vc12/settings.props new file mode 100644 index 0000000000..35d30f43f0 --- /dev/null +++ b/vc12/settings.props @@ -0,0 +1,84 @@ + + + + + $(TFSSDKDir)\LuaJIT-2.0.1\ + $(TFSSDKDir)\libxml2-2.7.8\ + $(TFSSDKDir)\libiconv-1.14\ + $(TFSSDKDir)\mpir-2.5.0\ + $(TFSSDKDir)\sqlite3-3.7.7.1\ + $(TFSSDKDir)\mysql-connector-c-6.0.2\ + __LUAJIT__;__USE_SQLITE__;__USE_MYSQL__;_CRT_SECURE_NO_WARNINGS; + $(BOOST_PATH);$(LUA_DIR)\include;$(LIBXML2_DIR)\include;$(LIBICONV_DIR)\include;$(GMP_DIR)\include;$(SQLITE_DIR)\include;$(MYSQLC_DIR)\include + $(BOOST_PATH)\stage\lib;$(LUA_DIR)\lib86;$(LIBXML2_DIR)\lib;$(LIBICONV_DIR)\lib;$(GMP_DIR)\lib;$(SQLITE_DIR)\lib;$(MYSQLC_DIR)\lib + $(BOOST_PATH)\stage64\lib;$(LUA_DIR)\lib64;$(LIBXML2_DIR)\lib64;$(LIBICONV_DIR)\lib64;$(GMP_DIR)\lib64;$(SQLITE_DIR)\lib64;$(MYSQLC_DIR)\lib64 + lua51.lib;libxml2.lib;mpir.lib;sqlite.lib;iconv.lib;libmysql.lib + + + false + + + + $(TFS_INCLUDES) + Level3 + true + true + Use + otpch.h + MultiThreadedDLL + + + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies);$(TFS_LIBDEPS) + Default + + + $(PREPROCESSOR_DEFS) + + + + + $(LUA_DIR) + true + + + $(LIBXML2_DIR) + true + + + $(LIBICONV_DIR) + true + + + $(GMP_DIR) + true + + + $(SQLITE_DIR) + true + + + $(MYSQLC_DIR) + true + + + $(PREPROCESSOR_DEFS) + true + + + $(TFS_INCLUDES) + true + + + $(TFS_LIBS) + true + + + $(TFS_LIBS64) + true + + + $(TFS_LIBDEPS) + true + + + \ No newline at end of file diff --git a/vc12/theforgottenserver.filters b/vc12/theforgottenserver.filters new file mode 100644 index 0000000000..41c2a89b5f --- /dev/null +++ b/vc12/theforgottenserver.filters @@ -0,0 +1,537 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/vc12/theforgottenserver.sln b/vc12/theforgottenserver.sln new file mode 100644 index 0000000000..42fdaee11f --- /dev/null +++ b/vc12/theforgottenserver.sln @@ -0,0 +1,26 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "theforgottenserver", "theforgottenserver.vcxproj", "{A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}.Debug|Win32.ActiveCfg = Debug|Win32 + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}.Debug|Win32.Build.0 = Debug|Win32 + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}.Debug|x64.ActiveCfg = Debug|x64 + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}.Debug|x64.Build.0 = Debug|x64 + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}.Release|Win32.ActiveCfg = Release|Win32 + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}.Release|Win32.Build.0 = Release|Win32 + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}.Release|x64.ActiveCfg = Release|x64 + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/vc12/theforgottenserver.vcxproj b/vc12/theforgottenserver.vcxproj new file mode 100644 index 0000000000..3553aa3817 --- /dev/null +++ b/vc12/theforgottenserver.vcxproj @@ -0,0 +1,303 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + Win32Proj + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E} + + + + Application + true + v110 + + + Application + true + v110 + + + Application + false + v110 + + + Application + false + v110 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WIN32;_DEBUG;_CONSOLE;$(PREPROCESSOR_DEFS);%(PreprocessorDefinitions) + ProgramDatabase + Disabled + + + MachineX86 + true + Console + + + + + WIN64;_DEBUG;_CONSOLE;$(PREPROCESSOR_DEFS);%(PreprocessorDefinitions) + ProgramDatabase + + + true + Console + + + + + WIN32;NDEBUG;_CONSOLE;$(PREPROCESSOR_DEFS);%(PreprocessorDefinitions) + MultiThreadedDLL + ProgramDatabase + + + MachineX86 + true + Console + true + true + + + + + WIN64;NDEBUG;_CONSOLE;$(PREPROCESSOR_DEFS);%(PreprocessorDefinitions) + MultiThreadedDLL + ProgramDatabase + + + true + Console + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + otpch.h + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file