From 8fcc7061e903547958f275dc18d7107014aa9469 Mon Sep 17 00:00:00 2001 From: red_kangaroo Date: Wed, 26 Feb 2020 20:47:01 +0100 Subject: [PATCH] (ready) Aslona (#576) * Implement Aslona Castle * Aslona, part II * Bloods * Magic helms have random material Magic helms with pre-defined materials are too strong. Random materials will make it more interesting when you find one. * Add crysteel * Update * Fix ownedroom * Aslona, part III * Goblin Fort, part I * Rebel Camp, part II * Add wizard * Golem update Golem spawning rates are still not OK, with powerful golems sometimes spawning way too early and often. This tries to address this. Also handle theoretical gaseous golems. * Nerf magic helmets Magic helmets with pre-defined materials are too strong, as they appear early with good material AND a magical effect. Make magic helmets use randomized materials. Their magic effects should be good enough to make them competitive with other helmets. Also switch most magic helmets to normal helmet, rather than full helmet. This allows full helmets to be more distinct, as they will offer the best coverage and AV. * Minor tweak * Add wizard II TODO: Fix gas golems! * Nerf magic helmet, attempt II Magic helmets with pre-defined materials are too strong, as they appear early with good material AND a magical effect. Make magic helmets use randomized materials. Their magic effects should be good enough to make them competitive with other helmets. Also switch most magic helmets to normal helmet, rather than full helmet. This allows full helmets to be more distinct, as they will offer the best coverage and AV. * Merge remote-tracking branch 'upstream/master' * Fixes Mirrored items cannot be dismantled into permanent lump of material #534 Use fix from @AquariusPower to stop weapon swap from auto-stealing items from shops #555 * Implement Black Market Especially in later stages of the game, players can find themselves with large amounts of money that can no longer be spend on anything useful (barring priest services and fixing equipment in Attnam), as shops not always offer good items, or anything of value was already bought. This adds a new dungeon, the Black Market, which is randomy placed on the worldmap and may be found by players who go explore the wilderness (This also gives some incentive to go explore, as otherwise the player will only go for the shortest route between UT, Attnam and GC/TX). It has two levels: The first one is full of guards, the second has a hige shop of overpriced items. The purpose of this is to have a palce where players can spend all their hard-earned money, with enough items generated to ensure there will be at least something for every player. However, to prevent unbalancing the game with new shopping options, the Black Market has intentionally overpriced items that will only be affordable to players who have accumulated some excess funds. * Tweaks and fixes * Add missing NEWS * Make some magic helmets full again * Tweaks and fixes * Allowed dungeons * New dialogues Also make NPCs sometimes talk on their own. * Minor changes * Make NPCs talk * Monsters * Minor fixes * Allow levitation in Black Market * Minor changes and fixes * Hide black market After some play testing, I realized that wandering on the worldmap looking for the black market is not really working out. Unfortunately it tends to spawn either close to one of the other dungeons, leading to it being discovered rather soon, or in very remote location, even on other continents. Because the black market should remain a late game area, I think it would be better to make it initially hidden and reveal it only once the player can buy a map that would lead them there. BTW, why was Tomb of Xinroch "locationAW"? :) * No cloth full helmets I forgot to restore non-flexible-only materials for full helmets. * Bug fix * Add dungeon definitions... ..for ease of use and future development. * Monster update * Oops * Fix wizard summons * Minor fixes * Add nurphe bats * Cat taming * Goblin Fort update * Display Will in secrets * Fix and finish Goblin Fort * Give the player a boat for exploration ALso drop "very" from alternate running descriptions, to make them fit on a single line. * Pyramid * Fungal Cave * Script fixes and balancing * Wilderness changes * You have to board/disembark your ship. * You no longer have limitless stamina on worldmap. * Most above ground locations now have day/night cycle and weather. * Quest changes Changes the structure of quests to better allow for multiple alternative main quests without having to juggle with the encrypted scroll for all of them. Petrus now takes the encrypted scroll and gives the player some time to wander Attnam. If the player chats with him again, they will receive the GC quest, but they can also find other people to get other quests. Chat with the necromancer for TX quest, plus some preliminary work for Aslona quests is done. * Basic quest framework * Minor changes * Unify musical instruments under magicalinstrument, move GetCooldown() to this class. * Make movement over ocean slightly slower than on dry land. * Make amulets easily recognizable Change materials of amulets so that they can be easily recognized by their color. Many amulets were off-white, so unlike rings, it was rather difficult to tell them apart without looking at the description. * Muramasa and Masamune * Fix gas traps They should be revealed when you step on them. * Add mirror imps * Add some fun materials * Hotness allows to define materials that burn on contact, like Acidicity works for acid damage. * Several new material effects. * Some minor bug fixes where panic or disease immunity was not correctly respected. * Lobh-se Bite attack can sometimes be replaced by vomiting, plus her SpecialBiteEffect now correctly respects disease immunity. * Lobh-se II Summons spiders, a bit like Genetrix Vesana. * Rebel camp and graphics New graphics by @fejoa, based on #569. * Assorted changes (#8) Merge changes and bugfixes from @AquariusPower. Should fix Attnam#554 Squashed because of large number of commits: * WIP: added auto-pickup list currently 'kiwi|wand|dagger', easy to become an user cfg option later; Fix for when entering a new area/map/dungeon, will create the map note for stairs and try to pick things there; * Fixed: help F1 in case of wide lines, hard limit 80 chars for now; AutoPickUpMatching user option added with detailed help; * autopickup: fix for owned rooms; * autopickup: ignore spoiling; * autopickUp: complex perl regex working with initial example; iosystem - global AlertConfirmMsg(); * autoPickUp: fixed default regex example; * autopickup: improved default regex; * autoPickUp: improved default regex; * more useful default pickup regex match * Doc/HelperScripts/ivanDbgmsg.sh: helper to monitor dbgmsg logs; * craft: limit firesparks to one square craft: re-allow craftable poison/sulphuricacid * craft: added dbg msgs and better abort on craft denial * craft: validate crafted only after it is completely ready; humanoid::SetEquipment: check if item is not null prior to ResetFlyingThrownStep; * craft: now anvil must be on adjacent square; * hiteffectSetup: better/granted init fields; craft: hiteffects when creating items/building; * craft: check craftability b4 being "broken"; * craft: hiteffect fix to check for tools and arms; * craft: workbench should be damaged w/o explosions (that are area effect of fire/forge); AutoPickUp: improved default; * craft: craftcore::canBeCrafted() always allows sticks now (happens when splitting one in many); * crafting: easier carving, most common bladed weapons can be used now; * craft: restored capability to craft the simple chests; * fixed old crash when Terra is being asked for lycanthropy cure at crystal cave; * Main/Include/definesvalidator.h: recreated using (ctrl+`) console command DefVal, also revalidated w/o errors!; * crafthandle::SpawnItem() fixed (workaround) a crash when the final message gets too big * fix Terra when player has the seedling and she wouldn't cure anymore * changed auto-pickup to "thrown weapons" only (never made much sense about other items) * crafted chests now come unlocked; wizard autoplay will be auto disabled to let the read command work again; * commandsystem::IsForRegionListItem() and ::IsForRegionSilhouette() properly implemented now to compare with the command's linked function pointer (instead of the hard to maintain description); auto map note for fountains; deprecated: Doc/HelperScripts/prepareCmdsDescrCode.sh; * auto map note for chair and doublebed; * craft: lowered annoyance to access new craftings by hitting space; * craft: changed flow to let resume/cancel be the first craft option; * craft: nice(?) chosen action message; * fixed: pet stay put at CheckForEnemies() if MayMoveRandomly=true; craft: cleared missing tool msg; * fix glitch that lets steal just sold weapons from shop using weaponswap command key; added bugtrack breakpoint (debug mode only) when showing the map, and also a workaround to prevent crash; * crafting: shows suspended total; * sfx: skips chat messages now; * "Show god info" option changed context: - now F1 shows god's description for coherency - and extra info is now last remembered player's offer god's reaction (much more useful) - v133 (at least) savegames will be imported automatically * items now have DescriptiveInfo that will be shown by pressing F1 * craft: sticks are now "dismantable" into lumps, to easify the melting code; * chat now is easier with previous NPC (as long it dont move) * craft: better carving fail message and failToolMsg(); auto-pickup: wont do it to items auto marked with #d when dropped (but cleared on pickup); auto-pickup: improved default regex; * auto-pickup: better help info; * TODO: fix FIRE_GAS * Bugfixes Among others: * Fix overflowing config strings. * Change WaterRain() into general LiquidRain(). * Fix ACID_GAS and FIRE_GAS. * Explosive liquids can no longer be used to douse flames (#121). * Better help for crafting. * Quests Also fix #530 and address some other bugs, oversights and balance issues. See NEWS. * Item descriptions * Prepare config options help Most entries must still be written. ;) Make restoring equipped items active for all and non-configurable #406 Remove redundant messages from auto map notes #505 * Y'yter Durr and snakes * Item descriptions * Some new graphics * Fountains and chastity belts * Tabs -> Spaces * Add Fusanga * Quests II * Tweaks and balancing * Quests III * Wand AI fix * Quests IV * Fixes * Further fixes * Travis fix * Minor fixes * More lore * Fix mommo crash Does not seem to be crashing anymore, so hopefully fixed. * Rework taming difficulty It was reported that some monsters (notably mistress whip champions and dark knights) were too easy to tame, which unbalanced both taming and body switch. This was because only TamingDifficulty was checked, which defined only several hard to tame monsters. The rest was nearly always successfully tamed. Change that to also take Willpower into account, so that monsters with higher overall stats are harder to tame. TamingDifficulty can still be used to make monsters with low Willpower but high difficulty of taming, or for NO_TAMING flag. * More fixes * Minor changes * Update README, add MANUAL We don't need two README files, and the info needed an update. Old AUTHORS and README can be still found in Doc/Obsolete. * Fixes Also attempt to make some crafting messages a bit more confusing. :) * Two new options * start with no pet * use descriptions instead of numeric HP * Nerf black market * Update * Fixes * Keybindings The command list claimed to be sorted alphabetically, but it really wasn't. Additionally, it was quite chaotic when one tried to find a specific command. I tried to reorganize the commands more by relation, putting similar commands close together. Hopefully this will make it easier for players to get oriented in the list. Also adds movement keys, because they weren't listed anywhere at all, AFAIK. * Update II * Fixes * Fixes * Minor fixes * remove commented out code * fix F1 help for ? menu * try to unify command prompts * Fixes! * Fix what @fejoa said. :) * Fix a bug that prevented temporary teleport lock from actually timing out. * Let the player review their stats on death. * Split CT_MISC_ORGANIC into _ANIMAL and _PLANT to prevent herbivores from eating animal products. * Fix help for sound options, because I misinterpreted them. * Fix + Descriptions * Aciouds -> Caustic once more. :) * Make multiple slayings on a single weapons possible. * Some item descriptions. --- .devsPrefs/red-kangaroo/code_snippets | 98 + .devsPrefs/red-kangaroo/feature_ideas | 184 ++ AUTHORS | 100 - CMakeLists.txt | 4 +- Doc/HelperScripts/ivanDbgmsg.sh | 20 + Doc/HelperScripts/prepareCmdsDescrCode.sh | 3 + Doc/Lore/Fiction/DwarvenWars.txt | 25 + .../Fiction/EncounterWithKamikazeDwarf.txt | 88 + Doc/Lore/Fiction/NewAttnamLegacy.txt | 144 ++ Doc/Work/Ighalli_Pray_ideas.md | 113 ++ FeLib/Include/config.h | 13 +- FeLib/Include/feio.h | 2 + FeLib/Source/config.cpp | 31 +- FeLib/Source/feio.cpp | 61 +- FeLib/Source/felist.cpp | 20 +- FeLib/Source/hscore.cpp | 1 + FeLib/Source/specialkeys.cpp | 12 +- Graphics/Char.png | Bin 27832 -> 31082 bytes Graphics/Humanoid.png | Bin 151117 -> 9721 bytes Graphics/OLTerra.png | Bin 161249 -> 11480 bytes Graphics/WTerra.png | Bin 96087 -> 5958 bytes MANUAL | 439 +++++ Main/Include/bodypart.h | 11 +- Main/Include/char.h | 25 +- Main/Include/command.h | 1 + Main/Include/confdef.h | 41 +- Main/Include/definesvalidator.h | 164 +- Main/Include/game.h | 14 + Main/Include/gear.h | 53 +- Main/Include/god.h | 4 +- Main/Include/hiteffect.h | 21 + Main/Include/human.h | 99 +- Main/Include/iconf.h | 12 +- Main/Include/item.h | 19 +- Main/Include/ivandef.h | 53 +- Main/Include/level.h | 9 +- Main/Include/lsquare.h | 2 +- Main/Include/lterra.h | 2 + Main/Include/lterras.h | 4 +- Main/Include/materia.h | 2 + Main/Include/miscitem.h | 76 +- Main/Include/nonhuman.h | 57 +- Main/Include/rooms.h | 22 + Main/Include/wterra.h | 2 +- Main/Include/wterras.h | 20 + Main/Source/actions.cpp | 4 +- Main/Source/bodypart.cpp | 75 +- Main/Source/bugworkaround.cpp | 2 +- Main/Source/char.cpp | 803 ++++++-- Main/Source/charsset.cpp | 2 + Main/Source/cmdcraft.cpp | 433 +++-- Main/Source/command.cpp | 439 +++-- Main/Source/database.cpp | 4 + Main/Source/dungeon.cpp | 2 +- Main/Source/game.cpp | 133 +- Main/Source/gear.cpp | 367 +++- Main/Source/god.cpp | 70 +- Main/Source/gods.cpp | 42 +- Main/Source/hiteffect.cpp | 39 +- Main/Source/human.cpp | 1430 ++++++++++++--- Main/Source/iconf.cpp | 244 ++- Main/Source/igraph.cpp | 22 +- Main/Source/item.cpp | 75 +- Main/Source/itemset.cpp | 1 + Main/Source/level.cpp | 38 +- Main/Source/lsquare.cpp | 208 ++- Main/Source/lterra.cpp | 14 + Main/Source/lterras.cpp | 620 ++++--- Main/Source/materia.cpp | 48 +- Main/Source/materias.cpp | 11 +- Main/Source/message.cpp | 37 +- Main/Source/miscitem.cpp | 291 ++- Main/Source/nonhuman.cpp | 476 ++++- Main/Source/rooms.cpp | 228 ++- Main/Source/stack.cpp | 2 + Main/Source/team.cpp | 2 +- Main/Source/wterra.cpp | 8 + NEWS | 233 ++- README | 206 --- README.md | 166 +- Save/delsaves.bat | 0 Script/char.dat | 1617 +++++++++++++++-- Script/define.dat | 81 +- Script/dungeon.dat | 29 +- Script/dungeons/AslonaCastle.dat | 681 +++++++ Script/dungeons/Attnam.dat | 60 +- Script/dungeons/BlackMarket.dat | 2 +- Script/dungeons/DarkForest.dat | 104 ++ Script/dungeons/FungalCave.dat | 779 ++++++++ Script/dungeons/GloomyCaves.dat | 6 +- Script/dungeons/GoblinFort.dat | 1093 +++++++++++ Script/dungeons/Irinox.dat | 104 ++ Script/dungeons/Mondedr.dat | 104 ++ Script/dungeons/Pyramid.dat | 716 ++++++++ Script/dungeons/RebelCamp.dat | 406 +++++ Script/dungeons/UnderwaterTunnel.dat | 5 +- Script/dungeons/XinrochTomb.dat | 25 +- Script/glterra.dat | 12 +- Script/item.dat | 757 ++++++-- Script/material.dat | 427 +++-- Script/olterra.dat | 28 +- Script/owterra.dat | 63 +- audio/MIDICodes.h | 516 +++--- audio/MIDIUtils.cpp | 520 +++--- audio/RtMidi.cpp | 20 +- audio/audio.cpp | 2 +- audio/audio_stack.h | 2 +- audio/linkedlist.cpp | 98 +- audio/linkedlist.h | 12 +- audio/midiparser.h | 46 +- audio/midiplayback.cpp | 10 +- fantasyname/namegen.h | 118 +- xbrzscale/xbrz/xbrz.cpp | 10 +- 113 files changed, 14082 insertions(+), 3217 deletions(-) create mode 100644 .devsPrefs/red-kangaroo/code_snippets create mode 100644 .devsPrefs/red-kangaroo/feature_ideas delete mode 100644 AUTHORS create mode 100755 Doc/HelperScripts/ivanDbgmsg.sh create mode 100644 Doc/Lore/Fiction/DwarvenWars.txt create mode 100644 Doc/Lore/Fiction/EncounterWithKamikazeDwarf.txt create mode 100644 Doc/Lore/Fiction/NewAttnamLegacy.txt create mode 100644 Doc/Work/Ighalli_Pray_ideas.md create mode 100644 MANUAL delete mode 100644 README mode change 100644 => 100755 Save/delsaves.bat create mode 100644 Script/dungeons/AslonaCastle.dat create mode 100644 Script/dungeons/DarkForest.dat create mode 100644 Script/dungeons/FungalCave.dat create mode 100644 Script/dungeons/GoblinFort.dat create mode 100644 Script/dungeons/Irinox.dat create mode 100644 Script/dungeons/Mondedr.dat create mode 100644 Script/dungeons/Pyramid.dat create mode 100644 Script/dungeons/RebelCamp.dat diff --git a/.devsPrefs/red-kangaroo/code_snippets b/.devsPrefs/red-kangaroo/code_snippets new file mode 100644 index 000000000..f9eacffa2 --- /dev/null +++ b/.devsPrefs/red-kangaroo/code_snippets @@ -0,0 +1,98 @@ +// Solicitus' banana prayer. +void solicitus::PrayBadEffect() +{ + if(PLAYER->HasAllBodyParts()) //Checks if Player has all his limbs! Important because this effect crashes if you try it with missing limbs. + { + ADD_MESSAGE("A voice speaks. \"BANANAAAAAAAAAAAAAAA.\""); + PLAYER->ChangeMainMaterial(MAKE_MATERIAL(BANANA_FLESH)); //Change whole body to banana flesh. + ADD_MESSAGE("You feel like a banana."); + } + else + ADD_MESSAGE("\"No banana for you.\""); +} + + + +// Sappho belt +ITEM(sappho, chastitybelt) +{ + public: + virtual truth TryKey(item*, character*); + long GetGearStates() const; +}; + +truth sappho::TryKey(item* Key, character* Applier) +{ + /* BROKEN_LOCK should not be possible. */ + + if(Key->CanOpenLockType(HEART_SHAPED_LOCK)) + { + if(Locked) + { + if(Applier->IsPlayer()) + ADD_MESSAGE("You unlock %s.", GetVirtualDescription(DEFINITE).CStr()); + else if(Applier->CanBeSeenByPlayer()) + ADD_MESSAGE("%s unlocks %s.", Applier->CHAR_NAME(DEFINITE), GetVirtualDescription(DEFINITE).CStr()); + } + else + { + if(Applier->IsPlayer()) + ADD_MESSAGE("You lock %s.", GetVirtualDescription(DEFINITE).CStr()); + else if(Applier->CanBeSeenByPlayer()) + ADD_MESSAGE("%s locks %s.", Applier->CHAR_NAME(DEFINITE), GetVirtualDescription(DEFINITE).CStr()); + } + + // Add a tiny chance that any key you use breaks. + if(!RAND_N(Key->GetMainMaterial()->GetStrengthValue())) + { + Key->Break(Applier); + } + + // We're changing GearStates when the belt is locked/unlocked, so re-calculate. + if(Applier->Equips(this)) + Applier->CalculateEquipmentState(); + + Locked = !Locked; + } + else + { + if(Applier->IsPlayer()) + ADD_MESSAGE("%s doesn't fit in the lock.", Key->CHAR_NAME(DEFINITE)); + else if(Applier->CanBeSeenByPlayer()) + ADD_MESSAGE("%s tries to fit %s in the lock, but fails.", + Applier->CHAR_NAME(DEFINITE), Key->CHAR_NAME(DEFINITE)); + } + + return true; +} + +long sappho::GetGearStates() const +{ + if(IsBroken()) + return 0; + else if(IsLocked()) + return TELEPORT_LOCK|POLYMORPH_LOCK; + else + return LEVITATION; +} + +sappho +{ + Possibility = 0; + Adjective = "seductive"; + PostFix = "with a heart-shaped lock named Sappho"; + Alias == "Sappho"; + ArticleMode = FORCE_THE; + CanBeWished = false; + IsMaterialChangeable = false; + IsPolymorphable = false; + IsPolymorphSpawnable = false; + CanBeCloned = false; + CanBeMirrored = true; + CanBePiled = false; + CanBeBroken = false; + CreateLockConfigurations = false; + MainMaterialConfig == CHROME; + MaterialConfigChances == 100; + Price = 1000; +} diff --git a/.devsPrefs/red-kangaroo/feature_ideas b/.devsPrefs/red-kangaroo/feature_ideas new file mode 100644 index 000000000..d7a8f6462 --- /dev/null +++ b/.devsPrefs/red-kangaroo/feature_ideas @@ -0,0 +1,184 @@ +* port 3D option from IVANX + +* zombie town +* valpurium mine +* penguins who perform anti-sci-talk +* taxidermy! +* smurfs with hammer and sickle + +* body part damage + * crits can cut off pieces: + head -> eyes + torso -> breasts (females only) + groin -> nuts (males only) + + +Features +======== +* wellspring +* fence +* gravestone + +* saplings for all trees + +Rooms +----- +* animal zoo +* graveyard +* orcish barracks +* swamp/flooded room +* lost library +* garden + * grass, trees, fountain +* dilapidated armory +* daemon pit +* fungus farm +* chameleon zoo + +Items +===== +* scroll of harden and change can be used on secondary material +* wand of locking/opening +* cage trap +* scythe of reaping +* mattock of tremors (causes earthquakes) +* flail + +* Saal'thul gets increased damage per enemy disabler (unconsc, conf, stuck etc.) +* charging/charged <- NOPE, would require passing user as arg +* mutagen +* Turox can be zapped +* weapon that drains stamina, causing Fainting +* lore books/scrolls +* buckler + * glove slot shield +* rusty items can poison +* cloak of magic (rare recharging) +* wand of asphalt rain +* axe of cleaving (battle-axe) +* robe +* demon armor (rF) + +rogue's ring of rapid retreat +* invis, haste, teleportitis +scroll of war +* summons mirrored valpurium battle-axe +10 + +wand of fear +wand of (cloud, poison? - venom, raw magic? - wonder) + +tunic (under chainmail) +expensive camera +vacuum cleaner +flamethrower +baby bottle +gigantic potatoe + +bow + arrow +sling + +* artifact portable anvil of Loricatus, for crafting +* artifact katana "Razor Wind", as vacuum blade in CLIVAN + +* weird trinkets for someone's collection (quest items) + * the vaccum cleaner, camera and flamethrower + +* Per penalty for full helmets +* horn of blasting (knockback) +* scroll of random material +* shield of displacement (teleport attacker) + +* amulet of psychic protection (MindBlanked) + * blocks telepathy and psi damage + * immunity to confusion and knockout + +scroll of forestry +* transforms nearby terrain into grass, including water and such +* creates several trees, mostly pines and banana trees + +wand of opening +* unlocks and opens doors +* removes or untraps bear traps + +magical staves +* Recharge times when not equipped almost double. +* Generally this counter is decreased by one per turn. +* If an item is equipped the counter is decreased by an extra turn. +* If you attack, it is decreased by an extra turn. +* If you hit, it is decreased by an extra turn. +* If you hit critically, it is decreased by an extra turn. +* If you kill the monster, it is decreased by an extra turn. + +Monsters +======== +drang knight + twin spears + twin hammers + +* huldra + +dwarves +* can be paid to follow you (see CLIVANT human.cpp assassin::BeTalkedTo) +* several types: + dual hammers + war hammer and shield + battle axe + thrown axes + grenadier + rookie kamikaze + +* mushrooms can talk to you if you have ESP +* reaper + only torso and groin vital + necromancer cloak + skull + flesh material: wraith bone (can regenerate) + scythe, cloak + ethereal, teleporting +* dark spirit knight +* leper knight +* orc hornblower + random horn for SpecialEnemySightedReaction +* flying fish +* ancient golem + can equip + oil for blood +* dark daemon + liquid darkness vomit +* blood daemon + oree stats -50% + +Uniques +------- +souldrinker Jorr Al Nah the exalted necromancer +* DRAIN insetad of magic missile +* conjure miasma -> random gas +* summon bone golems, ghosts + +Pristina the half-dragon priestess +* nod to DS, so has cloak of invisibility and a scythe +* the only high priestess of Valpurus ever, now censured to ever exist + +Fusanga the massive magical mushroom matriarch +* buffs as mystic frog, plus spawns as Jenny + +cult leader +* as female cultist, but randomly scythe of terror, oxid, or good enchantment +* only spawned in room on altar to Morty, with cultists + +kobold storyteller +* female, Mondedr +* Valpurus killed Mortifer, from his blood daemons, from him Curse of Undeath +Hime the masterless swordswoman +* wields katana and a short sword + +Coe-Nan the barbarian + +kobold matriarch +* largecreature + +* fallen valpurist +* the great white hunter + +Materials +========= +* dolphin flesh increases int +* ommel vomit grants huge nutrition diff --git a/AUTHORS b/AUTHORS deleted file mode 100644 index 650206221..000000000 --- a/AUTHORS +++ /dev/null @@ -1,100 +0,0 @@ -/------------------------------\ -|Iter Vehemens ad Necem credits| -\------------------------------/ - - -/-------\ -|IvanDev| -\-------/ - -Master Programmer -(implemented most of the bugs) ------------------------------- - -Timo Kiviluoto - - -Apprentice Programmer, PR Guy, Porter -(made some of the bugs, but they're -all really features, or OS's fault) -------------------------------------- - -Heikki Sairanen - - -Head Graphics Designer -(drew all the heads) ----------------------- - -Tuukka Virtaperko - - -/----------------------------\ -|Other people who have helped| -\----------------------------/ - -Additional coders ------------------ - -In order of importance - -Ilari Kaartinen -Alex Mooney -Mark Schreiber -Miles Bader -Perttu Luukko -Niko Kosonen - -Additional graphics -------------------- - -In order of importance - -Frederic "blob" Tarabout -Vesa Peltonen -Corey Martin - -Additional level design ------------------------ - -Corey Martin - -Authors of the RNG we use -------------------------- - -Takuji Nishimura -Makoto Matsumoto - -Author of the font we use -------------------------- - -Shawn Hargreaves's Allegro - - A game programming library - -Idea providers and bug hunters -------------------------------- - -In alphabetical order - -Atte Aholainen -Chris Allcock -Brian Angeletti -Laurent Birtz -Christian Harms -Matt Howe -Ilari Kaartinen -Wojciech Kaczmarek -Henri Kiviluoto -Thomas Klausner -Niko Kosonen -Perttu Luukko -Corey Martin -Janne Miettinen -Kari Pahula -Vesa Peltonen -Philip Rawson -Will Riley -Renne Sairanen -Adam Joseph Robert Walker Smith III -Norvell Spearman -Konstantin Stupnik diff --git a/CMakeLists.txt b/CMakeLists.txt index e0068797e..d58e96a89 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,7 @@ if(UNIX) if(BUILD_MAC_APP) install(DIRECTORY Graphics Script Music Sound DESTINATION "ivan") - install(FILES AUTHORS COPYING INSTALL LICENSING NEWS README README.md DESTINATION "docs") + install(FILES COPYING INSTALL LICENSING NEWS MANUAL README.md DESTINATION "docs") add_definitions(-DMAC_APP) else() install(DIRECTORY Graphics Script Music Sound DESTINATION "${CMAKE_INSTALL_DATADIR}/ivan") @@ -40,7 +40,7 @@ if(UNIX) elseif(WIN32) install(DIRECTORY Graphics Script Music Sound DESTINATION "ivan") - install(FILES AUTHORS COPYING INSTALL LICENSING NEWS README README.md DESTINATION "ivan") + install(FILES COPYING INSTALL LICENSING NEWS MANUAL README.md DESTINATION "ivan") add_definitions(-DWIN32) if(MINGW) diff --git a/Doc/HelperScripts/ivanDbgmsg.sh b/Doc/HelperScripts/ivanDbgmsg.sh new file mode 100755 index 000000000..4aa3cc6f6 --- /dev/null +++ b/Doc/HelperScripts/ivanDbgmsg.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -eu + +strFilter="$1" + +# both current and old log files +# only lines with datetime +# sort by datetime and dbgmsgId +while true;do + cat ~/.ivan.dbgmsg.log* ~/.ivan/.ivan.dbgmsg.log* \ + |egrep -a "..../../..-..:..:.." \ + |sort -t '(' -k 1.1,1.19 -k 2.1n \ + |egrep -ai "$1" \ + &&: + + ls -l ~/.ivan.dbgmsg.log* ~/.ivan/.ivan.dbgmsg.log*&&: + echo "================== `date` =================" + sleep 1 +done diff --git a/Doc/HelperScripts/prepareCmdsDescrCode.sh b/Doc/HelperScripts/prepareCmdsDescrCode.sh index 2ceebf30b..387cb29a4 100755 --- a/Doc/HelperScripts/prepareCmdsDescrCode.sh +++ b/Doc/HelperScripts/prepareCmdsDescrCode.sh @@ -1,4 +1,7 @@ #!/bin/bash + +echo DEPRECATED;exit 1 + echo "//this script will prepare the full code to determine what commands will enable stretched/scaledup blitting regions" echo "//just paste the output in the proper source file!" diff --git a/Doc/Lore/Fiction/DwarvenWars.txt b/Doc/Lore/Fiction/DwarvenWars.txt new file mode 100644 index 000000000..9b6cf0289 --- /dev/null +++ b/Doc/Lore/Fiction/DwarvenWars.txt @@ -0,0 +1,25 @@ +Dwarves were never numerous, but once upon a time, they were the greatest of all races. There used to be three mighty dwarven city-states: the Hammer, the Crown and the Anvil. + +Hammer-dwarves of Kharad-durr were artificers and sorcerers known for forging objects imbued with incredible magics, from golems to enchanted weaponry. The black iron walls of their fortress-city were never breached, and military supremacy of the Hammer was unquestionable ever since the death of Xinroch. + +Crown-dwarves of Khaz-zadm were artisans and merchants, their underground city sitting on rich mines and sprawling trade tunnels. They mined for gemstones and precious metals, then worked them into wonderful jewels and sold them for exorbitant prices. The wealth of the Crown was legendary, overshadowing even the hoards of ancient dragons. + +Anvil-dwarves of Kharaz-arad were alchemists and scholars, their progress in the natural sciences rapid and unstoppable. Their mountain halls of concrete and steel had automated heating, lighting and ventilation, their medicinal knowledge ensured long and healthy life, and their libraries overflowed with wisdom. + +As it often is with mighty nations, their objective prominence and subjective superiority caused rivalry, envy and friction, until they came to war. + +The initial struggles occurred between the Hammer and the Anvil, with the Crown adding metaphorical fuel to the flames on the scarce few pre-war diplomatic meetings, hoping to make good profit from trading with both sides once the fighting starts. Had everything gone according to their plans, they would act as a balancing force between the other two dwarven nations, making big money while the combatants exhausted themselves into insignificance. But no plan ever survives first contact with the enemy. + +Hammer-dwarves had an upper hand from the very the beginning, blitzkrieging through the outer defenses and fortifications of Kharaz-arad with ease. Their golems tore apart the forts stone from stone as easily as they tore the defenders limb from limb. The Anvil recouped with dangerous alchemical concoctions and guerrilla warfare. The golems might have been strong and barely stopped by walls, but the Anvil filled the underground with minefields and the golems crippled themselves. The soldiers of the Hammer wielded deadly weapons, so the Anvil never confronted them face to face. They were protected by enchanted armour, but what use are enchantments when the person wearing it is covered head to toe in napalm? They fought bravely, but even bravery breaks when the encampment is pumped full of mustard gas. + +Slowly and with great difficulties, the Anvil pushed back against the Hammer, and the Crown-dwarves observed their progress with satisfaction, as both sides were steadily running out of resources and would soon be forced to seek outside help. They greatly underestimated the wounded militaristic pride of Kharad-durr. The Hammer, humiliated and desperate for reclaiming their dominance, turned to whatever magic they could use. They started experimenting with necromancy, eventually placing soulsteel phylacteries of dead warriors into new golem-tech bodies, to combine the strength of the golems with the intelligence and ingenuity of the greatest dwarven heroes. These wraith-golems led armies of disposable zombies and skeletons raised right from the battlefield, unafraid of fire and immune to gas, overwhelming the minefields by sheer numbers. And yet the Hammer-dwarves had another great breakthrough around that time - they managed to trap a spell in an implement that could then discharge the spell without the need for a mage. The first magic wands were mass produced and transformed the battlefields into death zones of flying elemental bolts and curses. They crushed the Anvil-dwarves. + +The Crown finally entered the war at that time, terrified by the Hammer's new found powers of mass destruction enough that they firmly sided with the Anvil. A coordinated offensive of mercenary armies paid from the treasures of Khaz-zadm along with surgical strikes of the Anvil's brainwashed terrorists ready to give their lives to destroy strategically important structures forced the Hammer into retreat, cut them from supplies and prevented any further magical research. The troops of the Hammer were routed and scattered, their hillocks and strongholds conquered. With the enemy literally before their gates, the sovereign of Kharad-durr gave order to enact their last defense - a great magical ritual that took the life of most dwarven necromancers, but raised all dead around the city as an uncontrolled undead horde, and continued to raise them again and again. The battle of Kharad-durr was long, arduous and full of unsung sacrifices, but the alliance won and the Hammer-dwarves were destroyed. + +The toll that the Dwarven Wars took even on the victorious nations was enormous. Kharaz-arad was diminished, their city ruined beyond repair and much of their knowledge lost. Goblins and, after the death of Vol-Khan, even orcs saw their weakness and acted upon it, never giving the Anvil-dwarves the respite needed to recover their losses. Over the following centuries, they grew distant from the world, bitter and isolationist. It is said that only recently did they allow two foreigners, human brothers from the land of Bazzaria, to enter Kharaz-arad and learn their secret techniques of torture and brainwashing. + +Even Khaz-zadm, despite their short involvement in the war, was decimated. Their coffers were running low, their mines were undermined and trade routes filled with masterless golems or worse. It was then that the last duchess of Khaz-zadm, Moonrise Stealing Dark, was approached by the high priest Petrus with an offer of charitable help. She should have known better. The mines below their city held the richest deposits of valpurian ore on the whole continent, and the high priest was well known for his avarice. His assistance turned into an invasion, and Khaz-zadm burned. Only a dark and gloomy cave remains of this dwarven fortress-city today, and the few surviving Crown-dwarves remember nothing from their past, serving Valpurus with fanatical, suicidal zeal. + +Other consequences and lasting signs of the War are still present, too. Forgotten minefields, lost kamikaze dwarves who never learnt that their war is long over, necromantic radiation randomly raising undead in many places, cursed items and strange weapons stashed in long abandoned ruins and secret rooms. Folk tales and bardic songs also claim that even though the Hammer-dwarves were slaughtered and their city caved-in, the immortal lich-king of Kharad-durr survives and waits for the glorious day when his army of undead awakens again and the Hammer returns to glory. + +One is for sure, though. No matter the heights of progress and power that the dwarves once reached, they will be remembered for the Dwarven Wars that drove them to the very verge of extinction. How the mighty have fallen indeed. diff --git a/Doc/Lore/Fiction/EncounterWithKamikazeDwarf.txt b/Doc/Lore/Fiction/EncounterWithKamikazeDwarf.txt new file mode 100644 index 000000000..8aab25ec5 --- /dev/null +++ b/Doc/Lore/Fiction/EncounterWithKamikazeDwarf.txt @@ -0,0 +1,88 @@ +Your ears still ringing, you struggle to stand... and fail. Your lungs are burning; there's so much smoke! You begin to drag yourself, on hands and knee, through the shattered remains of the corridor. Your hands find a book, bound in blue, still gripped by the bloody hand of the dwarf. The rest of the vile creature's arm is nowhere to be seen. + +You grab the book - which is still burning - and crawl free of the smouldering ruins of the hallway. Somehow you find a room, and lock the door behind you. Away from the horrors of the dungeon at last, a chance to rest... + +You look at the book you picked up. After removing the hand, and blowing out the flames, you examine it. The book has been badly damaged, and portions are unreadable... The cover depicts a tree. Silva. You never were much of a fan of those tree-hugging bastards, and this "missionary" hasn't changed your opinion much. Still, considering the state of your remaining leg, you might be here a while. You open the book to the first page... + + + In the beginning, there was only TIME. He passed many eons in silence and solitude. + Then came VOID. TIME entered into VOID, and they begat CHAOS and ORDER. From CHAOS + and ORDER came the six elements; and from them, the Great Tree Puu, which carries + all the Cosmos upon her [...] + + [...] + + [...] the Sixteen Saplings, which came to be known as the Divine Trees, each from + a seed created by Puu: + The greatest of these was the firstborn: Paha, the great ebony tree. + The second born was named Kunin, who others call Va[...] + Third was [...] + [...] + The youngest sapling was called Pyha, the mighty sycamore. + + As the Saplings aged, Paha grew envious of the others; for though Paha was mighty, + her domain was home only to rot and death, while the other Divine Trees were tended + by mortals and spirits alike. Paha's hatred grew, until it began to manifest upon + the branches of Puu as a foul spreading blight which corrupted the Cosmos. + + The blight of Paha spread first to the Kuolema, [...] the other [...] to this day. + Neither the Divine Trees nor their spirits could stop the blight, and as it spread, + all it touched - even the Divine Trees themselves - became consumed by Paha's hatred + and rage; they became twisted and diseased, their wood darkened, and they became + themselves pits from whence the blight spread further. + + Pyha saw this, and watched as his brothers and sisters turned foul and evil. He sat + silently as his unblighted siblings and their spirits fought tirelessly against + the darkened Divines, and observed the destruction their war caused to the Cosmos: + stars were destroyed in incredible fireballs, planets were laid barren and stripped + to the raw stone, man and beast and plant alike fell victim to the unending battle. + And yet the corruption spread, until six Divine Trees lay in dark mockery of their [...] + + So, Pyha called her spirits to her, and spoke to them: + + GO INTO THE COSMOS AND FIND ME A CHAMPION, INNOCENT OF SOUL AND PURE OF MIND, STRONG + OF ARM AND STOUT OF HEART. BRING THIS MORTAL TO ME. + + And so, they did as he bade. As centuries passed, his spirits brought before him six + mortals in turn. Each in turn he turned away and sent back to their land. Then, one + among them called Nux returned with a man: a ranger named Mestari, who lived in + the forest of his world, who ate no flesh of beast nor bird nor fish but only the food + freely given by the trees and plants. To this man, Pyha spoke: + + MESTARI. + I GIVE YOU FREELY THIS GIFT OF MY FRUIT. EAT OF IT, AND I WILL FILL YOU WITH MY STRENGTH. + I GIVE YOU FREELY THIS GIFT OF MY LEAVES. WEAVE THEM INTO A GARMENT, AND I WILL SHIELD + YOU FROM OUR ENEMIES SIGHT. + I GIVE YOU FREELY THIS GIFT OF MY BARK. CARRY IT ALWAYS IN YOUR LEFT HAND, AND I WILL + GUARD YOU FROM HARM. + I GIVE YOU FREELY THIS GIFT OF MY WOOD. STRIKE OUR ENEMIES WITH IT, AND THEY SHALL BE + DESTROYED. + TAKE THESE GIFTS. FIND MY SISTER PAHA AND DESTROY THE CORRUPTION THAT PLAGUES THE COSMOS. + + And with these four gifts, Pyha withered and died. Mestari took her gifts, and left, + as Nux and the other spirits guarded the site where Pyha once stood proudly. + + Mestari went first to [...] + [...] + [...] the way. + + At last, Mestari stood before Paha. With a mighty cry of vengeance, he struck Paha + with [...] + [...] but she would not yield. He then [...] + Finally, exhausted and on the verge of death, Mestari plunged the weapon into Paha's + trunk and killed her. Mestari himself quietly slumped against the trunk of Paha and + succumbed to death. + + But even in death, Mestari still played a part. From the weapon embedded in Paha's + trunk sprouted a new Divine Sapling, fed by the flesh of Mestari's body, which he + willingly and happily gave. The sapling called out, and Pyha's spirits came to Her; + and Her name was Silva. She named Nux her archangel, and Mestari Her Champion Eternal. + Silva, Most Holy Divine, Goddess of life and death, of nature and the Great Cycle, + who watches over the wild places and protects us all. + + PRAISE BE UNTO -- + + +Ah, crap. You forgot to lock the other door. That goblin doesn't look too friendly... You manage to regain your foot, and grab your spear... only to discover that it has burned to the point of uselessness. + +As the goblin approaches, sword drawn, snickering with eager laughter, you look to the heavens, and start to pray... diff --git a/Doc/Lore/Fiction/NewAttnamLegacy.txt b/Doc/Lore/Fiction/NewAttnamLegacy.txt new file mode 100644 index 000000000..fd59d8144 --- /dev/null +++ b/Doc/Lore/Fiction/NewAttnamLegacy.txt @@ -0,0 +1,144 @@ +Once there was a village +New Attnam was its name +It grew many bananas +And sold much of the same +Across the village wandered +Kaethos, with one leg short +Tourists buying bananas +Without a second thought +Huang Ming Pong stood waiting +For his meal number seven +While a priestess of nature +Prayed unto the heaven +And in a golden building +Upon a silver floor +Richel Decos stood counting +His profits just once more +His evil grin was sparkling +Not unlike his mithil coat +In memory of rivals +Friends and foes, all smote +An unhappy situation +For all previously there +Cruelty and suffering +Yet Decos did not care +But greed swift turns to anger +When taxes are on the raise +And so upon the peasants +Decos did turn his gaze +Over to the plantations +Where bananas did grow +And there picking bananas +Stood one lowly slave Joe +Now Joe was not the norm here +Hardly timid or mild +Kaethos did often call him +Quite primitive and wild +Though why Decos did chose him +Was simpler than that +He had stood out quite clearly +For his skin was white, not black +So Joe was quickly hastened +From his loved crocodile bay +To the Decos's mansion +Where Decos had much to say +"I have a task for you" +Decos began angrily +"The damn taxes are rising +But bananas sell cheaply! +To Attnam you will travel +And take with you this letter +If you don't I'll eat you +So you had really better" +Decos then stood and waited +For Joe to laugh happily +But a shock was in store +When Joe did not agree +"No" Joe refused, annoyed +"I will not take this task +Only to end up splattered +Like my incarnations past +Too often my ancestors +Have become kobold lunch +Starved tangled in a web +Or limb's eaten with a crunch +Or had their groins destroyed +By a single angry ghost +Unwisely wielded Mjolak +And ended up as toast +Killed by hoards of livings plants +Or their own angered dog +Drunk from a clear fountain +And polyed into a log +Have all their wands explode +From stepping on a mine +Found no decent weapons +And forced to wield only pine +Maybe if they are lucky +Killed by an Enner Beast +A kamikaze dwarf +Or be mystic dark frog feast +So no, I will not accept +This doom another time +New Attnam may be yours +But my life is mine!" +Decos stared in surprise +For a moment thought real hard +And then opened his mouth +"Go on, kill him guards" +'Hmm' Joe thought wisely +As he stepped back in fear +'Perhaps this was not such +A brilliant idea' +It looked like the end for Joe +Fat ladies readied to sing +But hidden up his sleeve +Joe still had one more thing +Since Joe was just a child +From quite an early age +He'd found he had the power +To use magic; a mage +Spiriting up a keyboard +The guards he did perplex +Joe pressed that fateful key +That one that they called 'X' +Suddenly Joe shone brightly +Attributes all but maxed +He summoned Justifier +And began to relax +Nine angry guards flew at him +Their limbs went flying back +So a mildly peeved Decos +Decided to attack +"Die you communist pig!" +Decos yelled at him +As if that actually had +To do with anything +A-slashing went Joe's blade +Tearing up Decos's cloak +Joe kicked him in the groin +These were the words he spoke +"Decos you big fat meanie +Up would seem to be your time +I may still let you live if +You agree to toe the line" +Decos sighed and rolled his eyes +"It's just a game, political!" +Then slashed Joe's head with his whip +And scored a massive critical +Joe collapsed to the ground +His head holding on in vein +No, it could not happen +He could not just die again! +From the heavens burst forth +An illuminating light +He would not do this, cheater +He would rejoin the fight +"But how?" Decos said stunned +As Joe retrieved his blade +And said "It seems you chose +The wrong village to invade" +And so as chaos reigned +And gore took a new meaning +A final blow was dealt +And Richel Decos died screaming diff --git a/Doc/Work/Ighalli_Pray_ideas.md b/Doc/Work/Ighalli_Pray_ideas.md new file mode 100644 index 000000000..33c689eb9 --- /dev/null +++ b/Doc/Work/Ighalli_Pray_ideas.md @@ -0,0 +1,113 @@ +### Gods grant spells +- Mostly function similar to most pen and paper RPGs, which I've never seen in a roguelike +- Make it obvious to the player when they can pray for additional spells + - No hours since prayed or relying on a wiki to know how to make it work; just balance it properly +- Limited capacity for spells based on Widsom +- Deities grant better spells based on their relationship + +#### Spell ideas +From lower level to higher level +##### Common spells +- Summon angel +- Forge limb + - this would be much more difficult than it is currently + - mostly you'll have to rely on healing liquid / healers to get a normal limb back instead +- Summon archangel + +##### Valpurus +- No spells; just use Legifer and Atavus and sacrifice until champion +- When champion, auto-summon a guard as a permanent ally + - guards could possibly upgrade eventually + - at least be replaced some time after their deaths + +##### Legifer +- Current explosion effect +- Smite enemies +- Summon arms and armor (powerful but temporary) +- Summon temporary help + +##### Atavus +- Give away money to Atavus (in general, having less money = better Atavus spells granted) +- Give objects to creatures on the level + - in general, as we add item use we should make more items start in e.g. kobold pockets and fewer on the floors, anyway +- Give objects to player + +##### Seges +- Create water +- Create bread +- Remove poison +- Remove disease +- Cure wounds + +##### Dulcis +- Make the player more beautiful +- Make monsters temporarily pacified +- Summon a permanent bard to follow the player and be mostly useless +- Tame enemies + +##### Sophos +- Levitation +- Teleport the player +- Teleport enemies away +- Mirror self +- Teleport enemy limbs away + +##### Silva +- Create water +- Summon animal allies (temporary then permanent) +- Harden Leather (and higher materials) +- Create meat +- Earthquake + +##### Loricatus +- Ignite enemy equipment +- Detonate gunpowder (within LOS) +- Harden to bronze (and higher materials with higher spells) +- Break enemy equipment +- Create gunpowder + +##### Mellis +- Midas touch +- Sell an object +- Teleport player to a special shop + - be careful to not give the player too much time to heal and so forth here +- Or just buy an item through a menu + - probably the player just 'wishes' for a class of item (a weapon category or armor or boots) and Mellis presents an option to buy + - could lose favor by not taking him up on the offer + +##### Cleptia +- Summon Poison (like a dip; fill bottles or directly poison weapons) +- Haste +- Temporarily gain Searching status (thieves & assassins = know about traps..?) +- Invisibility + +##### Nefas +- Create alcohol +- Confuse enemies +- Summon vomit (can splash on adjacent tile) +- Summon mistress + +##### Scabies +- Disease enemies in sight +- Polymorph player (with higher level = controlled poly) +- Polymorph other (higher level = lower creature power, but based on original power too) +- Disease all enemies on floor + +##### Infuscor +- Ignite enemies + - This is really weak now, but if fire becomes worse, it wouldn't be entry-level anymore) +- Transmute fluid to acid + - e.g. the blood on the floors and on enemies +- Wand of necromancy duplicate +- Throw lightning + +##### Cruentus +- Cause fear +- Summon troll blood +- Summon arms and armor (powerful, but temporary) +- Decapitate foe (does damage to head; not actually automatic death) + +##### Mortifer +- Like today, no spells granted +- Create an Undead team (as opposed to the Enemy team) and when champion of Mortifer, they all stop attacking you and instead fight other enemies + diff --git a/FeLib/Include/config.h b/FeLib/Include/config.h index 74dbf632d..b2924349e 100644 --- a/FeLib/Include/config.h +++ b/FeLib/Include/config.h @@ -57,7 +57,7 @@ class configsystem struct configoption { - configoption(cchar*, cchar*); + configoption(cchar*, cchar*, cchar*); virtual ~configoption() = default; virtual void SaveValue(std::ofstream&) const = 0; virtual void LoadValue(inputfile&) = 0; @@ -65,12 +65,13 @@ struct configoption virtual void DisplayValue(festring&) const = 0; cchar* Name; cchar* Description; + cchar* HelpInfo; festring fsCategory; }; struct stringoption : public configoption { - stringoption(cchar*, cchar*, cfestring&, + stringoption(cchar*, cchar*, cchar*, cfestring&, void (*)(const stringoption*, festring&) = &configsystem::NormalStringDisplayer, truth (*)(stringoption*) @@ -91,7 +92,7 @@ struct stringoption : public configoption struct numberoption : public configoption { - numberoption(cchar*, cchar*, long, + numberoption(cchar*, cchar*, cchar*, long, void (*)(const numberoption*, festring&) = &configsystem::NormalNumberDisplayer, truth (*)(numberoption*) @@ -112,7 +113,7 @@ struct numberoption : public configoption struct scrollbaroption : public numberoption { - scrollbaroption(cchar*, cchar*, long, + scrollbaroption(cchar*, cchar*, cchar*, long, void (*)(const numberoption*, festring&), truth (*)(numberoption*), void (*)(numberoption*, long) @@ -123,7 +124,7 @@ struct scrollbaroption : public numberoption struct truthoption : public configoption { - truthoption(cchar*, cchar*, truth, + truthoption(cchar*, cchar*, cchar*, truth, void (*)(const truthoption*, festring&) = &configsystem::NormalTruthDisplayer, truth (*)(truthoption*) @@ -144,7 +145,7 @@ struct truthoption : public configoption struct cycleoption : public configoption { - cycleoption(cchar*, cchar*, long, long, + cycleoption(cchar*, cchar*, cchar*, long, long, void (*)(const cycleoption*, festring&) = &configsystem::NormalCycleDisplayer, truth (*)(cycleoption*) diff --git a/FeLib/Include/feio.h b/FeLib/Include/feio.h index 59e6e3d64..880435731 100644 --- a/FeLib/Include/feio.h +++ b/FeLib/Include/feio.h @@ -46,6 +46,8 @@ class iosystem truth = true, bitmapeditor = 0); static truth IsOnMenu(); static bool IsInUse(); + static bool AlertConfirmMsg(const char* cMsg,std::vector vfsCritMsgs = std::vector(),bool bConfirmMode=true); + static void AlertConfirmMsgDraw(bitmap* Buffer); }; #endif diff --git a/FeLib/Source/config.cpp b/FeLib/Source/config.cpp index e18f782a8..6419c7312 100644 --- a/FeLib/Source/config.cpp +++ b/FeLib/Source/config.cpp @@ -34,59 +34,58 @@ void configsystem::NormalTruthChanger(truthoption* O, truth What) void configsystem::NormalCycleChanger(cycleoption* O, long What) { O->Value = What; } -configoption::configoption(cchar* Name, cchar* Description) -: Name(Name), Description(Description) { } +configoption::configoption(cchar* Name, cchar* Description, cchar* HelpInfo) +: Name(Name), Description(Description), HelpInfo(HelpInfo) { } stringoption::stringoption(cchar* Name, cchar* Desc, - cfestring& Value, + cchar* HelpInfo, cfestring& Value, void (*ValueDisplayer)(const stringoption*, festring&), truth (*ChangeInterface)(stringoption*), void (*ValueChanger)(stringoption*, cfestring&)) -: configoption(Name, Desc), +: configoption(Name, Desc, HelpInfo), Value(Value), ValueDisplayer(ValueDisplayer), ChangeInterface(ChangeInterface), ValueChanger(ValueChanger) { } -numberoption::numberoption(cchar* Name, cchar* Desc, long Value, +numberoption::numberoption(cchar* Name, cchar* Desc, cchar* HelpInfo, long Value, void (*ValueDisplayer)(const numberoption*, festring&), truth (*ChangeInterface)(numberoption*), void (*ValueChanger)(numberoption*, long)) -: configoption(Name, Desc), +: configoption(Name, Desc, HelpInfo), Value(Value), ValueDisplayer(ValueDisplayer), ChangeInterface(ChangeInterface), ValueChanger(ValueChanger) { } -scrollbaroption::scrollbaroption(cchar* Name, - cchar* Desc, long Value, +scrollbaroption::scrollbaroption(cchar* Name, cchar* Desc, cchar* HelpInfo, long Value, void (*ValueDisplayer)(const numberoption*, festring&), truth (*ChangeInterface)(numberoption*), void (*ValueChanger)(numberoption*, long), void (*BarHandler)(long)) -: numberoption(Name, Desc, Value, ValueDisplayer, +: numberoption(Name, Desc, HelpInfo, Value, ValueDisplayer, ChangeInterface, ValueChanger), BarHandler(BarHandler) { } -truthoption::truthoption(cchar* Name, cchar* Desc, truth Value, +truthoption::truthoption(cchar* Name, cchar* Desc, cchar* HelpInfo, truth Value, void (*ValueDisplayer)(const truthoption*, festring&), truth (*ChangeInterface)(truthoption*), void (*ValueChanger)(truthoption*, truth)) -: configoption(Name, Desc), +: configoption(Name, Desc, HelpInfo), Value(Value), ValueDisplayer(ValueDisplayer), ChangeInterface(ChangeInterface), ValueChanger(ValueChanger) { } -cycleoption::cycleoption(cchar* Name, cchar* Desc, +cycleoption::cycleoption(cchar* Name, cchar* Desc, cchar* HelpInfo, long Value, long CycleCount, void (*ValueDisplayer)(const cycleoption*, festring&), truth (*ChangeInterface)(cycleoption*), void (*ValueChanger)(cycleoption*, long)) -: configoption(Name, Desc), +: configoption(Name, Desc, HelpInfo), Value(Value), CycleCount(CycleCount), ValueDisplayer(ValueDisplayer), ChangeInterface(ChangeInterface), @@ -139,7 +138,7 @@ void configsystem::Show(void (*BackGroundDrawer)(), int Chosen; truth TruthChange = false; - felist List(CONST_S("Which setting do you wish to configure? (* - req. restart)")); + felist List(CONST_S("Which setting do you wish to configure? (* requires restart)")); List.AddDescription(CONST_S("")); List.AddDescription(CONST_S("Setting Value")); @@ -164,6 +163,7 @@ void configsystem::Show(void (*BackGroundDrawer)(), Entry.Resize(iLim-1); Entry<<" "; //space between "columns" Option[c]->DisplayValue(Entry); + Entry.Resize(iLim+30); if(fsLastCategory!=Option[c]->fsCategory){ List.AddEntry(Option[c]->fsCategory, WHITE, 0, NO_IMAGE, false); @@ -171,7 +171,8 @@ void configsystem::Show(void (*BackGroundDrawer)(), } List.AddEntry(Entry, LIGHT_GRAY); - List.SetLastEntryHelp(Option[c]->Description); //TODO show all possible values, and each value could have more details, may require cycling thru them all to get all texts... + // TODO: help should show all possible values with details, may require cycling thru them + List.SetLastEntryHelp(festring() << Option[c]->Description << "\n\n" << Option[c]->HelpInfo); } if(SlaveScreen && ListAttributeInitializer) diff --git a/FeLib/Source/feio.cpp b/FeLib/Source/feio.cpp index bd68f521f..f995cd602 100644 --- a/FeLib/Source/feio.cpp +++ b/FeLib/Source/feio.cpp @@ -802,48 +802,55 @@ long iosystem::ScrollBarQuestion(cfestring& Topic, v2 Pos, return BarValue; } -bool AlertConfirmMsg(const char* cMsg,std::vector vfsCritMsgs = std::vector()) +struct sAlertConfirmMsg{ + bool bShow=false; + const char* cMsg; + std::vector vfsCritMsgs; + bool bConfirmMode; +} sAlertConfirmMsgInst; +void iosystem::AlertConfirmMsgDraw(bitmap* Buffer) { - bInUse=true; - - //TODO this method could be more global - //TODO calc all line withs to determine the full popup width to not look bad if overflow + if(!sAlertConfirmMsgInst.bShow)return; + + //TODO calc all line withs to determine the full popup width to not look bad if overflow, see specialkeys help dialog creation int iLineHeight=20; - v2 v2Border(700,100+(vfsCritMsgs.size()*iLineHeight)); + v2 v2Border(700,100+(sAlertConfirmMsgInst.vfsCritMsgs.size()*iLineHeight)); v2 v2TL(RES.X/2-v2Border.X/2,RES.Y/2-v2Border.Y/2); - DOUBLE_BUFFER->Fill(v2TL,v2Border,DARK_GRAY); - graphics::DrawRectangleOutlineAround(DOUBLE_BUFFER, v2TL, v2Border, YELLOW, true); + Buffer->Fill(v2TL,v2Border,DARK_GRAY); + graphics::DrawRectangleOutlineAround(Buffer, v2TL, v2Border, YELLOW, true); v2TL+=v2(16,16); int y=v2TL.Y; - #define ACMPRINT(S,C) {FONT->Printf(DOUBLE_BUFFER, v2(v2TL.X,y), C, "%s", S);y+=iLineHeight;} - ACMPRINT(cMsg,YELLOW); - ACMPRINT("(y)es, any other key to ignore this message.",WHITE); -// FONT->Printf(DOUBLE_BUFFER, v2(v2TL.X,y), YELLOW, "%s", cMsg); -// y+=iLineHeight; -// FONT->Printf(DOUBLE_BUFFER, v2(v2TL.X,y), WHITE, "%s", "(y)es, any other key to ignore this message."); -// y+=iLineHeight; - for(int i=0;iPrintf(Buffer, v2(v2TL.X,y), C, "%s", S);y+=iLineHeight;} + ACMPRINT(sAlertConfirmMsgInst.cMsg,YELLOW); + if(sAlertConfirmMsgInst.bConfirmMode)ACMPRINT("(y)es, any other key to ignore this message.",WHITE); + for(int i=0;iPrintf(DOUBLE_BUFFER, v2(v2TL.X,y), RED, "%s", "PROBLEMS:"); -// y+=iLineHeight; -// } - ACMPRINT(vfsCritMsgs[i].CStr(),WHITE); -// FONT->Printf(DOUBLE_BUFFER, v2(v2TL.X,y), WHITE, "%s", vfsCritMsgs[i].CStr()); -// y+=iLineHeight; + ACMPRINT(sAlertConfirmMsgInst.vfsCritMsgs[i].CStr(),WHITE); } +} +bool iosystem::AlertConfirmMsg(const char* cMsg,std::vector vfsCritMsgs,bool bConfirmMode) +{ + static bool bDummyInit = [](){graphics::AddDrawAboveAll(&AlertConfirmMsgDraw,100100,"iosystem::AlertConfirmMsgDraw"); return true;}(); + + bInUse=true; + + sAlertConfirmMsgInst.bShow=true; + sAlertConfirmMsgInst.cMsg=cMsg; + sAlertConfirmMsgInst.vfsCritMsgs=vfsCritMsgs; + sAlertConfirmMsgInst.bConfirmMode=bConfirmMode; + AlertConfirmMsgDraw(DOUBLE_BUFFER); graphics::BlitDBToScreen(); //as the final blit may be from StretchedBuffer - if(GET_KEY() == 'y'){ - bInUse=false; - return true; - } + bool bAnswer=false; + if(GET_KEY() == 'y')bAnswer=true; bInUse=false; - return false; + sAlertConfirmMsgInst.bShow=false; + return bAnswer; } static bool bSaveGameSortModeByDtTm; diff --git a/FeLib/Source/felist.cpp b/FeLib/Source/felist.cpp index 33cb0d280..a26f27fcb 100644 --- a/FeLib/Source/felist.cpp +++ b/FeLib/Source/felist.cpp @@ -521,7 +521,8 @@ uint felist::DrawFiltered(bool& bJustExitTheList) } //TODO copy the entiry list if not selectable? nah...? }else - if(specialkeys::IsRequestedEvent(specialkeys::FocusedElementHelp)){ + if(specialkeys::IsRequestedEvent(specialkeys::FocusedElementHelp)) + { festring fs; felistentry* fle = RetrieveSelectableEntry(Entry,Selected); if(fle!=NULL){ @@ -532,13 +533,14 @@ uint felist::DrawFiltered(bool& bJustExitTheList) fs<<"\n"; } fs<< - "[List Help:]\n" - " F1 - show this message\n" - " Ctrl+F - filter entries\n" - " Ctrl+DEL - clear filter\n" - " Home/End/PageUp/PageDown - navigate thru pages\n" - " ESC - exit the list\n" - " SPACE - continue (next page or exit if at last one)\n"; + "Commands:\n" + " F1 show help\n" + " Ctrl + F filter entries\n" + " Ctrl + Del clear filter\n" + " Home / End navigate pages\n" + " PgUp / PgDn\n" + " Esc exit menu\n" + " Space next page\n"; specialkeys::ConsumeEvent(specialkeys::FocusedElementHelp,fs); bJustRefreshOnce=true; break; @@ -971,7 +973,7 @@ truth felist::DrawPage(bitmap* Buffer, v2* pv2FinalPageSize, std::vector0 ? 1 : 0); if(iPgTot==0)iPgTot=1; FONT->Printf(Buffer, v2(Pos.X + 13, LastFillBottom + 10), WHITE, - "- Page %d/%d (Press F1 to show help info) -",iPg,iPgTot); + "- Page %d/%d (Press F1 for help) -",iPg,iPgTot); LastFillBottom += 30; } else diff --git a/FeLib/Source/hscore.cpp b/FeLib/Source/hscore.cpp index b3202ee6d..f0b6c3a60 100644 --- a/FeLib/Source/hscore.cpp +++ b/FeLib/Source/hscore.cpp @@ -91,6 +91,7 @@ void highscore::Draw() const Desc.Resize(13, ' '); Desc << Entry[c]; List.AddEntry(Desc, c == uint(LastAdd) ? WHITE : LIGHT_GRAY, 13); + List.SetLastEntryHelp(festring() << "The brave, foolish souls who ventured into the world of IVAN."); } List.SetFlags(FADE); diff --git a/FeLib/Source/specialkeys.cpp b/FeLib/Source/specialkeys.cpp index 52a715cd0..b7911e0b4 100644 --- a/FeLib/Source/specialkeys.cpp +++ b/FeLib/Source/specialkeys.cpp @@ -178,8 +178,16 @@ bool specialkeys::ConsumeEvent(SKEvent e,festring& fsInOut){DBGLN; std::stringstream ss(fsInOut.CStr()); std::string line; - while(std::getline(ss,line,'\n')) //TODO limit also by line length based on screen width - afsHelpDialog.push_back(festring(line.c_str())); + std::vector afs; + festring fsLine; + while(std::getline(ss,line,'\n')){ // if(fsInOut.Find('\n',0)!=festring::NPos){ + afs.clear(); + fsLine=line.c_str(); + festring::SplitString(fsLine,afs,80,0); //TODO limit line length based on fontWidth vs screenWidth + for(int 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 RV>|!=002ovPDHLkV1n~p8IAw| diff --git a/Graphics/Humanoid.png b/Graphics/Humanoid.png index 39081fa4abfef4b54df34ab0e47810965fdb164c..f16cfde03d626286bbf9478ed08d64331feaf127 100644 GIT binary patch delta 8970 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 XZQ)P8%4_p0UwnMufBV88{Hy;5=SixV diff --git a/Graphics/OLTerra.png b/Graphics/OLTerra.png index 9948d5e71da6037217cf4d1f600865f59b1bfcc0..92d639289062453fb5041ce7c20cda488f2f0bef 100644 GIT binary patch delta 10743 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 GvHu6_yKl(= literal 161249 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 ZcYn9*?|$(Lx3cQKudV!x|FrhM{C|tx94!C< diff --git a/MANUAL b/MANUAL new file mode 100644 index 000000000..b9e49a639 --- /dev/null +++ b/MANUAL @@ -0,0 +1,439 @@ +' + ██▓▄▄▄█████▓▓█████ ██▀███ + ▓██▒▓ ██▒ ▓▒▓█ ▀ ▓██ ▒ ██▒ + ▒██▒▒ ▓██░ ▒░▒███ ▓██ ░▄█ ▒ + ░██░░ ▓██▓ ░ ▒▓█ ▄ ▒██▀▀█▄ + ░██░ ▒██▒ ░ ░▒████▒░██▓ ▒██▒ + ░▓ ▒ ░░ ░░ ▒░ ░░ ▒▓ ░▒▓░ + ▒ ░ ░ ░ ░ ░ ░▒ ░ ▒░ + ▒ ░ ░ ░ ░░ ░ + ░ ░ ░ ░ + + ██▒ █▓▓█████ ██░ ██ ▓█████ ███▄ ▄███▓▓█████ ███▄ █ ██████ + ▓██░ █▒▓█ ▀ ▓██░ ██▒▓█ ▀ ▓██▒▀█▀ ██▒▓█ ▀ ██ ▀█ █ ▒██ ▒ + ▓██ █▒░▒███ ▒██▀▀██░▒███ ▓██ ▓██░▒███ ▓██ ▀█ ██▒░ ▓██▄ + ▒██ █░░▒▓█ ▄ ░▓█ ░██ ▒▓█ ▄ ▒██ ▒██ ▒▓█ ▄ ▓██▒ ▐▌██▒ ▒ ██▒ + ▒▀█░ ░▒████▒░▓█▒░██▓░▒████▒▒██▒ ░██▒░▒████▒▒██░ ▓██░▒██████▒▒ + ░ ▐░ ░░ ▒░ ░ ▒ ░░▒░▒░░ ▒░ ░░ ▒░ ░ ░░░ ▒░ ░░ ▒░ ▒ ▒ ▒ ▒▓▒ ▒ ░ + ░ ░░ ░ ░ ░ ▒ ░▒░ ░ ░ ░ ░░ ░ ░ ░ ░ ░░ ░░ ░ ▒░░ ░▒ ░ ░ + ░░ ░ ░ ░░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ + ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ + ░ + + ▄▄▄ ▓█████▄ ███▄ █ ▓█████ ▄████▄ ▓█████ ███▄ ▄███▓ + ▒████▄ ▒██▀ ██▌ ██ ▀█ █ ▓█ ▀ ▒██▀ ▀█ ▓█ ▀ ▓██▒▀█▀ ██▒ + ▒██ ▀█▄ ░██ █▌ ▓██ ▀█ ██▒▒███ ▒▓█ ▄ ▒███ ▓██ ▓██░ + ░██▄▄▄▄██ ░▓█▄ ▌ ▓██▒ ▐▌██▒▒▓█ ▄ ▒▓▓▄ ▄██▒▒▓█ ▄ ▒██ ▒██ + ▓█ ▓██▒░▒████▓ ▒██░ ▓██░░▒████▒▒ ▓███▀ ░░▒████▒▒██▒ ░██▒ + ▒▒ ▓▒█░ ▒▒▓ ▒ ░ ▒░ ▒ ▒ ░░ ▒░ ░░ ░▒ ▒ ░░░ ▒░ ░░ ▒░ ░ ░ + ▒ ▒▒ ░ ░ ▒ ▒ ░ ░░ ░ ▒░ ░ ░ ░ ░ ▒ ░ ░ ░░ ░ ░ + ░ ▒ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ + ░ ░ ░ ░ ░ ░░ ░ ░ ░ ░ + ░ ░ + +Fellow adventurer, turn back while you can! For here begins the roguelike +Iter Vehemens ad Necem, a Violent Road to Death. If you choose to travel along +it, you will dive into countless exciting adventures to gain items of great +magic, attain powerful equipment made of mysterious materials, bathe in +the blessings of mighty gods and recruit loyal allies of various shapes and +sizes. Unfortunately, along the way you will also often be dangerously injured, +poisoned, catch numerous diseases, lose several limbs and transform into +manifold different kinds of pitiful creatures in the darkest depths of hostile +dungeons. And, at the end of the road, you are bound to perish in a most +gruesome and painful way. + +Don't say we didn't warn you. + + + +Iter Vehemens ad Necem (IVAN) is a graphical roguelike game which currently +runs on Windows, DOS, Linux, and OS X. It features advanced body part and +material handling, multi-colored lighting and, above all, deep game play. + +This is a fan continuation of IVAN by members of the Cathedral of Attnam forum. +Join us on Attnam.com for news, updates, discussion of planned features, +or anything else regarding the game. + + +%%%%%%%%%%%%%%%%%%%%% +% Table of Contents % +%%%%%%%%%%%%%%%%%%%%% + +1. FAQ + +2. Random Name Generator Patterns + +3. Credits + + +Use Ctrl+F to find the appropriate heading by searching for: "% HeadingName" + + + +%%%%%%% +% FAQ % +%%%%%%% + +Q: There are hundreds of roguelikes out there and they are all the same. + Why would I want to try out IVAN? +A: Listing might be the most convenient way to answer this question: + + Body part system: + Remember Monty Python and the Holy Grail? All body parts in IVAN have + separate armors and separate hit points. After you've found your first + good weapons, you will be able to reduce some monsters to helpless + torsos by mutilating them frivolously! The game rewards brutality by + showing its results graphically. Brutality can also be applied to you, + though, so it's best to have a remedy available in case of a lost limb. + + Material and equipment system: + Unlike any other roguelike, there is a certain definition in IVAN for, + say, a longsword. However, the material of a longsword can be anything + at all. If you have been lucky enough to get a scroll of change material, + you will be able to turn your 'iron longsword' to a 'mithril longsword', + but also to a 'bread longsword' if you, for some reason, wanted to eat + a bread that is shaped like a longsword (I'm not saying it's a good idea, + but it's not impossible). Another example: you might also want to turn your + 'leather boots of strength +1' to much stronger 'phoenix feather boots + of strength +1'. Besides allowing you to upgrade your equipment in + a fresh way, the material system makes it possible for the game engine to + easily generate lots of different objects in the dungeons for your use and + amusement. + + Graphic effects: + IVAN has advanced graphics more than comparable to many modern roguelikes. + "But graphics are useless in a roguelike!" you might exclaim. You would + probably reconsider such an opinion when following a wounded and panicked + monster according to its trail of blood. Besides that, you'll face + dynamically generated and animated flames, smoke, auras, weather effects, + explosions, sources of colored light, water and much more. + + General gameplay: + IVAN brings gameplay issues further than most roguelikes, while still + remaining easy to play. Besides the bodypart system and the material and + equipment system there are such curiosities as 2x2 monsters, wandering + mercenaries and slaves which you can recruit/buy, smiths and tailors who + can repair broken equipment, and monsters which are able to reproduce. + The game also features many different deities ranging from chaotic to + neutral and lawful. Each god has its own specialities and altars, and + you will be able to affect your own alignment by the way you play. + And on the top of all that, IVAN's storyline will keep you pumped with + its unique events! + +Q: Help, I have no idea what am I doing! +A: IVAN works pretty much in the same way as other roguelikes. The player + controls a character, which moves and attacks from the direction keys. + All other commands can be found by pressing '?' key in the game. + +Q: What does "Iter Vehemens ad Necem" mean? +A: It's Latin and means "Violent Road to Death". For most players, that's + a perfect description of the typical game. + +Q: Why is my save deleted when I die? +A: Like the creators of other roguelikes, we think this makes gaming much + more exciting, since you must take full responsibility of all your + actions. You have only one chance to live or die. Also, we agree that + "normal" save-loading is OK for games which remain the same in all gaming + sessions, since in these you are not meant to really die as replaying + everything in exactly the same way would be annoying. But the same does + not apply to games with a multitude of random areas and events like IVAN; + the whole fun is trying again and again in everchanging environment, + encountering stranger and more complex situations and becoming better + and better in tackling them. + +Q: Can't you reconsider your opinion about saveloading? +A: No. There are the two things we swore never to do when starting the + project: discarding permadeath and making IVAN a 3D action game. Don't + bother us about this question anymore. + +Q: Not even as an "easy" game option? Pretty please? +A: Shut up. + +Q: What do these strange markings like Dex, Agi mean in the right sidebar? +A: + AStr = Arm Strength Increases melee and thrown damage. + LStr = Leg Strength Increases carrying capacity and kicking damage. + Dex = Dexterity Increases attack speed and accuracy. + Agi = Agility Increases movement speed and dodging ability. + End = Endurance Increases maximum health points and healing rate. + Per = Perception Increases sight range, accuracy and dodging ability. + Int = Intelligence Increases your ability to read and use magic scrolls. + Will = Willpower Increases resistance to mental effects and magic. + Wis = Wisdom Increases your ability to deal with the gods. + Cha = Charisma Increases your ability to haggle and make deals. + Mana = Mana Increases your ability to use magical items. + + If there are two numbers visible after these the first is the attribute + after bonuses and penalties obtained from your equipment or some other + temporary reason. The second number shows the base attribute without the + said modifications. If there is no net-bonus or net-minus, only the base + attribute is shown. + + If the first number is green, then it has increased a short while ago and + if it is red it has decreased. + + Ht = Height Your height in centimetres, or length of body for + animals. Increases your health points, but also your + enemies' chance to hit you. + Wt = Weight Your weight in kilograms. + + HP = Health Points The sum of your body parts' health. + + Gold The amount of money you have. + Day & Time The in-game time you have spent in this playthrough. + Turn The number of turns (commands which take time) you have + used in this playthrough. + +Q: I had 20 HP, but I still died. Is this a bug? +A: No, this is not a bug. You will die (at least in human form) if the HP of + your groin, torso or head reach 0. + +Q: Why do I always miss spiders with my trusty iron mace? +A: Mace is far too big for hitting small creatures. Do you really kill + spiders with such enormous objects in real life? + +Q: What do marks like L+, C-- mean in the pray menu? +A: All gods have a property that tells whether they are supporters of Law + or Chaos or if they are Neutral. This is however too general description + so the pluses and minuses tell of slight differences between the gods. + Eg. Valpurus has the letters L++ this means that Valpurus is extremely + lawful, when Sophos has the letters L-, which in turn means that Sophos + is lawful, but still leaning a bit towards neutrality and even chaos. + +Q: I just lost a leg. How do I get it back? +A: Priests, gods, certain potions and magical items may help you. + +Q: How is score calculated? +A: First the kills of the player and his pets are merged to a single + list. Then for every monster type, the score is calculated with the + following formula: + + Score = sqrt(a) * b * b + + sqrt(a) = Square root of the number of killed monsters of this type. + b = Sum of the attributes of a typical monster of this type. + + The individual scores of the monster types are then added together. + + You also get a high bonus for winning, depending on your victory type + (the score is multiplied by 2, 3, 4 or 5). + +Q: Why does time pass so fast in the game? +A: Who has said the a day is as long in IVAN's world as on Earth? + +Q: How can I compile IVAN source code? +A: See INSTALL. + +Q: I am a Linux / OS X user. Why can't I access the wizard (cheat) mode? +A: The wizard mode functions aren't compiled by default. Change + the environment variable CXXFLAGS to -DWIZARD and recompile. + +Q: I am a DOS user. When I try to run IVAN, I get the message "Load error: + no DPMI - Get csdpmi\b.zip". +A: The DOS binary is compiled using DJGPP, so you need a DPMI server to run + it. One should have been provided along with IVAN, but if that's not the + case, download it from www.delorie.com. + +Q: I've found a bug. What should I do? +A: Write a small description on how the bug occurred and if possible even + how we could replicate it, and report this bug on our GitHub page or the + forum: + + https://github.com/Attnam/ivan + http://attnam.com + + You should mention your full name so we can credit you , if you are the first + to discover this bug. + +Q: I've got a great idea to make IVAN better! What should I do? +A: Post the idea on our forum (see above). You will be credited if we + implement the feature, it's non-trivial, and you're first to suggest it. + +Q: I'm a programmer willing to help you. What should I do? +A: Join us on GitHub! + + Contributions are welcome. Fork the repository and then once you have + made and tested your changes, submit a pull request. It will be reviewed + by one of the admins as soon as possible. + + See .customs.emacs file for the official Ivan C++ Programming Style. + +Q: Will there be a Russian/Chinese/Esperato/etc. version? +A: No, unless you make it. + +Q: How do I buy and sell stuff in shops? +A: Pick items up to buy them, and drop what you want to sell. + +Q: What do the weapon and armor stats mean? +A: Let's see: + + an adamantine short sword covered in blood +6 [1000g, DAM 9-16, + extremely accurate, almost unbreakable, skill 16/0] + + This short sword is made of adamantine, covered in blood, enchanted up to +6, + weights 1000 grams, has the given damage range, is very accurate and very + durable, and your skill with the weapon category is 16, while your + accustomization with this specific sword is 0. + + a phoenix feather armor of great health +7 [1000g, AV 13] + + This armor of great health is made of phoenix feather material, + enchanted up to +7, weights 1000 grams, and has Armor Value (AV) of 13. + +Q: Help, I still have no idea what am I doing! +A: In this case, you might like our wiki: + + https://attnam.com/wiki/Main_Page + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Random Name Generator Patterns % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +Set a pattern for the random fantasy name generator. When you start +a game with no pre-set name, the generator will create a name based +on this pattern. The letters s, v, V, c, B, C, i, m, M, D, and d +represent different types of random replacements. Everything else +is produced literally. + + s - generic syllable + v - vowel + c - consonant + B - consonant or consonant combination suitable for beginning a word + C - consonant or consonant combination suitable anywhere in a word + i - insult + m - mushy name + M - mushy name ending + D - consonant suited for a stupid person's name + d - syllable suited for a stupid person's name (begins with a vowel) + +All characters between parenthesis () are produced literally. For example, +the pattern "s(dim)", produces a random generic syllable followed by "dim". + +Characters between angle brackets <> produce patterns from the table above. +Imagine the entire pattern is wrapped in one of these. + +In both types of groupings, a vertical bar | denotes a random choice. +Empty groups are allowed. For example, "(foo|bar)" produces either +"foo" or "bar". The pattern "" produces a constant, vowel, or +nothing at all. + +An exclamation point ! means to capitalize the component that follows +it. For example, "!(foo)" will produce "Foo" and "v!s" will produce a +lowercase vowel followed by a capitalized syllable, like "eRod". + +A tilde ~ means to reverse the letters of the component that follows +it. For example, "~(foo)" will produce "oof". To reverse an entire +template, wrap it in brackets. For example, to reverse "sV'i" as +a whole use "~". The template "~sV'i" will only reverse +the initial syllable. + + +%%%%%%%%%%% +% Credits % +%%%%%%%%%%% + +OldDev +====== + +Master Programmer +(implemented most of the bugs) +------------------------------ + +Timo Kiviluoto + +Apprentice Programmer, PR Guy, Porter +(made some of the bugs, but they're +all really features, or OS's fault) +------------------------------------- + +Heikki Sairanen + +Head Graphics Designer +(drew all the heads) +---------------------- + +Tuukka Virtaperko + + +NewDev +====== + +See on GitHub: + + https://github.com/Attnam/ivan/graphs/contributors + + +Other +===== + +Additional coders +----------------- + +In order of importance: + +Ilari Kaartinen +Alex Mooney +Mark Schreiber +Miles Bader +Perttu Luukko +Niko Kosonen + +Additional graphics +------------------- + +In order of importance: + +Frederic "blob" Tarabout +Vesa Peltonen +Corey Martin + +Level design +------------ + +Corey Martin + +Authors of RNG +-------------- + +Takuji Nishimura +Makoto Matsumoto + +Author of the font +------------------ + +Shawn Hargreaves's Allegro + - A game programming library + +Idea providers and bug hunters +------------------------------ + +In alphabetical order: + +Atte Aholainen +Chris Allcock +Brian Angeletti +Laurent Birtz +Christian Harms +Matt Howe +Ilari Kaartinen +Wojciech Kaczmarek +Henri Kiviluoto +Thomas Klausner +Niko Kosonen +Perttu Luukko +Corey Martin +Janne Miettinen +Kari Pahula +Vesa Peltonen +Philip Rawson +Will Riley +Renne Sairanen +Adam Joseph Robert Walker Smith III +Norvell Spearman +Konstantin Stupnik + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +FREE SOFTWARE FOREVER! diff --git a/Main/Include/bodypart.h b/Main/Include/bodypart.h index 3904382bf..3a56908c1 100644 --- a/Main/Include/bodypart.h +++ b/Main/Include/bodypart.h @@ -130,6 +130,7 @@ ITEM(bodypart, item) item* GetExternalHelmet() const; item* GetExternalBelt() const; virtual void ReceiveAcid(material*, cfestring&, long); + virtual void ReceiveHeat(material*, cfestring&, long); virtual truth ShowFluids() const { return false; } virtual void TryToRust(long); virtual truth TestActivationEnergy(int); @@ -516,7 +517,7 @@ ITEM(corpse, item) void SetDeceased(character*); virtual void Save(outputfile&) const; virtual void Load(inputfile&); - virtual truth IsDestroyable(ccharacter*) const; + virtual truth IsDestroyable(ccharacter*) const { return true; } virtual long GetTruePrice() const; virtual int GetMaterials() const { return 2; } virtual truth RaiseTheDead(character*); @@ -724,6 +725,14 @@ ITEM(magicmushroomtorso, normaltorso) virtual v2 GetBitmapPos(int) const; }; +ITEM(fusangatorso, largetorso) +{ + // TODO: Animated largetorso. + /*protected: + virtual int GetClassAnimationFrames() const { return 64; } + virtual v2 GetBitmapPos(int) const;*/ +}; + ITEM(dogtorso, normaltorso) { protected: diff --git a/Main/Include/char.h b/Main/Include/char.h index f951d4c68..732cc0d34 100644 --- a/Main/Include/char.h +++ b/Main/Include/char.h @@ -249,6 +249,7 @@ struct characterdatabase : public databasebase truth GhostCopyMaterials; truth CanBeGeneratedOnlyInTheCatacombs; truth IsAlcoholic; + truth IsUndead; truth IsImmuneToWhipOfThievery; fearray AllowedDungeons; }; @@ -309,7 +310,15 @@ class character : public entity, public id truth RemoveEncryptedScroll(); truth HasShadowVeil() const; truth HasLostRubyFlamingSword() const; - truth RemoveShadowVeil(); + truth RemoveShadowVeil(character*); + truth HasNuke() const; + truth RemoveNuke(character*); + truth HasWeepObsidian() const; + truth RemoveWeepObsidian(character*); + truth HasMuramasa() const; + truth RemoveMuramasa(character*); + truth HasMasamune() const; + truth RemoveMasamune(character*); truth IsPlayer() const { return Flags & C_PLAYER; } ulong GetFlags() const { return Flags; } //mainly for debugging truth Engrave(cfestring&); @@ -619,6 +628,7 @@ class character : public entity, public id DATA_BASE_TRUTH(GhostCopyMaterials); DATA_BASE_TRUTH(CanBeGeneratedOnlyInTheCatacombs); DATA_BASE_TRUTH(IsAlcoholic); + DATA_BASE_TRUTH(IsUndead); DATA_BASE_TRUTH(IsImmuneToWhipOfThievery); DATA_BASE_VALUE(const fearray&, AllowedDungeons); int GetType() const { return GetProtoType()->GetIndex(); } @@ -743,7 +753,6 @@ class character : public entity, public id void PrintEndDiseaseImmunityMessage() const; void PrintBeginTeleportLockMessage() const; void PrintEndTeleportLockMessage() const; - void TeleportLockHandler(); virtual void DisplayStethoscopeInfo(character*) const; virtual truth CanUseStethoscope(truth) const; virtual truth IsUsingArms() const; @@ -832,7 +841,7 @@ class character : public entity, public id void SignalBurnLevelChange(); virtual truth UseMaterialAttributes() const = 0; truth IsPolymorphed() const { return Flags & C_POLYMORPHED; } - truth IsInBadCondition() const { return HP * 3 < MaxHP; } + truth IsInBadCondition() const; truth IsInBadCondition(int HP) const { return HP * 3 < MaxHP; } int GetCondition() const; void UpdatePictures(); @@ -908,6 +917,8 @@ class character : public entity, public id character* GetRandomNeighbour(int = (HOSTILE|UNCARING|FRIEND)) const; virtual truth IsRetreating() const; virtual truth IsMushroom() const { return false; } + virtual truth IsMagicDrinker() const { return false; } + virtual truth DrinkMagic(const beamdata&) { return IsMagicDrinker(); } void ResetStates(); virtual head* Behead() { return 0; } void PrintBeginGasImmunityMessage() const; @@ -974,11 +985,14 @@ class character : public entity, public id virtual truth CreateRoute(); void TerminateGoingTo(); virtual truth IsSpy() const { return false; } + virtual truth IsKing() const { return false; } + virtual truth IsLarge() const { return false; } truth CheckForFood(int); truth CheckForFoodInSquare(v2); virtual truth CheckIfSatiated() { return GetNP() > SATIATED_LEVEL; } virtual void SignalNaturalGeneration() { } virtual truth IsBunny() const { return false; } + virtual truth IsFrog() const { return false; } virtual truth IsSpider() const { return false; } void SetConfig(int, int = 0); bodypartslot* GetBodyPartSlot(int I) { return &BodyPartSlot[I]; } @@ -1045,6 +1059,7 @@ class character : public entity, public id void ReceiveBlackUnicorn(long); void ReceiveGrayUnicorn(long); void ReceiveWhiteUnicorn(long); + void ReceiveSickness(long); void DecreaseStateCounter(long, int); truth IsImmuneToLeprosy() const; bodypart* SearchForOriginalBodyPart(int) const; @@ -1141,6 +1156,7 @@ class character : public entity, public id virtual void ApplySpecialAttributeBonuses() { } void ReceiveMustardGas(int, long); void ReceiveMustardGasLiquid(int, long); + void ReceiveFlames(long); truth IsBadPath(v2) const; double& GetExpModifierRef(expid); truth ForgetRandomThing(); @@ -1170,10 +1186,11 @@ class character : public entity, public id truth IsPlayerAutoPlay(); truth CheckAIZapOpportunity(); int GetAdjustedStaminaCost(int, int); - int GetMagicItemCooldown(int); truth TryToStealFromShop(character*, item*); int GetMyVomitMaterial() { return MyVomitMaterial; } void SetNewVomitMaterial(int What) { MyVomitMaterial = What; } + festring GetHitPointDescription() const; + truth WillGetTurnSoon() const; protected: static truth DamageTypeDestroysBodyPart(int); virtual void LoadSquaresUnder(); diff --git a/Main/Include/command.h b/Main/Include/command.h index d3fe75354..98283872a 100644 --- a/Main/Include/command.h +++ b/Main/Include/command.h @@ -43,6 +43,7 @@ class commandsystem static truth IsForRegionListItem(int iIndex); static truth IsForRegionSilhouette(int iIndex); static void PlayerDiedLookMode(bool bSeeWholeMapCheatMode=false); + static void PlayerDiedWeaponSkills(); static void SaveSwapWeapons(outputfile& SaveFile); static void LoadSwapWeapons(inputfile& SaveFile); static void ClearSwapWeapons(); diff --git a/Main/Include/confdef.h b/Main/Include/confdef.h index 1dc67e253..208456c9d 100644 --- a/Main/Include/confdef.h +++ b/Main/Include/confdef.h @@ -249,6 +249,8 @@ #define PYRITE (SOLID_ID + 229) #define HARDENED_ASH (SOLID_ID + 230) #define PSYPHER (SOLID_ID + 231) +#define CRYSTEEL (SOLID_ID + 232) +#define FLAWLESS_CRYSTEEL (SOLID_ID + 233) #define INGOT 2 @@ -298,6 +300,9 @@ #define MUSTARD_GAS (GAS_ID + 11) #define SLEEPING_GAS (GAS_ID + 12) #define TELEPORT_GAS (GAS_ID + 13) +#define LAUGHING_GAS (GAS_ID + 14) +#define ACID_GAS (GAS_ID + 15) +#define FIRE_GAS (GAS_ID + 16) #define LIQUID_ID (4 << 12) @@ -355,6 +360,12 @@ #define QUICK_SILVER (LIQUID_ID + 52) #define MUD (LIQUID_ID + 53) #define QUICK_SAND (LIQUID_ID + 54) +#define TELEPORT_FLUID (LIQUID_ID + 55) +#define VINEGAR (LIQUID_ID + 56) +#define BLACK_BLOOD (LIQUID_ID + 57) +#define POLYMORPHINE (LIQUID_ID + 58) +#define LAVA (LIQUID_ID + 59) +#define NAPALM (LIQUID_ID + 60) #define FLESH_ID (5 << 12) @@ -455,12 +466,13 @@ #define QUARTER_STAFF 15 #define HAMMER 16 #define GRAND_STOLLEN_KNIFE 17 -#define MAGE_STAFF 18 +//#define TODO 18 #define ROLLING_PIN 19 #define FRYING_PAN 20 #define CLAW 21 #define MEAT_CLEAVER 22 #define RUNE_SWORD 23 +#define KATANA 24 #define PUPPY_SKULL 1 @@ -468,6 +480,8 @@ #define GOROVITS_SICKLE 2 #define GOROVITS_SCIMITAR 3 +#define ROYAL_STAFF 1 + #define CHAIN_MAIL 1 #define PLATE_MAIL 2 #define ARMOR_OF_GREAT_HEALTH 3 @@ -520,6 +534,7 @@ #define BOOT_OF_STRENGTH 1 #define BOOT_OF_AGILITY 2 #define BOOT_OF_KICKING 3 +#define BOOT_OF_DISPLACEMENT 4 #define GAUNTLET_OF_STRENGTH 1 #define GAUNTLET_OF_DEXTERITY 2 @@ -574,10 +589,22 @@ #define VETERAN 2 #define EUNUCH 3 #define PATROL 4 +#define TEMPLAR 4 #define SHOP 5 +#define GRAVE_KEEPER 5 #define ELITE 6 #define MASTER 7 #define GRAND_MASTER 8 +#define TOMB_ENTRY 9 +#define TOMB_ENTRY_MASTER 10 +#define HONOR 11 +#define EMISSARY 12 +#define TRAINEE 13 +#define ROOKIE_FEMALE 14 +#define VETERAN_FEMALE 15 +#define ELITE_FEMALE 16 +#define CASTLE 17 +#define ROYAL 18 #define DARK 1 #define GREATER_DARK 2 @@ -596,12 +623,16 @@ #define CONICAL 1 #define FLAT 2 +#define MAGMA 3 +#define BLOAT 4 #define SKELETON_DOG 1 #define LARGE 1 #define GIANT 2 -#define ARANEA 3 +#define PHASE 3 +#define GIANT_GOLD 4 +#define ARANEA 5 #define IMPRISONED_HUNTER 1 @@ -636,6 +667,10 @@ #define GREATER 1 #define GIANT 2 +#define RED_SNAKE 1 +#define GREEN_SNAKE 2 +#define BLUE_SNAKE 3 + #define SLAUGHTERER 1 #define SQUAD_LEADER 2 #define OFFICER 3 @@ -704,6 +739,7 @@ #define STONE_WALL 6 #define ICE_WALL 7 #define BROKEN_WALL 8 +#define TENT_WALL 9 #define PINE 1 #define FIR 2 @@ -750,6 +786,7 @@ #define ROOM_LIBRARY 4 #define ROOM_BANANA_DROP_AREA 5 #define ROOM_SUMO_ARENA 6 +#define ROOM_OWNED_AREA 7 #define ALL_DUNGEONS 32767 diff --git a/Main/Include/definesvalidator.h b/Main/Include/definesvalidator.h index d0306b671..38848c095 100644 --- a/Main/Include/definesvalidator.h +++ b/Main/Include/definesvalidator.h @@ -280,6 +280,14 @@ class definesvalidator{ #endif +#ifdef AMULET_OF_FASTING // DO NOT MODIFY! + bsA = 9; + bsB = AMULET_OF_FASTING; + if(bsA!=bsB) + ssErrors << "Defined AMULET_OF_FASTING with value 9 from .dat file mismatches hardcoded c++ define value of " << AMULET_OF_FASTING << "!" << std::endl; +#endif + + #ifdef AMULET_OF_LIFE_SAVING // DO NOT MODIFY! bsA = 1; bsB = AMULET_OF_LIFE_SAVING; @@ -808,6 +816,14 @@ class definesvalidator{ #endif +#ifdef BEAM_SOFTEN_MATERIAL // DO NOT MODIFY! + bsA = 15; + bsB = BEAM_SOFTEN_MATERIAL; + if(bsA!=bsB) + ssErrors << "Defined BEAM_SOFTEN_MATERIAL with value 15 from .dat file mismatches hardcoded c++ define value of " << BEAM_SOFTEN_MATERIAL << "!" << std::endl; +#endif + + #ifdef BEAM_STRIKE // DO NOT MODIFY! bsA = 1; bsB = BEAM_STRIKE; @@ -832,6 +848,14 @@ class definesvalidator{ #endif +#ifdef BEAM_WALL_CREATION // DO NOT MODIFY! + bsA = 16; + bsB = BEAM_WALL_CREATION; + if(bsA!=bsB) + ssErrors << "Defined BEAM_WALL_CREATION with value 16 from .dat file mismatches hardcoded c++ define value of " << BEAM_WALL_CREATION << "!" << std::endl; +#endif + + #ifdef BEAM_WEBBING // DO NOT MODIFY! bsA = 13; bsB = BEAM_WEBBING; @@ -1449,10 +1473,10 @@ class definesvalidator{ #ifdef Base // DO NOT MODIFY! - bsA = 73; + bsA = 0; bsB = Base; if(bsA!=bsB) - ssErrors << "Defined Base with value 73 from .dat file mismatches hardcoded c++ define value of " << Base << "!" << std::endl; + ssErrors << "Defined Base with value 0 from .dat file mismatches hardcoded c++ define value of " << Base << "!" << std::endl; #endif @@ -1480,6 +1504,14 @@ class definesvalidator{ #endif +#ifdef CAN_BE_DETECTED // DO NOT MODIFY! + bsA = 128; + bsB = CAN_BE_DETECTED; + if(bsA!=bsB) + ssErrors << "Defined CAN_BE_DETECTED with value 128 from .dat file mismatches hardcoded c++ define value of " << CAN_BE_DETECTED << "!" << std::endl; +#endif + + #ifdef CAN_BE_MIRRORED // DO NOT MODIFY! bsA = 64; bsB = CAN_BE_MIRRORED; @@ -2936,6 +2968,14 @@ class definesvalidator{ #endif +#ifdef EFFECT_REGENERATION // DO NOT MODIFY! + bsA = 37; + bsB = EFFECT_REGENERATION; + if(bsA!=bsB) + ssErrors << "Defined EFFECT_REGENERATION with value 37 from .dat file mismatches hardcoded c++ define value of " << EFFECT_REGENERATION << "!" << std::endl; +#endif + + #ifdef EFFECT_SCHOOL_FOOD // DO NOT MODIFY! bsA = 8; bsB = EFFECT_SCHOOL_FOOD; @@ -2952,6 +2992,14 @@ class definesvalidator{ #endif +#ifdef EFFECT_TELEPORTATION // DO NOT MODIFY! + bsA = 38; + bsB = EFFECT_TELEPORTATION; + if(bsA!=bsB) + ssErrors << "Defined EFFECT_TELEPORTATION with value 38 from .dat file mismatches hardcoded c++ define value of " << EFFECT_TELEPORTATION << "!" << std::endl; +#endif + + #ifdef EFFECT_TELEPORT_CONTROL // DO NOT MODIFY! bsA = 23; bsB = EFFECT_TELEPORT_CONTROL; @@ -3928,6 +3976,14 @@ class definesvalidator{ #endif +#ifdef GUILD_TEAM // DO NOT MODIFY! + bsA = 5; + bsB = GUILD_TEAM; + if(bsA!=bsB) + ssErrors << "Defined GUILD_TEAM with value 5 from .dat file mismatches hardcoded c++ define value of " << GUILD_TEAM << "!" << std::endl; +#endif + + #ifdef GUN_POWDER // DO NOT MODIFY! bsA = 24577; bsB = GUN_POWDER; @@ -4337,10 +4393,10 @@ class definesvalidator{ #ifdef HOARD_MASTER // DO NOT MODIFY! - bsA = 2; + bsA = 3; bsB = HOARD_MASTER; if(bsA!=bsB) - ssErrors << "Defined HOARD_MASTER with value 2 from .dat file mismatches hardcoded c++ define value of " << HOARD_MASTER << "!" << std::endl; + ssErrors << "Defined HOARD_MASTER with value 3 from .dat file mismatches hardcoded c++ define value of " << HOARD_MASTER << "!" << std::endl; #endif @@ -5433,10 +5489,10 @@ class definesvalidator{ #ifdef MASTER_TORTURER // DO NOT MODIFY! - bsA = 1; + bsA = 2; bsB = MASTER_TORTURER; if(bsA!=bsB) - ssErrors << "Defined MASTER_TORTURER with value 1 from .dat file mismatches hardcoded c++ define value of " << MASTER_TORTURER << "!" << std::endl; + ssErrors << "Defined MASTER_TORTURER with value 2 from .dat file mismatches hardcoded c++ define value of " << MASTER_TORTURER << "!" << std::endl; #endif @@ -7488,6 +7544,30 @@ class definesvalidator{ #endif +#ifdef SHIELD_OF_ELECTRICITY_RESISTANCE // DO NOT MODIFY! + bsA = 4; + bsB = SHIELD_OF_ELECTRICITY_RESISTANCE; + if(bsA!=bsB) + ssErrors << "Defined SHIELD_OF_ELECTRICITY_RESISTANCE with value 4 from .dat file mismatches hardcoded c++ define value of " << SHIELD_OF_ELECTRICITY_RESISTANCE << "!" << std::endl; +#endif + + +#ifdef SHIELD_OF_FIRE_RESISTANCE // DO NOT MODIFY! + bsA = 3; + bsB = SHIELD_OF_FIRE_RESISTANCE; + if(bsA!=bsB) + ssErrors << "Defined SHIELD_OF_FIRE_RESISTANCE with value 3 from .dat file mismatches hardcoded c++ define value of " << SHIELD_OF_FIRE_RESISTANCE << "!" << std::endl; +#endif + + +#ifdef SHIELD_OF_MAGIC_RESISTANCE // DO NOT MODIFY! + bsA = 5; + bsB = SHIELD_OF_MAGIC_RESISTANCE; + if(bsA!=bsB) + ssErrors << "Defined SHIELD_OF_MAGIC_RESISTANCE with value 5 from .dat file mismatches hardcoded c++ define value of " << SHIELD_OF_MAGIC_RESISTANCE << "!" << std::endl; +#endif + + #ifdef SHOP // DO NOT MODIFY! bsA = 5; bsB = SHOP; @@ -8144,6 +8224,22 @@ class definesvalidator{ #endif +#ifdef TELEPORT_FLUID // DO NOT MODIFY! + bsA = 16439; + bsB = TELEPORT_FLUID; + if(bsA!=bsB) + ssErrors << "Defined TELEPORT_FLUID with value 16439 from .dat file mismatches hardcoded c++ define value of " << TELEPORT_FLUID << "!" << std::endl; +#endif + + +#ifdef TELEPORT_GAS // DO NOT MODIFY! + bsA = 12301; + bsB = TELEPORT_GAS; + if(bsA!=bsB) + ssErrors << "Defined TELEPORT_GAS with value 12301 from .dat file mismatches hardcoded c++ define value of " << TELEPORT_GAS << "!" << std::endl; +#endif + + #ifdef TELEPORT_LOCK // DO NOT MODIFY! bsA = 268435456; bsB = TELEPORT_LOCK; @@ -8168,6 +8264,14 @@ class definesvalidator{ #endif +#ifdef TERRA_TEAM // DO NOT MODIFY! + bsA = 16; + bsB = TERRA_TEAM; + if(bsA!=bsB) + ssErrors << "Defined TERRA_TEAM with value 16 from .dat file mismatches hardcoded c++ define value of " << TERRA_TEAM << "!" << std::endl; +#endif + + #ifdef THROW // DO NOT MODIFY! bsA = 32768; bsB = THROW; @@ -8512,6 +8616,14 @@ class definesvalidator{ #endif +#ifdef UR_STEEL // DO NOT MODIFY! + bsA = 28685; + bsB = UR_STEEL; + if(bsA!=bsB) + ssErrors << "Defined UR_STEEL with value 28685 from .dat file mismatches hardcoded c++ define value of " << UR_STEEL << "!" << std::endl; +#endif + + #ifdef USE_ADJECTIVE_AN // DO NOT MODIFY! bsA = 2; bsB = USE_ADJECTIVE_AN; @@ -8680,6 +8792,22 @@ class definesvalidator{ #endif +#ifdef VICE_ROY // DO NOT MODIFY! + bsA = 1; + bsB = VICE_ROY; + if(bsA!=bsB) + ssErrors << "Defined VICE_ROY with value 1 from .dat file mismatches hardcoded c++ define value of " << VICE_ROY << "!" << std::endl; +#endif + + +#ifdef VINEGAR // DO NOT MODIFY! + bsA = 16440; + bsB = VINEGAR; + if(bsA!=bsB) + ssErrors << "Defined VINEGAR with value 16440 from .dat file mismatches hardcoded c++ define value of " << VINEGAR << "!" << std::endl; +#endif + + #ifdef VITRELLOY // DO NOT MODIFY! bsA = 4191; bsB = VITRELLOY; @@ -8824,6 +8952,14 @@ class definesvalidator{ #endif +#ifdef WAND_OF_SOFTEN_MATERIAL // DO NOT MODIFY! + bsA = 17; + bsB = WAND_OF_SOFTEN_MATERIAL; + if(bsA!=bsB) + ssErrors << "Defined WAND_OF_SOFTEN_MATERIAL with value 17 from .dat file mismatches hardcoded c++ define value of " << WAND_OF_SOFTEN_MATERIAL << "!" << std::endl; +#endif + + #ifdef WAND_OF_STRIKING // DO NOT MODIFY! bsA = 2; bsB = WAND_OF_STRIKING; @@ -8888,6 +9024,14 @@ class definesvalidator{ #endif +#ifdef WAR_MASK // DO NOT MODIFY! + bsA = 13; + bsB = WAR_MASK; + if(bsA!=bsB) + ssErrors << "Defined WAR_MASK with value 13 from .dat file mismatches hardcoded c++ define value of " << WAR_MASK << "!" << std::endl; +#endif + + #ifdef WATER // DO NOT MODIFY! bsA = 16398; bsB = WATER; @@ -8984,6 +9128,14 @@ class definesvalidator{ #endif +#ifdef WHITE_STEEL // DO NOT MODIFY! + bsA = 28684; + bsB = WHITE_STEEL; + if(bsA!=bsB) + ssErrors << "Defined WHITE_STEEL with value 28684 from .dat file mismatches hardcoded c++ define value of " << WHITE_STEEL << "!" << std::endl; +#endif + + #ifdef WHITE_UNICORN_FLESH // DO NOT MODIFY! bsA = 20512; bsB = WHITE_UNICORN_FLESH; diff --git a/Main/Include/game.h b/Main/Include/game.h index d083a3b2f..2c04481bf 100644 --- a/Main/Include/game.h +++ b/Main/Include/game.h @@ -345,12 +345,20 @@ class game static void UpdateTrapID(entity*, ulong); static int GetStoryState() { return StoryState; } static void SetStoryState(int What) { StoryState = What; } + static int GetGloomyCaveStoryState() { return GloomyCaveStoryState; } + static void SetGloomyCaveStoryState(int What) { GloomyCaveStoryState = What; } static int GetXinrochTombStoryState() { return XinrochTombStoryState; } static void SetXinrochTombStoryState(int What) { XinrochTombStoryState = What; } static int GetFreedomStoryState() { return FreedomStoryState; } static void SetFreedomStoryState(int What) { FreedomStoryState = What; } + static int GetAslonaStoryState() { return AslonaStoryState; } + static void SetAslonaStoryState(int What) { AslonaStoryState = What; } + static int GetRebelStoryState() { return RebelStoryState; } + static void SetRebelStoryState(int What) { RebelStoryState = What; } static truth PlayerIsGodChampion() { return PlayerIsChampion; } static void MakePlayerGodChampion() { PlayerIsChampion = true; } // No way to switch that back, only one championship per game. + static truth PlayerHasBoat() { return HasBoat; } + static void GivePlayerBoat() { HasBoat = true; } static void SetIsInGetCommand(truth What) { InGetCommand = What; } static truth IsInGetCommand() { return InGetCommand; } static festring GetDataDir(); @@ -385,8 +393,10 @@ class game static bool ToggleShowMapNotes(); static bool CheckAddAutoMapNote(square* =NULL); static int CheckAutoPickup(square* sqr = NULL); + static void UpdateAutoPickUpMatching(); static int RotateMapNotes(); static char MapNoteToken(); + static bool IsAutoPickupMatch(cfestring fsName); #ifdef WIZARD static void ActivateWizardMode() { WizardMode = true; } @@ -546,8 +556,11 @@ class game static int Teams; static int Dungeons; static int StoryState; + static int GloomyCaveStoryState; static int XinrochTombStoryState; static int FreedomStoryState; + static int AslonaStoryState; + static int RebelStoryState; static truth InGetCommand; static truth PlayerHurtByExplosion; static area* CurrentArea; @@ -584,6 +597,7 @@ class game static long GlobalRainTimeModifier; static truth PlayerSumoChampion; static truth PlayerIsChampion; // This marks the player as a champion of some god. + static truth HasBoat; // Whether the player can sail the oceans of world map. static truth TouristHasSpider; static ulong SquarePartEmitationTick; static cint LargeMoveDirection[]; diff --git a/Main/Include/gear.h b/Main/Include/gear.h index f464cc4dd..9ade4f8c5 100644 --- a/Main/Include/gear.h +++ b/Main/Include/gear.h @@ -253,7 +253,6 @@ ITEM(boot, armor) virtual long GetPrice() const; virtual truth IsBoot(ccharacter*) const { return true; } virtual truth IsInCorrectSlot(int) const; - virtual truth IsKicking() const; }; ITEM(gauntlet, armor) @@ -380,6 +379,11 @@ ITEM(chastitybelt, lockablebelt) { public: virtual int GetFormModifier() const { return item::GetFormModifier(); } + virtual void AddInventoryEntry(ccharacter*, festring&, int, truth) const; + virtual truth CanBeEquipped(int) const; + virtual truth CanBeUnEquipped(int) const; + protected: + virtual void PostConstruct(); }; ITEM(darkaxe, meleeweapon) @@ -431,10 +435,10 @@ ITEM(taiaha, meleeweapon) virtual void ChargeFully(character*) { TimesUsed = 0; } virtual truth IsAppliable(ccharacter*) const { return false; } virtual truth IsZappable(ccharacter*) const { return true; } + virtual truth IsZapWorthy(ccharacter*) const { return Charges > TimesUsed; } virtual truth IsChargeable(ccharacter*) const { return true; } virtual truth Zap(character*, v2, int); - virtual void AddInventoryEntry(ccharacter*, festring&, int, truth) const; //this? - virtual truth IsExplosive() const { return true; } + virtual void AddInventoryEntry(ccharacter*, festring&, int, truth) const; protected: virtual int GetClassAnimationFrames() const; virtual col16 GetOutlineColor(int) const; @@ -475,6 +479,7 @@ ITEM(unpick, pickaxe) virtual truth IsZappable(const character*) const { return true; } virtual truth AllowAlphaEverywhere() const { return true; } virtual void FinalProcessForBone(); + virtual int GetCooldown(int, character*); protected: ulong LastUsed; virtual int GetClassAnimationFrames() const { return 32; } @@ -482,4 +487,46 @@ ITEM(unpick, pickaxe) virtual alpha GetOutlineAlpha(int) const; }; +ITEM(magestaff, meleeweapon) +{ + public: + magestaff() : LastUsed(0) { } + virtual void Load(inputfile&); + virtual void Save(outputfile&) const; + virtual truth Zap(character*, v2, int); + virtual truth IsZappable(const character*) const { return true; } + virtual void FinalProcessForBone(); + virtual int GetCooldown(int, character*); + protected: + ulong LastUsed; +}; + +ITEM(muramasa, meleeweapon) +{ + public: + virtual truth HitEffect(character*, character*, v2, int, int, truth); + virtual truth IsMuramasa() const { return true; } +}; + +ITEM(masamune, meleeweapon) +{ + public: + virtual truth HitEffect(character*, character*, v2, int, int, truth); + virtual truth IsMasamune() const { return true; } +}; + +ITEM(pica, meleeweapon) +{ + public: + pica() : LastUsed(0) { } + virtual void Load(inputfile&); + virtual void Save(outputfile&) const; + virtual truth Zap(character*, v2, int); + virtual truth IsZappable(const character*) const { return true; } + virtual void FinalProcessForBone(); + virtual int GetCooldown(int, character*); + protected: + ulong LastUsed; +}; + #endif diff --git a/Main/Include/god.h b/Main/Include/god.h index 742931fa4..febaa873d 100644 --- a/Main/Include/god.h +++ b/Main/Include/god.h @@ -47,6 +47,7 @@ class god virtual void Pray(); virtual cchar* GetName() const = 0; virtual cchar* GetDescription() const = 0; + cchar* GetLastKnownRelation() const; cchar* GetPersonalPronoun() const; cchar* GetObjectPronoun() const; virtual int GetAlignment() const = 0; @@ -62,7 +63,7 @@ class god truth ReceiveOffer(item*); virtual int GetBasicAlignment() const; int GetRelation() const { return Relation; } - void PrintRelation() const; + cfestring PrintRelation() const; void SetIsKnown(truth What) { Known = What; } truth IsKnown() const { return Known; } void PlayerKickedAltar() { AdjustRelation(-100); } @@ -86,6 +87,7 @@ class god virtual void PrayGoodEffect() = 0; virtual void PrayBadEffect() = 0; int Relation, LastPray; + festring fsLastKnownRelation; long Timer; truth Known; }; diff --git a/Main/Include/hiteffect.h b/Main/Include/hiteffect.h index 63f067be7..c73441d1c 100644 --- a/Main/Include/hiteffect.h +++ b/Main/Include/hiteffect.h @@ -17,6 +17,7 @@ #include "entity.h" #include "v2.h" +#include "lsquare.h" class lsquare; class blitdata; @@ -34,11 +35,31 @@ struct hiteffectSetup { //TODO some of these are not actually an external setup. character* WhoIsHit; ulong lWhoIsHitID; + lsquare* HitAtSquare; //crafting fancyness mainly ulong lItemEffectReferenceID; int iMode; lsquare* LSquareUnder; + + public: + hiteffectSetup(){ + Critical=false; + GivenDir=0; + Type=0; + + WhoHits=NULL; + lWhoHitsID=0; + + WhoIsHit=NULL; + lWhoIsHitID=0; + HitAtSquare=NULL; + + lItemEffectReferenceID=0; + + iMode=0; + LSquareUnder=NULL; + } }; class hiteffect : public entity { diff --git a/Main/Include/human.h b/Main/Include/human.h index 50869938a..29e49806b 100644 --- a/Main/Include/human.h +++ b/Main/Include/human.h @@ -14,6 +14,7 @@ #define __HUMAN_H__ #include "char.h" +#include "confdef.h" CHARACTER(humanoid, character) { @@ -156,7 +157,7 @@ CHARACTER(humanoid, character) virtual int GetAttributeAverage() const; virtual truth CanVomit() const; virtual truth CheckApply() const; - virtual truth CanForceVomit() const { return TorsoIsAlive() && HasAUsableArm(); } + virtual truth CanForceVomit() const { return CanVomit() && HasAUsableArm(); } virtual truth IsTransparent() const; virtual void ModifySituationDanger(double&) const; virtual int RandomizeTryToUnStickBodyPart(ulong) const; @@ -451,7 +452,7 @@ CHARACTER(zombie, humanoid) virtual void BeTalkedTo(); virtual truth BodyPartIsVital(int) const; virtual void CreateBodyParts(int); - virtual truth AllowSpoil() const { return true; } + virtual truth AllowSpoil() const { return GetConfig() != ZOMBIE_OF_KHAZ_ZADM; } void SetDescription(cfestring What) { Description = What; } virtual void Save(outputfile&) const; virtual void Load(inputfile&); @@ -460,7 +461,7 @@ CHARACTER(zombie, humanoid) virtual void PostConstruct(); virtual void AddPostFix(festring&, int) const; virtual void GetAICommand(); - virtual truth AllowExperience() const { return false; } + virtual truth AllowExperience() const { return GetConfig() == ZOMBIE_OF_KHAZ_ZADM; } festring Description; }; @@ -525,9 +526,28 @@ CHARACTER(xinrochghost, ghost) }; CHARACTER(imp, humanoid) +{ + public: + virtual truth SpecialEnemySightedReaction(character*); + protected: + virtual truth CanVomit() const { return true; } + virtual void PostConstruct(); +}; + +CHARACTER(crimsonimp, imp) { protected: virtual truth SpecialBiteEffect(character*, v2, int, int, truth, truth, int); + virtual void CreateCorpse(lsquare*); +}; + +CHARACTER(mirrorimp, imp) +{ + public: + virtual truth IsMagicDrinker() const { return true; } + virtual truth DrinkMagic(const beamdata&); + protected: + virtual void CreateCorpse(lsquare*); }; CHARACTER(mistress, humanoid) @@ -622,6 +642,8 @@ CHARACTER(orc, humanoid) CHARACTER(cossack, humanoid) { + public: + virtual void GetAICommand(); }; CHARACTER(bananagrower, humanoid) @@ -662,6 +684,8 @@ CHARACTER(elder, humanoid) { public: elder() : HasBeenSpokenTo(false) { } + virtual void Save(outputfile&) const; + virtual void Load(inputfile&); protected: virtual void GetAICommand(); virtual void CreateBodyParts(int); @@ -754,6 +778,7 @@ CHARACTER(siren, humanoid) virtual void GetAICommand(); virtual truth MoveRandomly(); protected: + virtual void PostConstruct(); virtual truth TryToSing(); }; @@ -763,6 +788,12 @@ CHARACTER(punisher, humanoid) CHARACTER(child, humanoid) { + public: + virtual truth MoveRandomly(); + virtual truth IsKing() const { return GetConfig() == KING; } + virtual truth MustBeRemovedFromBone() const; + protected: + virtual void BeTalkedTo(); }; CHARACTER(bum, humanoid) @@ -783,9 +814,71 @@ CHARACTER(terra, priest) { public: terra() : HasBeenSpokenTo(false) { } + virtual void Save(outputfile&) const; + virtual void Load(inputfile&); protected: virtual void BeTalkedTo(); truth HasBeenSpokenTo; }; +CHARACTER(aslonawizard, humanoid) +{ + public: + aslonawizard() : HasBeenSpokenTo(false) { } + virtual void Save(outputfile&) const; + virtual void Load(inputfile&); + protected: + virtual void GetAICommand(); + int GetSpellAPCost() const; + virtual void BeTalkedTo(); + virtual void CreateCorpse(lsquare*); + truth HasBeenSpokenTo; +}; + +CHARACTER(aslonacaptain, guard) +{ + public: + aslonacaptain() : HasBeenSpokenTo(false) { } + virtual void Save(outputfile&) const; + virtual void Load(inputfile&); + protected: + virtual void BeTalkedTo(); + truth HasBeenSpokenTo; +}; + +CHARACTER(aslonapriest, priest) +{ + public: + aslonapriest() : HasBeenSpokenTo(false) { } + virtual void Save(outputfile&) const; + virtual void Load(inputfile&); + protected: + virtual void BeTalkedTo(); + truth HasBeenSpokenTo; +}; + +CHARACTER(gasghoul, zombie) +{ + public: + virtual int TakeHit(character*, item*, bodypart*, v2, double, double, int, int, int, truth, truth); + virtual truth AllowSpoil() const { return false; } +}; + +CHARACTER(harvan, humanoid) +{ + public: + virtual truth SpecialEnemySightedReaction(character*); + virtual void BeTalkedTo(); + virtual void GetAICommand() { StandIdleAI(); } +}; + +CHARACTER(lordregent, humanoid) +{ + public: + virtual void BeTalkedTo(); + virtual void GetAICommand() { StandIdleAI(); } + protected: + virtual void SpecialBodyPartSeverReaction(); +}; + #endif diff --git a/Main/Include/iconf.h b/Main/Include/iconf.h index 394c9096d..5781ae8d9 100644 --- a/Main/Include/iconf.h +++ b/Main/Include/iconf.h @@ -23,6 +23,7 @@ class ivanconfig static cfestring& GetFantasyNamePattern() { return FantasyNamePattern.Value; } static cfestring& GetDefaultPetName() { return DefaultPetName.Value; } static cfestring& GetSelectedBkgColor() { return SelectedBkgColor.Value; } + static cfestring& GetAutoPickUpMatching() { return AutoPickUpMatching.Value; } static truth IsAllWeightIsRelevant() { return AllWeightIsRelevant.Value; } static long GetAutoSaveInterval() { return AutoSaveInterval.Value; } static long GetContrast() { return Contrast.Value; } @@ -43,8 +44,8 @@ class ivanconfig static truth IsTransparentMapLM() { return TransparentMapLM.Value; } static truth IsWaitNeutralsMoveAway() { return WaitNeutralsMoveAway.Value; } static truth IsEnhancedLights() { return EnhancedLights.Value; } - static int GetMemorizeEquipmentMode() { return MemorizeEquipmentMode.Value; } - static int GetDistLimitMagicMushrooms() { return DistLimitMagicMushrooms.Value; } + static int GetMemorizeEquipmentMode() { return 2; /*MemorizeEquipmentMode.Value;*/ } + static int GetDistLimitMagicMushrooms() { return DistLimitMagicMushrooms.Value * 4; } static truth IsShowFullDungeonName() { return ShowFullDungeonName.Value; } static truth IsCenterOnPlayerAfterLook(){ return CenterOnPlayerAfterLook.Value; } static truth IsShowGodInfo(){ return ShowGodInfo.Value; } @@ -54,6 +55,8 @@ class ivanconfig static truth IsXBRZScale() { return XBRZScale.Value; } static truth IsAutoPickupThrownItems() { return AutoPickupThrownItems.Value; } static truth IsAltAdentureInfo() { return AltAdentureInfo.Value; } + static truth UseDescriptiveHP() { return DescriptiveHP.Value; } + static truth GetNoPet() { return StartWithNoPet.Value; } static int GetXBRZSquaresAroundPlayer() { return XBRZSquaresAroundPlayer.Value; } static int GetStartingDungeonGfxScale() { return iStartingDungeonGfxScale; } static int GetStartingFontGfx() { return iStartingFontGfx; } @@ -111,6 +114,7 @@ class ivanconfig static truth DefaultNameChangeInterface(stringoption*); static truth FantasyNameChangeInterface(stringoption* O); static truth SelectedBkgColorChangeInterface(stringoption* O); + static truth AutoPickUpMatchingChangeInterface(stringoption* O); static truth DefaultPetNameChangeInterface(stringoption*); static truth AutoSaveIntervalChangeInterface(numberoption*); static truth XBRZSquaresAroundPlayerChangeInterface(numberoption* O); @@ -122,6 +126,7 @@ class ivanconfig static truth AltListItemWidthChangeInterface(numberoption* O); static truth ContrastChangeInterface(numberoption*); static void SelectedBkgColorChanger(stringoption* O, cfestring& What); + static void AutoPickUpMatchingChanger(stringoption* O, cfestring& What); static void AutoSaveIntervalChanger(numberoption*, long); static void XBRZSquaresAroundPlayerChanger(numberoption* O, long What); static void ShowItemsAtPlayerSquareChanger(cycleoption* O, long What); @@ -168,6 +173,7 @@ class ivanconfig static stringoption FantasyNamePattern; static stringoption DefaultPetName; static stringoption SelectedBkgColor; + static stringoption AutoPickUpMatching; static numberoption AutoSaveInterval; static truthoption AltAdentureInfo; static truthoption CenterOnPlayerAfterLook; @@ -202,6 +208,8 @@ class ivanconfig static truthoption LookZoom; static truthoption XBRZScale; static truthoption AutoPickupThrownItems; + static truthoption DescriptiveHP; + static truthoption StartWithNoPet; static cycleoption SaveGameSortMode; static cycleoption DistLimitMagicMushrooms; diff --git a/Main/Include/item.h b/Main/Include/item.h index 3ceda9399..6039d0f98 100644 --- a/Main/Include/item.h +++ b/Main/Include/item.h @@ -119,6 +119,7 @@ struct itemdatabase : public databasebase truth CanBeEnchanted; truth IsQuestItem; truth IsGoodWithPlants; + truth IsGoodWithUndead; truth CreateLockConfigurations; truth CanBePickedUp; truth HasSecondaryMaterial; @@ -196,6 +197,7 @@ struct itemdatabase : public databasebase festring BreakMsg; truth IsSadistWeapon; fearray AllowedDungeons; + festring DescriptiveInfo; }; class itemprototype @@ -252,9 +254,12 @@ class item : public object virtual void Fly(character*, int, int, truth=false); int HitCharacter(character*, character*, int, double, int); virtual truth DogWillCatchAndConsume(ccharacter*) const { return false; } + virtual truth CatWillCatchAndConsume(ccharacter*) const { return false; } + virtual truth BunnyWillCatchAndConsume(ccharacter*) const { return false; } virtual truth Apply(character*); virtual truth Zap(character*, v2, int) { return false; } virtual truth Polymorph(character*, stack*); + virtual truth Polymorph(character*, character*); virtual truth Alchemize(character*, stack*); virtual truth SoftenMaterial(); virtual truth CheckPickUpEffect(character*) { return true; } @@ -296,6 +301,7 @@ class item : public object virtual truth IsDipDestination(ccharacter*) const { return false; } virtual truth IsAppliable(ccharacter*) const { return false; } virtual truth IsZappable(ccharacter*) const { return false; } + virtual truth IsZapWorthy(ccharacter*) const { return false; } virtual truth IsChargeable(ccharacter*) const { return false; } virtual truth IsHelmet(ccharacter*) const { return false; } virtual truth IsAmulet(ccharacter*) const { return false; } @@ -405,6 +411,7 @@ class item : public object virtual DATA_BASE_VALUE_WITH_PARAMETER(v2, WieldedBitmapPos, int); DATA_BASE_TRUTH(IsQuestItem); DATA_BASE_TRUTH(IsGoodWithPlants); + DATA_BASE_TRUTH(IsGoodWithUndead); DATA_BASE_TRUTH(CanBePickedUp); DATA_BASE_VALUE(int, CoverPercentile); DATA_BASE_VALUE_WITH_PARAMETER(v2, TorsoArmorBitmapPos, int); @@ -427,6 +434,7 @@ class item : public object DATA_BASE_VALUE(cfestring&, BreakMsg); DATA_BASE_TRUTH(IsSadistWeapon); DATA_BASE_VALUE(const fearray&, AllowedDungeons); + DATA_BASE_VALUE(cfestring&, DescriptiveInfo); truth CanBeSoldInLibrary(character* Librarian) const { return CanBeRead(Librarian); } virtual truth TryKey(item*, character*) { return false; } long GetBlockModifier() const; @@ -481,6 +489,7 @@ class item : public object virtual double GetDamageBonus() const { return 0.; } virtual void DrawContents(ccharacter*) { } virtual truth IsBroken() const; + virtual truth IsFood() const; virtual int GetEnchantment() const { return 0; } long GetEnchantedPrice(int) const; virtual item* Fix(); @@ -502,16 +511,21 @@ class item : public object virtual truth IsBanana() const { return false; } virtual truth IsMangoSeedling() const { return false; } virtual truth IsEncryptedScroll() const { return false; } + virtual truth IsNuke() const { return false; } + virtual truth IsWeepObsidian() const { return false; } virtual truth IsShadowVeil() const { return false; } virtual truth IsLostRubyFlamingSword() const { return false; } virtual truth IsRuneSword() const { return false; } - virtual truth IsKicking() const { return false; } + virtual truth IsMuramasa() const { return false; } + virtual truth IsMasamune() const { return false; } cchar* GetStrengthValueDescription() const; cchar* GetBaseToHitValueDescription() const; cchar* GetBaseBlockValueDescription() const; virtual truth IsInCorrectSlot(int) const; truth IsInCorrectSlot() const; int GetEquipmentIndex() const; + virtual truth CanBeEquipped(int) const { return true; } + virtual truth CanBeUnEquipped(int) const { return true; } room* GetRoom(int I = 0) const { return GetLSquareUnder(I)->GetRoom(); } virtual truth HasBetterVersion() const { return false; } virtual void SortAllItems(const sortdata&) const; @@ -538,7 +552,6 @@ class item : public object virtual void Draw(blitdata&) const; v2 GetLargeBitmapPos(v2, int) const; void LargeDraw(blitdata&) const; - virtual truth BunnyWillCatchAndConsume(ccharacter*) const { return false; } void DonateIDTo(item*); virtual void SignalRustLevelChange(); virtual void SignalBurnLevelChange(); @@ -564,6 +577,7 @@ class item : public object void CheckFluidGearPictures(v2, int, truth); void DrawFluids(blitdata&) const; virtual void ReceiveAcid(material*, cfestring&, long); + virtual void ReceiveHeat(material*, cfestring&, long); virtual void FightFire(material*, cfestring&, long); virtual truth ShowFluids() const { return true; } void DonateFluidsTo(item*); @@ -615,6 +629,7 @@ class item : public object virtual bool WillExplodeSoon() const { return false; } virtual const character* GetWearer() const; virtual bool SpecialOfferEffect(int) { return false; } + virtual void BreakEffect(character*, cfestring&) { return; } void Haste(); void Slow(); void SendMemorizedUpdateRequest() const; diff --git a/Main/Include/ivandef.h b/Main/Include/ivandef.h index 6e2acf765..a06028c4a 100644 --- a/Main/Include/ivandef.h +++ b/Main/Include/ivandef.h @@ -319,16 +319,18 @@ cv2 SILHOUETTE_SIZE(48, 64); // it is TILE_SIZE*3,TILE_SIZE*4 tho.. /* ConsumeTypes */ -#define CT_FRUIT 1 -#define CT_MEAT 2 -#define CT_METAL 4 -#define CT_MINERAL 8 -#define CT_LIQUID 16 -#define CT_BONE 32 -#define CT_PROCESSED 64 -#define CT_MISC_ORGANIC 128 -#define CT_PLASTIC 256 -#define CT_GAS 512 +#define CT_FRUIT (1 << 0) +#define CT_MEAT (1 << 1) +#define CT_METAL (1 << 2) +#define CT_MINERAL (1 << 3) +#define CT_LIQUID (1 << 4) +#define CT_BONE (1 << 5) +#define CT_PROCESSED (1 << 6) +#define CT_MISC_PLANT (1 << 7) +#define CT_MISC_ANIMAL (1 << 8) +#define CT_PLASTIC (1 << 9) +#define CT_GAS (1 << 10) +#define CT_MAGIC (1 << 11) /* Possible square positions for item. The first four are used for items on walls */ @@ -399,6 +401,13 @@ cv2 SILHOUETTE_SIZE(48, 64); // it is TILE_SIZE*3,TILE_SIZE*4 tho.. #define EFFECT_TRAIN_WISDOM 36 #define EFFECT_REGENERATION 37 #define EFFECT_TELEPORTATION 38 +#define EFFECT_LAUGH 39 +#define EFFECT_POLYJUICE 40 +#define EFFECT_PUKE 41 +#define EFFECT_SICKNESS 42 +#define EFFECT_PHASE 43 +#define EFFECT_ACID_GAS 44 +#define EFFECT_FIRE_GAS 45 /* CEM = Consume End Message */ @@ -505,12 +514,22 @@ cv2 SILHOUETTE_SIZE(48, 64); // it is TILE_SIZE*3,TILE_SIZE*4 tho.. #define MONSTER_TEAM 1 #define ATTNAM_TEAM 2 #define SUMO_TEAM 3 +#define ANGEL_TEAM 4 +#define GUILD_TEAM 5 #define IVAN_TEAM 6 #define NEW_ATTNAM_TEAM 7 #define COLONIST_TEAM 8 #define TOURIST_GUIDE_TEAM 9 #define TOURIST_TEAM 10 #define BETRAYED_TEAM 11 +#define XINROCH_TOMB_ENTRY_TEAM 12 +#define XINROCH_TOMB_NECRO_TEAM 13 +#define XINROCH_TOMB_KAMIKAZE_DWARF_TEAM 14 +#define PRISONER_TEAM 15 +#define TERRA_TEAM 16 +#define ASLONA_TEAM 17 +#define REBEL_TEAM 18 + #define NO_TEAM 0xFFFF #define LOAD 1 @@ -540,11 +559,21 @@ cv2 SILHOUETTE_SIZE(48, 64); // it is TILE_SIZE*3,TILE_SIZE*4 tho.. #define EMPTY_AREA 5 #define XINROCH_TOMB 6 #define BLACK_MARKET 7 +#define ASLONA_CASTLE 8 +#define REBEL_CAMP 9 +#define GOBLIN_FORT 10 +#define FUNGAL_CAVE 11 +#define PYRAMID 12 +#define MONDEDR 13 +#define IRINOX 14 +#define DARK_FOREST 15 + #define UNDER_WATER_TUNNEL_EXIT 0x80 #define VESANA_LEVEL 2 #define CRYSTAL_LEVEL 3 #define SPIDER_LEVEL 4 + #define ENNER_BEAST_LEVEL 4 #define ZOMBIE_LEVEL 5 #define IVAN_LEVEL 7 @@ -554,6 +583,10 @@ cv2 SILHOUETTE_SIZE(48, 64); // it is TILE_SIZE*3,TILE_SIZE*4 tho.. #define DUAL_ENNER_BEAST_LEVEL 5 #define NECRO_CHAMBER_LEVEL 6 +#define FUSANGA_LEVEL 3 + +#define KING_LEVEL 5 + /* stack::DrawContents flags */ #define NO_SELECT 1 // only show items diff --git a/Main/Include/level.h b/Main/Include/level.h index 1c30ab699..e0539688b 100644 --- a/Main/Include/level.h +++ b/Main/Include/level.h @@ -97,7 +97,7 @@ struct explosion struct beamdata { beamdata(character*, cfestring&, int, ulong); - beamdata(character*, cfestring&, v2, col16, int, int, int, ulong); + beamdata(character*, cfestring&, v2, col16, int, int, int, ulong, item*); character* Owner; festring DeathMsg; v2 StartPos; @@ -106,6 +106,7 @@ struct beamdata int Direction; int Range; ulong SpecialParameters; + item* Wand; }; inline beamdata::beamdata @@ -130,7 +131,8 @@ inline beamdata::beamdata int BeamEffect, int Direction, int Range, - ulong SpecialParameters + ulong SpecialParameters, + item* Wand ) : Owner(Owner), DeathMsg(DeathMsg), @@ -139,7 +141,8 @@ BeamColor(BeamColor), BeamEffect(BeamEffect), Direction(Direction), Range(Range), -SpecialParameters(SpecialParameters) +SpecialParameters(SpecialParameters), +Wand(Wand) { } struct maze diff --git a/Main/Include/lsquare.h b/Main/Include/lsquare.h index 9c4388376..ba74bef91 100644 --- a/Main/Include/lsquare.h +++ b/Main/Include/lsquare.h @@ -187,7 +187,7 @@ class lsquare : public square truth Webbing(const beamdata&); truth Alchemize(const beamdata&); truth SoftenMaterial(const beamdata&); - truth WaterRain(const beamdata&); + truth LiquidRain(const beamdata&, int); int GetLevelIndex() const { return static_cast(AreaUnder)->GetIndex(); } int GetDungeonIndex() const { return static_cast(AreaUnder)->GetDungeon()->GetIndex(); } dungeon* GetDungeon() const { return static_cast(AreaUnder)->GetDungeon(); } diff --git a/Main/Include/lterra.h b/Main/Include/lterra.h index 75a8e6217..f69f9f898 100644 --- a/Main/Include/lterra.h +++ b/Main/Include/lterra.h @@ -101,6 +101,7 @@ class lterrain : public object virtual void SignalBurnLevelChange(); virtual void TryToRust(long); virtual void ReceiveAcid(material*, long) { } + virtual void ReceiveHeat(material*, long) { } void InitMaterials(material*, truth = true); material* SetMainMaterial(material*, int = 0); virtual void GenerateMaterials(); @@ -324,6 +325,7 @@ class olterrain : public lterrain, public oterrain virtual int GetTheoreticalWalkability() const { return DataBase->Walkability; } virtual void BeDestroyed() { Break(); } virtual void ReceiveAcid(material*, long); + virtual void ReceiveHeat(material*, long); virtual void SignalRustLevelChange(); virtual void SignalBurnLevelChange(); virtual truth IsFountainWithWater() const { return false; } diff --git a/Main/Include/lterras.h b/Main/Include/lterras.h index 60d9764f9..e1507f1be 100644 --- a/Main/Include/lterras.h +++ b/Main/Include/lterras.h @@ -139,7 +139,7 @@ OLTERRAIN(fountain, olterrain) virtual truth SitOn(character*); virtual truth Drink(character*); virtual truth HasDrinkEffect() const { return true; } - virtual void DryOut(); + virtual void DryOut(character*); virtual truth DipInto(item*, character*); virtual truth IsDipDestination() const; virtual material* GetSecondaryMaterial() const { return SecondaryMaterial; } @@ -238,6 +238,8 @@ OLTERRAIN(coffin, olterraincontainer) virtual v2 GetBitmapPos(int Frame) const { return Opened ? GetOpenBitmapPos(Frame) : olterraincontainer::GetBitmapPos(Frame); } virtual void GenerateGhost(lsquare*); + virtual void GenerateUndead(); + virtual void PostConstruct(); truth Opened; }; diff --git a/Main/Include/materia.h b/Main/Include/materia.h index 716e885df..7de047eb0 100644 --- a/Main/Include/materia.h +++ b/Main/Include/materia.h @@ -60,6 +60,7 @@ struct materialdatabase : public databasebase int StepInWisdomLimit; int RustModifier; int Acidicity; + int Hotness; contentscript NaturalForm; int HardenedMaterial; int SoftenedMaterial; @@ -154,6 +155,7 @@ class material DATA_BASE_VALUE(int, AttachedGod); DATA_BASE_VALUE(int, RustModifier); DATA_BASE_VALUE(int, Acidicity); + DATA_BASE_VALUE(int, Hotness); DATA_BASE_VALUE(const contentscript&, NaturalForm); DATA_BASE_VALUE(int, IntelligenceRequirement); DATA_BASE_VALUE(int, Stickiness); diff --git a/Main/Include/miscitem.h b/Main/Include/miscitem.h index d35f57e8b..1b62ab8cf 100644 --- a/Main/Include/miscitem.h +++ b/Main/Include/miscitem.h @@ -63,6 +63,7 @@ ITEM(banana, materialcontainer) virtual void Load(inputfile&); virtual void ChargeFully(character*) { TimesUsed = 0; } virtual truth IsZappable(ccharacter*) const { return true; } + virtual truth IsZapWorthy(ccharacter*) const { return Charges > TimesUsed; } virtual truth IsChargeable(ccharacter*) const { return true; } virtual void SignalSpoil(material*); virtual truth IsBanana() const { return true; } @@ -262,6 +263,7 @@ ITEM(wand, item) virtual void ChargeFully(character*) { TimesUsed = 0; } virtual truth IsAppliable(ccharacter*) const { return true; } virtual truth IsZappable(ccharacter*) const { return true; } + virtual truth IsZapWorthy(ccharacter*) const { return Charges > TimesUsed; } virtual truth IsChargeable(ccharacter*) const { return true; } virtual truth ReceiveDamage(character*, int, int, int); virtual truth Zap(character*, v2, int); @@ -407,11 +409,23 @@ ITEM(headofelpuri, item) // can't wear equipment, so not "head" virtual void Be() { } }; -ITEM(whistle, item) +ITEM(magicalinstrument, item) { public: - virtual truth Apply(character*); + magicalinstrument() : LastUsed(0) { } + virtual void Load(inputfile&); + virtual void Save(outputfile&) const; virtual truth IsAppliable(ccharacter*) const { return true; } + virtual void FinalProcessForBone(); + virtual int GetCooldown(int, character*); + protected: + ulong LastUsed; +}; + +ITEM(whistle, magicalinstrument) +{ + public: + virtual truth Apply(character*); virtual void BlowEffect(character*); protected: virtual col16 GetMaterialColorB(int) const; @@ -420,13 +434,7 @@ ITEM(whistle, item) ITEM(magicalwhistle, whistle) { public: - magicalwhistle() : LastUsed(0) { } virtual void BlowEffect(character*); - virtual void Load(inputfile&); - virtual void Save(outputfile&) const; - virtual void FinalProcessForBone(); - protected: - ulong LastUsed; }; ITEM(itemcontainer, lockableitem) @@ -557,17 +565,10 @@ ITEM(encryptedscroll, scroll) virtual truth IsEncryptedScroll() const { return true; } }; -ITEM(horn, item) +ITEM(horn, magicalinstrument) { public: - horn() : LastUsed(0) { } - virtual void Load(inputfile&); - virtual void Save(outputfile&) const; virtual truth Apply(character*); - virtual truth IsAppliable(ccharacter*) const { return true; } - virtual void FinalProcessForBone(); - protected: - ulong LastUsed; }; ITEM(carrot, item) @@ -578,18 +579,12 @@ ITEM(carrot, item) virtual col16 GetMaterialColorB(int) const; }; -ITEM(charmlyre, item) +ITEM(charmlyre, magicalinstrument) { public: - charmlyre(); virtual truth Apply(character*); - virtual truth IsAppliable(ccharacter*) const { return true; } - virtual void Load(inputfile&); - virtual void Save(outputfile&) const; - virtual void FinalProcessForBone(); protected: virtual col16 GetMaterialColorB(int) const; - ulong LastUsed; }; ITEM(scrollofdetectmaterial, scroll) @@ -700,6 +695,7 @@ ITEM(ullrbone, item) virtual truth Zap(character*, v2, int); virtual void ChargeFully(character*) { TimesUsed = 0; } virtual truth IsZappable(const character*) const { return true; } + virtual truth IsZapWorthy(ccharacter*) const { return Charges > TimesUsed; } virtual truth IsChargeable(const character*) const { return true; } virtual truth HitEffect(character*, character*, v2, int, int, truth); virtual void Be() { } @@ -750,14 +746,19 @@ ITEM(cauldron, materialcontainer) }; ITEM(trinket, item) +{ + protected: + virtual col16 GetMaterialColorB(int) const; + virtual col16 GetMaterialColorC(int) const; +}; + +ITEM(fish, item) { public: virtual material* RemoveMaterial(material* Material); virtual truth Necromancy(character*); virtual truth RaiseTheDead(character*); - protected: - virtual col16 GetMaterialColorB(int) const; - virtual col16 GetMaterialColorC(int) const; + virtual truth CatWillCatchAndConsume(ccharacter*) const; }; ITEM(gastrap, itemtrap) @@ -790,4 +791,27 @@ ITEM(locationmap, scroll) virtual void FinishReading(character*); }; +ITEM(nuke, materialcontainer) +{ + public: + virtual truth Apply(character*); + virtual truth IsAppliable(ccharacter*) const { return true; } + virtual truth IsNuke() const { return true; } + virtual truth IsExplosive() const; + virtual long GetTotalExplosivePower() const; +}; + +ITEM(weepobsidian, stone) +{ + public: + weepobsidian() { Enable(); } + virtual void Be(); + virtual truth IsWeepObsidian() const { return true; } + protected: + virtual truth CalculateHasBe() const { return true; } + virtual int GetClassAnimationFrames() const { return 32; } + virtual col16 GetOutlineColor(int) const; + virtual alpha GetOutlineAlpha(int) const; +}; + #endif diff --git a/Main/Include/nonhuman.h b/Main/Include/nonhuman.h index fa6604921..c55072d5f 100644 --- a/Main/Include/nonhuman.h +++ b/Main/Include/nonhuman.h @@ -81,6 +81,7 @@ CHARACTER(nonhumanoid, character) CHARACTER(frog, nonhumanoid) { public: + virtual truth IsFrog() const { return true; } virtual truth MoveRandomly() { return MoveRandomlyInRoom(); } }; @@ -106,8 +107,16 @@ CHARACTER(mommo, nonhumanoid) virtual void GetAICommand(); }; +CHARACTER(feline, nonhumanoid) +{ + public: + virtual truth Catches(item*); +}; + CHARACTER(canine, nonhumanoid) { + public: + virtual truth Catches(item*); }; CHARACTER(wolf, canine) @@ -119,7 +128,6 @@ CHARACTER(wolf, canine) CHARACTER(dog, canine) { public: - virtual truth Catches(item*); virtual void BeTalkedTo(); protected: virtual void CreateCorpse(lsquare*); @@ -137,7 +145,7 @@ CHARACTER(spider, nonhumanoid) virtual bodypart* MakeBodyPart(int) const; }; -CHARACTER(jackal, nonhumanoid) +CHARACTER(jackal, canine) { }; @@ -158,6 +166,8 @@ CHARACTER(dolphin, nonhumanoid) CHARACTER(bat, nonhumanoid) { + public: + virtual void GetAICommand(); protected: virtual bodypart* MakeBodyPart(int) const; }; @@ -168,7 +178,20 @@ CHARACTER(vampirebat, bat) virtual truth SpecialBiteEffect(character*, v2, int, int, truth, truth, int); }; -CHARACTER(largecat, nonhumanoid) +CHARACTER(nerfbat, bat) +{ + protected: + virtual int TakeHit(character*, item*, bodypart*, v2, double, double, int, int, int, truth, truth); +}; + +CHARACTER(fruitbat, bat) +{ + public: + virtual void GetAICommand(); + virtual truth IsRetreating() const; +}; + +CHARACTER(largecat, feline) { public: largecat() : Lives(7) { } @@ -196,7 +219,7 @@ CHARACTER(unicorn, nonhumanoid) void MonsterTeleport(cchar*); }; -CHARACTER(lion, nonhumanoid) +CHARACTER(lion, feline) { }; @@ -317,6 +340,8 @@ CHARACTER(skunk, nonhumanoid) CHARACTER(invisiblestalker, nonhumanoid) { + public: + virtual void GetAICommand(); }; CHARACTER(largecreature, nonhumanoid) @@ -342,7 +367,9 @@ CHARACTER(largecreature, nonhumanoid) virtual int GetUnconsciousSymbolSquareIndex() const { return 2; } virtual truth PlaceIsIllegal(v2, v2) const; truth PartCanMoveOn(const lsquare*) const; + virtual truth IsLarge() const { return true; } protected: + virtual truth CanVomit() const { return false; } virtual bodypart* MakeBodyPart(int) const; virtual void CreateCorpse(lsquare*); virtual void LoadSquaresUnder(); @@ -359,6 +386,7 @@ CHARACTER(elpuri, largecreature) virtual truth SpecialEnemySightedReaction(character*); virtual truth MustBeRemovedFromBone() const; virtual truth TryToRiseFromTheDead(); + virtual truth IsFrog() const { return true; } protected: virtual void GetAICommand(); virtual void CreateCorpse(lsquare*); @@ -444,13 +472,19 @@ CHARACTER(mysticfrog, frog) CHARACTER(lobhse, largecreature) { public: + lobhse() : TurnsExisted(0) { } + virtual void Save(outputfile&) const; + virtual void Load(inputfile&); + virtual void Bite(character*, v2, int, truth = false); virtual truth IsSpider() const { return true; } + virtual void FinalProcessForBone(); protected: virtual truth SpecialBiteEffect(character*, v2, int, int, truth, truth, int); virtual void GetAICommand(); virtual void CreateCorpse(lsquare*); virtual truth MustBeRemovedFromBone() const; virtual bodypart* MakeBodyPart(int) const; + long TurnsExisted; }; CHARACTER(mindworm, nonhumanoid) @@ -460,4 +494,19 @@ CHARACTER(mindworm, nonhumanoid) virtual truth TryToImplantLarvae(character*); virtual void PsiAttack(character*); }; + +CHARACTER(fusanga, largecreature) +{ + public: + virtual col16 GetSkinColor() const; + virtual bodypart* MakeBodyPart(int) const; + virtual void SpecialTurnHandler() { UpdatePictures(); } + virtual truth IsMushroom() const { return true; } + protected: + virtual truth AllowExperience() const { return false; } + virtual void GetAICommand(); + virtual void CreateCorpse(lsquare*); + virtual truth MustBeRemovedFromBone() const; +}; + #endif diff --git a/Main/Include/rooms.h b/Main/Include/rooms.h index 168888d12..aaaa9fb9a 100644 --- a/Main/Include/rooms.h +++ b/Main/Include/rooms.h @@ -99,4 +99,26 @@ ROOM(sumoarena, room) virtual truth CheckDestroyTerrain(character*); }; +ROOM(ownedarea, room) +{ + public: + virtual truth PickupItem(character*, item*, int); + virtual truth DropItem(character*, item*, int); + virtual void KickSquare(character*, lsquare*); + virtual truth ConsumeItem(character*, item*, int); + virtual truth AllowDropGifts() const { return false; } + virtual truth Drink(character*) const; + virtual truth HasDrinkHandler() const { return true; } + virtual truth Dip(character*) const; + virtual truth HasDipHandler() const { return true; } + virtual void TeleportSquare(character*, lsquare*); + virtual truth AllowSpoil(citem*) const { return false; } + virtual truth AllowKick(ccharacter*, const lsquare*) const; + virtual void HostileAction(character*) const; + virtual truth AllowAltarPolymorph() const { return false; } + virtual truth AllowFoodSearch() const { return false; } + virtual void AddItemEffect(item*); + character* FindRandomExplosiveReceiver() const; +}; + #endif diff --git a/Main/Include/wterra.h b/Main/Include/wterra.h index 8ddd5ec40..e850fb025 100644 --- a/Main/Include/wterra.h +++ b/Main/Include/wterra.h @@ -105,7 +105,7 @@ class gwterrain : public wterrain, public gterrain DATA_BASE_VALUE(const prototype*, ProtoType); DATA_BASE_VALUE(int, Config); void Draw(blitdata&) const; - virtual int GetEntryDifficulty() const { return 10; } + virtual int GetEntryDifficulty() const; void CalculateNeighbourBitmapPoses(); virtual int GetWalkability() const; DATA_BASE_TRUTH(UsesLongArticle); diff --git a/Main/Include/wterras.h b/Main/Include/wterras.h index fdf2ee89f..08e468152 100644 --- a/Main/Include/wterras.h +++ b/Main/Include/wterras.h @@ -77,6 +77,26 @@ OWTERRAIN(underwatertunnelexit, owterrain) { }; +OWTERRAIN(aslonacastle, owterrain) +{ +}; + +OWTERRAIN(rebelcamp, owterrain) +{ +}; + +OWTERRAIN(goblinfort, owterrain) +{ +}; + +OWTERRAIN(fungalcave, owterrain) +{ +}; + +OWTERRAIN(pyramid, owterrain) +{ +}; + OWTERRAIN(blackmarket, owterrain) { }; diff --git a/Main/Source/actions.cpp b/Main/Source/actions.cpp index 055925b4e..d3c95df58 100644 --- a/Main/Source/actions.cpp +++ b/Main/Source/actions.cpp @@ -343,10 +343,10 @@ void craft::Handle() } /** - * explosions may trigger something that apparently terminates the action and so also deletes it's recipedata - * TODO what is being triggered? + * TODO: CONFIRM IF STILL HAPPENING: explosions may trigger something that apparently terminates the action and so also deletes it's recipedata, what is being triggered? */ if(!rpdBkp.v2XplodAt.Is0() && rpdBkp.xplodStr>0){ + if(rpdBkp.xplodStr>9)rpdBkp.xplodStr=9; // to limit the "fire sparks" size to one square game::GetCurrentLevel()->Explosion( ActorLocal, CONST_S("killed by the forge heat"), rpdBkp.v2XplodAt, rpdBkp.xplodStr, false, false); ADD_MESSAGE("Forging sparks explode lightly."); //this will let sfx play TODO better message? the idea is to make forging a bit hazardous, diff --git a/Main/Source/bodypart.cpp b/Main/Source/bodypart.cpp index 4415ed67f..7ddf08815 100644 --- a/Main/Source/bodypart.cpp +++ b/Main/Source/bodypart.cpp @@ -812,19 +812,6 @@ leg::~leg() delete GetBoot(); } -truth corpse::IsDestroyable(ccharacter* Char) const -{ - for(int c = 0; c < GetDeceased()->GetBodyParts(); ++c) - { - bodypart* BodyPart = GetDeceased()->GetBodyPart(c); - - if(BodyPart && !BodyPart->IsDestroyable(Char)) - return false; - } - - return true; -} - long corpse::GetTruePrice() const { long Price = 0; @@ -2644,10 +2631,19 @@ truth corpse::SuckSoul(character* Soul, character* Summoner) double arm::GetTypeDamage(ccharacter* Enemy) const { - if(!GetWielded() || !GetWielded()->IsGoodWithPlants() || !Enemy->IsPlant()) - return Damage; - else - return Damage * 1.5; + double ActualDamage = GetDamage(); + + if(GetWielded()) + { + if(GetWielded()->IsGoodWithPlants() && Enemy->IsPlant()) + ActualDamage *= 1.5; + if(GetWielded()->IsGoodWithUndead() && Enemy->IsUndead()) + ActualDamage *= 1.5; + if(HasSadistWeapon() && Enemy->IsMasochist()) + ActualDamage *= 0.75; + } + + return ActualDamage; } void largetorso::Draw(blitdata& BlitData) const @@ -3244,10 +3240,10 @@ void bodypart::ReceiveAcid(material* Material, cfestring& LocationName, long Mod if(Master->IsPlayer()) { cchar* TName = LocationName.IsEmpty() ? GetBodyPartName().CStr() : LocationName.CStr(); - ADD_MESSAGE("Acidous %s dissolves your %s.", MName.CStr(), TName); + ADD_MESSAGE("Caustic %s dissolves your %s.", MName.CStr(), TName); } else - ADD_MESSAGE("Acidous %s dissolves %s.", MName.CStr(), Master->CHAR_NAME(DEFINITE)); + ADD_MESSAGE("Caustic %s dissolves %s.", MName.CStr(), Master->CHAR_NAME(DEFINITE)); } Master->ReceiveBodyPartDamage(0, Damage, ACID, GetBodyPartIndex(), YOURSELF, false, false, false); @@ -3257,6 +3253,47 @@ void bodypart::ReceiveAcid(material* Material, cfestring& LocationName, long Mod } } +void bodypart::ReceiveHeat(material* Material, cfestring& LocationName, long Modifier) +{ + if(Master && MainMaterial->GetInteractionFlags() & CAN_BURN) + { + long Tries = Modifier / 1000; + Modifier -= Tries * 1000; + int Damage = 0; + + for(long c = 0; c < Tries; ++c) + if(!(RAND() % 100)) + ++Damage; + + if(Modifier && !(RAND() % 100000 / Modifier)) + ++Damage; + + if(Damage) + { + ulong Minute = game::GetTotalMinutes(); + character* Master = this->Master; + + if(Master->GetLastAcidMsgMin() != Minute && (Master->CanBeSeenByPlayer() || Master->IsPlayer())) + { + Master->SetLastAcidMsgMin(Minute); // I don't really think we need to track acid and heat damage messages separately. + cfestring MName = Material->GetName(false, false); + + if(Master->IsPlayer()) + { + cchar* TName = LocationName.IsEmpty() ? GetBodyPartName().CStr() : LocationName.CStr(); + ADD_MESSAGE("Scorching %s burns your %s.", MName.CStr(), TName); + } + else + ADD_MESSAGE("Scorching %s burns %s.", MName.CStr(), Master->CHAR_NAME(DEFINITE)); + } + + Master->ReceiveBodyPartDamage(0, Damage, FIRE, GetBodyPartIndex(), YOURSELF, false, false, false); + ulong DeathFlags = Material->IsStuckTo(Master) ? IGNORE_TRAPS : 0; + Master->CheckDeath(CONST_S("burnt to death by ") + Material->GetName(), 0, DeathFlags); + } + } +} + void bodypart::TryToRust(long LiquidModifier) { if(MainMaterial->TryToRust(LiquidModifier << 4)) diff --git a/Main/Source/bugworkaround.cpp b/Main/Source/bugworkaround.cpp index 76150f9aa..10f361f29 100644 --- a/Main/Source/bugworkaround.cpp +++ b/Main/Source/bugworkaround.cpp @@ -63,7 +63,7 @@ bool bugfixdp::AlertConfirmFixMsg(const char* cMsg){ fsLastAlertConfirmFixMsg<<"PROBLEM! "<(GetSquareUnder())->GetNeighbourLSquare(I); } wsquare* character::GetNeighbourWSquare(int I) const { return static_cast(GetSquareUnder())->GetNeighbourWSquare(I); } -god* character::GetMasterGod() const { return game::GetGod(GetConfig()); } +god* character::GetMasterGod() const { return game::GetGod(GetConfig())!=NULL ? game::GetGod(GetConfig()) : game::GetGod(GetAttachedGod()); } //TODO explain why GetConfig() works in most cases? test-case Terra@UT4 vs Lycanthropy. Is the priest Config ID at char.dat the same found for such ID at ivandef.h? so in short, should this just use GetAttachedGod() coherency from the very beggining? col16 character::GetBodyPartColorA(int, truth) const { return GetSkinColor(); } col16 character::GetBodyPartColorB(int, truth) const @@ -465,9 +465,11 @@ int character::GetMoveType() const (!StateIsActivated(ETHEREAL_MOVING) ? DataBase->MoveType : DataBase->MoveType | ETHEREAL) | - (!StateIsActivated(SWIMMING) + ((!StateIsActivated(SWIMMING) && + !(IsPlayer() && game::IsInWilderness() && game::PlayerHasBoat())) ? DataBase->MoveType - : DataBase->MoveType | WALK|SWIM)); } + : DataBase->MoveType | SWIM) ); +} festring character::GetZombieDescription() const { return " of " + GetName(INDEFINITE); } truth character::BodyPartCanBeSevered(int I) const { return I; } @@ -1027,22 +1029,19 @@ void character::Be() if(!Action || Action->AllowFoodConsumption()) Hunger(); - int MinHPPercent = 128; for(int c = 0; c < BodyParts; ++c) { - int tempHpPercent; + int tempHpPercent; bodypart* BodyPart = GetBodyPart(c); if(BodyPart) { - tempHpPercent = (BodyPart->GetHP() * audio::MAX_INTENSITY_VOLUME) / BodyPart->GetMaxHP(); - if(tempHpPercent < MinHPPercent ) - { - MinHPPercent = tempHpPercent; - } - - + tempHpPercent = (BodyPart->GetHP() * audio::MAX_INTENSITY_VOLUME) / BodyPart->GetMaxHP(); + if(tempHpPercent < MinHPPercent ) + { + MinHPPercent = tempHpPercent; + } } } audio::IntensityLevel( audio::MAX_INTENSITY_VOLUME - MinHPPercent ); @@ -1205,7 +1204,7 @@ void character::Move(v2 MoveTo, truth TeleportMove, truth Run) if(IsPlayer()) { cchar* CrawlVerb = StateIsActivated(LEVITATION) ? "float" : "crawl"; - ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb); + ADD_MESSAGE("You try very hard to %s forward, but your load is too heavy.", CrawlVerb); } EditAP(-1000); @@ -1583,41 +1582,75 @@ truth character::TryMove(v2 MoveVector, truth Important, truth Run, truth* pbWai return false; } } - else + else // In wilderness: { /** No multitile support */ if(CanMove() && GetArea()->IsValidPos(MoveTo) && (CanMoveOn(GetNearWSquare(MoveTo)) - || game::GoThroughWallsCheatIsActive())) + || game::GoThroughWallsCheatIsActive()) + ) { if(!game::GoThroughWallsCheatIsActive()) { charactervector& V = game::GetWorldMap()->GetPlayerGroup(); - truth Discard = false; - for(uint c = 0; c < V.size(); ++c) - if(!V[c]->CanMoveOn(GetNearWSquare(MoveTo))) + if(IsPlayer() && game::PlayerHasBoat()) + { + if((GetSquareUnder()->GetSquareWalkability() & WALK) && // land + !(GetNearWSquare(MoveTo)->GetWalkability() & WALK)) // ocean { - if(!Discard) - { - ADD_MESSAGE("One or more of your team members cannot cross this terrain."); + if(!game::TruthQuestion("Board your ship? [y/N]")) + return false; - if(!game::TruthQuestion("Discard them? [y/N]")) - return false; + if(V.empty()) + ADD_MESSAGE("You board your ship and prepare to sail."); + else + ADD_MESSAGE("Your team boards your ship and prepares to sail."); - Discard = true; - } + EditStamina(-30000, false); + } + else if(!(GetSquareUnder()->GetSquareWalkability() & WALK) && + (GetNearWSquare(MoveTo)->GetWalkability() & WALK)) + { + if(!game::TruthQuestion("Disembark the ship? [y/N]")) + return false; - if(Discard) - delete V[c]; + if(V.empty()) + ADD_MESSAGE("You disembark your ship."); + else + ADD_MESSAGE("You and your team disembark your ship."); - V.erase(V.begin() + c--); + EditStamina(-30000, false); } + } + else // Cannot take some pets over ocean without a ship. + { + truth Discard = false; + + for(uint c = 0; c < V.size(); ++c) + if(!V[c]->CanMoveOn(GetNearWSquare(MoveTo))) + { + if(!Discard) + { + ADD_MESSAGE("One or more of your team members cannot cross this terrain."); + + if(!game::TruthQuestion("Abandon them? [y/N]")) + return false; + + Discard = true; + } + + if(Discard) + delete V[c]; + + V.erase(V.begin() + c--); + } + } } - Move(MoveTo, false); + Move(MoveTo, false, Run); return true; } else @@ -1848,6 +1881,20 @@ void character::Die(ccharacter* Killer, cfestring& Msg, ulong DeathFlags) festring NewMsg = MsgBut << Msg; AddScoreEntry(NewMsg, 2, true); } + else if(Max(game::GetAslonaStoryState(), game::GetRebelStoryState()) >= 4) // At least two of the three quests. + { + festring Whom; + if(game::GetAslonaStoryState() > game::GetRebelStoryState()) + Whom = "royalists"; + else + Whom = "rebels"; + + festring MsgBut = "fought in the civil war of Aslona on the side of " + Whom + ", but was "; + festring NewMsg = MsgBut << Msg; + int Bonus = Max(game::GetAslonaStoryState(), game::GetRebelStoryState()) - 2; + + AddScoreEntry(NewMsg, Bonus, true); + } else AddScoreEntry(Msg); @@ -2059,14 +2106,14 @@ truth character::RemoveEncryptedScroll() return false; } -truth character::RemoveShadowVeil() +truth character::RemoveShadowVeil(character* ToWhom) { for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) if(i->IsShadowVeil()) { item* Item = *i; Item->RemoveFromSlot(); - Item->SendToHell(); + ToWhom->ReceiveItemAsPresent(Item); return true; } @@ -2077,7 +2124,147 @@ truth character::RemoveShadowVeil() if(Item && Item->IsShadowVeil()) { Item->RemoveFromSlot(); - Item->SendToHell(); + ToWhom->ReceiveItemAsPresent(Item); + return true; + } + } + + return false; +} + +truth character::HasNuke() const +{ + for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) + if(i->IsNuke()) + return true; + + return combineequipmentpredicates()(this, &item::IsNuke, 1); +} + +truth character::RemoveNuke(character* ToWhom) +{ + for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) + if(i->IsNuke()) + { + item* Item = *i; + Item->RemoveFromSlot(); + ToWhom->ReceiveItemAsPresent(Item); + return true; + } + + for(int c = 0; c < GetEquipments(); ++c) + { + item* Item = GetEquipment(c); + + if(Item && Item->IsNuke()) + { + Item->RemoveFromSlot(); + ToWhom->ReceiveItemAsPresent(Item); + return true; + } + } + + return false; +} + +truth character::HasWeepObsidian() const +{ + for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) + if(i->IsWeepObsidian()) + return true; + + return combineequipmentpredicates()(this, &item::IsWeepObsidian, 1); +} + +truth character::RemoveWeepObsidian(character* ToWhom) +{ + for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) + if(i->IsWeepObsidian()) + { + item* Item = *i; + Item->RemoveFromSlot(); + ToWhom->ReceiveItemAsPresent(Item); + return true; + } + + for(int c = 0; c < GetEquipments(); ++c) + { + item* Item = GetEquipment(c); + + if(Item && Item->IsWeepObsidian()) + { + Item->RemoveFromSlot(); + ToWhom->ReceiveItemAsPresent(Item); + return true; + } + } + + return false; +} + +truth character::HasMuramasa() const +{ + for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) + if(i->IsMuramasa()) + return true; + + return combineequipmentpredicates()(this, &item::IsMuramasa, 1); +} + +truth character::RemoveMuramasa(character* ToWhom) +{ + for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) + if(i->IsMuramasa()) + { + item* Item = *i; + Item->RemoveFromSlot(); + ToWhom->ReceiveItemAsPresent(Item); + return true; + } + + for(int c = 0; c < GetEquipments(); ++c) + { + item* Item = GetEquipment(c); + + if(Item && Item->IsMuramasa()) + { + Item->RemoveFromSlot(); + ToWhom->ReceiveItemAsPresent(Item); + return true; + } + } + + return false; +} + +truth character::HasMasamune() const +{ + for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) + if(i->IsMasamune()) + return true; + + return combineequipmentpredicates()(this, &item::IsMasamune, 1); +} + +truth character::RemoveMasamune(character* ToWhom) +{ + for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) + if(i->IsMasamune()) + { + item* Item = *i; + Item->RemoveFromSlot(); + ToWhom->ReceiveItemAsPresent(Item); + return true; + } + + for(int c = 0; c < GetEquipments(); ++c) + { + item* Item = GetEquipment(c); + + if(Item && Item->IsMasamune()) + { + Item->RemoveFromSlot(); + ToWhom->ReceiveItemAsPresent(Item); return true; } } @@ -2368,7 +2555,7 @@ void character::AddScoreEntry(cfestring& Description, double Multiplier, truth A if(AddEndLevel) { if(game::IsInWilderness()) - Desc << " in the world map"; + Desc << " in the wilderness"; else Desc << " in " << game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex()); } @@ -3667,15 +3854,21 @@ void character::BeKicked(character* Kicker, item* Boot, bodypart* Leg, v2 HitPos case HAS_HIT: case HAS_BLOCKED: case DID_NO_DAMAGE: - if(IsEnabled() && (!CheckBalance(KickDamage) || (Boot && Boot->IsKicking()))) + if(IsEnabled()) { - if(IsPlayer()) - ADD_MESSAGE("The kick throws you off balance."); - else if(Kicker->IsPlayer()) - ADD_MESSAGE("The kick throws %s off balance.", CHAR_DESCRIPTION(DEFINITE)); + if(Boot && (Boot->GetConfig() == BOOT_OF_DISPLACEMENT) && Kicker->Displace(this, true)) + return; - v2 FallToPos = GetPos() + game::GetMoveVector(Direction); - FallTo(Kicker, FallToPos); + if(!CheckBalance(KickDamage) || (Boot && (Boot->GetConfig() == BOOT_OF_KICKING))) + { + if(IsPlayer()) + ADD_MESSAGE("The kick throws you off balance."); + else if(Kicker->IsPlayer()) + ADD_MESSAGE("The kick throws %s off balance.", CHAR_DESCRIPTION(DEFINITE)); + + v2 FallToPos = GetPos() + game::GetMoveVector(Direction); + FallTo(Kicker, FallToPos); + } } } } @@ -3948,8 +4141,12 @@ truth character::CheckForEnemies(truth CheckDoors, truth CheckGround, truth MayM if(CheckGround && CheckForUsefulItemsOnGround()) return true; - if(MayMoveRandomly && MoveRandomly()) // one has heard that an enemy is near but doesn't know where - return true; + if(!Leader || Leader!=PLAYER || (Leader==PLAYER && ivanconfig::GetHoldPosMaxDist()==0)){ // this lets all pets stay put if hold pos is > 0 + if(MayMoveRandomly){ + if(MoveRandomly()) // one has heard that an enemy is near but doesn't know where + return true; + } + } } return false; @@ -3998,10 +4195,11 @@ truth character::CheckForUsefulItemsOnGround(truth CheckFood) truth character::FollowLeader(character* Leader) { - if(!Leader || Leader == this || !IsEnabled()) + if(!Leader || Leader == this || !IsEnabled()) { DBG1(GetNameSingular().CStr()); return false; + } - if(CommandFlags & FOLLOW_LEADER && Leader->CanBeSeenBy(this) && Leader->SquareUnderCanBeSeenBy(this, true)){ + if(CommandFlags & FOLLOW_LEADER && Leader->CanBeSeenBy(this) && Leader->SquareUnderCanBeSeenBy(this, true)){ DBG1(GetNameSingular().CStr()); v2HoldPos = GoingTo; //will keep the last reference position possible v2 Distance = GetPos() - GoingTo; //set by SeekLeader() @@ -4012,10 +4210,10 @@ truth character::FollowLeader(character* Leader) } if(IsGoingSomeWhere()){ - if(!MoveTowardsTarget(true)){ + if(!MoveTowardsTarget(true)){ DBG1(GetNameSingular().CStr()); TerminateGoingTo(); return false; - }else{ + }else{ DBG1(GetNameSingular().CStr()); return true; } }else{ @@ -4024,7 +4222,7 @@ truth character::FollowLeader(character* Leader) v2HoldPos=GetPos(); //when the game is loaded keep current pos TODO could be savegamed tho if(ivanconfig::GetHoldPosMaxDist()>0){ v2 v2HoldDist = GetPos() - v2HoldPos; - if(abs(v2HoldDist.X) < ivanconfig::GetHoldPosMaxDist() && abs(v2HoldDist.Y) < ivanconfig::GetHoldPosMaxDist()){ + if(abs(v2HoldDist.X) < ivanconfig::GetHoldPosMaxDist() && abs(v2HoldDist.Y) < ivanconfig::GetHoldPosMaxDist()){ DBG1(GetNameSingular().CStr()); // will do other things return false; }else{ @@ -4268,10 +4466,15 @@ void character::ShowNewPosInfo() const if(GetLSquareUnder()->HasEngravings()) { - if(CanRead()) - ADD_MESSAGE("Something has been engraved here: \"%s\"", GetLSquareUnder()->GetEngraved()); - else - ADD_MESSAGE("Something has been engraved here."); + cchar* Text = GetLSquareUnder()->GetEngraved(); + + if(Text[0] != '#') // Prevent displaying map notes. + { + if(CanRead()) + ADD_MESSAGE("Something has been engraved here: \"%s\"", Text); + else + ADD_MESSAGE("Something has been engraved here."); + } } } @@ -4528,7 +4731,15 @@ void character::DisplayInfo(festring& Msg) else { Msg << ' ' << GetName(INDEFINITE).CapitalizeCopy() << " is " - << GetStandVerb() << " here. " << GetPersonalPronoun().CapitalizeCopy(); + << GetStandVerb() << " here. "; + + if(PLAYER->GetAttribute(WISDOM) > 11) + { + Msg << GetPersonalPronoun().CapitalizeCopy() << " is " + << GetHitPointDescription() << ". "; + } + + Msg << GetPersonalPronoun().CapitalizeCopy(); cchar* Separator1 = GetAction() ? "," : " and"; cchar* Separator2 = " and"; @@ -4656,10 +4867,15 @@ material* character::SetSecondaryMaterial(material*, int) void character::TeleportRandomly(truth Intentional) { v2 TelePos = ERROR_V2; + lsquare* FromSquare = GetLSquareUnder(); if(StateIsActivated(TELEPORT_LOCK)) { - ADD_MESSAGE("You flicker for a second."); + if(IsPlayer()) + ADD_MESSAGE("You flicker for a second."); + else if(CanBeSeenByPlayer()) + ADD_MESSAGE("%s flickers for a second.", CHAR_NAME(DEFINITE)); + return; } @@ -4745,6 +4961,15 @@ void character::TeleportRandomly(truth Intentional) if(IsPlayerAutoPlay()) AutoPlayAIReset(true); + + // There's a small chance that some warp gas/fluid is left behind. + if(FromSquare->IsFlyable() && !RAND_N(1000)) + { + if(!RAND_N(100)) + FromSquare->AddSmoke(gas::Spawn(TELEPORT_GAS, 50 + RAND() % 100)); + else + FromSquare->SpillFluid(this, liquid::Spawn(TELEPORT_FLUID, 50 + RAND() % 50)); + } } truth character::IsPlayerAutoPlay() @@ -4754,7 +4979,8 @@ truth character::IsPlayerAutoPlay() void character::DoDetecting() { - if(IsPlayerAutoPlay())return; + if(IsPlayerAutoPlay() || !IsPlayer()) + return; material* TempMaterial; @@ -4906,6 +5132,9 @@ int character::ReceiveBodyPartDamage(character* Damager, int Damage, int Type, i { bodypart* BodyPart = GetBodyPart(BodyPartIndex); + if(!BodyPart) + return 0; + if(!Damager || Damager->AttackMayDamageArmor()) BodyPart->DamageArmor(Damager, Damage, Type); @@ -5847,9 +6076,10 @@ void character::AddCocaColaConsumeEndMessage() const void character::ReceiveDarkness(long Amount) { - EditExperience(INTELLIGENCE, -Amount / 5, 1 << 13); - EditExperience(WISDOM, -Amount / 5, 1 << 13); - EditExperience(CHARISMA, -Amount / 5, 1 << 13); + // A bit of a gum solution, but spiders and frogs are immune to prevent + // Lobh-se and dark frogs from poisoning themselves. + if(!(IsSpider() || IsFrog())) + EditExperience(RAND() % BASE_ATTRIBUTES, -Amount, 1 << 14); if(IsPlayer()) game::DoEvilDeed(int(Amount / 50)); @@ -5894,7 +6124,9 @@ void character::AddBoneConsumeEndMessage() const if(IsPlayer()) ADD_MESSAGE("You feel like a hippie."); else if(CanBeSeenByPlayer()) - ADD_MESSAGE("%s barks happily.", CHAR_NAME(DEFINITE)); // this suspects that nobody except dogs can eat bones + // This suspects that nobody except dogs can eat bones. + // Necromancers can now eat bones, too. --red_kangaroo + ADD_MESSAGE("%s seems happy.", CHAR_NAME(DEFINITE)); } truth character::RawEditAttribute(double& Experience, int Amount) const @@ -5939,21 +6171,44 @@ void character::DrawPanel(truth AnimationDraw) const FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Ht %d cm", GetSize()); FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Wt %d kg", GetTotalCharacterWeight()); ++PanelPosY; - FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), - IsInBadCondition() ? RED : WHITE, "HP %d/%d", GetHP(), GetMaxHP()); + if(ivanconfig::UseDescriptiveHP()) + { + // Display health level description. + festring DescHP = GetHitPointDescription().CapitalizeCopy().CStr(); + + if(DescHP.GetSize() > 11) + { + // Description doesn't fit on the sidebar, so split it on two lines. + festring Desc2; + festring::SplitString(DescHP, Desc2, 11); + + FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), + IsInBadCondition() ? RED : WHITE, "%s", Desc2.CStr()); + FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), + IsInBadCondition() ? RED : WHITE, " %s", DescHP.CStr()); + } + else + FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), + IsInBadCondition() ? RED : WHITE, "%s", DescHP.CStr()); + } + else // Normal numeric HP. + { + FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), + IsInBadCondition() ? RED : WHITE, "HP: %d/%d", GetHP(), GetMaxHP()); + } ++PanelPosY; FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Gold: %ld", GetMoney()); ++PanelPosY; if(game::IsInWilderness()) - FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Worldmap"); + FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Wilderness"); else FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "%s", game::GetCurrentDungeon()->GetShortLevelDescription(game::GetCurrentLevelIndex()).CapitalizeCopy().CStr()); ivantime Time; game::GetTime(Time); - FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Day %d", Time.Day); + FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Day %d", Time.Day); FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Time %d:%s%d", Time.Hour, Time.Min < 10 ? "0" : "", Time.Min); @@ -5995,6 +6250,11 @@ void character::DrawPanel(truth AnimationDraw) const FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), TirednessStateColors[TirednessState], TirednessStateStrings[TirednessState]); + if(game::IsInWilderness() && game::PlayerHasBoat() && IsSwimming()) + { + FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "On Ship"); + } + if(game::PlayerIsRunning()) { FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, GetRunDescriptionLine(0)); @@ -6703,7 +6963,8 @@ truth character::CanBeSeenBy(ccharacter* Who, truth Theoretically, truth IgnoreE { truth MayBeESPSeen = Who->IsEnabled() && !IgnoreESP && Who->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5; truth MayBeInfraSeen = Who->IsEnabled() && Who->StateIsActivated(INFRA_VISION) && IsWarm(); - truth Visible = !StateIsActivated(INVISIBLE) || MayBeESPSeen || MayBeInfraSeen; + truth Visible = !StateIsActivated(INVISIBLE) || MayBeESPSeen || MayBeInfraSeen || Who->StateIsActivated(DETECTING); + // Let monsters also make use of Detecting status effect. Here we simulate that they always ask to detect player flesh. if(game::IsInWilderness()) return Visible; @@ -7224,16 +7485,6 @@ void character::PrintEndTeleportLockMessage() const ADD_MESSAGE("Your mind soars far and wide."); } -void character::TeleportLockHandler() -{ - if (StateIsActivated(TELEPORT_LOCK)) - { - EditTemporaryStateCounter(TELEPORT_LOCK, 1); - if (GetTemporaryStateCounter(TELEPORT_LOCK) < 1000) - EditTemporaryStateCounter(TELEPORT_LOCK, 1); - } -} - void character::DisplayStethoscopeInfo(character*) const { game::RegionListItemEnable(false); @@ -7251,7 +7502,8 @@ void character::DisplayStethoscopeInfo(character*) const Info.AddEntry(CONST_S("Height: ") + GetSize() + " cm", LIGHT_GRAY); Info.AddEntry(CONST_S("Weight: ") + GetTotalCharacterWeight() + " kg", LIGHT_GRAY); Info.AddEntry(CONST_S(""), LIGHT_GRAY); - Info.AddEntry(CONST_S("Hit points: ") + GetHP() + "/" + GetMaxHP(), IsInBadCondition() ? RED : LIGHT_GRAY); + Info.AddEntry(CONST_S("Hit points: ") + GetHP() + "/" + GetMaxHP() + " (" + GetHitPointDescription() + ")", + IsInBadCondition() ? RED : LIGHT_GRAY); Info.AddEntry(CONST_S(""), LIGHT_GRAY); Info.AddEntry(CONST_S("Body parts:"), LIGHT_GRAY); @@ -7333,6 +7585,9 @@ void character::DisplayStethoscopeInfo(character*) const break; } + if(IsPlayer() && game::PlayerHasBoat()) + Info.AddEntry("Ship Owned", LIGHT_GRAY); + game::SetStandardListAttributes(Info); Info.Draw(); } @@ -8390,6 +8645,11 @@ truth character::CheckZap() ADD_MESSAGE("This monster type can't zap."); return false; } + if(GetAttribute(INTELLIGENCE) < 5) + { + ADD_MESSAGE("You are too dumb to operate any delicate magical devices."); + return false; + } return true; } @@ -8606,7 +8866,19 @@ truth character::TryToChangeEquipment(stack* MainStack, stack* SecStack, int Cho } if(OldEquipment) - OldEquipment->MoveTo(MainStack); + { + if(!OldEquipment->CanBeUnEquipped(Chosen)) + { + if(IsPlayer()) + ADD_MESSAGE("You fail to unequip %s.", OldEquipment->CHAR_NAME(DEFINITE)); + else + ADD_MESSAGE("%s fails to unequip %s.", CHAR_DESCRIPTION(DEFINITE), OldEquipment->CHAR_NAME(DEFINITE)); + + return false; + } + else + OldEquipment->MoveTo(MainStack); + } sorter Sorter = EquipmentSorter(Chosen); @@ -8654,6 +8926,16 @@ truth character::TryToChangeEquipment(stack* MainStack, stack* SecStack, int Cho return false; } + if(!Item->CanBeEquipped(Chosen)) + { + if(IsPlayer()) + ADD_MESSAGE("You fail to equip %s.", Item->CHAR_NAME(DEFINITE)); + else + ADD_MESSAGE("%s fails to equip %s.", CHAR_DESCRIPTION(DEFINITE), Item->CHAR_NAME(DEFINITE)); + + return false; + } + Item->RemoveFromSlot(); SetEquipment(Chosen, Item); @@ -8834,6 +9116,13 @@ void character::SetBodyPart(int I, bodypart* What) truth character::ConsumeItem(item* Item, cfestring& ConsumeVerb, truth nibbling) { + if(Item->IsQuestItem() || !Item->IsDestroyable(this)) + { + if(IsPlayer()) + ADD_MESSAGE("You cannot eat that!"); + return false; + } + if(IsPlayer() && HasHadBodyPart(Item) && !game::TruthQuestion(CONST_S("Are you sure? You may be able to put it back... [y/N]"))) @@ -9070,13 +9359,13 @@ void character::ShowAdventureInfoAlt() const #ifdef WIZARD int Answer = game::KeyQuestion( - CONST_S("See (i)nventory, (m)essage history, (k)ill list, (l)ook, (x) cheat look or [ESC]/(n)othing?"), - 'z', 13, 'i','I', 'm','M', 'k','K', 'l','L', 'x','X', 'N','n', KEY_ESC); //default answer 'z' is ignored + CONST_S("See (i)nventory, (m)essage history, (k)ill list, (s)tats, (l)ook around or (n)othing?"), // ESC implicit + 'z', 13, 'i','I', 'm','M', 'k','K', 's', 'S', 'l','L', 'x','X', 'n','N', KEY_ESC); //default answer 'z' is ignored #else int Answer = game::KeyQuestion( - CONST_S("See (i)nventory, (m)essage history, (k)ill list, (l)ook or [ESC]/(n)othing?"), - 'z', 11, 'i','I', 'm','M', 'k','K', 'l','L', 'n','N', KEY_ESC); //default answer 'z' is ignored + CONST_S("See (i)nventory, (m)essage history, (k)ill list, (s)tats, (l)ook around or (n)othing?"), // ESC implicit + 'z', 11, 'i','I', 'm','M', 'k','K', 's', 'S', 'l','L', 'n','N', KEY_ESC); //default answer 'z' is ignored #endif if(Answer == 'i' || Answer == 'I'){ @@ -9092,7 +9381,10 @@ void character::ShowAdventureInfoAlt() const }else if(Answer == 'l' || Answer == 'L'){ commandsystem::PlayerDiedLookMode(); #endif - }else if(Answer == 'n' || Answer == 'N' || Answer == KEY_ESC){ +}else if(Answer == 's' || Answer == 'S'){ + DisplayStethoscopeInfo(NULL); + commandsystem::PlayerDiedWeaponSkills(); +}else if(Answer == 'n' || Answer == 'N' || Answer == KEY_ESC){ return; } } @@ -9146,14 +9438,16 @@ truth character::EditAllAttributes(int Amount) void character::AddAttributeInfo(festring& Entry) const { - Entry.Resize(57); + Entry.Resize(54); Entry << GetAttribute(ENDURANCE); - Entry.Resize(60); + Entry.Resize(57); Entry << GetAttribute(PERCEPTION); - Entry.Resize(63); + Entry.Resize(60); Entry << GetAttribute(INTELLIGENCE); - Entry.Resize(66); + Entry.Resize(63); Entry << GetAttribute(WISDOM); + Entry.Resize(66); + Entry << GetAttribute(WILL_POWER); Entry.Resize(69); Entry << GetAttribute(CHARISMA); Entry.Resize(72); @@ -9204,7 +9498,9 @@ void character::ReceiveHolyBanana(long Amount) EditExperience(PERCEPTION, Amount, 1 << 13); EditExperience(INTELLIGENCE, Amount, 1 << 13); EditExperience(WISDOM, Amount, 1 << 13); + EditExperience(WILL_POWER, Amount, 1 << 13); EditExperience(CHARISMA, Amount, 1 << 13); + EditExperience(MANA, Amount, 1 << 13); RestoreLivingHP(); } @@ -10593,13 +10889,36 @@ void character::ReceiveWhiteUnicorn(long Amount) game::DoEvilDeed(Amount / 50); BeginTemporaryState(TELEPORT, Amount / 100); + DecreaseStateCounter(LYCANTHROPY, -Amount / 100); DecreaseStateCounter(POISONED, -Amount / 100); DecreaseStateCounter(PARASITE_TAPE_WORM, -Amount / 100); + DecreaseStateCounter(PARASITE_MIND_WORM, -Amount / 100); DecreaseStateCounter(LEPROSY, -Amount / 100); DecreaseStateCounter(VAMPIRISM, -Amount / 100); } +void character::ReceiveSickness(long Amount) +{ + if(IsPlayer() && !RAND_N(10)) + ADD_MESSAGE("You don't feel so good."); + + if(!StateIsActivated(DISEASE_IMMUNITY) && !RAND_N(10)) + { + switch(RAND() % 5) + { + case 0: BeginTemporaryState(LYCANTHROPY, Amount); break; + case 1: BeginTemporaryState(VAMPIRISM, Amount); break; + case 2: BeginTemporaryState(PARASITE_TAPE_WORM, Amount); break; + case 3: BeginTemporaryState(PARASITE_MIND_WORM, Amount); break; + case 4: GainIntrinsic(LEPROSY); break; + } + } + + if(!RAND_N(10)) + BeginTemporaryState(POISONED, Amount + RAND_N(Amount)); +} + /* Counter should be negative. Removes intrinsics. */ void character::DecreaseStateCounter(long State, int Counter) @@ -11094,25 +11413,25 @@ truth character::EquipmentScreen(stack* MainStack, stack* SecStack) for(;;) { List.Empty(); - List.EmptyDescription(); + List.EmptyDescription(); - TotalEquippedWeight = 0; + TotalEquippedWeight = 0; - for(int c = 0; c < GetEquipments(); ++c) // if equipment exists, add to TotalEquippedWeight - { - item* Equipment = GetEquipment(c); - TotalEquippedWeight += (Equipment) ? Equipment->GetWeight() : 0; - } + for(int c = 0; c < GetEquipments(); ++c) // if equipment exists, add to TotalEquippedWeight + { + item* Equipment = GetEquipment(c); + TotalEquippedWeight += (Equipment) ? Equipment->GetWeight() : 0; + } - if(IsPlayer()) - { - festring Total("Total weight: "); - Total << TotalEquippedWeight; - Total << "g"; + if(IsPlayer()) + { + festring Total("Total weight: "); + Total << TotalEquippedWeight; + Total << "g"; - List.AddDescription(CONST_S("")); - List.AddDescription(Total); - } + List.AddDescription(CONST_S("")); + List.AddDescription(Total); + } if(!IsPlayer()) { @@ -11140,6 +11459,8 @@ truth character::EquipmentScreen(stack* MainStack, stack* SecStack) Entry << (GetBodyPartOfEquipment(c) ? "-" : "can't use"); List.AddEntry(Entry, LIGHT_GRAY, 20, game::AddToItemDrawVector(itemvector())); } + + List.SetLastEntryHelp(festring() << "Your equipped arms and armor."); } game::DrawEverythingNoBlit(); @@ -12022,22 +12343,28 @@ cchar* character::GetRunDescriptionLine(int I) const return !I ? GetRunDescriptionLineOne().CStr() : GetRunDescriptionLineTwo().CStr(); if(IsFlying()) - return !I ? "Flying" : "very fast"; + return !I ? "Flying" : " very fast"; if(IsSwimming()) - return !I ? "Swimming" : "very fast"; + { + if(IsPlayer() && game::IsInWilderness() && game::PlayerHasBoat()) + return !I ? "Sailing" : " very fast"; + else + return !I ? "Swimming" : " very fast"; + } return !I ? "Running" : ""; } void character::VomitAtRandomDirection(int Amount) { - if(game::IsInWilderness()) - return; - /* Lacks support of multitile monsters */ + if(game::IsInWilderness() || IsLarge() || Amount <= 0) + return; - v2 Possible[9]; + v2 Possible[9] = { GetPos(), GetPos(), GetPos(), + GetPos(), GetPos(), GetPos(), + GetPos(), GetPos(), GetPos() }; int Index = 0; for(int d = 0; d < 9; ++d) @@ -12203,6 +12530,46 @@ void character::ReceiveMustardGasLiquid(int BodyPartIndex, long Modifier) } } +void character::ReceiveFlames(long Volume) +{ + if(!Volume) + return; + + for(int c = 0; c < BodyParts; ++c) + { + bodypart* BodyPart = GetBodyPart(c); + + if(BodyPart && BodyPart->GetMainMaterial()) + { + if(BodyPart->CanBeBurned() + && (BodyPart->GetMainMaterial()->GetInteractionFlags() & CAN_BURN) + && !BodyPart->IsBurning()) + { + BodyPart->TestActivationEnergy(Volume); + } + else if(BodyPart->IsBurning()) + BodyPart->GetMainMaterial()->AddToThermalEnergy(Volume); + } + } + + for(int c = 0; c < GetEquipments(); ++c) + { + item* Equipment = GetEquipment(c); + + if(Equipment) + { + if(Equipment->CanBeBurned() + && (Equipment->GetMainMaterial()->GetInteractionFlags() & CAN_BURN) + && !Equipment->IsBurning()) + { + Equipment->TestActivationEnergy(Volume); + } + else if(Equipment->IsBurning()) + Equipment->GetMainMaterial()->AddToThermalEnergy(Volume); + } + } +} + truth character::IsBadPath(v2 Pos) const { if(!IsGoingSomeWhere()) @@ -12235,7 +12602,7 @@ truth character::ForgetRandomThing() return false; int RandomGod = RAND_N(Known.size()); - Known.at(RAND_N(Known.size()))->SetIsKnown(false); + Known.at(RandomGod)->SetIsKnown(false); ADD_MESSAGE("You forget how to pray to %s.", Known.at(RandomGod)->GetName()); return true; @@ -12272,6 +12639,19 @@ truth character::ReceiveSirenSong(character* Siren) if(Siren->GetRelation(this) != HOSTILE) return false; + if(RAND_N(GetAttribute(WILL_POWER)) > RAND_N(Siren->GetAttribute(CHARISMA))) + { + if(IsPlayer()) + ADD_MESSAGE("The beautiful song of %s makes you feel a little sad.", Siren->CHAR_NAME(DEFINITE)); + else if(CanBeSeenByPlayer()) + ADD_MESSAGE("%s sings a beautiful melody.", Siren->CHAR_NAME(DEFINITE)); + else + ADD_MESSAGE("You hear a beautiful song."); + + EditExperience(WILL_POWER, 100, 1 << 12); + return false; + } + if(!RAND_N(4)) { if(IsPlayer()) @@ -12290,7 +12670,7 @@ truth character::ReceiveSirenSong(character* Siren) ChangeTeam(Siren->GetTeam()); if(CanBeSeenByPlayer()) - ADD_MESSAGE("%s seems to be totally brainwashed by %s melodies.", CHAR_NAME(DEFINITE), Siren->CHAR_NAME(DEFINITE)); + ADD_MESSAGE("%s seems to be totally brainwashed by %s's melodies.", CHAR_NAME(DEFINITE), Siren->CHAR_NAME(DEFINITE)); else ADD_MESSAGE("You hear a beautiful song."); @@ -12311,11 +12691,16 @@ truth character::ReceiveSirenSong(character* Siren) CHAR_NAME(DEFINITE), What->CHAR_NAME(INDEFINITE), Siren->CHAR_NAME(DEFINITE), Siren->CHAR_OBJECT_PRONOUN); else ADD_MESSAGE("You hear a beautiful song."); + + if(Siren->GetConfig() == AMBASSADOR_SIREN) + Siren->TeleportRandomly(true); } else { if(IsPlayer()) ADD_MESSAGE("You would like to give something to %s.", Siren->CHAR_NAME(DEFINITE)); + else if(CanBeSeenByPlayer()) + ADD_MESSAGE("%s looks longingly at %s.", CHAR_NAME(DEFINITE), Siren->CHAR_NAME(DEFINITE)); else ADD_MESSAGE("You hear a beautiful song."); } @@ -12480,6 +12865,13 @@ truth character::CanTameWithDulcis(const character* Tamer) const if(GetAttachedGod() == DULCIS) return true; + if(!IgnoreDanger()) + TamingDifficulty = Max(TamingDifficulty, int(10 * GetRelativeDanger(Tamer))); + else + TamingDifficulty = Max(TamingDifficulty, (10 * GetHPRequirementForGeneration() / Max(Tamer->GetHP(), 1))); + + TamingDifficulty = Max(TamingDifficulty, GetAttribute(WILL_POWER)); + int Modifier = Tamer->GetAttribute(WISDOM) + Tamer->GetAttribute(CHARISMA); if(Tamer->IsPlayer()) @@ -12487,15 +12879,6 @@ truth character::CanTameWithDulcis(const character* Tamer) const else if(Tamer->GetAttachedGod() == DULCIS) Modifier += 50; - if(TamingDifficulty == 0) - { - if(!IgnoreDanger()) - TamingDifficulty = int(10 * GetRelativeDanger(Tamer)); - else - TamingDifficulty = 10 * GetHPRequirementForGeneration() - / Max(Tamer->GetHP(), 1); - } - return Modifier >= TamingDifficulty * 3; } @@ -12506,40 +12889,50 @@ truth character::CanTameWithLyre(const character* Tamer) const if(TamingDifficulty == NO_TAMING) return false; - if(TamingDifficulty == 0) - { - if(!IgnoreDanger()) - TamingDifficulty = int(10 * GetRelativeDanger(Tamer)); - else - TamingDifficulty = 10 * GetHPRequirementForGeneration() - / Max(Tamer->GetHP(), 1); - } + if(!IgnoreDanger()) + TamingDifficulty = Max(TamingDifficulty, int(10 * GetRelativeDanger(Tamer))); + else + TamingDifficulty = Max(TamingDifficulty, (10 * GetHPRequirementForGeneration() / Max(Tamer->GetHP(), 1))); + + TamingDifficulty = Max(TamingDifficulty, GetAttribute(WILL_POWER)); + + int Modifier = Tamer->GetAttribute(MANA) + Tamer->GetAttribute(CHARISMA); - return Tamer->GetAttribute(CHARISMA) >= TamingDifficulty; + return Modifier >= TamingDifficulty * 2; } truth character::CanTameWithScroll(const character* Tamer) const { int TamingDifficulty = GetTamingDifficulty(); - return (TamingDifficulty != NO_TAMING - && (TamingDifficulty == 0 - || Tamer->GetAttribute(INTELLIGENCE) * 4 - + Tamer->GetAttribute(CHARISMA) - >= TamingDifficulty * 5)); + + if(TamingDifficulty == NO_TAMING) + return false; + + /* + if(!IgnoreDanger()) + TamingDifficulty = Max(TamingDifficulty, int(10 * GetRelativeDanger(Tamer))); + else + TamingDifficulty = Max(TamingDifficulty, (10 * GetHPRequirementForGeneration() / Max(Tamer->GetHP(), 1))); + */ + + TamingDifficulty = Max(TamingDifficulty, GetAttribute(WILL_POWER)); + + int Modifier = Tamer->GetAttribute(INTELLIGENCE) * 4 + Tamer->GetAttribute(CHARISMA); + + return Modifier >= TamingDifficulty * 5; } truth character::CanTameWithResurrection(const character* Tamer) const { - int TamingDifficulty = GetTamingDifficulty(); + int TamingDifficulty = GetTamingDifficulty(); - if (TamingDifficulty == NO_TAMING) - return false; - if (TamingDifficulty == 0) - return true; + if(TamingDifficulty == NO_TAMING) + return false; - return (Tamer->GetAttribute(CHARISMA) >= TamingDifficulty / 2); - // || Tamer->GetAttribute(CHARISMA) + WisIntAvg >= (2 * TamingDifficulty) / 3); - // Alternate formula 2/3 * TamingDifficulty <= CHA + (WIS + INT) / 2 + TamingDifficulty = Max(TamingDifficulty, GetAttribute(WILL_POWER)); + + return Tamer->GetAttribute(CHARISMA) >= TamingDifficulty / 2; + // Alternate formula 2/3 * TamingDifficulty <= CHA + (WIS + INT) / 2 } truth character::CheckSadism() @@ -12643,7 +13036,7 @@ truth character::StateIsActivated (long What) const truth character::CheckAIZapOpportunity() { - if(!CanZap() || !IsHumanoid() || !IsEnabled()) + if(!CanZap() || !IsHumanoid() || !IsEnabled() || GetAttribute(INTELLIGENCE) < 5) return false; // Check visible area for hostiles: @@ -12728,26 +13121,31 @@ truth character::CheckAIZapOpportunity() } } if(!TargetFound) - { return false; - } } else return false; - // Check inventory for zappable item. + // Check inventory and equipment for zappable items. itemvector ItemVector; GetStack()->FillItemVector(ItemVector); - item* ToBeZapped = 0; + for(int c = 0; c < GetEquipments(); ++c) + { + item* Equipment = GetEquipment(c); + + if(Equipment) + ItemVector.push_back(Equipment); + } + std::random_shuffle(ItemVector.begin(), ItemVector.end()); + + item* ToBeZapped = 0; for(uint c = 0; c < ItemVector.size(); ++c) - if((ItemVector[c]->GetMinCharges() > 0) && ItemVector[c]->GetPrice()) // Empty wands have zero price! + if(ItemVector[c]->IsZappable(this) && ItemVector[c]->IsZapWorthy(this)) { ToBeZapped = ItemVector[c]; - - if(!(RAND() % 3)) // Do not always pick the first available wand to zap. - break; + break; } if(!ToBeZapped) @@ -12761,11 +13159,8 @@ truth character::CheckAIZapOpportunity() EditAP(-100000 / APBonus(GetAttribute(PERCEPTION))); return true; } - else - return false; - TerminateGoingTo(); // Is this useful here? I don't think the code will ever - return true; // get down here. + return false; // Steps: // (1) - Acquire target as nearest enemy. @@ -12785,29 +13180,16 @@ int character::GetAdjustedStaminaCost(int BaseCost, int Attribute) return BaseCost / 0.20; } -int character::GetMagicItemCooldown(int BaseCooldown) -{ - int Attribute = GetAttribute(MANA); - - if(Attribute > 1) - { - return BaseCooldown / log10(Attribute); - } - - return BaseCooldown / 0.20; -} - truth character::TryToStealFromShop(character* Shopkeeper, item* ToSteal) { + if(!IsPlayer()) + return RAND_2; // Plain 50% chance for monsters. + double perception_check; if(Shopkeeper) - { perception_check = 100 - (1000 / (10 + Shopkeeper->GetAttribute(PERCEPTION))); - } else - { perception_check = 0; - } double base_chance = 100 - (100000 / (2000 + game::GetGod(CLEPTIA)->GetRelation())); double size_mod = std::pow(0.99999, ((ToSteal->GetWeight() * ToSteal->GetSize()) / GetAttribute(ARM_STRENGTH))); @@ -12819,3 +13201,80 @@ truth character::TryToStealFromShop(character* Shopkeeper, item* ToSteal) return (1 + RAND() % 100 < normalized_chance); } + +truth character::IsInBadCondition() const +{ + for(int c = 0; c < BodyParts; ++c) + { + bodypart* BodyPart = GetBodyPart(c); + if(BodyPart && BodyPartIsVital(c) && + ((BodyPart->GetHP() * 3 < BodyPart->GetMaxHP()) || + ((BodyPart->GetHP() == 1) && (BodyPart->GetMaxHP() > 1)))) + return true; + } + + return HP * 3 < MaxHP; +} + +festring character::GetHitPointDescription() const +{ + float Health = (float)GetHP() / (float)GetMaxHP(); + festring Desc = "bugged"; + + // Do not describe humanoids with missing limbs as "unharmed". + if(!HasAllBodyParts() || IsInBadCondition()) + Health *= 0.75; + + if(TorsoIsAlive()) + { + if(Health == 1.0) + Desc = IsPlayer() ? "unharmed" : "not hurt"; // Reads better in look message. + else if(Health >= 0.95) + Desc = "bruised"; + else if(Health >= 0.8) + Desc = "lightly wounded"; + else if(Health >= 0.7) + Desc = "wounded"; + else if(Health >= 0.5) + Desc = "heavily wounded"; + else if(Health >= 0.3) + Desc = "severely wounded"; + else if(Health >= 0.1) + Desc = "mortally wounded"; + else if(Health > 0.0) + Desc = "almost dead"; + else if(Health <= 0.0) + Desc = "dead"; + } + else // Unliving creatures + { + if(Health == 1.0) + Desc = "undamaged"; + else if(Health >= 0.95) + Desc = "scratched"; + else if(Health >= 0.8) + Desc = "lightly damaged"; + else if(Health >= 0.7) + Desc = "damaged"; + else if(Health >= 0.5) + Desc = "heavily damaged"; + else if(Health >= 0.3) + Desc = "severely damaged"; + else if(Health >= 0.1) + Desc = "extremely damaged"; + else if(Health > 0.0) + Desc = "almost destroyed"; + else if(Health <= 0.0) + Desc = "destroyed"; + } + + if(IsUndead()) + Desc = "dead"; + + return Desc; +} + +truth character::WillGetTurnSoon() const +{ + return GetAP() >= 900; +} diff --git a/Main/Source/charsset.cpp b/Main/Source/charsset.cpp index 3b04982b4..1d201efd6 100644 --- a/Main/Source/charsset.cpp +++ b/Main/Source/charsset.cpp @@ -10,6 +10,8 @@ * */ +#include + #include "stack.h" #include "message.h" #include "actions.h" diff --git a/Main/Source/cmdcraft.cpp b/Main/Source/cmdcraft.cpp index 4e84d9462..fb7416243 100644 --- a/Main/Source/cmdcraft.cpp +++ b/Main/Source/cmdcraft.cpp @@ -14,6 +14,7 @@ #include +#include "hiteffect.h" #include "traps.h" #include "dbgmsgproj.h" @@ -128,29 +129,30 @@ float craftcore::CraftSkill(character* Char){ //is the current capability of suc bool craftcore::canBeCrafted(item* it){ if(dynamic_cast(it)!=NULL) return true; + if(dynamic_cast(it)!=NULL) //checked when splitting + return true; + if(dynamic_cast(it)!=NULL) //to allow crafting chests again + return true; const itemdatabase* itdb = it->GetDataBase(); - if( - game::IsQuestItem(it) || - it->GetEnchantment()!=0 || - it->GetCategory()==BOOK || - it->GetCategory()==MISC || - it->GetCategory()==SCROLL || - !itdb->CanBeWished || - itdb->Possibility <= 0 || - !itdb->PostFix.IsEmpty() || - false // just to make it easier to re-organize and add checks above - ){ - return false; - } - if(it->GetCategory()==POTION) { item* Bottle = dynamic_cast(it); - if(Bottle && !Bottle->GetSecondaryMaterial() && Bottle->GetNameSingular() == "bottle") - return true; + if(Bottle && (Bottle->GetNameSingular() == "bottle" || Bottle->GetNameSingular() == "vial")) //TODO really necessary the singular name check? excessive? + { + if(!Bottle->GetSecondaryMaterial()) + return true; + if( // extracting from corpses + // TODO may be the "kind of action" (as a parameter for this func) could allow some specific materials only, so a new action EXTRACT_FROM_CORPSE would allow only the ones below + Bottle->GetSecondaryMaterial()->GetConfig()==SULPHURIC_ACID || + Bottle->GetSecondaryMaterial()->GetConfig()==POISON_LIQUID || + Bottle->GetSecondaryMaterial()->GetConfig()==MAGIC_LIQUID + ) + return true; + } + DBG3(Bottle->GetSecondaryMaterial()->GetConfig(),SULPHURIC_ACID,POISON_LIQUID); return false; } @@ -161,12 +163,30 @@ bool craftcore::canBeCrafted(item* it){ if(Can && !Can->GetSecondaryMaterial()) return true; + DBGLN; return false; } //TODO all these things should have some easier kind of property to be checked - if(!craftcore::MoreCraftDeniedFilters(it)) + if(!craftcore::MoreCraftDeniedFilters(it)){ + DBGLN; + return false; + } + + if( // more complex/generic filter, better keep as last check? + game::IsQuestItem(it) || + it->GetEnchantment()!=0 || + it->GetCategory()==BOOK || + it->GetCategory()==MISC || + it->GetCategory()==SCROLL || + !itdb->CanBeWished || + itdb->Possibility <= 0 || + !itdb->PostFix.IsEmpty() || + false // just to make it easier to re-organize and add checks above + ){ + DBGLN; return false; + } return true; } @@ -629,7 +649,7 @@ struct recipe{ virtual void fillInfo(){ ABORT("missing recipe info implementation"); } void failPlacementMsg(recipedata& rpd){ - ADD_MESSAGE("%s can't be placed here!",name.CStr()); + ADD_MESSAGE("%s can't be placed here.",name.CStr()); rpd.bAlreadyExplained=true; } void failIngredientsMsg(recipedata& rpd){ @@ -639,7 +659,8 @@ struct recipe{ rpd.bAlreadyExplained=true; } void failToolMsg(recipedata& rpd,festring tool){ - ADD_MESSAGE("You don't have %s to work on.",tool.CStr()); + ADD_MESSAGE("You don't have a strong enough %s to work on the requested materials.",tool.CStr()); + // "strong enough" means that a too weak material wont be able to work on a much stronger material rpd.bAlreadyExplained=true; } @@ -668,9 +689,14 @@ struct recipe{ return false; } - bool where(recipedata& rpd,bool acceptSelfLocation=false){ + bool where(recipedata& rpd,bool acceptSelfLocation=false) + { if(whereRaw(rpd,"Build it where?",acceptSelfLocation)) - if(rpd.lsqrPlaceAt!=NULL && rpd.lsqrPlaceAt->GetOLTerrain()==NULL && rpd.lsqrPlaceAt->GetCharacter()==NULL){ + if(rpd.lsqrPlaceAt != NULL && rpd.lsqrPlaceAt->GetOLTerrain() == NULL && + rpd.lsqrPlaceAt->GetCharacter() == NULL && + !(rpd.lsqrPlaceAt->GetRoom() && rpd.lsqrPlaceAt->GetRoom()->GetMaster() && + rpd.lsqrPlaceAt->GetRoom()->GetMaster() != PLAYER)) // Prevents building in owned rooms. + { rpd.v2PlaceAt = rpd.lsqrPlaceAt->GetPos(); rpd.bCanBePlaced=true; return true; @@ -729,6 +755,13 @@ struct recipe{ ABORT("at most 2 tools '%s' '%s' '%s'",rpd.itTool->GetName(INDEFINITE).CStr(),rpd.itTool2->GetName(INDEFINITE).CStr(),itTool->GetName(INDEFINITE).CStr()); } + static item* findCarvingToolSpecific(recipedata& rpd, item* itTool,int iMinCarvingStr, int iType, int& riMult, int iIncMult){ //this one is to avoid using #define mess :>, but instead of a simple if() we get several method calls... tho more readable... + if(itTool==NULL){ + itTool = FindTool(rpd, iType, 0, iMinCarvingStr); + if(itTool!=NULL)riMult+=iIncMult; + } + return itTool; + } static item* findCarvingTool(recipedata& rpd,item* itToWorkOn){ int iCarvingStr=0; @@ -741,10 +774,17 @@ struct recipe{ int iMinCarvingStr = iCarvingStr/2; + // any blanded thing bug preferably a dagger + int iMult=1; item* itTool = FindTool(rpd, DAGGER, 0, iMinCarvingStr); //carving: tool cant be too much weaker - + itTool = findCarvingToolSpecific(rpd,itTool,iMinCarvingStr,DAGGER,iMult,0); + itTool = findCarvingToolSpecific(rpd,itTool,iMinCarvingStr,SICKLE,iMult,1); + itTool = findCarvingToolSpecific(rpd,itTool,iMinCarvingStr,SHORT_SWORD,iMult,1); + itTool = findCarvingToolSpecific(rpd,itTool,iMinCarvingStr,AXE,iMult,2); + itTool = findCarvingToolSpecific(rpd,itTool,iMinCarvingStr,MEAT_CLEAVER,iMult,2); + itTool = findCarvingToolSpecific(rpd,itTool,iMinCarvingStr,LONG_SWORD,iMult,2); + itTool = findCarvingToolSpecific(rpd,itTool,iMinCarvingStr,SPEAR,iMult,3); // mmm... spear is easy enough to be found already TODO all other more difficult to be used weapons with blades may be added below here :), basically create a balsa dagger using a balsa spear and you are good to go xD if(itTool!=NULL){ - int iMult=1; if(iCarvingStr>1){ int itStr=itTool->GetMainMaterial()->GetStrengthValue(); if(itStrGetName(DEFINITE).CStr(),ToUse[i]->GetNameSingular().CStr()); if(!CI.bMultSelect && lVolM!=reqVolPrecise) //TODO use error margin because of float VS integer calc? ex.: if diff is +1 or -1, just allow it. ABORT("remaining vol calc needs fixing %ld != %ld, %f, %ld",lVolM,reqVolPrecise,CI.fUsablePercVol,lRemainingVol); - matM->SetVolume(lVolM); + matM->SetVolume(lVolM); //TODO there is a big problem here... if the crafting cannot be started because a tool is missing, this ingredient will have less volume than required and may not be reused in case it is a non-meltable!!! // bool bForceLump = CI.fUsablePercVol<1.0; // item* lumpR = craftcore::PrepareRemains(rpd,matM,bForceLump); - item* lumpR = craftcore::PrepareRemains(rpd,matM,CIT_LUMP); + item* lumpR = craftcore::PrepareRemains(rpd,matM); lumpR->GetMainMaterial()->SetVolume(lRemainingVol); lumpMix(vi,lumpR,rpd.bSpendCurrentTurn); @@ -1050,8 +1090,15 @@ struct recipe{ if(CI.iReqCfg==INGOT) fsIngTpNm = "ingot"; else - fsIngTpNm = std::string(typeid(T).name()).substr(1).c_str(); //TODO demangling simple like that may give weird text one day if other types are added - ADD_MESSAGE("There is not enough %ss to craft it.",fsIngTpNm.CStr()); + { + // TODO: + // demangling simple like that may give weird text one day if other types are added + // This is way too confusing, just use generic "material" and rework later. + //fsIngTpNm = std::string(typeid(T).name()).substr(1).c_str(); + fsIngTpNm = "material"; + } + + ADD_MESSAGE("You don't have enough %ss.",fsIngTpNm.CStr()); } if(reqVol<=0) @@ -1133,10 +1180,11 @@ struct recipe{ } static bool findOLT(recipedata& rpd,int iCfgOLT,bool bReqOnlyVisible=false){ bool bFound=false; + //obs.: lsqr->CanBeFeltByPlayer() will NOT work if player CAN MOVE on it! if(bReqOnlyVisible){DBGLN; //even if far away for(int iY=0;iYGetYSize();iY++){for(int iX=0;iXGetXSize();iX++){ lsquare* lsqr = game::GetCurrentLevel()->GetLSquare(v2(iX,iY));DBG3(lsqr,iX,iY); - if((lsqr->CanBeFeltByPlayer() || lsqr->CanBeSeenBy(rpd.rc.H())) && chkOLT(rpd,lsqr,iCfgOLT)){ + if( chkOLT(rpd,lsqr,iCfgOLT) && (lsqr->GetPos().IsAdjacent(rpd.rc.H()->GetPos()) || lsqr->CanBeSeenBy(rpd.rc.H())) ){ SetOLT(rpd,lsqr,iCfgOLT); bFound = true; break; @@ -1149,7 +1197,7 @@ struct recipe{ if(game::GetCurrentLevel()->IsValidPos(v2Pos)){ lsquare* lsqr = rpd.rc.H()->GetNearLSquare(v2Pos);DBG1(lsqr); // if(lsqr->CanBeFeltBy(rpd.rc.H()) && chkOLT(rpd,lsqr,iCfgOLT)){ - if(lsqr->CanBeFeltByPlayer() && chkOLT(rpd,lsqr,iCfgOLT)){ + if(chkOLT(rpd,lsqr,iCfgOLT) && lsqr->GetPos().IsAdjacent(rpd.rc.H()->GetPos())){ SetOLT(rpd,lsqr,iCfgOLT); bFound = true; break; @@ -1199,7 +1247,7 @@ struct recipe{ struct srpCutWeb : public recipe{ virtual void fillInfo(){ init("cut","a web"); - desc << "Focus on cutting the web down. Much safer if you use a cutting tool!"; + desc << "Cut a web down. Much safer than tearing it down, especially if you use a cutting tool. You might still get stuck, though."; } virtual bool work(recipedata& rpd){ @@ -1452,7 +1500,7 @@ struct srpDoor : public srpOltBASE{ virtual void fillInfo(){ init("build","a secret door"); - desc << "You will need a hammer or other blunt tool, and sticks, bones or ingots."; + desc << "Build a door. You will need a hammer or some other blunt tool, plus sticks, bones or ingots."; } virtual bool work(recipedata& rpd){ @@ -1472,7 +1520,7 @@ struct srpChair : public srpOltBASE{ virtual void fillInfo(){ init("build","a chair"); - desc << fsDescBASE; + desc << "Create a chair to rest your tired legs. You will need a hammer or some other blunt tool."; } virtual bool work(recipedata& rpd){ @@ -1490,7 +1538,7 @@ struct srpAnvil : public srpOltBASE{ virtual void fillInfo(){ init("build","an anvil"); - desc << "You can create an anvil using ingots while near a forge.\n " << fsDescBASE; + desc << "Create an anvil for further crafting, using ingots. You must be near a forge.\n " << fsDescBASE; } virtual bool work(recipedata& rpd){ @@ -1511,7 +1559,7 @@ struct srpForge : public srpOltBASE{ virtual void fillInfo(){ init("build","a forge"); - desc << "You can build a forge. " << fsDescBASE; + desc << "Build a forge for further crafting. " << fsDescBASE; } virtual bool work(recipedata& rpd){ @@ -1534,7 +1582,7 @@ struct srpWorkBench : public srpOltBASE{ virtual void fillInfo(){ init("build","a workbench"); - desc << "You can build a workbench. " << fsDescBASE; + desc << "Build a workbench for further crafting. " << fsDescBASE; } virtual bool work(recipedata& rpd){ @@ -1558,7 +1606,7 @@ struct srpWall2 : public srpOltBASE{ virtual void fillInfo(){ init("construct","a wall"); - desc << "You can construct a wall piling stones, sticks or bones."; + desc << "Construct a wall by piling stones, sticks or bones."; } virtual bool work(recipedata& rpd){ @@ -1576,7 +1624,7 @@ struct srpWall2 : public srpOltBASE{ struct srpJoinLumps : public recipe{ virtual void fillInfo(){ init("merge","lumps"); - desc << "Merge lumps of the same material."; + desc << "Merge lumps of the same material into a single, bigger one."; } void askForEqualLumps(recipedata& rpd){ @@ -1633,7 +1681,7 @@ struct srpJoinLumps : public recipe{ struct srpMelt : public srpJoinLumps{ virtual void fillInfo(){ init("melt","an ingot"); - desc << "Near a forge, meltable lumps can be used to prepare ingots."; + desc << "Melt lumps of metal into ingots, later usable in crafting. You must be near a forge."; } virtual bool work(recipedata& rpd){ @@ -1731,7 +1779,7 @@ struct srpMelt : public srpJoinLumps{ struct srpDismantle : public recipe{ //TODO this is instantaneous, should take time? virtual void fillInfo(){ init("dismantle","item"); - desc << "Near a forge, any meltable item can be dismantled to recover it's materials.\n Otherwise you just need a cutting tool."; + desc << "Dismantle an item to recover its materials.\nMetal items require a forge to dismantle, all other need just a cutting tool."; } virtual bool work(recipedata& rpd){ @@ -1801,11 +1849,16 @@ struct srpDismantle : public recipe{ //TODO this is instantaneous, should take t return false; } + /** + * Meltable sticks should be lumpable to easify the code. + * Kept in case of wanting to re-enabling for some reason... + * if(dynamic_cast(itToUse)!=NULL){ ADD_MESSAGE("%s is already a stick.", itToUse->GetName(DEFINITE).CStr()); rpd.bAlreadyExplained=true; return false; } + */ // for now, uses just one turn to smash anything into lumps but needs to be near a FORGE // TODO should actually require a stronger hammer than the material's hardness being smashed, and could be anywhere... @@ -1868,8 +1921,8 @@ struct srpInspect : public recipe{ //TODO this is instantaneous, should take tim struct srpResistanceVS : public recipe{ //TODO this is instantaneous, should take time? virtual void fillInfo(){ - init("hardness check","(weaker material results)"); - desc << "Hit an item with another, the weaker one will receive a scratch."; + init("check","material strength"); + desc << "Check the relative hardness of two items. The item made of softer material will receive a scratch."; } virtual bool work(recipedata& rpd){ @@ -1944,7 +1997,8 @@ struct srpSplitLump : public recipe{ virtual void fillInfo(){ init("split","raw materials"); - desc << "Split them to be easier to work with.\n To just cut out (subtract) some volume, specify a negative value in cm3. \nIt is good to cut them precisely to not waste materials."; + desc << "Split raw materials to make them easier to work with.\n" + "To remove some volume, specify a negative value in cm3.\nIt is good to cut precisely not to waste materials."; } virtual bool work(recipedata& rpd){ @@ -2105,13 +2159,13 @@ struct srpSplitLump : public recipe{ struct srpForgeItem : public recipe{ virtual void fillInfo(){ init("create","an item"); - desc << "Using a blunt weapon as hammer,\n close to an anvil and with a forge nearby you can create items.\n Or a cutting weapon, if close to a workbench will speed up the work.\n" - "Meltables requires to be near a forge and an anvil.\n" - "Carving will be faster if near a workbench.\n" - "Ingots will be fully used.\n" - "Sticks will be almost fully used.\n" - "A single stone will be partially used thru carving.\n" - "Remaining materials will become lumps.\n"; + desc << "Try to craft an item.\n" + "You will need an appropriate tool. Normally a hammer or a dagger are used, " + "but other blunt and cutting weapons can serve as well. To work with metals, " + "you need to have ingots prepared and to be standing near a forge and an anvil. " + "Working on a workbench will be faster.\n" + "Ingots and stick are always fully used up, while stones can be used up partially. " + "Any remaining materials will become lumps."; } /** @@ -2168,12 +2222,10 @@ struct srpForgeItem : public recipe{ } virtual bool work(recipedata& rpd){DBGLN; - //////////////// let user type the item name + // let user type the item name static festring Default; //static to help on reusing! like creating more of the same -// if(rpd.itSpawn!=NULL) // this is important to grant the cleanup -// ABORT("item to be crafted should be NULL %d %s %s",rpd.itSpawn->GetID(),rpd.itSpawn->GetNameSingular().CStr(),rpd.dbgInfo().CStr()); + item* itSpawn; - item* itSpawn = NULL; for(;;){ festring Temp; Temp << Default;DBG4(Default.CStr(),Default.GetSize(),Temp.CStr(),Temp.GetSize()); // to let us fix previous instead of having to fully type it again @@ -2188,7 +2240,7 @@ struct srpForgeItem : public recipe{ itSpawn = protosystem::CreateItemToCraft(Temp);DBGLN; - if(itSpawn!=NULL){DBGLN; + if(itSpawn){DBGLN; if(craftcore::canBeCrafted(itSpawn)){DBG4("SendingToHellRejectedCraftItem",itSpawn->GetID(),itSpawn->GetNameSingular().CStr(),itSpawn); rpd.itSpawnType = CIT_PROTOTYPE; rpd.fsItemSpawnSearchPrototype = Temp; @@ -2217,10 +2269,12 @@ struct srpForgeItem : public recipe{ } ////////////////////////////////////////////////////////////////////////////////////////////////// + /* This message ends up being rather confusing. if(Default == itSpawn->GetName(INDEFINITE)) //TODO compare for EQUAL ignoring case ADD_MESSAGE("Now you need the materials to create a %s.",itSpawn->GetName(INDEFINITE).CStr()); else ADD_MESSAGE("Now you need the materials to create a %s as you would probably create %s.",Default.CStr(),itSpawn->GetName(INDEFINITE).CStr()); + */ long lVolM = itSpawn->GetMainMaterial()->GetVolume(); if(lVolM==0) @@ -2291,7 +2345,7 @@ struct srpForgeItem : public recipe{ } } if(!bM){ - ADD_MESSAGE("You will craft it later..."); + ADD_MESSAGE("You don't have the materials to craft a %s.", Default.CStr()); rpd.bAlreadyExplained=true; craftcore::SendToHellSafely(itSpawn); return false; @@ -2383,7 +2437,7 @@ struct srpForgeItem : public recipe{ return false; } - if(!recipe::findOLT(rpd,ANVIL,true)){ + if(!recipe::findOLT(rpd,ANVIL)){ //must be near the anvil to use it!!! craftcore::SendToHellSafely(itSpawn); return false; } @@ -2475,7 +2529,7 @@ struct srpForgeItem : public recipe{ if(bMissingTools){ rpd.itTool=rpd.itTool2=NULL; //to make it easy to check/inform, wont start if missing one anyway - failToolMsg(rpd,"the required tool(s)"); + failToolMsg(rpd,"tool"); craftcore::SendToHellSafely(itSpawn); return false; } @@ -2523,7 +2577,7 @@ struct srpFluidsBASE : public recipe{ ///////////// tool //////////////// rpd.itTool = FindTool(rpd, DAGGER); if(rpd.itTool==NULL){ - failToolMsg(rpd,"a dagger"); + failToolMsg(rpd,"dagger"); return false; } @@ -2623,7 +2677,7 @@ struct srpFluidsBASE : public recipe{ rpd.itSpawnCfg = itBottle->GetConfig(); //may be a vial rpd.itSpawnMatMainCfg = itBottle->GetMainMaterial()->GetConfig(); rpd.itSpawnMatMainVol = itBottle->GetMainMaterial()->GetVolume(); - rpd.itSpawnMatSecCfg = iLiqCfg; + rpd.itSpawnMatSecCfg = iLiqCfg;DBG1(iLiqCfg); rpd.itSpawnMatSecVol = volume; // rpd.itSpawn = potion::Spawn(itBottle->GetConfig()); //may be a vial // delete rpd.itSpawn->SetSecondaryMaterial(liquid::Spawn(iLiqCfg, volume)); @@ -2635,7 +2689,7 @@ struct srpFluidsBASE : public recipe{ rpd.iBaseTurnsToFinish=5; - rpd. bCanStart=true; + rpd.bCanStart=true; return true; } @@ -2648,9 +2702,8 @@ struct srpPoison : public srpFluidsBASE{ } virtual void fillInfo(){ - init("extract","some poison"); - desc << "Use a " << fsTool << " to " << action << " " << name << " from " - << fsCorpse << " into a " << fsBottle << "."; + init("extract","poison"); + desc << "Use a " << fsTool << " to " << action << " " << name << " from a poisonous corpse into a " << fsBottle << "."; } virtual bool work(recipedata& rpd){ @@ -2667,9 +2720,8 @@ struct srpAcid : public srpFluidsBASE{ } virtual void fillInfo(){ - init("extract","some sulphuric acid"); - desc << "Use a " << fsTool << " to " << action << " " << name << " from " - << fsCorpse << " into a " << fsBottle << "."; + init("extract","acid"); + desc << "Use a " << fsTool << " to " << action << " " << name << " from an acidic corpse into a " << fsBottle << "."; } virtual bool work(recipedata& rpd){ @@ -2679,6 +2731,24 @@ struct srpAcid : public srpFluidsBASE{ } };srpAcid rpAcid; +struct srpMagic : public srpFluidsBASE{ + virtual bool chkCorpse(const materialdatabase* blood, const materialdatabase* flesh){ + return (blood->Effect == EFFECT_MUSHROOM || flesh->Effect == EFFECT_MUSHROOM || + blood->Effect == EFFECT_MAGIC_MUSHROOM || flesh->Effect == EFFECT_MAGIC_MUSHROOM); + } + + virtual void fillInfo(){ + init("extract","raw magic"); + desc << "Use a " << fsTool << " to " << action << " raw liquefied magic from a mushroom into a " << fsBottle << "."; + } + + virtual bool work(recipedata& rpd){ + iLiqCfg=MAGIC_LIQUID; + + return srpFluidsBASE::work(rpd); + } +};srpMagic rpMagic; + felist craftRecipes(CONST_S("What do you want to craft?")); std::vector vrpAllRecipes; @@ -2686,15 +2756,20 @@ void updateCraftDesc(){ craftRecipes.EmptyDescription(); float fSkill=craftcore::CraftSkill(PLAYER); //TODO should this dynamic value show too where stats are? - festring fsSkill="Crafting Skill: "; + festring fsSkill="Crafting Proficiency: "; // It's actually different from skills, so don't call it a skill. static char cSkill[20]; sprintf(cSkill, "%.1f",fSkill); fsSkill<0) + fsDesc<<" (Suspended Actions: "<iListIndex=vrpAllRecipes.size(); + prp->iListIndex=vrpAllRecipes.size()+1; //+1 is about the 1st entry related to suspended actions if(prp->name.IsEmpty()) ABORT("empty recipe name '%s' '%s' %d",prp->name.CStr(),prp->desc.CStr(),prp->iListIndex); @@ -2750,6 +2825,14 @@ humanoid* craftcore::CheckHumanoid(character* Char) } return h; } + +recipe* CheckSelectedAndInitRecipeIfNeeded(bool bInitRecipes,recipedata& rpd,recipe* prp, recipe& rp){ // to avoid macro hard readability + if(prp==NULL && rp.IsTheSelectedOne(rpd))prp=&rp; \ + DBG3(prp,&rp,rp.desc.CStr()); \ + if(bInitRecipes)addRecipe((recipe*)&rp); + return prp; +} + truth craftcore::Craft(character* Char) //TODO currently this is an over simplified crafting system... should be easy to add recipes and show their formulas... { humanoid* h = CheckHumanoid(Char); @@ -2760,42 +2843,47 @@ truth craftcore::Craft(character* Char) //TODO currently this is an over simplif if(!CheckArms(h,bLOk,bROk)) return false; - if(craftcore::HasSuspended()){ - int key = game::KeyQuestion(CONST_S("There are suspended crafting actions: (r)esume/ENTER, (c)ancel or start a (n)ew one?"), - KEY_ESC, 4, 'r', 'c', 'n', KEY_ENTER); - if(key==KEY_ESC)return false; - - if(key==KEY_ENTER) - key='r'; - - felist LSusp("Suspended crafting actions:",WHITE); - game::SetStandardListAttributes(LSusp); - LSusp.AddFlags(SELECTABLE); - for(int i=0;i0){ + game::SetStandardListAttributes(craftRecipes); + craftRecipes.AddFlags(SELECTABLE); + craftRecipes.ClearFilter(); + updateCraftDesc(); + sel = craftRecipes.Draw(); DBG1(sel); + + if(sel & FELIST_ERROR_BIT) + return false; + + if(sel==0 && craftcore::HasSuspended()){ + int key = game::KeyQuestion(CONST_S("There are suspended crafting actions: (r)esume/ENTER or (c)ancel?"), + KEY_ESC, 3, 'r', 'c', KEY_ENTER); + if(key==KEY_ESC)return false; + + if(key==KEY_ENTER)key='r'; + + felist LSusp("Suspended crafting actions:",WHITE); + game::SetStandardListAttributes(LSusp); + LSusp.AddFlags(SELECTABLE); + for(int i=0;i0){ - game::SetStandardListAttributes(craftRecipes); - craftRecipes.AddFlags(SELECTABLE); - craftRecipes.ClearFilter(); - updateCraftDesc(); - sel = craftRecipes.Draw(); DBG1(sel); - if(sel & FELIST_ERROR_BIT) - return false; } recipedata rpd(h,sel); @@ -2828,22 +2905,28 @@ truth craftcore::Craft(character* Char) //TODO currently this is an over simplif * 1st call it just initializes the recipes list after all recipes have been configured! */ bool bInitRecipes = vrpAllRecipes.size()==0; + + if(bInitRecipes){ + craftRecipes.AddEntry(festring()+"Suspended crafting actions", LIGHT_GRAY, 20, 0, true); + craftRecipes.SetLastEntryHelp("Resume or remove already started crafting actions."); + } + + // these are kind of grouped and not ordered like a-z recipe* prp=NULL; - #define RP(rp) \ - if(prp==NULL && rp.IsTheSelectedOne(rpd))prp=&rp; \ - DBG3(prp,&rp,rp.desc.CStr()); \ - if(bInitRecipes)addRecipe((recipe*)&rp); - // these are kind of grouped and not ordered like a-z TODO add the commented section code as felist (non selectable) section entries? - if(bInitRecipes)craftRecipes.AddEntry(festring()+"Furniture:", DARK_GRAY, 0, NO_IMAGE, false); + #define RP(MACRO_PARAM_rp) prp=CheckSelectedAndInitRecipeIfNeeded(bInitRecipes,rpd,prp,MACRO_PARAM_rp); // just a simple macro to easify maintenance + + if(bInitRecipes)craftRecipes.AddEntry(festring()+"Construction:", DARK_GRAY, 0, NO_IMAGE, false); + RP(rpAnvil); RP(rpChair); RP(rpDoor); - - if(bInitRecipes)craftRecipes.AddEntry(festring()+"Buildings:", DARK_GRAY, 0, NO_IMAGE, false); + RP(rpForge); RP(rpWall2); + RP(rpWorkBench); - if(bInitRecipes)craftRecipes.AddEntry(festring()+"Potions:", DARK_GRAY, 0, NO_IMAGE, false); - RP(rpPoison); + if(bInitRecipes)craftRecipes.AddEntry(festring()+"Alchemy:", DARK_GRAY, 0, NO_IMAGE, false); RP(rpAcid); + RP(rpPoison); + RP(rpMagic); if(bInitRecipes)craftRecipes.AddEntry(festring()+"Material crafting:", DARK_GRAY, 0, NO_IMAGE, false); RP(rpDismantle); @@ -2852,16 +2935,9 @@ truth craftcore::Craft(character* Char) //TODO currently this is an over simplif RP(rpResistanceVS); RP(rpInspect); - if(bInitRecipes)craftRecipes.AddEntry(festring()+"Blacksmithing:", DARK_GRAY, 0, NO_IMAGE, false); - RP(rpMelt); - RP(rpAnvil); - RP(rpForge); - if(bInitRecipes)craftRecipes.AddEntry(festring()+"Item crafting:", DARK_GRAY, 0, NO_IMAGE, false); RP(rpForgeItem); - - if(bInitRecipes)craftRecipes.AddEntry(festring()+"Wood carving:", DARK_GRAY, 0, NO_IMAGE, false); - RP(rpWorkBench); + RP(rpMelt); if(bInitRecipes)craftRecipes.AddEntry(festring()+"Tailoring:", DARK_GRAY, 0, NO_IMAGE, false); RP(rpCutWeb); @@ -2873,6 +2949,7 @@ truth craftcore::Craft(character* Char) //TODO currently this is an over simplif return false; }DBGLN; + //ADD_MESSAGE("Your chosen crafting action is to %s %s.",prp->action.CStr(),prp->name.CStr()); bool bDummy = prp->work(rpd); //bDummy(fied) as there is more detailed fail status from rpd bools //TODO these messages are generic, therefore dont look good... improve it @@ -2889,7 +2966,7 @@ truth craftcore::Craft(character* Char) //TODO currently this is an over simplif fsTools<GetName(INDEFINITE); } if(!fsTools.IsEmpty()) - ADD_MESSAGE("Let's see.. You will use %s as tool(s).",fsTools.CStr()); + ADD_MESSAGE("You will use %s as a tool.",fsTools.CStr()); if(rpd.otSpawnType!=CTT_NONE || rpd.itSpawnType!=CIT_NONE) { int iCraftTimeMult=1; @@ -3026,7 +3103,7 @@ item* crafthandle::CheckBreakItem(bool bAllowBreak, recipedata& rpd, item* itSpa * things that can't be broken are special. * if it can't be broken, will just create a messy lump. */ - ADD_MESSAGE("Your lack of skill broke %s into pieces...",itSpawn->GetName(DEFINITE).CStr()); + ADD_MESSAGE("Your lack of skill broke %s into pieces!",itSpawn->GetName(DEFINITE).CStr()); craftcore::PrepareRemains(rpd,itSpawn->GetMainMaterial(),CIT_LUMP); if(itSpawn->GetSecondaryMaterial()!=NULL) craftcore::PrepareRemains(rpd,itSpawn->GetSecondaryMaterial(),CIT_LUMP); @@ -3067,7 +3144,7 @@ item* crafthandle::SpawnItem(recipedata& rpd, festring& fsCreated) item* itSpawn = NULL; material* matS = NULL; - bool bAllowBreak=false; + bool bAllowBreak=false;DBG3(rpd.itSpawnType,rpd.itSpawnCfg,rpd.itSpawnMatSecCfg); switch(rpd.itSpawnType){ case CIT_POTION: /** @@ -3083,6 +3160,7 @@ item* crafthandle::SpawnItem(recipedata& rpd, festring& fsCreated) break; case CIT_PROTOTYPE: itSpawn = protosystem::CreateItemToCraft(rpd.fsItemSpawnSearchPrototype); + bAllowBreak=true; break; case CIT_STONE: @@ -3099,14 +3177,10 @@ item* crafthandle::SpawnItem(recipedata& rpd, festring& fsCreated) if(itSpawn==NULL) ABORT("craft spawned no item."); - if(!craftcore::canBeCrafted(itSpawn)){ - ABORT( - "Dear developer, for the sake of balance and challenge do not create recipes for:\n" - "- Quest items.\n" - "- Magical items as rings, amulets, wands, scrolls, horns etc.\n" - "Crafting any of this would be unbalanced as hell and unrealistic given your characters upbringing.\n" - "You're after all a slave, with no knowledge of magic, and crafting magical items should be beyond most craftsmen.\n" - ); + itemcontainer* ic = dynamic_cast(itSpawn); + if(ic!=NULL){ + if(ic->IsLocked()) + ic->SetIsLocked(false); //to prevent annoyance of not having the key TODO should instead be always a broken lock considering the complexity of the locking system itself? } if(rpd.itSpawnMatMainCfg==0 || rpd.itSpawnMatMainVol==0) @@ -3135,9 +3209,23 @@ item* crafthandle::SpawnItem(recipedata& rpd, festring& fsCreated) fsCreated << " "; fsCreated << "You crafted "; + // this check is more detailed and must be when the item is completely ready! + if(!craftcore::canBeCrafted(itSpawn)){ + ABORT( + "Dear developer, for the sake of balance and challenge do not create recipes for:\n" + "- Quest items.\n" + "- Magical items as rings, amulets, wands, scrolls, horns etc.\n" + "Crafting any of this would be unbalanced as hell and unrealistic given your characters upbringing.\n" + "You're after all a slave, with no knowledge of magic, and crafting magical items should be beyond most craftsmen.\n" + "This attempt: %s", + itSpawn->GetName(DEFINITE).CStr() + ); + } + itSpawn = CheckBreakItem(bAllowBreak, rpd, itSpawn, fsCreated); if(itSpawn!=NULL){ - fsCreated << itSpawn->GetName(INDEFINITE); + if(fsCreated.GetSize()<200) // this will prevent a crash about "stack smashing detected". TODO to test it and provide a better solution, just comment this `if` line and split a corpse in 40 parts or more + fsCreated << itSpawn->GetName(INDEFINITE); itSpawn->MoveTo(rpd.rc.H()->GetStack());DBGLN; } @@ -3148,6 +3236,35 @@ void crafthandle::CraftWorkTurn(recipedata& rpd){ DBG1(rpd.iRemainingTurnsToFini rpd.iRemainingTurnsToFinish--; rpd.bSuccesfullyCompleted = rpd.iRemainingTurnsToFinish==0; + if(clock()%2==0 ? rpd.iRemainingTurnsToFinish%3==0 : rpd.iRemainingTurnsToFinish%5==0){ // to avoid unnecessarily spamming hiteffects + // keep this preference order! + lsquare* lsqrHF=NULL; + if(!lsqrHF)lsqrHF=rpd.lsqrPlaceAt; + if(!lsqrHF)lsqrHF=rpd.v2AnvilLocation.Is0() ? NULL : rpd.rc.H()->GetNearLSquare(rpd.v2AnvilLocation); + if(!lsqrHF)lsqrHF=rpd.v2WorkbenchLocation.Is0() ? NULL : rpd.rc.H()->GetNearLSquare(rpd.v2WorkbenchLocation); + if(!lsqrHF)lsqrHF=rpd.v2PlaceAt.Is0() ? NULL : rpd.rc.H()->GetNearLSquare(rpd.v2PlaceAt); + if(!lsqrHF)lsqrHF=rpd.v2ForgeLocation.Is0() ? NULL : rpd.rc.H()->GetNearLSquare(rpd.v2ForgeLocation); + if(!lsqrHF)lsqrHF=rpd.v2XplodAt.Is0() ? NULL : rpd.rc.H()->GetNearLSquare(rpd.v2XplodAt); + if(lsqrHF){ + hiteffectSetup* pHitEff=new hiteffectSetup(); + + pHitEff->Type = (rpd.itTool || rpd.itTool2) ? WEAPON_ATTACK : UNARMED_ATTACK; + pHitEff->WhoHits=rpd.rc.H(); + pHitEff->HitAtSquare=lsqrHF; + + item* itHF=NULL; //keep the below order + if(!itHF)itHF = rpd.itTool ? rpd.itTool : NULL; + if(!itHF)itHF = rpd.itTool2 ? rpd.itTool2 : NULL; + if(!itHF)itHF = rpd.rc.H()->GetRightArm() ? rpd.rc.H()->GetRightArm() : NULL; + if(!itHF)itHF = rpd.rc.H()->GetLeftArm() ? rpd.rc.H()->GetLeftArm() : NULL; + if(itHF){ + pHitEff->lItemEffectReferenceID = itHF->GetID(); + lsqrHF->AddHitEffect(*pHitEff); + } + delete pHitEff; + } + } + if(rpd.bGradativeCraftOverride){ GradativeCraftOverride(rpd); }else{ @@ -3183,7 +3300,8 @@ void crafthandle::CraftWorkTurn(recipedata& rpd){ DBG1(rpd.iRemainingTurnsToFini * - one rock into smaller ones * - one organic */ -void crafthandle::GradativeCraftOverride(recipedata& rpd){DBGLN; +void crafthandle::GradativeCraftOverride(recipedata& rpd) +{DBGLN; if(rpd.ingredientsIDs.size()!=1) ABORT("incompatible gradative mode and ingredients count %lu",rpd.ingredientsIDs.size()); @@ -3247,9 +3365,11 @@ void crafthandle::GradativeCraftOverride(recipedata& rpd){DBGLN; if(spawnedVol==matMRemVol) DestroyIngredients(rpd); //cant be left with 0 volume - else - if(spawnedVol>matMRemVol) //the total calc being precise, and leaving a tiny remaining lump behind, this will never happen, just in case something is changed... - ABORT("lump volume would become negative (if not unsigned long) or 0 as %lu > %l",spawnedVol,matMRemVol); + else if(spawnedVol>matMRemVol) // Can happen with sol stone, TODO something about it but don't crash. + { + ADD_MESSAGE("Your work disappears in a puff of logic!"); + DestroyIngredients(rpd); + } else matM->SetVolume(matMRemVol-spawnedVol); //the remaining volume becomes the action control/limit @@ -3381,8 +3501,9 @@ void crafthandle::CheckFacilities(recipedata& rpd){ rpd.bFailedTerminateCancel=true; } - rpd.bOnlyXplodIfCriticalFumble=true; - rpd.v2XplodAt=rpd.v2WorkbenchLocation; + //TODO workbench should be damaged w/o explosions (that is area effect related to fire/forge) + rpd.bOnlyXplodIfCriticalFumble=true; //TODO kept as WIP + //explode/sparks at workbench doesnt make much sense: rpd.v2XplodAt=rpd.v2WorkbenchLocation; } } diff --git a/Main/Source/command.cpp b/Main/Source/command.cpp index 52b05c2ca..276c65a97 100644 --- a/Main/Source/command.cpp +++ b/Main/Source/command.cpp @@ -78,59 +78,60 @@ command* commandsystem::Command[] = { 0, - /* Sort according to description */ + /* Sort according to relaiton and assumed frequency of use */ + new command(&NOP, "wait a turn", '.', '.', '.', true), + new command(&Go, "go / fastwalk", 'g', 'g', 'g', false), + new command(&GoDown, "go down / enter area", '>', '>', '>', true), + new command(&GoUp, "go up", '<', '<', '<', true), + new command(&PickUp, "pick up item", ',', ',', ',', false), + new command(&Drop, "drop item", 'd', 'd', 'd', true), + new command(&Throw, "throw item", 't', 't', 't', false), + new command(&EquipmentScreen, "equipment menu", 'E', 'E', 'E', true), + new command(&ShowInventory, "inventory menu", 'i', 'i', 'i', true), new command(&Apply, "apply item", 'a', 'a', 'a', false), new command(&ApplyAgain, "apply last item again", 'A', 'A', 'A', false), - new command(&Talk, "chat", 'C', 'C', 'C', false), - new command(&Close, "close", 'c', 'c', 'c', false), - new command(&Craft, "craft", 'f', 'F', 'f', false), - new command(&Dip, "dip into liquid", '!', '!', '!', false), + new command(&Zap, "zap a wand", 'z', 'z', 'z', false), + new command(&Read, "read", 'r', 'r', 'r', false), + new command(&Eat, "eat", 'e', 'e', 'e', true), new command(&Drink, "drink liquid", 'D', 'D', 'D', true), new command(&Taste, "taste a bit of liquid", 'T', 'T', 'T', true), - new command(&Drop, "drop item", 'd', 'd', 'd', true), - new command(&Eat, "eat", 'e', 'e', 'e', true), - new command(&WhatToEngrave, "engrave message", 'G', 'G', 'G', false), - new command(&EquipmentScreen, "equipment menu", 'E', 'E', 'E', true), - new command(&Go, "go / fastwalk", 'g', 'g', 'g', false), - new command(&GoDown, "go down / enter area", '>', '>', '>', true), - new command(&GoUp, "go up", '<', '<', '<', true), - new command(&IssueCommand, "issue commands to team members", 'I', 'I', 'I', false), - new command(&Kick, "kick", 'k', 'K', 'K', false), + new command(&Dip, "dip into liquid", '!', '!', '!', false), + new command(&Open, "open", 'o', 'O', 'o', false), + new command(&Close, "close", 'c', 'c', 'c', false), + new command(&Search, "search", 's', 's', 's', false), new command(&Look, "look around", 'l', 'L', 'L', true), new command(&ShowMap, "show map", 'm', 'm', 'm', false), - new command(&AssignName , "name team members", 'n', 'n', 'N', false), + new command(&WhatToEngrave, "engrave / inscribe", 'G', 'G', 'G', false), + new command(&Talk, "chat", 'C', 'C', 'C', false), + new command(&Craft, "craft", 'f', 'F', 'f', false), + new command(&AssignName, "name team members", 'n', 'n', 'N', false), + new command(&IssueCommand, "issue commands to team members", 'I', 'I', 'I', false), new command(&Offer, "offer to gods", 'O', 'f', 'O', false), - new command(&Open, "open", 'o', 'O', 'o', false), - new command(&PickUp, "pick up item", ',', ',', ',', false), - new command(&Pray, "pray", 'p', 'p', 'p', false), - new command(&Quit, "quit and abandon", 'Q', 'Q', 'Q', true), - new command(&Read, "read", 'r', 'r', 'r', false), + new command(&Pray, "pray to gods", 'p', 'p', 'p', false), + new command(&Sit, "sit down", '_', '_', '_', false), new command(&Rest, "rest and heal", 'h', 'h', 'H', true), new command(&Save, "save and quit", 'S', 'S', 'S', true), + new command(&Quit, "quit and abandon", 'Q', 'Q', 'Q', true), + new command(&DrawMessageHistory, "show message history", 'M', 'M', 'M', true), new command(&ScrollMessagesDown, "scroll messages down", '+', '+', '+', true), new command(&ScrollMessagesUp, "scroll messages up", '-', '-', '-', true), new command(&ShowConfigScreen, "show options menu", '\\', '\\', '\\', true), - new command(&ShowInventory, "show inventory", 'i', 'i', 'i', true), new command(&ShowKeyLayout, "show key layout", '?', '?', '?', true), - new command(&DrawMessageHistory, "show message history", 'M', 'M', 'M', true), new command(&ShowWeaponSkills, "show weapon skills", '@', '@', '@', true), - new command(&Search, "search", 's', 's', 's', false), - new command(&Sit, "sit down", '_', '_', '_', false), + new command(&WieldInRightArm, "wield in right hand", 'w', 'w', 'w', true), + new command(&WieldInLeftArm, "wield in left hand", 'W', 'W', 'W', true), new command(&SwapWeapons, "swap weapons", 'x', 'x', 'x', false), new command(&SwapWeaponsCfg, "swapping menu", 'X', 'X', 'X', false), - new command(&Throw, "throw item", 't', 't', 't', false), new command(&ToggleRunning, "toggle running", 'u', 'U', 'U', true), + new command(&Kick, "kick", 'k', 'K', 'K', false), new command(&ForceVomit, "vomit", 'V', 'V', 'V', false), - new command(&NOP, "wait a turn", '.', '.', '.', true), - new command(&WieldInRightArm, "wield in right arm", 'w', 'w', 'w', true), - new command(&WieldInLeftArm, "wield in left arm", 'W', 'W', 'W', true), + #ifdef WIZARD - new command(&WizardMode, "wizard mode activation (Ctrl+ to access console commands)", '`', '`', '`', true), + new command(&WizardMode, "wizard mode activation (Ctrl+ for console)", '`', '`', '`', true), #else new command(&DevConsCmd, "access console commands", '`', '`', '`', true), //works w/o Ctrl in this case #endif - new command(&Zap, "zap wand", 'z', 'z', 'z', false), #ifdef WIZARD @@ -166,128 +167,75 @@ truth commandsystem::DevConsCmd(character* Char) } #endif -truth commandsystem::IsForRegionListItem(int iIndex){ //see code generator helper script prepareCmdsDescrCode.sh (use cygwin) - cchar* str = Command[iIndex]->GetDescription(); - if(strcmp(str,"apply")==0)return true; -// if(strcmp(str,"chat")==0)return true; -// if(strcmp(str,"close")==0)return true; - if(strcmp(str,"dip")==0)return true; - if(strcmp(str,"drink")==0)return true; - if(strcmp(str,"drop")==0)return true; - if(strcmp(str,"eat")==0)return true; - if(strcmp(str,"engrave")==0)return true; - if(strcmp(str,"equipment menu")==0)return true; -// if(strcmp(str,"go")==0)return true; -// if(strcmp(str,"go down/enter area")==0)return true; -// if(strcmp(str,"go up")==0)return true; -// if(strcmp(str,"issue command(s) to team member(s)")==0)return true; -// if(strcmp(str,"kick")==0)return true; -// if(strcmp(str,"look")==0)return true; -// if(strcmp(str,"name")==0)return true; - if(strcmp(str,"offer")==0)return true; - if(strcmp(str,"open")==0)return true; - if(strcmp(str,"pick up")==0)return true; - if(strcmp(str,"pray")==0)return true; -// if(strcmp(str,"quit")==0)return true; - if(strcmp(str,"read")==0)return true; -// if(strcmp(str,"rest/heal")==0)return true; -// if(strcmp(str,"save game")==0)return true; -// if(strcmp(str,"scroll messages down")==0)return true; -// if(strcmp(str,"scroll messages up")==0)return true; -// if(strcmp(str,"show config screen")==0)return true; -// if(strcmp(str,"show inventory")==0)return true; -// if(strcmp(str,"show key layout")==0)return true; -// if(strcmp(str,"show message history")==0)return true; -// if(strcmp(str,"show weapon skills")==0)return true; -// if(strcmp(str,"search")==0)return true; -// if(strcmp(str,"sit")==0)return true; -// if(strcmp(str,"swap weapons")==0)return true; -// if(strcmp(str,"swap weapons configuration")==0)return true; - if(strcmp(str,"throw")==0)return true; -// if(strcmp(str,"toggle running")==0)return true; -// if(strcmp(str,"vomit")==0)return true; -// if(strcmp(str,"wait")==0)return true; - if(strcmp(str,"wield in right arm")==0)return true; - if(strcmp(str,"wield in left arm")==0)return true; -// if(strcmp(str,"wizard mode activation")==0)return true; - if(strcmp(str,"zap")==0)return true; -// if(strcmp(str,"raise stats")==0)return true; -// if(strcmp(str,"lower stats")==0)return true; -// if(strcmp(str,"see whole map")==0)return true; -// if(strcmp(str,"toggle walk through walls mode")==0)return true; -// if(strcmp(str,"raise your relations to the gods")==0)return true; -// if(strcmp(str,"lower your relations to the gods")==0)return true; -// if(strcmp(str,"gain knowledge of all gods")==0)return true; -// if(strcmp(str,"gain all items")==0)return true; -// if(strcmp(str,"reveal secret knowledge")==0)return true; -// if(strcmp(str,"detach a limb")==0)return true; -// if(strcmp(str,"set fire to a limb")==0)return true; -// if(strcmp(str,"summon monster")==0)return true; -// if(strcmp(str,"level teleport")==0)return true; -// if(strcmp(str,"possess creature")==0)return true; - if(strcmp(str,"polymorph")==0)return true; +truth commandsystem::IsForRegionListItem(int iIndex){ + truth (*LinkedFunction)(character*) = Command[iIndex]->GetLinkedFunction(); + + static std::vector vLF; + static bool bInitDummy = [](){ //for easy maintenance avoiding macros + vLF.push_back(&Apply); + vLF.push_back(&Dip); + vLF.push_back(&Drink); + vLF.push_back(&Drop); + vLF.push_back(&Eat); + vLF.push_back(&WhatToEngrave); + vLF.push_back(&EquipmentScreen); + vLF.push_back(&Offer); + vLF.push_back(&Open); + vLF.push_back(&PickUp); + vLF.push_back(&Pray); + vLF.push_back(&Read); + vLF.push_back(&Throw); + vLF.push_back(&WieldInLeftArm); + vLF.push_back(&WieldInRightArm); + vLF.push_back(&Zap); +#ifdef WIZARD + vLF.push_back(&Polymorph); +#endif + return true; + }(); + for(int i=0;iGetDescription(); - if(strcmp(str,"apply")==0)return true; -// if(strcmp(str,"chat")==0)return true; -// if(strcmp(str,"close")==0)return true; - if(strcmp(str,"dip")==0)return true; - if(strcmp(str,"drink")==0)return true; - if(strcmp(str,"drop")==0)return true; - if(strcmp(str,"eat")==0)return true; - if(strcmp(str,"engrave")==0)return true; - if(strcmp(str,"equipment menu")==0)return true; -// if(strcmp(str,"go")==0)return true; -// if(strcmp(str,"go down/enter area")==0)return true; -// if(strcmp(str,"go up")==0)return true; -// if(strcmp(str,"issue command(s) to team member(s)")==0)return true; -// if(strcmp(str,"kick")==0)return true; -// if(strcmp(str,"look")==0)return true; -// if(strcmp(str,"name")==0)return true; - if(strcmp(str,"offer")==0)return true; - if(strcmp(str,"open")==0)return true; - if(strcmp(str,"pick up")==0)return true; - if(strcmp(str,"pray")==0)return true; -// if(strcmp(str,"quit")==0)return true; - if(strcmp(str,"read")==0)return true; -// if(strcmp(str,"rest/heal")==0)return true; -// if(strcmp(str,"save game")==0)return true; -// if(strcmp(str,"scroll messages down")==0)return true; -// if(strcmp(str,"scroll messages up")==0)return true; -// if(strcmp(str,"show config screen")==0)return true; - if(strcmp(str,"show inventory")==0)return true; -// if(strcmp(str,"show key layout")==0)return true; -// if(strcmp(str,"show message history")==0)return true; -// if(strcmp(str,"show weapon skills")==0)return true; -// if(strcmp(str,"search")==0)return true; -// if(strcmp(str,"sit")==0)return true; -// if(strcmp(str,"swap weapons")==0)return true; - if(strcmp(str,"swap weapons configuration")==0)return true; - if(strcmp(str,"throw")==0)return true; -// if(strcmp(str,"toggle running")==0)return true; -// if(strcmp(str,"vomit")==0)return true; -// if(strcmp(str,"wait")==0)return true; - if(strcmp(str,"wield in right arm")==0)return true; - if(strcmp(str,"wield in left arm")==0)return true; -// if(strcmp(str,"wizard mode activation")==0)return true; - if(strcmp(str,"zap")==0)return true; -// if(strcmp(str,"raise stats")==0)return true; -// if(strcmp(str,"lower stats")==0)return true; -// if(strcmp(str,"see whole map")==0)return true; -// if(strcmp(str,"toggle walk through walls mode")==0)return true; -// if(strcmp(str,"raise your relations to the gods")==0)return true; -// if(strcmp(str,"lower your relations to the gods")==0)return true; -// if(strcmp(str,"gain knowledge of all gods")==0)return true; -// if(strcmp(str,"gain all items")==0)return true; -// if(strcmp(str,"reveal secret knowledge")==0)return true; -// if(strcmp(str,"detach a limb")==0)return true; -// if(strcmp(str,"set fire to a limb")==0)return true; -// if(strcmp(str,"summon monster")==0)return true; -// if(strcmp(str,"level teleport")==0)return true; -// if(strcmp(str,"possess creature")==0)return true; - if(strcmp(str,"polymorph")==0)return true; + truth (*LinkedFunction)(character*) = Command[iIndex]->GetLinkedFunction(); + + static std::vector vLF; + static bool bInitDummy = [](){ //for easy maintenance avoiding macros + vLF.push_back(&Apply); + vLF.push_back(&Dip); + vLF.push_back(&Drink); + vLF.push_back(&Drop); + vLF.push_back(&Eat); + vLF.push_back(&WhatToEngrave); + vLF.push_back(&EquipmentScreen); + vLF.push_back(&Offer); + vLF.push_back(&Open); + vLF.push_back(&PickUp); + vLF.push_back(&Pray); + vLF.push_back(&Read); + vLF.push_back(&ShowInventory); + vLF.push_back(&SwapWeaponsCfg); + vLF.push_back(&Read); + vLF.push_back(&Throw); + vLF.push_back(&WieldInLeftArm); + vLF.push_back(&WieldInRightArm); + vLF.push_back(&Zap); +#ifdef WIZARD + vLF.push_back(&Polymorph); +#endif + return true; + }(); + for(int i=0;iClose(Char); else { - int Dir = game::DirectionQuestion(CONST_S("What do you wish to close? [press a direction key]"), false); + int Dir = game::DirectionQuestion(CONST_S("What do you wish to close? [press a direction key]"), false); if(Dir != DIR_ERROR && Char->GetArea()->IsValidPos(Char->GetPos() + game::GetMoveVector(Dir))) return Char->GetNearLSquare(Char->GetPos() + game::GetMoveVector(Dir))->Close(Char); @@ -524,7 +472,7 @@ truth commandsystem::Close(character* Char) } else { - int Dir = game::DirectionQuestion(CONST_S("What do you wish to close? [press a direction key]"), false); + int Dir = game::DirectionQuestion(CONST_S("What do you wish to close? [press a direction key]"), false); if(Dir != DIR_ERROR && Char->GetArea()->IsValidPos(Char->GetPos() + game::GetMoveVector(Dir))) return Char->GetNearLSquare(Char->GetPos() + game::GetMoveVector(Dir))->Close(Char); @@ -576,8 +524,12 @@ truth commandsystem::Drop(character* Char) for(uint c = 0; c < ToDrop.size(); ++c) { ToDrop[c]->MoveTo(Char->GetStackUnder()); - if(ivanconfig::IsAutoPickupThrownItems()) + if(ivanconfig::IsAutoPickupThrownItems()){ ToDrop[c]->ClearTag('t'); //throw: to avoid auto-pickup + if(game::IsAutoPickupMatch(ToDrop[c]->GetName(DEFINITE))){ + ToDrop[c]->SetTag('d'); //intentionally dropped: this will let user decide specific items to NOT auto-pickup regex matching + } + } } Success = true; } @@ -724,6 +676,10 @@ truth commandsystem::PickUp(character* Char) PileVector[0][c]->ResetFlyingThrownStep(); PileVector[0][c]->MoveTo(Char->GetStack()); + + if(game::IsAutoPickupMatch(PileVector[0][c]->GetName(DEFINITE))){ + PileVector[0][c]->ClearTag('d'); //intentionally drop tag dismissed for autopickup regex match + } } ADD_MESSAGE("%s picked up.", PileVector[0][0]->GetName(INDEFINITE, Amount).CStr()); @@ -766,6 +722,10 @@ truth commandsystem::PickUp(character* Char) ToPickup[c]->ResetFlyingThrownStep(); ToPickup[c]->MoveTo(Char->GetStack()); + + if(game::IsAutoPickupMatch(ToPickup[c]->GetName(DEFINITE))){ + ToPickup[c]->ClearTag('d'); //intentionally drop tag dismissed for autopickup regex match + } } ADD_MESSAGE("%s picked up.", ToPickup[0]->GetName(INDEFINITE, ToPickup.size()).CStr()); @@ -801,6 +761,8 @@ truth commandsystem::Quit(character* Char) truth commandsystem::Talk(character* Char) { + static char cmdKey = findCmdKey(&Talk); + if(!Char->CheckTalk()) return false; @@ -832,11 +794,15 @@ truth commandsystem::Talk(character* Char) return ToTalk->ChatMenu(); else { - int Dir = game::DirectionQuestion(CONST_S("To whom do you wish to talk? [press a direction key]"), false, true); + static festring fsQ; + static bool bInitDummy=[](){fsQ<<"To whom do you wish to talk? [press a direction key or '"<GetArea()->IsValidPos(Char->GetPos() + game::GetMoveVector(Dir))) return false; + iPreviousDirChosen = Dir; character* Dude = Char->GetNearSquare(Char->GetPos() + game::GetMoveVector(Dir))->GetCharacter(); if(Dude == Char) @@ -897,6 +863,13 @@ truth commandsystem::Read(character* Char) } item* Item = Char->GetStack()->DrawContents(Char, CONST_S("What do you want to read?"), 0, &item::IsReadable); + +#ifdef WIZARD + // stops auto question timeout that was preventing reading at all + if(Item && game::GetAutoPlayMode()) + game::DisableAutoPlayMode(); +#endif + return Item && Char->ReadItem(Item); } @@ -973,6 +946,47 @@ truth commandsystem::ShowKeyLayout(character*) List.AddDescription(CONST_S("Key Description")); festring Buffer; + // Movement keys + // TODO: Better way to handle F1 help text. Needs to be assigned only to the first + // line because this is non-selectable list, but unfortunately has to be assigned + // immediately after setting the first line through SetLastEntryHelp() + switch(ivanconfig::GetDirectionKeyMap()) + { + case DIR_NORM: // Normal + { + List.AddEntry(CONST_S("789 movement (normal)"), LIGHT_GRAY); + List.SetLastEntryHelp(festring() << "IVAN uses most of the keyboard for command key bindings, though some " + << "commands are only accessible in wizard mode. Note that the game " + << "distinguishes between lowercase and uppercase letters, so if you are " + << "experiencing troubles, first check whether you don't have active CapsLock."); + List.AddEntry(CONST_S("4 6 or use arrow keys and"), LIGHT_GRAY); + List.AddEntry(CONST_S("123 Home, End, PgUp, PgDn"), LIGHT_GRAY); + break; + } + case DIR_ALT: // Alternative + { + List.AddEntry(CONST_S("789 movement (alternative)"), LIGHT_GRAY); + List.SetLastEntryHelp(festring() << "IVAN uses most of the keyboard for command key bindings, though some " + << "commands are only accessible in wizard mode. Note that the game " + << "distinguishes between lowercase and uppercase letters, so if you are " + << "experiencing troubles, first check whether you don't have active CapsLock."); + List.AddEntry(CONST_S("u o"), LIGHT_GRAY); + List.AddEntry(CONST_S("jkl"), LIGHT_GRAY); + break; + } + case DIR_HACK: // Nethack + { + List.AddEntry(CONST_S("yku movement (NetHack)"), LIGHT_GRAY); + List.SetLastEntryHelp(festring() << "IVAN uses most of the keyboard for command key bindings, though some " + << "commands are only accessible in wizard mode. Note that the game " + << "distinguishes between lowercase and uppercase letters, so if you are " + << "experiencing troubles, first check whether you don't have active CapsLock."); + List.AddEntry(CONST_S("h l"), LIGHT_GRAY); + List.AddEntry(CONST_S("bjn"), LIGHT_GRAY); + break; + } + } + for(int c = 1; GetCommand(c); ++c) if(!GetCommand(c)->IsWizardModeFunction()) { @@ -1026,9 +1040,9 @@ truth commandsystem::Look(character* Char) } if(!game::IsInWilderness()) - Msg = CONST_S("Direction keys move cursor, ESC exits, 'i' examines items, 'c' examines a character."); + Msg = CONST_S("Direction keys move cursor; examine (i)tems or a (c)haracter; ESC exits."); else - Msg = CONST_S("Direction keys move cursor, ESC exits, 'c' examines a character."); + Msg = CONST_S("Direction keys move cursor; examine a (c)haracter; ESC exits."); v2 pos = Char->GetPosSafely(); if(pos.Is0())pos = game::GetCamera()+v2(game::GetScreenXSize(),game::GetScreenYSize())/2; // gum: this may happen if player died, the probably position is around screen center, if it is not good enough just deny it and add a log message saying unable to. @@ -1053,8 +1067,7 @@ truth commandsystem::WhatToEngrave(character* Char,bool bEngraveMapNote,v2 v2Eng while(!(Key == KEY_ESC || Key == ' ')) { if(!bEngraveMapNote) - Key = game::AskForKeyPress(CONST_S("Where do you want to engrave? " - "'.' square, 'i' inventory, ESC exits")); + Key = game::AskForKeyPress(CONST_S("Do you want to (.) engrave a square, or inscribe an (i)tem? ['.' or 'i', ESC exits]")); int iLSqrLimit=80; if(bEngraveMapNote) @@ -1062,18 +1075,35 @@ truth commandsystem::WhatToEngrave(character* Char,bool bEngraveMapNote,v2 v2Eng festring What; lsquare* lsqrN = game::GetCurrentLevel()->GetLSquare(v2EngraveMapNotePos); - if(lsqrN!=NULL){ //TODO can this be NULL? + if(lsqrN!=NULL){ //TODO can this ever be NULL? if(lsqrN->GetEngraved()!=NULL){ cchar* c = lsqrN->GetEngraved(); - if(c!=NULL)What=c; - if(What.GetSize()>0 && What[0]==game::MapNoteToken()){ - std::string str=What.CStr();What.Empty();What<0){ + if(What[0]==game::MapNoteToken()){ //having map note token means it is already a map note, so let it be read/write at will + std::string str=What.CStr(); + What.Empty(); + What<HasBeenSeen()){ + /***** + * why empty it? + * to prevent determining (cheating) if that square has a golemn using the ShowMap/lookMode command! (possibly other things too) + * a minor problem is that the user may overwrite that engraving with a mapnote TODO something about it? + */ + What.Empty(); + } + } + } } } } if(game::StringQuestion(What, CONST_S("Write your map note (optionally position mouse cursor over it before editing):"), WHITE, 0, iLSqrLimit, true) == NORMAL_EXIT){ - game::SetMapNote(lsqrN,What); + if(What.GetSize()>0) { + game::SetMapNote(lsqrN,What); + } } break; @@ -1143,8 +1173,9 @@ truth commandsystem::Pray(character* Char) { desc.Empty(); desc << game::GetGod(c)->GetCompleteDescription(); - if(ivanconfig::IsShowGodInfo())desc << " ("<GetDescription()<<")"; + if(ivanconfig::IsShowGodInfo())desc << " " << game::GetGod(c)->GetLastKnownRelation(); Panthenon.AddEntry(desc, LIGHT_GRAY, 20, c); + Panthenon.SetLastEntryHelp(festring() << game::GetGod(c)->GetName() << ", the " << game::GetGod(c)->GetDescription()); Known[Index++] = c; } } @@ -1152,6 +1183,7 @@ truth commandsystem::Pray(character* Char) if(game::GetGod(DivineMaster)->IsKnown()) { Panthenon.AddEntry(game::GetGod(DivineMaster)->GetCompleteDescription(), LIGHT_GRAY, 20, DivineMaster); + Panthenon.SetLastEntryHelp(festring() << game::GetGod(DivineMaster)->GetName() << ", the " << game::GetGod(DivineMaster)->GetDescription()); Known[0] = DivineMaster; } else @@ -1236,8 +1268,8 @@ truth commandsystem::Kick(character* Char) return true; } - festring fsQ; - fsQ<<"In what direction do you wish to kick? [press a direction key or '"<EditNP(-50); Char->DexterityAction(5); if(ivanconfig::IsAutoPickupThrownItems()) - Item->SetTag('t'); + if(Item->IsWeapon(PLAYER)) //TODO never made much sense auto-picking up other things than weapons, but this could be optional + Item->SetTag('t'); return true; } else @@ -1583,7 +1616,7 @@ truth commandsystem::ShowMapWork(character* Char,v2* pv2ChoseLocation) if(bChoseLocationMode) key='l'; else - key = game::KeyQuestion(CONST_S("Cartography notes action: (t)oggle, (e)dit/add, (l)ook mode, (r)otate, (d)elete. (F1 help)"), //TODO KeyQuestion() should detect F1 and return a default answer, currently F1 will just override any other key press + key = game::KeyQuestion(CONST_S("Cartography notes action: (t)oggle, (e)dit/add, (l)ook mode, (r)otate, (d)elete. [press F1 for help]"), //TODO KeyQuestion() should detect F1 and return a default answer, currently F1 will just override any other key press KEY_ESC, 5, 't', 'l', 'r', 'd', 'e'); if(specialkeys::IsRequestedEvent(specialkeys::FocusedElementHelp)){ @@ -1623,6 +1656,14 @@ truth commandsystem::ShowMapWork(character* Char,v2* pv2ChoseLocation) if(start.Is0()) start=Char->GetPos(); + if(!game::GetCurrentArea()->IsValidPos(start)){ + // very rare case when opening the map will crash at game::PositionQuestion(,start,...) ... area::GetSquare(start) + DBG4("CrashWorkaround",DBGAV2(start),game::GetCurrentArea()->GetXSize(),game::GetCurrentArea()->GetYSize()); + DBGBREAKPOINT; + game::ToggleDrawMapOverlay(); //TODO hint something to the player like internal error was avoided and should just retry? + return false; + } + noteAddPos = game::PositionQuestion(fsMsg, start, NULL, NULL, true); DBGSV2(noteAddPos); if(noteAddPos==ERROR_V2){ game::ToggleDrawMapOverlay(); @@ -1764,6 +1805,7 @@ truth commandsystem::Go(character* Char) truth commandsystem::ShowConfigScreen(character*) { + configsystem::Load(); // some fields may be too big to be edited from the game interface ex.: autopickup regex would require multiline editing otherwise would not work. TODO should this be optional? ivanconfig::Show(); return false; } @@ -1831,6 +1873,10 @@ truth commandsystem::ShowWeaponSkills(character* Char) List.AddEntry(Buffer, LIGHT_GRAY); Something = true; + + List.SetLastEntryHelp(festring() << "Your proficiency with individual weapon categories and accustomization " + << "with the currently wielded weapons. Note that should you neglect training " + << "your skills for too long, they might start slowly decreasing in level."); } } @@ -1902,6 +1948,30 @@ truth commandsystem::WizardMode(character* Char) game::GetWorldMap()->GetWSquare(XinrochTombPos)->ChangeOWTerrain(xinrochtomb::Spawn()); game::GetWorldMap()->RevealEnvironment(XinrochTombPos, 1); + v2 AslonaPos = game::GetWorldMap()->GetEntryPos(0, ASLONA_CASTLE); + game::GetWorldMap()->GetWSquare(AslonaPos)->ChangeOWTerrain(aslonacastle::Spawn()); + game::GetWorldMap()->RevealEnvironment(AslonaPos, 1); + + v2 RebelPos = game::GetWorldMap()->GetEntryPos(0, REBEL_CAMP); + game::GetWorldMap()->GetWSquare(RebelPos)->ChangeOWTerrain(rebelcamp::Spawn()); + game::GetWorldMap()->RevealEnvironment(RebelPos, 1); + + v2 GoblinFortPos = game::GetWorldMap()->GetEntryPos(0, GOBLIN_FORT); + game::GetWorldMap()->GetWSquare(GoblinFortPos)->ChangeOWTerrain(goblinfort::Spawn()); + game::GetWorldMap()->RevealEnvironment(GoblinFortPos, 1); + + v2 FungalCavePos = game::GetWorldMap()->GetEntryPos(0, FUNGAL_CAVE); + game::GetWorldMap()->GetWSquare(FungalCavePos)->ChangeOWTerrain(fungalcave::Spawn()); + game::GetWorldMap()->RevealEnvironment(FungalCavePos, 1); + + v2 PyramidPos = game::GetWorldMap()->GetEntryPos(0, PYRAMID); + game::GetWorldMap()->GetWSquare(PyramidPos)->ChangeOWTerrain(pyramid::Spawn()); + game::GetWorldMap()->RevealEnvironment(PyramidPos, 1); + + v2 BlackMarketPos = game::GetWorldMap()->GetEntryPos(0, BLACK_MARKET); + game::GetWorldMap()->GetWSquare(BlackMarketPos)->ChangeOWTerrain(blackmarket::Spawn()); + game::GetWorldMap()->RevealEnvironment(BlackMarketPos, 1); + game::GetWorldMap()->SendNewDrawRequest(); } else @@ -1915,6 +1985,30 @@ truth commandsystem::WizardMode(character* Char) game::GetWorldMap()->GetWSquare(XinrochTombPos)->ChangeOWTerrain(xinrochtomb::Spawn()); game::GetWorldMap()->RevealEnvironment(XinrochTombPos, 1); + v2 AslonaPos = game::GetWorldMap()->GetEntryPos(0, ASLONA_CASTLE); + game::GetWorldMap()->GetWSquare(AslonaPos)->ChangeOWTerrain(aslonacastle::Spawn()); + game::GetWorldMap()->RevealEnvironment(AslonaPos, 1); + + v2 RebelPos = game::GetWorldMap()->GetEntryPos(0, REBEL_CAMP); + game::GetWorldMap()->GetWSquare(RebelPos)->ChangeOWTerrain(rebelcamp::Spawn()); + game::GetWorldMap()->RevealEnvironment(RebelPos, 1); + + v2 GoblinFortPos = game::GetWorldMap()->GetEntryPos(0, GOBLIN_FORT); + game::GetWorldMap()->GetWSquare(GoblinFortPos)->ChangeOWTerrain(goblinfort::Spawn()); + game::GetWorldMap()->RevealEnvironment(GoblinFortPos, 1); + + v2 FungalCavePos = game::GetWorldMap()->GetEntryPos(0, FUNGAL_CAVE); + game::GetWorldMap()->GetWSquare(FungalCavePos)->ChangeOWTerrain(fungalcave::Spawn()); + game::GetWorldMap()->RevealEnvironment(FungalCavePos, 1); + + v2 PyramidPos = game::GetWorldMap()->GetEntryPos(0, PYRAMID); + game::GetWorldMap()->GetWSquare(PyramidPos)->ChangeOWTerrain(pyramid::Spawn()); + game::GetWorldMap()->RevealEnvironment(PyramidPos, 1); + + v2 BlackMarketPos = game::GetWorldMap()->GetEntryPos(0, BLACK_MARKET); + game::GetWorldMap()->GetWSquare(BlackMarketPos)->ChangeOWTerrain(blackmarket::Spawn()); + game::GetWorldMap()->RevealEnvironment(BlackMarketPos, 1); + game::SaveWorldMap(); } @@ -2038,7 +2132,7 @@ truth commandsystem::SecretKnowledge(character* Char) switch(Chosen) { case 0: - List.AddDescription(CONST_S(" AS LS DX AG EN PE IN WI CH MA")); + List.AddDescription(CONST_S(" AS LS DX AG EN PE IN WS WL CH MA")); for(c = 0; c < Character.size(); ++c) { @@ -2297,3 +2391,8 @@ truth commandsystem::IssueCommand(character* Char) return game::CommandQuestion(); } + +void commandsystem::PlayerDiedWeaponSkills() +{ + commandsystem::ShowWeaponSkills(PLAYER); +} diff --git a/Main/Source/database.cpp b/Main/Source/database.cpp index 3f2eaa97f..bfdb65101 100644 --- a/Main/Source/database.cpp +++ b/Main/Source/database.cpp @@ -505,6 +505,7 @@ template<> void databasecreator::CreateDataBaseMemberMap() ADD_MEMBER(GhostCopyMaterials); ADD_MEMBER(CanBeGeneratedOnlyInTheCatacombs); ADD_MEMBER(IsAlcoholic); + ADD_MEMBER(IsUndead); ADD_MEMBER(IsImmuneToWhipOfThievery); ADD_MEMBER(AllowedDungeons); } @@ -594,6 +595,7 @@ template<> void databasecreator::CreateDataBaseMemberMap() ADD_MEMBER(WieldedBitmapPos); ADD_MEMBER(IsQuestItem); ADD_MEMBER(IsGoodWithPlants); + ADD_MEMBER(IsGoodWithUndead); ADD_MEMBER(CreateLockConfigurations); ADD_MEMBER(CanBePickedUp); ADD_MEMBER(CoverPercentile); @@ -620,6 +622,7 @@ template<> void databasecreator::CreateDataBaseMemberMap() ADD_MEMBER(BreakMsg); ADD_MEMBER(IsSadistWeapon); ADD_MEMBER(AllowedDungeons); + ADD_MEMBER(DescriptiveInfo); } template @@ -743,6 +746,7 @@ template<> void databasecreator::CreateDataBaseMemberMap() ADD_MEMBER(StepInWisdomLimit); ADD_MEMBER(RustModifier); ADD_MEMBER(Acidicity); + ADD_MEMBER(Hotness); ADD_MEMBER(NaturalForm); ADD_MEMBER(HardenedMaterial); ADD_MEMBER(SoftenedMaterial); diff --git a/Main/Source/dungeon.cpp b/Main/Source/dungeon.cpp index a4068687b..c1b4869fe 100644 --- a/Main/Source/dungeon.cpp +++ b/Main/Source/dungeon.cpp @@ -248,7 +248,7 @@ festring dungeon::GetShortLevelDescription(int I) if(GetLevel(I)->GetLevelScript()->GetShortDescription()) return *GetLevel(I)->GetLevelScript()->GetShortDescription(); else - return *DungeonScript->GetShortDescription() + " level " + (I + 1); + return *DungeonScript->GetShortDescription() + " lvl " + (I + 1); } outputfile& operator<<(outputfile& SaveFile, const dungeon* Dungeon) diff --git a/Main/Source/game.cpp b/Main/Source/game.cpp index e0d22b983..ab3b2ad4b 100644 --- a/Main/Source/game.cpp +++ b/Main/Source/game.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #if defined(UNIX) || defined(__DJGPP__) #include @@ -70,7 +71,7 @@ #include "dbgmsgproj.h" -#define SAVE_FILE_VERSION 133 // Increment this if changes make savefiles incompatible +#define SAVE_FILE_VERSION 134 // Increment this if changes make savefiles incompatible #define BONE_FILE_VERSION 118 // Increment this if changes make bonefiles incompatible #define LOADED 0 @@ -99,9 +100,13 @@ double game::AveragePlayerAgilityExperience; int game::Teams; int game::Dungeons; int game::StoryState; +int game::GloomyCaveStoryState; int game::XinrochTombStoryState; int game::FreedomStoryState; +int game::AslonaStoryState; +int game::RebelStoryState; truth game::PlayerIsChampion; +truth game::HasBoat; massacremap game::PlayerMassacreMap; massacremap game::PetMassacreMap; massacremap game::MiscMassacreMap; @@ -850,9 +855,13 @@ truth game::Init(cfestring& loadBaseName) Turn = 0; InitPlayerAttributeAverage(); StoryState = 0; + GloomyCaveStoryState = 0; XinrochTombStoryState = 0; FreedomStoryState = 0; + AslonaStoryState = 0; + RebelStoryState = 0; PlayerIsChampion = false; + HasBoat = false; PlayerMassacreMap.clear(); PetMassacreMap.clear(); MiscMassacreMap.clear(); @@ -863,10 +872,14 @@ truth game::Init(cfestring& loadBaseName) DefaultChangeMaterial.Empty(); DefaultDetectMaterial.Empty(); Player->GetStack()->AddItem(encryptedscroll::Spawn()); - character* Doggie = dog::Spawn(); - Doggie->SetTeam(GetTeam(0)); - GetWorldMap()->GetPlayerGroup().push_back(Doggie); - Doggie->SetAssignedName(ivanconfig::GetDefaultPetName()); + + if(!ivanconfig::GetNoPet()) + { + character* Doggie = dog::Spawn(); + Doggie->SetTeam(GetTeam(0)); + GetWorldMap()->GetPlayerGroup().push_back(Doggie); + Doggie->SetAssignedName(ivanconfig::GetDefaultPetName()); + } WizardMode = false; SeeWholeMapCheatMode = MAP_HIDDEN; GoThroughWallsCheat = false; @@ -1310,32 +1323,85 @@ int game::RotateMapNotes() return iMapNotesRotation; } -int game::CheckAutoPickup(square* sqr) +std::vector afsAutoPickupMatch; +pcre *reAutoPickup=NULL; +void game::UpdateAutoPickUpMatching() //simple matching syntax { - if(!ivanconfig::IsAutoPickupThrownItems()) - return false; + afsAutoPickupMatch.clear(); + + bool bSimple=false; + if(bSimple){ //TODO just drop the simple code? or start the string with something to let it be used instead of regex? tho is cool to let ppl learn regex :) + if(ivanconfig::GetAutoPickUpMatching().GetSize()==0 || ivanconfig::GetAutoPickUpMatching()[0]=='!')return; + std::stringstream ss(ivanconfig::GetAutoPickUpMatching().CStr()); + std::string match; + while(std::getline(ss,match,'|')) + afsAutoPickupMatch.push_back(festring(match.c_str())); + }else{ + //TODO test regex about: ignoring broken lanterns and bottles, ignore sticks on fire but pickup scrolls on fire + // static bool bDummyInit = [](){reAutoPickup=NULL;return true;}(); + const char *errMsg; + int iErrOffset; + if(reAutoPickup)pcre_free(reAutoPickup); + reAutoPickup = pcre_compile( + ivanconfig::GetAutoPickUpMatching().CStr(), //pattern + 0, //no options + &errMsg, &iErrOffset, + 0); // default char tables + if (!reAutoPickup){ + std::vector afsFullProblems; + afsFullProblems.push_back(festring(errMsg)); + afsFullProblems.push_back(festring()+"offset:"+iErrOffset); + bool bDummy = iosystem::AlertConfirmMsg("regex validation failed, if ignored will just not work at all",afsFullProblems,false); + } + } +} +bool game::IsAutoPickupMatch(cfestring fsName) { + return pcre_exec(reAutoPickup, 0, fsName.CStr(), fsName.GetSize(), 0, 0, NULL, 0) >= 0; +} +int game::CheckAutoPickup(square* sqr) +{ if(sqr==NULL) sqr = PLAYER->GetSquareUnder(); if(dynamic_cast(sqr)==NULL) - return false; + return 0; lsquare* lsqr = (lsquare*)sqr; + static bool bDummyInit = [](){UpdateAutoPickUpMatching();return true;}(); itemvector iv; lsqr->GetStack()->FillItemVector(iv); - int j=0; + int iTot=0; for(int i=0;iHasTag('t')){ //throw + if(it->GetRoom() && it->GetRoom()->GetMaster())continue; //not from owned rooms + if(it->GetSpoilLevel()>0)continue; + bool b=false; + if(!b && ivanconfig::IsAutoPickupThrownItems() && it->HasTag('t') )b=true; //was thrown + if(!b && !it->HasTag('d')){ + if(reAutoPickup!=NULL){ + if(IsAutoPickupMatch(it->GetName(DEFINITE))){ + b=true; + } + } + } + if(!b){ //TODO use player's perception, in case of a stack of items, to allow random pickup based on item volume (size) where smaller = harder like tiny rings, to compensate for the easiness of not losing a round having to pick up the item interactively + for(int i=0;iGetNameSingular().Find(afsAutoPickupMatch[i].CStr(),0) != festring::NPos){ + b=true; + break; //each simple match loop + } + } + } + if(b){ it->MoveTo(PLAYER->GetStack()); ADD_MESSAGE("%s picked up.", it->GetName(INDEFINITE).CStr()); - j++; + iTot++; } } - return j; + return iTot; } bool game::CheckAddAutoMapNote(square* sqr) @@ -1363,9 +1429,12 @@ bool game::CheckAddAutoMapNote(square* sqr) if( dynamic_cast(olt)!=NULL || dynamic_cast(olt)!=NULL || + dynamic_cast(olt)!=NULL || //TODO exclude cathedral? dynamic_cast(olt)!=NULL || dynamic_cast(olt)!=NULL || olt->GetConfig() == ANVIL || + olt->GetConfig() == DOUBLE_BED || + olt->GetConfig() == CHAIR || olt->GetConfig() == FORGE || olt->GetConfig() == WORK_BENCH || false @@ -3170,7 +3239,8 @@ void game::UpdateShowItemsAtPos(bool bAllowed,v2 v2AtPos){ }else if(v2AbsLevelSqrPos.Y >= (GetCurrentArea()->GetYSize() - iNearEC)){ //bottom edge bNearEC=true;iCycleCodeFallBack=4; //top right horiz } - if(bNearEC)bAboveHead=true; + if(bNearEC && iCode==1) + bAboveHead=true; if(bDynamic && bAboveHead && !bPositionQuestionMode){ // will not be above head in bPositionQuestionMode v2 v2Chk; //(v2AbsLevelSqrPos.X,v2AbsLevelSqrPos.Y-1); @@ -3355,8 +3425,9 @@ truth game::Save(cfestring& SaveName) SaveFile << AveragePlayerLegStrengthExperience; SaveFile << AveragePlayerDexterityExperience; SaveFile << AveragePlayerAgilityExperience; - SaveFile << Teams << Dungeons << StoryState << PlayerRunning << XinrochTombStoryState; - SaveFile << FreedomStoryState << PlayerIsChampion; + SaveFile << Teams << Dungeons << StoryState << GloomyCaveStoryState; + SaveFile << XinrochTombStoryState << FreedomStoryState << AslonaStoryState << RebelStoryState; + SaveFile << PlayerIsChampion << HasBoat << PlayerRunning; SaveFile << PlayerMassacreMap << PetMassacreMap << MiscMassacreMap; SaveFile << PlayerMassacreAmount << PetMassacreAmount << MiscMassacreAmount; SaveArray(SaveFile, EquipmentMemory, MAX_EQUIPMENT_SLOTS); @@ -3433,8 +3504,9 @@ int game::Load(cfestring& saveName) SaveFile >> AveragePlayerLegStrengthExperience; SaveFile >> AveragePlayerDexterityExperience; SaveFile >> AveragePlayerAgilityExperience; - SaveFile >> Teams >> Dungeons >> StoryState >> PlayerRunning >> XinrochTombStoryState; - SaveFile >> FreedomStoryState >> PlayerIsChampion; + SaveFile >> Teams >> Dungeons >> StoryState >> GloomyCaveStoryState; + SaveFile >> XinrochTombStoryState >> FreedomStoryState >> AslonaStoryState >> RebelStoryState; + SaveFile >> PlayerIsChampion >> HasBoat >> PlayerRunning; SaveFile >> PlayerMassacreMap >> PetMassacreMap >> MiscMassacreMap; SaveFile >> PlayerMassacreAmount >> PetMassacreAmount >> MiscMassacreAmount; LoadArray(SaveFile, EquipmentMemory, MAX_EQUIPMENT_SLOTS); @@ -4735,6 +4807,9 @@ void game::EnterArea(charactervector& Group, int Area, int EntryIndex) lsqr->KickAnyoneStandingHereAway(); Player->PutToOrNear(Pos); + + game::CheckAddAutoMapNote(); + game::CheckAutoPickup(); } else { @@ -4765,14 +4840,25 @@ void game::EnterArea(charactervector& Group, int Area, int EntryIndex) /* Gum solution! */ - if(New && CurrentDungeonIndex == ATTNAM && Area == 0) + if(New && GetCurrentLevel()->IsOnGround() && + CurrentDungeonIndex == ATTNAM) { GlobalRainLiquid = powder::Spawn(SNOW); GlobalRainSpeed = v2(-64, 128); CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed); } - if(New && CurrentDungeonIndex == NEW_ATTNAM && Area == 0) + if(New && GetCurrentLevel()->IsOnGround() && CurrentDungeonIndex == XINROCH_TOMB) + { + GlobalRainLiquid = powder::Spawn(SOOT); + GlobalRainSpeed = v2(-64, 128); + CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed); + } + + if(New && GetCurrentLevel()->IsOnGround() && + (CurrentDungeonIndex == NEW_ATTNAM || CurrentDungeonIndex == ASLONA_CASTLE || + CurrentDungeonIndex == REBEL_CAMP || CurrentDungeonIndex == MONDEDR || + CurrentDungeonIndex == DARK_FOREST || CurrentDungeonIndex == IRINOX)) { GlobalRainLiquid = liquid::Spawn(WATER); GlobalRainSpeed = v2(256, 512); @@ -5232,7 +5318,7 @@ int game::GetLevels() void game::SignalDeath(ccharacter* Ghost, ccharacter* Murderer, festring DeathMsg) { if(InWilderness) - DeathMsg << " in the world map"; + DeathMsg << " in the wilderness"; else DeathMsg << " in " << GetCurrentDungeon()->GetLevelDescription(CurrentLevelIndex); @@ -6009,7 +6095,7 @@ truth game::EndSumoWrestling(int Result) PlayerSumoChampion = true; character* Sumo = GetSumo(); festring Msg = Sumo->GetName(DEFINITE) + " seems humbler than before. \"Darn. You bested me.\n"; - Msg << "Here's a little something as a reward\", " << Sumo->GetPersonalPronoun() + Msg << "Here's a little something as a reward,\" " << Sumo->GetPersonalPronoun() << " says and hands you a belt of levitation.\n\""; (belt::Spawn(BELT_OF_LEVITATION))->MoveTo(Player->GetStack()); Msg << "Allow me to also teach you a few nasty martial art tricks the years have taught me.\""; @@ -6200,6 +6286,9 @@ truth game::PolymorphControlKeyHandler(int Key, festring& String) Entry << " (" << Req << ')'; int Int = Player->GetAttribute(INTELLIGENCE); List.AddEntry(Entry, Req > Int ? RED : LIGHT_GRAY, 0, c); + List.SetLastEntryHelp(festring() << "You can only intentionally polymorph into creatures that you have already " + << "encountered during your adventure. In addition to that, you need a certain" + << "amount of Intelligence to successfully control the transformation."); } } diff --git a/Main/Source/gear.cpp b/Main/Source/gear.cpp index a38ebc203..48642ae6a 100644 --- a/Main/Source/gear.cpp +++ b/Main/Source/gear.cpp @@ -79,7 +79,6 @@ truth cloak::IsShadowVeil() const long boot::GetPrice() const { return armor::GetPrice() / 5 + GetEnchantedPrice(Enchantment); } truth boot::IsInCorrectSlot(int I) const { return I == RIGHT_BOOT_INDEX || I == LEFT_BOOT_INDEX; } -truth boot::IsKicking() const { return (GetConfig() == BOOT_OF_KICKING); } long gauntlet::GetPrice() const { return armor::GetPrice() / 3 + GetEnchantedPrice(Enchantment); } truth gauntlet::IsInCorrectSlot(int I) const { return I == RIGHT_GAUNTLET_INDEX || I == LEFT_GAUNTLET_INDEX; } @@ -609,7 +608,8 @@ truth thunderhammer::HitEffect(character* Enemy, character* Hitter, v2 HitPos, BEAM_LIGHTNING, Direction, 4, - 0 + 0, + this ); GetLevel()->LightningBeam(Beam); @@ -1233,7 +1233,8 @@ truth terrorscythe::HitEffect(character* Enemy, character* Hitter, v2 HitPos, { truth BaseSuccess = meleeweapon::HitEffect(Enemy, Hitter, HitPos, BodyPartIndex, Direction, BlockedByArmour); - if(!IsBroken() && Enemy->IsEnabled() && !(RAND() % 2)) + if(!IsBroken() && Enemy->IsEnabled() && !Enemy->TemporaryStateIsActivated(PANIC) && + Enemy->GetPanicLevel() > (RAND() % (50 - Min(Hitter->GetAttribute(MANA), 49)))) { if(Hitter) { @@ -1270,6 +1271,7 @@ truth bansheesickle::HitEffect(character* Enemy, character* Hitter, v2 HitPos, if(Enemy->IsPlayer() || Enemy->CanBeSeenByPlayer()) ADD_MESSAGE("The sickle shrieks at %s.", Enemy->CHAR_DESCRIPTION(DEFINITE)); } + game::CallForAttention(Enemy->GetPos(), 100); return Enemy->ReceiveBodyPartDamage(Hitter, 4 + (RAND() & 4), SOUND, BodyPartIndex, Direction) || BaseSuccess; } @@ -1338,7 +1340,7 @@ truth sharpaxe::HitEffect(character* Enemy, character* Hitter, v2 HitPos, { truth BaseSuccess = meleeweapon::HitEffect(Enemy, Hitter, HitPos, BodyPartIndex, Direction, BlockedByArmour); - if(!IsBroken() && Enemy->IsEnabled() && Enemy->IsHumanoid() && !(Enemy->IsUnique())) + if(!IsBroken() && Enemy->IsEnabled() && Enemy->IsHumanoid() && (!Enemy->IsUnique() || Enemy->IsPlayer())) { bodypart* ToBeSevered = Enemy->GetBodyPart(BodyPartIndex); @@ -1391,7 +1393,8 @@ truth taiaha::Zap(character* Zapper, v2, int Direction) BEAM_STRIKE, Direction, 15, - 0 + 0, + this ); (GetLevel()->*level::GetBeam(PARTICLE_BEAM))(Beam); @@ -1409,15 +1412,15 @@ void taiaha::AddInventoryEntry(ccharacter* Viewer, festring& Entry, int, truth S if(ivanconfig::IsShowVolume()) Entry << " " << GetVolume() << "cm3"; Entry << ", DAM " << GetBaseMinDamage() << '-' << GetBaseMaxDamage(); - Entry << ", " << GetBaseToHitValueDescription(); + Entry << ", " << GetBaseToHitValueDescription(); - if(!IsBroken() && !IsWhip()) + if(!IsBroken() && !IsWhip()) Entry << ", " << GetStrengthValueDescription(); - int CWeaponSkillLevel = Viewer->GetCWeaponSkillLevel(this); + int CWeaponSkillLevel = Viewer->GetCWeaponSkillLevel(this); int SWeaponSkillLevel = Viewer->GetSWeaponSkillLevel(this); - if(CWeaponSkillLevel || SWeaponSkillLevel) + if(CWeaponSkillLevel || SWeaponSkillLevel) Entry << ", skill " << CWeaponSkillLevel << '/' << SWeaponSkillLevel; if(TimesUsed == 1) @@ -1433,7 +1436,7 @@ void taiaha::Save(outputfile& SaveFile) const { item::Save(SaveFile); SaveFile << TimesUsed << Charges; - SaveFile << Enchantment; + SaveFile << Enchantment; SaveFile << SecondaryMaterial; } @@ -1441,7 +1444,7 @@ void taiaha::Load(inputfile& SaveFile) { item::Load(SaveFile); SaveFile >> TimesUsed >> Charges; - SaveFile >> Enchantment; + SaveFile >> Enchantment; LoadMaterial(SaveFile, SecondaryMaterial); } @@ -1449,7 +1452,7 @@ void taiaha::PostConstruct() { Charges = GetMinCharges() + RAND() % (GetMaxCharges() - GetMinCharges() + 1); TimesUsed = 0; - meleeweapon::PostConstruct(); + meleeweapon::PostConstruct(); } alpha taiaha::GetOutlineAlpha(int Frame) const @@ -1631,24 +1634,37 @@ void unpick::FinalProcessForBone() LastUsed = 0; } +int unpick::GetCooldown(int BaseCooldown, character* User) +{ + int Attribute = User->GetAttribute(MANA); + + if(Attribute > 1) + { + return BaseCooldown / log10(Attribute); + } + + return BaseCooldown / 0.20; +} + truth unpick::Zap(character* Zapper, v2, int Direction) { - if(!LastUsed || game::GetTick() - LastUsed >= Zapper->GetMagicItemCooldown(1000)) + if(!LastUsed || game::GetTick() - LastUsed >= GetCooldown(1000, Zapper)) { LastUsed = game::GetTick(); ADD_MESSAGE("You zap %s!", CHAR_NAME(DEFINITE)); - Zapper->EditExperience(PERCEPTION, 150, 1 << 10); + Zapper->EditExperience(MANA, 150, 1 << 10); beamdata Beam ( - Zapper, - CONST_S("killed by ") + GetName(INDEFINITE), - Zapper->GetPos(), - TRANSPARENT_COLOR, - BEAM_WALL_CREATION, - Direction, - 1, - 0 + Zapper, + CONST_S("killed by ") + GetName(INDEFINITE), + Zapper->GetPos(), + TRANSPARENT_COLOR, + BEAM_WALL_CREATION, + Direction, + 1, + 0, + this ); (GetLevel()->*level::GetBeam(SHIELD_BEAM))(Beam); @@ -1658,3 +1674,310 @@ truth unpick::Zap(character* Zapper, v2, int Direction) return true; } + +truth muramasa::HitEffect(character* Enemy, character* Hitter, v2 HitPos, + int BodyPartIndex, int Direction, truth BlockedByArmour) +{ + truth BaseSuccess = meleeweapon::HitEffect(Enemy, Hitter, HitPos, BodyPartIndex, Direction, BlockedByArmour); + + if(!IsBroken() && Enemy->IsEnabled()) + { + // Special effect against lawful and neutral creatures. + bool IsGoodly = false; + + if(Enemy->IsPlayer() && game::GetPlayerAlignment() < 0) + IsGoodly = true; + else if(!Enemy->IsPlayer()) + { + switch (Enemy->GetAttachedGod()) + { + case VALPURUS: + case LEGIFER: + case ATAVUS: + case DULCIS: + case SEGES: + case SOPHOS: + case SILVA: + case LORICATUS: IsGoodly = true; break; + } + } + + if(IsGoodly) + { + if(!RAND_N(10)) + { + switch (RAND() % 7) + { + case 0: Enemy->BeginTemporaryState(LYCANTHROPY, 6000 + RAND_N(2000)); break; + case 1: Enemy->BeginTemporaryState(VAMPIRISM, 5000 + RAND_N(2500)); break; + case 2: Enemy->BeginTemporaryState(PARASITE_TAPE_WORM, 6000 + RAND_N(3000)); break; + case 3: Enemy->BeginTemporaryState(PARASITE_MIND_WORM, 400 + RAND_N(200)); break; + case 4: Enemy->BeginTemporaryState(HICCUPS, 1000 + RAND_N(2000)); break; + default: Enemy->GainIntrinsic(LEPROSY); break; + } + } + else + { + switch (RAND() % 3) + { + case 0: Enemy->BeginTemporaryState(SLOW, 400 + RAND_N(200)); break; + case 1: Enemy->BeginTemporaryState(POISONED, 80 + RAND() % 40); break; + case 2: Enemy->BeginTemporaryState(CONFUSED, 400 + RAND_N(1000)); break; + } + } + + if(Hitter) + { + if(Enemy->IsPlayer() || Hitter->IsPlayer() || Enemy->CanBeSeenByPlayer() || Hitter->CanBeSeenByPlayer()) + ADD_MESSAGE("%s %s defiles %s.", Hitter->CHAR_POSSESSIVE_PRONOUN, CHAR_NAME(UNARTICLED), Enemy->CHAR_DESCRIPTION(DEFINITE)); + } + else + { + if(Enemy->IsPlayer() || Enemy->CanBeSeenByPlayer()) + ADD_MESSAGE("%s defiles %s.", CHAR_NAME(DEFINITE), Enemy->CHAR_DESCRIPTION(DEFINITE)); + } + } + else if(!RAND_N(4)) // Striking a chaotic creature. + ADD_MESSAGE("%s seems reluctant to strike %s.", CHAR_NAME(DEFINITE), Enemy->CHAR_DESCRIPTION(DEFINITE)); + } + + return BaseSuccess; +} + +truth masamune::HitEffect(character* Enemy, character* Hitter, v2 HitPos, + int BodyPartIndex, int Direction, truth BlockedByArmour) +{ + truth BaseSuccess = meleeweapon::HitEffect(Enemy, Hitter, HitPos, BodyPartIndex, Direction, BlockedByArmour); + + if(!IsBroken() && Enemy->IsEnabled()) + { + // Special effect against chaotic creatures. + bool IsEvil = false; + + if(Enemy->IsPlayer() && game::GetPlayerAlignment() < 0) + IsEvil = true; + else if(!Enemy->IsPlayer()) + { + switch (Enemy->GetAttachedGod()) + { + case MELLIS: + case CLEPTIA: + case NEFAS: + case SCABIES: + case INFUSCOR: + case CRUENTUS: + case MORTIFER: IsEvil = true; break; + } + } + + if(IsEvil) + { + for(int c = 0; c < STATES; ++c) + { + // Remove most temporary status effects, leaving only some negative ones. + if((1 << c != SLOW) && (1 << c != POISONED) && + (1 << c != PANIC) && (1 << c != CONFUSED) && + (1 << c != TELEPORT_LOCK) && (1 << c != PARASITE_TAPE_WORM) && + (1 << c != PARASITE_MIND_WORM)) + { + Enemy->DeActivateTemporaryState(1 << c); + + if(!IsEnabled()) + break; + } + } + + // Terrify the evil doer and prevent them from escaping. + if(!Enemy->TemporaryStateIsActivated(PANIC) && !Enemy->TemporaryStateIsActivated(FEARLESS) && + Enemy->GetPanicLevel() > (RAND() % (50 - Min(Hitter->GetAttribute(MANA), 49)))) + Enemy->BeginTemporaryState(PANIC, 200 + RAND_N(100)); + if(!Enemy->TemporaryStateIsActivated(TELEPORT_LOCK)) + Enemy->BeginTemporaryState(TELEPORT_LOCK, 200 + RAND_N(500)); + + if(Hitter) + { + if(Enemy->IsPlayer() || Hitter->IsPlayer() || Enemy->CanBeSeenByPlayer() || Hitter->CanBeSeenByPlayer()) + ADD_MESSAGE("%s %s rebukes %s.", Hitter->CHAR_POSSESSIVE_PRONOUN, CHAR_NAME(UNARTICLED), Enemy->CHAR_DESCRIPTION(DEFINITE)); + } + else + { + if(Enemy->IsPlayer() || Enemy->CanBeSeenByPlayer()) + ADD_MESSAGE("%s rebukes %s.", CHAR_NAME(DEFINITE), Enemy->CHAR_DESCRIPTION(DEFINITE)); + } + } + else if(!RAND_N(4)) // Striking a good creature. + ADD_MESSAGE("%s seems reluctant to strike %s.", CHAR_NAME(DEFINITE), Enemy->CHAR_DESCRIPTION(DEFINITE)); + } + + return BaseSuccess; +} + +void magestaff::Save(outputfile& SaveFile) const +{ + meleeweapon::Save(SaveFile); + SaveFile << LastUsed; +} + +void magestaff::Load(inputfile& SaveFile) +{ + meleeweapon::Load(SaveFile); + SaveFile >> LastUsed; +} + +void magestaff::FinalProcessForBone() +{ + meleeweapon::FinalProcessForBone(); + LastUsed = 0; +} + +int magestaff::GetCooldown(int BaseCooldown, character* User) +{ + int Attribute = User->GetAttribute(MANA); + + if(Attribute > 1) + { + return BaseCooldown / log10(Attribute); + } + + return BaseCooldown / 0.20; +} + +truth magestaff::Zap(character* Zapper, v2, int Direction) +{ + int Cooldown; + switch (GetConfig()) + { + case ROYAL_STAFF: Cooldown = 2500; break; + default: Cooldown = 1000; break; + } + + if(!LastUsed || game::GetTick() - LastUsed >= GetCooldown(Cooldown, Zapper)) + { + LastUsed = game::GetTick(); + ADD_MESSAGE("You zap %s!", CHAR_NAME(DEFINITE)); + Zapper->EditExperience(MANA, 150, 1 << 10); + + // Prepare for magical staves with different effects. + switch (GetConfig()) + { + case ROYAL_STAFF: // Infinite polymorph, but with range of only 1. + { + beamdata Beam + ( + Zapper, + CONST_S("killed by ") + GetName(DEFINITE) + " zapped @bk", + Zapper->GetPos(), + GetBeamColor(), + GetBeamEffect(), + Direction, + GetBeamRange(), + 0, + this + ); + (GetLevel()->*level::GetBeam(PARTICLE_BEAM))(Beam); + break; + } + default: + { + if(CanBeSeenByPlayer()) + ADD_MESSAGE("%s releases a shower of polychromatic sparks.", CHAR_NAME(DEFINITE)); + break; + } + } + } + else + ADD_MESSAGE("Nothing happens."); + + return true; +} + +truth chastitybelt::CanBeEquipped(int I) const +{ + return !IsLocked(); +} + +truth chastitybelt::CanBeUnEquipped(int I) const +{ + // Worn locked chastity belt cannot be taken off. + return !IsInCorrectSlot(I) || !IsLocked(); +} + +void chastitybelt::AddInventoryEntry(ccharacter*, festring& Entry, int Amount, truth ShowSpecialInfo) const +{ + if(Amount == 1) + AddName(Entry, INDEFINITE); + else + { + Entry << Amount << ' '; + AddName(Entry, PLURAL); + } + + if(ShowSpecialInfo) + Entry << " [" << GetWeight() * Amount << "g"; + if(ivanconfig::IsShowVolume()) + Entry << ", " << GetVolume() << "cm3"; + Entry << ", AV " << GetStrengthValue(); + if(IsLocked()) + Entry << ", locked"; + Entry << ']'; +} + +void chastitybelt::PostConstruct() +{ + lockablebelt::PostConstruct(); + SetIsLocked(RAND_2); +} + +void pica::Save(outputfile& SaveFile) const +{ + meleeweapon::Save(SaveFile); + SaveFile << LastUsed; +} + +void pica::Load(inputfile& SaveFile) +{ + meleeweapon::Load(SaveFile); + SaveFile >> LastUsed; +} + +void pica::FinalProcessForBone() +{ + meleeweapon::FinalProcessForBone(); + LastUsed = 0; +} + +int pica::GetCooldown(int BaseCooldown, character* User) +{ + int Attribute = User->GetAttribute(MANA); + + if(Attribute > 1) + return BaseCooldown / log10(Attribute); + + return BaseCooldown / 0.20; +} + +truth pica::Zap(character* Zapper, v2, int Direction) +{ + if(!IsBroken() && (!LastUsed || game::GetTick() - LastUsed >= GetCooldown(50, Zapper))) + { + LastUsed = game::GetTick(); + ADD_MESSAGE("You zap %s!", CHAR_NAME(DEFINITE)); + Zapper->EditExperience(MANA, 50, 1 << 10); + + for(int i = 0; i < (RAND_N(Max(GetEnchantment(), 1)) + 1); i++) + { + meleeweapon* ToBeThrown = meleeweapon::Spawn(DAGGER); + ToBeThrown->InitMaterials(MAKE_MATERIAL(STAR_METAL), MAKE_MATERIAL(MOON_SILVER), true); + ToBeThrown->SetEnchantment(GetEnchantment()); + ToBeThrown->SetLifeExpectancy(50, Zapper->GetAttribute(MANA)); + Zapper->ReceiveItemAsPresent(ToBeThrown); + + ToBeThrown->Fly(Zapper, Direction, Zapper->GetAttribute(MANA), + ToBeThrown->IsWeapon(Zapper) && !ToBeThrown->IsBroken()); + } + } + else + ADD_MESSAGE("Nothing happens."); + + return true; +} diff --git a/Main/Source/god.cpp b/Main/Source/god.cpp index 39a22388d..b90a0fae0 100644 --- a/Main/Source/god.cpp +++ b/Main/Source/god.cpp @@ -267,29 +267,46 @@ character* god::CreateAngel(team* Team, int LifeBase) return 0; } -void god::PrintRelation() const +cfestring god::PrintRelation() const { cchar* VerbalRelation; + cchar* fsIs; + cchar* fsWas; + + if(GetRelation() == 1000){ + fsIs="greets";fsWas="greeted"; + VerbalRelation = "you as a Champion of the Cause!"; + }else if(GetRelation() > 750){ + fsIs="is";fsWas="was"; + VerbalRelation = "extremely pleased."; + }else if(GetRelation() > 250){ + fsIs="is";fsWas="was"; + VerbalRelation = "very pleased."; + }else if(GetRelation() > 50){ + fsIs="is";fsWas="was"; + VerbalRelation = "pleased."; + }else if(GetRelation() > -50){ + fsIs="is";fsWas="was"; + VerbalRelation = "content."; + }else if(GetRelation() > -250){ + fsIs="is";fsWas="was"; + VerbalRelation = "angry."; + }else if(GetRelation() > -750){ + fsIs="is";fsWas="was"; + VerbalRelation = "very angry."; + }else if(GetRelation() > -1000){ + fsIs="is";fsWas="was"; + VerbalRelation = "extremely angry."; + }else { + fsIs="hates";fsWas="hated"; + VerbalRelation = "you more than any other mortal."; + } + + ADD_MESSAGE("%s %s %s", GetName(), fsIs, VerbalRelation); - if(GetRelation() == 1000) - VerbalRelation = "greets you as a Champion of the Cause!"; - else if(GetRelation() > 750) - VerbalRelation = "is extremely pleased."; - else if(GetRelation() > 250) - VerbalRelation = "is very pleased."; - else if(GetRelation() > 50) - VerbalRelation = "is pleased."; - else if(GetRelation() > -50) - VerbalRelation = "is content."; - else if(GetRelation() > -250) - VerbalRelation = "is angry."; - else if(GetRelation() > -750) - VerbalRelation = "is very angry."; - else if(GetRelation() > -1000) - VerbalRelation = "is extremely angry."; - else VerbalRelation = "hates you more than any other mortal."; - - ADD_MESSAGE("%s %s", GetName(), VerbalRelation); + festring fsLKR; + fsLKR<CHAR_NAME(DEFINITE)); + ADD_MESSAGE("%s cannot be sacrificed.", Sacrifice->CHAR_NAME(DEFINITE)); } return false; } @@ -338,7 +355,7 @@ truth god::ReceiveOffer(item* Sacrifice) else ADD_MESSAGE("%s seems not to appreciate your gift at all.", GetName()); - PrintRelation(); + fsLastKnownRelation = PrintRelation(); int RandModifier = Sacrifice->GetAttachedGod() == GetType() ? 50 : 100; if(OfferValue > 0 && Relation > 250 && !(RAND() % RandModifier)) @@ -499,6 +516,11 @@ cchar* god::GetPersonalPronoun() const return GetSex() == MALE ? "He" : "She"; } +cchar* god::GetLastKnownRelation() const +{ + return fsLastKnownRelation.CStr(); +} + cchar* god::GetObjectPronoun() const { return GetSex() == MALE ? "Him" : "Her"; @@ -530,11 +552,15 @@ void god::Save(outputfile& SaveFile) const { SaveFile << static_cast(GetType()); SaveFile << Relation << Timer << Known << LastPray; + SaveFile << fsLastKnownRelation; } void god::Load(inputfile& SaveFile) { SaveFile >> Relation >> Timer >> Known >> LastPray; + if(game::GetCurrentSavefileVersion()>=134){ + SaveFile >> fsLastKnownRelation; + } } void god::ApplyDivineTick() diff --git a/Main/Source/gods.cpp b/Main/Source/gods.cpp index a69fbf124..be9cce2ce 100644 --- a/Main/Source/gods.cpp +++ b/Main/Source/gods.cpp @@ -128,11 +128,14 @@ col16 mortifer::GetEliteColor() const { return CHAOS_ELITE_COLOR; } void sophos::PrayGoodEffect() { + truth DidHelp = false; + if(!PLAYER->StateIsActivated(TELEPORT_LOCK)) { ADD_MESSAGE("Suddenly, the fabric of space experiences an unnaturally powerful quantum displacement!"); game::AskForKeyPress(CONST_S("You teleport! [press any key to continue]")); PLAYER->Move(game::GetCurrentLevel()->GetRandomSquare(PLAYER), true); + DidHelp = true; } // Give a little attribute experience (Cha already given by Dulcis and not Wis, @@ -142,7 +145,7 @@ void sophos::PrayGoodEffect() cchar* SecretType; int Experience = Min(200, Max(50, GetRelation() / 4)); - switch(RAND() % 2) + switch(RAND() % 3) { case 0: SecretType = "an ancient"; @@ -162,11 +165,12 @@ void sophos::PrayGoodEffect() } ADD_MESSAGE("%s whispers %s secret to you.", GetName(), SecretType); + DidHelp = true; } - else - { + + if(!DidHelp) ADD_MESSAGE("You hear a booming voice: \"Alas, I cannot help thee, mortal.\""); - } + return; } @@ -213,6 +217,7 @@ void valpurus::PrayBadEffect() void legifer::PrayGoodEffect() { // I think this is a remnant of past development that you call upon Inlux rather than Legifer. --red_kangaroo + // No, my bad. Inlux is an anagram of Linux, which will hopefully save us from the horrid Bill. ;) ADD_MESSAGE("A booming voice echoes: \"Inlux! Inlux! Save us!\" A huge firestorm engulfs everything around you."); //ADD_MESSAGE("You are surrounded by the righteous flames of %s.", GetName()); game::GetCurrentLevel()->Explosion(PLAYER, CONST_S("killed by the holy flames of ") + GetName(), PLAYER->GetPos(), @@ -504,14 +509,15 @@ void silva::PrayGoodEffect() beamdata Beam ( 0, - CONST_S("drowned by the showers of ") + GetName(), + CONST_S("drowned by the tears of ") + GetName(), YOURSELF, 0 ); lsquare* Square = PLAYER->GetLSquareUnder(); + PLAYER->SpillFluid(0, liquid::Spawn(WATER, 400 + RAND() % 800)); + Square->LiquidRain(Beam, WATER); - Square->WaterRain(Beam); ADD_MESSAGE("Silva allows a little spell of gentle rain to pour down from above."); } else if(!game::GetCurrentLevel()->IsOnGround()) @@ -1161,8 +1167,7 @@ void nefas::PrayBadEffect() void scabies::PrayGoodEffect() { - // TODO: as champion grant green slime vomit - if(PLAYER->IsImmuneToLeprosy()) // Spread leprosy whenever you won't harm your follwers. + if(PLAYER->IsImmuneToLeprosy()) // Spread leprosy whenever you won't harm your followers. { for(int c = 0; c < game::GetTeams(); ++c) if(PLAYER->GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE) @@ -1349,11 +1354,27 @@ void infuscor::PrayGoodEffect() void cruentus::PrayGoodEffect() { + // Blood for the god of blood! + if(!RAND_4 || Relation == 1000) + { + beamdata Beam + ( + 0, + CONST_S("drowned by the blood of ") + GetName(), + YOURSELF, + 0 + ); + lsquare* Square = PLAYER->GetLSquareUnder(); + Square->LiquidRain(Beam, BLOOD); + } + + // A little bit of healing, but only usable when panicked. if(PLAYER->StateIsActivated(PANIC)) { - ADD_MESSAGE("\"Fight, you lousy coward!\"", GetName()); + ADD_MESSAGE("%s snarls: \"Fight, you lousy coward!\"", GetName()); PLAYER->DeActivateTemporaryState(PANIC); - PLAYER->BeginTemporaryState(FEARLESS, 200 * PLAYER->GetAttribute(WISDOM) + Relation * 5); + PLAYER->BeginTemporaryState(REGENERATION, 200 * PLAYER->GetAttribute(WISDOM) + Relation * 3); + PLAYER->BeginTemporaryState(FEARLESS, 200 * PLAYER->GetAttribute(WISDOM) + Relation * 3); return; } @@ -1430,6 +1451,7 @@ void cruentus::PrayGoodEffect() ADD_MESSAGE("Cruentus recommends you to his master, Mortifer."); game::GetGod(MORTIFER)->AdjustRelation(100); } + return; } void cruentus::PrayBadEffect() diff --git a/Main/Source/hiteffect.cpp b/Main/Source/hiteffect.cpp index a1a9a00cc..2eba7de71 100644 --- a/Main/Source/hiteffect.cpp +++ b/Main/Source/hiteffect.cpp @@ -48,8 +48,16 @@ bool hiteffect::ItemEfRefExists() const bool hiteffect::WhoIsHitExists() const { - if(game::SearchCharacter(setup.lWhoIsHitID)==NULL)return false; - return setup.WhoIsHit->Exists(); + if(setup.WhoIsHit){ + if(game::SearchCharacter(setup.lWhoIsHitID)==NULL)return false; + return setup.WhoIsHit->Exists(); + }else + if(setup.HitAtSquare){ + return true; + } + + ABORT("either WhoIsHit or HitAtSquare must be set!"); + return false; //DUMMY } bool hiteffect::WhoHitsExists() const @@ -79,10 +87,10 @@ hiteffect::hiteffect(hiteffectSetup s) bldLum.Src=v2(); //reset bldLum.Bitmap->ClearToColor(TRANSPARENT_COLOR); //reset - setup=s; DBG4(this,s.WhoHits,s.WhoIsHit,"WhoHits");DBG2(s.WhoHits->GetName(DEFINITE).CStr(),s.WhoIsHit->GetName(DEFINITE).CStr()); + setup=s; DBG5(this,s.WhoHits,s.WhoIsHit,s.HitAtSquare,"WhoWhereHits");//DBG2(s.WhoHits->GetName(DEFINITE).CStr(),s.WhoIsHit->GetName(DEFINITE).CStr()); setup.lWhoHitsID = setup.WhoHits->GetID(); - setup.lWhoIsHitID = setup.WhoIsHit->GetID(); + if(setup.WhoIsHit)setup.lWhoIsHitID = setup.WhoIsHit->GetID(); if(setup.lItemEffectReferenceID==0)ABORT("invalid item ID for hiteffect"); item* itemEffectReference = game::SearchItem(setup.lItemEffectReferenceID); @@ -91,7 +99,11 @@ hiteffect::hiteffect(hiteffectSetup s) v2CamPos = game::GetCamera(); v2HitFromSqrPos=s.WhoHits->GetPos(); DBGSV2(v2HitFromSqrPos); - v2HitToSqrPos=s.WhoIsHit->GetPos(); DBGSV2(v2HitToSqrPos); + if(setup.WhoIsHit) + v2HitToSqrPos=s.WhoIsHit->GetPos(); + else + v2HitToSqrPos=s.HitAtSquare->GetPos(); + DBGSV2(v2HitToSqrPos); v2HitFromToSqrDiff = v2HitFromSqrPos-v2HitToSqrPos; DBGSV2(v2HitFromToSqrDiff); if(v2HitFromToSqrDiff.X!=0 && v2HitFromToSqrDiff.Y!=0){ DBGLN; //diagonal v2 v2FromXY(Min(v2HitFromSqrPos.X,v2HitToSqrPos.X),Min(v2HitFromSqrPos.Y,v2HitToSqrPos.Y)); DBGSV2(v2FromXY); @@ -103,7 +115,7 @@ hiteffect::hiteffect(hiteffectSetup s) } } - bWhoIsHitDied=s.WhoIsHit->IsDead(); DBGLN; + bWhoIsHitDied=false;if(s.WhoIsHit)bWhoIsHitDied=s.WhoIsHit->IsDead(); DBGLN; LSquareUnderOfWhoHits=s.WhoHits->GetLSquareUnder(); DBGLN; @@ -115,7 +127,7 @@ hiteffect::hiteffect(hiteffectSetup s) bldLum.Luminance = lumHigh; break; case 3: //colored indicators where possible - if(setup.WhoIsHit->IsPlayer() || setup.WhoIsHit->IsPet()){ + if(setup.WhoIsHit && (setup.WhoIsHit->IsPlayer() || setup.WhoIsHit->IsPet())){ bldLum.Luminance = lumReddish; }else if(setup.WhoHits->IsPlayer()){ @@ -128,14 +140,14 @@ hiteffect::hiteffect(hiteffectSetup s) } break; case 4: //dynamic colored indicators or based on usefulness of the color indicator - if(setup.WhoIsHit->IsPlayer() || setup.WhoHits->IsPlayer()){ //player being hit or hitting is w/o doubts the origin of the attack + if((setup.WhoIsHit && setup.WhoIsHit->IsPlayer()) || setup.WhoHits->IsPlayer()){ //player being hit or hitting is w/o doubts the origin of the attack if(setup.Critical){ bldLum.Luminance = lumReddish; }else{ bldLum.Luminance = NORMAL_LUMINANCE; // do not highlight non critical strikes } }else - if(setup.WhoIsHit->IsPet()){ + if(setup.WhoIsHit && setup.WhoIsHit->IsPet()){ if(setup.Critical){ bldLum.Luminance = lumReddish; }else{ @@ -342,6 +354,7 @@ void hiteffect::cleanup(){ setup.LSquareUnder=NULL; setup.WhoHits=NULL; setup.WhoIsHit=NULL; + setup.HitAtSquare=NULL; setup.lItemEffectReferenceID=0; //not external reference, must be deleted at destructor: bmpHitEffect=NULL; @@ -383,7 +396,13 @@ truth hiteffect::DrawStep() //showing all animations helps on understanding there happened a hit, even if it looks like a miss or weird (kills before hitting) :( if(bAnimate && bWhoIsHitDied)bAnimate=false; if(bAnimate && !WhoIsHitExists())bAnimate=false; - if(bDraw && bAnimate && setup.WhoIsHit->GetPos()!=v2HitToSqrPos){ + if(bDraw && bAnimate && + ( + (setup.HitAtSquare && setup.HitAtSquare->GetPos()!=v2HitToSqrPos) + || + (setup.WhoIsHit && setup.WhoIsHit->GetPos()!=v2HitToSqrPos) + ) + ){ /* * TODO if the hit target moves, the effect would play flying to it's last location (where it was actually) * and will look like a miss (what is wrong), so I tried setting it to instantly draw there, diff --git a/Main/Source/human.cpp b/Main/Source/human.cpp index 605c4b070..5f8d13c08 100644 --- a/Main/Source/human.cpp +++ b/Main/Source/human.cpp @@ -202,7 +202,7 @@ void skeleton::CreateCorpse(lsquare* Square) void petrus::CreateCorpse(lsquare* Square) { - if(game::GetStoryState() == 2) + if(game::GetGloomyCaveStoryState() == 2) game::GetTeam(ATTNAM_TEAM)->SetRelation(game::GetTeam(PLAYER_TEAM), FRIEND); Square->AddItem(leftnutofpetrus::Spawn()); @@ -650,6 +650,33 @@ void petrus::BeTalkedTo() return; } + if(PLAYER->HasMuramasa() && PLAYER->HasMasamune()) + { + if(game::TruthQuestion(CONST_S("Report your actions in the kingdom of Aslona? [y/N]"), REQUIRES_ANSWER)) + { + game::PlayVictoryMusic(); + game::TextScreen(CONST_S("\"Yes, citizen? Ah, it is thee. I was wondering were hast thou wandered off. Art thou\n" + "coming back to beg for forgiveness and mercy?\"\n\n" + "But Petrus' anger is quickly quelled when you fall to your knees and present to him\n" + "the two regal swords of Aslona. He takes them and admires them in silence for a while,\n" + "though interrupted by Sir Lancelyn who realizes your deeds, screams in rage and is\n" + "promptly dragged away to a prison cell.\n\n" + "Lost in thoughts, Petrus hands the swords to a servant and turns back to you again:\n\n" + "\"I have decided thy fate, slave. I shan't have thee executed if thou canst prove thy worth.\n" + "Sir Galladon hast telepathically informed me that our trainee guards are just about ready\n" + "for their first war. Thou shalt join my army and help conquer Aslona while they are reeling.\n" + "For the glory of Valpurus!\"\n\nYou are victorious!")); + + game::GetCurrentArea()->SendNewDrawRequest(); + game::DrawEverything(); + PLAYER->ShowAdventureInfo(); + festring Msg = CONST_S("became an officer of the Attnamese army"); + AddScoreEntry(Msg, 3, false); + game::End(Msg); + return; + } + } + if(PLAYER->HasGoldenEagleShirt()) { ADD_MESSAGE("Petrus smiles. \"Thou hast defeated Oree! Mayst thou be blessed by Valpurus for the rest of thy life! " @@ -683,7 +710,7 @@ void petrus::BeTalkedTo() SendNewDrawRequest(); game::AskForKeyPress(CONST_S("You are attacked! [press any key to continue]")); PLAYER->GetTeam()->Hostility(GetTeam()); - game::SetStoryState(2); + game::SetGloomyCaveStoryState(2); return; } } @@ -703,69 +730,83 @@ void petrus::BeTalkedTo() AddScoreEntry(Msg, 2, false); game::End(Msg); } - else + else if(game::GetGloomyCaveStoryState() == 1) + { + ADD_MESSAGE("Petrus says: \"Bring me the head of Elpuri and we'll talk.\""); + return; + } + + if(!game::GetStoryState()) { - if(!game::GetStoryState()) + if(PLAYER->RemoveEncryptedScroll()) { - if(PLAYER->RemoveEncryptedScroll()) - { - game::TextScreen(CONST_S("You kneel down and bow before the high priest and hand him the encrypted scroll.\n" - "Petrus raises his arm, the scroll glows yellow, and lo! The letters are clear and\n" - "readable. Petrus asks you to voice them aloud. The first two thousand words praise\n" - "Valpurus the Creator and all His manifestations and are followed by a canticle of\n" - "Saint Petrus the Lion-Hearted lasting roughly three thousand words. Finally there\n" - "are some sentences actually concerning your mission:\n\n" - "\"Alas, I fear dirty tongues have spread lies to my Lord's ears. I assure all tales\n" - "of treasures here in New Attnam are but mythic legends. There is nothing of value here.\n" - "The taxes are already an unbearable burden and I can't possibly pay more. However I do\n" - "not question the wisdom of the government's decisions. I will contribute what I can:\n" - "the ostriches will deliver an extra 10000 bananas to the capital and additionally the\n" - "slave that brought the message will henceforth be at Your disposal. I am certain this\n" - "satisfies the crown's needs.\"\n\n" - "\"Yours sincerely,\n" - "Richel Decos, the viceroy of New Attnam\"")); - - game::TextScreen(CONST_S("You almost expected the last bit. Petrus seems to be deep in his thoughts and you\n" - "wonder what shape your destiny is taking in his mind. Suddenly he seems to return\n" - "to this reality and talks to you.\n\n" - "\"Oh, thou art still here. We were just discussing telepathically with Sir Galladon.\n" - "We started doubting Decos's alleged poverty a while back when he bought a couple of\n" - "medium-sized castles nearby. Thy brethren from New Attnam have also told Us about\n" - "vast riches seized from them. Our law says all such stolen valuables belong to \n" - "the Cathedral's treasury, so this is a severe claim. However, proof is needed,\n" - "and even if such was provided, We couldn't send soldiers over the snow fields\n" - "ere spring.\"")); - - game::TextScreen(CONST_S("\"However, since thou now servest Us, We ought to find thee something to do. Sir\n" - "Galladon hath told Us his agents witnessed thou leaving the dreaded underwater tunnel.\n" - "This means thou most likely hast defeated genetrix vesana and art a talented warrior.\n" - "We happen to have a task perfect for such a person. An evil dark frog named Elpuri who\n" - "hates Valpurus and Attnam more than anything hath taken control over an abandoned mine\n" - "nearby. It is pestering our fine city in many ways and reconnaissance has reported an\n" - "army of monsters gathering in the cave. Our guards are not trained to fight underground\n" - "and We dare not send them. To make things worse, someone hath recently stolen Us the\n" - "greatest armor in existence - the Shirt of the Golden Eagle. Elpuri cannot wear\n" - "it but he who can is now nearly immortal.\"\n\n" - "\"We have marked the location of the gloomy cave on thy world map. We want you to dive\n" - "into it and slay the vile frog. Bring Us its head and We reward thee with freedom.\n" - "Shouldst thou also find the Shirt, We'll knight thee. Good luck, and return when\n" - "thou hast succeeded.\"")); - - game::LoadWorldMap(); - v2 ElpuriCavePos = game::GetWorldMap()->GetEntryPos(0, ELPURI_CAVE); - game::GetWorldMap()->GetWSquare(ElpuriCavePos)->ChangeOWTerrain(elpuricave::Spawn()); - game::GetWorldMap()->RevealEnvironment(ElpuriCavePos, 1); - game::SaveWorldMap(); - GetArea()->SendNewDrawRequest(); - ADD_MESSAGE("\"And by the way, visit the librarian. He might have advice for thee.\""); - game::SetStoryState(1); - } - else - ADD_MESSAGE("\"Yes, citizen? We are quite busy now, thou shalt not disturb Us without proper cause.\""); + game::TextScreen(CONST_S("You kneel down and bow before the high priest and hand him the encrypted scroll.\n" + "Petrus raises his arm, the scroll glows yellow, and lo! The letters are clear and\n" + "readable. Petrus asks you to voice them aloud. The first two thousand words praise\n" + "Valpurus the Creator and all His manifestations and are followed by a canticle of\n" + "Saint Petrus the Lion-Hearted lasting roughly three thousand words. Finally there\n" + "are some sentences actually concerning your mission:\n\n" + "\"Alas, I fear dirty tongues have spread lies to my Lord's ears. I assure all tales\n" + "of treasures here in New Attnam are but mythic legends. There is nothing of value here.\n" + "The taxes are already an unbearable burden and I can't possibly pay more. However I do\n" + "not question the wisdom of the government's decisions. I will contribute what I can:\n" + "the ostriches will deliver an extra 10000 bananas to the capital and additionally the\n" + "slave that brought the message will henceforth be at Your disposal. I am certain this\n" + "satisfies the crown's needs.\"\n\n" + "\"Yours sincerely,\n" + "Richel Decos, the viceroy of New Attnam\"")); + + game::TextScreen(CONST_S("You almost expected the last bit. Petrus seems to be deep in his thoughts and you\n" + "wonder what shape your destiny is taking in his mind. Suddenly he seems to return\n" + "to this reality and talks to you.\n\n" + "\"Oh, thou art still here. We were just discussing telepathically with Sir Galladon.\n" + "We started doubting Decos's alleged poverty a while back when he bought a couple of\n" + "medium-sized castles nearby. Thy brethren from New Attnam have also told Us about\n" + "vast riches seized from them. Our law says all such stolen valuables belong to \n" + "the Cathedral's treasury, so this is a severe claim. However, proof is needed,\n" + "and even if such was provided, We couldn't send soldiers over the snow fields\n" + "ere spring.\"\n\n" + "\"However, since thou now servest Us, We ought to find thee something to do. In Our\n" + "immesurable kindness and generosity, we giveth thee some time to see this majestic\n" + "city of Ours and marvel at its glory. Return to Us once thou hast admired Our\n" + "monumental Cathedral enough, and we shall have decided thy fate by then.\"")); + + GetArea()->SendNewDrawRequest(); + ADD_MESSAGE("\"The patrol guard will watch out for you, so don't thee think of running away.\""); + game::SetStoryState(1); } - else /* StoryState == 1 */ - ADD_MESSAGE("Petrus says: \"Bring me the head of Elpuri and we'll talk.\""); + else + ADD_MESSAGE("\"Yes, citizen? We are quite busy now, thou shalt not disturb Us without proper cause.\""); + } + else if(game::GetStoryState() == 1) + { + game::TextScreen(CONST_S("\"Ah, here thou art, my brave warrior savage! Our agents hast informed Us that they\n" + "witnessed thou leaving the dreaded underwater tunnel. This means thou most likely hast\n" + "defeated Genetrix Vesana and art a talented monster slayer. We happen to have a task\n" + "perfect for such a person.\"\n\n" + "\"An evil dark frog named Elpuri who hates Valpurus and Attnam more than anything hath\n" + "taken control over an abandoned mine nearby. It is pestering our fine city in many ways\n" + "and reconnaissance has reported an army of monsters gathering in the cave. Our guards\n" + "are not trained to fight underground and We dare not send them. To make things worse,\n" + "someone hath recently stolen from Us the greatest armor in existence - the Shirt of the Golden Eagle.\n" + "Elpuri cannot wear it but he who can is now nearly immortal.\"\n\n" + "\"We have marked the location of the gloomy cave on thy map. We want you to dive\n" + "into it and slay the vile frog. Bring Us its head and We reward thee with freedom.\n" + "Shouldst thou also find the Shirt, We'll knight thee.\"\n\n" + "\"Good luck, and return when thou hast succeeded.\"")); + + game::LoadWorldMap(); + v2 ElpuriCavePos = game::GetWorldMap()->GetEntryPos(0, ELPURI_CAVE); + game::GetWorldMap()->GetWSquare(ElpuriCavePos)->ChangeOWTerrain(elpuricave::Spawn()); + game::GetWorldMap()->RevealEnvironment(ElpuriCavePos, 1); + game::SaveWorldMap(); + GetArea()->SendNewDrawRequest(); + ADD_MESSAGE("\"And by the way, visit the librarian. He might have advice for thee.\""); + game::SetGloomyCaveStoryState(1); + game::SetStoryState(2); } + else // StoryState == 2 + ADD_MESSAGE("\"Yes, citizen? We are quite busy now, thou shalt not disturb Us without proper cause.\""); } void priest::BeTalkedTo() @@ -1229,16 +1270,19 @@ void slave::BeTalkedTo() void slave::GetAICommand() { - SeekLeader(GetLeader()); + SeekLeader(GetLeader()); DBG2(GetNameSingular().CStr(), GetLeader()); - if(CheckAIZapOpportunity()) + if(CheckAIZapOpportunity()){ DBG1(GetNameSingular().CStr()); return; + } - if(CheckForEnemies(true, true, true)) + if(CheckForEnemies(true, true, true)){ DBG1(GetNameSingular().CStr()); return; + } - if(CheckForUsefulItemsOnGround()) + if(CheckForUsefulItemsOnGround()){ DBG1(GetNameSingular().CStr()); return; + } if(FollowLeader(GetLeader())) return; @@ -1277,40 +1321,62 @@ void librarian::BeTalkedTo() { case 0: if(game::GetPetrus() && !game::GetStoryState()) + ADD_MESSAGE("\"Thou shouldst visit Petrus in his great Cathedral.\""); + else if(game::GetPetrus() && game::GetStoryState() == 1) ADD_MESSAGE("\"Thou shouldst visit Petrus if thou art in need of further adventures.\""); else ADD_MESSAGE("\"They say a wand of polymorph hath dozens of uses.\""); break; case 1: - if(game::GetPetrus() && game::GetStoryState() == 1) + if(game::GetPetrus() && game::GetGloomyCaveStoryState()) ADD_MESSAGE("\"Thou art going to fight Elpuri? Beware! It is a powerful enemy. Other monsters " "are very vulnerable if surrounded by thy party, but not that beast, for it may " "slay a horde of thy friends at once with its horrendous tail attack.\""); + else if(game::GetXinrochTombStoryState()) + ADD_MESSAGE("\"Thou art going to delve into the Tomb of Xinroch? Beware, for it is a place of horrific darkness and abundant necromancy.\""); + /*else if(game::GetAslonaStoryState()) + ADD_MESSAGE("\"Elpuri the Dark Frog abhors light and resides in a level of eternal darkness.\"");*/ else ADD_MESSAGE("\"Thou shalt remember: Scientia est potentia.\""); break; case 2: - if(game::GetPetrus() && game::GetStoryState() == 1) + if(game::GetPetrus() && game::GetGloomyCaveStoryState()) ADD_MESSAGE("\"Elpuri the Dark Frog abhors light and resides in a level of eternal darkness.\""); + else if(game::GetXinrochTombStoryState()) + ADD_MESSAGE("\"The Tomb of Xinroch is a chilling place. It is said that a whole cavern of magical ice can be found when wandering its tunnels.\""); + /*else if(game::GetAslonaStoryState()) + ADD_MESSAGE("\"Elpuri the Dark Frog abhors light and resides in a level of eternal darkness.\"");*/ else ADD_MESSAGE("\"Shh! Thou shalt be silent in the library.\""); break; case 3: - if(game::GetPetrus() && game::GetStoryState() == 1) + if(game::GetPetrus() && game::GetGloomyCaveStoryState()) ADD_MESSAGE("\"Elpuri's attacks are so strong that they may shatter many of thy precious items.\""); + else if(game::GetXinrochTombStoryState()) + ADD_MESSAGE("\"The Tomb of Xinroch is guarded by fanatical dark knights that once followed Xinroch and swore to protect him even in death.\""); + /*else if(game::GetAslonaStoryState()) + ADD_MESSAGE("\"Elpuri the Dark Frog abhors light and resides in a level of eternal darkness.\"");*/ else ADD_MESSAGE("\"Dost thou not smell all the knowledge floating around here?\""); break; case 4: - ADD_MESSAGE("\"It is said that Loricatus, the god of smithing, can upgrade thy weapons' materials.\""); + if(!RAND_2) + ADD_MESSAGE("\"It is said that Loricatus, the god of smithing, can upgrade thy weapons' materials.\""); + else + ADD_MESSAGE("\"It is said that Atavus, the god of support and charity, may bolster thy defenses.\""); + break; case 5: - if(game::GetPetrus() && game::GetStoryState() == 1) + if(game::GetPetrus() && game::GetGloomyCaveStoryState()) ADD_MESSAGE("\"The Shirt of the Golden Eagle is a legendary artifact. Thou canst not find a better armor.\""); + /*else if(game::GetXinrochTombStoryState()) + ADD_MESSAGE("\"The Tomb of Xinroch is guarded by fanatical dark knights that once followed Xinroch and swore to protect him even in death.\""); + else if(game::GetAslonaStoryState()) + ADD_MESSAGE("\"Elpuri the Dark Frog abhors light and resides in a level of eternal darkness.\"");*/ else ADD_MESSAGE("\"In this book they talk about Mortifer, the great chaos god. He hates us " "mortals more than anything and will respond only to Champions of Evil.\""); @@ -1330,16 +1396,24 @@ void librarian::BeTalkedTo() "Thou shouldst not engage it in melee but rather kill it from afar.\""); break; case 9: - if(game::GetPetrus() && game::GetStoryState() == 1) + if(game::GetPetrus() && game::GetGloomyCaveStoryState()) ADD_MESSAGE("\"Thou art not alone in thy attempt to defeat Elpuri. A brave " "adventurer called Ivan also diveth into its cave not long ago.\""); + /*else if(game::GetXinrochTombStoryState()) + ADD_MESSAGE("\"The Tomb of Xinroch is guarded by fanatical dark knights that once followed Xinroch and swore to protect him even in death.\""); + else if(game::GetAslonaStoryState()) + ADD_MESSAGE("\"Elpuri the Dark Frog abhors light and resides in a level of eternal darkness.\"");*/ else ADD_MESSAGE("\"It is said that chaotic gods offer great power to their followers. But thou " "must remember: unlike lawfuls, they shall not help thee when things go bad.\""); break; case 10: - ADD_MESSAGE("\"If a man cannot choose, he ceases to be a man.\""); + if(!RAND_2) + ADD_MESSAGE("\"If a man cannot choose, he ceases to be a man.\""); + else + ADD_MESSAGE("\"It is said that Cruentus, the god of bloodshed, may empower thy weapons.\""); + break; case 11: ADD_MESSAGE("%s sighs: \"The censorship laws in this town are really too strict...\"", @@ -1790,7 +1864,7 @@ item* humanoid::GetEquipment(int I) const void humanoid::SetEquipment(int I, item* What) { if(ivanconfig::GetRotateTimesPerSquare() > 0) - What->ResetFlyingThrownStep(); + if(What)What->ResetFlyingThrownStep(); switch(I) { @@ -2328,11 +2402,12 @@ void humanoid::Bite(character* Enemy, v2 HitPos, int Direction, truth ForceHit) void humanoid::Kick(lsquare* Square, int Direction, truth ForceHit) { leg* KickLeg = RAND_2 ? GetRightLeg() : GetLeftLeg(); + item* Boot = KickLeg->GetBoot(); EditNP(-50); EditAP(-KickLeg->GetKickAPCost()); EditStamina(GetAdjustedStaminaCost(-1000, GetAttribute(LEG_STRENGTH)), false); - if(Square->BeKicked(this, 0, KickLeg, KickLeg->GetKickDamage(), KickLeg->GetKickToHitValue(), + if(Square->BeKicked(this, Boot, KickLeg, KickLeg->GetKickDamage(), KickLeg->GetKickToHitValue(), RAND() % 26 - RAND() % 26, Direction, !(RAND() % GetCriticalModifier()), ForceHit)) { KickLeg->EditExperience(LEG_STRENGTH, 75, 1 << 9); @@ -2936,7 +3011,7 @@ void zombie::CreateBodyParts(int SpecialFlags) if((GetConfig() == ZOMBIE_OF_KHAZ_ZADM) || !!(SpecialFlags & NO_SEVERED_LIMBS)) { Anyway = true; - } // Khaz-Zadm needs his hands... + } // Khaz-Zadm needs her hands... for(int c = 0; c < BodyParts; ++c) if(Anyway || BodyPartIsVital(c) || RAND_N(3) || (c == HEAD_INDEX && !RAND_N(3))) @@ -3349,7 +3424,9 @@ truth humanoid::CheckZap() void bananagrower::GetAICommand() { - if(game::TweraifIsFree()) + if(game::TweraifIsFree() || + (GetDungeon()->GetIndex() != NEW_ATTNAM) + ) // Behave normally outside of New Attnam. { humanoid::GetAICommand(); return; @@ -4294,13 +4371,13 @@ void golem::BeTalkedTo() void humanoid::AddAttributeInfo(festring& Entry) const { - Entry.Resize(45); + Entry.Resize(42); Entry << GetAttribute(ARM_STRENGTH); - Entry.Resize(48); + Entry.Resize(45); Entry << GetAttribute(LEG_STRENGTH); - Entry.Resize(51); + Entry.Resize(48); Entry << GetAttribute(DEXTERITY); - Entry.Resize(54); + Entry.Resize(51); Entry << GetAttribute(AGILITY); character::AddAttributeInfo(Entry); } @@ -4457,6 +4534,17 @@ truth ennerchild::MustBeRemovedFromBone() const || GetLevel()->GetIndex() != DUAL_ENNER_BEAST_LEVEL; } +truth child::MustBeRemovedFromBone() const +{ + if(GetConfig() != KING) + return false; + + return !IsEnabled() + || GetTeam()->GetID() != ASLONA_TEAM + || GetDungeon()->GetIndex() != GOBLIN_FORT + || GetLevel()->GetIndex() != KING_LEVEL; +} + truth communist::MustBeRemovedFromBone() const { return !IsEnabled() @@ -5020,120 +5108,117 @@ void necromancer::BeTalkedTo() return; } - if(PLAYER->HasShadowVeil() && (game::GetXinrochTombStoryState() == 1)) + if(game::GetXinrochTombStoryState() == 1) { - if(PLAYER->RemoveShadowVeil()) + if(PLAYER->HasShadowVeil() && PLAYER->RemoveShadowVeil(this)) { - game::TextScreen(CONST_S("The Necromancer takes the Shadow Veil.\n" - "\"At last I can make my escape from Petrus' wretched clutches!\"\n" - "\n" - "The necromancer looks up \n" + game::TextScreen(CONST_S("\"At last I can make my escape from Petrus' wretched clutches!\"\n\n" + "Anmah takes the shadow veil from you and seems completely lost in\n" + "thoughts for a while. Suddenly, he looks up:\n\n" "\"Oh, you are still here. Good! Pray tell me, what did you find in the Tomb?\n" - "A portal? Did you traverse it? Of course! You can't do so bodily,\n" - "unless you were... ...changed in some way?\"\n")); + "A portal? Did you traverse it? Of course not! You can't do so bodily,\n" + "unless you were...\n\n...changed in some way.\"\n\n" + "Before you can stop him, he reaches for your face.")); - game::TextScreen(CONST_S("You feel a cold, tingling sensation in the middle of your forehead.\n" - "\n\n" + game::TextScreen(CONST_S("You feel a cold, tingling sensation in the middle of your forehead.\n\n" "\"Here, I give you the seal of the undead. You will be able to traverse\n" - "the portal without the use of the shadow veil. Go forth!\"")); + "the portal without the use of the shadow veil. You can still retrieve\n" + "the lost flaming ruby sword. If you go beyond the portal, you will find\n" + "the one who carries this lost sword. But be warned, he is a terrible foe!\"")); GetArea()->SendNewDrawRequest(); - ADD_MESSAGE("\"You can still retrieve the lost flaming ruby sword. If you go beyond the portal, you will find the one who carries this lost sword. But be warned, he is a terrible foe!\""); + ADD_MESSAGE("\"May Infuscor guide you towards the darkest of secrets.\""); game::SetXinrochTombStoryState(2); } - return; + else + { + ADD_MESSAGE("%s says: \"Bring me the shadow veil and we'll talk.\"", CHAR_NAME(DEFINITE)); + return; + } } if(PLAYER->HasLostRubyFlamingSword()) { - ADD_MESSAGE("The necromancer exclaims: \"What are you still doing down here? That sword belongs to the Champion of Infuscor!\""); + ADD_MESSAGE("%s exclaims: \"What are you still doing down here? That sword belongs to the Champion of Infuscor!\"", CHAR_NAME(DEFINITE)); return; } else if(game::GetXinrochTombStoryState() == 2) - ADD_MESSAGE("The necromancer says: \"I am just preparing to leave. Have you found that flaming ruby sword yet?\""); - - if(game::GetXinrochTombStoryState() == 1) - { - ADD_MESSAGE("The necromancer says: \"Bring me the shadow veil and we'll talk.\""); - return; - } + ADD_MESSAGE("%s says: \"I am just preparing to leave. Have you found that flaming ruby sword yet?\"", CHAR_NAME(DEFINITE)); - if(PLAYER->HasEncryptedScroll() && !game::GetXinrochTombStoryState()) + if(game::GetStoryState() == 1) { - ADD_MESSAGE("The necromancer looks up. \"Have you got the encrypted scroll?\""); + ADD_MESSAGE("%s looks up: \"Would you mind helping me with a little problem?\"", CHAR_NAME(DEFINITE)); - if(game::TruthQuestion(CONST_S("Will you give the encrypted scroll to the necromancer? [y/n]"), REQUIRES_ANSWER)) + if(game::TruthQuestion(CONST_S("Do you accept the quest? [y/N]"), REQUIRES_ANSWER)) { - if(PLAYER->RemoveEncryptedScroll()) - { - game::TextScreen(CONST_S("The necromancer takes the scroll and mutters an incantation in a low voice.\n" - "To your surprise, the words rearrange themselves on the page,\n" - "revealing a previously inscrutable message.\n" - "The necromancer scans the page from left to right several times. His face contorts:\n" - "\"Bah! A canticle of Saint Petrus the Lion-Hearted!\"\n" - "He continues down the page. His eyes widen:\n" - "\"O ho! 10 000 bananas? It sounds bad out in the colonies. I'm sorry to hear about it.\"\n" - "\n" - "The necromancer allows the scroll to burn and the ashes wither away in his hands.\n" - "\"Alas, no news about my trial. But thank you for sharing.\"\n" - "\n" - "\"What am I doing here you ask? You could say I spent some time arranging things\n" - "in the catacombs below. I was the undertaker for the city of Attnam, you see.\n" - "Well, curiosity got the better of me and I admit I dabbled in some necromancy.\n" - "223 years later, and I'm still down here, drinking blood, eating bones, and generally \n" - "trying all the old life-extension tricks. Finally I got caught out by that meddling Haedlac.\n" - "He's got nothing better to do these days. Sent me here to the Cellar, never too far away\n" - "from my minions. But alas, no more necromancy, that stupid floating eye hovers by here\n" - "every now and again to check up on me.\"\n")); - - game::TextScreen(CONST_S("\"Wait, don't go yet! It gets lonely here, with no one to talk to but the punishers.\n" - "Keep me company a little longer, please... Maybe I can tell you a story? I can relate\n" - "the history of dark knighthood to you.\n\n" - "Long ago, there lived a powerful warrior, Xinroch, who rose up the ranks\n" - "of the fearsome order of the dark knights, to become the grand master dark knight. \n\n" - "His soul dwells within his mausoleum, not far from here. He doesn't stand a chance\n" - "of returning to us; not without a piece of his soul getting out. There is a cadre\n" - "of devoted dark knights, called the Templars. Being eager to protect the resting place\n" - "of their legendary master, they may obstruct your entry to the tomb. Little do they know\n" - "that in order for their master to be reborn, his spirit must be freed from the place.\n" - "Of course, disturbing such a restless soul would be dangerous. You may need to subdue it\n" - "by force to gain what you need. Legend has it Xinroch's spirit is able to wield weapons,\n" - "and possesses a cloak of unimaginable usefulness: The Shadow Veil.\"")); - - game::TextScreen(CONST_S("The necromancer suddenly looks at you intently.\n" - "\"Tell you what, I think you can help me out. But first I'll need proof of your abilities.\n" - "It will take all your wits to survive the powers of the Tomb of Xinroch to the very end.\"\n\n" - "Bring me this shadow veil, and I might be able to help you in a lasting way. I need the\n" - "shadow veil to help make my escape from Attnam. It has certain properties conducive to\n" - "getting away unnoticed.\"\n\n" - "\"There is also the matter of Xinroch's lost sword. Its power lies in its symbolism.\n" - "If you were to gain it somehow, then I imagine most believers would be convinced that\n" - "you were Xinroch himself, returned to the flesh. Although you would need to prove this\n" - "with the help of our god, Infuscor... ...it might require some offering, or exchange?\n" - "I cannot say what trial would await you to retrieve the lost sword.\"")); - - game::LoadWorldMap(); - v2 XinrochTombPos = game::GetWorldMap()->GetEntryPos(0, XINROCH_TOMB); - game::GetWorldMap()->GetWSquare(XinrochTombPos)->ChangeOWTerrain(xinrochtomb::Spawn()); - game::GetWorldMap()->RevealEnvironment(XinrochTombPos, 1); - game::SaveWorldMap(); - GetArea()->SendNewDrawRequest(); - ADD_MESSAGE("\"By the way, if you find anything belonging to Xinroch, then don't lose it! I have a feeling it will help you greatly in your quest.\""); - game::SetXinrochTombStoryState(1); - return; - } + /*game::TextScreen(CONST_S("The necromancer takes the scroll and mutters an incantation in a low voice.\n" + "To your surprise, the words rearrange themselves on the page,\n" + "revealing a previously inscrutable message.\n" + "The necromancer scans the page from left to right several times. His face contorts:\n" + "\"Bah! A canticle of Saint Petrus the Lion-Hearted!\"\n" + "He continues down the page. His eyes widen:\n" + "\"O ho! 10 000 bananas? It sounds bad out in the colonies. I'm sorry to hear about it.\"\n" + "\n" + "The necromancer allows the scroll to burn and the ashes wither away in his hands.\n" + "\"Alas, no news about my trial. But thank you for sharing.\"\n\n" + */ + game::TextScreen(CONST_S("\"You might be asking what am I doing down here? Lets just say I had spent some time\n" + "arranging... things in the catacombs below. I was the undertaker for the city of Attnam,\n" + "you see. Well, curiosity got the better of me and I admit I dabbled in some necromancy.\n" + "223 years later, and I was still down here, drinking blood, eating bones, and generally \n" + "trying all the old life-extension tricks. Finally I got caught by that meddling Haedlac.\n" + "He's got nothing better to do these days, I guess. He sent me here to the Cellar, agonizingly\n" + "close to my minions, but still unable to escape. That stupid floating eye hovers by here\n" + "every now and again to check up on me.\"")); + + game::TextScreen(CONST_S("\"Wait, don't go yet! It gets lonely here, with no one to talk to but the punishers.\n" + "Keep me company a little longer, please... Maybe I can tell you a story? I can relate\n" + "the history of dark knighthood to you.\"\n\n" + "\"Long ago, there lived a powerful warrior, Xinroch, who rose up the ranks\n" + "of the fearsome order of the dark knights, to become the grand master dark knight. \n\n" + "His soul dwells within his mausoleum, not far from here. He doesn't stand a chance\n" + "of returning to us; not without a piece of his soul getting out. There is a cadre\n" + "of devoted dark knights, called the Templars. Being eager to protect the resting place\n" + "of their legendary master, they may obstruct your entry to the tomb. Little do they know\n" + "that in order for their master to be reborn, his spirit must be freed from the place.\n" + "Of course, disturbing such a restless soul would be dangerous. You may need to subdue it\n" + "by force to gain what you need. Legend has it Xinroch's spirit is able to wield weapons,\n" + "and possesses a cloak of unimaginable usefulness: The Shadow Veil.\"")); + + game::TextScreen(CONST_S("The necromancer suddenly looks at you intently.\n\n" + "\"Okay, we can talk now. The cardinals are not listening to our thoughts. I need you to\n" + "bring me the shadow veil, it will surely allow me to escape from Attnam. It has certain\n" + "properties conducive to getting away unnoticed.\"\n\n" + "\"It will take all your wits to survive the powers of the Tomb of Xinroch, but I believe\n" + "in you. You are my only hope. Oh, how I wish to taste fresh blood again!\"\n\n" + "\"Lastly, there is the matter of Xinroch's lost sword. Its power lies in its symbolism.\n" + "If you were to gain it somehow, then I imagine most believers would be convinced that\n" + "you were Xinroch himself, returned to the flesh. Although you would need to prove this\n" + "with the help of our god, Infuscor... ...it might require some offering, perhaps? I have\n" + "a feeling that if you find anything belonging to Xinroch, it will help you greatly\n" + "in your quest.\"\n\n" + "\"I cannot say what trial would await you to retrieve the lost sword, but I'm sure\n" + "a mighty adventurer like you would like to lead a whole order of dark knights?\"")); + + game::LoadWorldMap(); + v2 XinrochTombPos = game::GetWorldMap()->GetEntryPos(0, XINROCH_TOMB); + game::GetWorldMap()->GetWSquare(XinrochTombPos)->ChangeOWTerrain(xinrochtomb::Spawn()); + game::GetWorldMap()->RevealEnvironment(XinrochTombPos, 1); + game::SaveWorldMap(); + GetArea()->SendNewDrawRequest(); + ADD_MESSAGE("\"And don't worry about the patrol guard, he's not really paying attention to who leaves the Attnam or if they should be leaving.\""); + game::SetXinrochTombStoryState(1); + game::SetStoryState(2); + return; } else { - ADD_MESSAGE("The necromancer looks downcast. " - "\"I see. I guess I shall have to wait for another adventurer then.\""); + ADD_MESSAGE("%s looks downcast. \"I see. I guess I shall have to wait for another adventurer then.\"", CHAR_NAME(DEFINITE)); return; } } - else if(!game::GetXinrochTombStoryState()) /* XinrochTombStoryState == 0 */ - ADD_MESSAGE("The necromancer says: \"Bring me the encrypted scroll and we'll talk.\""); - - return; + else + ADD_MESSAGE("%s says: \"Come back when you are on no other quests.\"", CHAR_NAME(DEFINITE)); } void humanoid::StayOn(liquid* Liquid) @@ -5416,7 +5501,7 @@ void imperialist::BeTalkedTo() "the late viceroy were brothers? Not especially close brothers, but I think\n" "he'll still want to roast you very slowly over a firepit; flay and cut and\n" "tear and break you; and then have the priests heal you up to start over again.\n" - "So why did you come to tell me this, hmm?\"\n")); + "So why did you come to tell me this, hmm?\"")); game::TextScreen(CONST_S("\"Fifty thousand gold?! Oh, sorry I cried out. Hmm, yes, now I can see your point.\"\n" "\n" @@ -5426,15 +5511,15 @@ void imperialist::BeTalkedTo() "bits of the Empire just breaking free. It sets a bad example.\"\n" "\n" "\"Hmm, so fifty thousand gold pieces a year. And of course, you will\n" - "provide a steady supply of bananas. Hmm...\"\n")); + "provide a steady supply of bananas. Hmm...\"")); - game::TextScreen(CONST_S("\"Hmm...\"\n")); + game::TextScreen(CONST_S("\"Hmm...\"")); game::TextScreen(CONST_S("\"Very well! In that case, trouble yourself not with the master torturer,\n" "nor with the high priest. I will handle things here, as long as you handle\n" "things in your village. We have a deal. Hmm...\"\n" "\n" - "\"Congratulations, mister. It's nice to meet the new viceroy of Tweraif.\"\n")); + "\"Congratulations, mister. It's nice to meet the new viceroy of Tweraif.\"")); game::PlayVictoryMusic(); game::TextScreen(CONST_S("You are victorious!")); @@ -5580,13 +5665,15 @@ void darkknight::SpecialBodyPartSeverReaction() if(CanBeSeenByPlayer()) { ADD_MESSAGE("%s screams a profane incantation to Infuscor before disappearing.", CHAR_NAME(DEFINITE)); - TeleportRandomly(true); } + if(Called->CanBeSeenByPlayer()) ADD_MESSAGE("The whole area trembles terribly as %s emerges from the shadows.", Called->CHAR_NAME(INDEFINITE)); } else ADD_MESSAGE("You feel the sudden presence of a violent enemy nearby."); + + TeleportRandomly(true); } } @@ -5722,14 +5809,7 @@ void zombie::PostConstruct() truth orc::MoveRandomly() { - if(GetConfig() == REPRESENTATIVE) - { - return MoveRandomlyInRoom(); - } - else - { - return humanoid::MoveRandomly(); - } + return GetConfig() == REPRESENTATIVE ? MoveRandomlyInRoom() : humanoid::MoveRandomly(); } void orc::PostConstruct() @@ -5766,7 +5846,7 @@ void golem::CreateCorpse(lsquare* Square) } else if(Material->IsGaseous()) { - GetLevel()->GasExplosion(static_cast(Material), GetLSquareUnder(), this); + game::GetCurrentLevel()->GasExplosion(static_cast(Material), Square, this); } else if(Material->IsSolid()) { @@ -5826,6 +5906,19 @@ int darkmage::GetSpellAPCost() const return 4000; } +int aslonawizard::GetSpellAPCost() const +{ + /*switch(GetConfig()) + { + case APPRENTICE: return 4000; + case BATTLE_MAGE: return 2000; + case ELDER: return 1000; + case ARCH_MAGE: return 500; + }*/ + + return 1000; +} + int necromancer::GetSpellAPCost() const { switch(GetConfig()) @@ -5900,6 +5993,21 @@ void tailor::BeTalkedTo() void veterankamikazedwarf::PostConstruct() { kamikazedwarf::PostConstruct(); + + ivantime Time; + game::GetTime(Time); + int Modifier = Time.Day - KAMIKAZE_INVISIBILITY_DAY_MIN; + + if(Time.Day >= KAMIKAZE_INVISIBILITY_DAY_MAX + || (Modifier > 0 + && RAND_N(KAMIKAZE_INVISIBILITY_DAY_MAX - KAMIKAZE_INVISIBILITY_DAY_MIN) < Modifier)) + GainIntrinsic(INVISIBLE); +} + +void imp::PostConstruct() +{ + humanoid::PostConstruct(); + ivantime Time; game::GetTime(Time); int Modifier = Time.Day - KAMIKAZE_INVISIBILITY_DAY_MIN; @@ -5910,6 +6018,23 @@ void veterankamikazedwarf::PostConstruct() GainIntrinsic(INVISIBLE); } +void siren::PostConstruct() +{ + humanoid::PostConstruct(); + + if(GetConfig() != AMBASSADOR_SIREN) + { + ivantime Time; + game::GetTime(Time); + int Modifier = Time.Day - KAMIKAZE_INVISIBILITY_DAY_MIN; + + if(Time.Day >= KAMIKAZE_INVISIBILITY_DAY_MAX + || (Modifier > 0 + && RAND_N(KAMIKAZE_INVISIBILITY_DAY_MAX - KAMIKAZE_INVISIBILITY_DAY_MIN) < Modifier)) + GainIntrinsic(INVISIBLE); + } +} + truth humanoid::IsTransparent() const { return character::IsTransparent() || !(GetRightLeg() || GetLeftLeg()); @@ -5996,6 +6121,9 @@ void priest::GetAICommand() CallForMonsters(); } + if(CheckAIZapOpportunity()) + return; + StandIdleAI(); } @@ -6293,21 +6421,23 @@ cchar* humanoid::GetRunDescriptionLine(int I) const return !I ? GetRunDescriptionLineOne().CStr() : GetRunDescriptionLineTwo().CStr(); if(IsFlying()) - return !I ? "Flying" : "very fast"; + return !I ? "Flying" : " very fast"; if(IsSwimming()) { - if(!GetRightArm() && !GetLeftArm() && !GetRightLeg() && !GetLeftLeg()) - return !I ? "Floating" : "ahead fast"; + if(IsPlayer() && game::IsInWilderness() && game::PlayerHasBoat()) + return !I ? "Sailing" : " very fast"; + else if(!GetRightArm() && !GetLeftArm() && !GetRightLeg() && !GetLeftLeg()) + return !I ? "Floating" : " ahead fast"; else - return !I ? "Swimming" : "very fast"; + return !I ? "Swimming" : " very fast"; } if(!GetRightLeg() && !GetLeftLeg()) - return !I ? "Rolling" : "very fast"; + return !I ? "Rolling" : " very fast"; if(!GetRightLeg() || !GetLeftLeg()) - return !I ? "Hopping" : "very fast"; + return !I ? "Hopping" : " very fast"; return !I ? "Running" : ""; } @@ -6315,7 +6445,7 @@ cchar* humanoid::GetRunDescriptionLine(int I) const cchar* humanoid::GetNormalDeathMessage() const { if(BodyPartIsVital(HEAD_INDEX) && (!GetHead() || GetHead()->GetHP() <= 0)) - return "beheaded @k"; + return !RAND_2 ? "beheaded @k" : "decapitated @k"; else if(BodyPartIsVital(GROIN_INDEX) && (!GetGroin() || GetGroin()->GetHP() <= 0)) return "killed @bkp dirty attack below the belt"; else @@ -6519,22 +6649,68 @@ void petrusswife::BeTalkedTo() void guard::BeTalkedTo() { - if(GetPos().IsAdjacent(PLAYER->GetPos())) + if(!(GetConfig() == EMISSARY) || GetRelation(PLAYER) == HOSTILE) { - itemvector Item; + if(GetPos().IsAdjacent(PLAYER->GetPos()) && !(GetRelation(PLAYER) == HOSTILE)) + { + itemvector Item; + + if(!PLAYER->SelectFromPossessions(Item, CONST_S("Do you have something to give me?"), 0, &item::IsBeverage) + || Item.empty()) + + + for(size_t c = 0; c < Item.size(); ++c) + { + Item[c]->RemoveFromSlot(); + GetStack()->AddItem(Item[c]); + } + } - if(!PLAYER->SelectFromPossessions(Item, CONST_S("Do you have something to give me?"), 0, &item::IsBeverage) - || Item.empty()) + humanoid::BeTalkedTo(); + return; + } + /* We're talking to the emissary of Aslona from now on. */ + if(game::GetStoryState() == 1) + { + ADD_MESSAGE("%s eyes you, calculating: \"I might have work for you and I can make it worth your while.\"", CHAR_NAME(DEFINITE)); - for(size_t c = 0; c < Item.size(); ++c) + if(game::TruthQuestion(CONST_S("Do you accept the quest? [y/N]"), REQUIRES_ANSWER)) { - Item[c]->RemoveFromSlot(); - GetStack()->AddItem(Item[c]); + game::TextScreen(CONST_S("\"I shouldn't be saying this so openly, but my kingdom is in dire straits and needs any\n" + "help it can get. High priest Petrus will not hear my pleas and I don't believe that\n" + "my colleagues in other lands will be more successful. Lord Regent is doing his best,\n" + "but his army just barely holds the rebels back.\"\n\n" + "\"I know you are just one man, but maybe you could help where an army couldn't. Please,\n" + "go to the Castle of Aslona and seek out Lord Efra Peredivall. He will know what must\n" + "be done to mercilessly crush the rebel scum!\"")); + + game::GivePlayerBoat(); + game::LoadWorldMap(); + v2 AslonaPos = game::GetWorldMap()->GetEntryPos(0, ASLONA_CASTLE); + game::GetWorldMap()->GetWSquare(AslonaPos)->ChangeOWTerrain(aslonacastle::Spawn()); + game::GetWorldMap()->RevealEnvironment(AslonaPos, 1); + v2 RebelCampPos = game::GetWorldMap()->GetEntryPos(0, REBEL_CAMP); + game::GetWorldMap()->GetWSquare(RebelCampPos)->ChangeOWTerrain(rebelcamp::Spawn()); + game::GetWorldMap()->RevealEnvironment(RebelCampPos, 0); + game::SaveWorldMap(); + GetArea()->SendNewDrawRequest(); + ADD_MESSAGE("\"If you need to cross the sea, you can use my ship. It should be waiting at the shore.\""); + game::SetAslonaStoryState(1); + game::SetStoryState(2); + return; + } + else + { + ADD_MESSAGE("%s narrows his eyes: \"I would think twice about brushing me aside, if I were you. Think about it.\"", CHAR_NAME(DEFINITE)); + return; } } - humanoid::BeTalkedTo(); + if(game::GetAslonaStoryState() && !RAND_N(4)) // Isn't he charming? + ADD_MESSAGE("\"You should know that I'm counting on you. My whole country is counting on you. Don't screw it up!\""); + else + humanoid::BeTalkedTo(); } void xinrochghost::GetAICommand() @@ -6711,9 +6887,17 @@ void goblin::GetAICommand() humanoid::GetAICommand(); } +void cossack::GetAICommand() +{ + if(CheckAIZapOpportunity()) + return; + + humanoid::GetAICommand(); +} + void werewolfwolf::GetAICommand() { - if(GetConfig() == DRUID && !RAND_2) + if(GetConfig() == DRUID && !RAND_N(4)) if(CheckAIZapOpportunity()) return; @@ -6722,13 +6906,42 @@ void werewolfwolf::GetAICommand() truth humanoid::CheckAIZapOpportunity() { - if(!HasAUsableArm() || !CanZap() || !(RAND() % 2) || StateIsActivated(CONFUSED)) + if(!HasAUsableArm() || StateIsActivated(CONFUSED)) return false; else return character::CheckAIZapOpportunity(); } -truth imp::SpecialBiteEffect(character* Victim, v2 HitPos, int BodyPartIndex, int Direction, truth BlockedByArmour, truth Critical, int DoneDamage) +truth imp::SpecialEnemySightedReaction(character* Char) +{ + if(GetPos().IsAdjacent(Char->GetPos())) + { + if((StateIsActivated(PANIC) || IsInBadCondition()) && !RAND_4) + { + if(CanBeSeenByPlayer()) + ADD_MESSAGE("%s shrieks!", CHAR_NAME(DEFINITE)); + + TeleportRandomly(true); + return true; + } + else + return false; + } + + if(GetHP() > (GetMaxHP() / 2) && !RAND_N(10)) + { + if(CanBeSeenByPlayer()) + ADD_MESSAGE("%s chortles.", CHAR_NAME(DEFINITE)); + + TeleportNear(Char); + return true; + } + + return false; +} + +truth crimsonimp::SpecialBiteEffect(character* Victim, v2 HitPos, int BodyPartIndex, int Direction, + truth BlockedByArmour, truth Critical, int DoneDamage) { bodypart* BodyPart = Victim->GetBodyPart(BodyPartIndex); @@ -6746,6 +6959,46 @@ truth imp::SpecialBiteEffect(character* Victim, v2 HitPos, int BodyPartIndex, in return false; } +void crimsonimp::CreateCorpse(lsquare* Square) +{ + game::GetCurrentLevel()->Explosion(this, "consumed by the hellfire of " + GetName(INDEFINITE), + Square->GetPos(), 20 + RAND() % 5 - RAND() % 5); +} + +truth mirrorimp::DrinkMagic(const beamdata& Beam) +{ + if(!Beam.Wand) + return false; + if(!Beam.Wand->IsExplosive()) + return false; + + if(Beam.Owner && RAND_N(GetAttribute(MANA)) <= RAND_N(Beam.Owner->GetAttribute(WILL_POWER))) + { + Beam.Owner->EditExperience(WILL_POWER, 100, 1 << 12); + return false; + } + + festring DeathMsg = CONST_S("killed by an explosion of "); + Beam.Wand->AddName(DeathMsg, INDEFINITE); + DeathMsg << " caused @bk"; + + if(IsPlayer()) + ADD_MESSAGE("You grin as %s %s.", Beam.Wand->GetExtendedDescription().CStr(), Beam.Wand->GetBreakMsg().CStr()); + else if(CanBeSeenByPlayer()) + ADD_MESSAGE("%s cackles with glee as %s %s.", CHAR_NAME(DEFINITE), Beam.Wand->GetExtendedDescription().CStr(), + Beam.Wand->GetBreakMsg().CStr()); + + Beam.Wand->BreakEffect(this, DeathMsg); + return true; +} + +void mirrorimp::CreateCorpse(lsquare* Square) +{ + decoration* Shard = decoration::Spawn(SHARD); + Shard->InitMaterials(MAKE_MATERIAL(GLASS)); + Square->ChangeOLTerrainAndUpdateLights(Shard); +} + void elder::BeTalkedTo() { if(game::TweraifIsFree() && !game::GetFreedomStoryState() && !HasBeenSpokenTo @@ -6755,35 +7008,29 @@ void elder::BeTalkedTo() "I knew there was something special in you, something even\n" "the accursed hippos couldn't spoil. And now you have saved us\n" "from valpurian clutches and given us a chance at freedom!\n" - "Thank you so very, very much.\"\n" - "\n" + "Thank you so very, very much.\"\n\n" "\"Alas, I'm afraid Tweraif is not yet out of the proverbial woods.\n" "We are few and the Attnamese army is massive. Their battleships\n" "will be ready once the winter ends and the ice thaws, and they will\n" "not hesitate to bring their tyranny back. I still don't get why they\n" - "love those bananas so much.\"\n" - "\n" - "\"We have no hope to defeat them in a fight, so fight them we shan't.\"\n")); + "love those bananas so much.\"\n\n" + "\"We have no hope to defeat them in a fight, so fight them we shan't.\"")); - game::TextScreen(CONST_S("\"Let me tell you a story, or a myth if you will.\"\n" - "\n" + game::TextScreen(CONST_S("\"Let me tell you a story, or a myth if you will.\"\n\n" "\"Once upon a time, there was a town. No one could find the town\n" "unless they already knew where it was, and on one could enter\n" "uninvited. The town was called Mondedr and it was concealed\n" - "from the world by the power of Cleptia. It was never conquered.\"\n" - "\n" + "from the world by the power of Cleptia. It was never conquered.\"\n\n" "\"The thing is, I know for a fact that Mondedr exists, and that\n" "their cloaking spell can be replicated. Attnam tried to take our\n" "goddess away, but she is still strong in our hearts. I have faith\n" "she will protect this island from valpurians, just as Cleptia did\n" - "for Mondedr.\"\n")); + "for Mondedr.\"")); game::TextScreen(CONST_S("\"The prayers are simple, but no god can affect the world uninvited,\n" "and a miracle of such strength requires more power than any priest\n" - "could channel. We need a conduit, something close to Silva herself.\"\n" - "\n" - "\"We need a scion of the Holy Mango World-tree.\"\n" - "\n" + "could channel. We need a conduit, something close to Silva herself.\"\n\n" + "\"We need a scion of the Holy Mango World-tree.\"\n\n" "\"You have done so much for your village, yet I must ask for another\n" "favour. You know that the late viceroy destroyed the altar of Silva\n" "in our shrine, but you might not know that there is another shrine of Silva\n" @@ -6792,13 +7039,13 @@ void elder::BeTalkedTo() "buried the stairs to the crystal cave of Silva under a cave-in,\n" "once it was obvious that we will be conquered. We couldn't let the Attnamese\n" "desecrate that most holy place. There, in an ancient temple of Silva,\n" - "grows a tree of wondrous power, a tiny sapling of the World-tree.\"\n")); + "grows a tree of wondrous power, a tiny sapling of the World-tree.\"")); game::TextScreen(CONST_S("\"Please, bring back a seedling of this tree. Once we plant it here,\n" "in the village, I can cast the spell and no army will find us.\n" "The first valpurian attack surprised us, caught us unaware, unprepared\n" "and unable to defend our land. So let's not repeat history and\n" - "get ready for them this time.\"\n")); + "get ready for them this time.\"")); game::SetFreedomStoryState(1); GetArea()->SendNewDrawRequest(); @@ -6819,36 +7066,32 @@ void terra::BeTalkedTo() if((game::GetFreedomStoryState() == 1) && !HasBeenSpokenTo && !(GetRelation(PLAYER) == HOSTILE) && GetPos().IsAdjacent(PLAYER->GetPos())) { - game::TextScreen(CONST_S("\"Tweraif has been freed?! What wonderful news you bring me!\"\n" - "\n" + game::TextScreen(CONST_S("\"Tweraif has been freed?! What wonderful news you bring me!\"\n\n" "\"I have volunteered all those years ago to be buried here in this cave\n" "along with the shrine, to tend it and to protect the rites and traditions\n" "that the Attnamese would rather see burnt and forgotten. Yet I have hoped\n" "every day that a word would come about an end to the tyranny, that\n" "I would be free to return home. I guess my hope dwindled over the years,\n" - "but you are here now and my wishes came true. Thank you.\"\n" - "\n" + "but you are here now and my wishes came true. Thank you.\"\n\n" "\"Nevertheless, I know what you came for. A seedling of this holy tree,\n" "to channel the power of Silva and shroud Tweraif against further attacks.\n" - "I wish it was that simple, but I have no seeds to give you.\"\n")); + "I wish it was that simple, but I have no seeds to give you.\"")); game::TextScreen(CONST_S("\"You see, this shrine is built in a remote, lost cave for a reason.\n" - "It is a guarding post, a bulwark, and a seal on a prison.\"\n" - "\n" + "It is a guarding post, a bulwark, and a seal on a prison.\"\n\n" "\"One thousand years ago, Nefas, the goddess of forbidden pleasures,\n" "came to Scabies, the goddess of diseases, mutations and deformity,\n" "in the form of a handsome hero, and seduced her. Whether it was\n" "for Nefas' own amusement, or the humiliation Scabies suffered\n" "when she discovered who she laid with, no one knows, but Scabies got\n" "pregnant and eventually delivered a divine baby - a monstrous spider\n" - "the likes of which this world had never seen before.\"\n" - "\n" + "the likes of which this world had never seen before.\"\n\n" "\"The spider was a behemoth of her kind, massive and terrifying\n" "and truly detestable. Spurned and abandoned by both her mothers,\n" "the spider rampaged through the world until she was defeated and\n" "bound by a circle of druids and priests. Her name is Lobh-se and\n" "she is imprisoned below this cave, trapped by the power of Silva\n" - "channeled through the Holy Mango Tree.\"\n")); + "channeled through the Holy Mango Tree.\"")); game::TextScreen(CONST_S("\"Lobh-se is a terrible creature, an avatar of famine and consumption.\n" "She breeds thousands of lesser spiders and immediately devours them\n" @@ -6857,15 +7100,14 @@ void terra::BeTalkedTo() "plants that scrape a living this deep underground. I can somewhat keep her\n" "at bay, protecting myself and the tree, but the magic of the holy seedlings\n" "is sweet to Lobh-se, and not strong enough to ward her off. She devoured\n" - "the last seedling just a few days ago.\"\n" - "\n" + "the last seedling just a few days ago.\"\n\n" "\"You are a hero already for liberating our village,\n" "but if you really wish to ensure the safety of Tweraif, you have to venture\n" "deeper, to the very lair of Lobh-se. She may be a godling, but her body\n" "is still mortal. Cut the seedling from her gullet, and I will keep her spirit\n" - "bound so that it cannot create a new body to harass this world.\"\n")); + "bound so that it cannot create a new body to harass this world.\"")); - game::TextScreen(CONST_S("\"May Silva bless you in your doings.\"\n")); + game::TextScreen(CONST_S("\"May Silva bless you in your doings.\"")); GetArea()->SendNewDrawRequest(); ADD_MESSAGE("\"Oh, and give my love to Kaethos, if he's still alive.\""); @@ -6874,8 +7116,736 @@ void terra::BeTalkedTo() } else if((game::GetFreedomStoryState() == 2) && !(GetRelation(PLAYER) == HOSTILE)) { + priest::BeTalkedTo(); // in case player also needs a cure, before the tip (below) to grant it wont be ignored ADD_MESSAGE("\"You bested her, I see! Now hurry back to the village, and Attnam shall threaten us no more.\""); } else priest::BeTalkedTo(); } + +void aslonawizard::GetAICommand() +{ + SeekLeader(GetLeader()); + + if(FollowLeader(GetLeader())) + return; + + /* + * Teleports when in danger, otherwise either blinks his allies close to + * an enemy, or summons a gas golem. + */ + + character* NearestEnemy = 0; + long NearestEnemyDistance = 0x7FFFFFFF; + character* RandomFriend = 0; + charactervector Friend; + v2 Pos = GetPos(); + + for(int c = 0; c < game::GetTeams(); ++c) + { + if(GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE) + { + for(character* p : game::GetTeam(c)->GetMember()) + if(p->IsEnabled()) + { + long ThisDistance = Max(abs(p->GetPos().X - Pos.X), abs(p->GetPos().Y - Pos.Y)); + + if((ThisDistance < NearestEnemyDistance + || (ThisDistance == NearestEnemyDistance && !(RAND() % 3))) && p->CanBeSeenBy(this)) + { + NearestEnemy = p; + NearestEnemyDistance = ThisDistance; + } + } + } + else if(GetTeam()->GetRelation(game::GetTeam(c)) == FRIEND) + { + for(character* p : game::GetTeam(c)->GetMember()) + if(p->IsEnabled() && p->CanBeSeenBy(this)) + Friend.push_back(p); + } + } + + if(NearestEnemy && NearestEnemy->GetPos().IsAdjacent(Pos) && + (!(RAND() & 4) || StateIsActivated(PANIC))) + { + if(CanBeSeenByPlayer()) + ADD_MESSAGE("%s invokes a spell and disappears.", CHAR_NAME(DEFINITE)); + + TeleportRandomly(true); + EditAP(-GetSpellAPCost()); + return; + } + + if(!RAND_2 && CheckAIZapOpportunity()) + return; + + if(NearestEnemy && (NearestEnemyDistance < 10 || StateIsActivated(PANIC)) && RAND() & 3) + { + SetGoingTo((Pos << 1) - NearestEnemy->GetPos()); + + if(MoveTowardsTarget(true)) + return; + } + + if(Friend.size() && !(RAND() & 3)) + { + RandomFriend = Friend[RAND() % Friend.size()]; + NearestEnemy = 0; + } + + if(GetRelation(PLAYER) == HOSTILE && PLAYER->CanBeSeenBy(this)) + NearestEnemy = PLAYER; + + beamdata Beam + ( + this, + CONST_S("killed by the spells of ") + GetName(INDEFINITE), + YOURSELF, + 0 + ); + + if(NearestEnemy) + { + if(CanBeSeenByPlayer()) + ADD_MESSAGE("%s invokes a spell!", CHAR_NAME(DEFINITE)); + + if(RandomFriend && !RAND_N(4)) + { + EditAP(-GetSpellAPCost()); + + RandomFriend->GetLSquareUnder()->DrawParticles(RED); + RandomFriend->TeleportNear(NearestEnemy); + return; + } + else + { + lsquare* Square = NearestEnemy->GetLSquareUnder(); + character* ToBeCalled = 0; + + EditAP(-GetSpellAPCost()); + + int GasMaterial[] = { MUSTARD_GAS, MAGIC_VAPOUR, SLEEPING_GAS, TELEPORT_GAS, + EVIL_WONDER_STAFF_VAPOUR, EVIL_WONDER_STAFF_VAPOUR }; + ToBeCalled = golem::Spawn(GasMaterial[RAND() % 6]); + v2 Where = GetLevel()->GetNearestFreeSquare(ToBeCalled, Square->GetPos()); + + if(Where == ERROR_V2) + { + if(CanBeSeenByPlayer()) + ADD_MESSAGE("Nothing happens."); + + delete ToBeCalled; + } + else + { + ToBeCalled->SetGenerationDanger(GetGenerationDanger()); + ToBeCalled->SetTeam(GetTeam()); + ToBeCalled->PutTo(Where); + + if(ToBeCalled->CanBeSeenByPlayer()) + ADD_MESSAGE("Suddenly %s materializes!", ToBeCalled->CHAR_NAME(INDEFINITE)); + + ToBeCalled->GetLSquareUnder()->DrawParticles(RED); + } + + if(CanBeSeenByPlayer()) + NearestEnemy->DeActivateVoluntaryAction(CONST_S("The spell of ") + GetName(DEFINITE) + + CONST_S(" interrupts you.")); + else + NearestEnemy->DeActivateVoluntaryAction(CONST_S("The spell interrupts you.")); + + return; + } + } + + StandIdleAI(); +} + +int gasghoul::TakeHit(character* Enemy, item* Weapon, bodypart* EnemyBodyPart, v2 HitPos, double Damage, + double ToHitValue, int Success, int Type, int Direction, truth Critical, truth ForceHit) +{ + int Return = humanoid::TakeHit(Enemy, Weapon, EnemyBodyPart, HitPos, Damage, + ToHitValue, Success, Type, Direction, Critical, ForceHit); + + if(Return != HAS_DODGED && Return != HAS_BLOCKED && GetLSquareUnder()->IsFlyable()) + { + if(Enemy->IsPlayer()) + ADD_MESSAGE("%s releases a cloud of fumes as you strike %s.", CHAR_DESCRIPTION(DEFINITE), GetObjectPronoun().CStr()); + else if(IsPlayer()) + ADD_MESSAGE("You release a cloud of fumes as %s strikes you.", Enemy->CHAR_DESCRIPTION(DEFINITE)); + else if(CanBeSeenByPlayer() && Enemy->CanBeSeenByPlayer()) + ADD_MESSAGE("%s releases a cloud of fumes as %s strikes %s.", CHAR_DESCRIPTION(DEFINITE), Enemy->CHAR_DESCRIPTION(DEFINITE), + GetObjectPronoun().CStr()); + else if(CanBeSeenByPlayer()) + ADD_MESSAGE("%s releases a cloud of fumes as something strikes %s.", CHAR_DESCRIPTION(DEFINITE), GetObjectPronoun().CStr()); + + int GasMaterial[] = { MUSTARD_GAS, SKUNK_SMELL, ACID_GAS, FIRE_GAS }; + + if(Critical) + GetLevel()->GasExplosion(gas::Spawn(GasMaterial[RAND() % 4], 100), GetLSquareUnder(), this); + else + GetLSquareUnder()->AddSmoke(gas::Spawn(GasMaterial[RAND() % 4], 100)); + } + + return Return; +} + +void elder::Save(outputfile& SaveFile) const +{ + humanoid::Save(SaveFile); + SaveFile << HasBeenSpokenTo; +} + +void elder::Load(inputfile& SaveFile) +{ + humanoid::Load(SaveFile); + SaveFile >> HasBeenSpokenTo; +} + +void terra::Save(outputfile& SaveFile) const +{ + humanoid::Save(SaveFile); + SaveFile << HasBeenSpokenTo; +} + +void terra::Load(inputfile& SaveFile) +{ + humanoid::Load(SaveFile); + SaveFile >> HasBeenSpokenTo; +} + +void aslonawizard::Save(outputfile& SaveFile) const +{ + humanoid::Save(SaveFile); + SaveFile << HasBeenSpokenTo; +} + +void aslonawizard::Load(inputfile& SaveFile) +{ + humanoid::Load(SaveFile); + SaveFile >> HasBeenSpokenTo; +} + +void aslonacaptain::Save(outputfile& SaveFile) const +{ + humanoid::Save(SaveFile); + SaveFile << HasBeenSpokenTo; +} + +void aslonacaptain::Load(inputfile& SaveFile) +{ + humanoid::Load(SaveFile); + SaveFile >> HasBeenSpokenTo; +} + +void aslonapriest::Save(outputfile& SaveFile) const +{ + humanoid::Save(SaveFile); + SaveFile << HasBeenSpokenTo; +} + +void aslonapriest::Load(inputfile& SaveFile) +{ + humanoid::Load(SaveFile); + SaveFile >> HasBeenSpokenTo; +} + +void aslonawizard::BeTalkedTo() +{ + if(GetPos().IsAdjacent(PLAYER->GetPos()) && !(GetRelation(PLAYER) == HOSTILE)) + { + if((game::GetAslonaStoryState() > 1) && !HasBeenSpokenTo) + { + game::TextScreen(CONST_S("\"You have been sent by Lord Regent? Excellent! As the only protector of Aslona from\n" + "supernatural incursions, I cannot leave the castle, so I shall welcome any help you\n" + "can provide.\"\n\n" + "\"My request for you might cause some to brand me a coward. But no matter. We must win\n" + "the war quickly, or Aslona looses even if we eventually achieve victory. Swords and\n" + "spells may have won earlier battles, but now we need to end the rebels in one fell swoop.\n" + "Lord Mittrars is already working hard on locating the command centre of Harvan's forces,\n" + "so my task is to arrange for the weapon.\"\n\n" + "\"My research shows that such a weapon capable of complete obliteration appears in many\n" + "tales from the long lost empire of Otoul'iv Ik-Omit. I have uncovered the site of\n" + "a pyramid dating back to that era, and all sources indicate that deep within, an untouched\n" + "arms depot should be located.\"\n\n" + "\"Please fetch me this mighty weapon post haste. I'm sure you will recognize it once you see it.\"")); + + game::LoadWorldMap(); + v2 PyramidPos = game::GetWorldMap()->GetEntryPos(0, PYRAMID); + game::GetWorldMap()->GetWSquare(PyramidPos)->ChangeOWTerrain(pyramid::Spawn()); + game::GetWorldMap()->RevealEnvironment(PyramidPos, 1); + game::SaveWorldMap(); + + GetArea()->SendNewDrawRequest(); + ADD_MESSAGE("%s says with a concern: \"Be careful, the pyramid is said to be a dangerous place. Better be prepared when you go there.\"", CHAR_NAME(DEFINITE)); + + HasBeenSpokenTo = true; + return; + } + else if(PLAYER->HasNuke()) + { + if(game::TruthQuestion(CONST_S("Turn in the thaumic bomb? [y/N]"), REQUIRES_ANSWER)) + { + PLAYER->RemoveNuke(this); + ADD_MESSAGE("%s beams: \"Yes! Thank you, thank you! With this, we can blow the rebels to tiny bits, we can end the war!\"", CHAR_NAME(DEFINITE)); + game::SetAslonaStoryState(game::GetAslonaStoryState() + 1); + return; + } + } + } + + humanoid::BeTalkedTo(); +} + +void aslonawizard::CreateCorpse(lsquare* Square) +{ + game::GetCurrentLevel()->GasExplosion(gas::Spawn(MAGIC_VAPOUR, 100), Square, this); + SendToHell(); +} + +void aslonacaptain::BeTalkedTo() +{ + if(GetPos().IsAdjacent(PLAYER->GetPos()) && !(GetRelation(PLAYER) == HOSTILE)) + { + if((game::GetAslonaStoryState() > 1) && !HasBeenSpokenTo) + { + game::TextScreen(CONST_S("\"Finally someone not tangled up in their responsibilities! I was almost ready to drop\n" + "everything and go myself, but thankfully you are here.\"\n\n" + "\"Let me explain. When king Othyr died, Seges rest his soul, and Harvan fled justice to\n" + "start the rebellion, the crown prince, His Highness Artorius, was away on a visit to Castle\n" + "Noth. I sent for him to immediately return back to the Castle of Aslona, both for safety\n" + "and to pay respects to his father. But his retinue never arrived. My scouts found marks\n" + "of an ambush and tracked the responsible raiding party of goblins back to their lair\n" + "in a nearby ruined fort, where we also believe they imprisoned the young prince.\"\n\n" + "\"I would have loved to throw the whole army of Aslona at the goblins, but with\n" + "the ceaseless attacks of rebel squads, that would mean sacrificing the kingdom.\n" + "I was forced to wait for a momentary respite from the fighting, or for some trustworthy\n" + "outsider willing to go on a rescue mission.\"\n\n" + "\"It tears at my heart that poor prince languishes in some jail cell, as if his father's\n" + "death wasn't hard on him already. Save prince Artorius, I beg of you, and bring him to me\n" + "so that I need to worry no more.\"")); + + game::LoadWorldMap(); + v2 GoblinPos = game::GetWorldMap()->GetEntryPos(0, GOBLIN_FORT); + game::GetWorldMap()->GetWSquare(GoblinPos)->ChangeOWTerrain(goblinfort::Spawn()); + game::GetWorldMap()->RevealEnvironment(GoblinPos, 1); + game::SaveWorldMap(); + + GetArea()->SendNewDrawRequest(); + ADD_MESSAGE("%s says: \"Hurry, please.\"", CHAR_NAME(DEFINITE)); + + HasBeenSpokenTo = true; + return; + } + else if(HasBeenSpokenTo) + { + // Does the player have prince Artorius in his team? + character* CrownPrince = 0; + for(character* p : game::GetTeam(PLAYER_TEAM)->GetMember()) + if(p->IsEnabled() && !p->IsPlayer() && p->IsKing()) + CrownPrince = p; + + if(CrownPrince) + { + if(game::TruthQuestion(CONST_S("Entrust young prince to Lord Mittrars' care? [y/N]"), REQUIRES_ANSWER)) + { + team* Team = game::GetTeam(ASLONA_TEAM); + CrownPrince->ChangeTeam(Team); + + ADD_MESSAGE("\"Uncle Mittrars!\""); + ADD_MESSAGE("\"My prince! Thanks Seges and all the gods of Law, I was already loosing any hope that I will see you again.\""); + game::SetAslonaStoryState(game::GetAslonaStoryState() + 1); + return; + } + } + } + } + + humanoid::BeTalkedTo(); +} + +void aslonapriest::BeTalkedTo() +{ + if(GetPos().IsAdjacent(PLAYER->GetPos()) && !(GetRelation(PLAYER) == HOSTILE)) + { + if((game::GetAslonaStoryState() > 1) && !HasBeenSpokenTo) + { + game::TextScreen(CONST_S("\"Yes, I can most definitely put your skills to good use. You see, this senseless\n" + "war has already claimed many lives by the blades of the soldiers, but a more insidious\n" + "enemy is rearing her ugly head. I'm talking about Scabies. With many injured and access\n" + "to supplies limited, diseases are starting to spread, and even my skills and magic\n" + "are not enough without proper medical care and hygiene. Hygiene is essential to health,\n" + "but essential to hygiene is access to clean water, which is one of the things this castle\n" + "lacks right now.\"\n\n" + "\"I have found some adventurer's journal describing an artifact that might help solve\n" + "our troubles. A single tear of Silva, petrified into the form of an obsidian shard, yet still\n" + "weeping with rains of freshwater. I would ask of you to retrieve this shard for me. It should\n" + "be located in a nearby coal cave, though you should know the journal mentioned strange fungal\n" + "growths appearing in the cave, nourished by the life-giving water.\"")); + + game::LoadWorldMap(); + v2 CavePos = game::GetWorldMap()->GetEntryPos(0, FUNGAL_CAVE); + game::GetWorldMap()->GetWSquare(CavePos)->ChangeOWTerrain(fungalcave::Spawn()); + game::GetWorldMap()->RevealEnvironment(CavePos, 1); + game::SaveWorldMap(); + + GetArea()->SendNewDrawRequest(); + ADD_MESSAGE("%s says: \"Thank you very much for your kind help.\"", CHAR_NAME(DEFINITE)); + + HasBeenSpokenTo = true; + return; + } + else if(PLAYER->HasWeepObsidian()) + { + if(game::TruthQuestion(CONST_S("Turn in the weeping obsidian? [y/N]"), REQUIRES_ANSWER)) + { + PLAYER->RemoveWeepObsidian(this); + ADD_MESSAGE("%s says: \"Wonderful! Let me get to work right away.\"", CHAR_NAME(DEFINITE)); + game::SetAslonaStoryState(game::GetAslonaStoryState() + 1); + return; + } + } + } + + priest::BeTalkedTo(); +} + +void harvan::BeTalkedTo() +{ + if(GetPos().IsAdjacent(PLAYER->GetPos()) && !(GetRelation(PLAYER) == HOSTILE)) + { + if(!game::GetRebelStoryState()) + { + game::TextScreen(CONST_S("\"Well met, adventurer! It's always nice to talk to someone unaffected by\n" + "the unfortunate quarrels of our kingdom.\"\n\n" + "\"I don't know how much have you heard, but our old king Othyr was murdered\n" + "and then even the crown prince Artorius disappeared in an alleged goblin raid.\n" + "Immediately, Lord Efra Peredivall named himself the Lord Regent of Aslona and\n" + "made it known that he intends to *use* his newfound power. I have known\n" + "Lord Peredivall for most of my life, I used to call him my friend, but I cannot\n" + "stand for this high treason, and the people of Aslona support me. But now we must\n" + "hide in the woods like brigands, hunted by those corrupt or mislead by\n" + "Lord Regent's lies. I fear we will need some edge if we want to win this war\n" + "without drowning in blood.\"")); + + game::TextScreen(CONST_S("\"Ah-hah! And here I'm talking without realizing you could be of a great help!\n" + "You are an obvious foreigner, hardly suspected of any connections to us. You could\n" + "infiltrate Lord Regent's forces and try to glean his plans. Maybe even offer\n" + "your services and whatever he asks of you to acquire, bring to us instead!\n" + "Yes, go to the Castle of Aslona, help our cause and once we achieve victory,\n" + "I will reward you handsomely.\"")); + + GetArea()->SendNewDrawRequest(); + ADD_MESSAGE("%s pats you on the back. \"Good luck and return soon, my friend.\"", CHAR_NAME(DEFINITE)); + game::SetRebelStoryState(2); // To have same StoryState values as Aslona. + return; + } + else if(PLAYER->HasMasamune()) + { + if(game::TruthQuestion(CONST_S("Turn in the noble katana named E-numa sa-am? [y/N]"), REQUIRES_ANSWER)) + { + game::PlayVictoryMusic(); + game::TextScreen(CONST_S("You hand over the ancient katana and thus the regalia necessary to crown a new\n" + "king of Aslona are together again.\n\n" + "\"Thank you, my friend,\" Harvan says and then turns to his army. \"Comrades,\n" + "this is our hour of victory. You have fought well. You are heroes, all of you.\n" + "Our country and the very soul of our nation was saved only thanks to you, thanks\n" + "to your courage, loyalty, selflessness and resolve. But now, the war is over.\n" + "The traitors shall face justice and peace will return to our homes. Gods bless Aslona!\"\n\n" + "A deafening cheer echoes his words.\n\nYou are victorious!")); + + game::GetCurrentArea()->SendNewDrawRequest(); + game::DrawEverything(); + PLAYER->ShowAdventureInfo(); + + // Did the player do all quests just for rebels? + festring Msg; + if(game::GetRebelStoryState() == 5) + { + Msg = CONST_S("helped the rebels to an overwhelming victory"); + AddScoreEntry(Msg, 4, false); + } + else + { + Msg = CONST_S("helped the rebels to an uneasy victory"); + AddScoreEntry(Msg, 2, false); + } + + game::End(Msg); + return; + } + } + else if(PLAYER->HasNuke()) + { + if(game::TruthQuestion(CONST_S("Turn in the thaumic bomb? [y/N]"), REQUIRES_ANSWER)) + { + PLAYER->RemoveNuke(this); + ADD_MESSAGE("\"So this is the fate Lord Regent had planned for me and my people. Thank you for saving all of our lives, %s.\"", + PLAYER->GetAssignedName().CStr()); + game::SetRebelStoryState(game::GetRebelStoryState() + 1); + return; + } + } + else if(PLAYER->HasWeepObsidian()) + { + if(game::TruthQuestion(CONST_S("Turn in the weeping obsidian? [y/N]"), REQUIRES_ANSWER)) + { + PLAYER->RemoveWeepObsidian(this); + ADD_MESSAGE("\"The royalists are starting to feel their isolation in the castle, it would seem. Sooner or later, they will loose their strength to oppose us.\""); + game::SetRebelStoryState(game::GetRebelStoryState() + 1); + return; + } + } + else if(game::GetRebelStoryState() == 5 && game::GetStoryState() < 3) + { + game::TextScreen(CONST_S("\"You are a hero to me, you should know that. You have already done so much,\n" + "yet I must ask for one last favor.\"\n\n" + "\"This war is costly in both innocent lives and money we cannot spare,\n" + "it needs to end right now. Thanks to your help, we have the thaumic bomb and\n" + "could level the whole castle and wipe Lord Regent and his royalists from the surface\n" + "of the world, but I'm unwilling to sacrifice everyone in the castle and the castle\n" + "with itself. It would be a hollow victory to kill the traitor, but loose the kingdom\n" + "when the symbol of our proud history was in ruins.\"\n\n" + "\"Yet there is a third option. It is my sincere belief that many of Lord Peredivall's\n" + "troops would change sides if prince Artorius took on the title of his father as\n" + "the rightful king of Aslona. But for His Highness to be crowned the king without\n" + "any doubts or dispute, we need the regalia of Aslona, two ancient, masterwork katanas,\n" + "Asamarum and E-numa sa-am.\"")); + + game::TextScreen(CONST_S("\"I have managed to take Asamarum with me during Lord Regent's coup, but he still\n" + "holds E-numa sa-am. One last time, I would ask you to steal into the Castle of Aslona\n" + "and retrieve the sword from Lord Regent, so that we might claim victory without\n" + "bloodshed.\"")); + + GetArea()->SendNewDrawRequest(); + PLAYER->GetTeam()->Hostility(game::GetTeam(ASLONA_TEAM)); // Too easy otherwise. + ADD_MESSAGE("%s hugs you tightly. \"Godspeed, my friend.\"", CHAR_NAME(DEFINITE)); + game::SetStoryState(3); + return; + } + else + { + // Does the player have prince Artorius in his team? + character* CrownPrince = 0; + for(character* p : game::GetTeam(PLAYER_TEAM)->GetMember()) + if(p->IsEnabled() && !p->IsPlayer() && p->IsKing()) + CrownPrince = p; + + if(CrownPrince) + { + if(game::TruthQuestion(CONST_S("Entrust young prince to Harvan's care? [y/N]"), REQUIRES_ANSWER)) + { + team* Team = game::GetTeam(REBEL_TEAM); + CrownPrince->ChangeTeam(Team); + + ADD_MESSAGE("\"Hi, uncle Harvan! Where are we? When are we gonna go home?\""); + ADD_MESSAGE("\"Your Highness, I'm so very glad to see you. Don't worry, I will take you home soon.\""); + game::SetRebelStoryState(game::GetRebelStoryState() + 1); + return; + } + } + } + } + + humanoid::BeTalkedTo(); +} + +truth harvan::SpecialEnemySightedReaction(character* Char) +{ + if(!Char->IsPlayer() || GetPos().IsAdjacent(Char->GetPos())) + return false; + + if(GetHP() > (GetMaxHP() / 2) && !RAND_N(10)) + { + if(CanBeSeenByPlayer()) + { + ADD_MESSAGE("%s screams at you: \"Get over here!\"", CHAR_NAME(DEFINITE)); + } + else + ADD_MESSAGE("\"Get over here!\""); + + Char->TeleportNear(this); + } + + return false; +} + +void lordregent::BeTalkedTo() +{ + if(GetPos().IsAdjacent(PLAYER->GetPos()) && !(GetRelation(PLAYER) == HOSTILE)) + { + if(game::GetAslonaStoryState() == 1) + { + game::TextScreen(CONST_S("\"Sir Lancelyn sent you? He thought you can be of any use to me? I doubt that,\n" + "but then again maybe there is something in you, we shall see.\"\n\n" + "\"Several of my advisors have been complaining lately that we don't have enough\n" + "expendable personnel to send on special missions. Go see Lord Mittrars,\n" + "Myrddin the wizard and Senex of Seges, and report back once they are happy.\"\n\n" + "\"Now away with you, I have other things to worry about.\"")); + + GetArea()->SendNewDrawRequest(); + ADD_MESSAGE("%s sighs: \"I don't have time for this.\"", CHAR_NAME(DEFINITE)); + game::SetAslonaStoryState(2); + return; + } + else if(PLAYER->HasMuramasa()) + { + if(game::TruthQuestion(CONST_S("Turn in the wicked katana named Asa'marum? [y/N]"), REQUIRES_ANSWER)) + { + game::PlayVictoryMusic(); + game::TextScreen(CONST_S("You hand over the ancient katana and thus the regalia necessary to crown a new\n" + "king of Aslona are together again.\n\n" + "\"I am in your debt,\" Lord Peredivall says and then turns to his army. \"Citizens,\n" + "this is our hour of victory. You have fought well. You are heroes, all of you.\n" + "Our country and the very soul of our nation was saved only thanks to you, thanks\n" + "to your bravery, honor, devotion and determination. But now, the war is over.\n" + "The traitors shall face justice and peace will return to our homes. Gods bless Aslona!\"\n\n" + "A deafening cheer echoes his words.\n\nYou are victorious!")); + + game::GetCurrentArea()->SendNewDrawRequest(); + game::DrawEverything(); + PLAYER->ShowAdventureInfo(); + + // Did the player do all quests just for the royalists? + festring Msg; + if(game::GetAslonaStoryState() == 5) + { + Msg = CONST_S("helped the royalists to an overwhelming victory"); + AddScoreEntry(Msg, 4, false); + } + else + { + Msg = CONST_S("helped the royalists to an uneasy victory"); + AddScoreEntry(Msg, 2, false); + } + + game::End(Msg); + return; + } + } + else if(game::GetAslonaStoryState() == 5 && game::GetStoryState() < 3) + { + game::TextScreen(CONST_S("\"Ah, you are back? I guess I owe you an apology. I expected you to try and leverage\n" + "some money from us, and then scram. But you really showed what you are made of!\n" + "I hope you could lend your services to Aslona one last time.\"\n\n" + "\"This civil war needs to end right now, before our kingdom tears itself apart, but\n" + "I don't like the options I see. The scouts of Lord Mittrars finally discovered\n" + "the location of Harvan's headquarters, but attacking them head-on in a difficult\n" + "terrain would be foolish and costly. Myrddin informed me he has a magical item\n" + "that could destroy the rebels once and for all, but I'm hesitant to condemn\n" + "every single one of those misguided souls to death.\"")); + + game::TextScreen(CONST_S("\"Yet there is a third option. It is my sincere belief that many of Harvan's troops\n" + "would change sides if prince Artorius took the throne of his father as the rightful\n" + "king of Aslona. But for His Highness to be crowned the king without any doubts or\n" + "dispute, we need the regalia of Aslona, two ancient katanas of immaculate craft,\n" + "Asamarum and E-numa sa-am.\"\n\n" + "\"E-numa sa-am I already have, but Harvan absconded with Asamarum when he fled after\n" + "the old king's death. And where a direct assault would be hard-pressed for victory,\n" + "I think you as an outlander could slip through the sentries of the rebels' camp\n" + "unaccosted and steal Asamarum from Harvan.\"\n\n" + "\"A stealth mission, if you will.\"")); + + GetArea()->SendNewDrawRequest(); + PLAYER->GetTeam()->Hostility(game::GetTeam(REBEL_TEAM)); // So much for a stealth mission. ;) + ADD_MESSAGE("%s smiles at you: \"Together, we'll bring Harvan to justice.\"", CHAR_NAME(DEFINITE)); + game::SetStoryState(3); + return; + } + else + { + // Does the player have prince Artorius in his team? + character* CrownPrince = 0; + for(character* p : game::GetTeam(PLAYER_TEAM)->GetMember()) + if(p->IsEnabled() && !p->IsPlayer() && p->IsKing()) + CrownPrince = p; + + if(CrownPrince) + { + ADD_MESSAGE("%s bows his head slightly. \"Your Highness, it is excellent to see you safe and sound. Please, hurry to tell Lord Mittrars about your return. He was truly sick with worry.\"", CHAR_NAME(DEFINITE)); + return; + } + else if(PLAYER->HasNuke() || PLAYER->HasWeepObsidian()) + { + ADD_MESSAGE("\"Ah, seems like you were not idle. I'm sure my advisors will be thrilled.\""); + return; + } + } + } + + humanoid::BeTalkedTo(); +} + +struct distancepair +{ + distancepair(long Distance, character* Char) : Distance(Distance), Char(Char) { } + bool operator<(const distancepair& D) const { return Distance > D.Distance; } + long Distance; + character* Char; +}; + +void lordregent::SpecialBodyPartSeverReaction() +{ + if(HasHead()) + { + if(CanBeSeenByPlayer()) + { + ADD_MESSAGE("%s prays to Seges. You feel the sudden presence of enemies.", CHAR_NAME(DEFINITE)); + } + + // Summons allies and then teleports away. + std::vector ToSort; + v2 Pos = GetPos(); + + for(character* p : GetTeam()->GetMember()) + if(p->IsEnabled() && p != this) + ToSort.push_back(distancepair((Pos - p->GetPos()).GetLengthSquare(), p)); + + if(ToSort.size() > 5) + std::sort(ToSort.begin(), ToSort.end()); + + for(uint c = 0; c < 5 && c < ToSort.size(); ++c) + ToSort[c].Char->TeleportNear(this); + + /* Teleport away, but rather than doing it the simple way, we're going to use + * a teleport beam to also remove any items on the square. In effect, if the + * player cuts off Lord Efra's sword arm, he will teleport and so will + * Masamune, rather than for it to stay lying on the ground. + */ + beamdata Beam + ( + this, + CONST_S("killed by the fickle favor of Seges"), + YOURSELF, + 0 + ); + GetLSquareUnder()->Teleport(Beam); + //TeleportRandomly(true); + } +} + +void child::BeTalkedTo() +{ + if(GetConfig() == KING && + GetRelation(PLAYER) != HOSTILE && + GetTeam() != PLAYER->GetTeam() && + GetDungeon()->GetIndex() == GOBLIN_FORT && + GetLevel()->GetIndex() == KING_LEVEL && + GetPos().IsAdjacent(PLAYER->GetPos()) + ) // Prince Artorius will follow you back to Aslona. + { + ADD_MESSAGE("%s looks at you with hope. \"I want to go home. Will you take me home, %s?\"", + CHAR_DESCRIPTION(DEFINITE), PLAYER->GetAssignedName().CStr()); + + ChangeTeam(PLAYER->GetTeam()); + return; + } + + character::BeTalkedTo(); +} + +truth child::MoveRandomly() +{ + return GetConfig() == KING ? MoveRandomlyInRoom() : humanoid::MoveRandomly(); +} diff --git a/Main/Source/iconf.cpp b/Main/Source/iconf.cpp index 3212af9f5..7df4c94a5 100644 --- a/Main/Source/iconf.cpp +++ b/Main/Source/iconf.cpp @@ -25,253 +25,324 @@ #include "bugworkaround.h" stringoption ivanconfig::DefaultName( "DefaultName", - "player's default name", + "Player's default name", + "Pick a character name for all your future games. When left empty, the game will generate a random name if a random name generator pattern is configured, or otherwise ask you to pick a name whenever you start a new game.", "", &configsystem::NormalStringDisplayer, &DefaultNameChangeInterface); stringoption ivanconfig::FantasyNamePattern("FantasyNamePattern", - "random name generator pattern", + "Random name generator pattern", + "Set a pattern for the random fantasy name generator. See MANUAL for further information and pattern examples.", // TODO: this above "!ss !sV", &configsystem::NormalStringDisplayer, &FantasyNameChangeInterface); stringoption ivanconfig::DefaultPetName( "DefaultPetName", - "starting pet's default name", + "Starting pet's default name", + "Choose a name for your puppy.", CONST_S("Kenny"), &configsystem::NormalStringDisplayer, &DefaultPetNameChangeInterface); stringoption ivanconfig::SelectedBkgColor("SelectedBkgColor", - "highlight color of selected entry", + "Highlight color of selected entry", + "Choose the RGB code of a color used to highlight the selected entry in a menu.", "8,8,8", &configsystem::NormalStringDisplayer, &SelectedBkgColorChangeInterface, &SelectedBkgColorChanger); +stringoption ivanconfig::AutoPickUpMatching("AutoPickUpMatching", + "Auto pick up regex", + "Automatically pick up items according to a regular expression. To disable something, you can invalidate it with '_' without removing it from the expression (eg. '_dagger'). To disable everything at once, begin this config option with '!'. Due to current constraints on length of options, editing is easier to do externally for now.", //TODO if multiline text editing is implemented, remove the last help statement. + "!((book|can|dagger|grenade|horn of|kiwi|key|ring|scroll|wand|whistle)|^(?:(?!(broken|empty)).)*(bottle|vial)|sol stone)", + &configsystem::NormalStringDisplayer, + &AutoPickUpMatchingChangeInterface, + &AutoPickUpMatchingChanger); numberoption ivanconfig::AutoSaveInterval("AutoSaveInterval", - "autosave interval", + "Autosave interval", + "Automatically backs up your game in case of a crash.", 100, &AutoSaveIntervalDisplayer, &AutoSaveIntervalChangeInterface, &AutoSaveIntervalChanger); scrollbaroption ivanconfig::Contrast( "Contrast", - "contrast", + "Contrast", + "", 100, &ContrastDisplayer, &ContrastChangeInterface, &ContrastChanger, &ContrastHandler); cycleoption ivanconfig::HitIndicator( "HitIndicator", - "show successful hit", + "Show successful hit", + "", 0, 5, &HitIndicatorDisplayer); cycleoption ivanconfig::HoldPosMaxDist( "HoldPosMaxDist", - "pets stay put when not following", // if pet is set to not follow, will move away max the specified distance. if it loses the player, will stay near the last position it moves to trying to follow the player. + "Pets stay put when not following", + "If pets are set not to follow the player, they will move around from their spot no further than the maximum specified distance. If a pet set to be following loses track of the player, it will stay near the last position where it was still able to follow the player.", 0, 7, &HoldPosMaxDistDisplayer); cycleoption ivanconfig::ShowItemsAtPlayerSquare("ShowItemsAtPlayerSquare", - "show items at player's square", + "Show items at player's square", + "", 0, 12, &ShowItemsAtPlayerSquareDisplayer, &configsystem::NormalCycleChangeInterface, &ShowItemsAtPlayerSquareChanger); cycleoption ivanconfig::ShowMap( "ShowMap", - "map preferences", + "Map preferences", + "", 0, 5, &ShowMapDisplayer); cycleoption ivanconfig::RotateTimesPerSquare("RotateTimesPerSquare", - "thrown weapons rotate (times per square)", + "Thrown weapons rotate", + "Select how many times per square should the animation rotate a thrown weapon.", 0, 6, &RotateTimesPerSquareDisplayer); numberoption ivanconfig::WindowWidth( "WindowWidth", - "* window width in pixels (min 640)", + "* Window width in pixels", + "Choose the width of the game window, with a minimum of 640 pixels. Requires restarting the game to take effect.", 800, //default will be vanilla, but mininum still can be 640 &WindowWidthDisplayer, &WindowWidthChangeInterface, &WindowWidthChanger); numberoption ivanconfig::WindowHeight( "WindowHeight", - "* window height in pixels (min 480)", + "* Window height in pixels", + "Choose the height of the game window, with a minimum of 480 pixels. Requires restarting the game to take effect.", 600, //default will be vanilla, but mininum still can be 480 &WindowHeightDisplayer, &WindowHeightChangeInterface, &WindowHeightChanger); numberoption ivanconfig::StackListPageLength("StackListPageLength", - "page length in entries for non-selectable lists", + "Page length in entries for non-selectable menus", + "Choose how many entries will be displayed per page for non-selectable menus, such as when viewing your inventory. Note that selecting too many entries for the height of your screen may result in the menu being drawn partially off-screen.", stack::GetDefaultPageLength(), &StackListPageLengthDisplayer, &StackListPageLengthChangeInterface, &StackListPageLengthChanger); cycleoption ivanconfig::GoOnStopMode( "GoOnStopMode", - "fastwalk stop mode", + "Fastwalk stop mode", + "", 0, 4, &GoOnStopModeDisplayer); numberoption ivanconfig::FrameSkip( "FrameSkip", - "frame skip (assures responsiveness)", + "Frame skip (assures responsiveness)", + "", 0, &FrameSkipDisplayer, &FrameSkipChangeInterface, &FrameSkipChanger); truthoption ivanconfig::AllowMouseOnFelist("AllowMouseOnFelist", - "enable mouse cursor on lists", + "Enable mouse cursor on lists", + "", false, &configsystem::NormalTruthDisplayer, &configsystem::NormalTruthChangeInterface, &AllowMouseOnFelistChanger); truthoption ivanconfig::ShowMapAtDetectMaterial("ShowMapAtDetectMaterial", - "show map while detecting material", + "Show map while detecting material", + "", false); truthoption ivanconfig::AutoPickupThrownItems("AutoPickupThrownItems", - "auto pick up thrown items", + "Auto pick up thrown weapons", + "Automatically annotate any thrown weapon and pick it up without loosing a turn when you step on its square.", true); -truthoption ivanconfig::TransparentMapLM ("TransparentMapLM", - "show transparent map in look mode", +truthoption ivanconfig::TransparentMapLM( "TransparentMapLM", + "Show transparent map in look mode", + "Show transparent map of the whole level when in look mode.", true); truthoption ivanconfig::AllowImportOldSavegame("AllowImportOldSavegame", - "import old savegames (v131 up, experimental)", + "Import old savegames (v131 up, experimental)", + "", false); truthoption ivanconfig::WaitNeutralsMoveAway("WaitNeutralsMoveAway", - "wait until neutral NPCs move from your path", + "Wait until neutral NPCs move from your path", + "When you try to move in a direction that is blocked by a neutral NPC, skip turns until the path is clear. Will not skip turns if the NPC doesn't move from their square, or if there are hostiles nearby.", false); truthoption ivanconfig::AllWeightIsRelevant("AllWeightIsRelevant", - "only pile items with equal weight on lists", //clutter are useful now for crafting so their weight matters... + "Only pile items with equal weight on lists", //clutter are useful now for crafting so their weight matters... + "", false); truthoption ivanconfig::ShowVolume( "ShowVolume", - "show item volume in cm3", + "Show item volume in cm3", + "", false); truthoption ivanconfig::EnhancedLights( "EnhancedLights", - "allow distant lights to be seen", + "Allow distant lights to be seen", + "", true); truthoption ivanconfig::HideWeirdHitAnimationsThatLookLikeMiss("HideWeirdHitAnimationsThatLookLikeMiss", - "hide hit animations that look like miss", + "Hide hit animations that look like miss", + "", true); truthoption ivanconfig::ShowFullDungeonName("ShowFullDungeonName", - "show full name of current dungeon", + "Show full name of current dungeon", + "", false); truthoption ivanconfig::ShowGodInfo( "ShowGodInfo", - "show info about gods when praying", + "Show extra info about gods when praying", + "Remember the last response to a prayer for each god.", false); truthoption ivanconfig::CenterOnPlayerAfterLook("CenterOnPlayerAfterLook", - "center camera on player after exiting look mode", + "Center camera on player after exiting look mode", + "Always center the displayed region of the dungeon back on player after exiting look mode.", false); truthoption ivanconfig::WarnAboutDanger( "WarnAboutVeryDangerousMonsters", - "warn about very dangerous monsters", + "Warn about very dangerous monsters", + "Display a warning prompt when you encounter an unusually dangerous monster.", true); truthoption ivanconfig::AutoDropLeftOvers("AutoDropLeftOvers", - "drop food leftovers automatically", + "Drop food leftovers automatically", + "", true); truthoption ivanconfig::LookZoom( "LookZoom", - "zoom in look mode", + "Zoom in look mode", + "", false); truthoption ivanconfig::AltAdentureInfo( "AltAdentureInfo", - "enhanced message review mode after death", + "Enhanced message review mode after death", + "", + false); +truthoption ivanconfig::DescriptiveHP( "DescriptiveHP", + "Use health level descriptions", + "Display description of your relative health rather than numeric value of your hit points.", + false); +truthoption ivanconfig::StartWithNoPet( "StartWithNoPet", + "Start with no pet", + "Do not start the game with a puppy.", false); cycleoption ivanconfig::MemorizeEquipmentMode("MemorizeEquipmentMode", "NPCs restore equipped items after polymorph", - 0, 3, + "", + 2, 3, &MemorizeEquipmentModeDisplayer); truthoption ivanconfig::XBRZScale( "XBRZScale", - "use xBRZScale to stretch graphics", + "Use xBRZScale to stretch graphics", + "", false, &configsystem::NormalTruthDisplayer, &configsystem::NormalTruthChangeInterface, &XBRZScaleChanger); numberoption ivanconfig::XBRZSquaresAroundPlayer("XBRZSquaresAroundPlayer", - "stretch squares around player with xBRZ", + "Stretch squares around player with xBRZ", + "", 3, &XBRZSquaresAroundPlayerDisplayer, &XBRZSquaresAroundPlayerChangeInterface, &XBRZSquaresAroundPlayerChanger); cycleoption ivanconfig::DungeonGfxScale( "DungeonGfxScale", - "* select graphics scaling factor", + "* Select graphics scaling factor", + "", 1, 6, //from 1 to 6 (max xbrz) where 1 is no scale &DungeonGfxScaleDisplayer, &DungeonGfxScaleChangeInterface, &DungeonGfxScaleChanger); cycleoption ivanconfig::FontGfx( "FontGfx", - "* select font", + "* Select font", + "Select your favorite from the available fonts.", 1, 3, //from 1 to 3 (three options available) &FontGfxDisplayer, &FontGfxChangeInterface, &FontGfxChanger); cycleoption ivanconfig::DistLimitMagicMushrooms("DistLimitMagicMushrooms", - "breeders' active range (suggested 8)", - 0, 16, + "Breeders' active range", + "Select the maximum distance where breeding monsters will spawn more of their own. This option can be used to prevent lag from high number of creatures on slow computers, but may impact the intended game balance negatively.", + 0, 5, &DistLimitMagicMushroomsDisplayer); cycleoption ivanconfig::SaveGameSortMode( "SaveGameSortMode", - "sort savegame files by dungeon IDs", + "Sort savegame files by dungeon IDs", + "Savegame selection menu will be sorted according to the chosen criterion.", 0, 4, &SaveGameSortModeDisplayer, &configsystem::NormalCycleChangeInterface, &SaveGameSortModeChanger); cycleoption ivanconfig::SilhouetteScale( "SilhouetteScale", - "silhouette scaling factor (1 to disable)", + "Silhouette scaling factor", + "Select scaling factor for silhouette, 1x to disable (no scaling).", 1, 6, //from 1 to 6 (max xbrz) where 1 is no scale &SilhouetteScaleDisplayer, &SilhouetteScaleChangeInterface, &SilhouetteScaleChanger); cycleoption ivanconfig::AltSilhouette( "AltSilhouette", - "alternative silhouette mode", + "Alternative silhouette mode", + "", 0, 7, &AltSilhouetteDisplayer); cycleoption ivanconfig::AltSilhouettePreventColorGlitch("AltSilhouettePreventColorGlitch", - "alternative silhouette background", + "Alternative silhouette background", + "", 2, 3, &AltSilhouettePreventColorGlitchDisplayer); cycleoption ivanconfig::DirectionKeyMap( "DirectionKeyMap", - "movement control scheme", + "Movement control scheme", + "Select keybindings for movement of your character. Normal scheme uses NumPad, or arrow keys along with Home, End, PgUp and PgDn for diagonal directions. Alternative scheme is better suited for laptops and uses number and letter keys on the main keyboard. NetHack scheme uses vi keys. After you select a movement control scheme, you may also check the in game keybindings help to see the currently active movement keys.", DIR_NORM, 3, // {default value, number of options to cycle through} &DirectionKeyMapDisplayer); truthoption ivanconfig::SmartOpenCloseApply("SmartOpenCloseApply", - "smart open/close/apply behavior", + "Smart open/close/apply behavior", + "Automatically try to open doors when you walk into them, and don't ask for the target of close/apply actions when only one viable target is present.", true); truthoption ivanconfig::BeNice( "BeNice", - "be nice to pets", + "Be nice to pets", + "Don't let your sadistic tendencies hurt your pets.", true); cycleoption ivanconfig::AltListItemPos( "AltListItemPos", - "use alternative position of stretched lists", + "Use alternative position of stretched lists", + "", 0, 3, &AltListItemPosDisplayer); numberoption ivanconfig::AltListItemWidth("AltListItemWidth", - "list width for previous option", + "List width for alternative stretched lists", + "", game::getDefaultItemsListWidth(), &AltListItemWidthDisplayer, &AltListItemWidthChangeInterface, &AltListItemWidthChanger); scrollbaroption ivanconfig::Volume( "Volume", - "volume", + "Volume", + "Select volume for sound effects and game music.", 127, &VolumeDisplayer, &VolumeChangeInterface, &VolumeChanger, &VolumeHandler); -cycleoption ivanconfig::MIDIOutputDevice( "MIDIOutputDevice", - "select MIDI output device", +cycleoption ivanconfig::MIDIOutputDevice( "MIDIOutputDevice", + "Use MIDI soundtrack", + "Select an output device for the game music, or disable soundtrack.", 0, 0, // {default value, number of options to cycle through} &MIDIOutputDeviceDisplayer); #ifndef __DJGPP__ cycleoption ivanconfig::GraphicsScale( "GraphicsScale", - "select window scaling factor", + "Select window scaling factor", + "", 1, 2, &GraphicsScaleDisplayer, &GraphicsScaleChangeInterface, &GraphicsScaleChanger); truthoption ivanconfig::FullScreenMode( "FullScreenMode", - "full screen mode", + "Full screen mode", + "Display the game in full screen mode.", false, &configsystem::NormalTruthDisplayer, &configsystem::NormalTruthChangeInterface, &FullScreenModeChanger); cycleoption ivanconfig::ScalingQuality( "ScalingQuality", - "* scaling quality", + "* Scaling quality", + "", 0, 2, &ScalingQualityDisplayer); #endif col24 ivanconfig::ContrastLuminance = NORMAL_LUMINANCE; truthoption ivanconfig::PlaySounds( "PlaySounds", - "use sound effects", + "Use sound effects", + "Use sound effects for combat, explosions and more.", true); truthoption ivanconfig::ShowTurn( "ShowTurn", - "show game turn on message log", + "Show game turn on message log", + "Add a game turn number to each action described in the message log.", false); truthoption ivanconfig::OutlinedGfx( "OutlinedGfx", - "* use outlined graphics", + "* Use outlined graphics", + "The game graphics will be outlined in black for better differentiation.", false); v2 ivanconfig::GetQuestionPos() { return game::IsRunning() ? v2(16, 6) : v2(30, 30); } @@ -297,8 +368,16 @@ void ivanconfig::FrameSkipDisplayer(const numberoption* O, festring& Entry) void ivanconfig::DistLimitMagicMushroomsDisplayer(const cycleoption* O, festring& Entry) { - if(O->Value==0) + if(O->Value == 0) Entry << "everywhere"; + else if(O->Value == 1) + Entry << "close"; + else if(O->Value == 2) + Entry << "near"; + else if(O->Value == 3) + Entry << "medium"; + else if(O->Value == 4) + Entry << "far"; else Entry << O->Value; } @@ -348,7 +427,7 @@ void ivanconfig::HitIndicatorDisplayer(const cycleoption* O, festring& Entry) case 0: Entry << "disabled";break; case 1: Entry << "immersive";break; case 2: Entry << "indicator";break; - case 3: Entry << "ind+color";break; + case 3: Entry << "indicator + color";break; case 4: Entry << "dynamic";break; } } @@ -368,9 +447,9 @@ void ivanconfig::ShowMapDisplayer(const cycleoption* O, festring& Entry) switch(O->Value){ case 0:Entry << "vanilla";break; //mmm... just not using xBRZ case 1:Entry << "xBRZ";break; - case 2:Entry << "imersive";break; - case 3:Entry << "imersive2";break; - case 4:Entry << "imersive3";break; + case 2:Entry << "immersive 1";break; + case 3:Entry << "immersive 2";break; + case 4:Entry << "immersive 3";break; } } @@ -401,7 +480,7 @@ void ivanconfig::ShowItemsAtPlayerSquareDisplayer(const cycleoption* O, festring // Entry << ","; // Entry << "x" << game::ItemUnderZoom(O->Value); - Entry << ","; + Entry << ", "; Entry << (game::ItemUnderHV(iCode) ? "horizontal" : "vertical"); } } @@ -480,7 +559,7 @@ void ivanconfig::MIDIOutputDeviceDisplayer(const cycleoption* O, festring& Entry else { audio::ChangeMIDIOutputDevice(0); - Entry << CONST_S("No output device"); + Entry << CONST_S("no"); } } @@ -554,7 +633,7 @@ truth ivanconfig::SelectedBkgColorChangeInterface(stringoption* O) festring String; if(O)String<Value; - if(iosystem::StringQuestion(String, CONST_S("Set new RGB color (8 to 200 for each value), or leave empty to disable:"), + if(iosystem::StringQuestion(String, CONST_S("Set new RGB color (8 to 200 for each value, default \"8,8,8\"), or leave empty to disable:"), GetQuestionPos(), WHITE, 0, 20, !game::IsRunning(), true) == NORMAL_EXIT) O->ChangeValue(String); @@ -563,6 +642,20 @@ truth ivanconfig::SelectedBkgColorChangeInterface(stringoption* O) return false; } +truth ivanconfig::AutoPickUpMatchingChangeInterface(stringoption* O) +{ + festring String; + if(O)String<Value; + + if(iosystem::StringQuestion(String, CONST_S("What items do you want to automatically pick up?"), + GetQuestionPos(), WHITE, 0, 200, !game::IsRunning(), true) == NORMAL_EXIT) //TODO should have no limit? but crashes if going beyond screen limit... + O->ChangeValue(String); + + clearToBackgroundAfterChangeInterface(); + + return false; +} + truth ivanconfig::DefaultPetNameChangeInterface(stringoption* O) { festring String; @@ -747,6 +840,15 @@ void ivanconfig::SelectedBkgColorChanger(stringoption* O, cfestring& What) } } +void ivanconfig::AutoPickUpMatchingChanger(stringoption* O, cfestring& What) +{ + if(O!=NULL){ + O->Value.Empty(); + O->Value< DeviceNames; int NumDevices = audio::GetMIDIOutputDevices(DeviceNames); @@ -1005,6 +1109,7 @@ void ivanconfig::Initialize() MIDIOutputDevice.CycleCount = NumDevices+1; configsystem::AddOption(fsCategory,&MIDIOutputDevice); + configsystem::AddOption(fsCategory,&Volume); fsCategory="Input and Interface"; configsystem::AddOption(fsCategory,&DirectionKeyMap); @@ -1044,5 +1149,6 @@ void ivanconfig::Initialize() StackListPageLengthChanger(NULL, StackListPageLength.Value); SaveGameSortModeChanger(NULL, SaveGameSortMode.Value); SelectedBkgColorChanger(NULL, SelectedBkgColor.Value); + AutoPickUpMatchingChanger(NULL, AutoPickUpMatching.Value); AllowMouseOnFelistChanger(NULL, AllowMouseOnFelist.Value); } diff --git a/Main/Source/igraph.cpp b/Main/Source/igraph.cpp index 002dedb09..c154caa0a 100644 --- a/Main/Source/igraph.cpp +++ b/Main/Source/igraph.cpp @@ -60,11 +60,11 @@ int igraph::CurrentColorType = -1; void igraph::Init() { - if(ivanconfig::IsStartingOutlinedGfx()){ - RawGraphicFileName[GR_ITEM]="Graphics/Item-outlined.png"; - RawGraphicFileName[GR_CHARACTER]="Graphics/Char-outlined.png"; - RawGraphicFileName[GR_HUMANOID]="Graphics/Humanoid-outlined.png"; - } + if(ivanconfig::IsStartingOutlinedGfx()){ + RawGraphicFileName[GR_ITEM]="Graphics/Item-outlined.png"; + RawGraphicFileName[GR_CHARACTER]="Graphics/Char-outlined.png"; + RawGraphicFileName[GR_HUMANOID]="Graphics/Humanoid-outlined.png"; + } static truth AlreadyInstalled = false; @@ -90,14 +90,14 @@ void igraph::Init() graphics::SetSwitchModeHandler(ivanconfig::SwitchModeHandler); #endif if(ivanconfig::GetStartingFontGfx()==1){ - graphics::LoadDefaultFont(game::GetDataDir() + "Graphics/Font.png"); - } + graphics::LoadDefaultFont(game::GetDataDir() + "Graphics/Font.png"); + } if(ivanconfig::GetStartingFontGfx()==2){ - graphics::LoadDefaultFont(game::GetDataDir() + "Graphics/Font2.png"); - } + graphics::LoadDefaultFont(game::GetDataDir() + "Graphics/Font2.png"); + } if(ivanconfig::GetStartingFontGfx()==3){ - graphics::LoadDefaultFont(game::GetDataDir() + "Graphics/Font3.png"); - } + graphics::LoadDefaultFont(game::GetDataDir() + "Graphics/Font3.png"); + } FONT->CreateFontCache(WHITE); FONT->CreateFontCache(LIGHT_GRAY); felist::SetDefaultEntryImageSize(TILE_V2); diff --git a/Main/Source/item.cpp b/Main/Source/item.cpp index 8a453b855..e2fa013cb 100644 --- a/Main/Source/item.cpp +++ b/Main/Source/item.cpp @@ -54,6 +54,7 @@ truth item::IsInCorrectSlot() const { return IsInCorrectSlot(static_cast(*Slot)->GetEquipmentIndex(); } int item::GetGraphicsContainerIndex() const { return GR_ITEM; } truth item::IsBroken() const { return GetConfig() & BROKEN; } +truth item::IsFood() const { return DataBase->Category & FOOD; } cchar* item::GetBreakVerb() const { return "breaks"; } square* item::GetSquareUnderEntity(int I) const { return GetSquareUnder(I); } square* item::GetSquareUnder(int I) const { return Slot[I] ? Slot[I]->GetSquareUnder() : 0; } @@ -333,6 +334,58 @@ truth item::Polymorph(character* Polymorpher, stack* CurrentStack) } } +truth item::Polymorph(character* Polymorpher, character* Wielder) +{ + if(!IsPolymorphable()) + return false; + else if(!Wielder->Equips(this)) + return false; + else + { + if(Polymorpher && Wielder) + { + Polymorpher->Hostility(Wielder); + } + + item* NewItem = protosystem::BalancedCreateItem(0, MAX_PRICE, ANY_CATEGORY, 0, 0, 0, true); + int EquipSlot = GetEquipmentIndex(); + + if(Wielder->IsPlayer()) + ADD_MESSAGE("Your %s polymorphs into %s.", CHAR_NAME(UNARTICLED), NewItem->CHAR_NAME(INDEFINITE)); + else if(CanBeSeenByPlayer()) + ADD_MESSAGE("%s's %s polymorphs into %s.", Wielder->CHAR_NAME(DEFINITE), CHAR_NAME(UNARTICLED), NewItem->CHAR_NAME(INDEFINITE)); + + RemoveFromSlot(); + SendToHell(); + + switch (EquipSlot) + { + /* + case HELMET_INDEX: Wielder->SetHelmet(NewItem); break; + case AMULET_INDEX: Wielder->SetAmulet(NewItem); break; + case CLOAK_INDEX: Wielder->SetCloak(NewItem); break; + case BODY_ARMOR_INDEX: Wielder->SetBodyArmor(NewItem); break; + case BELT_INDEX: Wielder->SetBelt(NewItem); break; + */ + case RIGHT_WIELDED_INDEX: Wielder->SetRightWielded(NewItem); break; + case LEFT_WIELDED_INDEX: Wielder->SetLeftWielded(NewItem); break; + /* + case RIGHT_RING_INDEX: Wielder->SetRightRing(NewItem); break; + case LEFT_RING_INDEX: Wielder->SetLeftRing(NewItem); break; + case RIGHT_GAUNTLET_INDEX: Wielder->SetRightGauntlet(NewItem); break; + case LEFT_GAUNTLET_INDEX: Wielder->SetLeftGauntlet(NewItem); break; + case RIGHT_BOOT_INDEX: Wielder->SetRightBoot(NewItem); break; + case LEFT_BOOT_INDEX: Wielder->SetLeftBoot(NewItem); break; + */ + default: Wielder->ReceiveItemAsPresent(NewItem); break; + } + + if(Wielder->IsPlayer()) + game::AskForKeyPress(CONST_S("Equipment polymorphed! [press any key to continue]")); + return true; + } +} + /* Returns truth that tells whether the alchemical conversion really happened. */ truth item::Alchemize(character* Midas, stack* CurrentStack) @@ -475,12 +528,10 @@ void item::SetLabel(cfestring& What) void item::AddName(festring& Name, int Case) const { - if(label.GetSize()) - Name << label << " "; //this way user can decide how it should look, with or w/o delimiters and which ones -// Name << "{"<GetInteractionFlags() & CAN_BURN) + { + int Damage = Modifier / 1000; + + if(Damage) + { + Damage += RAND() % Damage; + ReceiveDamage(0, Damage, FIRE); + } + } +} + void item::FightFire(material*, cfestring&, long Volume) { int Amount = sqrt(Volume); diff --git a/Main/Source/itemset.cpp b/Main/Source/itemset.cpp index fbb200d9d..7385afd7f 100644 --- a/Main/Source/itemset.cpp +++ b/Main/Source/itemset.cpp @@ -26,6 +26,7 @@ EXTENDED_SYSTEM_SPECIALIZATIONS(item)(0, 0, 0, "item"); #include #include +#include #include "char.h" #include "message.h" diff --git a/Main/Source/level.cpp b/Main/Source/level.cpp index dad191ed1..1b99b16d4 100644 --- a/Main/Source/level.cpp +++ b/Main/Source/level.cpp @@ -329,12 +329,14 @@ void level::Generate(int Index) Map = reinterpret_cast(area::Map); SquareStack = new lsquare*[XSizeTimesYSize]; - if((Index == 0 && GetDungeon()->GetIndex() == NEW_ATTNAM) + /*if((Index == 0 && GetDungeon()->GetIndex() == NEW_ATTNAM) || (Index == 0 && GetDungeon()->GetIndex() == ATTNAM)) NightAmbientLuminance = MakeRGB24(95, 95, 95); - + */ if((Index == 0) && (GetDungeon()->GetIndex() == XINROCH_TOMB)) NightAmbientLuminance = MakeRGB24(105, 95, 95); + else if(IsOnGround()) + NightAmbientLuminance = MakeRGB24(95, 95, 95); int x, y; @@ -1512,7 +1514,7 @@ void level::GenerateRectangularRoom(std::vector& OKForDoor, std::vector& Border.push_back(v2(Pos.X, y)); Border.push_back(v2(Pos.X + Size.X - 1, y)); } - // Maze rooms only: put in the doors, in the corners. + // Maze rooms only: put in the doors, in the corners. if(Shape == MAZE_ROOM) { int MazeDoors = RAND() % 4; @@ -2617,6 +2619,22 @@ void level::CheckSunLight() AmbientLuminance = NightAmbientLuminance; } } + else if(IsOnGround()) + { + double Cos = cos(FPI * (game::GetTick() % 48000) / 24000.); + + if(Cos > 0.31) + { + int E = int(100 + (Cos - 0.30) * 30); + SunLightEmitation = MakeRGB24(E, E, E); + AmbientLuminance = MakeRGB24(E - 8, E - 8, E - 8); + } + else + { + SunLightEmitation = 0; + AmbientLuminance = NightAmbientLuminance; + } + } else return; @@ -3036,13 +3054,13 @@ int level::RevealDistantLightsToPlayer() //based on Draw() code { if(!ivanconfig::IsEnhancedLights()) return 0; - + if(!PLAYER->GetSquareUnder()) //NULL may happen on player's death, was polymorphed when the crash happened return 0; if(!PLAYER->GetSquareUnder()) //NULL may happen on player's death, was polymorphed when the crash happened return 0; - + cint XMin = Max(game::GetCamera().X, 0); cint YMin = Max(game::GetCamera().Y, 0); cint XMax = Min(XSize, game::GetCamera().X + game::GetScreenXSize()); @@ -3070,8 +3088,8 @@ int level::RevealDistantLightsToPlayer() //based on Draw() code } if(!bTryReveal){ - // TODO the farer, less range around the emmiter should be seen - + // TODO the farer, less range around the emmiter should be seen + // if(Square->GetOLTerrain() && Square->GetOLTerrain()->IsWall()) //do not show far walls to look better // continue; @@ -3089,7 +3107,7 @@ int level::RevealDistantLightsToPlayer() //based on Draw() code fLBorder = fLBorderLanternLOSM16 + fLBStep*LOSMdelta; } if(!bTryReveal && hasLight(Square->Luminance,0xFF*fLBorder)) - bTryReveal=true; + bTryReveal=true; } } @@ -3292,12 +3310,12 @@ void level::GasExplosion(gas* GasMaterial, lsquare* Square, character* Terrorist if(Neighbour && Neighbour->IsFlyable()) Neighbour->AddSmoke(static_cast(GasMaterial->SpawnMore(1000))); - if(Neighbour) + if(Neighbour) { character* Victim = Neighbour->GetCharacter(); if(Victim && Terrorist) - Terrorist->Hostility(Victim); + Terrorist->Hostility(Victim); } } } diff --git a/Main/Source/lsquare.cpp b/Main/Source/lsquare.cpp index 2b8cd8e1c..d07aa1dbe 100644 --- a/Main/Source/lsquare.cpp +++ b/Main/Source/lsquare.cpp @@ -1498,6 +1498,11 @@ truth lsquare::Polymorph(const beamdata& Beam) if(Character) { + if(Character->IsMagicDrinker() && Beam.Wand && Beam.Wand->IsExplosive()) + { + if(Character->DrinkMagic(Beam)) + return false; + } if(Beam.Owner && Character->GetTeam() != Beam.Owner->GetTeam()) Beam.Owner->Hostility(Character); @@ -1532,6 +1537,12 @@ truth lsquare::Strike(const beamdata& Beam) else if(Char->CanBeSeenByPlayer()) ADD_MESSAGE("%s is hit by a burst of energy!", Char->CHAR_NAME(DEFINITE)); + if(Char->IsMagicDrinker() && Beam.Wand && Beam.Wand->IsExplosive()) + { + if(Char->DrinkMagic(Beam)) + return false; + } + if(Beam.Owner) Beam.Owner->Hostility(Char); @@ -1547,8 +1558,16 @@ truth lsquare::Strike(const beamdata& Beam) truth lsquare::FireBall(const beamdata& Beam) { - if(!IsFlyable() || GetCharacter()) + character* Char = GetCharacter(); + + if(!IsFlyable() || Char) { + if(Char && Char->IsMagicDrinker() && Beam.Wand && Beam.Wand->IsExplosive()) + { + if(Char->DrinkMagic(Beam)) + return false; + } + if(CanBeSeenByPlayer(true)) ADD_MESSAGE("A magical explosion is triggered!"); @@ -1563,6 +1582,12 @@ truth lsquare::Teleport(const beamdata& Beam) { if(Character) { + if(Character->IsMagicDrinker() && Beam.Wand && Beam.Wand->IsExplosive()) + { + if(Character->DrinkMagic(Beam)) + return false; + } + if(Beam.Owner && Character->GetTeam() != Beam.Owner->GetTeam()) Beam.Owner->Hostility(GetCharacter()); @@ -1581,13 +1606,21 @@ truth lsquare::Teleport(const beamdata& Beam) return false; } -truth lsquare::Haste(const beamdata&) +truth lsquare::Haste(const beamdata& Beam) { GetStack()->Haste(); character* Dude = GetCharacter(); if(Dude) + { + if(Dude->IsMagicDrinker() && Beam.Wand && Beam.Wand->IsExplosive()) + { + if(Dude->DrinkMagic(Beam)) + return false; + } + Dude->Haste(); + } return false; } @@ -1599,6 +1632,11 @@ truth lsquare::Slow(const beamdata& Beam) if(Dude) { + if(Dude->IsMagicDrinker() && Beam.Wand && Beam.Wand->IsExplosive()) + { + if(Dude->DrinkMagic(Beam)) + return false; + } if(Beam.Owner) Beam.Owner->Hostility(Dude); @@ -1610,16 +1648,36 @@ truth lsquare::Slow(const beamdata& Beam) truth lsquare::Resurrect(const beamdata& Beam) { - if(GetCharacter()) - return GetCharacter()->RaiseTheDead(Beam.Owner); + character* Char = GetCharacter(); + + if(Char) + { + if(Char->IsMagicDrinker() && Beam.Wand && Beam.Wand->IsExplosive()) + { + if(Char->DrinkMagic(Beam)) + return false; + } + + return Char->RaiseTheDead(Beam.Owner); + } else return GetStack()->RaiseTheDead(Beam.Owner); } -truth lsquare::Invisibility(const beamdata&) +truth lsquare::Invisibility(const beamdata& Beam) { - if(GetCharacter()) - GetCharacter()->BeginTemporaryState(INVISIBLE, 1000 + RAND() % 1001); + character* Char = GetCharacter(); + + if(Char) + { + if(Char->IsMagicDrinker() && Beam.Wand && Beam.Wand->IsExplosive()) + { + if(Char->DrinkMagic(Beam)) + return false; + } + + Char->BeginTemporaryState(INVISIBLE, 1000 + RAND() % 1001); + } return false; } @@ -1630,7 +1688,15 @@ truth lsquare::Duplicate(const beamdata& Beam) character* Character = GetCharacter(); if(Character) + { + if(Character->IsMagicDrinker() && Beam.Wand && Beam.Wand->IsExplosive()) + { + if(Character->DrinkMagic(Beam)) + return false; + } + DuplicatedSomething = truth(Character->DuplicateToNearestSquare(Beam.Owner, Beam.SpecialParameters)); + } if(GetStack()->Duplicate(DuplicatedSomething ? 4 : 5, Beam.SpecialParameters)) DuplicatedSomething = true; @@ -1648,6 +1714,12 @@ truth lsquare::Lightning(const beamdata& Beam) if(Char) { + if(Char->IsMagicDrinker() && Beam.Wand && Beam.Wand->IsExplosive()) + { + if(Char->DrinkMagic(Beam)) + return false; + } + if(Char->IsPlayer()) ADD_MESSAGE("A massive burst of electricity runs through your body!"); else if(Char->CanBeSeenByPlayer()) @@ -1668,9 +1740,18 @@ truth lsquare::Lightning(const beamdata& Beam) truth lsquare::DoorCreation(const beamdata& Beam) { + character* Char = GetCharacter(); + if(Char) + { + if(Char->IsMagicDrinker() && Beam.Wand && Beam.Wand->IsExplosive()) + { + if(Char->DrinkMagic(Beam)) + return false; + } + } if((!GetOLTerrain() || GetOLTerrain()->IsSafeToCreateDoor()) - && !GetCharacter() + && !Char && (GetLevel()->IsOnGround() || (Pos.X > 0 && Pos.Y > 0 && Pos.X < GetLevel()->GetXSize() - 1 && Pos.Y < GetLevel()->GetYSize() - 1))) @@ -1693,9 +1774,18 @@ truth lsquare::DoorCreation(const beamdata& Beam) truth lsquare::WallCreation(const beamdata& Beam) { + character* Char = GetCharacter(); + if(Char) + { + if(Char->IsMagicDrinker() && Beam.Wand && Beam.Wand->IsExplosive()) + { + if(Char->DrinkMagic(Beam)) + return false; + } + } if((!GetOLTerrain() || GetOLTerrain()->IsSafeToCreateDoor()) - && !GetCharacter() + && !Char && (GetLevel()->IsOnGround() || (Pos.X > 0 && Pos.Y > 0 && Pos.X < GetLevel()->GetXSize() - 1 && Pos.Y < GetLevel()->GetYSize() - 1))) @@ -1805,8 +1895,17 @@ truth lsquare::LowerEnchantment(const beamdata& Beam) if(Char) { + if(Beam.Owner && RAND_N(Char->GetAttribute(WILL_POWER)) > RAND_N(Beam.Owner->GetAttribute(MANA))) + { + if(Char->IsPlayer()) + ADD_MESSAGE("%s glows dull brown for a second, but then it passes.", RandomItem->CHAR_NAME(DEFINITE)); + + Char->EditExperience(WILL_POWER, 100, 1 << 12); + return false; + } + if(Char->IsPlayer()) - ADD_MESSAGE("%s glows blue for a moment!", RandomItem->CHAR_NAME(DEFINITE)); + ADD_MESSAGE("%s glows dull brown for a moment!", RandomItem->CHAR_NAME(DEFINITE)); if(Beam.Owner) Beam.Owner->Hostility(Char); @@ -2704,7 +2803,16 @@ void lsquare::CreateMemorized() truth lsquare::AcidRain(const beamdata& Beam) { - if(!IsFlyable() || GetCharacter() || Beam.Direction == YOURSELF) + character* Character = GetCharacter(); + if(Character) + { + if(Character->IsMagicDrinker() && Beam.Wand && Beam.Wand->IsExplosive()) + { + if(Character->DrinkMagic(Beam)) + return false; + } + } + if(!IsFlyable() || Character || Beam.Direction == YOURSELF) { int StackSize = GetLevel()->AddRadiusToSquareStack(Pos, 9); lsquare** Stack = GetLevel()->GetSquareStack(); @@ -2714,11 +2822,12 @@ truth lsquare::AcidRain(const beamdata& Beam) for(int c = 0; c < StackSize; ++c) { Stack[c]->AddRain(liquid::Spawn(SULPHURIC_ACID, 300), Speed, Team, true); + Character = Stack[c]->GetCharacter(); Stack[c]->Flags &= ~IN_SQUARE_STACK; - } - if(Beam.Owner && Character && Character->GetTeam() != Beam.Owner->GetTeam()) - Beam.Owner->Hostility(Character); + if(Beam.Owner && Character && Character->GetTeam() != Beam.Owner->GetTeam()) + Beam.Owner->Hostility(Character); + } return true; } @@ -2726,9 +2835,18 @@ truth lsquare::AcidRain(const beamdata& Beam) return false; } -truth lsquare::WaterRain(const beamdata& Beam) +truth lsquare::LiquidRain(const beamdata& Beam, int Material) { - if(!IsFlyable() || GetCharacter() || Beam.Direction == YOURSELF) + character* Character = GetCharacter(); + if(Character) + { + if(Character->IsMagicDrinker() && Beam.Wand && Beam.Wand->IsExplosive()) + { + if(Character->DrinkMagic(Beam)) + return false; + } + } + if(!IsFlyable() || Character || Beam.Direction == YOURSELF) { int StackSize = GetLevel()->AddRadiusToSquareStack(Pos, 9); lsquare** Stack = GetLevel()->GetSquareStack(); @@ -2737,7 +2855,7 @@ truth lsquare::WaterRain(const beamdata& Beam) for(int c = 0; c < StackSize; ++c) { - Stack[c]->AddRain(liquid::Spawn(WATER, 1000), Speed, Team, true); + Stack[c]->AddRain(liquid::Spawn(Material, 1000), Speed, Team, true); Stack[c]->Flags &= ~IN_SQUARE_STACK; } @@ -2813,6 +2931,25 @@ void lsquare::SwapMemorized(lsquare* Square) truth lsquare::Necromancy(const beamdata& Beam) { + character* Character = GetCharacter(); + if(Character) + { + if(Character->IsMagicDrinker() && Beam.Wand && Beam.Wand->IsExplosive()) + { + if(Character->DrinkMagic(Beam)) + return false; + } + else if(Character->IsUndead()) + { + if(Character->IsPlayer()) + ADD_MESSAGE("Your dead flesh knits itself."); + else if(Character->CanBeSeenByPlayer()) + ADD_MESSAGE("%s looks deader than dead.", Character->CHAR_NAME(DEFINITE)); + + Character->RestoreLivingHP(); + } + } + return GetStack()->Necromancy(Beam.Owner); } @@ -2993,8 +3130,26 @@ void lsquare::AddSpecialCursors() OLTerrain->AddSpecialCursors(); } -truth lsquare::Webbing(const beamdata&) +truth lsquare::Webbing(const beamdata& Beam) { + character* Character = GetCharacter(); + if(Character) + { + if(Character->IsMagicDrinker() && Beam.Wand && Beam.Wand->IsExplosive()) + { + if(Character->DrinkMagic(Beam)) + return false; + } + if(Beam.Owner && Character->GetTeam() != Beam.Owner->GetTeam()) + Beam.Owner->Hostility(Character); + } + + if(!IsFlyable()) + return false; + + if(Beam.Owner && GetRoom()) + GetRoom()->HostileAction(Beam.Owner); + web* Web = web::Spawn(); Web->SetStrength(50); @@ -3005,6 +3160,18 @@ truth lsquare::Webbing(const beamdata&) truth lsquare::Alchemize(const beamdata& Beam) { + character* Character = GetCharacter(); + if(Character) + { + if(Character->IsMagicDrinker() && Beam.Wand && Beam.Wand->IsExplosive()) + { + if(Character->DrinkMagic(Beam)) + return false; + } + } + if(!IsFlyable()) + return false; + GetStack()->Alchemize(Beam.Owner); return false; } @@ -3020,6 +3187,11 @@ truth lsquare::SoftenMaterial(const beamdata& Beam) if(Character) { + if(Character->IsMagicDrinker() && Beam.Wand && Beam.Wand->IsExplosive()) + { + if(Character->DrinkMagic(Beam)) + return false; + } if(Beam.Owner && Character->GetTeam() != Beam.Owner->GetTeam()) Beam.Owner->Hostility(Character); diff --git a/Main/Source/lterra.cpp b/Main/Source/lterra.cpp index 36231c65d..89619c6d0 100644 --- a/Main/Source/lterra.cpp +++ b/Main/Source/lterra.cpp @@ -527,6 +527,20 @@ void olterrain::ReceiveAcid(material*, long Modifier) } } +void olterrain::ReceiveHeat(material*, long Modifier) +{ + if(GetMainMaterial()->GetInteractionFlags() & CAN_BURN) + { + int Damage = Modifier / 10000; + + if(Damage) + { + Damage += RAND() % Damage; + ReceiveDamage(0, Damage, FIRE); + } + } +} + void lterrain::InitMaterials(material* FirstMaterial, truth CallUpdatePictures) { InitMaterial(MainMaterial, FirstMaterial, 0); diff --git a/Main/Source/lterras.cpp b/Main/Source/lterras.cpp index 870d9c194..2ad66258e 100644 --- a/Main/Source/lterras.cpp +++ b/Main/Source/lterras.cpp @@ -240,41 +240,80 @@ void altar::StepOn(character* Stepper) truth throne::SitOn(character* Sitter) { + if(!Sitter->IsPlayer()) + return false; + Sitter->EditAP(-1000); - if(Sitter->HasPetrussNut() && Sitter->HasGoldenEagleShirt() && game::GetGod(VALPURUS)->GetRelation() != 1000) + if(GetLSquareUnder()->GetDungeonIndex() == ATTNAM && GetLSquareUnder()->GetLevelIndex() == 0) { - ADD_MESSAGE("You have a strange vision of yourself becoming a great ruler. The daydream fades in a whisper: " - "\"Thou shalt be Our Champion first!\""); - return true; - } + if(Sitter->HasPetrussNut() && Sitter->HasGoldenEagleShirt() && game::GetGod(VALPURUS)->GetRelation() != 1000) + { + ADD_MESSAGE("You have a strange vision of yourself becoming a great ruler. The daydream fades in a whisper: " + "\"Thou shalt be Our Champion first!\""); + return true; + } - if(Sitter->HasPetrussNut() && !Sitter->HasGoldenEagleShirt() && game::GetGod(VALPURUS)->GetRelation() == 1000) - { - ADD_MESSAGE("You have a strange vision of yourself becoming a great ruler. The daydream fades in a whisper: " - "\"Thou shalt wear Our shining armor first!\""); - return true; - } + if(Sitter->HasPetrussNut() && !Sitter->HasGoldenEagleShirt() && game::GetGod(VALPURUS)->GetRelation() == 1000) + { + ADD_MESSAGE("You have a strange vision of yourself becoming a great ruler. The daydream fades in a whisper: " + "\"Thou shalt wear Our shining armor first!\""); + return true; + } - if(!Sitter->HasPetrussNut() && Sitter->HasGoldenEagleShirt() && game::GetGod(VALPURUS)->GetRelation() == 1000) - { - ADD_MESSAGE("You have a strange vision of yourself becoming a great ruler. The daydream fades in a whisper: " - "\"Thou shalt surpass thy predecessor first!\""); - return true; - } + if(!Sitter->HasPetrussNut() && Sitter->HasGoldenEagleShirt() && game::GetGod(VALPURUS)->GetRelation() == 1000) + { + ADD_MESSAGE("You have a strange vision of yourself becoming a great ruler. The daydream fades in a whisper: " + "\"Thou shalt surpass thy predecessor first!\""); + return true; + } - if(Sitter->HasPetrussNut() && Sitter->HasGoldenEagleShirt() && game::GetGod(VALPURUS)->GetRelation() == 1000) + if(Sitter->HasPetrussNut() && Sitter->HasGoldenEagleShirt() && game::GetGod(VALPURUS)->GetRelation() == 1000) + { + game::PlayVictoryMusic(); + game::TextScreen(CONST_S("A heavenly choir starts to sing Grandis Rana and a booming voice fills the air:\n\n" + "\"MORTAL! THOU HAST SURPASSED PETRUS, AND PLEASED US GREATLY DURING THY ADVENTURES!\n" + "WE HEREBY TITLE THEE AS OUR NEW HIGH PRIEST!\"\n\nYou are victorious!")); + game::GetCurrentArea()->SendNewDrawRequest(); + game::DrawEverything(); + PLAYER->ShowAdventureInfo(); + festring Msg = CONST_S("became the new high priest of the Great Frog"); + PLAYER->AddScoreEntry(Msg, 5, false); + game::End(Msg); + return true; + } + } + else if(GetLSquareUnder()->GetDungeonIndex() == ASLONA_CASTLE) { - game::PlayVictoryMusic(); - game::TextScreen(CONST_S("A heavenly choir starts to sing Grandis Rana and a booming voice fills the air:\n\n" - "\"Mortal! Thou hast surpassed Petrus, and pleased Us greatly during thy adventures!\n" - "We hereby title thee as Our new high priest!\"\n\nYou are victorious!")); - game::GetCurrentArea()->SendNewDrawRequest(); - game::DrawEverything(); - PLAYER->ShowAdventureInfo(); - festring Msg = CONST_S("became the new high priest of the Great Frog"); - PLAYER->AddScoreEntry(Msg, 5, false); - game::End(Msg); + if(GetLSquareUnder()->GetLevelIndex() == 0) + { + if(Sitter->HasMuramasa() && Sitter->HasMasamune() && Sitter->GetBloodMaterial() == BLUE_BLOOD) + { + game::PlayVictoryMusic(); + game::TextScreen(CONST_S("You feel strangely judged for a moment, but then you raise Asamarum and E-numa sa-am\n" + "above your head, and the castle submits to your reign. One by one, people trickle to\n" + "the throne room, until the gathered crowd start chanting and cheering:\n\n" + "\"Long live the king!\"\n\nYou are victorious!")); + game::GetCurrentArea()->SendNewDrawRequest(); + game::DrawEverything(); + PLAYER->ShowAdventureInfo(); + festring Msg = CONST_S("usurped the throne of Aslona"); + PLAYER->AddScoreEntry(Msg, 4, false); + game::End(Msg); + return true; + } + else if(Sitter->HasMuramasa() || Sitter->HasMasamune()) + { + ADD_MESSAGE("You have a strange vision of the very castle walls shuddering in disdain and disgust."); + game::GetGod(SEGES)->CreateAngel(game::GetTeam(ASLONA_TEAM), 10000); + game::GetGod(SEGES)->AdjustRelation(-50); + } + else + ADD_MESSAGE("\"What do you think you're doing?! Get out!\""); + } + else + ADD_MESSAGE("You don't have to go."); + return true; } @@ -344,7 +383,33 @@ truth fountain::SitOn(character* Sitter) { if(GetSecondaryMaterial()) { - ADD_MESSAGE("You sit on the fountain. Water falls on your head and you get quite wet. You feel like a moron."); + if(GetSecondaryMaterial()->IsSolid()) + { + ADD_MESSAGE("You sit on the fountain. Nothing happens, %s", game::Insult()); + } + else if(GetSecondaryMaterial()->IsLiquid()) + { + ADD_MESSAGE("You sit on the fountain. %s sprinkles on your head.", GetSecondaryMaterial()->GetName(false, false).CapitalizeCopy().CStr()); + + Sitter->SpillFluid(Sitter, static_cast(GetSecondaryMaterial()->SpawnMore(100 + RAND() % 100))); + + if(!RAND_N(20)) + DryOut(Sitter); + } + else if(GetSecondaryMaterial()->IsGaseous()) + { + ADD_MESSAGE("You sit on the fountain. It releases some %s.", GetSecondaryMaterial()->GetName(false, false).CStr()); + GetLSquareUnder()->AddSmoke(static_cast(GetSecondaryMaterial()->SpawnMore(100 + RAND() % 100))); + + if(!RAND_N(20)) + DryOut(Sitter); + } + else // Should not happen. + { + ADD_MESSAGE("You don't dare to sit on this fountain."); + return false; + } + Sitter->EditAP(-1000); return true; } @@ -356,7 +421,12 @@ truth fountain::Drink(character* Drinker) { if(GetSecondaryMaterial()) { - if(GetSecondaryMaterial()->GetConfig() == WATER) + if(GetSecondaryMaterial()->IsSolid()) + { + ADD_MESSAGE("You cannot drink from this fountain."); + return false; + } + else // Gas or liquid { room* Room = GetRoom(); @@ -370,226 +440,257 @@ truth fountain::Drink(character* Drinker) Drinker->EditAP(-1000); - switch(RAND() % 12) + if(GetSecondaryMaterial()->IsLiquid()) { - case 0: - if(RAND_N(3)) + if(GetSecondaryMaterial()->GetConfig() == WATER) { - ADD_MESSAGE("The water is contaminated!"); - Drinker->EditNP(100); - - if(!RAND_4) - Drinker->PolymorphRandomly(0, 1000000, 2500 + RAND() % 2500); - else + switch(RAND() % 12) { - Drinker->ChangeRandomAttribute(-1); - Drinker->CheckDeath(CONST_S("died of contaminated water")); - } - - break; - } - case 1: - ADD_MESSAGE("The water tasted very good."); - Drinker->EditNP(2500); - Drinker->ChangeRandomAttribute(1); - break; - case 2: - if(!(RAND() % 15)) - { - ADD_MESSAGE("You have freed a spirit that grants you a wish. You may wish for an item."); - game::Wish(Drinker, - "%s appears from nothing and the spirit flies happily away!", - "Two %s appear from nothing and the spirit flies happily away!", false); - } - else - DryOut(); - - break; - case 4: - if(RAND() & 7) - { - ADD_MESSAGE("The water tastes normal, but there is an odd after taste."); - Drinker->ActivateRandomState(SRC_FOUNTAIN, 10000 + RAND() % 20000); - } - else - { - ADD_MESSAGE("This water tastes very odd."); - - if(!Drinker->GainRandomIntrinsic(SRC_FOUNTAIN)) - ADD_MESSAGE("You feel like a penguin."); /* This is rather rare, so no harm done */ - } + case 0: + if(RAND_N(3)) + { + ADD_MESSAGE("The water is contaminated!"); + Drinker->EditNP(100); - break; - case 5: - { - characterspawner Spawner = 0; - int Config = 0, AddChance = 0; - truth ForceAdjacency = false; + if(!RAND_4) + Drinker->PolymorphRandomly(0, 1000000, 2500 + RAND() % 2500); + else + { + Drinker->ChangeRandomAttribute(-1); + Drinker->CheckDeath(CONST_S("died of contaminated water")); + } - switch(RAND_N(5)) - { - case 0: - Spawner = reinterpret_cast(&snake::Spawn); - AddChance = 66; - break; + break; + } case 1: - Spawner = reinterpret_cast(&mommo::Spawn); - Config = RAND_2 ? CONICAL : FLAT; - AddChance = 50; + ADD_MESSAGE("The water tasted very good."); + Drinker->EditNP(2500); + Drinker->ChangeRandomAttribute(1); break; case 2: - Spawner = reinterpret_cast(&spider::Spawn); - - if(RAND_4) + if(!(RAND() % 15)) { - Config = LARGE; - AddChance = 90; + ADD_MESSAGE("You have freed a spirit that grants you a wish. You may wish for an item."); + game::Wish(Drinker, + "%s appears from nothing and the spirit flies happily away!", + "Two %s appear from nothing and the spirit flies happily away!", false); } else - { - Config = GIANT; - AddChance = 75; - } + DryOut(Drinker); break; - case 3: - if(!RAND_N(50)) - { - Spawner = reinterpret_cast(&dolphin::Spawn); - AddChance = 75; - ForceAdjacency = true; - } - else if(!RAND_N(50)) + case 4: + if(RAND() & 7) { - Spawner = reinterpret_cast(&mysticfrog::Spawn); - Config = DARK; - AddChance = 1; + ADD_MESSAGE("The water tastes normal, but there is an odd after taste."); + Drinker->ActivateRandomState(SRC_FOUNTAIN, 10000 + RAND() % 20000); } else { - Spawner = reinterpret_cast(&frog::Spawn); + ADD_MESSAGE("This water tastes very odd."); - if(RAND_N(5)) - { - Config = DARK; - AddChance = 10; - } - else if(RAND_N(5)) - { - Config = GREATER_DARK; - AddChance = 5; - } - else - { - Config = GIANT_DARK; - AddChance = 2; - } + if(!Drinker->GainRandomIntrinsic(SRC_FOUNTAIN)) + ADD_MESSAGE("You feel like a penguin."); /* This is rather rare, so no harm done */ } break; - case 4: - Spawner = reinterpret_cast(&largerat::Spawn); - AddChance = 90; - break; - } + case 5: + { + characterspawner Spawner = 0; + int Config = 0, AddChance = 0; + truth ForceAdjacency = false; - int TeamIndex = RAND_N(3) ? MONSTER_TEAM : PLAYER_TEAM; - team* Team = game::GetTeam(TeamIndex); - int Amount = 1 + femath::LoopRoll(AddChance, 7); - spawnresult SR = GetLevel()->SpawnMonsters(Spawner, Team, GetPos(), Config, Amount, ForceAdjacency); + switch(RAND_N(5)) + { + case 0: + Spawner = reinterpret_cast(&snake::Spawn); + Config = (RAND() % 3) + 1; + AddChance = 66; + break; + case 1: + Spawner = reinterpret_cast(&mommo::Spawn); + Config = RAND_2 ? CONICAL : FLAT; + AddChance = 50; + break; + case 2: + Spawner = reinterpret_cast(&spider::Spawn); + + if(RAND_4) + { + Config = LARGE; + AddChance = 90; + } + else + { + Config = GIANT; + AddChance = 75; + } + + break; + case 3: + if(!RAND_N(50)) + { + Spawner = reinterpret_cast(&dolphin::Spawn); + AddChance = 75; + ForceAdjacency = true; + } + else if(!RAND_N(50)) + { + Spawner = reinterpret_cast(&mysticfrog::Spawn); + Config = DARK; + AddChance = 1; + } + else + { + Spawner = reinterpret_cast(&frog::Spawn); + + if(RAND_N(5)) + { + Config = DARK; + AddChance = 10; + } + else if(RAND_N(5)) + { + Config = GREATER_DARK; + AddChance = 5; + } + else + { + Config = GIANT_DARK; + AddChance = 2; + } + } + + break; + case 4: + Spawner = reinterpret_cast(&largerat::Spawn); + AddChance = 90; + break; + } - msgsystem::EnterBigMessageMode(); + int TeamIndex = RAND_N(3) ? MONSTER_TEAM : PLAYER_TEAM; + team* Team = game::GetTeam(TeamIndex); + int Amount = 1 + femath::LoopRoll(AddChance, 7); + spawnresult SR = GetLevel()->SpawnMonsters(Spawner, Team, GetPos(), Config, Amount, ForceAdjacency); - if(SR.Seen == 1) - { - ADD_MESSAGE("%s appears from the fountain!", SR.Pioneer->CHAR_NAME(INDEFINITE)); + msgsystem::EnterBigMessageMode(); - if(TeamIndex == PLAYER_TEAM) - ADD_MESSAGE("%s seems to be friendly.", SR.Pioneer->CHAR_PERSONAL_PRONOUN); + if(SR.Seen == 1) + { + ADD_MESSAGE("%s appears from the fountain!", SR.Pioneer->CHAR_NAME(INDEFINITE)); - if(Amount > SR.Seen) - ADD_MESSAGE("Somehow you also sense %s isn't alone.", SR.Pioneer->CHAR_PERSONAL_PRONOUN); - } - else if(SR.Seen) - { - ADD_MESSAGE("%s appear from the fountain!", SR.Pioneer->GetName(PLURAL, SR.Seen).CStr()); + if(TeamIndex == PLAYER_TEAM) + ADD_MESSAGE("%s seems to be friendly.", SR.Pioneer->CHAR_PERSONAL_PRONOUN); - if(TeamIndex == PLAYER_TEAM) - ADD_MESSAGE("They seem to be friendly."); + if(Amount > SR.Seen) + ADD_MESSAGE("Somehow you also sense %s isn't alone.", SR.Pioneer->CHAR_PERSONAL_PRONOUN); + } + else if(SR.Seen) + { + ADD_MESSAGE("%s appear from the fountain!", SR.Pioneer->GetName(PLURAL, SR.Seen).CStr()); - if(Amount > SR.Seen) - ADD_MESSAGE("Somehow you also sense you haven't yet seen all of them."); - } - else - ADD_MESSAGE("You feel something moving near you."); + if(TeamIndex == PLAYER_TEAM) + ADD_MESSAGE("They seem to be friendly."); - msgsystem::LeaveBigMessageMode(); - break; - } - case 6: - if(!RAND_N(5)) - { - item* ToBeCreated = protosystem::BalancedCreateItem(0, MAX_PRICE, RING); - GetLSquareUnder()->AddItem(ToBeCreated); + if(Amount > SR.Seen) + ADD_MESSAGE("Somehow you also sense you haven't yet seen all of them."); + } + else + ADD_MESSAGE("You feel something moving near you."); - if(ToBeCreated->CanBeSeenByPlayer()) - ADD_MESSAGE("There's something sparkling in the water."); + msgsystem::LeaveBigMessageMode(); + break; + } + case 6: + if(!RAND_N(5)) + { + item* ToBeCreated = protosystem::BalancedCreateItem(0, MAX_PRICE, RING); + GetLSquareUnder()->AddItem(ToBeCreated); - break; - } - case 7: - { - if(!RAND_N(2)) - { - olterrain* Found = GetLevel()->GetRandomFountainWithWater(this); - Drinker->RemoveTraps(); + if(ToBeCreated->CanBeSeenByPlayer()) + ADD_MESSAGE("There's something sparkling in the water."); - if(Found && RAND_N(3)) - { - ADD_MESSAGE("The fountain sucks you in. You are thrown through a network " - "of tunnels and end up coming out from an other fountain."); - Found->GetLSquareUnder()->KickAnyoneStandingHereAway(); - Drinker->Move(Found->GetPos(), true); + break; } - else + case 7: { - int To = GetLSquareUnder()->GetDungeon()->GetLevelTeleportDestination(GetLevel()->GetIndex()); - int From = GetLevel()->GetIndex(); + if(!RAND_N(2)) + { + olterrain* Found = GetLevel()->GetRandomFountainWithWater(this); + Drinker->RemoveTraps(); + + if(Found && RAND_N(3)) + { + ADD_MESSAGE("The fountain sucks you in. You are thrown through a network " + "of tunnels and end up coming out from an other fountain."); + Found->GetLSquareUnder()->KickAnyoneStandingHereAway(); + Drinker->Move(Found->GetPos(), true); + } + else + { + int To = GetLSquareUnder()->GetDungeon()->GetLevelTeleportDestination(GetLevel()->GetIndex()); + int From = GetLevel()->GetIndex(); + + if(To == From) + game::TryTravel(game::GetCurrentDungeonIndex(), To, RANDOM, true, false); + else + game::TryTravel(game::GetCurrentDungeonIndex(), To, FOUNTAIN, true, false); + + olterrain* OLTerrain = Drinker->GetLSquareUnder()->GetOLTerrain(); + + if(OLTerrain && OLTerrain->IsFountainWithWater() && To != From) + ADD_MESSAGE("The fountain sucks you in. You are thrown through a network " + "of tunnels and end up coming out from an other fountain."); + else + ADD_MESSAGE("The fountain sucks you in. You are thrown through a network " + "of tunnels. Suddenly the wall of the tunnel bursts open and " + "you fly out with the water."); + } + + Drinker->GetLSquareUnder()->SpillFluid(Drinker, liquid::Spawn(WATER, 1000 + RAND() % 501), false, false); + break; + } + } + default: + ADD_MESSAGE("The water tastes good."); + Drinker->EditNP(500); + break; + } - if(To == From) - game::TryTravel(game::GetCurrentDungeonIndex(), To, RANDOM, true, false); - else - game::TryTravel(game::GetCurrentDungeonIndex(), To, FOUNTAIN, true, false); + // fountain might have dried out: don't do anything here. - olterrain* OLTerrain = Drinker->GetLSquareUnder()->GetOLTerrain(); + return true; + } + else + { + if(Drinker->IsPlayer()) + ADD_MESSAGE("You drink some %s.", GetSecondaryMaterial()->GetName(false, false).CStr()); + else + ADD_MESSAGE("%s drinks some %s.", Drinker->CHAR_NAME(DEFINITE), GetSecondaryMaterial()->GetName(false, false).CStr()); - if(OLTerrain && OLTerrain->IsFountainWithWater() && To != From) - ADD_MESSAGE("The fountain sucks you in. You are thrown through a network " - "of tunnels and end up coming out from an other fountain."); - else - ADD_MESSAGE("The fountain sucks you in. You are thrown through a network " - "of tunnels. Suddenly the wall of the tunnel bursts open and " - "you fly out with the water."); - } + GetSecondaryMaterial()->EatEffect(Drinker, 500); - Drinker->GetLSquareUnder()->SpillFluid(Drinker, liquid::Spawn(WATER, 1000 + RAND() % 501), false, false); - break; - } + if(!RAND_N(20)) + DryOut(Drinker); + + return true; } - default: - ADD_MESSAGE("The water tastes good."); - Drinker->EditNP(500); - break; } + else if(GetSecondaryMaterial()->IsGaseous()) + { + ADD_MESSAGE("%s releases some %s.", CHAR_NAME(DEFINITE), GetSecondaryMaterial()->GetName(false, false).CStr()); + GetLSquareUnder()->AddSmoke(static_cast(GetSecondaryMaterial()->SpawnMore(100 + RAND() % 100))); - // fountain might have dried out: don't do anything here. + if(!RAND_N(20)) + DryOut(Drinker); - return true; - } - else - { - ADD_MESSAGE("You don't dare to drink from this fountain."); - return false; + return true; + } + else // Should not happen. + { + ADD_MESSAGE("You don't dare to drink from this fountain."); + return false; + } } } else @@ -599,7 +700,7 @@ truth fountain::Drink(character* Drinker) } } -void fountain::DryOut() +void fountain::DryOut(character* Dude) { ADD_MESSAGE("%s dries out.", CHAR_NAME(DEFINITE)); delete SetSecondaryMaterial(0); @@ -609,6 +710,15 @@ void fountain::DryOut() GetLSquareUnder()->SendNewDrawRequest(); GetLSquareUnder()->SendMemorizedUpdateRequest(); } + + // Drying fountain of an owned room makes the master angry. + room* Room = GetRoom(); + + if(Dude && Room && Room->MasterIsActive()) + { + if(Room->GetMaster() != Dude && Dude->IsPlayer()) + Dude->Hostility(Room->GetMaster()); + } } void brokendoor::BeKicked(character* Kicker, int KickDamage, int) @@ -764,17 +874,30 @@ void door::Break() void door::ActivateBoobyTrap() { + if(BoobyTrap && GetLSquareUnder()->CanBeSeenByPlayer(true)) + ADD_MESSAGE("%s is booby trapped!", CHAR_NAME(DEFINITE)); + switch(BoobyTrap) { - case 1: - // Explosion - if(LSquareUnder->CanBeSeenByPlayer(true)) - ADD_MESSAGE("%s is booby trapped!", CHAR_NAME(DEFINITE)); - - BoobyTrap = 0; - GetLevel()->Explosion(0, "killed by an exploding booby trapped door", - GetPos(), 20 + RAND() % 5 - RAND() % 5); - break; + case 1: // Explosion + { + BoobyTrap = 0; + GetLevel()->Explosion(0, "killed by an exploding booby-trapped door", + GetPos(), 20 + RAND() % 5 - RAND() % 5); + break; + } + case 2: // Gas + { + int GasMaterial[] = { MUSTARD_GAS, FART, SKUNK_SMELL, EVIL_WONDER_STAFF_VAPOUR, + SLEEPING_GAS, TELEPORT_GAS, LAUGHING_GAS, ACID_GAS, FIRE_GAS }; + BoobyTrap = 0; + + if(!RAND_4) + GetLevel()->GasExplosion(gas::Spawn(GasMaterial[RAND() % 9], 250), GetLSquareUnder(), 0); + else + GetLSquareUnder()->AddSmoke(gas::Spawn(GasMaterial[RAND() % 9], 250)); + break; + } case 0: break; } @@ -782,12 +905,16 @@ void door::ActivateBoobyTrap() void door::CreateBoobyTrap() { - SetBoobyTrap(1); + SetBoobyTrap(!RAND_N(4) ? 2 : 1); } truth fountain::DipInto(item* ToBeDipped, character* Who) { ToBeDipped->DipInto(static_cast(GetSecondaryMaterial()->SpawnMore(ToBeDipped->GetDefaultSecondaryVolume())), Who); + + if(!RAND_N(20)) + DryOut(Who); + return true; } @@ -1268,7 +1395,7 @@ truth door::IsTransparent() const truth liquidterrain::DipInto(item* ToBeDipped, character* Who) { - ToBeDipped->DipInto(static_cast(GetMainMaterial()->SpawnMore(100)), Who); + ToBeDipped->DipInto(static_cast(GetMainMaterial()->SpawnMore(ToBeDipped->GetDefaultSecondaryVolume())), Who); return true; } @@ -1377,15 +1504,18 @@ truth coffin::Open(character* Opener) truth Success = olterraincontainer::Open(Opener); if(Success) { + ADD_MESSAGE("You feel a horrible curse spreading."); game::DoEvilDeed(25); + /* TODO: This function awaits repair. for(int c = 0; c < RAND_N(10); ++c) { v2 Pos = GetLevel()->GetRandomSquare(); if(Pos != ERROR_V2) { - //GenerateGhost(GetLevel()->GetLSquare(Pos)); // This function awaits repair + GenerateGhost(GetLevel()->GetLSquare(Pos)); } - } + }*/ + GenerateUndead(); } Opened = false; @@ -1397,21 +1527,23 @@ truth coffin::Open(character* Opener) void coffin::Break() { + GenerateUndead(); + /* TODO: This function awaits repair. for(int c = 0; c < 9; ++c) { lsquare* Neighbour = GetLSquareUnder()->GetNeighbourLSquare(c); if(!RAND_4 && Neighbour && Neighbour->IsFlyable()) { - //GenerateGhost(Neighbour); // This function awaits repair + GenerateGhost(Neighbour); } - } + }*/ olterraincontainer::Break(); } void coffin::GenerateGhost(lsquare* Square) { -/* +/* TODO: Fix this! v2 Pos = Square->GetPos(); character* Char = ghost::Spawn(); // Fix this Char->SetTeam(game::GetTeam(MONSTER_TEAM)); @@ -1429,6 +1561,44 @@ void coffin::GenerateGhost(lsquare* Square) */ } +void coffin::GenerateUndead() +{ + for(int c = 0; c < game::GetTeams(); ++c) + for(character* p : game::GetTeam(c)->GetMember()) + if(!p->IsEnabled() && p->GetMotherEntity() + && p->GetMotherEntity()->Exists()) + { + character* Zombie = p->GetMotherEntity()->TryNecromancy(0); + + if(Zombie && Zombie->CanBeSeenByPlayer()) + ADD_MESSAGE("%s is raised back to cursed undead life.", Zombie->CHAR_NAME(DEFINITE)); + } +} + +void coffin::PostConstruct() +{ + // Some grave goods to make the players steal from the dead. + long ItemNumber = RAND() % 6; + + for(int c = 0; c < ItemNumber; ++c) + { + item* NewItem = protosystem::BalancedCreateItem(); + long Volume = NewItem->GetVolume(); + + if(NewItem->HandleInPairs()) + Volume <<= 1; + + if(NewItem->CanBeGeneratedInContainer() + && (GetStorageVolume() - GetContained()->GetVolume()) >= Volume) + { + GetContained()->AddItem(NewItem); + NewItem->SpecialGenerationHandler(); + } + else + delete NewItem; + } +} + void barwall::Break() { if(GetConfig() == BROKEN_BARWALL) diff --git a/Main/Source/materia.cpp b/Main/Source/materia.cpp index 440d23195..c336c7cc0 100644 --- a/Main/Source/materia.cpp +++ b/Main/Source/materia.cpp @@ -95,13 +95,13 @@ truth material::Effect(character* Char, int BodyPart, long Amount) { if(!Char->StateIsActivated(DISEASE_IMMUNITY)) { - Char->BeginTemporaryState(LYCANTHROPY, Amount); - break; - } - else - { - break; + if(!RAND_N(Char->GetAttribute(ENDURANCE))) + Char->GainIntrinsic(LYCANTHROPY); + else + Char->BeginTemporaryState(LYCANTHROPY, Amount); } + + break; } case EFFECT_SCHOOL_FOOD: Char->ReceiveSchoolFood(Amount); break; case EFFECT_ANTIDOTE: Char->ReceiveAntidote(Amount); break; @@ -158,23 +158,25 @@ truth material::Effect(character* Char, int BodyPart, long Amount) case EFFECT_VAMPIRISM: { if(!Char->StateIsActivated(DISEASE_IMMUNITY)) - { Char->BeginTemporaryState(VAMPIRISM, Amount); - break; - } - else - { - break; - } + + break; } case EFFECT_PANACEA: { Char->ReceiveHeal(Amount); Char->ReceiveAntidote(Amount); + Char->RestoreStamina(); break; } case EFFECT_OMMEL_BLOOD: Char->ReceiveOmmelBlood(Amount); break; - case EFFECT_PANIC: Char->BeginTemporaryState(PANIC, Amount); break; + case EFFECT_PANIC: + { + if(!Char->StateIsActivated(FEARLESS) && Char->GetPanicLevel() > 0) + Char->BeginTemporaryState(PANIC, Amount); + + break; + } case EFFECT_TRAIN_WISDOM: { Char->EditExperience(WISDOM, Amount, 1 << 14); @@ -187,6 +189,18 @@ truth material::Effect(character* Char, int BodyPart, long Amount) Char->TeleportRandomly(false); break; } + case EFFECT_LAUGH: + { + game::CallForAttention(Char->GetPos(), Amount); + Char->BeginTemporaryState(HICCUPS, Amount); + break; + } + case EFFECT_POLYJUICE: Char->PolymorphRandomly(Amount, 999999, Amount * 10); break; + //case EFFECT_PUKE: Char->VomitAtRandomDirection(Amount); break; + case EFFECT_SICKNESS: Char->ReceiveSickness(Amount); break; + case EFFECT_PHASE: Char->BeginTemporaryState(ETHEREAL_MOVING, Amount); break; + case EFFECT_ACID_GAS: Char->SpillFluid(0, liquid::Spawn(SULPHURIC_ACID, Amount)); break; + case EFFECT_FIRE_GAS: Char->ReceiveFlames(Amount); break; default: return false; } @@ -196,7 +210,13 @@ truth material::Effect(character* Char, int BodyPart, long Amount) material* material::EatEffect(character* Eater, long Amount) { Amount = Volume > Amount ? Amount : Volume; + + if(Eater->StateIsActivated(VAMPIRISM) && (GetCategoryFlags() & IS_BLOOD)) + { + Amount *= 10; // Vampires are nourished by blood. + } Eater->ReceiveNutrition(GetNutritionValue() * Amount / 50); + if(Amount && Volume) { if(DisablesPanicWhenConsumed() && Eater->TemporaryStateIsActivated(PANIC)) diff --git a/Main/Source/materias.cpp b/Main/Source/materias.cpp index 3ec64357b..d8b8240f6 100644 --- a/Main/Source/materias.cpp +++ b/Main/Source/materias.cpp @@ -460,9 +460,12 @@ void liquid::TouchEffect(item* Item, cfestring& LocationName) if(GetAcidicity()) Item->ReceiveAcid(this, LocationName, Volume * GetAcidicity()); - if(Item->IsBurning()) + if(Item->IsBurning() && !GetHotness() && !GetExplosivePower()) Item->FightFire(this, LocationName, Volume); + if(GetHotness()) + Item->ReceiveHeat(this, LocationName, Volume * GetHotness()); + character* Char = Item->GetBodyPartMaster(); if(Char) @@ -476,6 +479,9 @@ void liquid::TouchEffect(lterrain* Terrain) if(GetAcidicity()) Terrain->ReceiveAcid(this, Volume * GetAcidicity()); + + if(GetHotness()) + Terrain->ReceiveHeat(this, Volume * GetHotness()); } void liquid::TouchEffect(character* Char, int BodyPartIndex) @@ -486,6 +492,9 @@ void liquid::TouchEffect(character* Char, int BodyPartIndex) if(Char->IsEnabled() && GetAcidicity()) Char->GetBodyPart(BodyPartIndex)->ReceiveAcid(this, CONST_S(""), Volume * GetAcidicity() >> 1); + if(Char->IsEnabled() && GetHotness()) + Char->GetBodyPart(BodyPartIndex)->ReceiveHeat(this, CONST_S(""), Volume * GetHotness() >> 1); + if(Char->IsEnabled()) Effect(Char, BodyPartIndex, Volume >> 9); } diff --git a/Main/Source/message.cpp b/Main/Source/message.cpp index 42b1154f8..43f906acb 100644 --- a/Main/Source/message.cpp +++ b/Main/Source/message.cpp @@ -362,6 +362,9 @@ void soundsystem::initSound() if(!fNew) SoundState = -1; truth bDbg=false; //TODO global command line for debug messages +#ifdef DBGMSG + bDbg=true; +#endif if(bDbg)std::cout << "Sound Effects (new) config file setup:" << std::endl; if(fNew) { @@ -469,10 +472,14 @@ void soundsystem::deInitSound() SoundFile *soundsystem::findMatchingSound(festring Buffer) { - for(int i = patterns.size() - 1; i >= 0; i--) - if(*patterns[i].re) - if(pcre_exec(*patterns[i].re, *patterns[i].extra, Buffer.CStr(), Buffer.GetSize(), 0, 0, NULL, 0) >= 0) - return &files[patterns[i].sounds[rand() % patterns[i].sounds.size()]]; + if(Buffer.IsEmpty() || Buffer.CStr()[0]=='"') //skips all chat messages lowering config file regex complexity + return NULL; + + for(int i = patterns.size() - 1; i >= 0; i--){ + if(*patterns[i].re) + if(pcre_exec(*patterns[i].re, *patterns[i].extra, Buffer.CStr(), Buffer.GetSize(), 0, 0, NULL, 0) >= 0) + return &files[patterns[i].sounds[rand() % patterns[i].sounds.size()]]; + } return NULL; } @@ -493,17 +500,17 @@ void soundsystem::playSound(festring Buffer) if(*sf->chunk) { - for(int i=0; i<16; i++) - { - if(!Mix_Playing(i)) - { - Mix_PlayChannel(i, *sf->chunk, 0); -// fprintf(debf, "Mix_PlayChannel(%d, \"%s\", 0);\n", i, sf->filename.CStr()); - // Mix_SetPosition(i, angle, dist); - return; - } - } - } + for(int i=0; i<16; i++) + { + if(!Mix_Playing(i)) + { + Mix_PlayChannel(i, *sf->chunk, 0); +// fprintf(debf, "Mix_PlayChannel(%d, \"%s\", 0);\n", i, sf->filename.CStr()); + // Mix_SetPosition(i, angle, dist); + return; + } + } + } } } diff --git a/Main/Source/miscitem.cpp b/Main/Source/miscitem.cpp index fb8ca69ab..72e88b3b8 100644 --- a/Main/Source/miscitem.cpp +++ b/Main/Source/miscitem.cpp @@ -56,6 +56,10 @@ truth backpack::IsExplosive() const { return GetSecondaryMaterial() && GetSecond long backpack::GetTotalExplosivePower() const { return GetSecondaryMaterial() ? GetSecondaryMaterial()->GetTotalExplosivePower() : 0; } +truth nuke::IsExplosive() const { return GetSecondaryMaterial() && GetSecondaryMaterial()->IsExplosive(); } +long nuke::GetTotalExplosivePower() const +{ return GetSecondaryMaterial() ? GetSecondaryMaterial()->GetTotalExplosivePower() : 0; } + long stone::GetTruePrice() const { return item::GetTruePrice() << 1; } //long ingot::GetTruePrice() const { return item::GetTruePrice() << 1; } @@ -87,6 +91,14 @@ alpha skullofxinroch::GetOutlineAlpha(int Frame) const return 50 + (Frame * (31 - Frame) >> 1); } +col16 weepobsidian::GetOutlineColor(int) const { return MakeRGB16(0, 0, 180); } + +alpha weepobsidian::GetOutlineAlpha(int Frame) const +{ + Frame &= 31; + return 50 + (Frame * (31 - Frame) >> 1); +} + truth scroll::CanBeRead(character* Reader) const { return Reader->CanRead() || game::GetSeeWholeMapCheatMode(); @@ -324,16 +336,16 @@ void scrollofbodyswitch::FinishReading(character* Reader) } else { - ADD_MESSAGE("Your mind is not strong enough for the transfer! The scroll turns to dust."); + ADD_MESSAGE("For a moment you feel very much like %s, but your mind is not strong enough for the transfer! The scroll turns to dust.", ToPossess->CHAR_NAME(INDEFINITE)); + Reader->Hostility(ToPossess); } RemoveFromSlot(); SendToHell(); } else - { ADD_MESSAGE("There's no one to possess, %s!", game::Insult()); - return; - } + + return; } truth wand::Apply(character* Terrorist) @@ -1021,6 +1033,11 @@ void mine::StepOnEffect(character* Stepper) if(!WillExplode(Stepper)) return; + // NPCs should get some benefit from searching too, so make them immune to + // traps if they have it. + if(!Stepper->IsPlayer() && Stepper->StateIsActivated(SEARCHING)) + return; + if(Stepper->IsPlayer()) { cchar* SenseVerb = Stepper->CanHear() ? "hear" : "sense"; @@ -1085,7 +1102,22 @@ truth key::Apply(character* User) } } - if(User->GetStack()->SortedItems(User, &item::HasLock)) + truth HasLockableItem = User->GetStack()->SortedItems(User, &item::HasLock); + if(!HasLockableItem) + { + for(int c = 0; c < User->GetEquipments(); ++c) + { + item* Equipment = User->GetEquipment(c); + + if(Equipment && Equipment->HasLock(User)) + { + HasLockableItem = true; + break; + } + } + } + + if(HasLockableItem) { if(SquaresWithOpenableItems.empty() && OpenableOLTerrains.empty()) Key = 'i'; @@ -1095,9 +1127,8 @@ truth key::Apply(character* User) if(Key == 'i') { - item* Item = User->GetStack()->DrawContents(User, CONST_S("What do you want " - "to lock or unlock?"), - 0, &item::HasLock); + item* Item = User->SelectFromPossessions(CONST_S("What do you want to lock or unlock?"), + &item::HasLock); return Item && Item->TryKey(this, User); } } @@ -1206,18 +1237,34 @@ truth whistle::Apply(character* Whistler) void whistle::BlowEffect(character* Whistler) { + cchar* SoundDescription; + if(LastUsed && (game::GetTick() - LastUsed < GetCooldown(2000, Whistler))) + SoundDescription = "an interesting"; + else + { + LastUsed = game::GetTick(); + + switch (RAND() % 4) + { + case 0: SoundDescription = "a shrill"; break; + case 1: SoundDescription = "a piercing"; break; + case 2: SoundDescription = "a loud"; break; + case 3: SoundDescription = "a strange"; break; + } + } + if(Whistler->IsPlayer()) { if(Whistler->CanHear()) - ADD_MESSAGE("You produce an interesting sound."); + ADD_MESSAGE("You produce %s sound.", SoundDescription); else ADD_MESSAGE("You blow %s", CHAR_NAME(DEFINITE)); } else if(Whistler->CanBeSeenByPlayer()) { if(PLAYER->CanHear()) - ADD_MESSAGE("%s blows %s and produces an interesting sound.", - Whistler->CHAR_NAME(DEFINITE), CHAR_NAME(DEFINITE)); + ADD_MESSAGE("%s blows %s and produces %s sound.", + Whistler->CHAR_NAME(DEFINITE), CHAR_NAME(DEFINITE), SoundDescription); else ADD_MESSAGE("%s blows %s.", Whistler->CHAR_NAME(DEFINITE), CHAR_NAME(DEFINITE)); } @@ -1237,7 +1284,7 @@ struct distancepair void magicalwhistle::BlowEffect(character* Whistler) { - if(LastUsed && ((game::GetTick() - LastUsed) < Whistler->GetMagicItemCooldown(2000))) + if(LastUsed && ((game::GetTick() - LastUsed) < GetCooldown(2000, Whistler))) { whistle::BlowEffect(Whistler); return; @@ -1507,6 +1554,11 @@ beartrap::~beartrap() void beartrap::StepOnEffect(character* Stepper) { + // NPCs should get some benefit from searching too, so make them immune to + // traps if they have it. + if(!Stepper->IsPlayer() && Stepper->StateIsActivated(SEARCHING)) + return; + if(IsActive() && !IsBroken()) { int StepperBodyPart = Stepper->GetRandomStepperBodyPart(); @@ -1743,7 +1795,8 @@ truth wand::Zap(character* Zapper, v2, int Direction) GetBeamEffect(), Direction, GetBeamRange(), - GetSpecialParameters() + GetSpecialParameters(), + this ); (GetLevel()->*level::GetBeam(GetBeamStyle()))(Beam); @@ -2090,18 +2143,6 @@ void itemcontainer::DrawContents(ccharacter* Char) i->DrawContents(Char); } -void magicalwhistle::Save(outputfile& SaveFile) const -{ - whistle::Save(SaveFile); - SaveFile << LastUsed; -} - -void magicalwhistle::Load(inputfile& SaveFile) -{ - whistle::Load(SaveFile); - SaveFile >> LastUsed; -} - int materialcontainer::GetSpoilLevel() const { return Max(MainMaterial->GetSpoilLevel(), SecondaryMaterial ? SecondaryMaterial->GetSpoilLevel() : 0); @@ -2130,10 +2171,18 @@ void itemcontainer::SetItemsInside(const fearray>& ItemArray } } -truth mine::CheckPickUpEffect(character*) +truth mine::CheckPickUpEffect(character* Picker) { if(WillExplode(0)) { + // Allow the player to sometimes defuse the mine. + if(Picker-> IsPlayer() && !RAND_N(80 / Max(Picker->GetAttribute(DEXTERITY), 1))) + { + SetIsActive(false); + ADD_MESSAGE("You successfully defuse %s.", CHAR_NAME(DEFINITE)); + return true; + } + lsquare* Square = GetLSquareUnder(); if(Square->CanBeSeenByPlayer(true)) @@ -2229,7 +2278,7 @@ truth horn::Apply(character* Blower) return false; } - if(!LastUsed || game::GetTick() - LastUsed >= Blower->GetMagicItemCooldown(2500)) + if(!LastUsed || game::GetTick() - LastUsed >= GetCooldown(2500, Blower)) { LastUsed = game::GetTick(); Blower->EditExperience(MANA, 150, 1 << 12); @@ -2289,6 +2338,7 @@ truth horn::Apply(character* Blower) } } else if(GetConfig() == FEAR && !Audience->TemporaryStateIsActivated(PANIC) && !Audience->StateIsActivated(FEARLESS) + && Audience->GetPanicLevel() > (RAND() % (50 - Min(PLAYER->GetAttribute(CHARISMA), 49))) && Blower->GetRelation(Audience) == HOSTILE && Audience->HornOfFearWorks()) Audience->BeginTemporaryState(PANIC, 500 + RAND() % 500); else if(GetConfig() == CONFUSION && Blower->GetRelation(Audience) == HOSTILE && Audience->CanHear()) @@ -2297,7 +2347,7 @@ truth horn::Apply(character* Blower) { if(Audience->IsPlayer()) ADD_MESSAGE("Your wounds are healed."); - else if(CanBeSeenByPlayer()) + else if(Audience->CanBeSeenByPlayer()) ADD_MESSAGE("%s looks sound and hale again.", Audience->CHAR_NAME(DEFINITE)); Audience->RestoreLivingHP(); @@ -2374,13 +2424,13 @@ truth horn::Apply(character* Blower) return true; } -void horn::Save(outputfile& SaveFile) const +void magicalinstrument::Save(outputfile& SaveFile) const { item::Save(SaveFile); SaveFile << LastUsed; } -void horn::Load(inputfile& SaveFile) +void magicalinstrument::Load(inputfile& SaveFile) { item::Load(SaveFile); SaveFile >> LastUsed; @@ -2537,8 +2587,13 @@ void wand::BreakEffect(character* Terrorist, cfestring& DeathMsg) ( Terrorist, DeathMsg, + Pos, + GetBeamColor(), + GetBeamEffect(), YOURSELF, - GetSpecialParameters() + GetBeamRange(), + GetSpecialParameters(), + NULL // Not this, because it won't exist in a second. ); for(c = 0; c < Stack.Size; ++c) @@ -2661,7 +2716,8 @@ truth holybanana::Zap(character* Zapper, v2, int Direction) BEAM_FIRE_BALL, Direction, 50, - 0 + 0, + this ); (GetLevel()->*level::GetBeam(PARTICLE_BEAM))(Beam); @@ -2757,21 +2813,27 @@ void itemcontainer::FinalProcessForBone() Contained->FinalProcessForBone(); } -void magicalwhistle::FinalProcessForBone() +void magicalinstrument::FinalProcessForBone() { - whistle::FinalProcessForBone(); + item::FinalProcessForBone(); LastUsed = 0; } -void horn::FinalProcessForBone() +int magicalinstrument::GetCooldown(int BaseCooldown, character* User) { - item::FinalProcessForBone(); - LastUsed = 0; + int Attribute = User->GetAttribute(MANA); + + if(Attribute > 1) + { + return BaseCooldown / log10(Attribute); + } + + return BaseCooldown / 0.20; } truth charmlyre::Apply(character* Charmer) { - if(LastUsed && game::GetTick() - LastUsed < Charmer->GetMagicItemCooldown(10000)) + if(LastUsed && game::GetTick() - LastUsed < GetCooldown(10000, Charmer)) { if(Charmer->IsPlayer()) { @@ -2849,29 +2911,6 @@ truth charmlyre::Apply(character* Charmer) return true; } -void charmlyre::Save(outputfile& SaveFile) const -{ - item::Save(SaveFile); - SaveFile << LastUsed; -} - -void charmlyre::Load(inputfile& SaveFile) -{ - item::Load(SaveFile); - SaveFile >> LastUsed; -} - -charmlyre::charmlyre() -{ - LastUsed = 0; -} - -void charmlyre::FinalProcessForBone() -{ - item::FinalProcessForBone(); - LastUsed = 0; -} - truth carrot::BunnyWillCatchAndConsume(ccharacter* Bunny) const { return GetConsumeMaterial(Bunny)->GetConfig() == CARROT_FLESH @@ -3764,14 +3803,15 @@ truth ullrbone::Zap(character* Zapper, v2, int Direction) beamdata Beam ( - Zapper, - CONST_S("killed by ") + GetName(INDEFINITE), - Zapper->GetPos(), - YELLOW, - BEAM_LIGHTNING, - Direction, - 50, - 0 + Zapper, + CONST_S("killed by ") + GetName(INDEFINITE), + Zapper->GetPos(), + YELLOW, + BEAM_LIGHTNING, + Direction, + 50, + 0, + this ); (GetLevel()->*level::GetBeam(PARTICLE_BEAM))(Beam); @@ -3819,11 +3859,11 @@ alpha ullrbone::GetOutlineAlpha(int Frame) const return 50 + (Frame * (31 - Frame) >> 1); } -material* trinket::RemoveMaterial(material* Material) +material* fish::RemoveMaterial(material* Material) { if(GetConfig() == DEAD_FISH) { - item* Bones = trinket::Spawn(BONE_FISH); + item* Bones = fish::Spawn(BONE_FISH); DonateSlotTo(Bones); DonateIDTo(Bones); SendToHell(); @@ -3833,56 +3873,71 @@ material* trinket::RemoveMaterial(material* Material) return item::RemoveMaterial(Material); } -truth trinket::Necromancy(character*) +truth fish::Necromancy(character*) { if(GetConfig() == BONE_FISH) { - GetSlot()->AddFriendItem(trinket::Spawn(DEAD_FISH)); + GetSlot()->AddFriendItem(fish::Spawn(DEAD_FISH)); RemoveFromSlot(); SendToHell(); return true; } - else - return false; + + return false; } -truth trinket::RaiseTheDead(character*) +truth fish::RaiseTheDead(character*) { + // TODO: Spawn a fish creature if done on WATER liquidterrain. + ADD_MESSAGE("%s suddenly comes back to life, but quickly suffocates again.", CHAR_NAME(DEFINITE)); + if(GetConfig() == BONE_FISH) { - ADD_MESSAGE("%s suddenly comes back to life, but quickly suffocates again.", CHAR_NAME(DEFINITE)); - GetSlot()->AddFriendItem(trinket::Spawn(DEAD_FISH)); + GetSlot()->AddFriendItem(fish::Spawn(DEAD_FISH)); RemoveFromSlot(); SendToHell(); return true; } - if(GetConfig() == DEAD_FISH) - { - ADD_MESSAGE("%s suddenly comes back to life, but quickly suffocates again.", CHAR_NAME(DEFINITE)); - return false; - } - else - return false; + + return false; } col16 trinket::GetMaterialColorB(int) const { - if(GetConfig() == POTTED_CACTUS) { return MakeRGB16(87, 59, 12); } - if(GetConfig() == POTTED_PLANT) { return MakeRGB16(200, 0, 0); } - if(GetConfig() == SMALL_CLOCK) { return MakeRGB16(124, 50, 16); } - if(GetConfig() == LARGE_CLOCK) { return MakeRGB16(124, 50, 16); } - else { return MakeRGB16(0, 0, 0); } + switch (GetConfig()) + { + case POTTED_CACTUS: return MakeRGB16(87, 59, 12); + case POTTED_PLANT: return MakeRGB16(200, 0, 0); + case SMALL_CLOCK: return MakeRGB16(124, 50, 16); + case LARGE_CLOCK: return MakeRGB16(124, 50, 16); + default: return MakeRGB16(0, 0, 0); + } } col16 trinket::GetMaterialColorC(int) const { - if(GetConfig() == POTTED_CACTUS) { return MakeRGB16(0, 160, 0); } - if(GetConfig() == POTTED_PLANT) { return MakeRGB16(0, 160, 0); } - else { return MakeRGB16(0, 0, 0); } + switch (GetConfig()) + { + case POTTED_CACTUS: return MakeRGB16(0, 160, 0); + case POTTED_PLANT: return MakeRGB16(0, 160, 0); + default: return MakeRGB16(0, 0, 0); + } +} + +truth fish::CatWillCatchAndConsume(ccharacter* Kitty) const +{ + return GetConfig() == DEAD_FISH && + GetConsumeMaterial(Kitty)->GetConfig() == SARDINE && + !GetConsumeMaterial(Kitty)->GetSpoilLevel(); } void gastrap::StepOnEffect(character* Stepper) { + // NPCs should get some benefit from searching too, so make them immune to + // traps if they have it. + if(!Stepper->IsPlayer() && Stepper->StateIsActivated(SEARCHING)) + return; + if(IsActive() && !IsBroken()) { if(Stepper->IsPlayer()) @@ -3893,6 +3948,14 @@ void gastrap::StepOnEffect(character* Stepper) if(Stepper->IsPlayer()) game::AskForKeyPress(CONST_S("Trap activated! [press any key to continue]")); + // Reveal the trap when we step on it. + int ViewerTeam = Stepper->GetTeam()->GetID(); + if(ViewerTeam != Team && DiscoveredByTeam.find(ViewerTeam) == DiscoveredByTeam.end()) + { + DiscoveredByTeam.insert(ViewerTeam); + SendNewDrawAndMemorizedUpdateRequest(); + } + if(GetSecondaryMaterial()) { if(CanBeSeenByPlayer()) @@ -4099,7 +4162,7 @@ void locationmap::FinishReading(character* Reader) /* Add new locations here. */ } - game::GetWorldMap()->RevealEnvironment(NewPos, 1); + game::GetWorldMap()->RevealEnvironment(NewPos, 0); game::SaveWorldMap(); ADD_MESSAGE("The map reveals to you a secret site. You quickly commit the location to memory as the map burns up."); @@ -4109,3 +4172,43 @@ void locationmap::FinishReading(character* Reader) SendToHell(); Reader->EditExperience(INTELLIGENCE, 150, 1 << 12); } + +truth nuke::Apply(character* Terrorist) +{ + if(IsExplosive()) + { + if(Terrorist->IsPlayer()) + ADD_MESSAGE("You attempt to arm %s, but you don't have the required eighteen-digit activation code.", CHAR_NAME(DEFINITE)); + else if(Terrorist->CanBeSeenByPlayer()) + ADD_MESSAGE("%s fiddles with %s, but nothing seems to happen.", Terrorist->CHAR_NAME(DEFINITE), CHAR_NAME(DEFINITE)); + + return true; + } + else if(Terrorist->IsPlayer()) + ADD_MESSAGE("%s unfortunately seems to be inoperative.", CHAR_NAME(DEFINITE)); + + return false; +} + +void weepobsidian::Be() +{ + stone::Be(); + + if(Exists() && !game::IsInWilderness()) + { + if(!RAND_N(1000)) + { + beamdata Beam + ( + 0, + CONST_S("drowned by the tears of ") + CHAR_NAME(DEFINITE), + YOURSELF, + 0 + ); + GetLSquareUnder()->LiquidRain(Beam, WATER); + + if(CanBeSeenByPlayer()) + ADD_MESSAGE("%s releases torrential rain.", CHAR_NAME(DEFINITE)); + } + } +} diff --git a/Main/Source/nonhuman.cpp b/Main/Source/nonhuman.cpp index 38b3dd9cc..326d97d51 100644 --- a/Main/Source/nonhuman.cpp +++ b/Main/Source/nonhuman.cpp @@ -30,7 +30,7 @@ cchar* billswill::ThirdPersonCriticalBiteVerb() const { return "emits powerful p int billswill::GetBodyPartWobbleData(int) const { return WOBBLE_HORIZONTALLY|(2 << WOBBLE_FREQ_SHIFT); } int mommo::GetBodyPartWobbleData(int) const -{ return (GetConfig() == CONICAL ? WOBBLE_HORIZONTALLY : WOBBLE_VERTICALLY)|(2 << WOBBLE_FREQ_SHIFT); } +{ return (GetConfig() % 2 != 0 ? WOBBLE_HORIZONTALLY : WOBBLE_VERTICALLY)|(2 << WOBBLE_FREQ_SHIFT); } bodypart* dog::MakeBodyPart(int) const { return dogtorso::Spawn(0, NO_MATERIALS); } @@ -53,6 +53,8 @@ int eddy::GetBodyPartWobbleData(int) const { return WOBBLE_VERTICALLY|(2 << WOBB bodypart* magicmushroom::MakeBodyPart(int) const { return magicmushroomtorso::Spawn(0, NO_MATERIALS); } +bodypart* fusanga::MakeBodyPart(int) const { return fusangatorso::Spawn(0, NO_MATERIALS); } + cchar* magpie::FirstPersonBiteVerb() const { return "peck"; } cchar* magpie::FirstPersonCriticalBiteVerb() const { return "critically peck"; } cchar* magpie::ThirdPersonBiteVerb() const { return "pecks"; } @@ -68,6 +70,8 @@ int hattifattener::GetBodyPartWobbleData(int) const col16 vladimir::GetSkinColor() const { return MakeRGB16(60 + RAND() % 190, 60 + RAND() % 190, 60 + RAND() % 190); } +col16 fusanga::GetSkinColor() const { return MakeRGB16(60 + RAND() % 190, 60 + RAND() % 190, 60 + RAND() % 190); } + bodypart* blinkdog::MakeBodyPart(int) const { return blinkdogtorso::Spawn(0, NO_MATERIALS); } int mysticfrog::GetBodyPartWobbleData(int) const @@ -118,7 +122,7 @@ truth elpuri::Hit(character* Enemy, v2, int, int Flags) return true; } -truth dog::Catches(item* Thingy) +truth canine::Catches(item* Thingy) { if(Thingy->DogWillCatchAndConsume(this)) { @@ -131,7 +135,38 @@ truth dog::Catches(item* Thingy) if(CanBeSeenByPlayer()) ADD_MESSAGE("%s catches %s and eats it.", CHAR_NAME(DEFINITE), Thingy->CHAR_NAME(DEFINITE)); - ChangeTeam(PLAYER->GetTeam()); + if(PLAYER->GetRelativeDanger(this, true) > 0.1) + ChangeTeam(PLAYER->GetTeam()); + ADD_MESSAGE("%s seems to be much more friendly towards you.", CHAR_NAME(DEFINITE)); + } + } + else if(IsPlayer()) + ADD_MESSAGE("You catch %s in mid-air.", Thingy->CHAR_NAME(DEFINITE)); + else if(CanBeSeenByPlayer()) + ADD_MESSAGE("%s catches %s.", CHAR_NAME(DEFINITE), Thingy->CHAR_NAME(DEFINITE)); + + return true; + } + else + return false; +} + +truth feline::Catches(item* Thingy) +{ + if(Thingy->CatWillCatchAndConsume(this)) + { + if(ConsumeItem(Thingy, CONST_S("eating"))) + { + if(IsPlayer()) + ADD_MESSAGE("You catch %s in mid-air and consume it.", Thingy->CHAR_NAME(DEFINITE)); + else + { + if(CanBeSeenByPlayer()) + ADD_MESSAGE("%s catches %s and eats it.", CHAR_NAME(DEFINITE), Thingy->CHAR_NAME(DEFINITE)); + + if(PLAYER->GetRelativeDanger(this, true) > 0.1) + ChangeTeam(PLAYER->GetTeam()); + ADD_MESSAGE("%s seems to be much more friendly towards you.", CHAR_NAME(DEFINITE)); } } else if(IsPlayer()) @@ -532,7 +567,13 @@ void nonhumanoid::CalculateBiteAttackInfo() void dog::BeTalkedTo() { - if(RAND_N(5)) + if(StateIsActivated(CONFUSED)) + { + ADD_MESSAGE("%s looks a bit confused: \"Meow.\"", CHAR_NAME(DEFINITE)); + return; + } + + if(GetPos().IsAdjacent(PLAYER->GetPos())) { if(GetRelation(PLAYER) != HOSTILE) { @@ -540,20 +581,21 @@ void dog::BeTalkedTo() cchar* Reply; if(GetHP() << 1 > GetMaxHP()) - Reply = Last ? "barks happily" : "wags its tail happily"; + Reply = Last ? "barks happily" : "wags its tail"; else Reply = Last ? "yelps" : "howls"; ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE), Reply); Last = !Last; } + else if(!RAND_N(100)) + ADD_MESSAGE("%s scoffs at you: \"Can't you understand I can't speak?\"", CHAR_NAME(DEFINITE)); else - character::BeTalkedTo(); + ADD_MESSAGE("%s snarls at you.", CHAR_NAME(DEFINITE)); + return; } - else if(RAND_N(5) && GetPos().IsAdjacent(PLAYER->GetPos())) - ADD_MESSAGE("\"Can't you understand I can't speak?\""); - else - ADD_MESSAGE("\"Meow.\""); + + character::BeTalkedTo(); } void dog::CreateCorpse(lsquare* Square) @@ -664,7 +706,9 @@ col16 carnivorousplant::GetTorsoSpecialColor() const // the flower void ostrich::GetAICommand() { - if(game::TweraifIsFree()) + if(game::TweraifIsFree() || + (GetDungeon()->GetIndex() != NEW_ATTNAM) + ) // Behave normally outside of New Attnam. { nonhumanoid::GetAICommand(); return; @@ -916,24 +960,63 @@ void elpuri::CreateCorpse(lsquare* Square) truth snake::SpecialBiteEffect(character* Char, v2, int, int, truth BlockedByArmour, truth Critical, int DoneDamage) { - if(!BlockedByArmour) + if(!BlockedByArmour || Critical) { - Char->BeginTemporaryState(POISONED, 400 + RAND_N(200)); + switch (GetConfig()) + { + case RED_SNAKE: Char->BeginTemporaryState(PANIC, 400 + RAND_N(200)); break; + case GREEN_SNAKE: Char->BeginTemporaryState(POISONED, 400 + RAND_N(200)); break; + case BLUE_SNAKE: Char->BeginTemporaryState(SLOW, 400 + RAND_N(200)); break; + } return true; } else return false; } -truth spider::SpecialBiteEffect(character* Char, v2, int, int, truth BlockedByArmour, truth Critical, int DoneDamage) +truth spider::SpecialBiteEffect(character* Victim, v2 HitPos, int BodyPartIndex, int Direction, truth BlockedByArmour, truth Critical, int DoneDamage) { - if(!BlockedByArmour) + if(!BlockedByArmour || Critical) { - Char->BeginTemporaryState(POISONED, GetConfig() == LARGE ? 80 + RAND_N(40) : 400 + RAND_N(200)); - return true; + if(GetConfig() == GIANT_GOLD) + { + bodypart* BodyPart = Victim->GetBodyPart(BodyPartIndex); + + if(BodyPart && BodyPart->IsMaterialChangeable()) + { + festring Desc; + int CurrentHP = BodyPart->GetHP(); + BodyPart->AddName(Desc, UNARTICLED); + + // Instead of a cockatrice turning you to stone, gold spider will turn you to gold! + delete BodyPart->SetMainMaterial(MAKE_MATERIAL(GOLD)); + + // Here changing material would revert all damage done, but we don't want that. + CurrentHP = Min(CurrentHP, BodyPart->GetHP()); + BodyPart->SetHP(CurrentHP); + + if(Victim->IsPlayer()) + { + Desc << " tingles painfully"; + ADD_MESSAGE("Your %s.", Desc.CStr()); + } + else if(Victim->CanBeSeenByPlayer()) + { + Desc << " vibrates and changes into gold"; + ADD_MESSAGE("%s's %s.", Victim->CHAR_DESCRIPTION(DEFINITE), Desc.CStr()); + } + + return true; + } + } + else + { + Victim->BeginTemporaryState(POISONED, GetConfig() == LARGE ? 80 + RAND_N(40) : 400 + RAND_N(200)); + return true; + } } - else - return false; + + return false; } truth vampirebat::SpecialBiteEffect(character* Victim, v2 HitPos, int BodyPartIndex, int Direction, truth BlockedByArmour, truth Critical, int DoneDamage) @@ -959,6 +1042,41 @@ truth vampirebat::SpecialBiteEffect(character* Victim, v2 HitPos, int BodyPartIn return false; } +int nerfbat::TakeHit(character* Enemy, item* Weapon, bodypart* EnemyBodyPart, v2 HitPos, double Damage, + double ToHitValue, int Success, int Type, int Direction, truth Critical, truth ForceHit) +{ + int Return = nonhumanoid::TakeHit(Enemy, Weapon, EnemyBodyPart, HitPos, Damage, ToHitValue, + Success, Type, Direction, Critical, ForceHit); + + if(Return != HAS_DIED) + { + // Compare Mana against enemy Willpower to see if they resist polymorph. + if(RAND_N(GetAttribute(MANA)) > RAND_N(Enemy->GetAttribute(WILL_POWER))) + { + if(IsPlayer()) + ADD_MESSAGE("You are engulfed in a malignant aura!."); + else if(CanBeSeenByPlayer()) + ADD_MESSAGE("%s is engulfed in a malignant aura!", CHAR_DESCRIPTION(DEFINITE)); + + if(Weapon) + Weapon->Polymorph(this, Enemy); + else if(EnemyBodyPart) + Enemy->PolymorphRandomly(1, 999999, (int)(Damage * 300 + RAND() % 500)); + } + else + { + if(IsPlayer()) + ADD_MESSAGE("You are engulfed in a malignant aura, but nothing seems to happen."); + else if(CanBeSeenByPlayer()) + ADD_MESSAGE("%s is engulfed in a malignant aura, but nothing seems to happen.", CHAR_DESCRIPTION(DEFINITE)); + + Enemy->EditExperience(WILL_POWER, 100, 1 << 12); + } + } + + return Return; +} + bool ChameleonPolymorphRandomly(chameleon* c){ character* NewForm = c->PolymorphRandomly(100, 1000, 500 + RAND() % 500); @@ -1131,9 +1249,11 @@ bool CPUwiseAI(nonhumanoid* nh) return bActivated; } + void magicmushroom::GetAICommand() { - if(!CPUwiseAI(this))return; + if(!CPUwiseAI(this)) + return; if(!(RAND() % 750)) { @@ -1387,9 +1507,9 @@ truth nonhumanoid::EditAllAttributes(int Amount) void nonhumanoid::AddAttributeInfo(festring& Entry) const { - Entry.Resize(45); + Entry.Resize(42); Entry << GetAttribute(ARM_STRENGTH); - Entry.Resize(48); + Entry.Resize(45); Entry << "- - " << GetAttribute(AGILITY); character::AddAttributeInfo(Entry); } @@ -1616,7 +1736,8 @@ void hattifattener::GetAICommand() BEAM_LIGHTNING, RAND() & 7, 1 + (RAND() & 7), - 0 + 0, + NULL ); GetLevel()->LightningBeam(Beam); @@ -1885,6 +2006,7 @@ truth bunny::Catches(item* Thingy) { if(CanBeSeenByPlayer()) ADD_MESSAGE("%s catches %s and eats it.", CHAR_NAME(DEFINITE), Thingy->CHAR_NAME(DEFINITE)); + ADD_MESSAGE("%s seems to be much more friendly towards you.", CHAR_NAME(DEFINITE)); ChangeTeam(PLAYER->GetTeam()); } @@ -1926,9 +2048,11 @@ truth mommo::Hit(character* Enemy, v2 Pos, int, int) Hostility(Enemy); if(IsPlayer()) - ADD_MESSAGE("You spill acidous slime at %s.", Enemy->CHAR_DESCRIPTION(DEFINITE)); + ADD_MESSAGE("You spill %s at %s.", GetTorso()->GetMainMaterial()->GetName(false, false).CStr(), + Enemy->CHAR_DESCRIPTION(DEFINITE)); else if(Enemy->IsPlayer() || CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer()) - ADD_MESSAGE("%s spills acidous slime at %s.", CHAR_DESCRIPTION(DEFINITE), Enemy->CHAR_DESCRIPTION(DEFINITE)); + ADD_MESSAGE("%s spills %s at %s.", CHAR_DESCRIPTION(DEFINITE), + GetTorso()->GetMainMaterial()->GetName(false, false).CStr(), Enemy->CHAR_DESCRIPTION(DEFINITE)); Vomit(Pos, 250 + RAND() % 250, false); EditAP(-1000); @@ -2318,7 +2442,7 @@ void spider::GetAICommand() if((ThisDistance < NearestDistance || (ThisDistance == NearestDistance && !(RAND() % 3))) - && p->CanBeSeenBy(this, false, IsGoingSomeWhere()) + && p->CanBeSeenBy(this, false, false /*IsGoingSomeWhere()*/) && (!IsGoingSomeWhere() || HasClearRouteTo(p->GetPos()))) { NearestChar = p; @@ -2326,10 +2450,10 @@ void spider::GetAICommand() } } - if(Hostiles && !RAND_N(Max(80 / Hostiles, 8))) + if(Hostiles && !RAND_N(Max(80 / Hostiles, 8)) && GetLSquareUnder()->IsFlyable()) { web* Web = web::Spawn(); - Web->SetStrength(GetConfig() == LARGE ? 10 : 25); + Web->SetStrength(GetConfig() * 10); if(GetLSquareUnder()->AddTrap(Web)) { @@ -2343,7 +2467,8 @@ void spider::GetAICommand() if(NearestChar) { - if(NearestChar->IsStuck()) + if(NearestChar->IsStuck() || GetConfig() == ARANEA || + (GetConfig() == PHASE && !CanBeSeenBy(NearestChar))) SetGoingTo(NearestChar->GetPos()); else SetGoingTo((Pos << 1) - NearestChar->GetPos()); @@ -2355,6 +2480,10 @@ void spider::GetAICommand() if(MoveRandomly()) return; + // Attack if trapped in a corner. + if(AttackAdjacentEnemyAI()) + return; + EditAP(-1000); } @@ -2421,11 +2550,33 @@ truth lobhse::MustBeRemovedFromBone() const || GetLevel()->GetIndex() != SPIDER_LEVEL; } +void lobhse::FinalProcessForBone() +{ + largecreature::FinalProcessForBone(); + TurnsExisted = 0; +} + +void lobhse::Bite(character* Enemy, v2 HitPos, int Direction, truth ForceHit) +{ + if(!RAND_N(7)) + { + if(IsPlayer()) + ADD_MESSAGE("You vomit at %s.", Enemy->CHAR_DESCRIPTION(DEFINITE)); + else if(Enemy->IsPlayer() || CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer()) + ADD_MESSAGE("%s vomits at %s.", CHAR_DESCRIPTION(DEFINITE), Enemy->CHAR_DESCRIPTION(DEFINITE)); + + Vomit(HitPos, 500 + RAND() % 500, false); + } + else + nonhumanoid::Bite(Enemy, HitPos, Direction, ForceHit); +} + truth lobhse::SpecialBiteEffect(character* Char, v2, int, int, truth BlockedByArmour, truth Critical, int DoneDamage) { - if(!BlockedByArmour) + if(!BlockedByArmour || Critical) { - switch(RAND() % 10) + int Effect = Char->StateIsActivated(DISEASE_IMMUNITY) ? 6 : RAND() % 10; + switch(Effect) { case 0: Char->BeginTemporaryState(LYCANTHROPY, 6000 + RAND_N(2000)); break; case 1: Char->BeginTemporaryState(VAMPIRISM, 5000 + RAND_N(2500)); break; @@ -2440,6 +2591,18 @@ truth lobhse::SpecialBiteEffect(character* Char, v2, int, int, truth BlockedByAr return false; } +void lobhse::Save(outputfile& SaveFile) const +{ + nonhumanoid::Save(SaveFile); + SaveFile << TurnsExisted; +} + +void lobhse::Load(inputfile& SaveFile) +{ + nonhumanoid::Load(SaveFile); + SaveFile >> TurnsExisted; +} + void lobhse::CreateCorpse(lsquare* Square) { largecreature::CreateCorpse(Square); @@ -2449,12 +2612,57 @@ void lobhse::CreateCorpse(lsquare* Square) void lobhse::GetAICommand() { + ++TurnsExisted; + /* Follow the leader, if any. */ SeekLeader(GetLeader()); if(FollowLeader(GetLeader())) return; + /* + Summon spiders + Lobh-se will summon some spiders to harass the player, but only if she's + hostile. As she can be tamed, we don't want to allow the player to amass + a free spidery army. We can explain it away as her summoning being tied + to SPIDER_LEVEL or something, if someone nags. ;) + */ + if(!(RAND() % 60) && GetRelation(PLAYER) == HOSTILE && !GetPos().IsAdjacent(PLAYER->GetPos())) + { + int NumberOfSpiders = RAND() % 3 + RAND() % 3 + RAND() % 3 + RAND() % 3; + + for(int i = 0; i < NumberOfSpiders; i++) + { + lsquare* LSquare = PLAYER->GetNeighbourLSquare(RAND() % GetNeighbourSquares()); + + if(LSquare && (LSquare->GetWalkability() & WALK) && !LSquare->GetCharacter()) + { + character* NewSpider; + long RandomValue = RAND() % TurnsExisted; + + if(RandomValue < 250) + NewSpider = spider::Spawn(!RAND_N(5) ? LARGE : GIANT); + else if(RandomValue < 1500) + NewSpider = spider::Spawn(ARANEA); + else + NewSpider = spider::Spawn(PHASE); + + for(int c = 3; c < TurnsExisted / 500; ++c) + NewSpider->EditAllAttributes(1); + + NewSpider->SetGenerationDanger(GetGenerationDanger()); + NewSpider->SetTeam(GetTeam()); + NewSpider->PutTo(LSquare->GetPos()); + + if(NewSpider->CanBeSeenByPlayer()) + ADD_MESSAGE("%s descends from the darkness above.", NewSpider->CHAR_NAME(INDEFINITE)); + } + } + + EditAP(-2000); + return; + } + if(GetHP() > (GetMaxHP() / 2)) { /* Boss fight, first phase: Spin webs and attack when player is entangled. */ @@ -2510,6 +2718,10 @@ void lobhse::GetAICommand() if(MoveRandomly()) return; + // Attack if trapped in a corner. + if(AttackAdjacentEnemyAI()) + return; + EditAP(-1000); return; } @@ -2583,3 +2795,205 @@ void mindworm::PsiAttack(character* Victim) EditAP(-2000); EditStamina(GetAdjustedStaminaCost(-1000, GetAttribute(INTELLIGENCE)), false); } + +void bat::GetAICommand() +{ + if(!IsRetreating() && PLAYER->WillGetTurnSoon() && + GetPos().IsAdjacent(PLAYER->GetPos())) + { + // Bats sometimes move away from the player. + SetGoingTo((GetPos() << 1) - PLAYER->GetPos()); + + if(MoveTowardsTarget(true)) + return; + } + + nonhumanoid::GetAICommand(); +} + +void invisiblestalker::GetAICommand() +{ + if(GetPos().IsAdjacent(PLAYER->GetPos())) + { + if(CanBeSeenByPlayer() && + (GetHP() < (GetMaxHP() >> 1) || IsRetreating()) + ) + { + ADD_MESSAGE("%s notices you looking and disappears.", CHAR_NAME(DEFINITE)); + + TeleportRandomly(true); + EditAP(-1000); + return; + } + else if(!IsRetreating() && PLAYER->WillGetTurnSoon()) + { + SetGoingTo((GetPos() << 1) - PLAYER->GetPos()); + + if(MoveTowardsTarget(true)) + return; + } + } + + nonhumanoid::GetAICommand(); +} + +truth fruitbat::IsRetreating() const +{ + if(nonhumanoid::IsRetreating()) + return true; + + for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) + if((*i)->IsFood()) + return true; + + return false; +} + +void fruitbat::GetAICommand() +{ + if(!IsRetreating()) + { + character* Char = GetRandomNeighbour(); + + if(Char) + { + itemvector Fruits; + + for(stackiterator i = Char->GetStack()->GetBottom(); i.HasItem(); ++i) + { + if((*i)->IsFood() && !MakesBurdened((*i)->GetWeight())) + Fruits.push_back(*i); + } + + if(!Fruits.empty()) + { + item* ToSteal = Fruits[RAND() % Fruits.size()]; + ToSteal->RemoveFromSlot(); + GetStack()->AddItem(ToSteal); + + if(Char->IsPlayer()) + ADD_MESSAGE("%s steals your %s.", CHAR_NAME(DEFINITE), ToSteal->CHAR_NAME(UNARTICLED)); + + EditAP(-500); + return; + } + } + } + + bat::GetAICommand(); +} + +void fusanga::GetAICommand() +{ + if(AttackAdjacentEnemyAI()) + return; + + /* Chaos magic */ + lsquare* Square = GetLevel()->GetLSquare(GetLevel()->GetRandomSquare(0, HAS_NO_OTERRAIN)); + + if(Square && !RAND_N(20)) + { + if(CanBeSeenByPlayer()) + ADD_MESSAGE("%s radiates pure magic.", CHAR_NAME(DEFINITE)); + + switch (RAND_4) + { + case 0: // Random spell + { + int BeamEffect = RAND_N(17); // Change if more beams are added. + beamdata Beam + ( + this, + CONST_S("killed by the sorcery of ") + GetName(DEFINITE), + GetPos(), + RANDOM_COLOR, + BeamEffect, + YOURSELF, + 1, + 0, + NULL + ); + (Square->*lsquare::GetBeamEffect(BeamEffect))(Beam); + break; + } + case 1: // Create gas + { + // Change if more gases are added. + if(!RAND_2) + GetLevel()->GasExplosion(gas::Spawn(GAS_ID + RAND_N(14) + 3, 100), Square, this); + else + Square->AddSmoke(gas::Spawn(GAS_ID + RAND_N(14) + 3, 100)); + + ADD_MESSAGE("You hear the hiss of gas."); + break; + } + default: // Create rain + { + beamdata Beam + ( + this, + CONST_S("killed by the showers of ") + GetName(DEFINITE), + YOURSELF, + 0 + ); + Square->LiquidRain(Beam, LIQUID_ID + RAND_N(60) + 1); // Change if more liquids are added. + + if(Square->CanBeSeenByPlayer()) + ADD_MESSAGE("A drizzle comes down."); + else + ADD_MESSAGE("You hear the sounds of rainfall."); + + break; + } + } + + EditAP(-4000); + return; + } + + /* Spawn mushrooms */ + if(!RAND_N(40)) + { + int NumberOfMushrooms = RAND() % 3 + RAND() % 3 + RAND() % 3 + RAND() % 3; + + if(CanBeSeenByPlayer()) + ADD_MESSAGE("%s radiates strange magic.", CHAR_NAME(DEFINITE)); + + for(int i = 0; i < NumberOfMushrooms; i++) + { + character* NewShroom; + + switch (RAND_4) + { + case 2: NewShroom = magicmushroom::Spawn(); break; + //case 3: NewShroom = weepmushroom::Spawn(); break; + default: NewShroom = mushroom::Spawn(); break; + } + + NewShroom->SetGenerationDanger(GetGenerationDanger()); + NewShroom->SetTeam(GetTeam()); + NewShroom->PutTo(GetLevel()->GetRandomSquare(NewShroom)); + + if(NewShroom->CanBeSeenByPlayer()) + ADD_MESSAGE("%s sprouts from the ground.", NewShroom->CHAR_NAME(INDEFINITE)); + } + + EditAP(-2000); + return; + } + + /* Just chill there. */ + EditAP(-1000); +} + +void fusanga::CreateCorpse(lsquare* Square) +{ + largecreature::CreateCorpse(Square); +} + +truth fusanga::MustBeRemovedFromBone() const +{ + return !IsEnabled() || GetTeam()->GetID() != MONSTER_TEAM + || GetDungeon()->GetIndex() != FUNGAL_CAVE + || GetLevel()->GetIndex() != FUSANGA_LEVEL; +} diff --git a/Main/Source/rooms.cpp b/Main/Source/rooms.cpp index 7e2343126..fc1f39b95 100644 --- a/Main/Source/rooms.cpp +++ b/Main/Source/rooms.cpp @@ -65,8 +65,7 @@ truth shop::PickupItem(character* Customer, item* ForSale, int Amount) else Price = 0; } - - if(GetMaster()->GetConfig() == BLACK_MARKET) + else if(GetMaster()->GetConfig() == BLACK_MARKET) { Price *= 4; } @@ -165,10 +164,22 @@ truth shop::DropItem(character* Customer, item* ForSale, int Amount) "Decos Bananas Co. if I wish to stay here.\""); return false; } + else if(GetMaster()->GetConfig() == REBEL_CAMP) + { + ADD_MESSAGE("\"I'm a quartermaster, not a merchant. Go sell your stuff somewhere else.\""); + return false; + } long Price = ForSale->GetTruePrice() * Amount * (100 + Customer->GetAttribute(CHARISMA)) / 400; + // Decrease the selling price of very expensive items sold in black market. + if(GetMaster()->GetConfig() == BLACK_MARKET) + { + float NewPrice = (float)Price * (100000 / (1000 + (float)Price)) / 100; + Price = int(NewPrice); + } + if(!Customer->IsPlayer()) { if(Price && Customer->CanBeSeenByPlayer() @@ -298,7 +309,7 @@ void cathedral::Enter(character* Visitor) truth cathedral::PickupItem(character* Visitor, item* Item, int) { - if(game::GetStoryState() == 2 + if(game::GetGloomyCaveStoryState() == 2 || game::GetTeam(ATTNAM_TEAM)->GetRelation(Visitor->GetTeam()) == HOSTILE) return true; @@ -327,7 +338,7 @@ truth cathedral::PickupItem(character* Visitor, item* Item, int) truth cathedral::DropItem(character* Visitor, item* Item, int) { - if(game::GetStoryState() == 2 + if(game::GetGloomyCaveStoryState() == 2 || game::GetTeam(ATTNAM_TEAM)->GetRelation(Visitor->GetTeam()) == HOSTILE) return true; @@ -351,7 +362,7 @@ truth cathedral::DropItem(character* Visitor, item* Item, int) void cathedral::KickSquare(character* Kicker, lsquare* Square) { if(!AllowKick(Kicker, Square) - && Kicker->IsPlayer() && game::GetStoryState() != 2 + && Kicker->IsPlayer() && game::GetGloomyCaveStoryState() != 2 && game::GetTeam(ATTNAM_TEAM)->GetRelation(Kicker->GetTeam()) != HOSTILE) { ADD_MESSAGE("You have harmed the property of the Cathedral!"); @@ -361,7 +372,7 @@ void cathedral::KickSquare(character* Kicker, lsquare* Square) truth cathedral::ConsumeItem(character* HungryMan, item*, int) { - if(game::GetStoryState() == 2 + if(game::GetGloomyCaveStoryState() == 2 || (game::GetTeam(ATTNAM_TEAM)->GetRelation(HungryMan->GetTeam()) == HOSTILE)) return true; @@ -394,7 +405,7 @@ void cathedral::Load(inputfile& SaveFile) truth cathedral::Drink(character* Thirsty) const { - if(game::GetStoryState() == 2 + if(game::GetGloomyCaveStoryState() == 2 || game::GetTeam(ATTNAM_TEAM)->GetRelation(Thirsty->GetTeam()) == HOSTILE) return game::TruthQuestion(CONST_S("Do you want to drink? [y/N]")); @@ -426,7 +437,7 @@ void shop::TeleportSquare(character* Infidel, lsquare* Square) void cathedral::TeleportSquare(character* Teleporter, lsquare* Square) { - if(game::GetStoryState() == 2 || !Teleporter + if(game::GetGloomyCaveStoryState() == 2 || !Teleporter || (game::GetTeam(ATTNAM_TEAM)->GetRelation(Teleporter->GetTeam()) == HOSTILE)) return; @@ -441,7 +452,7 @@ void cathedral::TeleportSquare(character* Teleporter, lsquare* Square) truth cathedral::Dip(character* Thirsty) const { - if(game::GetStoryState() == 2 + if(game::GetGloomyCaveStoryState() == 2 || game::GetTeam(ATTNAM_TEAM)->GetRelation(Thirsty->GetTeam()) == HOSTILE) return true; @@ -861,7 +872,7 @@ void shop::HostileAction(character* Guilty) const void cathedral::HostileAction(character* Guilty) const { - if(game::GetStoryState() != 2 && Guilty) + if(game::GetGloomyCaveStoryState() != 2 && Guilty) Guilty->GetTeam()->Hostility(game::GetTeam(ATTNAM_TEAM)); } @@ -982,3 +993,200 @@ character* cathedral::FindRandomExplosiveReceiver() const else return ListOfDwarfs[RAND_N(ListOfDwarfs.size())]; } + +/* TODO */ + +truth ownedarea::PickupItem(character* Visitor, item* Item, int) +{ + if(!MasterIsActive() || Visitor == GetMaster() || + GetMaster()->GetRelation(Visitor) == HOSTILE) + return true; + + if(Visitor->IsPlayer()) + { + if(Item->IsQuestItem()) + return true; + + ADD_MESSAGE("Picking up private property is prohibited."); + + if(game::TruthQuestion(CONST_S("Do you still want to do this? [y/N]"))) + { + Visitor->Hostility(GetMaster()); + return true; + } + } + + return false; +} + +truth ownedarea::DropItem(character* Visitor, item* Item, int) +{ + if(!MasterIsActive() || Visitor == GetMaster() || + GetMaster()->GetRelation(Visitor) == HOSTILE) + return true; + + if(Visitor->IsPlayer()) + { + if(Item->IsQuestItem()) + { + ADD_MESSAGE("Donating this item wouldn't be wise. You may still need it."); + return false; + } + + if(game::TruthQuestion(CONST_S("Do you wish to donate this item? [y/n]"))) + return true; + } + + return false; +} + +void ownedarea::KickSquare(character* Kicker, lsquare* Square) +{ + if(!AllowKick(Kicker, Square) && Kicker->IsPlayer() && + (GetMaster()->GetRelation(Kicker) != HOSTILE)) + { + ADD_MESSAGE("You have harmed a private property!"); + Kicker->Hostility(GetMaster()); + } +} + +truth ownedarea::ConsumeItem(character* HungryMan, item*, int) +{ + if(!MasterIsActive() || HungryMan == GetMaster() || + GetMaster()->GetRelation(HungryMan) == HOSTILE) + return true; + + if(HungryMan->IsPlayer()) + { + ADD_MESSAGE("Eating private property is forbidden."); + + if(game::TruthQuestion(CONST_S("Do you still want to do this? [y/n]"))) + { + HungryMan->Hostility(GetMaster()); + return true; + } + } + + return false; +} + +truth ownedarea::Drink(character* Thirsty) const +{ + if(!MasterIsActive() || Thirsty == GetMaster() || + GetMaster()->GetRelation(Thirsty) == HOSTILE) + return true; + + if(Thirsty->IsPlayer()) + { + ADD_MESSAGE("Drinking private property is prohibited."); + + if(game::TruthQuestion(CONST_S("Do you still want to do this? [y/n]"))) + { + Thirsty->Hostility(GetMaster()); + return true; + } + } + + return false; +} + +void ownedarea::TeleportSquare(character* Infidel, lsquare* Square) +{ + if(Square->GetStack()->GetItems() && MasterIsActive() + && Infidel && Infidel != GetMaster() + && GetMaster()->GetRelation(Infidel) != HOSTILE) + { + ADD_MESSAGE("\"Thief! Thief!\""); + Infidel->Hostility(GetMaster()); + } +} + +truth ownedarea::Dip(character* Thirsty) const +{ + if(!MasterIsActive() || Thirsty == GetMaster() || + GetMaster()->GetRelation(Thirsty) == HOSTILE) + return true; + + if(Thirsty->IsPlayer()) + { + /* What if it's not water? */ + + ADD_MESSAGE("Stealing water is prohibited."); + + if(game::TruthQuestion(CONST_S("Are you sure you want to dip? [y/n]"))) + { + Thirsty->Hostility(GetMaster()); + return true; + } + } + + return false; +} + +truth ownedarea::AllowKick(ccharacter* Char, const lsquare* LSquare) const +{ + return (!LSquare->GetStack()->GetItems() || !MasterIsActive() + || Char == GetMaster() || GetMaster()->GetRelation(Char) == HOSTILE + || !LSquare->CanBeSeenBy(GetMaster())); +} + +void ownedarea::HostileAction(character* Guilty) const +{ + if(MasterIsActive() && Guilty && Guilty != GetMaster() + && GetMaster()->GetRelation(Guilty) != HOSTILE) + { + ADD_MESSAGE("\"You vandal!\""); + Guilty->Hostility(GetMaster()); + } +} + +void ownedarea::AddItemEffect(item* Dropped) +{ + + truth SeenBeforeTeleport = Dropped->CanBeSeenByPlayer(); + character* KamikazeDwarf = FindRandomExplosiveReceiver(); + + if(!Dropped->IsKamikazeWeapon(KamikazeDwarf)) + return; + + if(KamikazeDwarf) + { + Dropped->MoveTo(KamikazeDwarf->GetStack()); + + if(KamikazeDwarf->CanBeSeenByPlayer()) + { + if(SeenBeforeTeleport) + ADD_MESSAGE("%s disappears and reappears in %s's inventory.", + Dropped->GetName(DEFINITE).CStr(), + KamikazeDwarf->GetName(DEFINITE).CStr()); + else + ADD_MESSAGE("%s appears in %s's inventory.", + Dropped->GetName(DEFINITE).CStr(), + KamikazeDwarf->GetName(DEFINITE).CStr()); + } + else if(SeenBeforeTeleport) + ADD_MESSAGE("%s disappears.", Dropped->GetName(DEFINITE).CStr()); + } + else + { + if(SeenBeforeTeleport) + ADD_MESSAGE("%s flickers for a moment.", Dropped->GetNameSingular().CStr()); + } +} + +character* ownedarea::FindRandomExplosiveReceiver() const +{ + if(!MasterIsActive()) + return 0; + + std::vector ListOfDwarfs; + + for(character* p : GetMaster()->GetTeam()->GetMember()) + if(p->IsEnabled() && p->IsKamikazeDwarf()) + ListOfDwarfs.push_back(p); + + if(ListOfDwarfs.empty()) + return 0; + else + return ListOfDwarfs[RAND_N(ListOfDwarfs.size())]; +} diff --git a/Main/Source/stack.cpp b/Main/Source/stack.cpp index 715cff93c..9e1657fd8 100644 --- a/Main/Source/stack.cpp +++ b/Main/Source/stack.cpp @@ -600,6 +600,8 @@ void stack::AddContentsToList(felist& Contents, ccharacter* Viewer, !(Flags & NO_SPECIAL_INFO)); int ImageKey = game::AddToItemDrawVector(PileVector[p]); Contents.AddEntry(Entry, LIGHT_GRAY, 0, ImageKey); + if(!Item->GetDescriptiveInfo().IsEmpty()) + Contents.SetLastEntryHelp(festring()<GetDescriptiveInfo()); } } diff --git a/Main/Source/team.cpp b/Main/Source/team.cpp index 5fefc23af..b0533f60e 100644 --- a/Main/Source/team.cpp +++ b/Main/Source/team.cpp @@ -63,7 +63,7 @@ void team::Hostility(team* Enemy) if(PLAYER->CanHear()) ADD_MESSAGE("You hear an alarm ringing."); - if(game::GetStoryState() != 2) + if(game::GetGloomyCaveStoryState() != 2) { v2 AngelPos = game::GetPetrus() ? game::GetPetrus()->GetPos() : v2(28, 20); int Seen = 0; diff --git a/Main/Source/wterra.cpp b/Main/Source/wterra.cpp index 08f17f03f..6c018a959 100644 --- a/Main/Source/wterra.cpp +++ b/Main/Source/wterra.cpp @@ -227,3 +227,11 @@ cfestring& owterrain::GetNameStem() const { return DataBase->NameStem; } + +int gwterrain::GetEntryDifficulty() const +{ + if(!(GetWalkability() & WALK)) // ocean + return 25; + + return 10; +} diff --git a/NEWS b/NEWS index f1756e861..484ccd857 100644 --- a/NEWS +++ b/NEWS @@ -11,8 +11,75 @@ Next version ------------ Changes: +* Add Aslona! +* Add new ROOM_OWNED_AREA type for generic owned rooms. Uses code similar to + the Cathedral or Decos' house. +* Change structure of quests. Petrus now takes the encrypted scroll and gives + you some alone time. If you chat with him again, you will receive the GC quest, + or you can find other people to chat for other quests. +* Add some new monsters, items, artifacts, materials and crafting recipes. +* Hotness can now be used to define materials that deal fire damage on contact, + just like Acidicity for acidic materials. +* Use GitHub app for LGTM. +* Cats can be tamed with fishes. +* Orcs have black blood. +* Lobh-se is a bit more fun. +* Make amulets easily recognizable by color. +* Land mines can now be sometimes defused if you levitate over and pick them up. +* Willpower now protects against some hostile magic. +* Add autopick regex. +* Add an option to show info about gods, displaying their last reaction to prayer. +* Better 'F1' command in menus. + * Some items now have descriptions. + * Better help for crafting actions. + * New help for config options. +* Add options to start with no pet and to use health descriptions. +* Add alternate door traps. +* Prevent very dumb creatures from using wands. +* You cannot unequip locked chastity belt. +* Update README and add MANUAL. +* A wise player can look at an enemy for a rough estimation of their health. +* Add day/night cycle to all above-ground locations. +* Enable weather effects for more above-ground locations. +* Let monsters also benefit from Detecting status effect. +* Coffins now generate with grave goods and do something when you steal from them. +* Hammers are good with the undead. +* Some monsters now have different diets. Fixes: +* Decos no longer lets you steal from him. +* You can no longer leave the Black Market through walls. +* Slightly nerf Black Market. +* Display Willpower in wizard mode secrets. +* Running in wilderness now correctly takes stamina. +* Fix some quest messages not being saved as already displayed. +* Reveal gas traps when you step on them. +* Fix several places where panic immunity or disease immunity was not respected. +* Fix several crafting bugs, hopefully preventing crashes. +* Fix Terra not offering priestly services after you killed Lobh-se. +* Spill more water over a burning player when they pray to Silva, so there's + a better chance to be extinguished. +* Prevent long strings in config options from overflowing when displayed. +* Prevent building features in owned rooms. +* Explosive liquids can no longer be used to douse flames. +* Wands of webbing were not causing hostility. +* Fix horns of fear causing panic without checking the panic resistance of the victim. +* Remove redundant messages from auto map notes. +* Fix sitting on fountains and drinking from non-water fountains. +* Fix AI not using equipped zappable items. +* Make taming/possessing more powerful creatures scale correctly. +* Fix a bug where boots were not correctly considered for kicking effects. +* Fix teleport lock from non-equipment sources never timing out. +* Assorted minor fixes and balancing. + +Aslona: +* A new main quest will see you trying to bring peace back to the kingdom of + Aslona, currently in the middle of a civil war. +* Add five new locations. +* Player can now have a ship to sail the oceans of worldmap. This also means + that they can now bring pets that would normally be unable to cross the ocean + with them. +* Moving over the ocean is slightly slower than on dry land. Version 0.57, released 24th September 2019 ------------------------------------------ @@ -305,7 +372,7 @@ Changes: * Lanterns ignite the square they break in. * Explosions split to fire only (fireballs) and fire + physical (land mines). * Female player head sprites added. -* Body parts are warm if they're burning - Infravision +* Body parts are warm if they're burning (Infravision) * Ghosts now resemble the deceased! Fixes: @@ -397,7 +464,7 @@ Fire subsystem: * Made it so that the words "(on fire)" appear in the post-fix of meleeweapons and armors that are on fire, a la fluids (covered in ...). Fixes: -* Found that materials no longer sparkle if dipped in fluids, and no longer resume sparkling even after the fluid has evaporated - which is a BUG! +* Found that materials no longer sparkle if dipped in fluids, and no longer resume sparkling even after the fluid has evaporated, which is a BUG! * Fixed a provisional bug where scrolls and books could be dipped without being on fire. * Ghosts, powder, fluids were all burning. Even the snow in Attnam was on fire! This was fixed for gases, fluids and powders. * Fixed window bug. @@ -523,131 +590,131 @@ Fixes: Version 0.50, released 10th December 2004 ----------------------------------------- -- fluids can now cover items and characters and interact with them -- items made of iron alloys can now rust -- added directional light and day and night which use it -- added some cosmetical weather effects -- New Attnam has now many new NPCs, for instance a sumo wrestler who +* fluids can now cover items and characters and interact with them +* items made of iron alloys can now rust +* added directional light and day and night which use it +* added some cosmetical weather effects +* New Attnam has now many new NPCs, for instance a sumo wrestler who can be challenged -- polymorph control is now more interesting; you need to see a monster +* polymorph control is now more interesting; you need to see a monster once before you can polymorph into it, and more powerful ones require more intelligence -- added wands of acid rain, mirroring and necromancy -- added scrolls of detect material, harden material and golem creation -- added several new monsters, eg. powerful named archangels for each god +* added wands of acid rain, mirroring and necromancy +* added scrolls of detect material, harden material and golem creation +* added several new monsters, eg. powerful named archangels for each god and necromancers who raise skeletons and zombies to do their bidding -- one can now give pets tactical commands, change their equipment and +* one can now give pets tactical commands, change their equipment and use them to carry extra stuff (these are accessed using 'C'hat and 'I'ssue commands keys) -- the player can now panic if he gets hit too much, like the monsters +* the player can now panic if he gets hit too much, like the monsters have done in previous versions -- the player can now become exhausted if he fights for too long and/or +* the player can now become exhausted if he fights for too long and/or uses the new r'u'n command too much -- spiders are now able to make webs -- you can now get stuck to slime -- badly hurt/trapped bodyparts now become unusable until they regain +* spiders are now able to make webs +* you can now get stuck to slime +* badly hurt/trapped bodyparts now become unusable until they regain some HP/become untrapped -- it is now possible to browse detailed death reasons of individual +* it is now possible to browse detailed death reasons of individual monsters in the postgame massacre lists -- added many new informative graphical details, for instance recently +* added many new informative graphical details, for instance recently altered attributes are shown with a different color for some time -- gloomy cave is now longer and has more special levels and rooms -- all the endgame battles are more complex -- added leprosy, a nasty disease which causes your limbs to drop off +* gloomy cave is now longer and has more special levels and rooms +* all the endgame battles are more complex +* added leprosy, a nasty disease which causes your limbs to drop off randomly Version 0.430, released 5th August 2003 --------------------------------------- -- certain monsters and their corpses are now larger than one square -- thirteen new monsters added, including electric hattifatteners and +* certain monsters and their corpses are now larger than one square +* thirteen new monsters added, including electric hattifatteners and reproducing rabbits -- monsters with high enough intelligence now use much more advanced +* monsters with high enough intelligence now use much more advanced pathfinding methods -- a very unique side branch added to the underwater tunnel -- added an alternative keyboard layout to ease playing on some laptops -- score system simplified +* a very unique side branch added to the underwater tunnel +* added an alternative keyboard layout to ease playing on some laptops +* score system simplified Version 0.420, released 11th May 2003 ------------------------------------- -- you can now find a level where you died in a former game (a bonefile) -- added one new monster, a few items and several materials -- the player's and monsters' representations on the game screen now depend +* you can now find a level where you died in a former game (a bonefile) +* added one new monster, a few items and several materials +* the player's and monsters' representations on the game screen now depend on what they wield -- added many new monster animations -- corrected many balancing problems, especially with magical mushrooms -- corrected several fatal bugs -- corrected some memory leaks -- the game's compile speed increased greatly -- lots of monster AI and graphics routine optimizations +* added many new monster animations +* corrected many balancing problems, especially with magical mushrooms +* corrected several fatal bugs +* corrected some memory leaks +* the game's compile speed increased greatly +* lots of monster AI and graphics routine optimizations Version 0.410, released 27th March 2003 --------------------------------------- -- added smoke effects -- added eleven new monsters -- added several new animations -- user interface tweaks -- removed all assembler code -- the player's representation on the game screen now depends on his armor -- added search command and searching state which can detect traps -- a list of killed monsters is now displayed when the game ends -- breaking wands now all have unique effects -- dipping to corpses is again possible +* added smoke effects +* added eleven new monsters +* added several new animations +* user interface tweaks +* removed all assembler code +* the player's representation on the game screen now depends on his armor +* added search command and searching state which can detect traps +* a list of killed monsters is now displayed when the game ends +* breaking wands now all have unique effects +* dipping to corpses is again possible Version 0.401, released 5th February 2003 ----------------------------------------- -- added lightning effects -- added several new artifact and regular weapons -- added floating eye -- it is now much easier to gather nutrition -- many abuses prohibited -- explosions are now stopped by walls -- corrected a fatal bug in the door breaking code -- corrected many non-fatal bugs -- decreased IVAN's RAM usage greatly -- doubled IVAN's compile speed +* added lightning effects +* added several new artifact and regular weapons +* added floating eye +* it is now much easier to gather nutrition +* many abuses prohibited +* explosions are now stopped by walls +* corrected a fatal bug in the door breaking code +* corrected many non-fatal bugs +* decreased IVAN's RAM usage greatly +* doubled IVAN's compile speed Version 0.40, released 10th December 2002 ----------------------------------------- -- bodyparts added -- animations added -- multi-colored lights added -- carnivorous plants, lion, buffalo, snake, orc and many other monsters added -- added unique monsters -- added modified versions of old and new monsters -- enlarged the quest by one new dungeon and one new village -- tripled the amount of items -- added many equipable items (gauntlets, boots, cloaks etc) +* bodyparts added +* animations added +* multi-colored lights added +* carnivorous plants, lion, buffalo, snake, orc and many other monsters added +* added unique monsters +* added modified versions of old and new monsters +* enlarged the quest by one new dungeon and one new village +* tripled the amount of items +* added many equipable items (gauntlets, boots, cloaks etc) Version 0.311, released 25th February 2002 ------------------------------------------ -- a few fatal bugs fixed -- some non-fatal bugs fixed -- lots of optimization +* a few fatal bugs fixed +* some non-fatal bugs fixed +* lots of optimization Version 0.310, released 4th February 2002 ----------------------------------------- -- Linux and DOS ports -- fountain effects -- starting pet -- the first dungeon shop -- mammoth, unicorn, kamikaze dwarf and genie -- magic lamp, jewels, holy book, scroll of charging -- explosions -- doors can be locked and they can be broken by kicking -- graphical effects for wand beams -- dolphins have more living space -- danger levels and alignments added to the panel -- processor loads decreased -- ergonomic tweaks, e.g. with go, rest and look commands +* Linux and DOS ports +* fountain effects +* starting pet +* the first dungeon shop +* mammoth, unicorn, kamikaze dwarf and genie +* magic lamp, jewels, holy book, scroll of charging +* explosions +* doors can be locked and they can be broken by kicking +* graphical effects for wand beams +* dolphins have more living space +* danger levels and alignments added to the panel +* processor loads decreased +* ergonomic tweaks, e.g. with go, rest and look commands Version 0.301, released 10th December 2001 ------------------------------------------ -- first public release +* first public release diff --git a/README b/README deleted file mode 100644 index 266fd9ef9..000000000 --- a/README +++ /dev/null @@ -1,206 +0,0 @@ -Iter Vehemens ad Necem v0.50 ----------------------------- - -For news and updates visit our unofficial homepage at http://attnam.com - ----------------------------- - -1. Description - -2. System requirements - -3. General gameplay - -4. FAQ - ------------------------------ - -1. Description - -Iter Vehemens ad Necem (IVAN) is a graphical roguelike game, which currently -runs in Windows, DOS, Linux, and OS X. It features advanced bodypart and -material handling, multi-colored lighting and, above all, deep gameplay. - ------------------------------ - -2. System requirements - -We recommend that you use at least a: - - Pentium 266 MHz - 48 Megs of RAM - - Windows 9x/ME/XP/2000 (at least) - - or: - - DOS - Vesa 2.0 compatible video card - - or: - - Linux with - SDL library version 1.2.0 or higher - - or: - - OS X with - SDL library version 1.2.0 or higher - ------------------------------ - -3. General gameplay - -IVAN works pretty much in the same way as other roguelikes. The player -controls a character, which moves and attacks from the direction keys. -All other commands can be found by pressing ?-key in the game. - -Additional help can be obtained from the unofficial site at -http://attnam.com - ------------------------------ - -4. FAQ - -Q: What does "Iter Vehemens ad Necem" mean? - -A: It's Latin and means a "Violent Road to Death". For most players, that's - a perfect description of the typical game. - -Q: Why can't I make multiple saves and why is my save deleted when I die? - -A: Like the creators of other roguelikes, we think this makes gaming much - more exciting, since you must take full responsibility of all your - actions. You have only one chance to live or die. Also, we agree that - "normal" saveloading is OK for games which remain the same in all gaming - sessions, since in these you are not meant to really die as replaying - everything in exactly the same way would be annoying. But the same does - not apply to games with a multitude of random areas and events like IVAN; - the whole fun is trying again and again in everchanging environment, - encountering stranger and more complex situations and becoming better - and better in tackling them. - -Q: Can't you reconsider your opinion about saveloading? - -A: No. There are the two things we swore never to do when starting the - project: discarding permadeath and making IVAN a 3D action game. Don't - bother us about this question anymore. - -Q: Not even as an "easy" game option? Pretty please? - -A: Shut up. - -Q: What do these strange markings like Dex, Agi mean in the right sidebar? - -A: - AStr = Arm Strength - Increases damage inflicted on enemies - LStr = Leg Strength - Increases carrying capacity and kicking damage - Dex = Dexterity - Increases accuracy and hit speed - Agi = Agility - Increase movement speed and ability to dodge attacks - End = Endurance - Increases maximum health points and healing rate - Per = Perception - Increases sight range and accuracy - Int = Intelligence - Increases your ability to read and use magic - Wis = Wisdom - Increases your ability to deal with gods - Cha = Charisma - Improves your ability to negotiate and make deals - - If there are two numbers visible after these the first is the attribute - after bonuses and penalties obtained from your equipment or some other - temporary reason. The second number shows the base attribute without the - said modifications. If there is no net-bonus or net-minus, only the base - attribute is shown. - - If the first number is green, then it has increased a short while ago and - if it is red it has decreased. - - Siz = Size or height, in cm - Increases your enemies' chance to hit you - HP = Health Points - The sum of your bodyparts' HPs - Gold = The amount of money you have - Day & Time = The amount of absolute game time you have spent in this game - Turn = The turns (commands which take time) you have used in this game - -Q: I had 20 HPs, but I still died. Is this a bug? - -A: No, this is not a bug. You will die (at least in human-form) if the HPs - of your groin, torso or head reach 0. - -Q: Why do I always miss spiders with my trusty iron mace? - -A: Mace is far too big for hitting small creatures. Do you really kill - spiders with such enormous objects in real life? - -Q: What do marks like L+, C-- mean in the pray menu? - -A: All gods have a property that tells whether they are supporters of Law - or Chaos or if they are Neutral. This is however too general description - so the plusses and minuses tell of slight differences between the gods. - Eg Valpurus has the letters L++ this means that Valpurus is extremely - lawful, when Sophos has the letters L-, which in turn means that Sophos - is lawful, but still leaning a bit towards neutrality and even chaos. - -Q: I just lost a leg. How do I get it back? - -A: Certain gods, potions and special characters may help you. - -Q: How is score calculated? - -A: First the kills of the player and his pets are merged to a single - list. Then for every monster type, the score is calculated with the - following formula - - Score = sqrt(a) * b * b - - sqrt(a) = Square root of the number of killed monsters of this type - b = Sum of the attributes of a typical monster of this type - - The individual scores of the monster types are then added together. - You also get a high bonus for winning, depending on your victory type - (the score is multiplied by 2, 3, 4 or 5). - -Q: Why does time pass so fast in the game? - -A: Who has said the a day is as long in IVAN's world as on earth? - -Q: How can I compile IVAN source code? - -A: See INSTALL. - -Q: I am a Linux / OS X user. Why can't I access the wizard (cheat) mode? - -A: The wizard mode functions aren't compiled by default. Change - the environment variable CXXFLAGS to -DWIZARD and recompile. - -Q: I am a DOS user. When I try to run IVAN, I get the message "Load error: - no DPMI - Get csdpmi*b.zip". - - The DOS binary is compiled using DJGPP, so you need a DPMI server to run - it. One should have been provided along with IVAN, but if that's not the - case, download it from www.delorie.com. - -Q: I've found a bug. What should I do? - -A: Write a small description on how the bug occurred and if possible even - how we could replicate it, and post this information on our forum: - - http://attnam.com - - You should mention your full name so we can credit you at the end of the - AUTHORS file, if you are the first to discover this bug. - -Q: I've got a great idea to make IVAN better! What should I do? - -A: Post the idea on our forum (see above). You will be credited if we - implement the feature, it's non-trivial, and you're first to suggest it. - -Q: I'm a programmer willing to help you. What should I do? - -A: Join us on Github! https://github.com/Attnam/ivan - Contributions are welcome. Fork the repository and then once you have - made and tested your changes, submit a pull request. It will be reviewed - by one of the admins as soon as possible. - - See .customs.emacs file for the official Ivan C++ Programming - Style. - ------------------------------ - -FREE SOFTWARE FOREVER! diff --git a/README.md b/README.md index 52ebeb09b..aeb220d8a 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ Iter Vehemens ad Necem -========================== +====================== [![Build Status](https://travis-ci.org/Attnam/ivan.svg?branch=master)](https://travis-ci.org/Attnam/ivan) [![Build status](https://ci.appveyor.com/api/projects/status/lsx9yspma48ag0fr?svg=true)](https://ci.appveyor.com/project/Attnam/ivan) [![Community](https://img.shields.io/badge/forum-cathedral%20of%20attnam-yellow.svg)](https://attnam.com/) -[![Latest Release](https://img.shields.io/github/release/attnam/ivan/all.svg)](https://github.com/Attnam/ivan/releases/tag/v056) +[![Latest Release](https://img.shields.io/github/release/attnam/ivan/all.svg)](https://github.com/Attnam/ivan/releases/tag/v058) -[![Latest Release Downloads](https://img.shields.io/github/downloads/attnam/ivan/v056/total.svg)](https://attnam.com/projects/official/IVAN-Continuation) -[![Commits Since Latest Release](https://img.shields.io/github/commits-since/attnam/ivan/v056.svg)](https://github.com/Attnam/ivan/compare/v056...master) +[![Latest Release Downloads](https://img.shields.io/github/downloads/attnam/ivan/v058/total.svg)](https://attnam.com/projects/official/IVAN-Continuation) +[![Commits Since Latest Release](https://img.shields.io/github/commits-since/attnam/ivan/v058.svg)](https://github.com/Attnam/ivan/compare/v058...master) [![Total Downloads](https://img.shields.io/github/downloads/attnam/ivan/total.svg)](https://attnam.com/projects/official/IVAN-Continuation) [![Total alerts](https://img.shields.io/lgtm/alerts/g/Attnam/ivan.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/Attnam/ivan/alerts/) @@ -26,157 +26,27 @@ Contributing This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report any unacceptable behavior. -Contributions are welcome! Please fork this repository and then once you have -made and tested your changes, submit a pull request. It will be reviewed by +Contributions are welcome! Please fork this repository and then once you have +made and tested your changes, submit a pull request. It will be reviewed by one of the admins as soon as possible. Forum -------------------------- -Join us on Attnam.com for discussion of planned features, etc. (or to yell at -us if we neglect your pull request for too long). +Join us in [the Cathedral of Attnam](https://attnam.com/ "Official forums") for discussion of gameplay, planned features +and more (or to yell at us if we neglect your pull request for too long). +More -------------------------- -*From the original README...* - -General gameplay ------------------------------ - -IVAN works pretty much in the same way as other roguelikes. The player -controls a character, which moves and attacks from the direction keys. -All other commands can be found by pressing ?-key in the game. - -Additional help can be obtained from the unofficial site at -http://attnam.com - -FAQ ------------------------------ - -**Q:** What does "Iter Vehemens ad Necem" mean? -**A:** It's Latin and means a "Violent Road to Death". For most players, that's - a perfect description of the typical game. - -**Q:** Why can't I make multiple saves and why is my save deleted when I die? -**A:** Like the creators of other roguelikes, we think this makes gaming much - more exciting, since you must take full responsibility of all your - actions. You have only one chance to live or die. Also, we agree that - "normal" saveloading is OK for games which remain the same in all gaming - sessions, since in these you are not meant to really die as replaying - everything in exactly the same way would be annoying. But the same does - not apply to games with a multitude of random areas and events like IVAN; - the whole fun is trying again and again in everchanging environment, - encountering stranger and more complex situations and becoming better - and better in tackling them. - -**Q:** Can't you reconsider your opinion about saveloading? -**A:** No. There are the two things we swore never to do when starting the - project: discarding permadeath and making IVAN a 3D action game. Don't - bother us about this question anymore. - -**Q:** Not even as an "easy" game option? Pretty please? -**A:** Shut up. - -**Q:** What do these strange markings like Dex, Agi mean in the right sidebar? -**A:** - AStr = Arm Strength - Increases damage inflicted on enemies - LStr = Leg Strength - Increases carrying capacity and kicking damage - Dex = Dexterity - Increases accuracy and hit speed - Agi = Agility - Increase movement speed and ability to dodge attacks - End = Endurance - Increases maximum health points and healing rate - Per = Perception - Increases sight range and accuracy - Int = Intelligence - Increases your ability to read and use magic - Wis = Wisdom - Increases your ability to deal with gods - Cha = Charisma - Improves your ability to negotiate and make deals - - If there are two numbers visible after these the first is the attribute - after bonuses and penalties obtained from your equipment or some other - temporary reason. The second number shows the base attribute without the - said modifications. If there is no net-bonus or net-minus, only the base - attribute is shown. - - If the first number is green, then it has increased a short while ago and - if it is red it has decreased. - - Siz = Size or height, in cm - Increases your enemies' chance to hit you - HP = Health Points - The sum of your bodyparts' HPs - Gold = The amount of money you have - Day & Time = The amount of absolute game time you have spent in this game - Turn = The turns (commands which take time) you have used in this game - -**Q:** I had 20 HPs, but I still died. Is this a bug? -**A:** No, this is not a bug. You will die (at least in human-form) if the HPs - of your groin, torso or head reach 0. - -**Q:** Why do I always miss spiders with my trusty iron mace? -**A:** Mace is far too big for hitting small creatures. Do you really kill - spiders with such enormous objects in real life? - -**Q:** What do marks like L+, C-- mean in the pray menu? -**A:** All gods have a property that tells whether they are supporters of Law - or Chaos or if they are Neutral. This is however too general description - so the plusses and minuses tell of slight differences between the gods. - Eg Valpurus has the letters L++ this means that Valpurus is extremely - lawful, when Sophos has the letters L-, which in turn means that Sophos - is lawful, but still leaning a bit towards neutrality and even chaos. - -**Q:** I just lost a leg. How do I get it back? -**A:** Certain gods, potions and special characters may help you. - -**Q:** How is score calculated? -**A:** First the kills of the player and his pets are merged to a single - list. Then for every monster type, the score is calculated with the - following formula - -``` -Score = sqrt(a) * b * b -``` - - sqrt(a) = Square root of the number of killed monsters of this type - b = Sum of the attributes of a typical monster of this type - - The individual scores of the monster types are then added together. - You also get a high bonus for winning, depending on your victory type - (the score is multiplied by 2, 3, 4 or 5). - -**Q:** Why does time pass so fast in the game? -**A:** Who has said the a day is as long in IVAN's world as on earth? - -**Q:** How can I compile IVAN source code? -**A:** See INSTALL. - -**Q:** I am a Linux / OS X user. Why can't I access the wizard (cheat) mode? -**A:** The wizard mode functions aren't compiled by default. Change - the environment variable CXXFLAGS to -DWIZARD and recompile. - -**Q:** I am a DOS user. When I try to run IVAN, I get the message "Load error: - no DPMI - Get csdpmi\*b.zip". -**A:** The DOS binary is compiled using DJGPP, so you need a DPMI server to run - it. One should have been provided along with IVAN, but if that's not the - case, download it from www.delorie.com. - -**Q:** I've found a bug. What should I do? -**A:** Write a small description on how the bug occurred and if possible even - how we could replicate it, and post this information on our forum: - - http://attnam.com - - You should mention your full name so we can credit you at the end of the - AUTHORS file, if you are the first to discover this bug. - -**Q:** I've got a great idea to make IVAN better! What should I do? -**A:** Post the idea on our forum (see above). You will be credited if we - implement the feature, it's non-trivial, and you're first to suggest it. - -**Q:** I'm a programmer willing to help you. What should I do? -**A:** Join us on Github! https://github.com/Attnam/ivan - Contributions are welcome. Fork the repository and then once you have - made and tested your changes, submit a pull request. It will be reviewed - by one of the admins as soon as possible. - - See `.customs.emacs` file for the official Ivan C++ Programming - Style. - ------------------------------ +See MANUAL for FAQ and more information about the game. + +See INSTALL for instructions on compiling the game. + +See LICENSING for copyright notice. + +See Doc/Lore for fiction and short stories from the world of IVAN. + +-------------------------- FREE SOFTWARE FOREVER! diff --git a/Save/delsaves.bat b/Save/delsaves.bat old mode 100644 new mode 100755 diff --git a/Script/char.dat b/Script/char.dat index dfc5324c4..6af1bcd49 100644 --- a/Script/char.dat +++ b/Script/char.dat @@ -32,6 +32,7 @@ character DefaultEndurance = 0; DefaultPerception = 0; DefaultIntelligence = 0; + DefaultWillPower = 10; DefaultWisdom = 0; DefaultCharisma = 0; DefaultMana = 0; @@ -275,7 +276,6 @@ character RunDescriptionLineTwo = ""; VomittingIsUnhealthy = true; AllowPlayerToChangeEquipment = true; - DefaultWillPower = 10; TamingDifficulty = 0; IsSadist = false; IsMasochist = false; @@ -288,6 +288,7 @@ character GhostCopyMaterials = false; CanBeGeneratedOnlyInTheCatacombs = false; IsAlcoholic = false; + IsUndead = false; IsImmuneToWhipOfThievery = false; AllowedDungeons == ALL_DUNGEONS; /* example: AllowedDungeons = { 2, UNDER_WATER_TUNNEL, ELPURI_CAVE; } */ } @@ -337,6 +338,7 @@ playerkind DefaultEndurance = 10; DefaultPerception = 10; DefaultIntelligence = 10; + DefaultWillPower = 10; DefaultWisdom = 10; DefaultCharisma = 10; DefaultMana = 10; @@ -362,6 +364,7 @@ petrus DefaultEndurance = 40; DefaultPerception = 40; DefaultIntelligence = 40; + DefaultWillPower = 40; DefaultWisdom = 40; DefaultCharisma = 40; DefaultMana = 40; @@ -398,6 +401,7 @@ petrus DefaultName = "Petrus"; DeathMessage = "The high priest disappears in a bright light and only his left nut is left behind."; /* Replies overridden */ + /* BloodMaterial specifically *not* BLUE_BLOOD, as Petrus is of humble parentage */ AttachedGod = VALPURUS; BodyPartsDisappearWhenSevered = true; CanBeConfused = false; @@ -419,6 +423,7 @@ farmer DefaultEndurance = 20; DefaultPerception = 18; DefaultIntelligence = 10; + DefaultWillPower = 10; DefaultWisdom = 10; DefaultCharisma = 10; DefaultMana = 5; @@ -487,7 +492,7 @@ farmer Config CULTIST; { - CanBeGenerated = false; + CanBeGenerated = true; AttributeBonus = 10; NameSingular = "cultist"; TorsoBitmapPos = 48, 352; @@ -537,6 +542,7 @@ farmer } ScienceTalkPossibility = 0; Inventory = { 2, holybook(CRUENTUS) { Chance = 10; }, wand(WAND_OF_NECROMANCY) { Chance = 10; } } + AllowedDungeons == XINROCH_TOMB; } Config CRAZED_FARMER; @@ -560,6 +566,7 @@ farmer IsSadist = true; UndeadVersions = false; CanBeGenerated = true; + Frequency = 1000; ClassStates = CONFUSED; DeathMessage = "@Dd laughs and sobs and screams and dies."; HostileReplies = @@ -582,6 +589,26 @@ farmer } Inventory == wand(WAND_OF_RESURRECTION); } + + Config REBEL_CAMP; + { + NameSingular = "rebel"; + FriendlyReplies = + { + 10, + "\"We're all talking about the old king's murder. How could Lord Regent get away with it?\"", + "\"I've heard some people talk about just burning the castle to the ground. Probably just talk though. Everyone's a bit on edge right now.\"", + "\"Is there anywhere in the camp where you can avoid rats? Disease spreading little nuisances.\"", + "\"I don't know what to think about the goblins escaping their owners. Things seem to go from bad to worse, don't they?\"", + "\"Aiding an escaping slave is theft, and a crime. That was always the law.\"", /* What, you thought they were the good guys? :) */ + "\"Lord Regent recently announced that he won't stop until slavery is outlawed. It's our right to have slaves, by the old laws!\"", + "\"How does Lord Regent think we'll work the fields with no slaves? Our economy is already falling and he wants to kill it completely!\"", + "\"What?\"", + "\"These necromancer twins give me the creeps. Why do we even associate with such types?\"", + "@Dd boasts about @sp farmstead back home."; + } + ScienceTalkPossibility = 0; + } } guard @@ -593,6 +620,7 @@ guard DefaultEndurance = 20; DefaultPerception = 25; DefaultIntelligence = 10; + DefaultWillPower = 15; DefaultWisdom = 10; DefaultCharisma = 15; DefaultMana = 10; @@ -720,7 +748,7 @@ guard Config PATROL; { - Helmet = helmet(HELM_OF_PERCEPTION) { Enchantment = 2; } + Helmet = IRON helmet(HELM_OF_PERCEPTION) { Enchantment = 2; } BodyArmor = IRON bodyarmor(PLATE_MAIL); Cloak = BEAR_SKIN cloak; RightWielded = IRON IRON meleeweapon(LONG_SWORD); @@ -823,7 +851,7 @@ guard { AttributeBonus = 60; TotalVolume = 120000; - Helmet = helmet(HELM_OF_PERCEPTION) { Enchantment = 3; } + Helmet = ILLITHIUM helmet(HELM_OF_PERCEPTION) { Enchantment = 3; } Amulet = amulet(AMULET_OF_ESP); BodyArmor = ILLITHIUM bodyarmor(PLATE_MAIL) { Enchantment = 3; } Cloak = PHOENIX_FEATHER cloak(CLOAK_OF_FIRE_RESISTANCE); @@ -840,7 +868,7 @@ guard PanicLevel = 5; TamingDifficulty = NO_TAMING; IsUnique = true; - DefaultName = "Sir Haedlac Galladon VII"; + DefaultName = "Sir Haedlac Galladon VII"; /* Galahad */ IsNameable = false; CanBeCloned = false; IsPolymorphable = false; @@ -994,12 +1022,12 @@ guard ClothColor = rgb16(128, 128, 128); HeadBitmapPos = 96, 368; Helmet = 0; - BodyArmor = BRASS bodyarmor(PLATE_MAIL) { Enchantment = 2; } + BodyArmor = HEPATIZON bodyarmor(PLATE_MAIL) { Enchantment = 2; } RightWielded = OCTIRON EMERALD meleeweapon(BASTARD_SWORD) { Enchantment = 2; } - LeftWielded = EBONY_WOOD shield { Enchantment = 2; } - RightGauntlet = BRASS gauntlet { Enchantment = 2; } - RightBoot = BRASS boot { Enchantment = 2; } - DefaultName = "Sir Lancelyn"; + LeftWielded = OCTIRON shield { Enchantment = 2; } + RightGauntlet = HEPATIZON gauntlet { Enchantment = 2; } + RightBoot = HEPATIZON boot { Enchantment = 2; } + DefaultName = "Sir Lancelyn"; /* Lancelot */ NameSingular = "emissary"; PostFix = "of Aslona"; CWeaponSkillHits = { 2, 500, 500; } @@ -1012,7 +1040,7 @@ guard CanBeCloned = false; IsPolymorphable = false; UndeadVersions = false; - TamingDifficulty = 25; + TamingDifficulty = NO_TAMING; AttachedGod = CRUENTUS; BloodMaterial = BLUE_BLOOD; FriendlyReplies = @@ -1022,7 +1050,7 @@ guard "\"I've heard other emissaries were sent from Aslona even to goblin lands to negotiate assistance.\"", "\"After the old king was assassinated and the Rebellion started, Aslona lost much of its former power.\"", "\"I will see the Rebellion crushed and every last rebel executed for their crimes!\"", - "\"My king is young and inexperienced; his army just barely holds the rebels back. My only hope is I can return to him successful.\"", + "\"Lord Regent is doing his best, but his army just barely holds the rebels back. My only hope is I can return to him successful.\"", "\"Bandits are roving free through the forests of Aslona; you can never be sure which town supports the king and which the Rebellion. What happened to my country?\"", "\"My father was a trusted general of the old king, and the first one to meet with the rebel army on a battlefield after the regicide. Alas, he was betrayed by his slave-soldiers and murdered along with all of his loyal knights.\"", "\"I see a representative of the orcish freeholds arrived. Don't trust him! Those orcish vultures would love to feast on the carcass of Aslona, but I won't let that happen.\"", @@ -1038,6 +1066,117 @@ guard "\"Low-blooded scum! You dare to attack a knight of Aslona?\""; } } + + Config CASTLE; + { + Helmet = DEEP_BRONZE helmet(FULL_HELMET); + BodyArmor = DEEP_BRONZE bodyarmor(PLATE_MAIL); + RightWielded = DEEP_BRONZE MAHOGANY_WOOD meleeweapon(HALBERD); + Adjective = "castle"; + KnownCWeaponSkills = { 2, POLE_ARMS, KICK; } + CWeaponSkillHits = { 2, 100, 100; } + RightSWeaponSkillHits = 10; + LeftSWeaponSkillHits = 10; + PanicLevel = 25; + ClothColor = rgb16(120, 80, 40); + FriendlyReplies = + { + 22, + "@Dd says gravely: \"I think we're all in for some hard times.\"", + "\"...\"", + "\"Hmph.\"", + "\"Move along, stranger.\"", + "\"What do you need?\"", + "\"Trouble? Causing or having?\"", + "\"Things have been bad since old king Othyr died.\"", + "\"Poor prince Artorius. He's too young to inherit a broken country.\"", + "\"Seges bless Lord Regent for standing by the crown prince and this country in their hour of need.\"", + "\"I've heard that Harvan Black-cloak poisoned old king Othyr and tried to dispose of prince Artorius as well.\"", + "\"Lord Regent saved the kingdom, sure enough.\"", + "\"The roads near the Castle are safer now, thanks to Lord Regent. He's an example for us all.\"", + "\"Damn the rebel scum! We should hunt them down and hang them from the castle walls.\"", + "\"I've heard the orc are stirring now, too. We should have burned down the freeholds when we had a chance.\"", + "\"The guard duty here in the castle is usually a dull affair. Near the borders, though, things are a bit more challenging, and one can make a name for oneself.\"", + "\"Kings have been assassinated before, but this time... I don't know. It's different.\"", + "\"The king is dead, his heir too young. We needed a strong leader and Lord Regent answered the call.\"", + "\"Can you believe that the bastard Black-cloak managed to sneak into the very king's rooms?!\"", + "\"If you've got to travel, by Seges, stay on the roads! The wilderness just isn't safe. We've had sightings of bandits, goblins and even undead!\"", + "\"Would you believe it? Harvan Black-cloak murdered king Othyr, and yet people go flocking to the banner of his rebellion!\"", + "\"I understand that the treasury is empty, but begging for help in Attnam seems like a bad idea.\"", + "\"So many bandits have appeared in the backcountry. We just don't have the troops nor the time to comb the wilderness for them.\""; + } + } + + Config ROYAL; + { + AttributeBonus = 40; + Helmet = BRASS helmet(FULL_HELMET) { Enchantment = 2; } + BodyArmor = BRASS bodyarmor(PLATE_MAIL) { Enchantment = 2; } + RightWielded = BRASS OCTIRON meleeweapon(BASTARD_SWORD) { Enchantment = 2; } + LeftWielded = BRASS shield { Enchantment = 2; } + RightGauntlet = BRASS gauntlet { Enchantment = 2; } + RightBoot = BRASS boot { Enchantment = 2; } + RightRing = ring(RING_OF_INFRA_VISION); + LeftRing = ring(RING_OF_POISON_RESISTANCE); + Adjective = "royal"; + CWeaponSkillHits = { 2, 500, 500; } + RightSWeaponSkillHits = 50; + LeftSWeaponSkillHits = 50; + PanicLevel = 10; + TamingDifficulty = 30; + TotalVolume = 80000; + ClothColor = rgb16(181, 166, 66); + FriendlyReplies = + { + 10, + "@Dd says gravely: \"The king's death was a harsh lesson for all of us. We failed in our duty. It must not happen again.\"", + "\"Things have been bad since old king Othyr died.\"", + "\"Poor prince Artorius. He's too young to inherit a broken country.\"", + "\"Seges bless Lord Regent for standing by the crown prince and this country in their hour of need.\"", + "\"Lord Regent saved the kingdom, sure enough.\"", + "\"Kings have been assassinated before, but this time... I don't know. It's different.\"", + "\"The king is dead, his heir too young. We needed a strong leader and Lord Regent answered the call.\"", + "\"Would you believe it? Harvan Black-cloak murdered king Othyr, and yet people go flocking to the banner of his rebellion!\"", + "\"I understand that the treasury is empty, but begging for help in Attnam seems like a bad idea.\"", + "\"So many bandits have appeared in the backcountry. We just don't have the troops nor the time to comb the wilderness for them.\""; + } + } + + Config REBEL; + { + Helmet = DEEP_BRONZE helmet(FULL_HELMET); + BodyArmor = DEEP_BRONZE bodyarmor(PLATE_MAIL); + RightWielded = DEEP_BRONZE CYPRESS_WOOD meleeweapon(BASTARD_SWORD); + LeftWielded = DEEP_BRONZE shield; + Adjective = "rebel"; + CWeaponSkillHits = { 2, 200, 200; } + RightSWeaponSkillHits = 50; + LeftSWeaponSkillHits = 50; + PanicLevel = 5; + ClothColor = rgb16(120, 80, 40); + FriendlyReplies = + { + 18, + "@Dd says gravely: \"I think we're all in for some hard times.\"", + "\"...\"", + "\"Hmph.\"", + "\"Move along, stranger.\"", + "\"What do you need?\"", + "\"Trouble? Causing or having?\"", + "\"Things have been bad since old king Othyr died.\"", + "\"Lord Regent's men think us lawless beasts. How deluded can they be?\"", + "\"We must free crown prince Artorius from the clutches of that wretched Lord Regent.\"", + "\"Lord Regent didn't think anyone would stand up to his treason, but here we are.\"", + "\"We won't give up to Lord Regent without a fight, don't you worry.\"", + "\"We're all talking about the old king's murder. How could Lord Regent get away with it?\"", + "\"Everyone wants to know what really happened to the old king.\"", + "\"I'm sure Harvan will find some way to hold the kingdom together, once the young prince is crowned and we have a king again.\"", + "\"Truth is, we don't know who was behind the old king's murder. But too much power has suddenly fallen into Lord Regent's lap.\"", + "\"Is there anywhere in the camp where you can avoid rats? Disease spreading little nuisances.\"", + "\"I've heard some people talk about just burning the castle to the ground. Probably just talk though. Everyone's a bit on edge right now.\"", + "\"You hear about the scouting squad? They were caught and now they're in jail, waiting for the gallows!\""; + } + } } shopkeeper @@ -1049,6 +1188,7 @@ shopkeeper DefaultEndurance = 20; DefaultPerception = 30; DefaultIntelligence = 25; + DefaultWillPower = 15; DefaultWisdom = 15; DefaultCharisma = 30; DefaultMana = 10; @@ -1141,7 +1281,7 @@ shopkeeper "@Dd sighs: \"I wonder why I have so few customers these days...\"", "\"The topmost reason why I work here is that the monsters devour tax collectors.\"", "\"Elpuri, eh? Folks say he hatched as a light frog in the Cathedral of Attnam. Yet his voracious appetite quickly set him apart. He ate and grew until his swollen bulk dwarfed even the largest of the giant dark frogs.\"", - "\"I've heard that Elpuri once was the favourite pet of Petrus. When a prisoner outlived their usefulness, the high priest threw the poor soul to Elpuri. Yet such morbid meals only darkened Elpuri's blood and did nothing to sate his ever-growing hunger.\"", + "\"I've heard that Elpuri once was the favorite pet of Petrus. When a prisoner outlived their usefulness, the high priest threw the poor soul to Elpuri. Yet such morbid meals only darkened Elpuri's blood and did nothing to sate his ever-growing hunger.\"", "\"After being renounced by Petrus, Elpuri fled the Cathedral and found refuge here in these caves, where he bides his time to build an army of dark frogs and strike back against the high priest.\"", "\"Elpuri is also known by some as the Devourer. His hunger is truly unchecked - he is said to have fathered generations of dark frogs, yet devoured them faster than they could flood and torment the world.\"", "\"The monsters don't really bother me, no. Not even they are stupid enough to want to be on the bad side of the Guild.\"", @@ -1223,7 +1363,7 @@ shopkeeper LeftSWeaponSkillHits = 1000; Sex = FEMALE; FleshMaterial = ORC_FLESH; - /* BloodMaterial = BLACK_BLOOD; */ /* TODO: change once orcish blood is implemented */ + BloodMaterial = BLACK_BLOOD; ClassStates = GAS_IMMUNITY|INFRA_VISION; HeadBitmapPos = 112, 240; TorsoBitmapPos = 32, 16; @@ -1265,6 +1405,39 @@ shopkeeper } Inventory = { 2, holybook(MELLIS), DIAMOND EBONY_WOOD pickaxe { Enchantment = -1; } } } + + Config REBEL_CAMP; + { + AttributeBonus = 50; + HeadBitmapPos = 96, 0; + TorsoBitmapPos = 48, 80; + ClothColor = rgb16(75, 83, 32); + Helmet = HEPATIZON helmet { Enchantment = 3; } + Cloak = NYMPH_HAIR cloak(CLOAK_OF_ELECTRICITY_RESISTANCE) { Enchantment = 3; } + BodyArmor = HEPATIZON bodyarmor(CHAIN_MAIL) { Enchantment = 3; } + Belt = HEPATIZON belt(BELT_OF_REGENERATION) { Enchantment = 3; } + RightWielded = HEPATIZON darkaxe { Enchantment = 3; } + LeftWielded = HEPATIZON shield(SHIELD_OF_FIRE_RESISTANCE) { Enchantment = 3; } + RightRing = ring(RING_OF_INFRA_VISION); + LeftRing = ring(RING_OF_POISON_RESISTANCE); + RightGauntlet = SELKIE_SKIN gauntlet { Enchantment = 3; } + RightBoot = SELKIE_SKIN boot { Enchantment = 3; } + KnownCWeaponSkills = { 2, AXES, SHIELDS; } + CWeaponSkillHits = { 2, 1000, 1000; } + RightSWeaponSkillHits = 500; + LeftSWeaponSkillHits = 500; + DefaultName = "Gustaff"; + NameSingular = "quartermaster"; + FriendlyReplies = + { + 4, + "@Dd sighs: \"Business is slow these days, with the civil war and stuff.\"", + "@Dd smiles: \"If you need money fast, I will buy your stuff. No problem.\"", + "\"Harvan is a sneaky one. Kind of makes you wonder what he's up to.\"", + "\"Lots of rats in this camp. Lots of children, too. Of course, I prefer the taste of rats.\""; + } + Inventory = { 2, holybook(MELLIS), HEPATIZON EBONY_WOOD pickaxe { Enchantment = 3; } } + } } priest @@ -1301,6 +1474,7 @@ priest DefaultEndurance = 15; DefaultPerception = 24; DefaultIntelligence = 15; + DefaultWillPower = 20; DefaultWisdom = 25; DefaultCharisma = 20; DefaultMana = 20; @@ -1363,6 +1537,7 @@ priest DefaultEndurance = 10; DefaultPerception = 24; DefaultIntelligence = 20; + DefaultWillPower = 25; DefaultWisdom = 35; DefaultCharisma = 30; DefaultMana = 25; @@ -1375,6 +1550,7 @@ priest SkinColor = rgb16(160, 100, 64); HairColor = rgb16(80, 48, 32); AttachedGod = SILVA; + BloodMaterial = PLANT_SAP; DefaultName = "Florea"; NameSingular = "priestess"; PostFix = "of Silva"; @@ -1408,6 +1584,7 @@ priest DefaultEndurance = 14; DefaultPerception = 24; DefaultIntelligence = 25; + DefaultWillPower = 25; DefaultWisdom = 25; DefaultCharisma = 20; DefaultMana = 25; @@ -1421,6 +1598,7 @@ priest HairColor = rgb16(64, 0, 64); EyeColor = rgb16(128, 0, 64); AttachedGod = INFUSCOR; + BloodMaterial = MAGIC_LIQUID; DefaultName = "Praecantrix"; NameSingular = "priestess"; PostFix = "of Infuscor"; @@ -1462,6 +1640,7 @@ priest DefaultEndurance = 30; DefaultPerception = 35; DefaultIntelligence = 25; + DefaultWillPower = 35; DefaultWisdom = 37; DefaultCharisma = 55; DefaultMana = 30; @@ -1552,6 +1731,8 @@ priest "tradition", "knightly orders", "heraldry", "quests", "genealogy"; } } + + /* Config SEGES: see aslonapriest */ } oree @@ -1563,6 +1744,7 @@ oree DefaultEndurance = 25; DefaultPerception = 30; DefaultIntelligence = 30; + DefaultWillPower = 40; DefaultWisdom = 20; DefaultCharisma = 3; DefaultMana = 30; @@ -1632,6 +1814,7 @@ darkknight DefaultEndurance = 22; DefaultPerception = 25; DefaultIntelligence = 20; + DefaultWillPower = 20; DefaultWisdom = 10; DefaultCharisma = 10; DefaultMana = 10; @@ -1974,6 +2157,7 @@ ennerbeast DefaultEndurance = 20; DefaultPerception = 12; DefaultIntelligence = 1; + DefaultWillPower = 12; DefaultWisdom = 1; DefaultCharisma = 1; DefaultMana = 0; @@ -2000,7 +2184,7 @@ ennerbeast PanicLevel = 75; FleshMaterial = ENNER_BEAST_FLESH; DeathMessage = "@Dd dies and the world is finally freed from this terrible monster."; - Inventory = { 2, trinket(DEAD_FISH) { Times = 1:10; }, horn; } + Inventory = { 2, fish(DEAD_FISH) { Times = 1:10; }, horn; } IsUnique = true; HostileReplies = { @@ -2040,6 +2224,7 @@ ennerchild DefaultEndurance = 14; DefaultPerception = 14; DefaultIntelligence = 1; + DefaultWillPower = 8; DefaultWisdom = 1; DefaultCharisma = 40; DefaultMana = 0; @@ -2092,6 +2277,7 @@ ennerchild DefaultAgility = 15; DefaultEndurance = 15; DefaultPerception = 16; + DefaultWillPower = 14; Sex = FEMALE; NameSingular = "girl"; FriendlyReplies = @@ -2116,6 +2302,7 @@ frog "@Dd jumps up and down in an enlightened trance.", "@Dd goes \"Ribbit! Ribbit!\" full of transcendent delight."; } + DeathMessage = "@Dd croaks and is no more."; NameSingular = "frog"; IsAbstract = true; AttackStyle = USE_HEAD; @@ -2135,6 +2322,7 @@ frog DefaultEndurance = 10; DefaultPerception = 24; DefaultIntelligence = 15; + DefaultWillPower = 15; DefaultWisdom = 20; DefaultCharisma = 5; DefaultMana = 20; @@ -2153,7 +2341,6 @@ frog ScienceTalkWisdomModifier = 5; ScienceTalkIntelligenceRequirement = 10; ScienceTalkWisdomRequirement = 5; - FriendlyReplies == "@Dd croaks happily."; IsCatacombCreature = true; } @@ -2164,6 +2351,7 @@ frog DefaultEndurance = 15; DefaultPerception = 30; DefaultIntelligence = 20; + DefaultWillPower = 20; DefaultWisdom = 25; DefaultCharisma = 4; DefaultMana = 25; @@ -2183,7 +2371,6 @@ frog ScienceTalkWisdomModifier = 10; ScienceTalkIntelligenceRequirement = 15; ScienceTalkWisdomRequirement = 10; - FriendlyReplies == "@Dd croaks happily."; IsCatacombCreature = true; } @@ -2194,6 +2381,7 @@ frog DefaultEndurance = 20; DefaultPerception = 36; DefaultIntelligence = 25; + DefaultWillPower = 25; DefaultWisdom = 30; DefaultCharisma = 3; DefaultMana = 30; @@ -2215,7 +2403,6 @@ frog ScienceTalkWisdomModifier = 25; ScienceTalkIntelligenceRequirement = 20; ScienceTalkWisdomRequirement = 15; - FriendlyReplies == "@Dd croaks happily."; IsCatacombCreature = true; } @@ -2226,6 +2413,7 @@ frog DefaultEndurance = 10; DefaultPerception = 24; DefaultIntelligence = 5; + DefaultWillPower = 20; DefaultWisdom = 30; DefaultCharisma = 15; DefaultMana = 20; @@ -2251,6 +2439,7 @@ frog DefaultEndurance = 15; DefaultPerception = 36; DefaultIntelligence = 10; + DefaultWillPower = 25; DefaultWisdom = 35; DefaultCharisma = 20; DefaultMana = 25; @@ -2277,6 +2466,7 @@ frog DefaultEndurance = 20; DefaultPerception = 36; DefaultIntelligence = 15; + DefaultWillPower = 30; DefaultWisdom = 40; DefaultCharisma = 25; DefaultMana = 30; @@ -2307,6 +2497,7 @@ billswill DefaultEndurance = 5; DefaultPerception = 27; DefaultIntelligence = 2; + DefaultWillPower = 20; DefaultWisdom = 2; DefaultCharisma = 5; DefaultMana = 10; @@ -2418,6 +2609,7 @@ skeleton IsExtraFragile = true; CanChoke = false; IsCatacombCreature = true; + IsUndead = true; UndeadVersions = false; CreateUndeadConfigurations = true; UndeadAttributeModifier = 75; @@ -2488,7 +2680,7 @@ skeleton } FriendlyReplies == "@Dd laughs: \"Verily, thou art a skilled cheater!\""; /* no taming */ DeathMessage = "As @Dd crumples into a heap of bones, you hear an echoing whisper: \"We shall meet again...\""; - AllowedDungeons = { 3, ATTNAM, UNDER_WATER_TUNNEL, ELPURI_CAVE; } + AllowedDungeons = { 7, ATTNAM, UNDER_WATER_TUNNEL, ELPURI_CAVE, GOBLIN_FORT, FUNGAL_CAVE, PYRAMID, DARK_FOREST; } } } @@ -2501,6 +2693,7 @@ goblin DefaultEndurance = 12; DefaultPerception = 15; DefaultIntelligence = 5; + DefaultWillPower = 5; DefaultWisdom = 5; DefaultCharisma = 5; DefaultMana = 5; @@ -2730,7 +2923,36 @@ goblin EnergyResistance = 30; TamingDifficulty = 20; UndeadVersions = false; - /* TODO: Special dialogue. */ + /* Gus is just a bit odd. */ + HostileReplies = + { + 10, + "@Dd seems to be deep in thoughts.", + "\"Can a paladin kill baby orcs?\"", + "\"Must a paladin never stab a man in the back?\"", + "\"Must laws be upheld with no reason or logic?\"", + "\"Is saying you love someone always good?\"", + "\"Can a king like me be lawful?\"", + "\"Is it chaotic to refuse an order to kill an innocent man?\"", + "\"Can you be born evil? If so, are you compelled to do evil, regardless of its utility?\"", + "\"Is being nailed to things good?\"", + "\"Is everything not forbidden compulsory?\""; + } + FriendlyReplies = + { + 10, + "@Dd seems to be deep in thoughts.", + "\"Can a paladin kill baby orcs?\"", + "\"Must a paladin never stab a man in the back?\"", + "\"Must laws be upheld with no reason or logic?\"", + "\"Is saying you love someone always good?\"", + "\"Can a king like me be lawful?\"", + "\"Is it chaotic to refuse an order to kill an innocent man?\"", + "\"Can you be born evil? If so, are you compelled to do evil, regardless of its utility?\"", + "\"Is being nailed to things good?\"", + "\"Is everything not forbidden compulsory?\""; + } + DeathMessage = "@Dd passed on, @pp ceased to be, @pp expired and went to meet @sp maker. @Pp is an ex-king!"; } } @@ -2797,6 +3019,7 @@ mommo DefaultAgility = 4; DefaultPerception = 9; DefaultIntelligence = 3; + DefaultWillPower = 3; DefaultWisdom = 3; DefaultCharisma = 4; TotalVolume = 150000; @@ -2817,6 +3040,7 @@ mommo DefaultAgility = 2; DefaultPerception = 9; DefaultIntelligence = 2; + DefaultWillPower = 2; DefaultWisdom = 2; DefaultCharisma = 3; TotalVolume = 300000; @@ -2831,6 +3055,48 @@ mommo FleshMaterial = BROWN_SLIME; VomitMaterial = BROWN_SLIME; } + + Config BLOAT; + { + DefaultArmStrength = 2; + DefaultAgility = 2; + DefaultPerception = 9; + DefaultIntelligence = 1; + DefaultWillPower = 1; + DefaultWisdom = 1; + DefaultCharisma = 2; + TotalVolume = 100000; + TorsoBitmapPos = 192, 0; + TotalSize = 50; + Adjective = "bloated"; + Frequency = 1000; + CanHear = false; + BloodMaterial = VOMIT; + FleshMaterial = VOMIT; + VomitMaterial = VOMIT; + } + + Config MAGMA; + { + DefaultArmStrength = 5; + DefaultAgility = 5; + DefaultPerception = 8; + DefaultIntelligence = 3; + DefaultWillPower = 3; + DefaultWisdom = 3; + DefaultCharisma = 5; + TotalVolume = 250000; + TorsoBitmapPos = 176, 0; + TotalSize = 100; + Adjective = "smouldering"; + HPRequirementForGeneration = 150; + DayRequirementForGeneration = 15; + Frequency = 500; + BloodMaterial = LAVA; + FleshMaterial = LAVA; + VomitMaterial = LAVA; + FireResistance = 1000; + } } golem @@ -2842,6 +3108,7 @@ golem DefaultEndurance = 0; /* has no effect */ DefaultPerception = 12; DefaultIntelligence = 4; + DefaultWillPower = 4; DefaultWisdom = 4; DefaultCharisma = 5; DefaultMana = 5; @@ -2899,7 +3166,7 @@ golem IsEnormous = true; CanChoke = false; UndeadVersions = false; - AllowedDungeons = { 2, XINROCH_TOMB, ELPURI_CAVE; } + AllowedDungeons = { 5, XINROCH_TOMB, ELPURI_CAVE, GOBLIN_FORT, PYRAMID, DARK_FOREST; } Config VALPURIUM; { @@ -2945,6 +3212,86 @@ golem Adjective = "acidous blood"; AttachedGod = CRUENTUS; } + + /* Catacomb golems */ + Config HUMAN_NAIL; + { + Adjective = "human nail"; + IsCatacombCreature = true; + AttachedGod = SCABIES; + } + + Config BONE; + { + Adjective = "bone"; + IsCatacombCreature = true; + AttachedGod = INFUSCOR; + } + + Config WRAITH_BONE; + { + Adjective = "wraith bone"; + IsCatacombCreature = true; + AttachedGod = INFUSCOR; + } + + Config HUMAN_SKIN; + { + Adjective = "human skin"; + IsCatacombCreature = true; + AttachedGod = SCABIES; + } + + Config PETRIFIED_DARK; + { + Adjective = "petrified darkness"; + IsCatacombCreature = true; + AttachedGod = MORTIFER; + } + + Config BLOOD; + { + Adjective = "blood"; + IsCatacombCreature = true; + AttachedGod = CRUENTUS; + } + + /* The following golems are only summoned by wizards. */ + + Config EVIL_WONDER_STAFF_VAPOUR; + { + CanBeGenerated = false; + Adjective = "mysterious red gas"; + AttachedGod = INFUSCOR; + } + + Config MAGIC_VAPOUR; + { + CanBeGenerated = false; + Adjective = "raw vapourized magic"; + AttachedGod = INFUSCOR; + } + + Config MUSTARD_GAS; + { + CanBeGenerated = false; + Adjective = "mustard gas"; + AttachedGod = INFUSCOR; + } + + Config SLEEPING_GAS; + { + CanBeGenerated = false; + Adjective = "sleeping gas"; + AttachedGod = INFUSCOR; + } + + Config TELEPORT_GAS; + { + CanBeGenerated = false; + Adjective = "warp gas"; + AttachedGod = INFUSCOR; + } } canine @@ -2953,30 +3300,38 @@ canine ConsumeFlags = CT_FRUIT|CT_MEAT|CT_LIQUID|CT_PROCESSED|CT_BONE; AttackStyle = USE_HEAD; KnownCWeaponSkills == BITE; + HostileReplies = + { + 3, + "@Dd lets out a hostile howl.", + "@Dd barks in fury.", + "@Dd growls madly."; + + } + FriendlyReplies = + { + 5, + "@Dd growls.", + "@Dd howls.", + "@Dd yips.", + "@Dd whines.", + "@Dd barks."; + } } -wolf +wolf /* canine-> */ { DefaultArmStrength = 10; DefaultAgility = 25; DefaultEndurance = 10; DefaultPerception = 24; DefaultIntelligence = 7; + DefaultWillPower = 5; DefaultWisdom = 5; DefaultCharisma = 10; DefaultMana = 0; TotalVolume = 40000; TorsoBitmapPos = 224, 0; - HostileReplies == "@Dd growls madly."; - FriendlyReplies = - { - 5, - "@Dd growls.", - "@Dd howls.", - "@Dd yips.", - "@Dd whines.", - "@Dd barks."; - } TotalSize = 100; /* SkinColor overridden */ NameSingular = "wolf"; @@ -2987,21 +3342,34 @@ wolf ClassStates = INFRA_VISION; FleshMaterial = WOLF_FLESH; AttachedGod = SILVA; + DeathMessage = "@Dd howls one last time and dies."; + + Config DIRE; + { + AttributeBonus = 50; + TotalVolume = 120000; + TotalSize = 150; + Adjective = "dire"; + TorsoSpecialColor = rgb16(220, 0, 0); /* EyeColor */ + BaseBiteStrength = 900; + CWeaponSkillHits == 600; + AllowedDungeons == DARK_FOREST; + } } -dog +dog /* canine-> */ { DefaultArmStrength = 5; DefaultAgility = 15; DefaultEndurance = 8; DefaultPerception = 18; DefaultIntelligence = 5; + DefaultWillPower = 5; DefaultWisdom = 5; DefaultCharisma = 15; DefaultMana = 0; TotalVolume = 20000; TorsoBitmapPos = 240, 16; - HostileReplies == "@Dd barks in fury."; /* FriendlyReplies overridden */ TotalSize = 70; SkinColor = rgb16(111, 74, 37); @@ -3013,7 +3381,7 @@ dog FleshMaterial = DOG_FLESH; AttachedGod = SILVA; Alias == "dog"; - DeathMessage = "@Dd is killed."; + DeathMessage = "@Dd is killed. You feel sad for a moment, but then it passes."; ScienceTalkAdjectiveAttribute = { 16, @@ -3063,6 +3431,7 @@ dog IgnoreDanger = true; CanChoke = false; IsCatacombCreature = true; + IsUndead = true; UndeadVersions = false; CreateUndeadConfigurations = true; UndeadAttributeModifier = 75; @@ -3071,7 +3440,7 @@ dog Adjective = "skeleton"; BaseBiteStrength = 800; CWeaponSkillHits == 80; - AllowedDungeons = { 2, XINROCH_TOMB, ATTNAM; } + AllowedDungeons = { 3, XINROCH_TOMB, ATTNAM, PYRAMID; } Frequency = 2000; } } @@ -3082,6 +3451,8 @@ spider BloodMaterial = SPIDER_BLOOD; CanOpen = false; SkinColor = rgb16(64, 64, 100); + TorsoMainColor = rgb16(30, 30, 30); + TorsoBitmapPos = 256, 0; NameSingular = "spider"; AttackStyle = USE_HEAD; CanBeGenerated = true; @@ -3094,8 +3465,9 @@ spider IsImmuneToStickiness = true; DangerModifier = 25; IsAbstract = true; - TorsoMainColor = rgb16(30, 30, 30); IsCatacombCreature = true; + HostileReplies == "@Dd stridulates wildly, producing a small creaking noise."; + FriendlyReplies == "@Dd stridulates softly, producing a barely audible but quite comfortable noise."; Config LARGE; { @@ -3104,6 +3476,7 @@ spider DefaultEndurance = 4; DefaultPerception = 9; DefaultIntelligence = 3; + DefaultWillPower = 3; DefaultWisdom = 3; DefaultCharisma = 4; Adjective = "large"; @@ -3123,53 +3496,93 @@ spider DefaultEndurance = 12; DefaultPerception = 15; DefaultIntelligence = 4; + DefaultWillPower = 4; DefaultWisdom = 4; DefaultCharisma = 5; Adjective = "giant"; - TorsoBitmapPos = 256, 0; BaseBiteStrength = 400; CWeaponSkillHits == 50; TotalVolume = 5000; TotalSize = 50; - HostileReplies == "@Dd stridulates wildly, producing a small creaking noise."; - FriendlyReplies == "@Dd stridulates softly, producing a barely audible but quite comfortable noise."; } Config ARANEA; { - DefaultArmStrength = 20; + DefaultArmStrength = 18; DefaultAgility = 26; DefaultEndurance = 18; DefaultPerception = 26; - DefaultIntelligence = 4; - DefaultWisdom = 4; - DefaultCharisma = 5; + DefaultIntelligence = 8; + DefaultWillPower = 14; + DefaultWisdom = 14; + DefaultCharisma = 8; Adjective = "aranea"; - TorsoBitmapPos = 256, 0; + SkinColor = rgb16(100, 64, 64); BaseBiteStrength = 400; - CWeaponSkillHits == 50; + CWeaponSkillHits == 150; TotalVolume = 5000; TotalSize = 50; Frequency = 0; - HostileReplies == "@Dd stridulates wildly, producing a small creaking noise."; - FriendlyReplies == "@Dd stridulates softly, producing a barely audible but quite confortable noise."; + } + + Config PHASE; + { + DefaultArmStrength = 20; + DefaultAgility = 24; + DefaultEndurance = 20; + DefaultPerception = 20; + DefaultIntelligence = 24; + DefaultWillPower = 8; + DefaultWisdom = 8; + DefaultCharisma = 14; + DefaultMana = 14; + Adjective = "phase"; + SkinColor = rgb16(64, 100, 64); + BaseBiteStrength = 400; + CWeaponSkillHits == 150; + TotalVolume = 5000; + TotalSize = 50; + Frequency = 10; + PanicLevel = 50; + ClassStates = ETHEREAL_MOVING|TELEPORT|INVISIBLE|ESP; + } + + Config GIANT_GOLD; + { + DefaultArmStrength = 18; + DefaultAgility = 26; + DefaultEndurance = 18; + DefaultPerception = 26; + DefaultIntelligence = 8; + DefaultWillPower = 14; + DefaultWisdom = 14; + DefaultCharisma = 8; + DefaultMana = 14; + Adjective = "giant gold"; + SkinColor = rgb16(224, 224, 0); + BaseBiteStrength = 400; + CWeaponSkillHits == 150; + AttachedGod = INFUSCOR; + TotalVolume = 5000; + TotalSize = 50; + Frequency = 1; + IsCatacombCreature = false; } } -jackal +jackal /* canine-> */ { DefaultArmStrength = 3; DefaultAgility = 12; DefaultEndurance = 6; DefaultPerception = 18; DefaultIntelligence = 5; + DefaultWillPower = 5; DefaultWisdom = 5; DefaultCharisma = 7; DefaultMana = 0; TotalVolume = 15000; TorsoBitmapPos = 304, 0; - HostileReplies == "@Dd howls in fury."; - FriendlyReplies == "@Dd howls happily."; TotalSize = 80; SkinColor = rgb16(255, 255, 255); NameSingular = "jackal"; @@ -3189,6 +3602,7 @@ ass DefaultEndurance = 15; DefaultPerception = 15; DefaultIntelligence = 5; + DefaultWillPower = 5; DefaultWisdom = 5; DefaultCharisma = 5; DefaultMana = 0; @@ -3214,6 +3628,7 @@ ass AutomaticallySeen = true; WillCarryItems = true; BloodMaterial = GLOWING_BLOOD; + ConsumeFlags = CT_FRUIT|CT_LIQUID|CT_MISC_PLANT|CT_PROCESSED; } communist /* Ivan */ @@ -3225,6 +3640,7 @@ communist /* Ivan */ DefaultEndurance = 30; DefaultPerception = 18; DefaultIntelligence = 7; + DefaultWillPower = 18; DefaultWisdom = 6; DefaultCharisma = 10; DefaultMana = 5; @@ -3285,7 +3701,7 @@ communist /* Ivan */ "\"@Nu buy kyber eyes. @Nu see in dark.\"", "\"Uncle Lenin live in Russia. Lenin strong guy. @Nu like.\"", "\"Vodka strong, meat rotten.\"", - "\"@Nu gone through spacetime portal. @Nu searches for stolen family treasure - exact replica of Lenin's mummy!\"", + "\"@Nu gone through space-time portal. @Nu searches for stolen family treasure - exact replica of Lenin's mummy!\"", "\"You can always find party in Lunethia. But in Soviet Russia, Party can always find you!\"", "\"Vladimir @nu best buddy. @Nu meet Vladimir first in magic test area near Voktsovadil.\"", "\"Lenin want @nu kill capitalists!\""; @@ -3319,6 +3735,7 @@ hunter DefaultEndurance = 15; DefaultPerception = 24; DefaultIntelligence = 10; + DefaultWillPower = 10; DefaultWisdom = 10; DefaultCharisma = 15; DefaultMana = 5; @@ -3436,6 +3853,7 @@ bear DefaultEndurance = 20; DefaultPerception = 15; DefaultIntelligence = 8; + DefaultWillPower = 8; DefaultWisdom = 10; DefaultCharisma = 15; DefaultMana = 0; @@ -3451,6 +3869,7 @@ bear DeathMessage = "@Dd groans terribly and falls dead to the ground."; AttachedGod = SILVA; IsAbstract = true; + ConsumeFlags = CT_FRUIT|CT_MEAT|CT_LIQUID|CT_PROCESSED|CT_BONE|CT_MISC_PLANT|CT_MISC_ANIMAL; Config PANDA_BEAR; { @@ -3463,6 +3882,7 @@ bear TorsoBitmapPos = 192, 16; DeathMessage = "@Dd groans terribly and falls dead to the ground. You feel really bad for endangering this species."; Frequency = 100; + ConsumeFlags = CT_FRUIT|CT_LIQUID|CT_MISC_PLANT; } Config BLACK_BEAR; @@ -3533,6 +3953,7 @@ dolphin DefaultEndurance = 10; DefaultPerception = 30; DefaultIntelligence = 100; + DefaultWillPower = 30; DefaultWisdom = 100; DefaultCharisma = 30; DefaultMana = 0; @@ -3551,6 +3972,7 @@ dolphin AttackStyle = USE_HEAD; BaseBiteStrength = 200; HasALeg = false; + IsAbstract = true; FleshMaterial = DOLPHIN_FLESH; AttachedGod = SOPHOS; ForceVomitMessage = "You push your fin down to your throat and vomit."; @@ -3605,6 +4027,7 @@ slave DefaultEndurance = 20; DefaultPerception = 15; DefaultIntelligence = 15; + DefaultWillPower = 15; DefaultWisdom = 20; DefaultCharisma = 10; DefaultMana = 5; @@ -3642,6 +4065,7 @@ petrusswife DefaultEndurance = 7; DefaultPerception = 21; DefaultIntelligence = 8; + DefaultWillPower = 10; DefaultWisdom = 10; DefaultCharisma = 80; DefaultMana = 20; @@ -3700,7 +4124,7 @@ petrusswife "\"You met professor Kaethos?! I have read everything he ever wrote! I would love to meet him some day and discuss his theory of thaumic superunification.\"", "\"I wed Petrus because it made access to education much easier for me. And you know what? I eventually fell in love with him.\""; } - DeathMessage = "With calm face, @Dd whispers, dying: \"Dulce et decorum est pro patria mori.\""; + DeathMessage = "With a calm face, @Dd whispers, dying: \"Dulce et decorum est pro patria mori.\""; ScienceTalkIntelligenceModifier = 25; ScienceTalkWisdomModifier = 25; ScienceTalkIntelligenceRequirement = 20; @@ -3794,6 +4218,7 @@ petrusswife HairColor = rgb16(48, 40, 8); ClothColor = rgb16(119,136,153); HeadBitmapPos = 112, 0; + DefaultWillPower = 25; PostFix = "number 3"; FriendlyReplies = { @@ -3817,6 +4242,7 @@ petrusswife "hunting", "horses", "steppe", "trapping", "sharpshooting", "combat", "traditions", "storytelling", "freedom", "boredom"; } + BloodMaterial = BLUE_BLOOD; } Config 4; @@ -3886,6 +4312,7 @@ petrusswife ClothColor = rgb16(119,136,153); BeltColor = rgb16(105,105,105); HeadBitmapPos = 112, 0; + DefaultWillPower = 5; Adjective = "re-educated"; ArticleMode = 0; FriendlyReplies = @@ -3915,6 +4342,7 @@ petrusswife "hunting", "horses", "steppe", "trapping", "sharpshooting", "combat", "traditions", "storytelling", "freedom", "boredom"; } + BloodMaterial = BLUE_BLOOD; } } @@ -3927,6 +4355,7 @@ housewife DefaultEndurance = 15; DefaultPerception = 24; DefaultIntelligence = 10; + DefaultWillPower = 15; DefaultWisdom = 20; DefaultCharisma = 20; DefaultMana = 10; @@ -4031,18 +4460,77 @@ housewife } ScienceTalkPossibility = 0; Inventory = { 2, holybook(INFUSCOR) { Chance = 10; }, HUMAN_FLESH lump; } + CanBeGenerated = true; + AllowedDungeons == XINROCH_TOMB; } -} -femaleslave -{ - DefaultArmStrength = 10; - DefaultLegStrength = 10; - DefaultDexterity = 10; - DefaultAgility = 10; - DefaultEndurance = 10; + Config ASLONA_CASTLE; + { + NameSingular = "maid"; + NamePlural = "maids"; + LegMainColor = rgb16(0, 191, 255); + FriendlyReplies = + { + 14, + "\"A master sorcerer like Myrddin inevitably outlives all his peers. It must be a lonely existence.\"", + "\"I hear Myrddin is quite a mage. Of course, it's not like he'd bother to stop to talk to you when he ventures into the castle.\"", + "\"I went to the chapel yesterday, but that kamikaze dwarf was sitting there. He makes me nervous, so I figured I'd come back another time.\"", + "\"I don't know if Senex ever leaves the chapel. That man works day and night to take care of all of us.\"", + "\"Things have been bad since old king Othyr died.\"", + "\"Poor prince Artorius. He's too young to inherit a broken country.\"", + "\"Seges bless Lord Regent for standing by the crown prince and this country in their hour of need.\"", + "\"I've heard that Harvan Black-cloak poisoned old king Othyr and tried to dispose of prince Artorius as well.\"", + "\"Lord Regent saved the kingdom, sure enough.\"", + "\"The roads near the Castle are safer now, thanks to Lord Regent. He's an example for us all.\"", + "\"The king is dead, his heir too young. We needed a strong leader and Lord Regent answered the call.\"", + "\"Can you believe that the bastard Black-cloak managed to sneak into the very king's rooms?!\"", + "\"Would you believe it? Harvan Black-cloak murdered king Othyr, and yet people go flocking to the banner of his rebellion!\"", + "@Dd chatters."; + } + HostileReplies = + { + 3, + "\"You will hang for this!\"", + "\"Guards! Guards!\"", + "@Dd screams for help."; + } + ScienceTalkPossibility = 0; + } + + Config REBEL_CAMP; + { + NameSingular = "rebel"; + NamePlural = "rebels"; + FriendlyReplies = + { + 10, + "\"We're all talking about the old king's murder. How could Lord Regent get away with it?\"", + "\"I've heard some people talk about just burning the castle to the ground. Probably just talk though. Everyone's a bit on edge right now.\"", + "\"Is there anywhere in the camp where you can avoid rats? Disease spreading little nuisances.\"", + "\"I don't know what to think about the goblins escaping their owners. Things seem to go from bad to worse, don't they?\"", + "\"Aiding an escaping slave is theft, and a crime. That was always the law.\"", /* What, you thought they were the good guys? :) */ + "\"Lord Regent recently announced that he won't stop until slavery is outlawed. It's our right to have slaves, by the old laws!\"", + "\"How does Lord Regent think we'll work the fields with no slaves? Our economy is already falling and he wants to kill it completely!\"", + "\"What?\"", + "\"These necromancer twins give me the creeps. Why do we even associate with such types?\"", + "@Dd boasts about @sp farmstead back home."; + } + HostileReplies == "\"I will smack you down, young man!\""; + ScienceTalkPossibility = 0; + IsExtraCoward = false; + } +} + +femaleslave +{ + DefaultArmStrength = 10; + DefaultLegStrength = 10; + DefaultDexterity = 10; + DefaultAgility = 10; + DefaultEndurance = 10; DefaultPerception = 18; DefaultIntelligence = 15; + DefaultWillPower = 15; DefaultWisdom = 25; DefaultCharisma = 25; DefaultMana = 5; @@ -4083,6 +4571,7 @@ femaleslave DefaultAgility = 15; DefaultEndurance = 15; DefaultIntelligence = 20; + DefaultWillPower = 20; DefaultWisdom = 30; DefaultCharisma = 20; NameSingular = "servant"; @@ -4128,6 +4617,7 @@ femaleslave DefaultDexterity = 15; DefaultAgility = 20; DefaultIntelligence = 25; + DefaultWillPower = 15; DefaultWisdom = 30; HairColor = rgb16(51, 51, 255); TorsoMainColor = rgb16(255, 0, 0); @@ -4194,6 +4684,7 @@ librarian DefaultEndurance = 10; DefaultPerception = 12; DefaultIntelligence = 30; + DefaultWillPower = 15; DefaultWisdom = 15; DefaultCharisma = 10; DefaultMana = 20; @@ -4260,6 +4751,7 @@ zombie DefaultEndurance = 10; DefaultPerception = 12; DefaultIntelligence = 3; + DefaultWillPower = 3; DefaultWisdom = 3; DefaultCharisma = 3; DefaultMana = 0; @@ -4275,6 +4767,7 @@ zombie NameSingular = "zombie"; CanBeGenerated = true; Sex = UNDEFINED; + ConsumeFlags = CT_MEAT|CT_LIQUID|CT_BONE; PanicLevel = 0; BaseUnarmedStrength = 200; /* HostileReplies overridden */ @@ -4291,6 +4784,7 @@ zombie WieldedPosition = -1, -2; IsExtraFragile = true; IsCatacombCreature = true; + IsUndead = true; CreateUndeadConfigurations = true; UndeadVersions = false; UndeadAttributeModifier = 75; @@ -4310,6 +4804,7 @@ zombie DefaultEndurance = 15; DefaultPerception = 18; DefaultIntelligence = 7; + DefaultWillPower = 20; DefaultWisdom = 15; DefaultCharisma = 5; HeadBitmapPos = 112, 160; /* all dwarves have beards, no exceptions */ @@ -4406,6 +4901,34 @@ zombie } } +gasghoul /* zombie-> */ +{ + DefaultDexterity = 10; + DefaultAgility = 12; + CapColor = rgb16(27, 226, 21); /* eyes */ + SkinColor = rgb16(120, 70, 40); + ClothColor = rgb16(125, 125, 125); + HeadBitmapPos = 112, 512; /* broken gas mask */ + NameSingular = "gas-ghoul"; + DangerModifier = 150; + PanicLevel = 20; + HPRequirementForGeneration = 60; + DayRequirementForGeneration = 4; + IsExtraFragile = false; + CreateUndeadConfigurations = false; + UndeadVersions = false; + FriendlyReplies = + { + 3, + "\"War... never... changes...\"", + "\"How sweet and honourable it is to die for one's country...\"", + "\"No more... Never more...\""; + } + DeathMessage = "@Dd is put to rest."; + Inventory == gasgrenade { Chance = 10; } + AllowedDungeons == PYRAMID; +} + ghost { DefaultArmStrength = 8; @@ -4415,6 +4938,7 @@ ghost DefaultEndurance = 8; DefaultPerception = 24; DefaultIntelligence = 15; + DefaultWillPower = 10; DefaultWisdom = 10; DefaultCharisma = 10; DefaultMana = 15; @@ -4465,6 +4989,7 @@ ghost CanBeGenerated = true; CanBeGeneratedOnlyInTheCatacombs = true; IsCatacombCreature = true; + IsUndead = true; UndeadVersions = false; IsAbstract = true; CanUseEquipment = false; @@ -4490,6 +5015,7 @@ xinrochghost DefaultEndurance = 30; DefaultPerception = 36; DefaultIntelligence = 25; + DefaultWillPower = 25; DefaultWisdom = 20; DefaultCharisma = 20; DefaultMana = 30; @@ -4563,12 +5089,10 @@ imp { DefaultPerception = 15; DefaultIntelligence = 10; + DefaultWillPower = 10; DefaultWisdom = 10; DefaultCharisma = 5; - DefaultMana = 10; - EyeColor = rgb16(200, 200, 0); - ClothColor = rgb16(111, 74, 37); - HairColor = rgb16(100, 0, 0); + DefaultMana = 15; HeadBitmapPos = 96, 16; TorsoBitmapPos = 48, 112; ArmBitmapPos = 64, 32; @@ -4576,22 +5100,21 @@ imp TotalVolume = 40000; TotalSize = 100; NameSingular = "imp"; - UsesLongArticle = true; + IsAbstract = true; CanBeGenerated = true; Sex = UNDEFINED; AttackStyle = USE_ARMS|USE_HEAD; + ConsumeFlags = CT_METAL|CT_MINERAL|CT_LIQUID|CT_PLASTIC; KnownCWeaponSkills = { 2, UNARMED, BITE; } CWeaponSkillHits = { 2, 50, 50; } - PanicLevel = 75; + PanicLevel = 0; BaseUnarmedStrength = 300; BaseBiteStrength = 500; - FleshMaterial = SULFUR; - UsesNutrition = false; - AttachedGod = MORTIFER; - ClassStates = GAS_IMMUNITY; - FireResistance = 1000; + VomittingIsUnhealthy = false; + ClassStates = GAS_IMMUNITY|FEARLESS; CanChoke = false; UndeadVersions = false; + IsSadist = true; FriendlyReplies = { 6, @@ -4616,6 +5139,39 @@ imp "@Dd grins maniacally.", "@Dd grins sadistically."; } + Inventory = { 2, potion { SecondaryMaterial = ASPHALT; Chance = 50; }, + potion { SecondaryMaterial = QUICK_SILVER; Chance = 50; } } +} + +crimsonimp /* imp-> */ +{ + EyeColor = rgb16(200, 200, 0); + ClothColor = rgb16(111, 74, 37); + HairColor = rgb16(100, 0, 0); + Adjective = "crimson"; + FleshMaterial = SULFUR; + BloodMaterial = NAPALM; + VomitMaterial = NAPALM; + AttachedGod = CRUENTUS; + FireResistance = 1000; + DeathMessage = "@Dd dies in a gout of hellfire."; +} + +mirrorimp /* imp-> */ +{ + EyeColor = rgb16(160, 35, 160); + ClothColor = rgb16(0, 71, 171); + HairColor = rgb16(180, 180, 255); + Adjective = "mirror"; + FleshMaterial = GLASS; + BloodMaterial = LIQUID_FEAR; + VomitMaterial = LIQUID_FEAR; + AttachedGod = INFUSCOR; + EnergyResistance = 100; + PoisonResistance = 100; + ElectricityResistance = 100; + AcidResistance = 100; + DeathMessage = "@Dd dies in a frozen explosion of glass."; } bat @@ -4625,6 +5181,7 @@ bat DefaultEndurance = 8; DefaultPerception = 24; DefaultIntelligence = 7; + DefaultWillPower = 5; DefaultWisdom = 5; DefaultCharisma = 5; DefaultMana = 0; @@ -4645,6 +5202,7 @@ bat "@Dd chirps.", "@Dd squeaks."; } + DeathMessage = "@Dd chirps one last time and dies."; TotalSize = 20; SkinColor = rgb16(96, 64, 48); NameSingular = "bat"; @@ -4662,7 +5220,7 @@ bat IsCatacombCreature = true; } -vampirebat +vampirebat /* bat-> */ { DefaultArmStrength = 4; TorsoSpecialColor = rgb16(220, 0, 0); /* EyeColor */ @@ -4672,8 +5230,34 @@ vampirebat TotalVolume = 1200; TotalSize = 30; SkinColor = rgb16(64, 64, 96); - NameSingular = "vampire bat"; - AllowedDungeons = { 2, XINROCH_TOMB, ATTNAM; } + Adjective = "vampire"; + AllowedDungeons = { 5, XINROCH_TOMB, ATTNAM, ASLONA_CASTLE, PYRAMID, DARK_FOREST; } +} + +nerfbat /* bat-> */ +{ + DefaultArmStrength = 3; + DefaultIntelligence = 12; + DefaultMana = 20; + TorsoSpecialColor = rgb16(128, 0, 128); /* EyeColor */ + SkinColor = rgb16(175, 150, 175); + Frequency = 500; + Adjective = "nurphe"; + IgnoreDanger = true; + AllowedDungeons = { 4, GOBLIN_FORT, FUNGAL_CAVE, PYRAMID, DARK_FOREST; } + AttackWisdomLimit = 12; +} + +fruitbat /* bat-> */ +{ + DefaultEndurance = 10; + TorsoSpecialColor = rgb16(180, 180, 180); /* EyeColor */ + SkinColor = rgb16(190, 130, 64); + Frequency = 500; + Adjective = "fruit"; + IgnoreDanger = true; + IsCatacombCreature = false; + AllowedDungeons = { 3, GOBLIN_FORT, FUNGAL_CAVE, DARK_FOREST; } } mistress @@ -4685,6 +5269,7 @@ mistress DefaultEndurance = 20; DefaultPerception = 30; DefaultIntelligence = 20; + DefaultWillPower = 20; DefaultWisdom = 20; DefaultCharisma = 40; DefaultMana = 10; @@ -4900,6 +5485,7 @@ werewolfhuman DefaultEndurance = 15; DefaultPerception = 18; DefaultIntelligence = 20; + DefaultWillPower = 20; DefaultWisdom = 20; DefaultCharisma = 20; DefaultMana = 20; @@ -4950,6 +5536,7 @@ werewolfwolf DefaultEndurance = 25; DefaultPerception = 24; DefaultIntelligence = 10; + DefaultWillPower = 10; DefaultWisdom = 10; DefaultCharisma = 10; DefaultMana = 0; @@ -5040,6 +5627,7 @@ vampire DefaultEndurance = 15; DefaultPerception = 24; DefaultIntelligence = 20; + DefaultWillPower = 20; DefaultWisdom = 20; DefaultCharisma = 20; DefaultMana = 20; @@ -5087,6 +5675,7 @@ vampire BaseBiteStrength = 800; CanBeWished = false; ClassStates = HASTE|INFRA_VISION|VAMPIRISM; + ConsumeFlags = CT_LIQUID; /* Could limit that further to only blood. */ PanicLevel = 0; FleshMaterial = VAMPIRE_FLESH; AttachedGod = MORTIFER; @@ -5096,9 +5685,10 @@ vampire CanRead = true; UndeadVersions = false; IsCatacombCreature = true; + IsUndead = true; StandVerb = "prowling"; BonusBiteStrength = 0; /* Doesn't need to be more powerful */ - AllowedDungeons = { 2, ATTNAM, XINROCH_TOMB; } + AllowedDungeons = { 3, ATTNAM, XINROCH_TOMB, PYRAMID; } } kobold @@ -5110,6 +5700,7 @@ kobold DefaultEndurance = 9; DefaultPerception = 12; DefaultIntelligence = 4; + DefaultWillPower = 4; DefaultWisdom = 4; DefaultCharisma = 4; DefaultMana = 0; @@ -5127,12 +5718,22 @@ kobold NameSingular = "kobold"; CanBeGenerated = true; Sex = UNDEFINED; + ClassStates = SEARCHING; RightWielded = BALSA_WOOD BALSA_WOOD meleeweapon(SPEAR); KnownCWeaponSkills == POLE_ARMS; CWeaponSkillHits == 5; RightSWeaponSkillHits = 0; PanicLevel = 75; FleshMaterial = KOBOLD_FLESH; + HostileReplies = + { + 5, + "@Dd grunts angrily.", + "@Dd yells at the top of @sp lungs.", + "@Dd makes vulgar gestures in your general direction.", + "@Dd eyes you warily.", + "@Dd screams: \"You killed all my friends!\""; + } DeathMessage = "@Dd dies yelling like a tortured hyena."; StandVerb = "skulking"; AttachedGod = CRUENTUS; @@ -5212,6 +5813,14 @@ kobold "\"The mind is everything. What you think you become.\"", "\"Peace comes from within. Do not seek it without.\""; } + HostileReplies = + { + 4, + "@Dd snarls: \"You murderer!\"", + "@Dd frowns: \"I cannot let you terrorize my kind any longer.\"", + "\"Die, you widowmaker!\"", + "\"How many of my kin have you slaughtered?!\""; + } } Config ASSASSIN; @@ -5281,6 +5890,7 @@ gibberling DefaultEndurance = 10; DefaultPerception = 15; DefaultIntelligence = 4; + DefaultWillPower = 4; DefaultWisdom = 4; DefaultCharisma = 5; DefaultMana = 0; @@ -5385,18 +5995,11 @@ gibberling } } -largecat +feline { - DefaultArmStrength = 4; - DefaultAgility = 35; - DefaultEndurance = 10; - DefaultPerception = 21; - DefaultIntelligence = 10; - DefaultWisdom = 10; - DefaultCharisma = 20; - DefaultMana = 0; - TotalVolume = 15000; - TorsoBitmapPos = 496, 0; + IsAbstract = true; + AttackStyle = USE_HEAD; + KnownCWeaponSkills == BITE; HostileReplies = { 4, @@ -5407,23 +6010,37 @@ largecat } FriendlyReplies = { - 7, + 8, "@Dd mews.", "@Dd purrs.", "@Dd weaves between your legs.", "@Dd kneads you with its paws. Ouch!", "@Dd yowls.", + "@Dd says: \"Nyah.\"", "You pet @dd.", "@Dd headbutts you playfully."; } +} + +largecat /* feline-> */ +{ + DefaultArmStrength = 4; + DefaultAgility = 35; + DefaultEndurance = 10; + DefaultPerception = 21; + DefaultIntelligence = 10; + DefaultWillPower = 15; + DefaultWisdom = 10; + DefaultCharisma = 20; + DefaultMana = 0; + TotalVolume = 15000; + TorsoBitmapPos = 496, 0; TotalSize = 60; SkinColor = rgb16(50, 50, 50); Adjective = "large"; NameSingular = "cat"; - AttackStyle = USE_HEAD; BaseBiteStrength = 600; CanBeGenerated = true; - KnownCWeaponSkills == BITE; CWeaponSkillHits == 50; ClassStates = INFRA_VISION; FleshMaterial = CAT_FLESH; @@ -5438,6 +6055,7 @@ largerat DefaultEndurance = 10; DefaultPerception = 12; DefaultIntelligence = 5; + DefaultWillPower = 5; DefaultWisdom = 5; DefaultCharisma = 4; DefaultMana = 0; @@ -5483,6 +6101,7 @@ angel DefaultEndurance = 25; DefaultPerception = 35; DefaultIntelligence = 25; + DefaultWillPower = 25; DefaultWisdom = 35; DefaultCharisma = 50; DefaultMana = 35; @@ -5588,6 +6207,7 @@ kamikazedwarf DefaultEndurance = 15; DefaultPerception = 18; DefaultIntelligence = 10; + DefaultWillPower = 30; DefaultWisdom = 5; DefaultCharisma = 10; DefaultMana = 10; @@ -5645,6 +6265,7 @@ mammoth DefaultEndurance = 15; DefaultPerception = 18; DefaultIntelligence = 5; + DefaultWillPower = 7; DefaultWisdom = 7; DefaultCharisma = 10; DefaultMana = 0; @@ -5677,6 +6298,7 @@ unicorn DefaultEndurance = 10; DefaultPerception = 18; DefaultIntelligence = 25; + DefaultWillPower = 20; DefaultWisdom = 20; DefaultCharisma = 30; DefaultMana = 25; @@ -5688,11 +6310,13 @@ unicorn "@Dd whinnies.", "@Dd whickers."; } + DeathMessage = "@Dd neighs one last time and dies."; TotalVolume = 100000; TorsoBitmapPos = 544, 0; TotalSize = 200; NameSingular = "unicorn"; AttackStyle = USE_LEGS|USE_HEAD; + ConsumeFlags = CT_FRUIT|CT_LIQUID|CT_MISC_PLANT|CT_PROCESSED; BaseKickStrength = 700; BaseBiteStrength = 350; CanBeGenerated = true; @@ -5739,6 +6363,7 @@ genie DefaultEndurance = 15; DefaultPerception = 18; DefaultIntelligence = 20; + DefaultWillPower = 25; DefaultWisdom = 25; DefaultCharisma = 20; DefaultMana = 50; @@ -5785,27 +6410,24 @@ genie UndeadVersions = false; } -lion +lion /* feline-> */ { DefaultArmStrength = 20; DefaultAgility = 25; DefaultEndurance = 15; DefaultPerception = 24; DefaultIntelligence = 7; + DefaultWillPower = 7; DefaultWisdom = 15; DefaultCharisma = 20; DefaultMana = 0; TotalVolume = 100000; TorsoBitmapPos = 576, 0; - HostileReplies == "@Dd growls furiously."; - FriendlyReplies == "@Dd growls happily."; TotalSize = 200; SkinColor = rgb16(200, 200, 112); NameSingular = "lion"; - AttackStyle = USE_HEAD; BaseBiteStrength = 800; CanBeGenerated = true; - KnownCWeaponSkills == BITE; CWeaponSkillHits == 200; FleshMaterial = LION_FLESH; DeathMessage = "@Dd growls and is slain."; @@ -5820,14 +6442,22 @@ carnivorousplant DefaultEndurance = 7; DefaultPerception = 6; DefaultIntelligence = 3; + DefaultWillPower = 1; DefaultWisdom = 2; DefaultCharisma = 3; DefaultMana = 0; CanOpen = false; TotalVolume = 20000; TorsoBitmapPos = 0, 16; - HostileReplies == "@Dd is silent."; - FriendlyReplies == "@Dd is silent."; + HostileReplies = + { + 4, + "@Dd is silent in a hostile manner.", + "@Dd rustles @sp leaves.", + "@Dd sways.", + "@Dd drools."; /* It's carnivorous, after all. */ + } + FriendlyReplies == "@Dd is silent in a firendly manner."; TotalSize = 100; Adjective = "carnivorous"; NameSingular = "plant"; @@ -5885,6 +6515,7 @@ buffalo DefaultEndurance = 20; DefaultPerception = 24; DefaultIntelligence = 5; + DefaultWillPower = 5; DefaultWisdom = 5; DefaultCharisma = 5; DefaultMana = 0; @@ -5909,6 +6540,7 @@ buffalo AttachedGod = SILVA; Frequency = 2500; IsEnormous = true; + ConsumeFlags = CT_FRUIT|CT_LIQUID|CT_MISC_PLANT|CT_PROCESSED; } snake @@ -5918,6 +6550,7 @@ snake DefaultEndurance = 10; DefaultPerception = 9; DefaultIntelligence = 5; + DefaultWillPower = 5; DefaultWisdom = 5; DefaultCharisma = 5; DefaultMana = 0; @@ -5933,8 +6566,8 @@ snake "@Dd rattles!"; } FriendlyReplies == "@Dd hisses in a friendly manner."; + DeathMessage = "@Dd hisses one last time and dies."; TotalSize = 250; - SkinColor = rgb16(130, 0, 0); NameSingular = "snake"; AttackStyle = USE_HEAD; BaseBiteStrength = 500; @@ -5944,13 +6577,35 @@ snake CWeaponSkillHits == 100; ClassStates = INFRA_VISION; HasALeg = false; + IsAbstract = true; FleshMaterial = SNAKE_FLESH; DangerModifier = 75; CanOpen = false; AttachedGod = MELLIS; AutomaticallySeen = true; RunDescriptionLineOne = "Slithering"; - RunDescriptionLineTwo = "very fast"; + RunDescriptionLineTwo = " very fast"; + + Config RED_SNAKE; + { + Adjective = "garnet"; + SkinColor = rgb16(130, 0, 0); + Frequency = 2000; + } + + Config GREEN_SNAKE; + { + Adjective = "viridian"; + SkinColor = rgb16(0, 130, 0); + Frequency = 6000; + } + + Config BLUE_SNAKE; + { + Adjective = "cerulean"; + SkinColor = rgb16(0, 0, 130); + Frequency = 2000; + } } orc @@ -5962,6 +6617,7 @@ orc DefaultEndurance = 15; DefaultPerception = 18; DefaultIntelligence = 7; + DefaultWillPower = 4; DefaultWisdom = 6; DefaultCharisma = 4; DefaultMana = 0; @@ -5988,6 +6644,7 @@ orc RightSWeaponSkillHits = 20; LeftSWeaponSkillHits = 20; ClassStates = GAS_IMMUNITY|INFRA_VISION; + BloodMaterial = BLACK_BLOOD; PanicLevel = 50; HostileReplies = { @@ -6191,6 +6848,7 @@ orc { AttributeBonus = 60; DefaultWisdom = 19; + DefaultWillPower = 15; HairColor = rgb16(224, 224, 0); ClothColor = rgb16(224, 224, 0); BeltColor = rgb16(176, 0, 0); @@ -6200,7 +6858,7 @@ orc TorsoBitmapPos = 48, 32; ArmBitmapPos = 80, 16; LegBitmapPos = 0, 224; - Helmet = helmet(HELM_OF_ATTRACTIVITY) { Enchantment = 7; } + Helmet = GOLD helmet(HELM_OF_ATTRACTIVITY) { Enchantment = 7; } Cloak = FABRIC cloak { Enchantment = 2; } BodyArmor = GOLD bodyarmor(CHAIN_MAIL) { Enchantment = 2; } Belt = GOLD belt(BELT_OF_CARRYING) { Enchantment = 2; } @@ -6252,6 +6910,7 @@ cossack DefaultEndurance = 15; DefaultPerception = 21; DefaultIntelligence = 10; + DefaultWillPower = 15; DefaultWisdom = 7; DefaultCharisma = 7; DefaultMana = 0; @@ -6275,7 +6934,7 @@ cossack RightGauntlet = WOLF_SKIN gauntlet; RightBoot = WOLF_SKIN boot; PanicLevel = 10; - DeathMessage = "@Dd falls shouting: \"Hope there's vodka in hell!"; + DeathMessage = "@Dd falls shouting: \"Hope there's vodka in hell!\""; HostileReplies == "@Dd shouts wildly: \"For Tataria!\""; AttachedGod = SILVA; FriendlyReplies = @@ -6287,6 +6946,76 @@ cossack "\"I followed Ivan through some weird portal and ended up in this land. At least no creditors can find me.\"", "\"Women are odd. No matter how many times I take them to hunt wild beasts of the Steppe or show them my collection of old vodka bottles, none of them still likes me.\""; } + + Config REBEL_SOLDIER; + { + ClothColor = rgb16(75, 83, 32); + CapColor = rgb16(220, 0, 0); + TotalVolume = 70000; + TotalSize = 170; + NameSingular = "rebel soldier"; + Helmet = BRONZE helmet; + Cloak = ELF_CLOTH cloak; + BodyArmor = BRONZE bodyarmor(CHAIN_MAIL); + Belt = BLACK_LEATHER belt; + RightWielded = BRONZE CYPRESS_WOOD meleeweapon(BASTARD_SWORD); + RightGauntlet = BLACK_LEATHER gauntlet; + RightBoot = BLACK_LEATHER boot; + FriendlyReplies = + { + 10, + "\"Lord Regent's men think us lawless beasts. How deluded can they be?\"", + "\"We must free young prince Artorius from the clutches of that wretched Lord Regent.\"", + "\"Lord Regent didn't think anyone would stand up to his treason, but here we are.\"", + "\"We won't give up to Lord Regent without a fight, don't you worry.\"", + "\"I've heard some people talk about just burning the castle to the ground. Probably just talk though. Everyone's a bit on edge right now.\"", + "\"You heard about the scouting squad? They were caught and now they're in jail, waiting for the gallows!\"", + "\"Truth is, we don't know who was behind the old king's murder. But too much power has suddenly fallen into Lord Regent's lap.\"", + "\"Everyone wants to know what really happened to the old king.\"", + "\"I'm sure Harvan will find some way to hold the kingdom together, once the young prince is crowned and we have a good king again.\"", + "\"We're all talking about the old king's murder. How could Lord Regent get away with it?\""; + } + HostileReplies == "@Dd screams obscenities."; + DeathMessage = "@Dd falls shouting: \"For Aslona!\""; + AttachedGod = SEGES; + } + + Config REBEL_LIEUTENANT; + { + AttributeBonus = 20; + ClothColor = rgb16(75, 83, 32); + CapColor = rgb16(128, 0, 128); + TotalVolume = 80000; + TotalSize = 175; + NameSingular = "rebel lieutenant"; + CWeaponSkillHits == 200; + RightSWeaponSkillHits = 150; + Helmet = DEEP_BRONZE helmet; + Cloak = ELF_CLOTH cloak { Enchantment = 2; } + BodyArmor = DEEP_BRONZE bodyarmor(CHAIN_MAIL); + Belt = BLACK_LEATHER belt; + RightWielded = DEEP_BRONZE CYPRESS_WOOD meleeweapon(BASTARD_SWORD) { Enchantment = 1; } + RightGauntlet = BLACK_LEATHER gauntlet { Enchantment = 2; } + RightBoot = BLACK_LEATHER boot { Enchantment = 2; } + FriendlyReplies = + { + 10, + "\"Lord Regent's men think us lawless beasts. How deluded can they be?\"", + "\"We must free young prince Artorius from the clutches of that wretched Lord Regent.\"", + "\"Lord Regent didn't think anyone would stand up to his treason, but here we are.\"", + "\"We won't give up to Lord Regent without a fight, don't you worry.\"", + "\"Truth is, we don't know who was behind the old king's murder. But too much power has suddenly fallen into Lord Regent's lap.\"", + "\"I don't know what to think about the goblins escaping their owners. Things seem to go from bad to worse, don't they?\"", + "\"Lord Regent recently announced that he won't stop until slavery is outlawed. It's our right to have slaves, by the old laws!\"", + "\"How does Lord Regent think we'll work the fields with no slaves? Our economy is already falling and he wants to kill it completely!\"", + "\"I'm sure Harvan will find some way to hold the kingdom together, once the young prince is crowned and we have a good king again.\"", + "\"We all believe Harvan will lead us to victory. But we can't let it go to his head, right? Heh heh.\""; + } + HostileReplies == "@Dd screams obscenities."; + DeathMessage = "@Dd falls shouting: \"For Aslona!\""; + AttachedGod = SEGES; + Inventory == Random { Category = WAND; Times = 0:2; } + } } bananagrower @@ -6298,6 +7027,7 @@ bananagrower DefaultEndurance = 15; DefaultPerception = 15; DefaultIntelligence = 20; + DefaultWillPower = 20; DefaultWisdom = 30; DefaultCharisma = 10; DefaultMana = 5; @@ -6313,7 +7043,7 @@ bananagrower ArmBitmapPos = 64, 64; LegBitmapPos = 0, 288; Inventory == banana { Times = 10; } - TamingDifficulty = NO_TAMING; /* AI will go insane if he leaves New Attnam */ + /* AI will no longer go insane if he leaves New Attnam. */ HostileReplies == "\"Banana POWER!\""; AttachedGod = SILVA; CanRead = true; @@ -6348,6 +7078,7 @@ imperialist DefaultEndurance = 15; DefaultPerception = 24; DefaultIntelligence = 35; + DefaultWillPower = 20; DefaultWisdom = 10; DefaultCharisma = 25; DefaultMana = 10; @@ -6440,6 +7171,7 @@ imperialist ScienceTalkWisdomRequirement = 0; ScienceTalkCharismaRequirement = 15; IsSadist = true; + IsAbstract = true; UndeadVersions = false; Config VICE_ROY; @@ -6612,6 +7344,7 @@ smith DefaultEndurance = 30; DefaultPerception = 15; DefaultIntelligence = 15; + DefaultWillPower = 15; DefaultWisdom = 15; DefaultCharisma = 15; DefaultMana = 10; @@ -6619,10 +7352,7 @@ smith TotalSize = 190; NameSingular = "smith"; Helmet = STEEL helmet { Enchantment = 1; } - Cloak = OMMEL_HAIR cloak(CLOAK_OF_FIRE_RESISTANCE) { Enchantment = 1; } BodyArmor = STEEL bodyarmor(PLATE_MAIL) { Enchantment = 1; } - RightWielded = MITHRIL meleeweapon(HAMMER) { Enchantment = 2; } - RightGauntlet = LEATHER gauntlet(GAUNTLET_OF_DEXTERITY) { Enchantment = 4; } RightRing = ring(RING_OF_FIRE_RESISTANCE); LeftRing = ring(RING_OF_FIRE_RESISTANCE); RightBoot = STEEL boot { Enchantment = 1; } @@ -6630,20 +7360,46 @@ smith CWeaponSkillHits == 500; RightSWeaponSkillHits = 200; LeftSWeaponSkillHits = 200; - TorsoBitmapPos = 48, 192; - HeadBitmapPos = 112, 160; - ArmBitmapPos = 64, 64; - LegBitmapPos = 0, 96; - HairColor = rgb16(200, 200, 200); - ClothColor = rgb16(100, 100, 100); CanRead = true; /* Replies overridden */ IsUnique = true; + IsAbstract = true; IsNameable = false; CanBeCloned = false; - DefaultName = "Ikiros"; TamingDifficulty = NO_TAMING; AttachedGod = LORICATUS; + + Config ATTNAM; + { + DefaultName = "Ikiros"; + TorsoBitmapPos = 48, 192; + HeadBitmapPos = 112, 160; + ArmBitmapPos = 64, 64; + LegBitmapPos = 0, 96; + HairColor = rgb16(200, 200, 200); + ClothColor = rgb16(100, 100, 100); + Cloak = OMMEL_HAIR cloak(CLOAK_OF_FIRE_RESISTANCE) { Enchantment = 1; } + RightGauntlet = LEATHER gauntlet(GAUNTLET_OF_DEXTERITY) { Enchantment = 4; } + RightWielded = MITHRIL meleeweapon(HAMMER) { Enchantment = 2; } + } + + Config ASLONA_CASTLE; + { + DefaultName = "Khalybs"; + TorsoBitmapPos = 48, 192; + HeadBitmapPos = 112, 480; + ArmBitmapPos = 64, 64; + LegBitmapPos = 0, 96; + HairColor = rgb16(44, 34, 43); + ClothColor = rgb16(100, 100, 100); + TotalVolume = 90000; + TotalSize = 200; + Cloak = SELKIE_SKIN cloak(CLOAK_OF_FIRE_RESISTANCE) { Enchantment = 1; } + RightGauntlet = SELKIE_SKIN gauntlet(GAUNTLET_OF_DEXTERITY) { Enchantment = 4; } + RightWielded = CHROME MAHOGANY_WOOD meleeweapon(HAMMER); + LeftWielded = CHROME MAHOGANY_WOOD meleeweapon(HAMMER); + Sex = FEMALE; + } } ostrich @@ -6653,6 +7409,7 @@ ostrich DefaultEndurance = 10; DefaultPerception = 15; DefaultIntelligence = 5; + DefaultWillPower = 5; DefaultWisdom = 5; DefaultCharisma = 7; DefaultMana = 0; @@ -6674,7 +7431,7 @@ ostrich FleshMaterial = OSTRICH_FLESH; DeathMessage = "@Dd is squashed to a bloody mass of feathers."; StandVerb = "floating"; - TamingDifficulty = NO_TAMING; /* AI will go insane if it leaves New Attnam */ + /* AI will no longer go insane if it leaves New Attnam */ AttachedGod = SILVA; ClassStates = LEVITATION; AutomaticallySeen = true; @@ -6689,6 +7446,7 @@ elder DefaultEndurance = 7; DefaultPerception = 21; DefaultIntelligence = 25; + DefaultWillPower = 30; DefaultWisdom = 40; DefaultCharisma = 20; DefaultMana = 20; @@ -6747,6 +7505,7 @@ encourager DefaultEndurance = 15; DefaultPerception = 21; DefaultIntelligence = 7; + DefaultWillPower = 7; DefaultWisdom = 7; DefaultCharisma = 7; DefaultMana = 0; @@ -6789,6 +7548,7 @@ chameleon DefaultEndurance = 12; DefaultPerception = 12; DefaultIntelligence = 5; + DefaultWillPower = 8; DefaultWisdom = 5; DefaultCharisma = 8; DefaultMana = 0; @@ -6821,6 +7581,7 @@ floatingeye DefaultEndurance = 6; DefaultPerception = 50; DefaultIntelligence = 2; + DefaultWillPower = 2; DefaultWisdom = 2; DefaultCharisma = 8; DefaultMana = 0; @@ -6870,6 +7631,7 @@ eddy DefaultEndurance = 3; DefaultPerception = 12; DefaultIntelligence = 1; + DefaultWillPower = 1; DefaultWisdom = 1; DefaultCharisma = 1; DefaultMana = 10; @@ -6919,6 +7681,7 @@ mushroom DefaultEndurance = 10; DefaultPerception = 5; DefaultIntelligence = 2; + DefaultWillPower = 2; DefaultWisdom = 2; DefaultCharisma = 8; DefaultMana = 4; @@ -6945,17 +7708,25 @@ mushroom IsRooted = true; AllowUnconsciousness = false; CanChoke = false; - HostileReplies == "@Dd expresses @sp utter contempt for your pitiful and loathsome existence by wobbling erratically."; + HostileReplies = + { + 4, + "@Dd wobbles threateningly.", + "@Dd suddenly seems very still.", + "@Dd sporulates a bit.", + "@Dd expresses @sp utter contempt for your pitiful and loathsome existence by wobbling erratically."; + } FriendlyReplies == "@Dd wobbles knowingly."; } -magicmushroom +magicmushroom /* mushroom-> */ { DefaultArmStrength = 6; DefaultAgility = 3; DefaultEndurance = 20; DefaultPerception = 7; DefaultIntelligence = 3; + DefaultWillPower = 3; DefaultWisdom = 3; DefaultCharisma = 10; DefaultMana = 7; @@ -6964,10 +7735,6 @@ magicmushroom TotalSize = 60; Adjective = "magical"; NameSingular = "mushroom"; - HasEyes = false; - HasHead = false; - HasALeg = false; - UsesNutrition = false; FleshMaterial = MAGIC_MUSHROOM_FLESH; SkinColor = rgb16(200, 170, 170); CanBeGenerated = true; @@ -6978,7 +7745,6 @@ magicmushroom Frequency = 5000; DangerModifier = 75; StandVerb = "wobbling"; - CanChoke = false; } darkmage @@ -7040,6 +7806,7 @@ darkmage DefaultEndurance = 15; DefaultPerception = 21; DefaultIntelligence = 15; + DefaultWillPower = 10; DefaultWisdom = 10; DefaultCharisma = 10; DefaultMana = 20; @@ -7079,6 +7846,7 @@ darkmage DefaultEndurance = 15; DefaultPerception = 18; DefaultIntelligence = 30; + DefaultWillPower = 15; DefaultWisdom = 15; DefaultCharisma = 15; DefaultMana = 30; @@ -7116,6 +7884,7 @@ darkmage DefaultEndurance = 15; DefaultPerception = 15; DefaultIntelligence = 45; + DefaultWillPower = 20; DefaultWisdom = 20; DefaultCharisma = 20; DefaultMana = 40; @@ -7155,6 +7924,7 @@ darkmage DefaultEndurance = 15; DefaultPerception = 12; DefaultIntelligence = 60; + DefaultWillPower = 30; DefaultWisdom = 25; DefaultCharisma = 25; DefaultMana = 50; @@ -7225,6 +7995,7 @@ twoheadedmoose DefaultEndurance = 15; DefaultPerception = 18; DefaultIntelligence = 5; + DefaultWillPower = 5; DefaultWisdom = 5; DefaultCharisma = 3; DefaultMana = 0; @@ -7248,6 +8019,7 @@ twoheadedmoose CanKick = true; DangerModifier = 50; IsEnormous = true; + ConsumeFlags = CT_FRUIT|CT_LIQUID|CT_MISC_PLANT|CT_PROCESSED; } magpie @@ -7257,6 +8029,7 @@ magpie DefaultEndurance = 8; DefaultPerception = 20; DefaultIntelligence = 6; + DefaultWillPower = 4; DefaultWisdom = 5; DefaultCharisma = 6; DefaultMana = 0; @@ -7291,6 +8064,7 @@ skunk DefaultEndurance = 9; DefaultPerception = 18; DefaultIntelligence = 5; + DefaultWillPower = 5; DefaultWisdom = 5; DefaultCharisma = 4; DefaultMana = 0; @@ -7323,6 +8097,7 @@ invisiblestalker DefaultEndurance = 15; DefaultPerception = 24; DefaultIntelligence = 8; + DefaultWillPower = 7; DefaultWisdom = 6; DefaultCharisma = 5; DefaultMana = 0; @@ -7356,7 +8131,7 @@ invisiblestalker DangerModifier = 250; KnownCWeaponSkills == UNARMED; CWeaponSkillHits == 200; - ClassStates = INVISIBLE; + ClassStates = INVISIBLE|SEARCHING; BodyPartsDisappearWhenSevered = true; IsImmuneToStickiness = true; @@ -7367,8 +8142,8 @@ invisiblestalker TotalSize = 100; CWeaponSkillHits == 500; NameSingular = "slayer"; - PanicLevel = 50; EyeColor = rgb16(180, 0, 0); + ClassStates = INVISIBLE|SEARCHING|TELEPORT; } } @@ -7387,6 +8162,7 @@ elpuri DefaultEndurance = 20; DefaultPerception = 42; DefaultIntelligence = 45; + DefaultWillPower = 35; DefaultWisdom = 35; DefaultCharisma = 2; DefaultMana = 35; @@ -7437,6 +8213,7 @@ genetrixvesana DefaultEndurance = 30; DefaultPerception = 24; DefaultIntelligence = 12; + DefaultWillPower = 8; DefaultWisdom = 8; DefaultCharisma = 20; DefaultMana = 0; @@ -7468,7 +8245,7 @@ genetrixvesana FriendlyReplies == "@Dd is silent."; ClassStates = INFRA_VISION; HasALeg = false; - SpillsBlood = false; + SpillsBlood = true; /* It will be more fun! */ Sweats = false; StandVerb = "rooted"; IsRooted = true; @@ -7483,6 +8260,7 @@ hedgehog DefaultEndurance = 10; DefaultPerception = 12; DefaultIntelligence = 5; + DefaultWillPower = 5; DefaultWisdom = 5; DefaultCharisma = 6; DefaultMana = 0; @@ -7510,6 +8288,7 @@ bunny DefaultEndurance = 16; DefaultPerception = 18; DefaultIntelligence = 10; + DefaultWillPower = 6; DefaultWisdom = 6; DefaultCharisma = 10; DefaultMana = 0; @@ -7580,6 +8359,7 @@ vladimir DefaultEndurance = 25; DefaultPerception = 30; DefaultIntelligence = 8; + DefaultWillPower = 12; DefaultWisdom = 7; DefaultCharisma = 15; DefaultMana = 0; @@ -7617,6 +8397,7 @@ hattifattener DefaultEndurance = 10; DefaultPerception = 9; DefaultIntelligence = 3; + DefaultWillPower = 3; DefaultWisdom = 3; DefaultCharisma = 6; DefaultMana = 0; @@ -7683,6 +8464,7 @@ necromancer DefaultEndurance = 15; DefaultPerception = 21; DefaultIntelligence = 15; + DefaultWillPower = 15; DefaultWisdom = 10; DefaultCharisma = 10; DefaultMana = 20; @@ -7732,6 +8514,7 @@ necromancer DefaultEndurance = 15; DefaultPerception = 18; DefaultIntelligence = 30; + DefaultWillPower = 30; DefaultWisdom = 15; DefaultCharisma = 15; DefaultMana = 30; @@ -7793,6 +8576,7 @@ necromancer DefaultEndurance = 18; DefaultPerception = 22; DefaultIntelligence = 36; + DefaultWillPower = 35; DefaultWisdom = 20; DefaultCharisma = 9; DefaultMana = 50; @@ -7836,6 +8620,7 @@ sumowrestler DefaultEndurance = 12; DefaultPerception = 15; DefaultIntelligence = 10; + DefaultWillPower = 15; DefaultWisdom = 15; DefaultCharisma = 6; DefaultMana = 0; @@ -7851,7 +8636,7 @@ sumowrestler ArmBitmapPos = 64, 336; LegBitmapPos = 16, 176; TamingDifficulty = NO_TAMING; - HostileReplies == "\"SSSUUUMMMMMMOOOOORRRRGGGGGHHHHH!!!! (Mr. Decos said sumo wrestlers always yell like this)\""; + HostileReplies == "\"SSSUUUMMMMMMOOOOORRRRGGGGGHHHHH!!!! (Mr. Decos said sumo wrestlers always yell like this.)\""; AttachedGod = SILVA; KnownCWeaponSkills = { 2, UNARMED, KICK; } CWeaponSkillHits = { 2, 1000, 1000; } @@ -7901,6 +8686,7 @@ tourist DefaultEndurance = 10; DefaultPerception = 15; DefaultIntelligence = 10; + DefaultWillPower = 10; DefaultWisdom = 7; DefaultCharisma = 8; TotalVolume = 90000; @@ -7937,6 +8723,7 @@ tourist DefaultEndurance = 8; DefaultPerception = 21; DefaultIntelligence = 7; + DefaultWillPower = 15; DefaultWisdom = 10; DefaultCharisma = 10; TotalVolume = 70000; @@ -7974,6 +8761,7 @@ tourist DefaultEndurance = 10; DefaultPerception = 18; DefaultIntelligence = 12; + DefaultWillPower = 5; DefaultWisdom = 5; DefaultCharisma = 6; TotalVolume = 50000; @@ -8006,6 +8794,7 @@ blinkdog DefaultEndurance = 8; DefaultPerception = 21; DefaultIntelligence = 10; + DefaultWillPower = 5; DefaultWisdom = 5; DefaultCharisma = 10; DefaultMana = 5; @@ -8018,6 +8807,7 @@ blinkdog AttachedGod = SOPHOS; DangerModifier = 50; FleshMaterial = BLINK_DOG_FLESH; + ConsumeFlags = CT_FRUIT|CT_MEAT|CT_LIQUID|CT_PROCESSED|CT_BONE|CT_MAGIC; } veterankamikazedwarf @@ -8117,7 +8907,7 @@ archangel Config LEGIFER; { DefaultName = "Iustitia"; - Helmet = helmet(HELM_OF_PERCEPTION) { Enchantment = 4; } + Helmet = ILLITHIUM helmet(HELM_OF_PERCEPTION) { Enchantment = 4; } BodyArmor = ILLITHIUM bodyarmor(CHAIN_MAIL) { Enchantment = 4; } RightWielded = SUN_CRYSTAL GOLD flamingsword { Enchantment = 7; } LeftWielded = SUN_CRYSTAL shield { Enchantment = 7; } @@ -8135,7 +8925,7 @@ archangel Config DULCIS; { DefaultName = "Amatrix"; - Helmet = helmet(HELM_OF_ATTRACTIVITY) { Enchantment = 10; } + Helmet = DARK_GOLD helmet(HELM_OF_ATTRACTIVITY) { Enchantment = 10; } RightWielded = 0; LeftWielded = 0; KnownCWeaponSkills = { 2, UNARMED, BITE; } @@ -8147,7 +8937,7 @@ archangel Config SEGES; { DefaultName = "Salubris"; - Helmet = helmet(HELM_OF_UNDERSTANDING) { Enchantment = 4; } + Helmet = UNICORN_HORN helmet(HELM_OF_UNDERSTANDING) { Enchantment = 4; } RightWielded = DIAMOND OCTIRON bansheesickle { Enchantment = 6; } LeftWielded = DIAMOND OCTIRON bansheesickle { Enchantment = 6; } KnownCWeaponSkills = { 2, SMALL_SWORDS, LARGE_SWORDS; } @@ -8157,10 +8947,10 @@ archangel Config SOPHOS; { DefaultName = "Magus"; - Helmet = helmet(HELM_OF_BRILLIANCE) { Enchantment = 4; } + Helmet = ARCANITE helmet(HELM_OF_BRILLIANCE) { Enchantment = 4; } BodyArmor = SPIDER_SILK bodyarmor(PLATE_MAIL) { Enchantment = 0; } Cloak = SPIDER_SILK cloak { Enchantment = 0; } - RightWielded = ARCANITE ARCANITE meleeweapon(QUARTER_STAFF) { Enchantment = 6; } + RightWielded = OCCULTUM OCCULTUM meleeweapon(QUARTER_STAFF) { Enchantment = 6; } Belt = SPIDER_SILK belt { Enchantment = 0; } RightGauntlet = SPIDER_SILK gauntlet(GAUNTLET_OF_STRENGTH) { Enchantment = 4; } KnownCWeaponSkills = { 2, BLUNT_WEAPONS, UNARMED; } @@ -8234,7 +9024,7 @@ archangel { DefaultName = "Sinistra"; IsSadist = true; - Helmet = helmet(HELM_OF_MANA) { Enchantment = 4; } + Helmet = OCTIRON helmet(HELM_OF_MANA) { Enchantment = 4; } RightWielded = OCTIRON PURPLE_CRYSTAL wondersmellstaff { Enchantment = 6; } KnownCWeaponSkills = { 2, BLUNT_WEAPONS, UNARMED; } } @@ -8255,6 +9045,7 @@ archangel { DefaultName = "Erado"; IsSadist = true; + IsUndead = true; Helmet = OMMEL_BONE skull; RightWielded = PSYPHER OCTIRON terrorscythe { Enchantment = 2; } LeftWielded = PSYPHER OCTIRON meleeweapon(SICKLE) { Enchantment = 4; } @@ -8271,35 +9062,63 @@ tailor DefaultEndurance = 20; DefaultPerception = 27; DefaultIntelligence = 15; + DefaultWillPower = 15; DefaultWisdom = 15; DefaultCharisma = 15; DefaultMana = 10; - TotalVolume = 70000; - TotalSize = 180; NameSingular = "tailor"; - Cloak = LEATHER cloak { Enchantment = 1; } - BodyArmor = HARDENED_LEATHER bodyarmor(PLATE_MAIL) { Enchantment = 1; } - RightWielded = MITHRIL meleeweapon(DAGGER) { Enchantment = 2; } - RightGauntlet = NYMPH_HAIR gauntlet(GAUNTLET_OF_DEXTERITY) { Enchantment = 4; } - RightBoot = OMMEL_HAIR boot { Enchantment = 1; } KnownCWeaponSkills == SMALL_SWORDS; CWeaponSkillHits == 500; RightSWeaponSkillHits = 200; LeftSWeaponSkillHits = 200; - TorsoBitmapPos = 48, 48; - HeadBitmapPos = 96, 0; - ArmBitmapPos = 64, 16; - LegBitmapPos = 0, 0; - ClothColor = rgb16(200, 200, 200); - LegMainColor = rgb16(111, 64, 37); CanRead = true; /* Replies overridden */ IsUnique = true; + IsAbstract = true; IsNameable = false; CanBeCloned = false; - DefaultName = "Mirvo"; TamingDifficulty = NO_TAMING; AttachedGod = SOPHOS; + + Config ATTNAM; + { + DefaultName = "Mirvo"; + TorsoBitmapPos = 48, 48; + HeadBitmapPos = 96, 0; + ArmBitmapPos = 64, 16; + LegBitmapPos = 0, 0; + ClothColor = rgb16(200, 200, 200); + LegMainColor = rgb16(111, 64, 37); + TotalVolume = 70000; + TotalSize = 180; + Cloak = LEATHER cloak { Enchantment = 1; } + BodyArmor = HARDENED_LEATHER bodyarmor(PLATE_MAIL) { Enchantment = 1; } + RightWielded = MITHRIL meleeweapon(DAGGER) { Enchantment = 2; } + RightGauntlet = NYMPH_HAIR gauntlet(GAUNTLET_OF_DEXTERITY) { Enchantment = 4; } + RightBoot = OMMEL_HAIR boot { Enchantment = 1; } + } + + Config ASLONA_CASTLE; + { + DefaultName = "Palea"; + NameSingular = "seamstress"; + HeadBitmapPos = 112, 64; + TorsoBitmapPos = 32, 160; + ArmBitmapPos = 64, 160; + LegBitmapPos = 0, 112; + HairColor = rgb16(181, 82, 57); + ClothColor = rgb16(0, 191, 255); + BeltColor = rgb16(100, 100, 200); + TotalVolume = 60000; + TotalSize = 165; + Cloak = MERMAID_HAIR cloak { Enchantment = 1; } + BodyArmor = MERMAID_HAIR bodyarmor(PLATE_MAIL) { Enchantment = 3; } + Belt = MERMAID_HAIR belt { Enchantment = 1; } + RightWielded = STAINLESS_STEEL meleeweapon(DAGGER) { Enchantment = 6; } + RightGauntlet = MERMAID_HAIR gauntlet(GAUNTLET_OF_DEXTERITY) { Enchantment = 4; } + RightBoot = SELKIE_SKIN boot { Enchantment = 1; } + Sex = FEMALE; + } } mysticfrog @@ -8322,6 +9141,7 @@ mysticfrog Config DARK; { DefaultIntelligence = 35; + DefaultWillPower = 35; DefaultWisdom = 25; DefaultCharisma = 6; BloodMaterial = DARK_FROG_BLOOD; @@ -8346,6 +9166,7 @@ mysticfrog Config LIGHT; { DefaultIntelligence = 25; + DefaultWillPower = 30; DefaultWisdom = 35; DefaultCharisma = 30; BloodMaterial = LIGHT_FROG_BLOOD; @@ -8368,6 +9189,7 @@ lobhse DefaultEndurance = 40; DefaultPerception = 21; DefaultIntelligence = 5; + DefaultWillPower = 5; DefaultWisdom = 5; DefaultCharisma = 6; DefaultMana = 0; @@ -8388,8 +9210,7 @@ lobhse AttachedGod = SCABIES; FleshMaterial = SICK_SPIDER_FLESH; BloodMaterial = SICK_BLOOD; - SweatMaterial = LIQUID_DARKNESS; - Sweats = true; + VomitMaterial = LIQUID_DARKNESS; CanOpen = false; BiteCapturesBodyPart = false; AutomaticallySeen = true; @@ -8438,6 +9259,7 @@ siren DefaultEndurance = 12; DefaultPerception = 25; DefaultIntelligence = 10; + DefaultWillPower = 10; DefaultWisdom = 15; DefaultCharisma = 50; DefaultMana = 5; @@ -8467,6 +9289,7 @@ siren "\"Hello, sailor!\""; "\"I'm singing in the rain... Oh wait...\""; } + DeathMessage = "@Dd stops singing once and for all."; /* if somebody really wants to J_Kahvi has provided detailed discussion of nipple and pubic hair @@ -8521,6 +9344,7 @@ siren SkinColor = rgb16(153, 204, 255); HairColor = rgb16(51, 51, 255); TorsoMainColor = rgb16(153, 204, 255); + ClassStates = SWIMMING; } Config RED_SIREN; @@ -8528,6 +9352,7 @@ siren SkinColor = rgb16(255, 51, 51); HairColor = rgb16(255, 255, 255); TorsoMainColor = rgb16(255, 51, 51); + ClassStates = REGENERATION; } Config PINK_SIREN; @@ -8535,6 +9360,7 @@ siren SkinColor = rgb16(240, 200, 201); HairColor = rgb16(255, 51, 153); TorsoMainColor = rgb16(240, 200, 201); + ClassStates = DETECTING; } Config HISPANIC_SIREN; @@ -8547,6 +9373,7 @@ siren Config AMBASSADOR_SIREN; { DefaultIntelligence = 30; + DefaultWillPower = 30; DefaultWisdom = 35; DefaultCharisma = 75; HeadBitmapPos = 112, 400; @@ -8558,12 +9385,13 @@ siren IsUnique = true; CanBeCloned = false; IsPolymorphable = false; - TamingDifficulty = 40; + TamingDifficulty = NO_TAMING; + ClassStates = HASTE|FEARLESS; DefaultName = "Elianise"; NameSingular = "elven ambassador"; PostFix = "of the Dark Forest"; Cloak = NYMPH_HAIR cloak { Enchantment = 4; } - /* RightWielded = charmlyre; */ + RightWielded = charmlyre; FriendlyReplies = { 7, @@ -8634,8 +9462,9 @@ mindworm Config HATCHLING; { DefaultIntelligence = 20; + DefaultWillPower = 20; NameSingular = "mind worm hatchling"; - Frequency = 500; + Frequency = 100; ScienceTalkPossibility = 25; ScienceTalkIntelligenceModifier = 10; ScienceTalkWisdomModifier = 5; @@ -8646,8 +9475,9 @@ mindworm Config BOIL; { DefaultIntelligence = 35; + DefaultWillPower = 35; NameSingular = "mind worm boil"; - Frequency = 1500; + Frequency = 500; ScienceTalkPossibility = 25; ScienceTalkIntelligenceModifier = 100; ScienceTalkWisdomModifier = 50; @@ -8665,6 +9495,7 @@ punisher DefaultEndurance = 15; DefaultPerception = 21; DefaultIntelligence = 15; + DefaultWillPower = 7; DefaultWisdom = 7; DefaultCharisma = 10; DefaultMana = 0; @@ -8705,6 +9536,7 @@ punisher DefaultEndurance = 10; DefaultPerception = 24; DefaultIntelligence = 28; + DefaultWillPower = 10; DefaultWisdom = 25; DefaultCharisma = 30; Sex = FEMALE; @@ -8766,6 +9598,7 @@ child DefaultEndurance = 10; DefaultPerception = 14; DefaultIntelligence = 9; + DefaultWillPower = 5; DefaultWisdom = 5; DefaultCharisma = 8; DefaultMana = 0; @@ -8815,10 +9648,52 @@ child { 3, "\"My sister was eaten by a wolf yesterday.\"", - "\"I hope I will get a doll of the high priest on Atavus day.\"", + "\"I hope I will get a doll on Atavus day.\"", "\"Do you want to play make-believe? I will be a princess and you can be my man-servant!\""; } } + + Config KING; + { + HeadBitmapPos = 96, 415; + HairColor = rgb16(36, 36, 36); + ClothColor = rgb16(176, 0, 0); + DefaultName = "His Highness crown prince Artorius"; + NameSingular = "heir"; + PostFix = "to the throne of Aslona"; + Cloak = FABRIC cloak { Enchantment = 1; } + Belt = FABRIC belt { Enchantment = 1; } + Sex = MALE; + AttachedGod = SEGES; + BloodMaterial = BLUE_BLOOD; + TamingDifficulty = 20; + IsUnique = true; + IsNameable = false; + CanBeCloned = false; + IsPolymorphable = false; + UndeadVersions = false; + CanRead = true; + FriendlyReplies = + { + 8, + "\"I was just coming back home from Castle Noth when those ugly goblins attacked and took me here.\"", + "\"I will tell daddy to catch all the goblins and send them to the mines!\"", + "\"I will tell uncle Myrddin to burn this whole dungeon down with his magic!\"", + "\"I hope uncle Mittrars is all right. He always worries so much!\"", + "\"I hope uncle Efra and uncle Harvan are talking with each other again. When I last saw them they were arguing pretty badly.\"", + "\"I hate goblins! They are mean and stinky!\"", + "@Dd whines: \"Are we there yet?\"", + "\"I can't wait to see my daddy! I miss him so much!\""; + } + HostileReplies = + { + 3, + "\"How dare you assault a prince!\"", + "\"Guards! Guards!\"", + "@Dd screams for help."; + } + Alias == "Artorius"; + } } bum @@ -8830,6 +9705,7 @@ bum DefaultEndurance = 25; DefaultPerception = 10; DefaultIntelligence = 6; + DefaultWillPower = 18; DefaultWisdom = 6; DefaultCharisma = 2; DefaultMana = 5; @@ -8852,7 +9728,7 @@ bum PanicLevel = 0; /* too drunk */ FriendlyReplies = { - 151, /* rumors, more to come */ + 153, /* rumors, more to come */ "\"There was this guy I used to adventure with, who nearly got killed by a spider because he couldn't hit it with his halberd. Har har har, what was he thinking, right? Trying to squash the tiny spider with such a huge weapon?\"", "\"You shouldn't leave your weapons filthy! I once had this nice adamantine sword, but some blood got on it and before I knew it, the sword was completely covered in rust.\"", "\"I've heard about a belt that can make you float in the air! Now that would have been useful that one time in the caves I stepped on a mine and nearly got my leg blown off.\"", @@ -8875,7 +9751,7 @@ bum "\"Breaking your wands can have quite an effect, har har har. I've learned the hard way.\"", "\"One of my friends once told me that altars and wands of polymorph mix surprisingly well. Hm, I'll have to try for myself one day.\"", "\"My mama always told me, eat carrots and you'll see as well as a giant golden eagle.\"", - "\"Mushrooms are my favourite! They spawn so quickly you'll never run out of food! Har har har!\"", + "\"Mushrooms are my favorite! They spawn so quickly you'll never run out of food! Har har har!\"", "\"My mama always told me that fat people are not very agile and gaunt people are weak. It's best to try and stay fit, she said.\"", "\"Ommels, eh? Magnificent creatures. Some say they have bones harder than steel and teeth like adamant!\"", "\"Ommels, eh? Magnificent creatures. Some say you can even make good use of their excretions.\"", @@ -8963,6 +9839,8 @@ bum "\"I've heard you can fetch a nice iron arm from the dwarves of Kharaz-arad.\"", "\"I once tried to pet a hedgehog and got my hand all screwed up. Tried it next time with a gauntlet, and let me tell ya, it takes the sting from their spines!\"", "\"You ever thrown a bone to a doggie? I love the way it makes them happy.\"", + "\"You ever thrown a fish to a kitty? I love the way it makes them happy.\"", + "\"You ever thrown a carrot to a bunny? I love the way it makes them happy.\"", "\"You know what I've heard? That intellectuals can get harder. Har har har.\"", "\"All those priests can say so many weird and foreign words... I betcha gods love listening to wise words!\"", "\"You know how to best get a discount in a shop, eh? Use your charms, dummy! Har har!\"", @@ -8971,8 +9849,8 @@ bum "\"You came through that scary underwater tunnel, right? I've heard it's deeper than it seems. Sounds spooky, eh?\"", "\"I hate zombies! Dead things should stay dead and not try to bite you. I was bitten once, in the Cellar, and my arm got all inflamed and necrotic.\"", "\"You know how people talk about the genies locked in old oil lamps? Tavern stories and wishful legends, I tell ya!\"", - "\"One cossack once told me about an old guy named Xinroch. He lived and died long ago, but he lost his favourite sword, so he came back.\"", - "\"Some cossack once told me about a guy named Xinroch who can be at several places at once! And to top it off, he's dead! Yeah!\"", + "\"One Cossack once told me about an old guy named Xinroch. He lived and died long ago, but he lost his favorite sword, so he came back.\"", + "\"Some Cossack once told me about a guy named Xinroch who can be at several places at once! And to top it off, he's dead! Yeah!\"", "\"I've heard that only the dead can be safe around an enner beast! I say when you're dead, it's no longer of concern what's around you, eh?\"", "\"The Cathedral has impenetrable defenses with its four Cardinals scanning the thoughts of everyone, they say. Well, they still get their stuff stolen by kobolds, har har har! Those little midgets are so dumb no thoughts can be read from them! Har har!\"", "\"My mama told me you only need a wand of polymorph and some rocks to make a fortune. Eh, never held a wand in my hands, so what can I know?\"", @@ -8985,7 +9863,7 @@ bum "\"My mama always told me, don't eat people, it's indecent. I'd say more like evil, but she always knew best.\"", "\"I tried to pray once and it worked! So I said to myself, once is good, twice is better, right? Then I got smote. I guess gods don't like pestering, right?\"", "\"Some priest once told me that Law and Chaos mix like cats and dogs. That's bad, right?\"", - "\"Some priest once told me that gods can be quite jealous of their worshippers. I guess that's why thay can't stand each other?\"", + "\"Some priest once told me that gods can be quite jealous of their worshipers. I guess that's why they can't stand each other?\"", "\"When my mama took me to an altar for the first time, she made me kneel there. It makes the gods happy, she said.\"", "\"I knew a guy who had this altar in his home and kept pushing his fingers down his throat and vomiting all over it! He said it was an offering for some Scabies, but ew!\"", "\"There was this arch-magic-guy some years back running around with a wand of resurrection. Those lucky bastards he raised where so grateful he quickly amassed an army of followers! Then he attacked Attnam and got promptly massacred.\"", @@ -8993,7 +9871,7 @@ bum "\"They say necromancy sows Chaos into your heart. I say you have to already have some Chaos in you to even try using necromancy.\"", "\"Only time I ever ate a fortune cookie, it said: 'When aflame, douse yourself with water from a fountain.' Why the Dark Gods would I be aflame in the first place?\"", "\"I used to drink from the fountains, because it's free, but then I got all sick, so it's not always healthy. Now I drink vodka.\"", - "\"I've heard those fountains are connected by pipes large enough for a man to fit into! You wouldn't want to get suckud inside, right? Har har!\"", + "\"I've heard those fountains are connected by pipes large enough for a man to fit into! You wouldn't want to get sucked inside, right? Har har!\"", "\"One mage once told me that wet clothes and electricity don't mix well. Eh, it's not like I know what an electricity is, but wet clothes are uncomfortable.\"", "\"One mage once told me that metal armor and electricity don't mix well. I have neither armor, nor electricity, so I don't care.\"", "\"One mage once told me not only gods can wield thunder in their hands. I told her that's a blasphemy and she should burn at a stake.\"", @@ -9012,6 +9890,7 @@ bum "\"Bad adventurer! No more living for you!\"", "@Dd yells gibberish in a drunken rage."; } + Alias == "hobo"; } nihil @@ -9056,6 +9935,7 @@ nihil CanRead = true; UndeadVersions = false; UsesNutrition = false; + IgnoreDanger = true; AllowPlayerToChangeEquipment = false; Helmet = BLACK_DIAMOND helmet(HELM_OF_WILLPOWER) { Enchantment = 4; } Amulet = BLACK_DIAMOND amulet(AMULET_OF_LIFE_SAVING); @@ -9077,7 +9957,7 @@ nihil TotalSize = 200; Sex = FEMALE; AttachedGod = NONE; - ClassStates = TELEPORT|TELEPORT_CONTROL|HASTE|INFRA_VISION|ESP|DISEASE_IMMUNITY|GAS_IMMUNITY; + ClassStates = TELEPORT|TELEPORT_CONTROL|HASTE|INFRA_VISION|ESP|DISEASE_IMMUNITY|GAS_IMMUNITY|FEARLESS; TamingDifficulty = NO_TAMING; MoveType = FLY; StandVerb = "flying"; @@ -9093,7 +9973,8 @@ nihil "\"The point is there's no point.\""; } Inventory = { 8, celestialmonograph, NEUTRONIUM stone(SOL_STONE), wand(WAND_OF_RESURRECTION), wand(WAND_OF_CLONING), scrollofchangematerial, scrollofcharging, scrollofwishing, horn(HEALING); } - DayRequirementForGeneration = 60; + HPRequirementForGeneration = 450; + DayRequirementForGeneration = 45; } terra @@ -9105,6 +9986,7 @@ terra DefaultEndurance = 15; DefaultPerception = 28; DefaultIntelligence = 25; + DefaultWillPower = 25; DefaultWisdom = 42; DefaultCharisma = 35; DefaultMana = 55; @@ -9121,6 +10003,7 @@ terra NameSingular = "elder priestess"; PostFix = "of Silva"; Sex = FEMALE; + BloodMaterial = PLANT_SAP; BodyArmor = RATA_WOOD bodyarmor(PLATE_MAIL) { Enchantment = 3; } Cloak = RAINBOW_CLOTH cloak(CLOAK_OF_ACID_RESISTANCE) { Enchantment = 3; } Belt = RATA_WOOD belt(BELT_OF_REGENERATION) { Enchantment = 3; } @@ -9142,10 +10025,424 @@ terra "\"Hopefully one day, I will find an apprentice to take my place. How I wish to see Tweraif again!\"", "\"My vigil is long and lonely, but necessary. Lobh-se cannot be let out of her prison.\"", "\"Beware and avoid Lobh-se at all costs! She is the misbegotten daughter of Scabies, who exists only to devour any man or beast she senses.\"", - "\"Through the millenia, Lobh-se has gained every imaginable disease and was bitten by every existing poisonous creature. Now she is practically invulnerable to all damage.\"", + "\"Through the millennia, Lobh-se has gained every imaginable disease and was bitten by every existing poisonous creature. Now she is practically invulnerable to all damage.\"", "\"Fortunately, Lobh-se only leaves her lair in the heart of the night and even then does not venture far, since nutrition is plenty here, and she returns promptly when satiated. Still, it is becoming harder and harder to ward her off.\"", "\"This cave once was a magnificent garden, a bulwark against evil. But after the Attnamese invasion, the power of Silva was weakened, and darkness and strife crept in here from below.\"", "\"I am bound by sacred oaths to protect this shrine with my life. I remained behind when Tweraifians buried the entrance to hide this place from Attnam.\""; } DeathMessage = "@Dd dies, whispering: \"I'm sorry! I failed you, Silva.\""; } + +aslonawizard +{ + DefaultArmStrength = 13; + DefaultLegStrength = 13; + DefaultDexterity = 21; + DefaultAgility = 18; + DefaultEndurance = 15; + DefaultPerception = 8; + DefaultIntelligence = 35; + DefaultWillPower = 40; + DefaultWisdom = 25; + DefaultCharisma = 25; + DefaultMana = 60; + HeadBitmapPos = 112, 304; + TorsoBitmapPos = 48, 224; + ArmBitmapPos = 80, 64; + LegBitmapPos = 16, 160; + HairColor = rgb16(139, 69, 19); + EyeColor = rgb16(96, 96, 200); + CapColor = rgb16(112, 128, 144); + BeltColor = rgb16(80, 80, 80); + ClothColor = rgb16(128, 128, 128); + DefaultName = "Myrddin Wyllt"; + Adjective = "royal"; + NameSingular = "wizard"; + PostFix = "of Aslona"; + TotalVolume = 100000; + TotalSize = 175; + Helmet = SELKIE_SKIN helmet(HELM_OF_BRILLIANCE) { Enchantment = 5; } + Amulet = amulet(AMULET_OF_UNBREATHING); + BodyArmor = SELKIE_SKIN bodyarmor(PLATE_MAIL) { Enchantment = 5; } + Cloak = MERMAID_HAIR cloak(CLOAK_OF_FIRE_RESISTANCE) { Enchantment = 3; } + RightWielded = MAHOGANY_WOOD SAPPHIRE meleeweapon(QUARTER_STAFF) { Enchantment = 4; } + RightRing = ring(RING_OF_MAGIC_RESISTANCE); + LeftRing = ring(RING_OF_SEARCHING); + RightBoot = SELKIE_SKIN boot { Enchantment = 5; } + Inventory == Random { MinPrice = 150; Category = WAND|SCROLL|POTION; Times = 5; } + KnownCWeaponSkills == BLUNT_WEAPONS; + CWeaponSkillHits == 200; + RightSWeaponSkillHits = 150; + LeftSWeaponSkillHits = 150; + BloodMaterial = MAGIC_LIQUID; + AttachedGod = SOPHOS; + ClassStates = ESP; + TamingDifficulty = NO_TAMING; + PanicLevel = 5; + IsUnique = true; + IsNameable = false; + CanBeCloned = false; + IsPolymorphable = false; + CanBeGenerated = false; + UndeadVersions = false; + IsImmuneToItemTeleport = true; + AllowUnconsciousness = false; + IsExtraFragile = false; + BodyPartsDisappearWhenSevered = true; + CanRead = true; + HostileReplies = + { + 6, + "\"Abracadabra!\"", + "\"Hocus pocus!\"", + "\"Alakazam.\"", + "\"Sim sala bim!\"", + "@Dd screams: \"I shall blow thee to smithereens!\"", + "@Dd yells: \"Prepare to meet your maker!\""; + } + FriendlyReplies = + { + 8, + "\"What exactly do you think I'm capable of?\"", + "\"Aslona isn't built on magic, but on people, on their faith.\"", + "\"Remember, there's always someone cleverer than yourself.\"", + "\"I've never studied magic or been taught... I was born like this.\"", + "\"Do you know how it feels, to be a monster? To be afraid of what you can do?\"", + "\"It is my responsibility to protect the people of this kingdom, whoever they may be.\"", + "\"The love that binds us is more important than the power we wield.\"", + "\"No young man, no matter how great, can know his destiny.\""; + } + DeathMessage = "@Dd discorporates in a puff of magic."; +} + +aslonacaptain /* guard-> */ +{ + AttributeBonus = 50; + TotalVolume = 120000; + Helmet = SEA_SERPENT_SCALE helmet(FULL_HELMET) { Enchantment = 3; } + Amulet = amulet(AMULET_OF_WARDING); + BodyArmor = SEA_SERPENT_SCALE bodyarmor(PLATE_MAIL) { Enchantment = 3; } + Cloak = MERMAID_HAIR cloak(CLOAK_OF_QUICKNESS) { Enchantment = 3; } + RightWielded = OCTIRON SAPPHIRE meleeweapon(BASTARD_SWORD) { Enchantment = 3; } + LeftWielded = OCTIRON shield { Enchantment = 3; } + RightRing = ring(RING_OF_TELEPORT_CONTROL); + LeftRing = ring(RING_OF_INFRA_VISION); + RightGauntlet = SEA_SERPENT_SCALE gauntlet(GAUNTLET_OF_STRENGTH) { Enchantment = 3; } + RightBoot = SEA_SERPENT_SCALE boot(BOOT_OF_AGILITY) { Enchantment = 3; } + CWeaponSkillHits = { 2, 2000, 2000; } + RightSWeaponSkillHits = 200; + LeftSWeaponSkillHits = 200; + PanicLevel = 5; + CriticalModifier = 4; + TamingDifficulty = NO_TAMING; + BloodMaterial = BLUE_BLOOD; + IsUnique = true; + DefaultName = "Lord Mittrars"; /* Tristram */ + NameSingular = "field marshal"; + PostFix = "of Aslona"; + IsNameable = false; + CanBeCloned = false; + IsPolymorphable = false; + ClothColor = rgb16(0, 0, 139); + CanBeConfused = false; + FriendlyReplies = + { + 5, + "@Dd says with a sad nod: \"This civil war, I worry that it will be the end of us all.\"", + "@Dd sighs with a deep sadness.", + "\"This will all end in tears, I'm telling you.\"", + "\"I knew something bad was about to happen when the dolphin looked all dejected, and then prince Artorius was abducted.\"", + "\"I knew something bad was about to happen when I spilled wine on my favorite cloak, and then king Othyr died.\""; + } + HostileReplies = + { + 3, + "\"Die, you treacherous bastard!\"", + "\"I will crush you like a grape!\"", + "\"Low-blooded scum! You dare to attack a knight of Aslona?\""; + } + DeathMessage = "@Dd falls screaming: \"For Aslona!\""; + IsImmuneToItemTeleport = true; + AllowUnconsciousness = false; + DisplacePriority = 4; + UndeadVersions = false; +} + +aslonapriest /* priest-> */ +{ + DefaultArmStrength = 8; + DefaultLegStrength = 12; + DefaultDexterity = 10; + DefaultAgility = 15; + DefaultEndurance = 18; + DefaultPerception = 14; + DefaultIntelligence = 20; + DefaultWillPower = 20; + DefaultWisdom = 35; + DefaultCharisma = 20; + DefaultMana = 30; + HairColor = rgb16(204, 204, 204); + EyeColor = rgb16(0, 126, 178); + ClothColor = rgb16(39, 119, 20); + HeadBitmapPos = 96, 128; + TorsoBitmapPos = 32, 16; + ArmBitmapPos = 64, 16; + LegBitmapPos = 0, 32; + TotalVolume = 80000; + TotalSize = 175; + DefaultName = "Senex"; + PostFix = "of Seges"; + AttachedGod = SEGES; + BloodMaterial = BLUE_BLOOD; + Helmet = HEPATIZON helmet(HELM_OF_UNDERSTANDING) { Enchantment = 2; } + Amulet = amulet(AMULET_OF_WARDING); + BodyArmor = HEPATIZON bodyarmor(CHAIN_MAIL) { Enchantment = 2; } + Cloak = ELF_CLOTH cloak { Enchantment = 2; } + Belt = ELF_CLOTH belt { Enchantment = 2; } + RightWielded = HEPATIZON bansheesickle { Enchantment = 3; } + LeftWielded = holybook(SEGES); + RightGauntlet = ELF_CLOTH gauntlet { Enchantment = 2; } + RightBoot = ELF_CLOTH boot { Enchantment = 2; } + KnownCWeaponSkills == SMALL_SWORDS; + CWeaponSkillHits == 50; + RightSWeaponSkillHits = 20; + LeftSWeaponSkillHits = 20; + TamingDifficulty = NO_TAMING; + PanicLevel = 50; + IsUnique = true; + IsNameable = false; + CanBeCloned = false; + IsPolymorphable = false; + CanBeGenerated = false; + UndeadVersions = false; + FriendlyReplies = + { + 5, + "\"The people have faith in Seges, and I do my best to heal and comfort them in adversity.\"", + "\"No wonder everyone is so cranky these days. If you had to live year-round in a cold, drafty castle like this one, you'd be cranky, too.\"", + "\"Was it the Black-cloak or Lord Regent who caused the old king's death? I doubt we'll ever know for sure.\"", + "\"I've sometimes thought I'd like to make a pilgrimage to the Cathedral of Attnam, but my duties prevent me from such extensive and dangerous travel.\"", + "\"There's something strange about Lord Regent.\""; + } + Inventory = { 3, lantern, potion { Times = 2; SecondaryMaterial = CURE_ALL_LIQUID; }, Random { Category = WAND; Times = 5; } } +} + +harvan +{ + DefaultArmStrength = 30; + DefaultLegStrength = 25; + DefaultDexterity = 30; + DefaultAgility = 25; + DefaultEndurance = 30; + DefaultPerception = 30; + DefaultIntelligence = 20; + DefaultWillPower = 35; + DefaultWisdom = 20; + DefaultCharisma = 35; + DefaultMana = 13; + FireResistance = 10; + ElectricityResistance = 10; + HeadBitmapPos = 96, 112; + TorsoBitmapPos = 48, 0; + ArmBitmapPos = 80, 32; + LegBitmapPos = 0, 240; + HairColor = rgb16(6, 6, 6); /* horns */ + SkinColor = rgb16(160, 160, 160); /* arms */ + ClothColor = rgb16(211, 211, 211); + WieldedPosition = -1, -1; + DefaultName = "Harvan Black-cloak"; + NameSingular = "rebel leader"; + IsUnique = true; + IsNameable = false; + CanBeCloned = false; + IsPolymorphable = false; + CanBeConfused = false; + CanBeGenerated = false; + IsImmuneToItemTeleport = true; + IsImmuneToStickiness = true; + AllowUnconsciousness = false; + CanRead = true; + UndeadVersions = false; + Helmet = UR_STEEL helmet(FULL_HELMET) { Enchantment = 4; } + Amulet = amulet(AMULET_OF_LIFE_SAVING); + BodyArmor = UR_STEEL bodyarmor(CHAIN_MAIL); + Cloak = SOUL_STEEL cloak(CLOAK_OF_PROTECTION) { Enchantment = 10; } + Belt = UR_STEEL belt(BELT_OF_CARRYING) { Enchantment = 4; } + RightWielded = muramasa; + LeftWielded = UR_STEEL shield(SHIELD_OF_FIRE_RESISTANCE) { Enchantment = 4; } + RightRing = ring(RING_OF_INFRA_VISION); + LeftRing = ring(RING_OF_POISON_RESISTANCE); + RightGauntlet = SPIDER_SILK gauntlet(GAUNTLET_OF_DEXTERITY) { Enchantment = 4; } + RightBoot = SPIDER_SILK boot(BOOT_OF_AGILITY) { Enchantment = 4; } + KnownCWeaponSkills = { 5, LARGE_SWORDS, SHIELDS, UNARMED, KICK, BITE; } + CWeaponSkillHits = { 5, 2000, 2000, 2000, 1500, 1500; } + RightSWeaponSkillHits = 500; + LeftSWeaponSkillHits = 200; + PanicLevel = 0; + TamingDifficulty = NO_TAMING; + TotalVolume = 120000; + TotalSize = 195; + Sex = MALE; + AttachedGod = LEGIFER; + NaturalSparkleFlags = CLOTH_COLOR; + BloodMaterial = BLUE_BLOOD; + DeathMessage = "@Dd dies and the rebellion will soon follow."; + FriendlyReplies = + { + 7, + "\"We are surrounded by jackals and vultures, and now Lord Peredivall drives a new wedge between the people of Aslona.\"", + "\"The death of old king Othyr was a great blow, but greater still is the unrest that Lord Regent brings with his controversies.\"", + "\"I never wished to lead, yet here I am. I will not allow Aslona to fall apart.\"", + "\"You cannot come to an already bad situation and expect to make it better by disturbing things further, yet this is what Lord Efra seems to be doing.\"", + "\"Lord Efra used to be like a brother to me. I don't know what madness possessed him to start a civil war like this.\"", + "\"We must end this quickly and decisively. I won't let Aslona turn its back on hundreds of years of history.\"", + "\"Lord Peredivall used to be the secretary of the state of Aslona. Interesting how quickly he grabbed for more power after king Othyr's death, right?\""; + } + HostileReplies = + { + 3, + "\"So you're with him, after all.\"", + "\"I should have expected someone from Attnam to be nothing but a traitor.\"", + "\"I don!t want to kill you, but trust me that I will.\""; + } +} + +lordregent +{ + DefaultArmStrength = 24; + DefaultLegStrength = 24; + DefaultDexterity = 56; + DefaultAgility = 56; + DefaultEndurance = 24; + DefaultPerception = 36; + DefaultIntelligence = 56; + DefaultWillPower = 24; + DefaultWisdom = 36; + DefaultCharisma = 36; + DefaultMana = 7; + AcidResistance = 10; + PoisonResistance = 10; + HeadBitmapPos = 96, 368; + TorsoBitmapPos = 32, 144; + ArmBitmapPos = 80, 176; + LegBitmapPos = 0, 0; + HairColor = rgb16(200, 0, 0); /* beard */ + ClothColor = rgb16(0, 35, 102); + BeltColor = rgb16(224, 224, 0); + LegSpecialColor = rgb16(6, 6, 6); + DefaultName = "His Excellency Efra Peredivall"; /* Percival */ + NameSingular = "Lord Regent"; + PostFix = "of Aslona"; + IsUnique = true; + IsNameable = false; + CanBeCloned = false; + IsPolymorphable = false; + CanBeConfused = false; + CanBeGenerated = false; + IsImmuneToItemTeleport = true; + IsImmuneToStickiness = true; + AllowUnconsciousness = false; + CanRead = true; + UndeadVersions = false; + Helmet = HEPATIZON helmet(HELM_OF_TELEPATHY) { Enchantment = 3; } + Amulet = amulet(AMULET_OF_LIFE_SAVING); + BodyArmor = SPIDER_SILK bodyarmor(ARMOR_OF_GREAT_HEALTH) { Enchantment = 6; } + Cloak = SPIDER_SILK cloak(CLOAK_OF_FIRE_RESISTANCE) { Enchantment = 3; } + Belt = GOLD belt(BELT_OF_GIANT_STRENGTH) { Enchantment = 6; } + RightWielded = masamune; + LeftWielded = OCTIRON GOLD daggerofvenom { Enchantment = 6; } + RightRing = ring(RING_OF_ACID_RESISTANCE); + LeftRing = ring(RING_OF_ELECTRICITY_RESISTANCE); + RightGauntlet = SPIDER_SILK gauntlet(GAUNTLET_OF_STRENGTH) { Enchantment = 3; } + RightBoot = SPIDER_SILK boot(BOOT_OF_AGILITY) { Enchantment = 3; } + KnownCWeaponSkills = { 5, LARGE_SWORDS, SMALL_SWORDS, UNARMED, KICK, BITE; } + CWeaponSkillHits = { 5, 5000, 5000, 2000, 1500, 1500; } + RightSWeaponSkillHits = 500; + LeftSWeaponSkillHits = 500; + PanicLevel = 0; + TamingDifficulty = NO_TAMING; + TotalVolume = 75000; + TotalSize = 155; + Sex = MALE; + AttachedGod = SEGES; + BloodMaterial = BLUE_BLOOD; + DeathMessage = "@Dd dies and the few remaining royalists will soon scatter without his lead."; + FriendlyReplies = + { + 7, + "\"Change is never simple, but we must change or be left behind.\"", + "\"A tragedy has granted me this chance, but I will try to make sure Aslona benefits in the end.\"", + "\"Lord Harvan seems to think that ends justify the means. I will never stand for that.\"", + "\"Lord Harvan used to be like a brother to me. I don't know what madness possessed him to start a civil war like this.\"", + "\"The rebels don't understand that we *need* to change. The kingdom is rigid and crumbling under the weight of history, our traditions will hinder our very survival if we don't approach them with reason and not reverence.\"", + "\"Not only did we lost our king, but crown prince Artorius was abducted by a goblin raiding party, too!\"", + "\"Our history was forged from the blood and sweat of others, but never more. I cannot stand by and let our history thwart our future.\""; + } + HostileReplies = + { + 3, + "\"I don't know what Harvan told you, but I hope it's worth dying for.\"", + "@Dd hisses: \"I cannot let you stop me now!\"", + "\"Do you really wish to see Aslona fall?\""; + } +} + +fusanga +{ + DefaultArmStrength = 60; + DefaultAgility = 8; + DefaultEndurance = 60; + DefaultPerception = 40; + DefaultIntelligence = 40; + DefaultWisdom = 8; + DefaultWillPower = 60; + DefaultCharisma = 40; + DefaultMana = 60; + TotalVolume = 100000; + TotalSize = 300; + Adjective = "massive magical"; + NameSingular = "mushroom"; + DefaultName = "Sieni Fusanga"; + TorsoBitmapPos = 128, 128; + TorsoMainColor = rgb16(111, 74, 60); /* the cap */ + TorsoSpecialColor = rgb16(111, 74, 60); + BaseEmitation = rgb24(140, 100, 100); + IsNameable = false; + IsUnique = true; + CanBeCloned = false; + IsPolymorphable = false; + CanBeConfused = false; + CanBeGenerated = false; + IsImmuneToItemTeleport = true; + IsImmuneToStickiness = true; + AllowUnconsciousness = false; + UndeadVersions = false; + HasEyes = false; + HasHead = false; + HasALeg = false; + UsesNutrition = false; + SpillsBlood = false; + Sweats = false; + CanOpen = false; + IsRooted = true; + CanChoke = false; + FleshMaterial = MAGIC_MUSHROOM_FLESH; + AttachedGod = SCABIES; + ClassStates = ESP|TELEPORT_LOCK|GAS_IMMUNITY|REGENERATION; + /* No damage resistances, but regenerates. */ + TamingDifficulty = NO_TAMING; + PanicLevel = 0; + BaseUnarmedStrength = 600; + KnownCWeaponSkills == UNARMED; + CWeaponSkillHits == 150; + StandVerb = "glowering"; + DeathMessage = "@Dd is finally squashed."; + HostileReplies = + { + 2, + "@Dd radiates loathing and contempt.", + "@Dd radiates danger and majesty."; + } + FriendlyReplies == "@Dd wobbles disappointedly due to the fact that you are cheating."; + Alias == "Fusanga"; +} diff --git a/Script/define.dat b/Script/define.dat index b1461a3dd..ffb092721 100644 --- a/Script/define.dat +++ b/Script/define.dat @@ -153,16 +153,18 @@ #define NEUTRAL 2 #define EVIL 3 -#define CT_FRUIT 1 -#define CT_MEAT 2 -#define CT_METAL 4 -#define CT_MINERAL 8 -#define CT_LIQUID 16 -#define CT_BONE 32 -#define CT_PROCESSED 64 -#define CT_MISC_ORGANIC 128 -#define CT_PLASTIC 256 -#define CT_GAS 512 +#define CT_FRUIT (1 << 0) +#define CT_MEAT (1 << 1) +#define CT_METAL (1 << 2) +#define CT_MINERAL (1 << 3) +#define CT_LIQUID (1 << 4) +#define CT_BONE (1 << 5) +#define CT_PROCESSED (1 << 6) +#define CT_MISC_PLANT (1 << 7) +#define CT_MISC_ANIMAL (1 << 8) +#define CT_PLASTIC (1 << 9) +#define CT_GAS (1 << 10) +#define CT_MAGIC (1 << 11) #define LEFT 0 #define DOWN 1 @@ -230,6 +232,13 @@ #define EFFECT_TRAIN_WISDOM 36 #define EFFECT_REGENERATION 37 #define EFFECT_TELEPORTATION 38 +#define EFFECT_LAUGH 39 +#define EFFECT_POLYJUICE 40 +#define EFFECT_PUKE 41 +#define EFFECT_SICKNESS 42 +#define EFFECT_PHASE 43 +#define EFFECT_ACID_GAS 44 +#define EFFECT_FIRE_GAS 45 /* CEM = Consume End Message */ @@ -497,6 +506,8 @@ #define PYRITE (SOLID_ID + 229) #define HARDENED_ASH (SOLID_ID + 230) #define PSYPHER (SOLID_ID + 231) +#define CRYSTEEL (SOLID_ID + 232) +#define FLAWLESS_CRYSTEEL (SOLID_ID + 233) #define ORGANIC_ID (4096 * 2) @@ -544,6 +555,9 @@ #define MUSTARD_GAS (GAS_ID + 11) #define SLEEPING_GAS (GAS_ID + 12) #define TELEPORT_GAS (GAS_ID + 13) +#define LAUGHING_GAS (GAS_ID + 14) +#define ACID_GAS (GAS_ID + 15) +#define FIRE_GAS (GAS_ID + 16) #define LIQUID_ID (4096 * 4) @@ -603,6 +617,10 @@ #define QUICK_SAND (LIQUID_ID + 54) #define TELEPORT_FLUID (LIQUID_ID + 55) #define VINEGAR (LIQUID_ID + 56) +#define BLACK_BLOOD (LIQUID_ID + 57) +#define POLYMORPHINE (LIQUID_ID + 58) +#define LAVA (LIQUID_ID + 59) +#define NAPALM (LIQUID_ID + 60) #define FLESH_ID (4096 * 5) @@ -747,12 +765,12 @@ #define QUARTER_STAFF 15 #define HAMMER 16 #define GRAND_STOLLEN_KNIFE 17 -#define MAGE_STAFF 18 #define ROLLING_PIN 19 #define FRYING_PAN 20 #define CLAW 21 #define MEAT_CLEAVER 22 #define RUNE_SWORD 23 +#define KATANA 24 #define PUPPY_SKULL 1 @@ -760,6 +778,8 @@ #define GOROVITS_SICKLE 2 #define GOROVITS_SCIMITAR 3 +#define ROYAL_STAFF 1 + #define CHAIN_MAIL 1 #define PLATE_MAIL 2 #define ARMOR_OF_GREAT_HEALTH 3 @@ -818,6 +838,7 @@ #define BOOT_OF_STRENGTH 1 #define BOOT_OF_AGILITY 2 #define BOOT_OF_KICKING 3 +#define BOOT_OF_DISPLACEMENT 4 #define GAUNTLET_OF_STRENGTH 1 #define GAUNTLET_OF_DEXTERITY 2 @@ -913,6 +934,9 @@ #define ROOKIE_FEMALE 14 #define VETERAN_FEMALE 15 #define ELITE_FEMALE 16 +#define CASTLE 17 +#define ROYAL 18 +#define REBEL 19 #define DARK 1 #define GREATER_DARK 2 @@ -933,12 +957,18 @@ #define CONICAL 1 #define FLAT 2 +#define MAGMA 3 +#define BLOAT 4 + +#define DIRE 1 #define SKELETON_DOG 1 #define LARGE 1 #define GIANT 2 -#define ARANEA 3 +#define PHASE 3 +#define GIANT_GOLD 4 +#define ARANEA 5 #define IMPRISONED_HUNTER 1 @@ -975,6 +1005,10 @@ #define GREATER 1 #define GIANT 2 +#define RED_SNAKE 1 +#define GREEN_SNAKE 2 +#define BLUE_SNAKE 3 + #define SLAUGHTERER 1 #define SQUAD_LEADER 2 #define OFFICER 3 @@ -982,6 +1016,9 @@ #define MARSHAL 5 #define REPRESENTATIVE 6 +#define REBEL_SOLDIER 1 +#define REBEL_LIEUTENANT 2 + #define VICE_ROY 1 #define MASTER_TORTURER 2 #define HOARD_MASTER 3 @@ -1038,6 +1075,7 @@ #define POOL 1 #define UNDERGROUND_LAKE 2 +#define SEA 3 #define BRICK_FINE 1 #define BRICK_PROPAGANDA 2 @@ -1047,6 +1085,7 @@ #define STONE_WALL 6 #define ICE_WALL 7 #define BROKEN_WALL 8 +#define TENT_WALL 9 #define PINE 1 #define FIR 2 @@ -1107,6 +1146,7 @@ #define BARDOOR 1 #define SECRET_DOOR 2 +#define CURTAIN 3 #define WORLD_MAP 255 @@ -1118,6 +1158,7 @@ #define MONSTER_TEAM 1 #define ATTNAM_TEAM 2 #define SUMO_TEAM 3 +#define ANGEL_TEAM 4 #define GUILD_TEAM 5 #define IVAN_TEAM 6 #define NEW_ATTNAM_TEAM 7 @@ -1130,6 +1171,8 @@ #define XINROCH_TOMB_KAMIKAZE_DWARF_TEAM 14 #define PRISONER_TEAM 15 #define TERRA_TEAM 16 +#define ASLONA_TEAM 17 +#define REBEL_TEAM 18 #define NOT_WALKABLE 1 #define HAS_CHARACTER 2 @@ -1146,6 +1189,15 @@ #define EMPTY_AREA 5 #define XINROCH_TOMB 6 #define BLACK_MARKET 7 +#define ASLONA_CASTLE 8 +#define REBEL_CAMP 9 +#define GOBLIN_FORT 10 +#define FUNGAL_CAVE 11 +#define PYRAMID 12 +#define MONDEDR 13 +#define IRINOX 14 +#define DARK_FOREST 15 + #define UNDER_WATER_TUNNEL_EXIT 128 #define ALL_DUNGEONS 32767 @@ -1163,6 +1215,10 @@ #define DUAL_ENNER_BEAST_LEVEL 5 #define NECRO_CHAMBER_LEVEL 6 +#define FUSANGA_LEVEL 3 + +#define KING_LEVEL 5 + #define RECTANGLE 1 #define ROUND_CORNERS 2 #define MAZE_ROOM 3 @@ -1193,6 +1249,7 @@ #define ROOM_LIBRARY 4 #define ROOM_BANANA_DROP_AREA 5 #define ROOM_SUMO_ARENA 6 +#define ROOM_OWNED_AREA 7 #define BEAM_POLYMORPH 0 #define BEAM_STRIKE 1 diff --git a/Script/dungeon.dat b/Script/dungeon.dat index 7902d5f91..073516ff7 100644 --- a/Script/dungeon.dat +++ b/Script/dungeon.dat @@ -21,7 +21,7 @@ /* Team data for the game */ -Teams = 17; +Teams = 19; /* * Description of hard-coded teams: @@ -42,6 +42,8 @@ Teams = 17; * 14 == XINROCH_TOMB_KAMIKAZE_DWARF_TEAM == The fanatics in the gas chambers. * 15 == PRISONER_TEAM == Prisoners in the Cathedral Cellar. * 16 == TERRA_TEAM == Terra in the Crystal Cave. + * 17 == ASLONA_TEAM == People loyal to Aslona. + * 18 == REBEL_TEAM == The rebels. */ Team ATTNAM_TEAM; @@ -55,13 +57,13 @@ Team SUMO_TEAM; Relation 0, HOSTILE; } -Team 4; /* Spawned hostile angels */ +Team ANGEL_TEAM; { Relation 0, HOSTILE; Relation 1, UNCARING; } -Team 5; /* Dungeon shopkeepers */ +Team GUILD_TEAM; { Relation 1, UNCARING; KillEvilness = 100; @@ -121,6 +123,14 @@ Team XINROCH_TOMB_KAMIKAZE_DWARF_TEAM; KillEvilness = 100; } +Team PRISONER_TEAM; +{ + Relation 1, UNCARING; + /*Relation 17, FRIEND; + Relation 18, FRIEND;*/ + KillEvilness = 100; +} + Team TERRA_TEAM; { Relation 1, UNCARING; @@ -129,6 +139,19 @@ Team TERRA_TEAM; KillEvilness = 150; } +Team ASLONA_TEAM; +{ + Relation 0, UNCARING; + Relation 18, HOSTILE; + KillEvilness = 50; +} + +Team REBEL_TEAM; +{ + Relation 17, HOSTILE; + KillEvilness = 50; +} + /* Dungeons to be included in the game */ Include "dungeons/"; diff --git a/Script/dungeons/AslonaCastle.dat b/Script/dungeons/AslonaCastle.dat new file mode 100644 index 000000000..2d1709295 --- /dev/null +++ b/Script/dungeons/AslonaCastle.dat @@ -0,0 +1,681 @@ +/* + * + * Iter Vehemens ad Necem (IVAN) + * Copyright (C) Timo Kiviluoto + * Released under the GNU General + * Public License + * + * See LICENSING which should be included + * along with this file for more details + * + */ + +/* + * NOTICE!!! + * + * This file contains SPOILERS, which might ruin your IVAN experience + * totally. Also, editing anything can DESTROY GAME BALANCE or CAUSE + * OBSCURE BUGS if you don't know what you're doing. So from here on, + * proceed at your own risk! + */ + +Dungeon ASLONA_CASTLE; +{ + Levels = 4; + Description = "castle dungeon"; + ShortDescription = "CD"; + + LevelDefault + { + Size = 42, 26; + GenerateMonsters = false; + Rooms = 15; + Items = 0; + IsOnGround = false; + TeamDefault = MONSTER_TEAM; + LOSModifier = 16; + IgnoreDefaultSpecialSquares = false; + CanGenerateBone = true; + EnchantmentMinusChanceBase = 0; + EnchantmentMinusChanceDelta = 0; + EnchantmentPlusChanceBase = 5; + EnchantmentPlusChanceDelta = 5; + BackGroundType = BLUE_FRACTAL; + FillSquare = CALCITE solidterrain(GROUND), LIME_STONE earth; + TunnelSquare = CALCITE solidterrain(GROUND), 0; + DifficultyBase = 70; + DifficultyDelta = 10; + MonsterAmountBase = 5; + MonsterAmountDelta = 5; + MonsterGenerationIntervalBase = 150; + MonsterGenerationIntervalDelta = -40; + EarthquakesAffectTunnels = true; + AudioPlayList = + { + 1, + "Empty.mid"; + } + + RoomDefault + { + Pos = 2:XSize-5,2:YSize-5; + FloorSquare = CALCITE solidterrain(GROUND), 0; + DoorSquare = CALCITE solidterrain(GROUND), 0; + + Size = 3,3; + AltarPossible = false; + GenerateDoor = true; + DivineMaster = 0; + GenerateTunnel = true; + GenerateLanterns = false; + Type = ROOM_NORMAL; + GenerateFountains = false; + AllowLockedDoors = false; + AllowBoobyTrappedDoors = false; + Shape = RECTANGLE; + IsInside = true; + GenerateWindows = false; + UseFillSquareWalls = true; + Flags = 0; + } + + Square, Random NOT_IN_ROOM|ATTACHABLE; + { + OTerrain = stairs(STAIRS_UP); + EntryIndex = STAIRS_UP; + AttachRequired = true; + } + + Square, Random NOT_IN_ROOM|ATTACHABLE; + { + OTerrain = stairs(STAIRS_DOWN); + EntryIndex = STAIRS_DOWN; + AttachRequired = true; + } + } + + Level 0; + { + Description = "Castle of Aslona"; + ShortDescription = "Castle"; + FillSquare = liquidterrain(SEA), 0; + Size = 54, 54; + GenerateMonsters = false; + Rooms = 1; + Items = 0; + IsOnGround = true; + TeamDefault = ASLONA_TEAM; + LOSModifier = 48; + IgnoreDefaultSpecialSquares = true; + CanGenerateBone = false; + + RoomDefault + { + GenerateTunnel = false; + IsInside = false; + UseFillSquareWalls = true; + } + + /* The Castle */ + Room + { + Pos = 2,2; + Size = 50,50; + DivineMaster = SEGES; + Type = ROOM_OWNED_AREA; + + GTerrainMap + { + Pos = 0,0; + Size = 49,50; + Types + { + # = BASALT solidterrain(GROUND) { IsInside = true; } + ; = MARBLE solidterrain(FLOOR) { IsInside = true; } + 1 = CORAL solidterrain(FLOOR) { IsInside = true; } + 2 = NACRE solidterrain(FLOOR) { IsInside = true; } + _ = CYPRESS_WOOD solidterrain(PARQUET) { IsInside = true; } + * = CYPRESS_WOOD solidterrain(PARQUET); + - = liquidterrain(POOL) { IsInside = true; } + + = solidterrain(GROUND); + , = solidterrain(GRASS_TERRAIN); + ~ = liquidterrain(SEA); + } + } + { + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,~~~~~~~ + ~~~~~~,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,~~~~~~ + ~~~~~,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,~~~~~ + ~~~~,############,,,,,,,,,,,,,,,############,~~~~ + ~~~,,#______#___#,,,,,,,,,,,,,,,#;;;#;;;;;;#,~~~~ + ~~~,~#______#___#,,,,,,,,,,,,,,,#;;;#;;;;;;#,,~~~ + ~~~~~#______#___#################;;;#;;;;;;#,,~~~ + ~~~~,#______#;###_______________#;;;#;;;;;;#,,,~~ + ~~~~,#_---______#_______________#;;;######;#,,,~~ + ~~~,,#_---______#################;;;;;;;;;;#,,,~~ + ~~~,,#_---______#121212121212121#;;;;;;;;;;#,,,~~ + ~~~,,#__________#212121212121212#;;;;;;;;;;#,,,~~ + ~~~,,########;###121212121212121#####;######,,,~~ + ~~,,,,,,#;;;;;;;#212121212121212#;;;;;;#,,,,,,,~~ + ~~,,,,,,#;;;;;;;#121212121212121#;;;;;;#,,,,,,,~~ + ~~,,,,,,#;;;;;;;;212121212121212;;;;;;;#,,,,,,,~~ + ~~,,,,,,#;;;;;;;#121212121212121#;;;;;;#,,,,,,,~~ + ~~,,,,,,##;;############;############;##,,,,,,,~~ + ~~,,,,,,#;;;;#,,,,,,,##,+,##,,,,,,,#;;;#,,,,,,,~~ + ~~,,,,,,#;;;;#,,,,,,,#,,+,,#,,,,,,,#;;;#,,,,,,,~~ + ~,,,,,,,#;;;;#,,,,,,,,,,+,,,,,,,,,,#;;;#,,,,,,,~~ + ~,,,,,,,#;;;;#,,,,,,##,,+,,##,,,,,,#;;;#,#######~ + ~,,,,,,,#;;;;#,,,,,,##;;;;;##,,,,,,#;;;#,#_____#~ + ~,,,,,,,#;;;;#,,,,,,,;;---;;,,,,,,,#;;;#,#_____#~ + ~,,,,,,,#;;;;#,,,,,,,;-----;,,,,,,,#;;;#,#_____#~ + ~,,,,,,,#;;;;#,,,,,,,;-----;+++++++;;;;#,#_____#~ + ~,,,,,,,##;;##,,,,,,,;;---;;,,,,,,,##;##,#_____#~ + ~,,,,,,,#++++,,,,,,,##;;;;;##,,,,,,#;;;#,###;###~ + ~~,,,,,,#++++,,,,,,,##,+++,##,,,,,,#;;;#,,,,,,,~~ + ~~,,,#####;#####,,,,,,,+++,,,,,,,###########,,,~~ + ~~~,,#___#;#___;+++++++++++++++++;;;;;;;;;;#,,,~~ + ~~~,,#___;;;___###;####;;;####;###;#;;;;;#;#,,,~~ + ~~~,,#####;#___#;;;;;;#;;;#;;;;;;#;;;;;;;;;#,,,~~ + ~~~,,#___;;#####;;;;;;;;;;;;;;;;;#;#;;;;;#;#,,~~~ + ~~~,,#___#;#___#;;;;;;#;;;#;;;;;;#;;;;;;;;;#,,~~~ + ~~~,,#####;;___#;;;;;;#;;;#;;;;;;#;#;;;;;#;#,,,~~ + ~~~,,#___#;############;;;########;;;;;;;;;#,,~~~ + ~~~,,#___#;#___#,,,,,,,+++,,,,,,,#;#;;;;;#;#~~~~~ + ~~~,,#___;;;___#,,,,,,,+++,,,,,,,#;;;;;;;;;#~~~~~ + ~~~,,###########,,,,,,,+++,,,,,,,###########~~~~~ + ~~~,,,,,,,,,,,,,,,,,,,,+++,,,,,,,,,,,~~~~~~~~~~~~ + ~~~~~,,,,,,,,,,,,,,,,,,***,,,,,,~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~***~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~***~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~***~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~***~~~~~~~~~~~~~~~~~~~~~~~ + } + + OTerrainMap + { + Pos = 0,0; + Size = 49,50; + Types + { + ~ = 0; + , = 0; + # = BASALT wall(BRICK_FINE); + o = BASALT wall(BRICK_FINE|WINDOW); + T = throne; + | = decoration(CARPET); + A = SAPPHIRE altar(SEGES); + + = MAHOGANY_WOOD door(HEXAGONAL_LOCK); + L = MAHOGANY_WOOD door(HEXAGONAL_LOCK) { Parameters = LOCKED; } + S = BASALT door(SECRET_DOOR) { Parameters = LOCKED; } + z = BASALT door(SECRET_DOOR); + & = decoration(DWARF_BIRCH); + 8 = decoration(BIRCH); + 9 = decoration(TEAK); + ; = boulder(1); + : = boulder(2); + 1 = decoration(POOL_CORNER) { VisualEffects = NONE; } + 2 = decoration(POOL_CORNER) { VisualEffects = MIRROR; } + 3 = decoration(POOL_CORNER) { VisualEffects = MIRROR | FLIP; } + 4 = decoration(POOL_CORNER) { VisualEffects = FLIP; } + 5 = decoration(POOL_BORDER) { VisualEffects = NONE; } + 6 = decoration(POOL_BORDER) { VisualEffects = ROTATE; } + 7 = decoration(POOL_BORDER) { VisualEffects = FLIP; } + 0 = decoration(POOL_BORDER) { VisualEffects = MIRROR | ROTATE; } + s = decoration(COUCH); + f = fountain; + d = decoration(DOUBLE_BED); + b = decoration(PLAIN_BED); + B = SILVER boulder(1); + - = decoration(BENCH); + _ = decoration(DESK); + h = decoration(CHAIR); + a = decoration(ARM_CHAIR); + = = decoration(EXPENSIVE_BED); + [ = decoration(OVEN); + O = decoration(TABLE); + n = decoration(ANVIL); + v = decoration(WELL); + P = BLACK_GRANITE decoration(PEDESTAL); + F = decoration(FORGE); + w = decoration(WORK_BENCH); + x = olterraincontainer(CHEST_OF_DRAWERS) { ItemsInside = { 2, Random { MinPrice = 100; Category = CLOAK|BODY_ARMOR|BELT|BOOT|GAUNTLET; ConfigFlags = NO_BROKEN; }, Random { MinPrice = 100; Category = CLOAK|BODY_ARMOR|BELT|BOOT|GAUNTLET; ConfigFlags = NO_BROKEN; } } } + X = olterraincontainer(BOOK_CASE) { ItemsInside = { 2, Random { MaxPrice = 500; Category = SCROLL|BOOK; }, Random { MaxPrice = 500; Category = SCROLL|BOOK; } } } + % = DEEP_BRONZE barwall; + D = DEEP_BRONZE door(BARDOOR); + H = olterraincontainer(SHELF) + { + ItemsInside = { 4, can { Times = 0:3; }, loaf { Times = 2:10; }, fish(DEAD_FISH) { Times = 5:15; }, carrot { Times = 0:3; } } + } + W = olterraincontainer(SHELF) + { + ItemsInside = { 3, potion { Times = 0:3; SecondaryMaterial = CIDER; }, potion { Times = 0:3; SecondaryMaterial = WHITE_WINE; }, potion { Times = 0:3; SecondaryMaterial = RED_WINE; } } + } + > = stairs(STAIRS_DOWN); + } + } + { + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~,,8,,,,,,&,,8,,,,,,,,,,,,,,8,,,&,,~~~~~~~ + ~~~~~~,8,,,,;,,,8,,,,,,,,,8,,,,,8,,,,,,,,8,~~~~~~ + ~~~~~,,,,,8,,,,,,,,,,8,,,,,,,,8,,,,:,,8,,,,,~~~~~ + ~~~~,#########o##,,,,,,,v,,,,,,,##o#########,~~~~ + ~~~,,#XXXX.x#...#,,8,,,,,,,8,,,,#...#HHHHHH#,~~~~ + ~~~,~#X.....#.d.o,,,,;8,,,,,,,8,z.h.#H....O#8,~~~ + ~~~~~#X.a...#...#################.O.#[....O#,,~~~ + ~~~~,o......#+###.....B.D.D.....#.h.#[.....#;,,~~ + ~~~~,#.152.....s#.B.....%.%B....#...######+#,8,~~ + ~~~,&#.0~6......#########S#######..h....h..#,,,~~ + ~~~,,#.473....a_#.......T.......#.hOh..hOh.o,,,~~ + ~~~,,#..........#.......|.......#..h....h..#,,8~~ + ~~~,,########L###.......|.......#####+###o##,,,~~ + ~~,;,,,,#.s....s#.......|.......#......#,,,,8,,~~ + ~~,,,,8,#.......#.......|.......#..PP..#,:,,,,&~~ + ~~8,,,,,#s......+.......|.......+..PP..#,,,,,,,~~ + ~~,,&,,,#....s..#.......|.......#......#,,8,,,,~~ + ~~,,,,8,##++##o#####o###+###o#####o##+##,,,,,8,~~ + ~~,,,,,,#....#,,-,-,,##,.,##,,-,-,,#...#,,&,,,,~~ + ~~,,,8,,#....#,,,,,,,#,,.,,#,,,,,,,o..-#8,,,,,,~~ + ~,,8,,,,#....o,,,f,,,,,,.,,,,,,f,,,#...#,,,,,,8~~ + ~,,,,,,,#....#,,,,,,##,,.,,##,,,,,,o..-#,###o###~ + ~8,,,,,8#....#,9,,9,##.....##,9,,9,#...#,#XX_XX#~ + ~,,,&,,,#....o,,,,,,,..~~~..,,,,,,,o..-#,#X.a.X#~ + ~,,,,;,,#....#,9,,9,,.~~~~~.,,9,,9,#...#,ox...=o~ + ~,8,,,,,#....#,,,,,,,.~~~~~........+...#8#X...X#~ + ~,,,,,,,##++##,9,,9,,..~~~..,,9,,9,##L##,#XX.XX#~ + ~,,8,,8,#....,,,,,,,##.....##,,,,,,o..W#,###+###~ + ~~,,,,,,#....,,,,,,,##,...,##,,,,,,#>.W#,,,,,,,~~ + ~~&,,#####+##o##,,,,,,,...,,,,,,,###########,&,~~ + ~~~,,#b..#.#hw.+.................+.........S,,,~~ + ~~~,,oh..+.+...###+####%%%####+###.#--|--#.#,,,~~ + ~~~,,#####.#n.F#b.....#...#.....b#....|....#,,;~~ + ~~~,:#...L.#####......+...L......#.#--|--#.#,,~~~ + ~~~,,o.b.#.#x.b#b.....#...#.....b#....|....#,,~~~ + ~~~,,#####.+...#.b.b.b#...#b.b.b.#.#--|--#.#,,8~~ + ~~~,,#X.a#.########o###%D%###o####....|....#:,~~~ + ~~~,,o=._#.#..b#,,,,,,,...,,,,,,,#.#..A..#.#~~~~~ + ~~~,,#x..+.L..O#,,,;,,,...,,,,,&,#.........#~~~~~ + ~~~,8#####o#####,,,,,,,...,,,,,,,###o#o#o###~~~~~ + ~~~,,,,,,,,,,,,,,,,,&,,...,:,,,,,,8,,~~~~~~~~~~~~ + ~~~~~,,,,,,,,8,,,,,,,,,...,,,,,,~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~...~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~...~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~...~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~...~~~~~~~~~~~~~~~~~~~~~~~ + } + + ItemMap + { + Pos = 0,0; + Size = 49,50; + Types + { + # == 0; + ~ == 0; + , == 0; + + == 0; + e == backpack; + g == SUN_CRYSTAL stone(SOL_STONE) { Times = 1:3; } + L == oillamp; + 5 == PLATINUM stone; + 6 == EMERALD stone; + 7 == WHITE_JADE stone; + 8 == BLACK_DIAMOND stone; + 1 == lantern { SquarePosition = UP; } + 2 == lantern { SquarePosition = DOWN; } + 3 == lantern { SquarePosition = RIGHT; } + 4 == lantern { SquarePosition = LEFT; } + Y == MITHRIL itemcontainer(LARGE_CHEST) { Parameters = LOCKED; ItemsInside == Random { Times = 3:7; MinPrice = 1250; } } + y == MITHRIL itemcontainer(SMALL_CHEST) { Parameters = LOCKED; ItemsInside = { 2, stone { Times = 3:15; }, fiftymillionroubles; } } + C == cauldron { SecondaryMaterial = CHICKEN_SOUP; } + a == PEARL amulet(AMULET_OF_WARDING); + A == SLADE amulet(AMULET_OF_SPEED); + b == HEPATIZON ring(RING_OF_POLYMORPH_LOCK); + B == HEPATIZON ring(RING_OF_BRAVERY); + c == VACUUM bodyarmor(ARMOR_OF_GREAT_HEALTH) { Enchantment = 3; } + E == PSYPHER EBONY_WOOD rustscythe; + d == PRIMORDIAL_ICE helmet(HELM_OF_TELEPORTATION); + D == PETRIFIED_DARK helmet(HELM_OF_MANA) { Enchantment = 7; } + f == SEA_SERPENT_SCALE belt(BELT_OF_REGENERATION); + F == CORAL wand(WAND_OF_ALCHEMY); + G == trinket(LARGE_CLOCK); + i == trinket(POTTED_PLANT); + I == trinket(POTTED_CACTUS); + h == itemcontainer(SMALL_CHEST) { Parameters = LOCKED; ItemsInside == Random { Category = CLOAK|BODY_ARMOR|BELT|BOOT|GAUNTLET; ConfigFlags = NO_BROKEN; } } + H == itemcontainer(LARGE_CHEST) { ItemsInside = { 4, can { Times = 0:3; }, loaf { Times = 2:10; }, sausage { Times = 0:10; }, fish(DEAD_FISH) { Times = 0:3; } } } + j == trinket(SMALL_CLOCK); + J == OCTIRON SAPPHIRE wondersmellstaff { Enchantment = 2; } + k == EMERALD BRASS sharpaxe { Enchantment = 2; } + K == SHADOW_CLOTH SAPPHIRE chameleonwhip { Enchantment = 2; } + l == DIAMOND SIDGURE_WOOD taiaha { Enchantment = 2; } + } + } + { + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,~~~~~~~ + ~~~~~~,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,~~~~~~ + ~~~~~,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,~~~~~ + ~~~~,#####2######,,,,,,,,,,,,,,,############,~~~~ + ~~~,,#......#...#,,,,,,,,,,,,,,,#...#......#,~~~~ + ~~~,~#.....h3...#,,,,,,,,,,,,,,,+...#......#,,~~~ + ~~~~~#.....h#...#########2#######...4...C..#,,~~~ + ~~~~,#......#.###gBc65+a+.+dY5fA#...#..H...#,,,~~ + ~~~~,#.~~~......#E+7bF5y+.++6D8L#...##2#1#+#,,,~~ + ~~~,,#.~~~......4#2###2##+2###2##..........#,,,~~ + ~~~,,3.~~~......#...............#..........#,,,~~ + ~~~,,#.i.......G#...............#..........#,,,~~ + ~~~,,######2#+##3...............4##2#+######,,,~~ + ~~,,,,,,#.......#...............#......#,,,,,,,~~ + ~~,,,,,,#.......3...............4..Jk..#,,,,,,,~~ + ~~,,,,,,#.......+...............+..Kl..4,,,,,,,~~ + ~~,,,,,,3.......#...............#......#,,,,,,,~~ + ~~,,,,,,##++2#####1###1#+#1###1######+##,,,,,,,~~ + ~~,,,,,,#...e#,,,,,,,##,.,##,,,,,,,#..i#,,,,,,,~~ + ~~,,,,,,#e...#,,,,,,,#,,.,,#,,,,,,,#...#,,,,,,,~~ + ~,,,,,,,#...e#,,,,,,,,,,.,,,,,,,,,,#..I4,,,,,,,~~ + ~,,,,,,,3e...#,,,,,,##,,.,,##,,,,,,#...#,#######~ + ~,,,,,,,#...e#,,,,,,##.....##,,,,,,#..i#,#.....#~ + ~,,,,,,,#e...#,,,,,,,..~~~..,,,,,,,#...#,#.....#~ + ~,,,,,,,#...e#,,,,,,,.~~~~~.,,,,,,,#..I4,#.....#~ + ~,,,,,,,#e...#,,,,,,,.~~~~~........+..G#,#.....#~ + ~,,,,,,,#1++#2,,,,,,,..~~~..,,,,,,,##+##,#.....#~ + ~,,,,,,,#....,,,,,,,##.....##,,,,,,#...#,###+###~ + ~~,,,,,,#....,,,,,,,##,...,##,,,,,,#...#,,,,,,,~~ + ~~,,,##2##+#####,,,,,,,...,,,,,,,#####2#####,,,~~ + ~~~,,#..h#.#...+.................+.........#,,,~~ + ~~~,,#...+.+...###+#2##+++##2#+###.#.....#.#,,,~~ + ~~~,,##2##.#...#......#...#......3.........4,,,~~ + ~~~,,#j..+.41##3h.....+...+.....h4.#.....#.#,,~~~ + ~~~,,#..h#.#.h.#......3...4......#.........#,,~~~ + ~~~,,####3.+..i#h.h.h.#...#.h.h.h#.#.....#.#,,,~~ + ~~~,,#...#.##1#########+++#######3.........4,,~~~ + ~~~,,#...#.#h..#,,,,,,,...,,,,,,,#.#.....#.#~~~~~ + ~~~,,#...+.+...#,,,,,,,...,,,,,,,#.........#~~~~~ + ~~~,,##1#####1##,,,,,,,...,,,,,,,###########~~~~~ + ~~~,,,,,,,,,,,,,,,,,,,,...,,,,,,,,,,,~~~~~~~~~~~~ + ~~~~~,,,,,,,,,,,,,,,,,,...,,,,,,~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~...~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~...~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~...~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~...~~~~~~~~~~~~~~~~~~~~~~~ + } + + CharacterMap + { + Pos = 0,0; + Size = 49,50; + Types + { + # = 0; + ~ = 0; + , = 0; + + = 0; + P = lordregent { Flags = IS_MASTER; } + g = guard(CASTLE); + G = guard(ROYAL); + C = aslonacaptain; + s = smith(ASLONA_CASTLE); + t = tailor(ASLONA_CASTLE); + W = aslonawizard; + p = aslonapriest; + D = dolphin(ADULT_MALE); + F = mysticfrog(LIGHT); + j = femaleslave(JESTER); + m = housewife(ASLONA_CASTLE); + k = kamikazedwarf(SEGES); + K = veterankamikazedwarf(SEGES); + } + } + { + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,~~~~~~~ + ~~~~~~,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,~~~~~~ + ~~~~~,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,~~~~~ + ~~~~,############,,,,,,,,,,,,,,,############,~~~~ + ~~~g,#......#...#,,,,,,,,,,,,,,,#...#......#,~~~~ + ~~~,~#......#...#,,,,,,,,,,,,,,,+...#......#,,~~~ + ~~~~~#......#...#################...#......#,,~~~ + ~~~~,#......#.###...............#...#......#,,,~~ + ~~~~,#.~~~......#...............#...######+#,,,~~ + ~~~,,#.~~~......#########+#######....m.....#,,,~~ + ~~~,,#.~~~......#G..G..G.G..G..G#.g........#,,,~~ + ~~~,,#..........#...............#..........#,,,~~ + ~~~,,########+###.........P.....#####+######,,,~~ + ~~,,,,,,#...G.G.#G.............G#......#,,,,,,,~~ + ~~,,,,,,#.......#...............#.m....#,,,,,,,~~ + ~~,,,,,,#....m..+...............+......#,,,,,,,~~ + ~~,,,,,,#.......#G..G..G.G..G..G#......#,,,,,,,~~ + ~~,,,,,,##++############+############+##,,,,,,,~~ + ~~,,,,,,#k...#,,,,,,,##g.g##,,,,j,,#...#,,,,,,,~~ + ~~,,,,,,#...k#,,,,,,,#g,.,g#,,,,,,,#...#,,,,,,,~~ + ~,,,,,,,#k...#,,,,,,,,,,.,,,,,,,,,,#.m.#,,,,,,,~~ + ~,,,,,,,#...k#,,,,,,##,,.,,##,,,,,,#...#,#######~ + ~,,,,,,,#k...#,,,,,,##.....##,,,,,,#...#,#.....#~ + ~,,,,,,,#...k#,,,,,,,..~~~..,,,,,,,#.m.#,#..W..#~ + ~,,,,,,,#k...#,,,,,,,.~~D~~.,,,,,,,#...#,#.....#~ + ~,,,,,,,#...k#,,,,,,,.~~F~~........+...#,#.....#~ + ~,,,,,,,##++##,,,,,,,..~~~..,,,,,,,##+##,#.....#~ + ~,,,,,,,#g...,,,,,,,##.....##,,,,,,#...#,###+###~ + ~~,,,,,,#g...,,,,,,,##,...,##,,,,,,#...#,,,,,,,~~ + ~~,,,#####+#####,,,,,,,...,,,,,,,###########,,,~~ + ~~~,,#...#.#t..+.................+.........#,,,~~ + ~~~,,#g..+.+...###+####+++####+###.#.....#.#,,,~~ + ~~~,,#####.#.s.#.....g#...#g.....#.........#,,,~~ + ~~~,,#...+.#####.g....+...+....g.#.#K....#.#,,~~~ + ~~~,,#...#.#...#.....g#...#g.....#.........#,,~~~ + ~~~,,#####.+...#......#...#......#.#.....#.#,,,~~ + ~~~,,#..C#.############+++########.....p...#,,~~~ + ~~~,,#...#.#...#,,,,,,g...g,,,,,,#.#.....#.#~~~~~ + ~~~,,#...+.+...#,,,,,,,...,,,,,,,#.........#~~~~~ + ~~~,,###########,,,,,,,...,,,,,,,###########~~~~~ + ~~~,,,,,,,,,,,,,,,,,,,,...,,,,,,,,,,,~~~~~~~~~~~~ + ~~~~~,,,,,,,,,,,,,,,,,,...,,,,,,~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~...~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~...~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~...~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~...~~~~~~~~~~~~~~~~~~~~~~~ + } + + Square, Pos 36,32; + { + OTerrain = stairs(STAIRS_DOWN); + EntryIndex = STAIRS_DOWN; + } + } + + /* the Bridge */ + Square, Pos 25,YSize - 1; + { + GTerrain = CYPRESS_WOOD solidterrain(PARQUET); + } + + Square, Pos 26,YSize - 1; + { + GTerrain = CYPRESS_WOOD solidterrain(PARQUET); + EntryIndex = STAIRS_UP; + } + + Square, Pos 27,YSize - 1; + { + GTerrain = CYPRESS_WOOD solidterrain(PARQUET); + } + + Square, Pos 25,YSize - 2; + { + GTerrain = CYPRESS_WOOD solidterrain(PARQUET); + } + + Square, Pos 26,YSize - 2; + { + GTerrain = CYPRESS_WOOD solidterrain(PARQUET); + } + + Square, Pos 27,YSize - 2; + { + GTerrain = CYPRESS_WOOD solidterrain(PARQUET); + } + } + + /* Throne Room */ + RandomLevel 1:2; + { + Room + { + Size = 3,3; + WallSquare = CALCITE solidterrain(GROUND), BASALT wall(BRICK_FINE); + FloorSquare = MARBLE solidterrain(FLOOR), 0; + UseFillSquareWalls = false; + GenerateDoor = false; + GenerateTunnel = false; + Flags = NO_MONSTER_GENERATION; + + Square, Pos 1,1; + { + OTerrain = PORCELAIN throne; + } + + Square, Pos 1,2; + { + GTerrain = MARBLE solidterrain(FLOOR); + OTerrain = CYPRESS_WOOD door(HEART_SHAPED_LOCK) { Parameters = LOCKED; } + AttachRequired = true; + } + } + } + + /* Wine Cellar */ + RandomLevel 1:2; + { + Room + { + Size = 9,9; + WallSquare = CALCITE solidterrain(GROUND), BASALT wall(BRICK_FINE); + DoorSquare = MARBLE solidterrain(FLOOR), CYPRESS_WOOD door; + FloorSquare = MARBLE solidterrain(FLOOR), 0; + GenerateLanterns = true; + UseFillSquareWalls = false; + + OTerrainMap + { + Pos = 2,2; + Size = 5,5; + + Types + { + = = olterraincontainer(SHELF) { ItemsInside = { 2, potion { SecondaryMaterial = WHITE_WINE; Times = 0:3; }, + potion { SecondaryMaterial = RED_WINE; Times = 0:3; } } } + X = olterraincontainer(SHELF) { ItemsInside == potion { SecondaryMaterial = VALDEMAR; } } + B = olterraincontainer(SHELF) { ItemsInside = { 4, potion { SecondaryMaterial = BEER; Times = 0:2; }, + potion { SecondaryMaterial = ELF_ALE; Times = 0:2; }, + potion { SecondaryMaterial = DWARF_BEER; Times = 0:2; }, + potion { SecondaryMaterial = CIDER; Times = 0:2; } } } + } + } + { + =.=.= + =.B.= + ..... + =.=.= + =.=.X + } + } + } + + Level 2; + { + IgnoreDefaultSpecialSquares = true; + + Room + { + Size = 7,7; + UseFillSquareWalls = false; + WallSquare = CALCITE solidterrain(GROUND), 0; + Shape = ROUND_CORNERS; + + Square, Random HAS_NO_OTERRAIN; + { + OTerrain = stairs(STAIRS_DOWN); + EntryIndex = STAIRS_DOWN; + } + + Square, Random HAS_NO_OTERRAIN; + { + OTerrain = CYPRESS_WOOD sign { Text = "Entry forbidden by royal decree."; } + } + Square, Random HAS_NO_OTERRAIN; + { + OTerrain = CYPRESS_WOOD sign { Text = "Pity those who lost their way."; } + } + } + + Square, Random NOT_IN_ROOM|ATTACHABLE; + { + OTerrain = stairs(STAIRS_UP); + EntryIndex = STAIRS_UP; + AttachRequired = true; + } + } + + Level 3; + { + Description = "cistern"; + ShortDescription = "Cistern"; + IgnoreDefaultSpecialSquares = true; + + Square, Random NOT_IN_ROOM|ATTACHABLE; + { + OTerrain = stairs(STAIRS_UP); + EntryIndex = STAIRS_UP; + AttachRequired = true; + } + + Room + { + Size = 7,7; + GenerateDoor = false; + GenerateTunnel = false; + GenerateLanterns = true; + AllowLockedDoors = true; + UseFillSquareWalls = false; + Shape = ROUND_CORNERS; + DivineMaster = SEGES; + + WallSquare = CALCITE solidterrain(GROUND), BLACK_IRON wall(BRICK_OLD); + FloorSquare = OBSIDIAN solidterrain(PARQUET), 0; + + Square, Random IN_ROOM|HAS_NO_OTERRAIN; + { + Character = angel(SEGES) { Inventory = { 4, wand(WAND_OF_MIRRORING) { Chance = 50; }, + wand(WAND_OF_ALCHEMY) { Chance = 50; }, + Random { Times = 2:5; Category = AMULET|RING|SCROLL|WAND|TOOL; MinPrice = 250; }, + key(HEART_SHAPED_LOCK); } } + } + + Square, Pos 0,4; + { + GTerrain = OBSIDIAN solidterrain(PARQUET); + OTerrain = BLACK_IRON door(SECRET_DOOR); + AttachRequired = true; + } + + Square, Pos 6,4; + { + GTerrain = OBSIDIAN solidterrain(PARQUET); + OTerrain = BLACK_IRON door(SECRET_DOOR); + AttachRequired = true; + } + } + } +} diff --git a/Script/dungeons/Attnam.dat b/Script/dungeons/Attnam.dat index 6690c5eb5..fd4a31c78 100644 --- a/Script/dungeons/Attnam.dat +++ b/Script/dungeons/Attnam.dat @@ -39,7 +39,7 @@ Dungeon ATTNAM; EnchantmentMinusChanceBase = 0; EnchantmentMinusChanceDelta = 0; EnchantmentPlusChanceBase = 0; - EnchantmentPlusChanceDelta = 0; + EnchantmentPlusChanceDelta = 5; BackGroundType = GRAY_FRACTAL; FillSquare = GRANITE solidterrain(GROUND), BLACK_GRANITE earth; TunnelSquare = GRANITE solidterrain(GROUND), 0; @@ -491,7 +491,7 @@ Dungeon ATTNAM; k = kamikazedwarf(VALPURUS); K = veterankamikazedwarf(VALPURUS); h = imperialist(HOARD_MASTER); - G = golem(GOLD); + G = golem(GOLD) { Inventory == GOLD stone(SOL_STONE); } o = orc(REPRESENTATIVE); a = siren(AMBASSADOR_SIREN); A = guard(EMISSARY) { WayPoint = { 2, 26, 35, 34, 35; } } @@ -573,10 +573,6 @@ Dungeon ATTNAM; Square, Pos 5, 1; { Character = shopkeeper(ATTNAM) { Flags = IS_MASTER; } - } - - Square, Pos 5, 1; - { OTerrain = decoration(CHAIR); } @@ -736,7 +732,7 @@ Dungeon ATTNAM; Square, Pos 3, 2; { - Character = smith { Flags = IS_MASTER; } + Character = smith(ATTNAM) { Flags = IS_MASTER; } } Square, Pos 3, 3; @@ -810,7 +806,7 @@ Dungeon ATTNAM; Square, Pos 3, 2; { - Character = tailor { Flags = IS_MASTER; } + Character = tailor(ATTNAM) { Flags = IS_MASTER; } } Square, Pos 3, 3; @@ -911,7 +907,7 @@ Dungeon ATTNAM; DivineMaster = VALPURUS; GenerateTunnel = false; GenerateLanterns = false; - Type = ROOM_CATHEDRAL; + Type = ROOM_OWNED_AREA; GenerateFountains = false; AllowLockedDoors = false; AllowBoobyTrappedDoors = false; @@ -939,12 +935,12 @@ Dungeon ATTNAM; *#..#*******#......#....#....##.###...# *#..#*******#......##.#####.##........# *####*******#...#.................##### - ************#######..#.###...####.....# - *****************....#.**#...#**#.#...# - ***************..*####*.*#...#**#.#...# - ************************#######*#.##### - ************************#.....#*#.....# - #########################.....#*#.#...# + ************#######...####...####.....# + *****************.....#**#...#**#.#...# + #####**********..*#...#.*#...#**#.#...# + #...#*************#####*#######*#.##### + #...#*******************#.....#*#.....# + ##.######################.....#*#.#...# #.............................#*#.#...# #.............................#*#.##### ##.#####.#####.#####.####.....#*#...#** @@ -989,8 +985,10 @@ Dungeon ATTNAM; * = MORAINE earth; # = BLACK_GRANITE wall(BRICK_OLD); @ = IRON barwall; + & = PAPER_BOARD barwall; /* Yes, it's a cardboard prison joke. */ $ = IRON barwall(BROKEN_BARWALL); % = IRON door(BARDOOR); + = = PAPER_BOARD door(BARDOOR) { Parameters = LOCKED; } ^ = MARBLE wall(BRICK_PROPAGANDA); > = stairs(STAIRS_DOWN); < = stairs(STAIRS_UP); @@ -1038,18 +1036,18 @@ Dungeon ATTNAM; *####*******X.s.@..@...sX....#...s$...# *#..#*******#...@..@....#....#@B@@@...# *#..#*******#s..L..@@B@@#@@%@#b...%...# - *####*******X...@............S....XX### - ************#######..#X##X@L@#XX#.%..s# - *****************b...#***#...X**X.@...# - ***************>.*####***#.s.#**#.$...X - ************************##NPN##*#.#XXX# - ************************#.....#*#.%...X - #########################.....#*#.@.s.# + *##S#*******X...@............S....XX### + **#.#*******#######@%$X##X@L@#XX#.%..s# + **#.#************b....#**#...X**X.@...# + ###S#**********>.*#s..#**#.s.#**#.$...X + #..I#*************#XX##*##NPN##*#.#XXX# + #...#*******************#.....#*#.%...X + #@L@#####################.....#*#.@.s.# #.............................#*#.@...# #.............................#*#S##### - #@L@###@%@###@L@###@%@###.....#*#.WW#** + #@L@###&=&###@L@###@%@###.....#*#.WW#** #...#*#...#*#...#*#...#*#.....#*#..W#** - #..s#*#.s.#*#.s.#*#...#*#.....#*#.WW#** + #..s#*#.d.#*#.s.#*#...#*#.....#*#.WW#** #####*#####*#####*#####*##...##*#..W#** *************************#...#**#.WW#** #####*#####*#####*#####*##...##*#..W#** @@ -1106,11 +1104,11 @@ Dungeon ATTNAM; ************#..b#...............b.##### ************#######..#####...####.....# *****************.r.b#***#...#**#.#...# - ***************.p*####***#...#**#.#...# - ************************##...##*#.##### - ************************#.....#*#.....# - ##2#####2#####2#####2###3.....#*#.#...# - #.............................#*#.#...# + #####**********.p*####***#...#**#.#...# + #...#*******************##...##*#.##### + #...#*******************#.....#*#.....# + ##.#####2#####2#####2###3.....#*#.#...# + 3.............................#*#.#...# #.............................#*#.##### ##.#####.#####.#####.####.....#*#...#** #...#*#...#*#...#*#...#*3.....#*#...#** @@ -1347,8 +1345,8 @@ Dungeon ATTNAM; LevelMessage = "You hear rhythmic chanting of some profane ritual: \"Ia! Ia! Khaz-zadm fhtagn!\""; DifficultyBase = 100; DifficultyDelta = 0; - EnchantmentMinusChanceBase = 0; - EnchantmentMinusChanceDelta = 0; + ItemMinPriceBase = 100; + ItemMinPriceDelta = 10; Square, Random NOT_IN_ROOM|ATTACHABLE; { diff --git a/Script/dungeons/BlackMarket.dat b/Script/dungeons/BlackMarket.dat index 535dac3a1..1ddf5cb35 100644 --- a/Script/dungeons/BlackMarket.dat +++ b/Script/dungeons/BlackMarket.dat @@ -34,7 +34,7 @@ Dungeon BLACK_MARKET; GenerateMonsters = false; Rooms = 1; Items = 0; - IsOnGround = true; + IsOnGround = false; TeamDefault = GUILD_TEAM; IgnoreDefaultSpecialSquares = true; AutoReveal = false; diff --git a/Script/dungeons/DarkForest.dat b/Script/dungeons/DarkForest.dat new file mode 100644 index 000000000..fcb3302bb --- /dev/null +++ b/Script/dungeons/DarkForest.dat @@ -0,0 +1,104 @@ +/* + * + * Iter Vehemens ad Necem (IVAN) + * Copyright (C) Timo Kiviluoto + * Released under the GNU General + * Public License + * + * See LICENSING which should be included + * along with this file for more details + * + */ + +/* + * NOTICE!!! + * + * This file contains SPOILERS, which might ruin your IVAN experience + * totally. Also, editing anything can DESTROY GAME BALANCE or CAUSE + * OBSCURE BUGS if you don't know what you're doing. So from here on, + * proceed at your own risk! + */ + + /* Work in progress! */ + +Dungeon DARK_FOREST; +{ + Levels = 1; + Description = "Dark Forest"; + ShortDescription = "DF"; + + Level 0; + { + FillSquare = solidterrain(GRASS_TERRAIN), 0; + Size = 42, 26; + GenerateMonsters = true; + Rooms = 0; + Items = 0; + IsOnGround = true; + TeamDefault = MONSTER_TEAM; + LOSModifier = 48; + IgnoreDefaultSpecialSquares = false; + AutoReveal = true; + CanGenerateBone = false; + DifficultyBase = 50; + DifficultyDelta = 0; + EnchantmentMinusChanceBase = -15; + EnchantmentMinusChanceDelta = 0; + EnchantmentPlusChanceBase = 0; + EnchantmentPlusChanceDelta = 0; + BackGroundType = GREEN_FRACTAL; + EarthquakesAffectTunnels = true; + AudioPlayList = + { + 1, + "Empty.mid"; + } + + Square, Random; + { + Items == beartrap { Team = MONSTER_TEAM; IsActive = false; } + Times = 0:3; + } + + Square, Random; + { + Items == Random { Category = FOOD; } + Times = 1:2; + } + + Square, Random NOT_IN_ROOM|HAS_NO_OTERRAIN; + { + OTerrain = boulder(1); + Times = 10; + } + + Square, Random NOT_IN_ROOM|HAS_NO_OTERRAIN; + { + OTerrain = boulder(2); + Times = 10; + } + + RoomDefault + { + Pos = 2:40,2:24; + Size = 4:6,4:6; + AltarPossible = false; + WallSquare = solidterrain(GRASS_TERRAIN), wall(BRICK_PRIMITIVE); + FloorSquare = solidterrain(GRASS_TERRAIN), 0; + DoorSquare = solidterrain(GRASS_TERRAIN), BALSA_WOOD door; + GenerateDoor = true; + DivineMaster = 0; + GenerateTunnel = false; + GenerateLanterns = false; + Type = ROOM_NORMAL; + GenerateFountains = false; + AllowLockedDoors = false; + AllowBoobyTrappedDoors = false; + Shape = ROUND_CORNERS; + IsInside = true; + GenerateWindows = true; + UseFillSquareWalls = false; + Flags = 0; + } + } +} diff --git a/Script/dungeons/FungalCave.dat b/Script/dungeons/FungalCave.dat new file mode 100644 index 000000000..2c153608a --- /dev/null +++ b/Script/dungeons/FungalCave.dat @@ -0,0 +1,779 @@ +/* + * + * Iter Vehemens ad Necem (IVAN) + * Copyright (C) Timo Kiviluoto + * Released under the GNU General + * Public License + * + * See LICENSING which should be included + * along with this file for more details + * + */ + +/* + * NOTICE!!! + * + * This file contains SPOILERS, which might ruin your IVAN experience + * totally. Also, editing anything can DESTROY GAME BALANCE or CAUSE + * OBSCURE BUGS if you don't know what you're doing. So from here on, + * proceed at your own risk! + */ + +Dungeon FUNGAL_CAVE; +{ + Levels = 5; + Description = "Fungal Cave"; + ShortDescription = "FC"; + + LevelDefault + { + FillSquare = CLAY solidterrain(SAND_TERRAIN), CLAY earth; + TunnelSquare = MYCELIUM solidterrain(DARK_GRASS_TERRAIN), 0; + Size = 32, 64; + Rooms = 10:20; + Items = 15:30; + GenerateMonsters = true; + IsOnGround = false; + TeamDefault = MONSTER_TEAM; + LOSModifier = 16; + IgnoreDefaultSpecialSquares = false; + DifficultyBase = 70; + DifficultyDelta = 10; + MonsterAmountBase = 15; + MonsterAmountDelta = 5; + MonsterGenerationIntervalBase = 120; + MonsterGenerationIntervalDelta = -15; + CanGenerateBone = true; + ItemMinPriceBase = 15; + ItemMinPriceDelta = 15; + EnchantmentMinusChanceBase = 0; + EnchantmentMinusChanceDelta = 0; + EnchantmentPlusChanceBase = 10; + EnchantmentPlusChanceDelta = 5; + BackGroundType = GREEN_FRACTAL; + IsCatacomb = false; + EarthquakesAffectTunnels = true; + AudioPlayList = + { + 3, + "Dungeon.mid", + "Dungeon2.mid", + "Dungeon3.mid"; + } + + /* Decorations */ + Square, Random HAS_NO_OTERRAIN; + { + Items == CLAY stone; + Times = 5:10; + } + + /* Stairs & Traps */ + Square, Random NOT_WALKABLE|ATTACHABLE; + { + OTerrain = stairs(STAIRS_UP); + GTerrain = MYCELIUM solidterrain(DARK_GRASS_TERRAIN); + EntryIndex = STAIRS_UP; + AttachRequired = true; + } + + Square, Random NOT_WALKABLE|ATTACHABLE; + { + OTerrain = stairs(STAIRS_DOWN); + GTerrain = MYCELIUM solidterrain(DARK_GRASS_TERRAIN); + EntryIndex = STAIRS_DOWN; + AttachRequired = true; + } + + Square, Random HAS_NO_OTERRAIN; + { + Items == mine { Team = MONSTER_TEAM; IsActive = true; } + Times = 0:3; + } + + Square, Random HAS_NO_OTERRAIN; + { + Items == beartrap { Team = MONSTER_TEAM; IsActive = true; } + Times = 0:3; + } + + Square, Random HAS_NO_OTERRAIN; + { + Items == gastrap { Team = MONSTER_TEAM; IsActive = true; } + Times = 0:3; + } + + /* Monsters */ + Square, Random HAS_NO_OTERRAIN; + { + Character = mushroom; + Times = 2:4; + } + + Square, Random HAS_NO_OTERRAIN; + { + Character = magicmushroom; + Times = 0:3; + } + + RoomDefault + { + Pos = 2:XSize-5,2:YSize-5; + Size = 4:11,4:11; + AltarPossible = true; + WallSquare = CLAY solidterrain(SAND_TERRAIN), FUNGI_WOOD wall(BRICK_PRIMITIVE); + FloorSquare = FUNGI_WOOD solidterrain(PARQUET), 0; + DoorSquare = FUNGI_WOOD solidterrain(PARQUET), MYCELIUM door(CURTAIN); + GenerateDoor = true; + DivineMaster = 0; + GenerateTunnel = true; + GenerateLanterns = true; + Type = ROOM_NORMAL; + GenerateFountains = true; + AllowLockedDoors = false; + AllowBoobyTrappedDoors = true; + Shape = RECTANGLE; + IsInside = true; + GenerateWindows = false; + UseFillSquareWalls = false; + Flags = 0; + } + } + + Level 0; + { + TunnelSquare = CLAY solidterrain(SAND_TERRAIN), 0; + + /* Cave Rooms */ + Room + { + Size = 7,7; + GenerateFountains = false; + Shape = ROUND_CORNERS; + FloorSquare = CLAY solidterrain(SAND_TERRAIN), 0; + UseFillSquareWalls = true; + GenerateDoor = false; + GenerateLanterns = false; + AltarPossible = false; + + Square, Pos 3,0; + { + OTerrain = 0; + AttachRequired = true; + } + Square, Pos 0,3; + { + OTerrain = 0; + AttachRequired = true; + } + Square, Pos 6,3; + { + OTerrain = 0; + AttachRequired = true; + } + Square, Pos 3,6; + { + OTerrain = 0; + AttachRequired = true; + } + } + + Room + { + Size = 7,7; + GenerateFountains = false; + Shape = ROUND_CORNERS; + FloorSquare = CLAY solidterrain(SAND_TERRAIN), 0; + UseFillSquareWalls = true; + GenerateDoor = false; + GenerateLanterns = false; + AltarPossible = false; + + Square, Pos 3,0; + { + OTerrain = 0; + AttachRequired = true; + } + Square, Pos 0,3; + { + OTerrain = 0; + AttachRequired = true; + } + Square, Pos 6,3; + { + OTerrain = 0; + AttachRequired = true; + } + Square, Pos 3,6; + { + OTerrain = 0; + AttachRequired = true; + } + } + + /* Pool */ + Room + { + Size = 4:11,4:11; + GenerateLanterns = false; + GenerateFountains = false; + GenerateDoor = false; + GenerateTunnel = false; + AltarPossible = false; + WallSquare = CLAY solidterrain(SAND_TERRAIN), 0; + FloorSquare = liquidterrain(UNDERGROUND_LAKE), 0; + Shape = ROUND_CORNERS; + Flags = NO_MONSTER_GENERATION; + + Square, Pos 0,0; + { + AttachRequired = true; + } + } + } + + Level 1; + { + /* Cave Room */ + Room + { + Size = 7,7; + GenerateFountains = false; + Shape = ROUND_CORNERS; + FloorSquare = MYCELIUM solidterrain(DARK_GRASS_TERRAIN), 0; + UseFillSquareWalls = true; + GenerateDoor = false; + GenerateLanterns = false; + AltarPossible = false; + + Square, Pos 3,0; + { + OTerrain = 0; + GTerrain = MYCELIUM solidterrain(DARK_GRASS_TERRAIN); + AttachRequired = true; + } + Square, Pos 0,3; + { + OTerrain = 0; + GTerrain = MYCELIUM solidterrain(DARK_GRASS_TERRAIN); + AttachRequired = true; + } + Square, Pos 6,3; + { + OTerrain = 0; + GTerrain = MYCELIUM solidterrain(DARK_GRASS_TERRAIN); + AttachRequired = true; + } + Square, Pos 3,6; + { + OTerrain = 0; + GTerrain = MYCELIUM solidterrain(DARK_GRASS_TERRAIN); + AttachRequired = true; + } + } + + /* Pools */ + Room + { + Size = 4:11,4:11; + GenerateLanterns = false; + GenerateFountains = false; + GenerateDoor = false; + GenerateTunnel = false; + AltarPossible = false; + WallSquare = MYCELIUM solidterrain(DARK_GRASS_TERRAIN), 0; + FloorSquare = liquidterrain(UNDERGROUND_LAKE), 0; + Shape = ROUND_CORNERS; + Flags = NO_MONSTER_GENERATION; + + Square, Pos 0,0; + { + AttachRequired = true; + } + } + + Room + { + Size = 4:11,4:11; + GenerateLanterns = false; + GenerateFountains = false; + GenerateDoor = false; + GenerateTunnel = false; + AltarPossible = false; + WallSquare = MYCELIUM solidterrain(DARK_GRASS_TERRAIN), 0; + FloorSquare = liquidterrain(UNDERGROUND_LAKE), 0; + Shape = ROUND_CORNERS; + Flags = NO_MONSTER_GENERATION; + + Square, Pos 0,0; + { + AttachRequired = true; + } + } + } + + Level 2; + { + /* Cave Room */ + Room + { + Size = 7,7; + GenerateFountains = false; + Shape = ROUND_CORNERS; + FloorSquare = MYCELIUM solidterrain(DARK_GRASS_TERRAIN), 0; + UseFillSquareWalls = true; + GenerateDoor = false; + GenerateLanterns = false; + AltarPossible = false; + + Square, Pos 3,0; + { + OTerrain = 0; + GTerrain = MYCELIUM solidterrain(DARK_GRASS_TERRAIN); + AttachRequired = true; + } + Square, Pos 0,3; + { + OTerrain = 0; + GTerrain = MYCELIUM solidterrain(DARK_GRASS_TERRAIN); + AttachRequired = true; + } + Square, Pos 6,3; + { + OTerrain = 0; + GTerrain = MYCELIUM solidterrain(DARK_GRASS_TERRAIN); + AttachRequired = true; + } + Square, Pos 3,6; + { + OTerrain = 0; + GTerrain = MYCELIUM solidterrain(DARK_GRASS_TERRAIN); + AttachRequired = true; + } + } + + /* Pool */ + Room + { + Size = 4:11,4:11; + GenerateLanterns = false; + GenerateFountains = false; + GenerateDoor = false; + GenerateTunnel = false; + AltarPossible = false; + WallSquare = MYCELIUM solidterrain(DARK_GRASS_TERRAIN), 0; + FloorSquare = liquidterrain(UNDERGROUND_LAKE), 0; + Shape = ROUND_CORNERS; + Flags = NO_MONSTER_GENERATION; + + Square, Pos 0,0; + { + AttachRequired = true; + } + } + } + + /* Fusanga Level */ + Level FUSANGA_LEVEL; + { + FillSquare = COAL solidterrain(GROUND), COAL earth; + Size = 64, 64; + Rooms = 100; + LOSModifier = 42; + IgnoreDefaultSpecialSquares = true; + MonsterAmountBase = 20; + MonsterAmountDelta = 7; + + Square, Random NOT_WALKABLE|ATTACHABLE; + { + OTerrain = stairs(STAIRS_UP); + GTerrain = MYCELIUM solidterrain(DARK_GRASS_TERRAIN); + EntryIndex = STAIRS_UP; + AttachRequired = true; + } + + /* Monsters */ + Square, Random HAS_NO_OTERRAIN; + { + Character = magicmushroom; + Times = 10; + } + + RoomDefault + { + Pos = 2:XSize-5,2:YSize-5; + Size = 3:4,3:4; + AltarPossible = false; + FloorSquare = MYCELIUM solidterrain(DARK_GRASS_TERRAIN), 0; + DoorSquare = MYCELIUM solidterrain(DARK_GRASS_TERRAIN), 0; + GenerateLanterns = false; + GenerateFountains = false; + UseFillSquareWalls = true; + } + + /* Fusanga Room */ + Room + { + Size = 8,8; + WallSquare = MYCELIUM solidterrain(DARK_GRASS_TERRAIN), 0; + UseFillSquareWalls = false; + Shape = ROUND_CORNERS; + + CharacterMap + { + Pos = 2,2; + Size = 4,4; + + Types + { + f = mushroom; + F = fusanga; + } + } + { + ffff + fF.f + f..f + ffff + } + + Square, Pos 3,3; + { + OTerrain = stairs(STAIRS_DOWN); + EntryIndex = STAIRS_DOWN; + } + } + + /* Pool */ + Room + { + Size = 5:9,5:9; + UseFillSquareWalls = false; + WallSquare = MYCELIUM solidterrain(DARK_GRASS_TERRAIN), 0; + FloorSquare = liquidterrain(UNDERGROUND_LAKE), 0; + Shape = ROUND_CORNERS; + Flags = NO_MONSTER_GENERATION; + + Square, Pos 0,0; + { + AttachRequired = true; + } + } + } + + /* MacGuffin */ + Level 4; + { + Description = "deep lake"; + ShortDescription = "Deep Lake"; + Size = 42, 26; + Items = 30:70; + IgnoreDefaultSpecialSquares = true; + CanGenerateBone = false; + LOSModifier = 48; + FillSquare = COAL solidterrain(GROUND), COAL earth; + LevelMessage = "You shudder. The scent of magic and death is in the air."; + + RoomDefault + { + Pos = 2:XSize-5,2:YSize-5; + Size = 5:7,5:7; + WallSquare = PETRIFIED_WOOD solidterrain(GROUND), PETRIFIED_WOOD wall(STONE_WALL); + FloorSquare = PETRIFIED_WOOD solidterrain(GROUND), 0; + DoorSquare = PETRIFIED_WOOD solidterrain(GROUND), 0; + AltarPossible = false; + GenerateLanterns = false; + GenerateFountains = false; + Shape = ROUND_CORNERS; + } + + Square, Random NOT_WALKABLE|ATTACHABLE; + { + OTerrain = stairs(STAIRS_UP); + GTerrain = MYCELIUM solidterrain(DARK_GRASS_TERRAIN); + EntryIndex = STAIRS_UP; + AttachRequired = true; + } + + Square, Random; + { + Character = kamikazedwarf(MORTIFER); + Times = 1:2; + } + + Room + { + Size = 11,7; + WallSquare = MYCELIUM solidterrain(DARK_GRASS_TERRAIN), 0; + DoorSquare = MYCELIUM solidterrain(DARK_GRASS_TERRAIN), 0; + FloorSquare = liquidterrain(UNDERGROUND_LAKE), 0; + Flags = NO_MONSTER_GENERATION; + + Square, Pos 5,3; + { + Items == weepobsidian; + } + + Square, Random; + { + Character = darkmage(BATTLE_MAGE); + } + } + } + + /* "I'm so EVIL!!1!" Room */ + RandomLevel 1:3; + { + Room + { + Size = 11,11; + GenerateLanterns = false; + UseFillSquareWalls = false; + WallSquare = CLAY solidterrain(SAND_TERRAIN), HARDENED_CLAY wall(STONE_WALL); + DoorSquare = solidterrain(DARK_GRASS_TERRAIN), FUNGI_WOOD door; + FloorSquare = solidterrain(DARK_GRASS_TERRAIN), 0; + + Square, Pos 5,5; + { + OTerrain = SUN_CRYSTAL decoration(SHARD); + } + + Square, Random HAS_NO_OTERRAIN; + { + OTerrain = decoration(DWARF_BIRCH); + Times = 2:5; + } + + Square, Random HAS_NO_OTERRAIN; + { + OTerrain = decoration(TEAK); + Times = 3:6; + } + + /* Treasure */ + Square, Random HAS_NO_OTERRAIN; + { + Items == Random { ConfigFlags = NO_BROKEN; } + Times = 10:20; + } + + /* Monsters to make the player really, really angry: */ + Square, Random HAS_NO_OTERRAIN; + { + Character = magpie; + Times = 3:6; + } + + Square, Random HAS_NO_OTERRAIN; + { + Character = fruitbat; + Times = 3:6; + } + + Square, Random HAS_NO_OTERRAIN; + { + Character = nerfbat; + Times = 1:3; + } + + Square, Random HAS_NO_OTERRAIN; + { + Character = floatingeye; + } + + Square, Random HAS_NO_OTERRAIN; + { + Character = siren(LIGHT_ASIAN_SIREN); + } + Square, Random HAS_NO_OTERRAIN; + { + Character = siren(DARK_ASIAN_SIREN); + } + Square, Random HAS_NO_OTERRAIN; + { + Character = siren(CAUCASIAN_SIREN); + } + Square, Random HAS_NO_OTERRAIN; + { + Character = siren(DARK_SIREN); + } + Square, Random HAS_NO_OTERRAIN; + { + Character = siren(GREEN_SIREN); + } + Square, Random HAS_NO_OTERRAIN; + { + Character = siren(BLUE_SIREN); + } + Square, Random HAS_NO_OTERRAIN; + { + Character = siren(RED_SIREN); + } + Square, Random HAS_NO_OTERRAIN; + { + Character = siren(PINK_SIREN); + } + Square, Random HAS_NO_OTERRAIN; + { + Character = siren(HISPANIC_SIREN); + } + + Square, Random HAS_NO_OTERRAIN; + { + Character = carnivorousplant(GIANT) { Inventory == Random { Chance = 50; MinPrice = 250; Times = 3; } } + } + } + } + + /* Poison Room */ + RandomLevel 0:2; + { + Room + { + Size = 7,6; + AltarPossible = false; + Shape = ROUND_CORNERS; + + CharacterMap + { + Pos = 1,1; + Size = 5,4; + + Types + { + A = spider(ARANEA) { Inventory == Random { Chance = 50; MinPrice = 250; Times = 3; } } + B = spider(GIANT); + L = spider(LARGE); + s = snake; + S = skunk; + } + } + { + .sSs. + sBLBs + sLALs + .sSs. + } + } + } + + /* Mommo Room */ + RandomLevel 2:4; + { + Room + { + Size = 9,7; + GenerateLanterns = false; + GenerateFountains = false; + AltarPossible = false; + UseFillSquareWalls = false; + WallSquare = CLAY solidterrain(SAND_TERRAIN), HARDENED_CLAY wall(STONE_WALL); + FloorSquare = CLAY solidterrain(SAND_TERRAIN), 0; + DoorSquare = CLAY solidterrain(SAND_TERRAIN), 0; + Shape = ROUND_CORNERS; + + OTerrainMap + { + Pos = 1,1; + Size = 7,5; + + Types + { + # = HARDENED_CLAY wall(STONE_WALL); + } + } + { + ..#.#.. + .#.#.#. + #.#.#.# + .#.#.#. + ..#.#.. + } + + CharacterMap + { + Pos = 1,1; + Size = 7,5; + + Types + { + c = mommo(CONICAL) { Inventory == Random { Chance = 50; MinPrice = 250; Times = 3; } } + f = mommo(FLAT); + b = mommo(BLOAT); + } + } + { + .b.b.b. + b.f.f.b + .f.c.f. + b.f.f.b + .b.b.b. + } + } + } + + /* Vault */ + RandomLevel 0:4; + { + Room + { + GenerateLanterns = false; + GenerateFountains = false; + GenerateDoor = false; + GenerateTunnel = false; + AltarPossible = false; + Size = 7,7; + Type = ROOM_OWNED_AREA; /* Should prevent decay. */ + UseFillSquareWalls = true; + Flags = NO_MONSTER_GENERATION; + + OTerrainMap + { + Pos = 1,1; + Size = 5,5; + + Types + { + = = olterraincontainer(SHELF) { ItemsInside = { 3, carrot { Times = 0:2; }, + Random { MinPrice = 100; Category = POTION; Times = 1:2; }, + Random { MinPrice = 200; Category = POTION; Chance = 50; } } } + x = BRONZE wall(BRICK_OLD); + } + } + { + xxxxx + x=.=x + x=.=x + x=.=x + xxxxx + } + + Square, Random; + { + Items == mine(BIG_MINE) { Team = MONSTER_TEAM; IsActive = true; Chance = 75; } + } + + Square, Pos 3,3; + { + Items == cauldron; + } + } + } + + RandomLevel 1:4; + { + /* I'm Evil, Bwahaha */ + Square, Random IN_ROOM|HAS_NO_OTERRAIN; + { + Items = { 2, mine(BIG_MINE) { Team = MONSTER_TEAM; IsActive = true; }, Random { MinPrice = 250; Category = SCROLL; } } + } + } + + RandomLevel 1:3; + { + Square, Random IN_ROOM|HAS_NO_OTERRAIN; + { + Character = spider(GIANT_GOLD); + } + } +} diff --git a/Script/dungeons/GloomyCaves.dat b/Script/dungeons/GloomyCaves.dat index f06b2ef4f..19cce781b 100644 --- a/Script/dungeons/GloomyCaves.dat +++ b/Script/dungeons/GloomyCaves.dat @@ -240,8 +240,8 @@ Dungeon ELPURI_CAVE; Types { - g = guard(SHOP) { Team = 5; } - s = shopkeeper(ELPURI_CAVE) { Team = 5; Flags = IS_MASTER; } + g = guard(SHOP) { Team = GUILD_TEAM; } + s = shopkeeper(ELPURI_CAVE) { Team = GUILD_TEAM; Flags = IS_MASTER; } } } { @@ -889,7 +889,7 @@ Dungeon ELPURI_CAVE; Level DARK_LEVEL; { Description = "dark fortress"; - ShortDescription = "Dark Fortress"; + ShortDescription = "Dark Fort"; LevelMessage = "You shudder as you sense a being of pure darkness nearby. Your goal is near."; FillSquare = STEEL solidterrain(FLOOR), STEEL wall(BRICK_OLD); TunnelSquare = STEEL solidterrain(FLOOR), 0; diff --git a/Script/dungeons/GoblinFort.dat b/Script/dungeons/GoblinFort.dat new file mode 100644 index 000000000..fa40ab526 --- /dev/null +++ b/Script/dungeons/GoblinFort.dat @@ -0,0 +1,1093 @@ +/* + * + * Iter Vehemens ad Necem (IVAN) + * Copyright (C) Timo Kiviluoto + * Released under the GNU General + * Public License + * + * See LICENSING which should be included + * along with this file for more details + * + */ + +/* + * NOTICE!!! + * + * This file contains SPOILERS, which might ruin your IVAN experience + * totally. Also, editing anything can DESTROY GAME BALANCE or CAUSE + * OBSCURE BUGS if you don't know what you're doing. So from here on, + * proceed at your own risk! + */ + +Dungeon GOBLIN_FORT; +{ + Levels = 6; + Description = "Goblin Fort"; + ShortDescription = "GF"; + + LevelDefault + { + FillSquare = DIRT solidterrain(GROUND), SLATE earth; + TunnelSquare = DIRT solidterrain(GROUND), 0; + Size = 40, 40; + Rooms = 10:30; + Items = 15:30; + GenerateMonsters = true; + IsOnGround = false; + TeamDefault = MONSTER_TEAM; + LOSModifier = 16; + IgnoreDefaultSpecialSquares = false; + DifficultyBase = 60; + DifficultyDelta = 15; + MonsterAmountBase = 10; + MonsterAmountDelta = 3; + MonsterGenerationIntervalBase = 200; + MonsterGenerationIntervalDelta = -30; + CanGenerateBone = true; + ItemMinPriceBase = 20; + ItemMinPriceDelta = 8; + EnchantmentMinusChanceBase = 0; + EnchantmentMinusChanceDelta = 0; + EnchantmentPlusChanceBase = 5; + EnchantmentPlusChanceDelta = 5; + BackGroundType = GRAY_FRACTAL; + IsCatacomb = false; + EarthquakesAffectTunnels = true; + AudioPlayList = + { + 3, + "Dungeon.mid", + "Dungeon2.mid", + "Dungeon3.mid"; + } + + /* Decorations */ + Square, Random NOT_WALKABLE|NOT_IN_ROOM; + { + Items == stone; + Times = 10:30; + } + + Square, Random HAS_NO_OTERRAIN|NOT_IN_ROOM; + { + OTerrain = boulder(1); + Times = 1:3; + } + + Square, Random HAS_NO_OTERRAIN|NOT_IN_ROOM; + { + OTerrain = boulder(2); + Times = 1:3; + } + + /* Traps & Stairs */ + Square, Random HAS_NO_OTERRAIN; + { + Items == mine { Team = MONSTER_TEAM; IsActive = true; } + Times = 0:3; + } + + Square, Random HAS_NO_OTERRAIN; + { + Items == beartrap { Team = MONSTER_TEAM; IsActive = true; } + Times = 0:3; + } + + Square, Random HAS_NO_OTERRAIN; + { + Items == gastrap { Team = MONSTER_TEAM; IsActive = true; } + Times = 0:3; + } + + Square, Random NOT_WALKABLE|ATTACHABLE; + { + OTerrain = stairs(STAIRS_UP); + EntryIndex = STAIRS_UP; + AttachRequired = true; + } + + Square, Random NOT_WALKABLE|ATTACHABLE; + { + OTerrain = stairs(STAIRS_DOWN); + EntryIndex = STAIRS_DOWN; + AttachRequired = true; + } + + RoomDefault + { + Pos = 2:XSize-5,2:YSize-5; + Size = 3:7,3:7; + AltarPossible = true; + WallSquare = DIRT solidterrain(GROUND), GNEISS wall(BRICK_OLD); + FloorSquare = OAK_WOOD solidterrain(PARQUET), 0; + DoorSquare = OAK_WOOD solidterrain(PARQUET), OAK_WOOD door; + GenerateDoor = true; + DivineMaster = 0; + GenerateTunnel = true; + GenerateLanterns = true; + Type = ROOM_NORMAL; + GenerateFountains = true; + AllowLockedDoors = true; + AllowBoobyTrappedDoors = true; + Shape = RECTANGLE; + IsInside = true; + GenerateWindows = false; + UseFillSquareWalls = false; + Flags = 0; + } + } + + Level 0; + { + Square, Random IN_ROOM; + { + OTerrain = stairs(STAIRS_DOWN) { AttachedArea = 4; } + EntryIndex = STAIRS_DOWN + 1; + } + + Square, Random HAS_NO_OTERRAIN; + { + Character = goblin; + Times = 2; + } + + /* Cave Rooms */ + Room + { + Size = 7,7; + GenerateFountains = false; + Shape = ROUND_CORNERS; + FloorSquare = DIRT solidterrain(GROUND), 0; + UseFillSquareWalls = true; + GenerateDoor = false; + GenerateLanterns = false; + AltarPossible = false; + + Square, Pos 3,0; + { + OTerrain = 0; + AttachRequired = true; + } + Square, Pos 0,3; + { + OTerrain = 0; + AttachRequired = true; + } + Square, Pos 6,3; + { + OTerrain = 0; + AttachRequired = true; + } + Square, Pos 3,6; + { + OTerrain = 0; + AttachRequired = true; + } + } + + Room + { + Size = 7,7; + GenerateFountains = false; + Shape = ROUND_CORNERS; + FloorSquare = DIRT solidterrain(GROUND), 0; + UseFillSquareWalls = true; + GenerateDoor = false; + GenerateLanterns = false; + AltarPossible = false; + + Square, Pos 3,0; + { + OTerrain = 0; + AttachRequired = true; + } + Square, Pos 0,3; + { + OTerrain = 0; + AttachRequired = true; + } + Square, Pos 6,3; + { + OTerrain = 0; + AttachRequired = true; + } + Square, Pos 3,6; + { + OTerrain = 0; + AttachRequired = true; + } + } + } + + Level 1; + { + /* Goblins */ + Square, Random HAS_NO_OTERRAIN; + { + Character = goblin; + Times = 2:4; + } + + Square, Random HAS_NO_OTERRAIN; + { + Character = goblin(BERSERKER); + } + } + + Level 2; + { + /* Goblins */ + Square, Random HAS_NO_OTERRAIN; + { + Character = goblin(BUTCHER); + } + } + + Level 3; + { + IgnoreDefaultSpecialSquares = true; + + /* Decorations */ + Square, Random NOT_WALKABLE|NOT_IN_ROOM; + { + Items == stone; + Times = 10:30; + } + + Square, Random HAS_NO_OTERRAIN|NOT_IN_ROOM; + { + OTerrain = boulder(1); + Times = 1:3; + } + + Square, Random HAS_NO_OTERRAIN|NOT_IN_ROOM; + { + OTerrain = boulder(2); + Times = 1:3; + } + + /* Traps & Stairs */ + Square, Random; + { + Items == mine { Team = MONSTER_TEAM; IsActive = true; } + Times = 1:4; + } + + Square, Random; + { + Items == mine(BIG_MINE) { Team = MONSTER_TEAM; IsActive = true; } + Times = 1:2; + } + + Square, Random; + { + Items == beartrap { Team = MONSTER_TEAM; IsActive = true; } + Times = 3:6; + } + + Square, Random; + { + Items == gastrap { Team = MONSTER_TEAM; IsActive = true; } + Times = 2:5; + } + + Square, Random NOT_WALKABLE|ATTACHABLE; + { + OTerrain = stairs(STAIRS_UP); + EntryIndex = STAIRS_UP; + AttachRequired = true; + } + + Square, Random NOT_WALKABLE|ATTACHABLE; + { + OTerrain = stairs(STAIRS_DOWN) { AttachedArea = 5; AttachedEntry = STAIRS_UP + 1; } + EntryIndex = STAIRS_DOWN; + AttachRequired = true; + } + + /* Goblins */ + Square, Random HAS_NO_OTERRAIN; + { + Character = goblin(MONK); + Times = 4:8; + } + } + + /* Big Level */ + Level 4; + { + Description = "salt cave"; + ShortDescription = "Salt Cave"; + Rooms = 50:150; + Items = 100:250; + Size = 100, 100; + LOSModifier = 42; + IgnoreDefaultSpecialSquares = true; + MonsterAmountBase = 30; + MonsterAmountDelta = 30; + LevelMessage = "Sounds echo around this huge level."; + FillSquare = SALT solidterrain(GROUND), ROCK_SALT earth; + TunnelSquare = SALT solidterrain(GROUND), 0; + + RoomDefault + { + Size = 6:23,6:23; + Pos = 2:XSize-5,2:YSize-5; + WallSquare = SALT solidterrain(GROUND), LIME_STONE wall(BRICK_OLD); + FloorSquare = CALCITE solidterrain(PARQUET), 0; + DoorSquare = CALCITE solidterrain(PARQUET), TEAK_WOOD door; + } + + /* Goblins */ + Square, Random HAS_NO_OTERRAIN; + { + Character = goblin; + Times = 5:10; + } + + Square, Random HAS_NO_OTERRAIN; + { + Character = goblin(BERSERKER); + Times = 2:6; + } + + Square, Random HAS_NO_OTERRAIN; + { + Character = goblin(BUTCHER); + Times = 1:4; + } + + Square, Random HAS_NO_OTERRAIN; + { + Character = goblin(MONK); + Times = 1:4; + } + + Square, Random HAS_NO_OTERRAIN; + { + Character = goblin(WARLOCK) { Inventory == Random { Category = WAND; Times = 2; } } + Times = 2; + } + + /* Decorations */ + Square, Random NOT_WALKABLE|NOT_IN_ROOM; + { + Items == stone; + Times = 10:30; + } + + Square, Random HAS_NO_OTERRAIN|NOT_IN_ROOM; + { + OTerrain = boulder(1); + Times = 1:3; + } + + Square, Random HAS_NO_OTERRAIN|NOT_IN_ROOM; + { + OTerrain = boulder(2); + Times = 1:3; + } + + /* Traps */ + Square, Random; + { + Items == mine { Team = MONSTER_TEAM; IsActive = true; } + Times = 1:4; + } + + Square, Random; + { + Items == mine(BIG_MINE) { Team = MONSTER_TEAM; IsActive = true; } + Times = 1:2; + } + + Square, Random; + { + Items == beartrap { Team = MONSTER_TEAM; IsActive = true; } + Times = 3:6; + } + + Square, Random; + { + Items == gastrap { Team = MONSTER_TEAM; IsActive = true; } + Times = 2:5; + } + + /* Stairs are always close together: */ + Square, BoundedRandom XSize-20, 2, XSize-2, 20, NOT_WALKABLE|ATTACHABLE; + { + OTerrain = stairs(STAIRS_UP) { AttachedArea = 0; AttachedEntry = STAIRS_DOWN + 1; } + EntryIndex = STAIRS_UP; + AttachRequired = true; + } + + Square, BoundedRandom XSize-20, 2, XSize-2, 20, NOT_WALKABLE|ATTACHABLE; + { + OTerrain = stairs(STAIRS_DOWN) { AttachedArea = 5; AttachedEntry = STAIRS_UP; } + EntryIndex = STAIRS_DOWN; + AttachRequired = true; + } + + /* Some extra items far from the entrance. */ + Square, BoundedRandom 1, YSize-40, 40, YSize-2, HAS_NO_OTERRAIN; + { + Items == Random { MinPrice = 500; Chance = 75; } + Times = 4; + } + + /* Invisible Room */ + Room + { + Size = 7,6; + AltarPossible = false; + Shape = ROUND_CORNERS; + + CharacterMap + { + Pos = 1,1; + Size = 5,4; + + Types + { + i = invisiblestalker; + I = invisiblestalker(SLAYER); + } + } + { + .iii. + iIIIi + iIIIi + .iii. + } + } + + /* Imp Room */ + Room + { + Size = 6,6; + AltarPossible = false; + Shape = ROUND_CORNERS; + + CharacterMap + { + Pos = 1,1; + Size = 4,4; + + Types + { + c = crimsonimp; + m = mirrorimp; + } + } + { + .cc. + cmmc + cmmc + .cc. + } + } + + /* Cave Rooms */ + Room + { + Size = 7,7; + GenerateFountains = false; + Shape = ROUND_CORNERS; + FloorSquare = SALT solidterrain(GROUND), 0; + UseFillSquareWalls = true; + GenerateDoor = false; + GenerateLanterns = false; + AltarPossible = false; + + Square, Pos 3,0; + { + OTerrain = 0; + AttachRequired = true; + } + Square, Pos 0,3; + { + OTerrain = 0; + AttachRequired = true; + } + Square, Pos 6,3; + { + OTerrain = 0; + AttachRequired = true; + } + Square, Pos 3,6; + { + OTerrain = 0; + AttachRequired = true; + } + } + + Room + { + Size = 7,7; + GenerateFountains = false; + Shape = ROUND_CORNERS; + FloorSquare = SALT solidterrain(GROUND), 0; + UseFillSquareWalls = true; + GenerateDoor = false; + GenerateLanterns = false; + AltarPossible = false; + + Square, Pos 3,0; + { + OTerrain = 0; + AttachRequired = true; + } + Square, Pos 0,3; + { + OTerrain = 0; + AttachRequired = true; + } + Square, Pos 6,3; + { + OTerrain = 0; + AttachRequired = true; + } + Square, Pos 3,6; + { + OTerrain = 0; + AttachRequired = true; + } + } + + Room + { + Size = 7,7; + GenerateFountains = false; + Shape = ROUND_CORNERS; + FloorSquare = SALT solidterrain(GROUND), 0; + UseFillSquareWalls = true; + GenerateDoor = false; + GenerateLanterns = false; + AltarPossible = false; + + Square, Pos 3,0; + { + OTerrain = 0; + AttachRequired = true; + } + Square, Pos 0,3; + { + OTerrain = 0; + AttachRequired = true; + } + Square, Pos 6,3; + { + OTerrain = 0; + AttachRequired = true; + } + Square, Pos 3,6; + { + OTerrain = 0; + AttachRequired = true; + } + } + + Room + { + Size = 7,7; + GenerateFountains = false; + Shape = ROUND_CORNERS; + FloorSquare = SALT solidterrain(GROUND), 0; + UseFillSquareWalls = true; + GenerateDoor = false; + GenerateLanterns = false; + AltarPossible = false; + + Square, Pos 3,0; + { + OTerrain = 0; + AttachRequired = true; + } + Square, Pos 0,3; + { + OTerrain = 0; + AttachRequired = true; + } + Square, Pos 6,3; + { + OTerrain = 0; + AttachRequired = true; + } + Square, Pos 3,6; + { + OTerrain = 0; + AttachRequired = true; + } + } + } + + /* Goblin Prince Level */ + Level 5; + { + Description = "goblin hideout"; + ShortDescription = "Hideout"; + Size = 36, 64; + Items = 30:70; + IgnoreDefaultSpecialSquares = true; + CanGenerateBone = false; + MonsterAmountBase = 30; + MonsterAmountDelta = 30; + LevelMessage = "You hear the squabbling of many voices."; + FillSquare = SHALE solidterrain(GROUND), SLADE earth; + TunnelSquare = SHALE solidterrain(GROUND), 0; + BackGroundType = BLUE_FRACTAL; + LevelMessage = "You hear the hubbub of many goblinish voices."; + + Square, Random NOT_WALKABLE|ATTACHABLE; + { + OTerrain = stairs(STAIRS_UP) { AttachedArea = 4; AttachedEntry = STAIRS_DOWN; } + EntryIndex = STAIRS_UP; + AttachRequired = true; + } + + Square, Random NOT_WALKABLE|ATTACHABLE; + { + OTerrain = stairs(STAIRS_UP) { AttachedArea = 3; AttachedEntry = STAIRS_DOWN; } + EntryIndex = STAIRS_UP + 1; + AttachRequired = true; + } + + RoomDefault + { + Size = 4:10,4:10; + Pos = 2:XSize-5,2:YSize-5; + WallSquare = SHALE solidterrain(GROUND), MAHOGANY_WOOD wall(BRICK_OLD); + FloorSquare = SHALE solidterrain(PARQUET), 0; + DoorSquare = SHALE solidterrain(PARQUET), MAHOGANY_WOOD door; + } + + /* Goblin Prince Room */ + Room + { + Size = 9,9; + AltarPossible = false; + GenerateFountains = false; + FloorSquare = SILVER solidterrain(PARQUET), 0; + Shape = ROUND_CORNERS; + + CharacterMap + { + Pos = 1,1; + Size = 7,7; + + Types + { + g = goblin { Inventory == Random { MinPrice = 100; Chance = 50; } } + b = goblin(BERSERKER) { Inventory == Random { MinPrice = 100; Chance = 50; } } + B = goblin(BUTCHER) { Inventory == Random { MinPrice = 100; Chance = 50; } } + m = goblin(MONK) { Inventory == Random { MinPrice = 100; Chance = 50; } } + w = goblin(WARLOCK) { Inventory == Random { MinPrice = 100; Chance = 50; } } + } + } + { + .ggbgg. + gmbwbmg + gbBBBbg + bwB.Bwb + gbBBBbg + gmbwbmg + .ggbgg. + } + + Square, Pos 4,4; + { + Character = goblin(PRINCE); + OTerrain = throne; + } + } + + /* Aslona Prince Room */ + Room + { + Size = 11,9; + GenerateLanterns = false; + GenerateFountains = false; + GenerateDoor = false; + GenerateTunnel = false; + AltarPossible = false; + Flags = NO_MONSTER_GENERATION; + + Square, Pos 2, 0; + { + Items == lantern { SquarePosition = DOWN; } + } + + Square, Pos 5,0; + { + GTerrain = SHALE solidterrain(PARQUET); + OTerrain = COPPER door { Parameters = LOCKED; } + AttachRequired = true; + } + + Square, Pos 5,8; + { + GTerrain = SHALE solidterrain(PARQUET); + OTerrain = COPPER door { Parameters = LOCKED; } + AttachRequired = true; + } + + OTerrainMap + { + Pos = 1,1; + Size = 9,7; + Types + { + # = COPPER barwall; + $ = COPPER barwall(BROKEN_BARWALL); + x = COPPER door(BARDOOR) { Parameters = LOCKED; } + } + } + { + ...#.#... + ...x.x... + ...#.#... + ####.#### + ...$.#... + ...x.x... + ...#.#... + } + + CharacterMap + { + Pos = 1,1; + Size = 9,7; + Types + { + # = 0; + K = child(KING) { Team = PRISONER_TEAM; } + g = goblin { Team = PRISONER_TEAM; } + G = guard(CASTLE) { Team = PRISONER_TEAM; } + } + } + { + ...#.#... + .g.#.#..G + ...#.#... + ####.#### + ...#.#... + ...#.#.K. + ...#.#... + } + } + + /* Resource Room */ + Room + { + Size = 11,7; + GenerateLanterns = false; + GenerateFountains = false; + GenerateDoor = false; + GenerateTunnel = false; + AltarPossible = false; + Shape = ROUND_CORNERS; + + + Square, Pos 0, 2; + { + Items == lantern { SquarePosition = RIGHT; } + } + + Square, Pos 0, 4; + { + Items == lantern { SquarePosition = RIGHT; } + } + + Square, Pos 10, 2; + { + Items == lantern { SquarePosition = LEFT; } + } + + Square, Pos 10, 4; + { + Items == lantern { SquarePosition = LEFT; } + } + + Square, Pos 0,3; + { + GTerrain = SHALE solidterrain(PARQUET); + OTerrain = MAHOGANY_WOOD door { Parameters = LOCKED; } + AttachRequired = true; + } + + Square, Pos 10,3; + { + GTerrain = SHALE solidterrain(PARQUET); + OTerrain = MAHOGANY_WOOD door { Parameters = LOCKED; } + AttachRequired = true; + } + + Square, Pos 5,0; + { + GTerrain = SHALE solidterrain(PARQUET); + OTerrain = MAHOGANY_WOOD door { Parameters = LOCKED; } + AttachRequired = true; + } + + Square, Pos 5,6; + { + GTerrain = SHALE solidterrain(PARQUET); + OTerrain = MAHOGANY_WOOD door { Parameters = LOCKED; } + AttachRequired = true; + } + + OTerrainMap + { + Pos = 1,1; + Size = 9,5; + Types + { + = = olterraincontainer(SHELF) { ItemsInside = { 3, can { Times = 2:4; }, + loaf { Times = 2:4; }, + sausage { Times = 1:6; } } } + x = olterraincontainer(SHELF) { ItemsInside == Random { Category = SCROLL|BOOK; Times = 1:3; } } + X = olterraincontainer(SHELF) { ItemsInside == locationmap(BLACK_MARKET); } + } + } + { + .===.===. + ......... + .=xX.=x=. + ......... + .===.===. + } + + Square, Random HAS_NO_OTERRAIN; + { + Character = goblin(WARLOCK) { Inventory = { 2, wand(WAND_OF_FIRE_BALLS), wand(WAND_OF_ACID_RAIN); } } + Times = 2; + } + } + } + + /* Cat Room */ + RandomLevel 1:2; + { + LevelMessage = "You hear distant purring."; + + Room + { + Size = 6,6; + AltarPossible = false; + Shape = ROUND_CORNERS; + + CharacterMap + { + Pos = 1,1; + Size = 4,4; + + Types + { + c = largecat { Inventory == fish(BONE_FISH) { Chance = 50; } } + L = lion { Inventory == fish(DEAD_FISH) { Chance = 50; } } + X = lion { Inventory == scrollofenchantarmor; } + Y = lion { Inventory == scrollofenchantweapon; } + } + } + { + .cc. + cLXc + cYLc + .cc. + } + } + } + + /* Explosive Room */ + RandomLevel 3:4; + { + Room + { + Size = 5,5; + AllowLockedDoors = true; + AllowBoobyTrappedDoors = true; + GenerateFountains = false; + AltarPossible = false; + Flags = NO_MONSTER_GENERATION; + + Square, Random HAS_NO_OTERRAIN; + { + Items == mine; + Times = 1:5; + } + + Square, Random HAS_NO_OTERRAIN; + { + Items == mine(BIG_MINE); + Times = 1:5; + } + + Square, Random HAS_NO_OTERRAIN; + { + Items == gastrap; + Times = 1:5; + } + + Square, Random HAS_NO_OTERRAIN; + { + Items == backpack; + Times = 2:4; + } + + Square, Random HAS_NO_OTERRAIN; + { + Items == gasgrenade; + Times = 2:6; + } + + Square, Random HAS_NO_OTERRAIN; + { + Items == scrolloffireballs { Chance = 10; Times = 4; } + } + + Square, Random HAS_NO_OTERRAIN; + { + Character = kamikazedwarf(CLEPTIA); + } + } + } + + /* Vault */ + RandomLevel 0:4; + { + Room + { + GenerateLanterns = false; + GenerateFountains = false; + GenerateDoor = false; + GenerateTunnel = false; + AltarPossible = false; + Size = 7,7; + UseFillSquareWalls = true; + Flags = NO_MONSTER_GENERATION; + + OTerrainMap + { + Pos = 1,1; + Size = 5,5; + Types + { + x = IRON wall(BRICK_OLD); + } + } + { + xxxxx + x...x + x...x + x...x + xxxxx + } + + Square, Random; + { + Items == mine { Team = MONSTER_TEAM; IsActive = true; Chance = 50; } + } + + Square, Random; + { + Items == mine(BIG_MINE) { Team = MONSTER_TEAM; IsActive = true; } + } + + Square, Random; + { + Items == UR_STEEL beartrap { Team = MONSTER_TEAM; IsActive = true; Chance = 50; } + Times = 2; + } + + Square, Pos 3,3; + { + Items == UR_STEEL itemcontainer(CHEST|OCTAGONAL_LOCK) + { + Parameters = LOCKED; + ItemsInside = { 2, Random { MinPrice = 1250; }, + Random { MinPrice = 100; Chance = 75; Times = 3; } } + } + } + } + } + + /* Living Quarters */ + RandomLevel 0:3; + { + Room + { + AllowBoobyTrappedDoors = false; + Flags = NO_MONSTER_GENERATION; + + Square, Random HAS_NO_OTERRAIN; + { + OTerrain = decoration(PLAIN_BED); + } + + Square, Random HAS_NO_OTERRAIN; + { + OTerrain = decoration(TABLE); + } + + Square, Random HAS_NO_OTERRAIN; + { + OTerrain = olterraincontainer(CHEST_OF_DRAWERS); + } + } + } + + RandomLevel 0:3; + { + Room + { + AllowBoobyTrappedDoors = false; + Flags = NO_MONSTER_GENERATION; + + Square, Random HAS_NO_OTERRAIN; + { + OTerrain = decoration(ANVIL); + } + + Square, Random HAS_NO_OTERRAIN; + { + OTerrain = decoration(FORGE); + } + + Square, Random HAS_NO_OTERRAIN; + { + OTerrain = decoration(WORK_BENCH); + } + } + } + + RandomLevel 0:3; + { + Room + { + AllowBoobyTrappedDoors = false; + Flags = NO_MONSTER_GENERATION; + + Square, Random HAS_NO_OTERRAIN; + { + OTerrain = decoration(CHEAP_BED); + Times = 2:6; + } + } + } + + RandomLevel 0:4; + { + Square, Random HAS_NO_OTERRAIN|IN_ROOM; + { + OTerrain = decoration(WELL); + } + } + + RandomLevel 1:4; + { + Square, Random HAS_NO_OTERRAIN; + { + Character = spider(PHASE); + Times = 1:3; + } + } +} diff --git a/Script/dungeons/Irinox.dat b/Script/dungeons/Irinox.dat new file mode 100644 index 000000000..b918cecd0 --- /dev/null +++ b/Script/dungeons/Irinox.dat @@ -0,0 +1,104 @@ +/* + * + * Iter Vehemens ad Necem (IVAN) + * Copyright (C) Timo Kiviluoto + * Released under the GNU General + * Public License + * + * See LICENSING which should be included + * along with this file for more details + * + */ + +/* + * NOTICE!!! + * + * This file contains SPOILERS, which might ruin your IVAN experience + * totally. Also, editing anything can DESTROY GAME BALANCE or CAUSE + * OBSCURE BUGS if you don't know what you're doing. So from here on, + * proceed at your own risk! + */ + +/* Work in progress! */ + +Dungeon IRINOX; +{ + Levels = 1; + Description = "Irinox"; + ShortDescription = "Irinox"; + + Level 0; + { + FillSquare = solidterrain(GRASS_TERRAIN), 0; + Size = 42, 26; + GenerateMonsters = false; + Rooms = 0; + Items = 0; + IsOnGround = true; + TeamDefault = MONSTER_TEAM; + LOSModifier = 48; + IgnoreDefaultSpecialSquares = false; + AutoReveal = true; + CanGenerateBone = false; + DifficultyBase = 50; + DifficultyDelta = 0; + EnchantmentMinusChanceBase = -15; + EnchantmentMinusChanceDelta = 0; + EnchantmentPlusChanceBase = 0; + EnchantmentPlusChanceDelta = 0; + BackGroundType = GREEN_FRACTAL; + EarthquakesAffectTunnels = true; + AudioPlayList = + { + 1, + "Empty.mid"; + } + + Square, Random; + { + Items == beartrap { Team = MONSTER_TEAM; IsActive = false; } + Times = 0:3; + } + + Square, Random; + { + Items == Random { Category = FOOD; } + Times = 1:2; + } + + Square, Random NOT_IN_ROOM|HAS_NO_OTERRAIN; + { + OTerrain = boulder(1); + Times = 10; + } + + Square, Random NOT_IN_ROOM|HAS_NO_OTERRAIN; + { + OTerrain = boulder(2); + Times = 10; + } + + RoomDefault + { + Pos = 2:40,2:24; + Size = 4:6,4:6; + AltarPossible = false; + WallSquare = solidterrain(GRASS_TERRAIN), wall(BRICK_PRIMITIVE); + FloorSquare = solidterrain(GRASS_TERRAIN), 0; + DoorSquare = solidterrain(GRASS_TERRAIN), BALSA_WOOD door; + GenerateDoor = true; + DivineMaster = 0; + GenerateTunnel = false; + GenerateLanterns = false; + Type = ROOM_NORMAL; + GenerateFountains = false; + AllowLockedDoors = false; + AllowBoobyTrappedDoors = false; + Shape = ROUND_CORNERS; + IsInside = true; + GenerateWindows = true; + UseFillSquareWalls = false; + Flags = 0; + } + } +} diff --git a/Script/dungeons/Mondedr.dat b/Script/dungeons/Mondedr.dat new file mode 100644 index 000000000..c651b886a --- /dev/null +++ b/Script/dungeons/Mondedr.dat @@ -0,0 +1,104 @@ +/* + * + * Iter Vehemens ad Necem (IVAN) + * Copyright (C) Timo Kiviluoto + * Released under the GNU General + * Public License + * + * See LICENSING which should be included + * along with this file for more details + * + */ + +/* + * NOTICE!!! + * + * This file contains SPOILERS, which might ruin your IVAN experience + * totally. Also, editing anything can DESTROY GAME BALANCE or CAUSE + * OBSCURE BUGS if you don't know what you're doing. So from here on, + * proceed at your own risk! + */ + + /* Work in progress! */ + +Dungeon MONDEDR; +{ + Levels = 1; + Description = "Mondedr"; + ShortDescription = "Mondedr"; + + Level 0; + { + FillSquare = solidterrain(GRASS_TERRAIN), 0; + Size = 42, 26; + GenerateMonsters = false; + Rooms = 0; + Items = 0; + IsOnGround = true; + TeamDefault = MONSTER_TEAM; + LOSModifier = 48; + IgnoreDefaultSpecialSquares = false; + AutoReveal = true; + CanGenerateBone = false; + DifficultyBase = 50; + DifficultyDelta = 0; + EnchantmentMinusChanceBase = -15; + EnchantmentMinusChanceDelta = 0; + EnchantmentPlusChanceBase = 0; + EnchantmentPlusChanceDelta = 0; + BackGroundType = GREEN_FRACTAL; + EarthquakesAffectTunnels = true; + AudioPlayList = + { + 1, + "Empty.mid"; + } + + Square, Random; + { + Items == beartrap { Team = MONSTER_TEAM; IsActive = false; } + Times = 0:3; + } + + Square, Random; + { + Items == Random { Category = FOOD; } + Times = 1:2; + } + + Square, Random NOT_IN_ROOM|HAS_NO_OTERRAIN; + { + OTerrain = boulder(1); + Times = 10; + } + + Square, Random NOT_IN_ROOM|HAS_NO_OTERRAIN; + { + OTerrain = boulder(2); + Times = 10; + } + + RoomDefault + { + Pos = 2:40,2:24; + Size = 4:6,4:6; + AltarPossible = false; + WallSquare = solidterrain(GRASS_TERRAIN), wall(BRICK_PRIMITIVE); + FloorSquare = solidterrain(GRASS_TERRAIN), 0; + DoorSquare = solidterrain(GRASS_TERRAIN), BALSA_WOOD door; + GenerateDoor = true; + DivineMaster = 0; + GenerateTunnel = false; + GenerateLanterns = false; + Type = ROOM_NORMAL; + GenerateFountains = false; + AllowLockedDoors = false; + AllowBoobyTrappedDoors = false; + Shape = ROUND_CORNERS; + IsInside = true; + GenerateWindows = true; + UseFillSquareWalls = false; + Flags = 0; + } + } +} diff --git a/Script/dungeons/Pyramid.dat b/Script/dungeons/Pyramid.dat new file mode 100644 index 000000000..b037c6429 --- /dev/null +++ b/Script/dungeons/Pyramid.dat @@ -0,0 +1,716 @@ +/* + * + * Iter Vehemens ad Necem (IVAN) + * Copyright (C) Timo Kiviluoto + * Released under the GNU General + * Public License + * + * See LICENSING which should be included + * along with this file for more details + * + */ + +/* + * NOTICE!!! + * + * This file contains SPOILERS, which might ruin your IVAN experience + * totally. Also, editing anything can DESTROY GAME BALANCE or CAUSE + * OBSCURE BUGS if you don't know what you're doing. So from here on, + * proceed at your own risk! + */ + +Dungeon PYRAMID; +{ + Levels = 5; + Description = "Pyramid"; + ShortDescription = "PY"; + + LevelDefault + { + FillSquare = solidterrain(SAND_TERRAIN), SAND_STONE wall(BRICK_OLD); + TunnelSquare = solidterrain(SAND_TERRAIN), 0; + Size = 64, 36; + Rooms = 10:30; + Items = 25:50; + GenerateMonsters = true; + IsOnGround = false; + TeamDefault = MONSTER_TEAM; + LOSModifier = 16; + IgnoreDefaultSpecialSquares = false; + DifficultyBase = 120; + DifficultyDelta = 30; + MonsterAmountBase = 25; + MonsterAmountDelta = 5; + MonsterGenerationIntervalBase = 100; + MonsterGenerationIntervalDelta = -10; + CanGenerateBone = true; + ItemMinPriceBase = 80; + ItemMinPriceDelta = 10; + EnchantmentMinusChanceBase = 0; + EnchantmentMinusChanceDelta = 0; + EnchantmentPlusChanceBase = 15; + EnchantmentPlusChanceDelta = 10; + BackGroundType = YELLOW_FRACTAL; + IsCatacomb = true; + EarthquakesAffectTunnels = true; + AudioPlayList = + { + 3, + "Dungeon.mid", + "Dungeon2.mid", + "Dungeon3.mid"; + } + + /* Decorations */ + Square, Random HAS_NO_OTERRAIN; + { + Items == bone; + Times = 12:24; + } + + Square, Random HAS_NO_OTERRAIN; + { + Items == skull; + Times = 4:8; + } + + Square, Random HAS_NO_OTERRAIN|IN_ROOM; + { + OTerrain = RED_SAND_STONE coffin; + Times = 2:6; + } + + /* Traps & Monsters */ + Square, Random HAS_NO_OTERRAIN; + { + Items == beartrap { Team = MONSTER_TEAM; IsActive = true; } + Times = 3:9; + } + + Square, Random HAS_NO_OTERRAIN; + { + Character = necromancer(APPRENTICE_NECROMANCER); + Times = 0:3; + } + + Square, Random HAS_NO_OTERRAIN; + { + Character = skeleton(WARRIOR); + Times = 2:4; + } + + RoomDefault + { + Pos = 2:XSize-5,2:YSize-5; + Size = 5:9,5:9; + AltarPossible = true; + FloorSquare = SAND_STONE solidterrain(PARQUET), 0; + DoorSquare = SAND_STONE solidterrain(PARQUET), IRON door(BARDOOR); + GenerateDoor = true; + DivineMaster = 0; + GenerateTunnel = true; + GenerateLanterns = true; + Type = ROOM_NORMAL; + GenerateFountains = true; + AllowLockedDoors = true; + AllowBoobyTrappedDoors = true; + Shape = ROUND_CORNERS; + IsInside = true; + GenerateWindows = false; + UseFillSquareWalls = true; + Flags = 0; + } + } + + Level 0; + { + Rooms = 2:3; + Size = 42, 26; + + Room + { + Size = 21,21; + AltarPossible = false; + GenerateFountains = false; + GenerateLanterns = false; + Shape = MAZE_ROOM; + + /* Traps & Stairs */ + Square, Random HAS_NO_OTERRAIN; + { + OTerrain = stairs(STAIRS_DOWN); + EntryIndex = STAIRS_DOWN; + } + + Square, Random HAS_NO_OTERRAIN; + { + Items == gastrap { Team = MONSTER_TEAM; IsActive = true; } + Times = 2:5; + } + + /* Monsters */ + Square, Random HAS_NO_OTERRAIN; + { + Character = zombie; + Times = 5:10; + } + + Square, Random HAS_NO_OTERRAIN; + { + Character = skeleton; + Times = 5:10; + } + + Square, Random HAS_NO_OTERRAIN; + { + Character = spider(GIANT); + Times = 2:4; + } + } + + Square, Random NOT_WALKABLE|ATTACHABLE; + { + OTerrain = stairs(STAIRS_UP); + EntryIndex = STAIRS_UP; + AttachRequired = true; + } + } + + Level 1; + { + Size = 20, 100; + + RoomDefault + { + Pos = 2:XSize-5,2:YSize-5; + } + + Square, Random; + { + Character = veterankamikazedwarf(MORTIFER); + } + + /* Stairs & Traps */ + Square, BoundedRandom 1, 1, 18, 18, NOT_WALKABLE|ATTACHABLE; + { + OTerrain = stairs(STAIRS_UP); + EntryIndex = STAIRS_UP; + AttachRequired = true; + } + + Square, BoundedRandom 1, 81, 18, 98, NOT_WALKABLE|ATTACHABLE; + { + OTerrain = stairs(STAIRS_DOWN); + EntryIndex = STAIRS_DOWN; + AttachRequired = true; + } + + Square, Random HAS_NO_OTERRAIN; + { + Items == mine { Team = MONSTER_TEAM; IsActive = true; } + Times = 1:4; + } + + Square, Random HAS_NO_OTERRAIN; + { + Items == gastrap { Team = MONSTER_TEAM; IsActive = true; } + Times = 1:4; + } + } + + Level 2; + { + Size = 100, 20; + IsCatacomb = false; + FillSquare = CONCRETE solidterrain(GROUND), CONCRETE wall(BRICK_OLD); + TunnelSquare = CONCRETE solidterrain(GROUND), 0; + + RoomDefault + { + Pos = 2:XSize-5,2:YSize-5; + FloorSquare = CONCRETE solidterrain(PARQUET), 0; + DoorSquare = CONCRETE solidterrain(PARQUET), STEEL door(BARDOOR); + } + + /* Stairs & Traps */ + Square, BoundedRandom 81, 1, 98, 18, NOT_WALKABLE|ATTACHABLE; + { + OTerrain = stairs(STAIRS_UP); + EntryIndex = STAIRS_UP; + AttachRequired = true; + } + + Square, BoundedRandom 1, 1, 18, 18, NOT_WALKABLE|ATTACHABLE; + { + OTerrain = stairs(STAIRS_DOWN); + EntryIndex = STAIRS_DOWN; + AttachRequired = true; + } + + Square, Random HAS_NO_OTERRAIN; + { + Items == mine { Team = MONSTER_TEAM; IsActive = true; } + Times = 1:4; + } + + Square, Random HAS_NO_OTERRAIN; + { + Items == mine(BIG_MINE) { Team = MONSTER_TEAM; IsActive = true; } + Times = 0:3; + } + + Square, Random HAS_NO_OTERRAIN; + { + Items == gastrap { Team = MONSTER_TEAM; IsActive = true; } + Times = 1:4; + } + } + + Level 3; + { + Size = 20, 100; + IsCatacomb = false; + FillSquare = HARDENED_CONCRETE solidterrain(GROUND), HARDENED_CONCRETE wall(BRICK_FINE); + TunnelSquare = HARDENED_CONCRETE solidterrain(GROUND), 0; + + RoomDefault + { + Pos = 2:XSize-5,2:YSize-5; + FloorSquare = HARDENED_CONCRETE solidterrain(PARQUET), 0; + DoorSquare = HARDENED_CONCRETE solidterrain(PARQUET), STEEL door(BARDOOR); + } + + /* Stairs & Traps */ + Square, BoundedRandom 1, 81, 18, 98, NOT_WALKABLE|ATTACHABLE; + { + OTerrain = stairs(STAIRS_UP); + EntryIndex = STAIRS_UP; + AttachRequired = true; + } + + Square, BoundedRandom 1, 1, 18, 18, NOT_WALKABLE|ATTACHABLE; + { + OTerrain = stairs(STAIRS_DOWN); + EntryIndex = STAIRS_DOWN; + AttachRequired = true; + } + + Square, Random HAS_NO_OTERRAIN; + { + Items == mine { Team = MONSTER_TEAM; IsActive = true; } + Times = 2:4; + } + + Square, Random HAS_NO_OTERRAIN; + { + Items == mine(BIG_MINE) { Team = MONSTER_TEAM; IsActive = true; } + Times = 1:3; + } + + Square, Random HAS_NO_OTERRAIN; + { + Items == gastrap { Team = MONSTER_TEAM; IsActive = true; } + Times = 2:4; + } + + Room + { + Size = 9,9; + AltarPossible = false; + GenerateFountains = false; + GenerateLanterns = false; + Shape = MAZE_ROOM; + + Square, Random HAS_NO_OTERRAIN; + { + Character = mysticfrog(DARK); + } + + Square, Random HAS_NO_OTERRAIN; + { + Character = frog(GREATER_DARK); + } + + Square, Random HAS_NO_OTERRAIN; + { + Character = frog(DARK); + Times = 2:4; + } + } + } + + Level 4; + { + Description = "ancient bunker"; + ShortDescription = "Bunker"; + Items = 0; + IsCatacomb = false; + CanGenerateBone = false; + IgnoreDefaultSpecialSquares = true; + FillSquare = SUPER_CONCRETE solidterrain(GROUND), SUPER_CONCRETE wall(BRICK_FINE); + TunnelSquare = SUPER_CONCRETE solidterrain(GROUND), 0; + BackGroundType = GRAY_FRACTAL; + LevelMessage = "You feel a sudden pang of pain in the back of your brain."; + + RoomDefault + { + FloorSquare = SUPER_CONCRETE solidterrain(PARQUET), 0; + DoorSquare = SUPER_CONCRETE solidterrain(PARQUET), STAINLESS_STEEL door(BARDOOR); + } + + /* TODO: boss? */ + + Square, Random NOT_WALKABLE|ATTACHABLE; + { + OTerrain = stairs(STAIRS_UP); + EntryIndex = STAIRS_UP; + AttachRequired = true; + } + + /* the Nuke */ + Room + { + Size = 11,11; + GenerateLanterns = true; + GenerateFountains = false; + AltarPossible = false; + GenerateDoor = false; + GenerateTunnel = false; + UseFillSquareWalls = false; + WallSquare = LEAD solidterrain(PARQUET), LEAD wall(BRICK_FINE); + FloorSquare = LEAD solidterrain(PARQUET), 0; + Shape = RECTANGLE; + Flags = NO_MONSTER_GENERATION; + + Square, Pos 0,1; + { + GTerrain = LEAD solidterrain(PARQUET); + OTerrain = LEAD door { Parameters = LOCKED; } + AttachRequired = true; + } + + Square, Pos 10,9; + { + GTerrain = LEAD solidterrain(PARQUET); + OTerrain = LEAD door { Parameters = LOCKED; } + AttachRequired = true; + } + + OTerrainMap + { + Size = 5,5; + Pos = 3,3; + Types + { + # = GLASS wall(BRICK_FINE); + _ = PLASTIC_STEEL decoration(PEDESTAL); + } + } + { + ##### + #...# + #...# + #...# + ##### + } + + Square, Pos 5,5; + { + Items == nuke; + } + } + + /* Mind Worms */ + Room + { + Size = 7,7; + GenerateFountains = false; + AltarPossible = false; + Shape = RECTANGLE; + + OTerrainMap + { + Pos = 2,2; + Size = 3,3; + + Types + { + # = STAINLESS_STEEL barwall; + } + } + { + ### + #.# + ### + } + + Square, Pos 3,3; + { + Character = mindworm(BOIL); + } + } + + Room + { + Size = 7,7; + GenerateFountains = false; + AltarPossible = false; + Shape = RECTANGLE; + + OTerrainMap + { + Pos = 2,2; + Size = 3,3; + + Types + { + # = STAINLESS_STEEL barwall; + } + } + { + ### + #.# + ### + } + + Square, Pos 3,3; + { + Character = mindworm(BOIL); + } + } + + Room + { + Size = 7,7; + GenerateFountains = false; + AltarPossible = false; + Shape = RECTANGLE; + + OTerrainMap + { + Pos = 2,2; + Size = 3,3; + + Types + { + # = STAINLESS_STEEL barwall; + } + } + { + ### + #.# + ### + } + + Square, Pos 3,3; + { + Character = mindworm(BOIL); + } + } + + /* Treasure */ + Room + { + GenerateLanterns = true; + GenerateFountains = false; + AltarPossible = false; + Shape = RECTANGLE; + + Square, Random HAS_NO_OTERRAIN; + { + Items == Random { MinPrice = 150; ConfigFlags = NO_BROKEN; } + Times = 20; + } + } + + Room + { + GenerateLanterns = true; + GenerateFountains = false; + AltarPossible = false; + Shape = RECTANGLE; + + Square, Random HAS_NO_OTERRAIN; + { + Items == Random { MinPrice = 150; ConfigFlags = NO_BROKEN; } + Times = 20; + } + } + } + + /* Decorations */ + RandomLevel 0:3; + { + Square, Random HAS_NO_OTERRAIN|IN_ROOM; + { + OTerrain = fountain { SecondaryMaterial = LIQUID_DARKNESS; } + } + } + + RandomLevel 0:3; + { + Square, Random HAS_NO_OTERRAIN|IN_ROOM; + { + OTerrain = fountain { SecondaryMaterial = POISON_LIQUID; } + } + } + + RandomLevel 0:3; + { + Square, Random HAS_NO_OTERRAIN|IN_ROOM; + { + OTerrain = fountain { SecondaryMaterial = SICK_BLOOD; } + } + } + + /* Quicksand Pool */ + RandomLevel 1:3; + { + Room + { + GenerateLanterns = false; + GenerateFountains = false; + AltarPossible = false; + FloorSquare = QUICK_SAND liquidterrain(UNDERGROUND_LAKE), 0; + Flags = NO_MONSTER_GENERATION; + } + } + + /* Cactus Room */ + RandomLevel 1:3; + { + Room + { + FloorSquare = RED_SAND solidterrain(SAND_TERRAIN), 0; + Shape = RECTANGLE; + + Square, Random HAS_NO_OTERRAIN; + { + OTerrain = decoration(CACTUS); + Times = 4:8; + } + } + } + + /* Sepulcher */ + RandomLevel 1:3; + { + Room + { + GenerateLanterns = false; + GenerateFountains = false; + GenerateDoor = false; + GenerateTunnel = false; + AltarPossible = false; + Size = 5,5; + UseFillSquareWalls = true; + FloorSquare = IRON solidterrain(PARQUET), 0; + Shape = RECTANGLE; + Flags = NO_MONSTER_GENERATION; + + OTerrainMap + { + Pos = 1,1; + Size = 3,3; + Types + { + # = IRON wall(BRICK_OLD); + } + } + { + ### + #.# + ### + } + + Square, Pos 2,2; + { + Character = genie { Inventory = { 2, oillamp, Random { MinPrice = 2000; } } } + Items == ARCANITE ARCANITE meleeweapon(TWO_HANDED_SCIMITAR); + } + } + } + + /* Minibosses */ + RandomLevel 1:3; + { + Room + { + Size = 9,9; + AltarPossible = false; + GenerateFountains = false; + GenerateLanterns = false; + Shape = MAZE_ROOM; + + Square, Random HAS_NO_OTERRAIN; + { + Character = necromancer(APPRENTICE_NECROMANCER); + Times = 2; + } + + Square, Random HAS_NO_OTERRAIN; + { + Character = necromancer(MASTER_NECROMANCER); + } + } + } + + RandomLevel 1:3; + { + Room + { + Size = 9,9; + AltarPossible = false; + GenerateFountains = false; + GenerateLanterns = false; + Shape = MAZE_ROOM; + + Square, Random HAS_NO_OTERRAIN; + { + Character = vampire; + Times = 3; + } + } + } + + RandomLevel 1:3; + { + Room + { + Size = 9,9; + AltarPossible = false; + GenerateFountains = false; + GenerateLanterns = false; + Shape = MAZE_ROOM; + + Square, Random HAS_NO_OTERRAIN; + { + Character = gasghoul; + Times = 2:5; + } + } + } + + RandomLevel 1:3; + { + Room + { + Size = 9,9; + AltarPossible = false; + GenerateFountains = false; + GenerateLanterns = false; + Shape = MAZE_ROOM; + + Square, Random HAS_NO_OTERRAIN; + { + Character = golem(WRAITH_BONE); + Times = 2:5; + } + + Square, Random HAS_NO_OTERRAIN; + { + Character = golem(ACIDOUS_BLOOD); + } + } + } +} diff --git a/Script/dungeons/RebelCamp.dat b/Script/dungeons/RebelCamp.dat new file mode 100644 index 000000000..eba4f9261 --- /dev/null +++ b/Script/dungeons/RebelCamp.dat @@ -0,0 +1,406 @@ +/* + * + * Iter Vehemens ad Necem (IVAN) + * Copyright (C) Timo Kiviluoto + * Released under the GNU General + * Public License + * + * See LICENSING which should be included + * along with this file for more details + * + */ + +/* + * NOTICE!!! + * + * This file contains SPOILERS, which might ruin your IVAN experience + * totally. Also, editing anything can DESTROY GAME BALANCE or CAUSE + * OBSCURE BUGS if you don't know what you're doing. So from here on, + * proceed at your own risk! + */ + +Dungeon REBEL_CAMP; +{ + Levels = 1; + Description = "Rebel Camp"; + ShortDescription = "RC"; + + Level 0; + { + LevelMessage = "You pass the sentries and draw closer to the camp."; + Description = "Rebel Camp"; + ShortDescription = "Rebel Camp"; + FillSquare = solidterrain(GRASS_TERRAIN), 0; + Size = 55, 55; + GenerateMonsters = false; + Rooms = 10:15; + Items = 0; + IsOnGround = true; + TeamDefault = REBEL_TEAM; + LOSModifier = 48; + IgnoreDefaultSpecialSquares = false; + AutoReveal = false; + CanGenerateBone = false; + DifficultyBase = 50; + DifficultyDelta = 0; + EnchantmentMinusChanceBase = 0; + EnchantmentMinusChanceDelta = 0; + EnchantmentPlusChanceBase = 5; + EnchantmentPlusChanceDelta = 0; + BackGroundType = GREEN_FRACTAL; + EarthquakesAffectTunnels = false; + AudioPlayList = + { + 1, + "Empty.mid"; + } + + RoomDefault + { + Pos = 2:XSize-5,2:YSize-5; + Size = 4:6,4:6; + AltarPossible = false; + WallSquare = solidterrain(GRASS_TERRAIN), wall(TENT_WALL); + FloorSquare = solidterrain(GRASS_TERRAIN), 0; + DoorSquare = solidterrain(GRASS_TERRAIN), door(CURTAIN); + GenerateDoor = true; + DivineMaster = 0; + GenerateTunnel = false; + GenerateLanterns = true; + Type = ROOM_NORMAL; + GenerateFountains = false; + AllowLockedDoors = false; + AllowBoobyTrappedDoors = false; + Shape = ROUND_CORNERS; + IsInside = true; + GenerateWindows = false; + UseFillSquareWalls = false; + Flags = 0; + } + + Square, Pos XSize/2, 0; + { + EntryIndex = STAIRS_UP; + } + + /* Forest trees & Decorations */ + Square, Random NOT_IN_ROOM|HAS_NO_OTERRAIN; + { + OTerrain = boulder(1); + Times = 10; + } + + Square, Random NOT_IN_ROOM|HAS_NO_OTERRAIN; + { + OTerrain = boulder(2); + Times = 10; + } + + Square, Random NOT_IN_ROOM|HAS_NO_OTERRAIN; + { + OTerrain = decoration(BIRCH); + Times = 50:150; + } + + Square, Random NOT_IN_ROOM|HAS_NO_OTERRAIN; + { + OTerrain = decoration(TEAK); + Times = 50:150; + } + + Square, Random NOT_IN_ROOM|HAS_NO_OTERRAIN; + { + OTerrain = decoration(DEAD_TREE); + Times = 10; + } + + Square, BoundedRandom XSize/3, YSize/3, 4*XSize/3, 4*YSize/3, NOT_IN_ROOM|HAS_NO_OTERRAIN; + { + OTerrain = decoration(WELL); + } + + Square, Random IN_ROOM|HAS_NO_OTERRAIN; + { + Items == itemcontainer(CHEST) + { + Parameters = LOCKED; + ItemsInside = { 3, Random { Category = WEAPON; }, potion(VIAL) { SecondaryMaterial = HEALING_LIQUID; }, DARK_BREAD loaf; } + } + } + + /* People */ + Square, Pos 0, 0; + { + Character = guard(REBEL) { WayPoint = { 4, 0, 0, XSize - 1, 0, XSize - 1, YSize - 1, 0, YSize - 1; } } + } + + Square, Random NOT_IN_ROOM|HAS_NO_OTERRAIN; + { + Character = cossack(REBEL_SOLDIER); + Times = 15; + } + + Square, Random NOT_IN_ROOM|HAS_NO_OTERRAIN; + { + Character = cossack(REBEL_LIEUTENANT); + Times = 5; + } + + Square, Random NOT_IN_ROOM; + { + Character = farmer(REBEL_CAMP); + Times = 5; + } + + Square, Random NOT_IN_ROOM; + { + Character = housewife(REBEL_CAMP); + Times = 5; + } + + Square, Random IN_ROOM; + { + Character = child(BOY); + Times = 1:3; + } + + Square, Random IN_ROOM; + { + Character = child(GIRL); + Times = 1:3; + } + + Square, Random IN_ROOM; + { + Character = guard(REBEL); + Times = 2:5; + } + + Square, Random IN_ROOM; + { + Character = mysticfrog(LIGHT); + Times = 1:2; + } + + Square, Random IN_ROOM; + { + Character = necromancer(MASTER_NECROMANCER); + Times = 2; + } + + Square, Random NOT_IN_ROOM|HAS_NO_OTERRAIN; + { + Character = largerat; + Times = 1:4; + } + + /* Leader Room */ + Room + { + Size = 7,7; + GenerateDoor = false; + GenerateLanterns = false; + Type = ROOM_OWNED_AREA; + + OTerrainMap + { + Pos = 1,1; + Size = 5,5; + + Types + { + a = decoration(ARM_CHAIR); + | = decoration(CARPET); + d = decoration(DESK); + b = decoration(EXPENSIVE_BED); + T = decoration(TABLE); + B = decoration(BENCH); + x = olterraincontainer(CHEST_OF_DRAWERS) { ItemsInside == Random { MinPrice = 100; Times = 3; Category = CLOAK|BODY_ARMOR|BELT|BOOT|GAUNTLET; ConfigFlags = NO_BROKEN; } } + } + } + { + .da.. + ..... + ..T.b + x.... + .B.B. + } + + ItemMap + { + Pos = 0,0; + Size = 7,6; + + Types + { + # == 0; + 2 == lantern { SquarePosition = DOWN; } + 3 == lantern { SquarePosition = RIGHT; } + 4 == lantern { SquarePosition = LEFT; } + c == Random { MinPrice = 350; Category = WEAPON|SHIELD; ConfigFlags = NO_BROKEN; Enchantment = 3; } + n == trinket(POTTED_PLANT); + } + } + { + .#2###. + ##...## + #.....# + 3c....4 + #....n# + ##...## + } + + CharacterMap + { + Pos = 1,1; + Size = 5,5; + + Types + { + # = 0; + h = harvan { Flags = IS_MASTER; } + g = guard(REBEL); + } + } + { + #.h.# + g...g + ..... + ..... + #...# + } + + Square, Pos 3,6; + { + OTerrain = door(CURTAIN); + } + } + + /* Shop */ + Room + { + Size = 7,7; + GenerateDoor = false; + GenerateLanterns = false; + Type = ROOM_SHOP; + + ItemMap + { + Pos = 0,0; + Size = 7,7; + + Types + { + # == 0; + 1 == lantern { SquarePosition = UP; } + 2 == lantern { SquarePosition = DOWN; } + 3 == lantern { SquarePosition = RIGHT; } + a == Random { MinPrice = 250; Category = HELMET|CLOAK|BODY_ARMOR|BELT|BOOT|GAUNTLET; } + d == Random { MinPrice = 250; Category = RING|AMULET; } + w == Random { MinPrice = 500; Category = WEAPON|SHIELD; } + e == Random { MinPrice = 10; Category = FOOD|POTION; } + u == Random { MinPrice = 200; Category = WAND|TOOL; } + v == Random { MinPrice = 500; Category = SCROLL|TOOL; } + s = { 2, belt(BELT_OF_CARRYING), wand(WAND_OF_STRIKING); } + } + } + { + .##2##. + ##eeu## + #aeeew# + 3au.ee. + #adevv# + ##dws## + .##1##. + } + + Square, Pos 3,3; + { + Character = shopkeeper(REBEL_CAMP) { Flags = IS_MASTER; } + OTerrain = decoration(CHAIR); + } + + Square, Pos 6,3; + { + OTerrain = door(CURTAIN); + } + Square, Pos 7,2; + { + Character = guard(REBEL); + } + Square, Pos 7,4; + { + Character = guard(REBEL); + } + } + + /* Kamikaze Room */ + Room + { + Size = 6,6; + + CharacterMap + { + Pos = 1,1; + Size = 4,4; + + Types + { + k = kamikazedwarf(LEGIFER); + K = veterankamikazedwarf(LEGIFER); + } + } + { + .kk. + k..k + kK.k + .kk. + } + } + + /* Other Rooms */ + Room + { + Shape = RECTANGLE; + + Square, Random; + { + OTerrain = decoration(CHEAP_BED); + Times = 4:10; + } + } + + Room + { + Shape = RECTANGLE; + + Square, Random; + { + OTerrain = decoration(CHEAP_BED); + Times = 4:10; + } + } + + Room + { + Square, Random HAS_NO_OTERRAIN; + { + OTerrain = decoration(TABLE); + } + + Square, Random HAS_NO_OTERRAIN; + { + OTerrain = decoration(CHAIR); + Times = 1:3; + } + } + + Room + { + Square, Random HAS_NO_OTERRAIN; + { + OTerrain = decoration(WORK_BENCH); + } + } + } +} diff --git a/Script/dungeons/UnderwaterTunnel.dat b/Script/dungeons/UnderwaterTunnel.dat index 93e0160ec..188b2177d 100644 --- a/Script/dungeons/UnderwaterTunnel.dat +++ b/Script/dungeons/UnderwaterTunnel.dat @@ -156,7 +156,7 @@ Dungeon UNDER_WATER_TUNNEL; Parameters = LOCKED; ItemsInside = { 3, Random { MinPrice = 300; Chance = 75; Category = WEAPON|SHIELD; }, Random { MinPrice = 300; Chance = 75; Category = HELMET|CLOAK|BODY_ARMOR|GAUNTLET|BELT|BOOT; }, - Random { Category = FOOD|POTION|VALUABLE; } + Random { Category = WAND|RING|POTION; } } } Character = golem(BAMBOO_WOOD); @@ -713,6 +713,7 @@ Dungeon UNDER_WATER_TUNNEL; Character = lobhse; } + /* Rewards */ Square, Random; { Items == SPIDER_SILK bodyarmor(PLATE_MAIL); @@ -720,7 +721,7 @@ Dungeon UNDER_WATER_TUNNEL; Square, Random; { - Items == taiaha(BROKEN); + Items == taiaha { Enchantment = 2; } } } } diff --git a/Script/dungeons/XinrochTomb.dat b/Script/dungeons/XinrochTomb.dat index f06d35d1d..699504345 100644 --- a/Script/dungeons/XinrochTomb.dat +++ b/Script/dungeons/XinrochTomb.dat @@ -213,6 +213,7 @@ Dungeon XINROCH_TOMB; FloorSquare = ASH solidterrain(SNOW_TERRAIN), 0; GenerateDoor = false; DivineMaster = INFUSCOR; + Type = ROOM_OWNED_AREA; GenerateLanterns = false; IsInside = false; GenerateWindows = false; @@ -404,7 +405,7 @@ Dungeon XINROCH_TOMB; | = 0; % = 0; g = guard(TOMB_ENTRY); - E = guard(TOMB_ENTRY_MASTER); + E = guard(TOMB_ENTRY_MASTER) { Flags = IS_MASTER; } y = mysticfrog(DARK); k = kamikazedwarf(INFUSCOR); K = veterankamikazedwarf(INFUSCOR); @@ -670,7 +671,7 @@ Dungeon XINROCH_TOMB; { Square, Random; { - Items == PSYPHER bodyarmor(ARMOR_OF_GREAT_HEALTH) { Enchantment = 6; LifeExpectancy = 1000; Chance = 5; } + Items == PSYPHER bodyarmor(ARMOR_OF_GREAT_HEALTH) { Enchantment = 6; LifeExpectancy = 1000:10000; Chance = 5; } } } @@ -967,7 +968,7 @@ Dungeon XINROCH_TOMB; Square, Pos 3,3; { - Items == cauldron { SecondaryMaterial = TROLL_BLOOD; } + Items == cauldron; } } } @@ -1233,12 +1234,18 @@ Dungeon XINROCH_TOMB; Square, Random IN_ROOM; { Character = golem(ICE); - Times = 2:5; + Times = 1:4; } Square, Random IN_ROOM; { - Character = golem(BLUE_CRYSTAL); + Character = golem(SNOW); + Times = 1:3; + } + + Square, Random IN_ROOM; + { + Character = golem(JOTUN_ICE); Times = 2; } @@ -1630,20 +1637,12 @@ Dungeon XINROCH_TOMB; Square, Pos 1,20; { OTerrain = stairs(STAIRS_UP); - } - - Square, Pos 1,20; - { EntryIndex = STAIRS_UP; } Square, Pos 1,1; { OTerrain = stairs(STAIRS_DOWN); - } - - Square, Pos 1,1; - { EntryIndex = STAIRS_DOWN; } diff --git a/Script/glterra.dat b/Script/glterra.dat index cfd19e02c..92e99034e 100644 --- a/Script/glterra.dat +++ b/Script/glterra.dat @@ -133,7 +133,7 @@ liquidterrain /* glterrain-> */ Config POOL; { NameSingular = "pool"; - SitMessage = "You sit on the pool. Oddly enough, you sink. You feel stupid."; + SitMessage = "You sit on the pool surface. Oddly enough, you sink. You feel stupid."; } Config UNDERGROUND_LAKE; @@ -141,7 +141,15 @@ liquidterrain /* glterrain-> */ UsesLongAdjectiveArticle = true; Adjective = "underground"; NameSingular = "lake"; - /*SitMessage = "You sit on the pool. Oddly enough, you sink. You feel stupid.";*/ + SitMessage = "You sit on the lake surface. Oddly enough, you sink. You feel stupid."; + UseBorderTiles = true; + BorderTilePriority = 10; + } + + Config SEA; + { + NameSingular = "sea"; + SitMessage = "You sit on the sea waves. Oddly enough, you sink. You feel stupid."; UseBorderTiles = true; BorderTilePriority = 10; } diff --git a/Script/item.dat b/Script/item.dat index 5f899636f..196cf6adf 100644 --- a/Script/item.dat +++ b/Script/item.dat @@ -25,6 +25,7 @@ item { + DescriptiveInfo = ""; Possibility = 0; IsDestroyable = true; CanBeWished = true; @@ -101,7 +102,8 @@ item /* Obligatory: AttachedGod */ WieldedBitmapPos = 176, 0; IsQuestItem = false; - IsGoodWithPlants = true; + IsGoodWithPlants = false; + IsGoodWithUndead = false; CreateLockConfigurations = false; /* Can't be overridden by Configs */ CanBePickedUp = true; /* Obligatory if helmet: CoverPercentile */ @@ -154,6 +156,7 @@ lantern WallBitmapPos = 0, 192; AttachedGod = LEGIFER; WieldedBitmapPos = 160, 160; + DescriptiveInfo = "Often carried strapped on a backpack to leave your hands free, a lantern is invaluable when delving through the dark dungeons."; Config BROKEN; { @@ -195,7 +198,10 @@ can /* materialcontainer-> */ AttachedGod = NONE; WieldedBitmapPos = 176, 144; IsValuable = false; - AllowedDungeons = { 5, NEW_ATTNAM, ATTNAM, UNDER_WATER_TUNNEL, ELPURI_CAVE, BLACK_MARKET; } + AllowedDungeons = { 10, + NEW_ATTNAM, ATTNAM, UNDER_WATER_TUNNEL, ELPURI_CAVE, ASLONA_CASTLE, + REBEL_CAMP, MONDEDR, BLACK_MARKET, IRINOX, DARK_FOREST; } + DescriptiveInfo = "A sturdy, hermetically-sealed container that will prevent the contents from spoiling."; } lump @@ -216,7 +222,10 @@ lump AttachedGod = NONE; WieldedBitmapPos = 160, 112; IsValuable = false; - AllowedDungeons = { 5, NEW_ATTNAM, ATTNAM, UNDER_WATER_TUNNEL, ELPURI_CAVE, BLACK_MARKET; } + AllowedDungeons = { 10, + NEW_ATTNAM, ATTNAM, UNDER_WATER_TUNNEL, ELPURI_CAVE, ASLONA_CASTLE, + REBEL_CAMP, MONDEDR, BLACK_MARKET, IRINOX, DARK_FOREST; } + DescriptiveInfo = "A shapeless chunk."; } meleeweapon @@ -228,6 +237,7 @@ meleeweapon CanBeEnchanted = true; HasSecondaryMaterial = true; TeleportPriority = 500; + DescriptiveInfo = "Who lives by the sword dies by the sword. Who lives by the hammer dies by the hammer. Who lives by the spoon... You get the idea."; Config LONG_SWORD; { @@ -287,6 +297,7 @@ meleeweapon EnchantmentPlusChance = 5; DamageFlags = SLASH|PIERCE; Alias = { 2, "longsword", "sword"; } + DescriptiveInfo = "The staple of any fighter, the long sword makes an effective weapon. Critics denounce the trend for everyone to wield a long sword, citing that it leads to boring battle scenes and little originality in gear, but said critics likely don't have to wander dangerous dungeons and fight for their life."; } Config BROKEN|LONG_SWORD; @@ -377,6 +388,7 @@ meleeweapon EnchantmentPlusChance = 5; DamageFlags = SLASH; Alias = { 4, "greatsword", "great sword", "broadsword", "broad sword"; } + DescriptiveInfo = "An unusually large and heavy sword normally wielded with two hands. Favored by many barbaric warriors for the sheer power of its blows."; } Config BROKEN|TWO_HANDED_SWORD; @@ -467,6 +479,7 @@ meleeweapon EnchantmentMinusChance = 5; EnchantmentPlusChance = 5; DamageFlags = SLASH; + DescriptiveInfo = "A hulking sword, built to cleave opponents in twain."; Alias = { 3, "scimitar", "curved sword", "curved blade"; } } @@ -530,6 +543,7 @@ meleeweapon WieldedBitmapPos = 176, 112; Price = 250; DamageFlags = SLASH|BLUNT; + DescriptiveInfo = "Every year on the Atavus day, a humongous loaf of banana stollen is baked in the Cathedral and blessed by the high priest himself. The whole town then gathers for a feast where the stollen is cut by this very ceremonial knife and the celebrations of Atavus' charity can properly begin."; } Config BROKEN|GRAND_STOLLEN_KNIFE; @@ -603,6 +617,7 @@ meleeweapon WieldedBitmapPos = 160, 96; EnchantmentPlusChance = 15; DamageFlags = PIERCE; + DescriptiveInfo = "A common, simple thrusting weapon."; } Config BROKEN|SPEAR; @@ -676,6 +691,7 @@ meleeweapon WieldedBitmapPos = 160, 176; EnchantmentPlusChance = 15; DamageFlags = SLASH; + DescriptiveInfo = "A hatchet that can serve both as a weapon and a tool of many uses."; } Config BROKEN|AXE; @@ -738,6 +754,7 @@ meleeweapon EnchantmentMinusChance = 5; EnchantmentPlusChance = 5; DamageFlags = SLASH|PIERCE; + DescriptiveInfo = "A long-hilted weapon combining the traits of an axe and a spear. Strong, but difficult to handle."; } Config BROKEN|HALBERD; @@ -799,6 +816,7 @@ meleeweapon AttachedGod = LEGIFER; WieldedBitmapPos = 160, 192; EnchantmentPlusChance = 10; + DescriptiveInfo = "What maces lack in accuracy, they make up for in brute power."; } Config BROKEN|MACE; @@ -857,9 +875,11 @@ meleeweapon Roundness = 20; IsTwoHanded = true; CanBeUsedBySmith = true; + IsGoodWithUndead = true; AttachedGod = LORICATUS; WieldedBitmapPos = 176, 320; EnchantmentPlusChance = 15; + DescriptiveInfo = "A heavy hammerhead atop a stout handle. This weapon can easily smash through the strongest of armor."; } Config BROKEN|WAR_HAMMER; @@ -921,6 +941,7 @@ meleeweapon IsGoodWithPlants = true; EnchantmentPlusChance = 20; DamageFlags = SLASH; + DescriptiveInfo = "A short, curved blade often used for weeding overgrown plants."; } Config BROKEN|SICKLE; @@ -953,7 +974,7 @@ meleeweapon FormModifier = 75; StrengthModifier = 60; NameSingular = "dagger"; - Alias = { 3, "knife", "cutter", "cut"; } + Alias = { 4, "knife", "cutter", "cut", "cutting tool"; } MainMaterialConfig = { 67, DRAGON_BONE, OBSIDIAN, JASPER, JACINTH, DEEP_CORAL, HALCYON, QUARTZITE, ROSE_QUARTZ, AMETHYST, @@ -996,6 +1017,7 @@ meleeweapon WieldedBitmapPos = 160, 288; EnchantmentPlusChance = 25; DamageFlags = SLASH|PIERCE; + DescriptiveInfo = "A short blade suitable for quick strikes, throwing and even crafting."; } Config BROKEN|DAGGER; @@ -1078,6 +1100,7 @@ meleeweapon EnchantmentPlusChance = 15; DamageFlags = SLASH|PIERCE; Alias = { 2, "shortsword", "blade"; } + DescriptiveInfo = "The smaller cousin of the eternally popular long sword."; } Config BROKEN|SHORT_SWORD; @@ -1164,6 +1187,7 @@ meleeweapon WieldedBitmapPos = 160, 16; EnchantmentPlusChance = 5; DamageFlags = SLASH; + DescriptiveInfo = "A straight sword with a long, broad blade designed to be wielded with one and a half hand."; } Config BROKEN|BASTARD_SWORD; @@ -1235,6 +1259,7 @@ meleeweapon EnchantmentPlusChance = 10; DamageFlags = SLASH; Alias = { 3, "battleaxe", "battle-axe", "great axe"; } + DescriptiveInfo = "An axe crafted for battle, not labor."; } Config BROKEN|BATTLE_AXE; @@ -1297,6 +1322,7 @@ meleeweapon IsGoodWithPlants = true; EnchantmentPlusChance = 20; DamageFlags = SLASH; + DescriptiveInfo = "A long shaft topped with a wicked, curved blade. Normally used for crop harvesting, scythe can make a potentially dangerous weapon in skilled hands."; } Config BROKEN|SCYTHE; @@ -1356,6 +1382,7 @@ meleeweapon AttachedGod = SILVA; WieldedBitmapPos = 160, 304; EnchantmentPlusChance = 20; + DescriptiveInfo = "The favoured weapon of travellers, wizards and goblin monks."; Alias = { 2, "staff", "stave"; } } @@ -1411,10 +1438,12 @@ meleeweapon 100, 10, 5, 100, 50, 25; } Roundness = 20; CanBeUsedBySmith = true; + IsGoodWithUndead = true; AttachedGod = LORICATUS; WieldedBitmapPos = 176, 16; EnchantmentPlusChance = 20; - Alias == "mallet"; + Alias = { 2, "mallet", "blunt tool"; } + DescriptiveInfo = "A blunt tool useful for crushing skulls and forging metal."; } Config BROKEN|HAMMER; @@ -1436,56 +1465,6 @@ meleeweapon EnchantmentPlusChance = 40; } - Config MAGE_STAFF; - { - /* - * Two hundred years ago, the hellish Dwarven Wars came to a close when - * the joint forces of Kharaz-arad and Khaz-zadm broke through the defences - * of Khazad-durr and ended their enemy in gunpowder explosions and clouds - * of mustard gas. Yet it is said that the sovereign of Khazad-durr was - * never captured, instead transforming into a gargantuan spider and - * scuttling away. His regalia vanished along with him, including the royal - * staff bearing the name Y'yter Durr. - */ - DefaultSize = 220; - Possibility = 1; - WeaponCategory = BLUNT_WEAPONS; - StrengthModifier = 150; - BitmapPos = 64, 240; - FormModifier = 125; - DefaultMainVolume = 2000; - DefaultSecondaryVolume = 200; - NameSingular = "mage staff"; - PostFix = "named Y'yter Durr"; - Alias == "Y'yter Durr"; - MainMaterialConfig == OCTIRON; - SecondaryMaterialConfig == GREEN_CRYSTAL; - Roundness = 20; - IsTwoHanded = true; - AttachedGod = SCABIES; - WieldedBitmapPos = 160, 304; - Price = 1000; - EnchantmentPlusChance = 2; - ArticleMode = FORCE_THE; - CanBeWished = false; - IsMaterialChangeable = false; - IsPolymorphable = false; - IsPolymorphSpawnable = false; - CanBeCloned = false; - CanBeMirrored = true; - CanBePiled = false; - GearStates = POLYMORPH|POLYMORPH_CONTROL; - } - - Config BROKEN|MAGE_STAFF; - { - Possibility = 1; - BitmapPos = 64, 256; - WieldedBitmapPos = 176, 304; - GearStates = 0; - EnchantmentPlusChance = 5; - } - Config ROLLING_PIN; { DefaultSize = 45; @@ -1511,6 +1490,7 @@ meleeweapon AttachedGod = SEGES; WieldedBitmapPos = 160, 352; EnchantmentPlusChance = 15; + DescriptiveInfo = "Hell hath no fury like a woman scorned, especially if she has a rolling pin in hand."; } Config BROKEN|ROLLING_PIN; @@ -1544,6 +1524,7 @@ meleeweapon AttachedGod = SEGES; WieldedBitmapPos = 128, 48; EnchantmentPlusChance = 15; + DescriptiveInfo = "Great for food preparation and face smashing."; } Config BROKEN|FRYING_PAN; @@ -1657,6 +1638,7 @@ meleeweapon WieldedBitmapPos = 160, 32; EnchantmentPlusChance = 15; DamageFlags = SLASH; + Alias == "meat cleaver"; } Config BROKEN|MEAT_CLEAVER; @@ -1694,13 +1676,14 @@ meleeweapon Roundness = 10; AttachedGod = CRUENTUS; WieldedBitmapPos = 160, 16; - EnchantmentPlusChance = 5; + EnchantmentPlusChance = 20; DamageFlags = SLASH|PIERCE; PriceIsProportionalToEnchantment = true; IsMaterialChangeable = false; CanBeCloned = false; CanBeMirrored = true; Alias == "runesword"; + DescriptiveInfo = "A thin blade inscribed with countless, softly glowing runes. They seem to be thirstily drinking in any enchantments you present to them."; Price = 500; } @@ -1709,8 +1692,75 @@ meleeweapon Possibility = 2; BitmapPos = 32, 208; WieldedBitmapPos = 176, 80; - EnchantmentPlusChance = 20; + EnchantmentPlusChance = 50; } + + Config KATANA; + { + DefaultSize = 100; + Possibility = 25; + WeaponCategory = LARGE_SWORDS; + DefaultMainVolume = 110; + DefaultSecondaryVolume = 30; + BitmapPos = 112, 192; + FormModifier = 115; + StrengthModifier = 110; + NameSingular = "katana"; + MainMaterialConfig = { 79, + ASH_WOOD, CYPRESS_WOOD, BAMBOO_WOOD, MAHOGANY_WOOD, + OAK_WOOD, TEAK_WOOD, EBONY_WOOD, KAURI_WOOD, RATA_WOOD, + PETRIFIED_WOOD, DRAGON_BONE, OBSIDIAN, JASPER, JACINTH, + DEEP_CORAL, HALCYON, QUARTZITE, ROSE_QUARTZ, AMETHYST, + MALACHITE, NEPHRITE, BLACK_JADE, RED_JADE, GREEN_JADE, + ROCK_CRYSTAL, PURPLE_CRYSTAL, BRIM_STONE, AEGI_SALT, + HARDENED_ASH, COPPER, BRONZE, BIRCH_WOOD, ONYX, RESIDUUM, + VERDIGRIS, DEEP_BRONZE, BRASS, HEPATIZON, OCTIRON, SILVER, + MOON_SILVER, FAIRY_STEEL, MITHRIL, PLATINUM, LEAD, GOLD, + DARK_GOLD, DARK_MATTER, ELECTRUM, NICKEL, COBALT, TUNGSTEN, + RUBBER, STAINLESS_STEEL, MAGIC_CRYSTAL, ARCANITE, GLASS, + PEARL_GLASS, ILLITHIUM, RED_ICE, JOTUN_ICE, STEEL_LEAF, + PIG_IRON, IRON, STEEL, DWARF_STEEL, METEORIC_STEEL, + UKKU_STEEL, BLACK_IRON, SOUL_STEEL, BLOOD_IRON, BLOOD_STEEL, + WHALE_BONE, WRAITH_BONE, OMMEL_BONE, OMMEL_TOOTH, SHARK_TOOTH, + TROLL_TUSK, MAMMOTH_TUSK; } + SecondaryMaterialConfig == MAHOGANY_WOOD; + MaterialConfigChances = { 79, + 100, 50, 100, 50, 100, 50, 50, 25, 5, 1, 10, 200, 50, 50, + 25, 5, 25, 15, 5, 100, 50, 25, 20, 15, 25, 5, 1, 1, 1, + 1000, 1500, 100, 25, 5, 150, 100, 50, 25, 5, 50, 25, 25, + 15, 5, 250, 25, 15, 5, 5, 5, 4, 3, 1, 50, 2, 3, 1, 3, 4, 2, + 4, 100, 1000, 750, 100, 50, 25, 5, 80, 20, 80, 20, 150, + 100, 10, 5, 100, 50, 25; } + Roundness = 20; + AttachedGod = LEGIFER; + WieldedBitmapPos = 176, 112; + EnchantmentPlusChance = 5; + DamageFlags = SLASH|PIERCE; + AllowedDungeons = { 2, BLACK_MARKET, DARK_FOREST; } + DescriptiveInfo = "To train with the sword, first master sweeping. When you have mastered sweeping, you must master the way of drawing water. Once you have learned how to draw water, you must split wood. Once you have split wood, you must learn the arts of finding the fine herbs in the forest, the arts of writing, the arts of paper making, and poetry writing. You must become familiar with the awl and the pen in equal measure. When you have mastered all these things you must master building a house. Once your house is built, you have no further need for a sword, since it is an ugly piece of metal and its adherents idiots."; /* K6BD */ + IsTwoHanded = true; + } + + Config BROKEN|KATANA; + { + Possibility = 3; + BitmapPos = 112, 208; + WieldedBitmapPos = 176, 80; + MainMaterialConfig = { 36, + KAURI_WOOD, RATA_WOOD, SIDGURE_WOOD, DRAGON_BONE, AMETHYST, + TOPAZ, SAPPHIRE, BLACK_JADE, RED_JADE, GREEN_JADE, BLUE_JADE, + WHITE_JADE, GREEN_CRYSTAL, BLUE_CRYSTAL, DEEP_BRONZE, BRASS, + HEPATIZON, OCTIRON, ORICHALCUM, MOON_SILVER, FAIRY_STEEL, + MITHRIL, GALVORN, STAR_METAL, DARK_GOLD, DARK_MATTER, + STAINLESS_STEEL, ARCANITE, ILLITHIUM, STEEL, DWARF_STEEL, + METEORIC_STEEL, UKKU_STEEL, ADAMANT, SOUL_STEEL, BLOOD_STEEL; } + MaterialConfigChances = { 36, + 25, 25, 25, 10, 5, 5, 5, 25, 15, 10, 5, 5, 5, 5, 250, 50, + 15, 5, 1, 100, 50, 25, 5, 1, 50, 25, 100, 25, 25, 300, 100, + 50, 10, 5, 50, 50; } + EnchantmentPlusChance = 10; + AllowedDungeons = { 2, BLACK_MARKET, DARK_FOREST; } +} } banana /* materialcontainer-> */ @@ -1737,6 +1787,7 @@ banana /* materialcontainer-> */ IsValuable = false; IsSadistWeapon = true; AllowedDungeons = { 5, NEW_ATTNAM, ATTNAM, UNDER_WATER_TUNNEL, ELPURI_CAVE, BLACK_MARKET; } + DescriptiveInfo = "Ugh, not bananas again! There was pretty much nothing but bananas to eat back in New Attnam, and you would finally like to try a different diet."; Config BROKEN; { @@ -1772,6 +1823,7 @@ holybanana /* materialcontainer->banana-> */ IsValuable = true; CanBePiled = false; IsMaterialChangeable = false; + DescriptiveInfo = "Oily Orpiv was an explorer turned merchant. His commercial empire began its rise when on one expedition, he discovered a small village that never had to deal with the hardships of agriculture because of a seemingly miraculous crop. Oily Orpiv had discovered the banana. Being a follower of Mellis, he saw the monetary potential in this new fruit, subjugated the people and turned them into a highly efficient banana production facility. He made a quick profit that he soon used as investment for further business ventures. It is said that Oily Orpiv saved the third banana he ever saw (he ate the first and sold the second, claiming it was the first one) as a keepsake. When later in his life he became the champion of Mellis, this banana was imbued with powers both beneficial and deadly by Oily Orpiv's appreciative patron."; } justifier /* meleeweapon-> */ @@ -1888,6 +1940,7 @@ pickaxe /* meleeweapon-> */ WieldedBitmapPos = 160, 64; EnchantmentPlusChance = 10; DamageFlags = PIERCE; + DescriptiveInfo = "Strike the earth!"; Config BROKEN; { @@ -1915,6 +1968,7 @@ armor IsAbstract = true; CanBePiled = false; CanBeEnchanted = true; + DescriptiveInfo = "Offering protection against the weapons, claws and carnassials of your many adversaries, it is invaluable to your continued life. Heavy armor might hinder your movements, though."; } bodyarmor /* armor-> */ @@ -1928,6 +1982,7 @@ bodyarmor /* armor-> */ AttachedGod = LORICATUS; WieldedBitmapPos = 160, 144; TeleportPriority = 200; + DescriptiveInfo = "Most of one's vitals lie within the torso, so it is wise indeed to have them properly protected. Many a swordmaster had felt secure in their ability to defend against any opponent, only to receive a dagger to the kidneys from behind."; Config CHAIN_MAIL; { @@ -2121,6 +2176,7 @@ bodyarmor /* armor-> */ PriceIsProportionalToEnchantment = true; AttachedGod = SEGES; EnchantmentPlusChance = 2; + DescriptiveInfo = "Traditionally crafted and worn by members of the Monastic Order of the Haemophiliac Knights, this cuirass boosts one's bodily endurance."; Alias = { 5, "AoGH", "armour", "life", "health", "endurance"; } TorsoArmorBitmapPos = 32, 464; ArmArmorBitmapPos = 80, 464; @@ -2196,6 +2252,7 @@ bodyarmor /* armor-> */ ArmArmorBitmapPos = 80, 416; AthleteArmArmorBitmapPos = 80, 448; LegArmorBitmapPos = 16, 416; + DescriptiveInfo = "You know how dromedaries have one hump and camels have two? Ommels have eight, in a circular pattern. They are gargantuan yet docile herbivores that are remarkable in that their physiology seems reversed from almost all other animals. Their diet consists mostly of plants that to other animals (including humans) are poisonous at best, or even outright deadly on contact. While their meat is unsurprisingly vile and toxic, most of their waste products are extremely beneficial to other animals (still including humans). Ommels live in large herds headed by a single white-haired ommel, informally referred to as \"bristler\" due to the sharp spine-like quality their hair takes on as it turns white. Ommels have significantly larger lifespans than all humanoid races and a given herd will only have one bristler at a time, another male becoming a bristler only if the previous dies. Combined with the fact that bristlers do not shed their hair during the summer like normal ommels do, this renders armor made of their distinctive barbed hair incredibly rare."; } } @@ -2244,7 +2301,7 @@ potion /* materialcontainer-> */ FormModifier = 25; NameSingular = "bottle"; MainMaterialConfig == GLASS; - SecondaryMaterialConfig = { 44, + SecondaryMaterialConfig = { 45, WATER, HEALING_LIQUID, OMMEL_URINE, POISON_LIQUID, VALDEMAR, ANTIDOTE_LIQUID, VODKA, TROLL_BLOOD, OMMEL_SWEAT, OMMEL_TEARS, SULPHURIC_ACID, MUSTARD_GAS_LIQUID, LIQUID_DARKNESS, @@ -2253,10 +2310,11 @@ potion /* materialcontainer-> */ MILK, HONEY, COCA_COLA, INK, BRINE, CIDER, WHITE_WINE, RED_WINE, BEER, ELF_ALE, DWARF_BEER, LIQUID_FEAR, ASPHALT, BLOOD, GREEN_BLOOD, BLUE_BLOOD, PLANT_SAP, GLOWING_BLOOD, MERCURY, QUICK_SILVER, - OMMEL_BLOOD, VINEGAR; } - MaterialConfigChances = { 44, + OMMEL_BLOOD, VINEGAR, NAPALM; } + MaterialConfigChances = { 45, 50, 50, 5, 20, 1, 8, 5, 20, 5, 5, 10, 2, 1, 3, 1, 2, 2, 1, 2, 3, 1, - 5, 5, 5, 1, 1, 1, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 10; } + 5, 5, 5, 1, 1, 1, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 10, + 10; } Alias = { 2, "potion", "drink"; } Roundness = 70; CanBeBroken = true; @@ -2264,6 +2322,7 @@ potion /* materialcontainer-> */ WieldedBitmapPos = 176, 144; IsValuable = false; TeleportPriority = 200; + DescriptiveInfo = "You have heard that in some realms, the glass-blowing guild is so powerful that adventurers are required by law to destroy their bottles immediately after consuming the contents. You are glad that here, recycling is encouraged, even if that might mean cluttering your backpack with empty bottles."; Config VIAL; { @@ -2273,13 +2332,14 @@ potion /* materialcontainer-> */ DefaultMainVolume = 10; DefaultSecondaryVolume = 100; TeleportPriority = 20; - SecondaryMaterialConfig = { 9, + SecondaryMaterialConfig = { 11, WATER, HEALING_LIQUID, TROLL_BLOOD, SULPHURIC_ACID, LIGHT_FROG_BLOOD, CURE_ALL_LIQUID, MAGIC_LIQUID, - LIQUID_FEAR, TELEPORT_FLUID; } - MaterialConfigChances = { 9, - 25, 75, 20, 10, 3, 1, 20, 5, 10; } + LIQUID_FEAR, TELEPORT_FLUID, POLYMORPHINE, LAVA; } + MaterialConfigChances = { 11, + 25, 75, 20, 10, 3, 1, 20, 5, 10, 10, 1; } Alias = { 2, "small potion", "phial"; } + DescriptiveInfo = "A tiny bottle good enough for a single sip."; } } @@ -2301,6 +2361,7 @@ bananapeels AllowEquip = false; IsValuable = false; IsSadistWeapon = true; + DescriptiveInfo = "Careful, someone might slip on it!"; } brokenbottle /* cannot contain anything, so NOT bottle-> */ @@ -2321,6 +2382,8 @@ brokenbottle /* cannot contain anything, so NOT bottle-> */ IsValuable = false; DamageFlags = PIERCE; IsSadistWeapon = true; + DescriptiveInfo = "Sharp and dangerous to your feet. Maybe it could still be put back together?"; + Config VIAL; { NameSingular = "vial"; @@ -2343,6 +2406,7 @@ scroll WieldedBitmapPos = 176, 144; AllowEquip = false; TeleportPriority = 300; + DescriptiveInfo = "Words of Power written down for ease of use."; } scrollofteleportation /* scroll-> */ @@ -2353,6 +2417,7 @@ scrollofteleportation /* scroll-> */ Alias = { 2, "teleport", "teleportation"; } AttachedGod = SOPHOS; ReadDifficulty = 50; + DescriptiveInfo = "With the proper incantation to alter the flow of ley lines, one can arbitrarily increase the similarity of two different locations. When the areas become superimposed (with a maximum deviation of 2^-20 m), the Universe itself can no longer tell them apart. The net effect is that, while to an outside observer the inexplicable miracle of teleportation took place, what has actually happened is that the source location became the destination. This is yet another proof that the so-called miracles are only as of yet unexplained phenomena of magical science."; } scrolloffireballs /* scroll-> */ @@ -2418,6 +2483,7 @@ nut WieldedBitmapPos = 160, 368; IsValuable = false; IsSadistWeapon = true; + DescriptiveInfo = "This makes you feel rather uneasy in the groin region."; } leftnutofpetrus /* nut-> */ @@ -2442,6 +2508,7 @@ leftnutofpetrus /* nut-> */ IsQuestItem = true; IsValuable = true; CanBeBurned = false; + DescriptiveInfo = "A relic of the fallen high priest."; } bone @@ -2485,7 +2552,10 @@ loaf AttachedGod = NONE; WieldedBitmapPos = 160, 352; IsValuable = false; - AllowedDungeons = { 5, NEW_ATTNAM, ATTNAM, UNDER_WATER_TUNNEL, ELPURI_CAVE, BLACK_MARKET; } + AllowedDungeons = { 10, + NEW_ATTNAM, ATTNAM, UNDER_WATER_TUNNEL, ELPURI_CAVE, ASLONA_CASTLE, + REBEL_CAMP, MONDEDR, BLACK_MARKET, IRINOX, DARK_FOREST; } + DescriptiveInfo = "Some processed food to sate your hunger."; } scrollofwishing /* scroll-> */ @@ -2512,6 +2582,7 @@ copyofleftnutofpetrus /* nut-> */ AttachedGod = MELLIS; IsValuable = false; IsAbstract = true; + DescriptiveInfo = "High priest Petrus lost his right nut in a gruesome, unfortunate accident that left it mangled beyond repair by even divine magic. His remaining nut thus became an object of veneration as the only hope for an heir to the Attnamese throne."; Config CHEAP; { @@ -2548,6 +2619,8 @@ wand WieldedBitmapPos = 160, 352; TeleportPriority = 1000; CanBeBurned = false; + BreakMsg = "releases its powers, causing a number of highly unexpected effects to occur around itself"; + DescriptiveInfo = "A fragile rod filled to the brim with magic."; Config WAND_OF_POLYMORPH; { @@ -2565,6 +2638,7 @@ wand AttachedGod = SCABIES; IsKamikazeWeapon = true; BreakMsg = "shatters, its pieces transforming into a myriad of exotic forms before vanishing"; + DescriptiveInfo = "The target of this wand is not really transformed. It is shunted to a pocket dimension while the enchantments shift through a myriad of quantum variations on this reality, substantiating what the target could have been if a tiny change was introduced millions of years ago."; } Config WAND_OF_STRIKING; @@ -2581,6 +2655,7 @@ wand AttachedGod = INFUSCOR; IsKamikazeWeapon = true; BreakMsg = "explodes, releasing all of its energy at once"; + DescriptiveInfo = "Loaded with raw destructive magic, this wand is highly popular among the wandslingers in the western lands."; } Config WAND_OF_FIRE_BALLS; @@ -2598,6 +2673,7 @@ wand BreakEffectRangeSquare = 0; IsKamikazeWeapon = true; BreakMsg = "turns into a fiery ball of rapidly expanding plasma"; + DescriptiveInfo = "A fire elemental is trapped inside and tormented every time you zap this wand. Its wrath is then channeled into a mighty explosion. It might not be very understanding should it ever break free."; } Config WAND_OF_TELEPORTATION; @@ -2615,6 +2691,7 @@ wand AttachedGod = SOPHOS; BreakEffectRangeSquare = 9; BreakMsg = "breaks apart and a thousand splinters scatter in random directions, each cutting a tiny hole in space and time"; + DescriptiveInfo = "A hole was torn in the space-time continuum and then sealed in this wand. It's absolutely, perfectly, hundred percent safe. Really."; } Config WAND_OF_HASTE; @@ -2630,6 +2707,7 @@ wand BeamEffect = BEAM_HASTE; AttachedGod = CLEPTIA; BreakMsg = "breaks, speeding up everything around it"; + DescriptiveInfo = "A bit of extra time saved up for when you really need it."; } Config WAND_OF_SLOW; @@ -2660,6 +2738,7 @@ wand BeamEffect = BEAM_RESURRECT; AttachedGod = SEGES; BreakMsg = "breaks, releasing countless green rays of highly compressed life energy"; + DescriptiveInfo = "Filled with life energy drained from a thousand sacrifices and capable of bringing back the dead, this wand is rare and powerful. All claims of permanent brain damage, demonic possession or other side effects of resurrection are unsubstantiated."; Alias = { 2, "heal", "healing"; } } @@ -2676,7 +2755,8 @@ wand BeamEffect = BEAM_DOOR_CREATION; BeamStyle = SHIELD_BEAM; AttachedGod = LORICATUS; - BreakMsg = "releases its powers, causing a number of highly unexpected effects to occur around itself"; + BreakMsg = "releases its powers, thousands of spectral doors manifesting all around"; + DescriptiveInfo = "Transmuting the surrounding matter and focusing it through a Platonic ideal of a gateway, this wand can manifest door out of the proverbial thin air."; Alias == "wand of door"; } @@ -2711,6 +2791,7 @@ wand AttachedGod = INFUSCOR; BreakEffectRangeSquare = 0; BreakMsg = "breaks up, its dispersing splinters multiplying infinitely"; + DescriptiveInfo = "The pinnacle of alchemical expertise, this wand allows one to fabricate a perfect, permanent duplicate of nearly anything imaginable."; } Config WAND_OF_LIGHTNING; @@ -2729,6 +2810,7 @@ wand BreakEffectRangeSquare = 9; IsKamikazeWeapon = true; BreakMsg = "breaks and releases all of its stored electricity at once"; + DescriptiveInfo = "The actinic glare of a lightning bolt searing through a darkened corridor would be enough to strike terror in the most stout of warrior. One can only imagine their terror when the bolt, which seemed to just miss them, bounces off a wall to strike them from behind! In the past, massive thunderstorm farms were necessary to produce and harvest the lightning stored in this wand. Nowadays, the progress in the techniques of hattifattener breeding allowes for much quicker, cheaper and more environmental friendly manufacture."; } Config WAND_OF_ACID_RAIN; @@ -2746,6 +2828,7 @@ wand BreakEffectRangeSquare = 1; IsKamikazeWeapon = true; BreakMsg = "vaporizes in an instant, unleashing a deadly rainstorm of polluted acid"; + DescriptiveInfo = "The magic of this wand catalyses a reaction between the water vapors in the air and the naturally occurring nitrogen. The resulting nitric acid condenses into larger particles, forming a caustic shower that wreaks havoc on anything caught inside."; Alias == "wand of acid"; } @@ -2765,6 +2848,7 @@ wand AttachedGod = INFUSCOR; BreakEffectRangeSquare = 0; BreakMsg = "shatters, its slivers reflecting each other recursively ad infinitum"; + DescriptiveInfo = "The enchantments of this wand construct an astral simulacrum of the target. Unfortunately, the false matter of the duplicate is only metastable and will eventually discorporate."; } Config WAND_OF_NECROMANCY; @@ -2780,6 +2864,7 @@ wand BeamEffect = BEAM_NECROMANCY; AttachedGod = INFUSCOR; BreakMsg = "emits a gross of unholy rays and self-obliterates"; + DescriptiveInfo = "Dabblers who lack the motivation or ability to learn proper incantations use this wand to infuse the fallen bodies of their foes with a sick parody of life. Contemporary necromantic science is unfortunately unable to induce undeath in non-humanoid corpses, though at least one research team has recently reported a successful animation of a puppy skeleton."; Alias = { 2, "necromancy", "undeath"; } } @@ -2796,6 +2881,7 @@ wand BeamEffect = BEAM_WEBBING; AttachedGod = SCABIES; BreakMsg = "breaks, sending strands of spider silk everywhere"; + DescriptiveInfo = "When you zap this wand, a spray of special high-pressure shear-thinning liquid is ejected from an extradimensional reservoir. On contact with air, this long-chain polymer rapidly knits and forms an extremely tough webbing."; } Config WAND_OF_ALCHEMY; @@ -2811,6 +2897,7 @@ wand BeamEffect = BEAM_ALCHEMY; AttachedGod = MELLIS; BreakMsg = "explodes in a puff of golden dust"; + DescriptiveInfo = "Monetary value, as every scholar of Mellis knows, is an intrinsic quality of every object, just as its temperature or mass. This wand can dispose of the unnecessary substance of an item, transferring its value to you directly."; } Config WAND_OF_SOFTEN_MATERIAL; @@ -2826,6 +2913,7 @@ wand BeamEffect = BEAM_SOFTEN_MATERIAL; AttachedGod = SCABIES; /* Change to SOLICITUS? */ BreakMsg = "turns itself into dust"; + DescriptiveInfo = "Inscribed with the same magic formula as a scroll of harden material, except backwards."; } } @@ -2884,7 +2972,10 @@ kiwi WieldedBitmapPos = 160, 368; AllowEquip = false; IsValuable = false; - AllowedDungeons = { 5, NEW_ATTNAM, ATTNAM, UNDER_WATER_TUNNEL, ELPURI_CAVE, BLACK_MARKET; } + AllowedDungeons = { 7, + NEW_ATTNAM, ATTNAM, UNDER_WATER_TUNNEL, ELPURI_CAVE, FUNGAL_CAVE, + DARK_FOREST, BLACK_MARKET; } + DescriptiveInfo = "Kiwis would be more popular if the plants didn't try to eat the harvesters."; } pineapple @@ -2903,7 +2994,10 @@ pineapple AttachedGod = SILVA; WieldedBitmapPos = 176, 144; IsValuable = false; - AllowedDungeons = { 5, NEW_ATTNAM, ATTNAM, UNDER_WATER_TUNNEL, ELPURI_CAVE, BLACK_MARKET; } + AllowedDungeons = { 10, + NEW_ATTNAM, ATTNAM, UNDER_WATER_TUNNEL, ELPURI_CAVE, ASLONA_CASTLE, + REBEL_CAMP, MONDEDR, BLACK_MARKET, IRINOX, DARK_FOREST; } + DescriptiveInfo = "A delicious fruit native to the vast pine forests of the far north."; } palmbranch @@ -2956,6 +3050,7 @@ whip /* meleeweapon-> */ DamageFlags = SLASH; IsSadistWeapon = true; Alias = { 4, "BDSM", "love", "porn", "sex"; } + DescriptiveInfo = "Favored by slave-masters and mistresses of Nefas."; Config BROKEN; { @@ -3032,6 +3127,7 @@ holybook AttachedGod = NONE; WieldedBitmapPos = 176, 144; ReadDifficulty = 1000; + DescriptiveInfo = "A tome of myths, rituals, allegories, parables and ancient wisdom. The gods are fickle, but this might help you understand them more."; } fiftymillionroubles @@ -3052,6 +3148,7 @@ fiftymillionroubles AttachedGod = MELLIS; WieldedBitmapPos = 160, 128; IsValuable = false; + DescriptiveInfo = "Jackpot! You're filthy rich, you lucky bastard!"; /* maybe in Soviet Russia */ } oillamp @@ -3076,6 +3173,7 @@ oillamp AttachedGod = ATAVUS; WieldedBitmapPos = 160, 128; CanBeBurned = false; + DescriptiveInfo = "You have heard stories about genies trapped in this kind of a lamp."; } stone @@ -3111,6 +3209,7 @@ stone WieldedBitmapPos = 160, 368; IsValuable = false; HasNormalPictureDirection = false; + DescriptiveInfo = "It's a rock, duh."; Config SOL_STONE; { @@ -3120,8 +3219,10 @@ stone Price = 100; MainMaterialConfig == OBSIDIAN; IsValuable = true; + IsDestroyable = false; TeleportPriority = 2000; Alias = { 0; } + DescriptiveInfo = "This rock seems to be floating just above the ground."; } Config INGOT; @@ -3130,7 +3231,20 @@ stone Possibility = 10; NameSingular = "ingot"; IsValuable = true; + MainMaterialConfig = { 35, + VALPURIUM, BRASS, HEPATIZON, OCTIRON, ORICHALCUM, SILVER, + MOON_SILVER, FAIRY_STEEL, MITHRIL, GALVORN, STAR_METAL, + PLATINUM, DARK_GOLD, ELECTRUM, IRIDIUM, PALLADIUM, CHROME, + TITANITE, STAINLESS_STEEL, PLASTIC_STEEL, DURALLOY, + VITRELLOY, URANIUM, SOLARIUM, NEUTRONIUM, ARCANITE, + OCCULTUM, ILLITHIUM, METEORIC_STEEL, UKKU_STEEL, ADAMANT, + SOUL_STEEL, BLOOD_STEEL, WHITE_STEEL, UR_STEEL; } + MaterialConfigChances = { 35, + 1, 200, 75, 25, 5, 300, 250, 100, 75, 25, 5, 50, 50, 50, + 30, 20, 10, 5, 20, 15, 10, 5, 15, 10, 5, 100, 15, 150, + 150, 75, 5, 200, 200, 200, 75; } Alias == "metal"; + DescriptiveInfo = "Useful in crafting."; } } @@ -3142,6 +3256,7 @@ scrolloftaming /* scroll-> */ AttachedGod = DULCIS; ReadDifficulty = 100; Alias = { 8, "pet", "ally", "minion", "companion", "familiar", "slave", "friend", "help"; } + DescriptiveInfo = "All brain washing, memory altering and mind controlling magic is forbidden under the penalty of death in nine out of ten kingdoms. Thankfully, you are in the one that allows it."; } mine /*materialcontainer-> */ @@ -3240,6 +3355,7 @@ skeletonkey /* key-> */ CanBePiled = false; IsValuable = true; BaseEmitation = rgb24(125, 125, 105); + DescriptiveInfo = "A rough key carved from a knucklebone of a grand dragon. The beast reportedly didn't even stir as the bone was stolen from its paw. The key is said to open any lock easily."; } shield /* armor-> */ @@ -3288,6 +3404,7 @@ shield /* armor-> */ WieldedBitmapPos = 128, 32; EnchantmentPlusChance = 15; Alias = { 2, "defense", "protection"; } + DescriptiveInfo = "A shield can both deflect attacks aside and provide a measure of defence against those that manage to connect."; Config BROKEN; { @@ -3322,7 +3439,7 @@ shield /* armor-> */ Possibility = 1; Adjective = "blessed"; PostFix = "of the Phoenix"; - Alias = { 0; } + Alias == "phoenix shield"; MainMaterialConfig == PHOENIX_FEATHER; EnchantmentPlusChance = 2; StrengthModifier = 150; @@ -3335,19 +3452,9 @@ shield /* armor-> */ CanBeCloned = false; CanBeMirrored = true; CanBePiled = false; + CanBeBroken = false; /* Prevent breaking, because dying when a strong attack first breaks the shield and then kills you feels really cheap. */ Price = 2500; - } - - Config BROKEN|PHOENIX_SHIELD; - { - Possibility = 1; - DefaultSize = 40; - FormModifier = 20; - StrengthModifier = 75; - BitmapPos = 48, 224; - WieldedBitmapPos = 128, 48; - EnchantmentPlusChance = 5; - GearStates = 0; + DescriptiveInfo = "A phoenix displayed or, langued and membered sable, on a field azure is emblazoned on this shield. According to the legends, it once belonged to Kuolee Kauheasti the Immortal Knight, who was killed in the battle of Prym after his shield arm was cut off."; } Config AEGIS_SHIELD; @@ -3375,11 +3482,12 @@ shield /* armor-> */ CanBeMirrored = true; CanBePiled = false; Price = 1000; + DescriptiveInfo = "A tower displayed argent on a field sable is emblazoned on this shield. Forged in likeness of lord Xinroch's arcanite shield, it used to belong to the master of dark templars who guard Xinroch's eternal resting place. Both Cruentus and Infuscor blessed the shield to offer unparalleled protection against blade and spell alike."; } Config SHIELD_OF_FIRE_RESISTANCE; { - Possibility = 2; + Possibility = 10; PostFix = "of fire resistance"; Price = 500; FireResistance = 15; @@ -3388,7 +3496,7 @@ shield /* armor-> */ Config BROKEN|SHIELD_OF_FIRE_RESISTANCE; { - Possibility = 1; + Possibility = 5; DefaultSize = 40; FormModifier = 20; StrengthModifier = 50; @@ -3401,7 +3509,7 @@ shield /* armor-> */ Config SHIELD_OF_ELECTRICITY_RESISTANCE; { - Possibility = 2; + Possibility = 10; PostFix = "of electricity resistance"; Price = 500; ElectricityResistance = 15; @@ -3410,7 +3518,7 @@ shield /* armor-> */ Config BROKEN|SHIELD_OF_ELECTRICITY_RESISTANCE; { - Possibility = 1; + Possibility = 5; DefaultSize = 40; FormModifier = 20; StrengthModifier = 50; @@ -3477,6 +3585,7 @@ cloak /* armor-> */ CanBeBroken = true; CloakBitmapPos = 48, 416; EnchantmentPlusChance = 10; + DescriptiveInfo = "A long, hooded cloak faded from years of exposure to rough weather."; Config BROKEN; { @@ -3593,6 +3702,7 @@ cloak /* armor-> */ ArticleMode = FORCE_THE; CanBeEnchanted = false; CanBePiled = false; + DescriptiveInfo = "Lord Xinroch was the greatest and most powerful grand master dark knight to ever live. He was highly successful in all his campaigns, and it was during his life that the Unholy Order of the Dark Knights truly flourished. But years eventually caught up with him, after a life full of victory. No matter how strong magic sustains the body, mortals were created to die and all that lives will wither eventually. But Xinroch wasn't one to give up and thus he started to heavily fund research into the newly-invented magic school of necromancy. It was thanks to his sponsorship that this discipline could start to spread, but unfortunately the early necromancers were unable to stave off the dwindling of Xinroch's life. The Tomb of Xinroch was erected on the battlefield where Lord Xinroch was slain, to honor and guard his grave against all who would wish to disturb his last sleep or defile his memory."; } Config CLOAK_OF_WEREWOLF; @@ -3613,6 +3723,7 @@ cloak /* armor-> */ CanBeMirrored = true; CanBePiled = false; Price = 1000; + DescriptiveInfo = "A huge pile of stinking, lice-infested hide flayed alive from a werewolf. There's little humanity left in those who wear it."; } Config BROKEN|CLOAK_OF_WEREWOLF; @@ -3763,6 +3874,7 @@ cloak /* armor-> */ CanBeMirrored = true; CanBePiled = false; Price = 1000; + DescriptiveInfo = "An ageless, dark robe stained with dried blood. Now that you think of it, maybe you wouldn't be against some prowling and blood-drinking..."; } Config BROKEN|CLOAK_OF_VAMPIRE; @@ -3836,7 +3948,8 @@ boot /* armor-> */ BootBitmapPos = 16, 432; CanBeBroken = true; EnchantmentPlusChance = 10; - Alias = { 3, "boots", "shoes", "moccasins"; } + DescriptiveInfo = "Protection for your legs."; + Alias = { 4, "boots", "shoes", "moccasins", "greaves"; } Config BROKEN; { @@ -3874,7 +3987,7 @@ boot /* armor-> */ Config BOOT_OF_STRENGTH; { - Possibility = 10; + Possibility = 5; StrengthModifier = 125; PostFix = "of strength"; AffectsLegStrength = true; @@ -3889,7 +4002,6 @@ boot /* armor-> */ DefaultSize = 30; FormModifier = 30; BitmapPos = 80, 368; - Possibility = 20; AffectsLegStrength = false; Price = 10; EnchantmentPlusChance = 4; @@ -3899,7 +4011,7 @@ boot /* armor-> */ Config BOOT_OF_AGILITY; { - Possibility = 10; + Possibility = 5; StrengthModifier = 125; PostFix = "of agility"; AffectsAgility = true; @@ -3915,7 +4027,6 @@ boot /* armor-> */ DefaultSize = 30; FormModifier = 30; BitmapPos = 80, 368; - Possibility = 20; AffectsAgility = false; Price = 25; EnchantmentPlusChance = 4; @@ -3949,7 +4060,6 @@ boot /* armor-> */ DefaultSize = 30; FormModifier = 30; BitmapPos = 80, 368; - Possibility = 20; MainMaterialConfig = { 30, COPPER, BRONZE, VERDIGRIS, DEEP_BRONZE, BRASS, SILVER, MOON_SILVER, FAIRY_STEEL, MITHRIL, PLATINUM, LEAD, GOLD, DARK_GOLD, DARK_MATTER, @@ -3964,6 +4074,26 @@ boot /* armor-> */ InElasticityPenaltyModifier = 30; BootBitmapPos = 16, 512; } + + Config BOOT_OF_DISPLACEMENT; + { + Possibility = 5; + StrengthModifier = 125; + PostFix = "of displacement"; + Price = 50; + EnchantmentPlusChance = 2; + } + + Config BROKEN|BOOT_OF_DISPLACEMENT; + { + DefaultSize = 30; + FormModifier = 30; + BitmapPos = 80, 368; + Price = 10; + EnchantmentPlusChance = 4; + InElasticityPenaltyModifier = 30; + BootBitmapPos = 16, 512; + } } gauntlet /* armor-> */ @@ -4027,6 +4157,7 @@ gauntlet /* armor-> */ CanBeBroken = true; EnchantmentPlusChance = 10; HasNormalPictureDirection = false; + DescriptiveInfo = "Protection for your hands."; Config BROKEN; { @@ -4064,7 +4195,7 @@ gauntlet /* armor-> */ Config GAUNTLET_OF_STRENGTH; { - Possibility = 10; + Possibility = 5; StrengthModifier = 110; PostFix = "of strength"; AffectsArmStrength = true; @@ -4089,7 +4220,7 @@ gauntlet /* armor-> */ Config GAUNTLET_OF_DEXTERITY; { - Possibility = 10; + Possibility = 5; StrengthModifier = 110; PostFix = "of dexterity"; AffectsDexterity = true; @@ -4147,6 +4278,7 @@ belt /* armor-> */ EnchantmentPlusChance = 10; DamageFlags = SLASH; IsSadistWeapon = true; + DescriptiveInfo = "You should realize that your groin is the most valuable part of your body. Wait, or was it your head?"; Config BROKEN; { @@ -4281,6 +4413,7 @@ belt /* armor-> */ MaterialConfigChances == 100; EnchantmentPlusChance = 2; Alias == "strength"; + DescriptiveInfo = "Was it made by giants, for giants, or from giants?"; } Config BROKEN|BELT_OF_GIANT_STRENGTH; @@ -4334,6 +4467,7 @@ belt /* armor-> */ MaterialConfigChances == 100; EnchantmentPlusChance = 2; Alias == "regeneration"; + DescriptiveInfo = "Makes you ready to get at it again much faster than normal."; } Config BROKEN|BELT_OF_REGENERATION; @@ -4362,6 +4496,7 @@ ring WieldedBitmapPos = 160, 368; TeleportPriority = 300; CanBeBurned = false; + DescriptiveInfo = "A piece of jewellery imbued with arcane powers."; Config RING_OF_FIRE_RESISTANCE; { @@ -4470,6 +4605,7 @@ ring Price = 500; AttachedGod = LEGIFER; GearStates = SEARCHING; + DescriptiveInfo = "This ring bestows the diligent observational skills necessary to readily identify any hidden traps you might happen upon during your dungeoneering adventures."; } Config RING_OF_ACID_RESISTANCE; @@ -4538,7 +4674,7 @@ ring MainMaterialConfig == URANIUM; Price = 2500; AttachedGod = SCABIES; - GearStates = PARASITE_TAPE_WORM; + GearStates = PARASITE_TAPE_WORM|DISEASE_IMMUNITY; TeleportPriority = 500; ArticleMode = FORCE_THE; CanBeWished = false; @@ -4551,6 +4687,7 @@ ring CanBeBroken = false; CanBeBurned = false; CanBePiled = false; + DescriptiveInfo = "A coiled worm transmuted into a sickly glowing ring. It whispers promises of safety into your mind, but also nibbles at your hand with a barely restrained hunger."; Alias == "worm ring"; } @@ -4568,7 +4705,7 @@ ring Config RING_OF_SPEED; { Possibility = 0; /* Artifact held by One-eyed Sam the black marketeer. */ - PostFix = "of great speed"; + PostFix = "of ludicrous speed"; MainMaterialConfig == QUICK_SILVER; Price = 2500; AttachedGod = CLEPTIA; @@ -4599,12 +4736,13 @@ amulet FormModifier = 10; UsesLongArticle = true; NameSingular = "amulet"; - MainMaterialConfig == GOLD; + MainMaterialConfig == SILVER; Roundness = 10; /* it is considered here opened */ IsAbstract = true; WieldedBitmapPos = 160, 112; HasNormalPictureDirection = false; CanBeBurned = false; + DescriptiveInfo = "A trinket imbued with magical power."; Config AMULET_OF_LIFE_SAVING; { @@ -4616,13 +4754,14 @@ amulet Price = 5000; AttachedGod = SEGES; Alias = { 3, "AoLS", "longevity", "immortality"; } + DescriptiveInfo = "The most sure path to immortality is not to die. This amulet will help."; } Config AMULET_OF_ESP; { Possibility = 7; PostFix = "of ESP"; - MainMaterialConfig == ILLITHIUM; + MainMaterialConfig == DARK_MATTER; GearStates = ESP; Price = 1000; AttachedGod = INFUSCOR; @@ -4648,10 +4787,11 @@ amulet { Possibility = 3; PostFix = "of magic breath"; - MainMaterialConfig == ARCANITE; + MainMaterialConfig == CORAL; GearStates = GAS_IMMUNITY|SWIMMING; Price = 2000; AttachedGod = SCABIES; + DescriptiveInfo = "This magical trinket provides a bubble of fresh air, protecting you from gases and drowning."; Alias = { 2, "amulet of unbreathing", "swimming"; } } @@ -4686,7 +4826,7 @@ amulet { Possibility = 3; PostFix = "of disease immunity"; - MainMaterialConfig == UNICORN_HORN; + MainMaterialConfig == MALACHITE; GearStates = DISEASE_IMMUNITY; Price = 2000; AttachedGod = SEGES; @@ -4697,7 +4837,7 @@ amulet { Possibility = 3; PostFix = "of dimensional protection"; - MainMaterialConfig == SIDGURE_WOOD; + MainMaterialConfig == RED_SAND_STONE; GearStates = TELEPORT_LOCK; Price = 2000; AttachedGod = SOPHOS; @@ -4708,7 +4848,7 @@ amulet { Possibility = 3; PostFix = "of fasting"; - MainMaterialConfig == PEARL; + MainMaterialConfig == LAPIS_LAZULI; GearStates = FASTING; Price = 2000; AttachedGod = DULCIS; @@ -4834,6 +4974,7 @@ headofelpuri /* cannot wear helmet etc, so NOT head-> */ IsQuestItem = true; CanBeBurned = false; CanBePiled = false; + DescriptiveInfo = "Alas, great Elpuri, may thy hunger be finally forgotten in the sleep of death."; } corpse @@ -4886,6 +5027,10 @@ magicmushroomtorso /* normaltorso-> */ { } +fusangatorso /* largetorso-> */ +{ +} + dogtorso /* normaltorso-> */ { } @@ -4918,7 +5063,12 @@ magpietorso /* normaltorso-> */ { } -whistle +magicalinstrument +{ + IsAbstract = true; +} + +whistle /* magicalinstrument-> */ { Possibility = 50; Category = TOOL; @@ -4936,7 +5086,7 @@ whistle CanBeBurned = false; } -magicalwhistle /* whistle-> */ +magicalwhistle /* magicalinstrument->whistle-> */ { Possibility = 5; MainMaterialConfig == RUBY; @@ -4971,6 +5121,7 @@ itemcontainer WieldedBitmapPos = 160, 144; CreateLockConfigurations = true; CanBeBurned = false; + DescriptiveInfo = "A sturdy container for your necessities."; Config SMALL_CHEST; { @@ -5040,6 +5191,7 @@ itemcontainer BitmapPos = 48, 368; DamageDivider = 5; StrengthModifier = 150; + DescriptiveInfo = "A small, portable safe that can protect your valuables from harm."; } } @@ -5138,7 +5290,7 @@ helmet CoverPercentile = 50; HelmetBitmapPos = 112, 416; EnchantmentPlusChance = 15; - SoundResistance = 0; + DescriptiveInfo = "A sturdy helmet can provide an excellent protection against hits to the head. Unfortunately, the sound of rain on it would soon drive you mad."; Config BROKEN; { @@ -5363,6 +5515,7 @@ helmet CanBeCloned = false; CanBeMirrored = true; CanBePiled = false; + DescriptiveInfo = "A durable mask fitted with a filter that provides good protection against dangerous gases and vapors."; } Config HELM_OF_WILLPOWER; @@ -5417,6 +5570,7 @@ helmet AttachedGod = INFUSCOR; EnchantmentPlusChance = 2; Alias = { 3, "helmet of ESP", "helm of ESP", "cap of ESP"; } + DescriptiveInfo = "Purportedly, the helmet of telepathy was invented by witch-queen Yala, who was looking for a way to read the thoughts of her manservants and thus dispose of any with dirty fantasies. According to her memoirs, the enchantments were an utter failure as she never found a way to decipher the detected thoughts. She later decided to dispatch all her servants and use skeletons instead. However, the Society of Adventuring Gentlemen recognized the value in her research and cheaply bought the magical patent. They turned a quick profit on helmets alerting a cautious delver to any lurking dangers even through closed doors."; } Config BROKEN|HELM_OF_TELEPATHY; @@ -5597,7 +5751,7 @@ helmet IRON, STEEL, DWARF_STEEL, METEORIC_STEEL, BLACK_IRON, SOUL_STEEL, BLOOD_IRON, BLOOD_STEEL, AMETHYST, TOPAZ, SAPPHIRE, EMERALD, RUBY; } MaterialConfigChances = { 100, - 15, 50, 100, 25, 25, 5, 1, 50, 25, 25, 5, 15, 50, 50, + 150, 50, 100, 25, 25, 5, 1, 50, 25, 25, 5, 15, 50, 50, 25, 50, 5, 3, 1, 5, 100, 50, 50, 5, 5, 5, 10, 75, 5, 15, 5, 25, 15, 30, 20, 100, 50, 40, 50, 60, 75, 25, 30, 40, 50, 60, 30, 40, 5, 50, 60, 75, 25, 10, 1, 50, 10, @@ -5605,6 +5759,7 @@ helmet 25, 100, 75, 50, 25, 50, 5, 50, 1, 1, 100, 75, 50, 5, 25, 5, 50, 10, 5, 1, 15, 5, 15, 5, 5, 5, 3, 3, 1; } EnchantmentPlusChance = 30; + DescriptiveInfo = "A highly decorative mask to conceal all your troubles."; } Config BROKEN|MASK; @@ -5627,14 +5782,7 @@ helmet Config WAR_MASK; { - /* - * In an age long past, the goblin empire of Quetzaltia fought a terrible - * war against the spawn of Scabies, the dreaded mind worms. The goblins - * were barely holding on until the wizards and alchemists of their king, - * the venerable Tlachtlallotlachitl, invented a new type of mask that - * rendered the powers of mind worms useless. Very few of those masks - * remain today, but any worm-slayer would give a fortune to own one. - */ + DescriptiveInfo = "In an age long past, the goblin empire of Quetzaltia fought a terrible war against the spawn of Scabies, the dreaded mind worms. The goblins were barely holding on until the wizards and alchemists of their king, the venerable Tlachtlallotlachitl, invented a new type of mask that rendered the powers of mind worms useless. Very few of those masks remain today, but any worm-slayer would give a fortune to own one."; DefaultSize = 25; DefaultMainVolume = 100; StrengthModifier = 250; @@ -5731,6 +5879,7 @@ flamingsword /* meleeweapon-> */ EnchantmentPlusChance = 2; DamageFlags = SLASH|PIERCE; CanBeBurned = false; + DescriptiveInfo = "A sword wreathed in ceaseless flames."; Config BROKEN; { @@ -5778,6 +5927,7 @@ flamingsword /* meleeweapon-> */ ArticleMode = FORCE_THE; DamageFlags = SLASH|PIERCE; GearStates = GAS_IMMUNITY|DISEASE_IMMUNITY; + DescriptiveInfo = "Lord Xinroch was the greatest and most powerful grand master dark knight to ever live. Some sources claim that he was no one in his youth, maybe even one of the children kidnapped from forgotten villages to be trained for war. But soon, his talents became apparent and he rose quickly through the ranks, never stopping until the very top. In his many skirmishes with the forces of Light, Xinroch destroyed two archangels, Incendo and Lucis, and took their flaming swords. He wielded them with unparalleled skill, and his two crossed flaming swords are still found on the banner of the grand master dark knights to this day. During a battle late in his life, Xinroch lost one of the flaming swords he wielded for most of his life, and after his death, the lost sword became shrouded in myths. It was even said that the return of this sword will herald the return of Lord Xinroch himself."; } } @@ -5855,6 +6005,7 @@ vermis /* meleeweapon-> */ EnchantmentPlusChance = 2; DamageFlags = PIERCE; GearStates = TELEPORT|TELEPORT_CONTROL; + DescriptiveInfo = "Granted power over spacetime by the blessing of Sophos, this spear allowed the scholar-knight Karl to unite many kobold tribes and start them on the Path of Enlightenment, until he stepped on a land mine and died, the kobolds promptly falling back to savagery."; Config BROKEN; { @@ -5896,6 +6047,7 @@ turox /* meleeweapon-> */ AttachedGod = LEGIFER; WieldedBitmapPos = 160, 192; EnchantmentPlusChance = 2; + DescriptiveInfo = "It is said that when the siege of Prym drove the Shining Knights to desperation and they prepared for one last charge to break through the orcish hordes, their Lord Legifer smiled upon their bravery and blessed their maces with divine fire, naming them all Turox. The Shining Knights were slaughtered anyway, and the holy maces were lost."; Config BROKEN; { @@ -5923,6 +6075,7 @@ whipofthievery /* meleeweapon->whip-> */ Price = 500; AttachedGod = CLEPTIA; EnchantmentPlusChance = 10; + DescriptiveInfo = "This whip is normally wielded by the clergy of Cleptia. The lash seems to be moving on its own accord, snatching weapons from the hands of your foes."; Config BROKEN; { @@ -6011,6 +6164,7 @@ skullofxinroch ArticleMode = FORCE_THE; IsQuestItem = true; Price = 500; + DescriptiveInfo = "It was a great shame to the guardians of the Tomb of Xinroch when several decades ago, an unknown necromancer managed to slip past their guard and animate the skeleton of Xinroch in a mockery of his once-living might. But even though you hold in your hands the skull of this diminished and defeated Xinroch, it radiates an aura of ageless, adamant glory. All hail the mighty lord Xinroch."; } gorovitsweapon @@ -6028,6 +6182,7 @@ gorovitsweapon IsAbstract = true; CanBeBurned = false; CanBePiled = false; + DescriptiveInfo = "The mystical glow of communism marks this mighty weapon as an interdimensional import from the legendary land of Soviet Russia."; Config GOROVITS_HAMMER; { @@ -6045,6 +6200,7 @@ gorovitsweapon Roundness = 20; IsTwoHanded = true; CanBeUsedBySmith = true; + IsGoodWithUndead = true; BaseEnchantment = 3; Price = 250; AttachedGod = LORICATUS; @@ -6070,6 +6226,7 @@ gorovitsweapon AttachedGod = SEGES; WieldedBitmapPos = 176, 32; DamageFlags = SLASH; + IsGoodWithPlants = true; } Config GOROVITS_SCIMITAR; @@ -6117,9 +6274,10 @@ encryptedscroll /* scroll-> */ AttachedGod = MELLIS; IsQuestItem = true; CanBeBurned = false; + DescriptiveInfo = "This scroll contains a coded message for the eyes of the high priest Petrus only. Given to you by Richel Decos, the viceroy of New Attnam, it should be delivered posthaste to the Cathedral of Attnam."; } -horn +horn /* magicalinstrument-> */ { StrengthModifier = 70; FormModifier = 30; @@ -6143,6 +6301,7 @@ horn { MainMaterialConfig == SILVER; PostFix = "of bravery"; + Alias = { 2, "courage", "valor"; } } Config FEAR; @@ -6207,6 +6366,7 @@ thunderhammer /* meleeweapon-> */ Roundness = 20; IsTwoHanded = true; CanBeUsedBySmith = true; + IsGoodWithUndead = true; Price = 500; BaseEmitation = rgb24(130, 130, 130); AttachedGod = LORICATUS; @@ -6266,6 +6426,7 @@ saalthul WieldedBitmapPos = 160, 32; EnchantmentPlusChance = 2; DamageFlags = SLASH|PIERCE; + DescriptiveInfo = "Once upon a time, there was a thief of great skill who sought to impress his beloved goddess by deeds of ever-increasing gall and daring. Eventually, he blundered and died a horrible death, but his name and skills live forever through the sword he used to wield."; Config BROKEN; { @@ -6296,6 +6457,7 @@ chameleonwhip /* meleeweapon->whip-> */ WieldedBitmapPos = 160, 224; EnchantmentMinusChance = 50; EnchantmentPlusChance = 50; + DescriptiveInfo = "Woven into the lash are tiny fragments of protean chaos, each strike destabilizing the biomorphic field of your foes."; Config BROKEN; { @@ -6327,7 +6489,10 @@ carrot AttachedGod = SEGES; WieldedBitmapPos = 160, 352; IsValuable = false; - AllowedDungeons = { 5, NEW_ATTNAM, ATTNAM, UNDER_WATER_TUNNEL, ELPURI_CAVE, BLACK_MARKET; } + AllowedDungeons = { 12, + NEW_ATTNAM, ATTNAM, UNDER_WATER_TUNNEL, ELPURI_CAVE, ASLONA_CASTLE, MONDEDR, + REBEL_CAMP, GOBLIN_FORT, BLACK_MARKET, IRINOX, DARK_FOREST, FUNGAL_CAVE; } + DescriptiveInfo = "Beloved by bunnies and people with poor eyesight."; Config BROKEN; { @@ -6340,7 +6505,7 @@ mango { WeaponCategory = BLUNT_WEAPONS; DefaultSize = 20; - Possibility = 0; + Possibility = 400; Category = FOOD; DefaultMainVolume = 200; StrengthModifier = 75; @@ -6353,7 +6518,8 @@ mango WieldedBitmapPos = 176, 144; IsValuable = false; CanBeBroken = true; - /* AllowedDungeons = { 2, DARK_FOREST, BLACK_MARKET; } */ + AllowedDungeons = { 3, DARK_FOREST, BLACK_MARKET, FUNGAL_CAVE; } + DescriptiveInfo = "Yummy!"; Config BROKEN; { @@ -6384,7 +6550,9 @@ sausage WieldedBitmapPos = 160, 320; IsValuable = false; IsSadistWeapon = true; - AllowedDungeons = { 5, NEW_ATTNAM, ATTNAM, UNDER_WATER_TUNNEL, ELPURI_CAVE, BLACK_MARKET; } + AllowedDungeons = { 10, + NEW_ATTNAM, ATTNAM, UNDER_WATER_TUNNEL, ELPURI_CAVE, ASLONA_CASTLE, + REBEL_CAMP, MONDEDR, BLACK_MARKET, IRINOX, DARK_FOREST; } } wondersmellstaff @@ -6412,6 +6580,7 @@ wondersmellstaff WieldedBitmapPos = 160, 304; Price = 500; EnchantmentPlusChance = 5; + DescriptiveInfo = "It is said that Infuscor was astounded when she saw the massacre that became of the Dwarven Wars. Her magical destruction was flashy and powerful, but the indiscriminate, insidious death that alchemical combat gases brought to the battlefield impressed her enough that she rushed to inspire one of her priestesses to enchant a magic staff for a similar purpose. With a little bit of a twist, this staff conjures both gases detrimental to your foes and beneficial to you and your allies, soon plunging the combat zone into utter chaos, exactly to Infuscor's liking."; Config BROKEN; { @@ -6422,7 +6591,7 @@ wondersmellstaff } } -charmlyre +charmlyre /* magicalinstrument-> */ { DefaultMainVolume = 1500; StrengthModifier = 50; @@ -6440,6 +6609,7 @@ charmlyre AttachedGod = DULCIS; WieldedBitmapPos = 176, 352; CanBeBurned = false; + DescriptiveInfo = "Finally, everyone will be working together. For you."; Alias = { 6, "music", "charm", "domination", "control", "peace", "world peace"; } } @@ -6465,6 +6635,7 @@ decosadshirt /* armor->bodyarmor-> */ AthleteArmArmorBitmapPos = 80, 448; LegArmorBitmapPos = 16, 416; Price = 0; + DescriptiveInfo = "Eat a banana. Be happy."; } filthytunic /* armor->bodyarmor-> */ @@ -6579,11 +6750,12 @@ gasgrenade Possibility = 75; Price = 50; MainMaterialConfig == IRON; - SecondaryMaterialConfig = { 7, + SecondaryMaterialConfig = { 9, MUSTARD_GAS, SKUNK_SMELL, FART, MAGIC_VAPOUR, - SLEEPING_GAS, EVIL_WONDER_STAFF_VAPOUR, TELEPORT_GAS; } - MaterialConfigChances = { 7, - 50, 10, 1, 10, 5, 5, 10; } + SLEEPING_GAS, EVIL_WONDER_STAFF_VAPOUR, TELEPORT_GAS, + ACID_GAS, FIRE_GAS; } + MaterialConfigChances = { 9, + 50, 10, 1, 10, 5, 5, 10, 5, 5; } Adjective = "dwarven"; Alias == "grenade"; } @@ -6604,6 +6776,7 @@ holyhandgrenade Alias = { 3, "holy grenade", "hand grenade", "handgrenade"; } CanBePiled = false; CanBeBurned = false; + DescriptiveInfo = "And Saint Attila raised the hand grenade up on high, saying: \"O Lord, bless this Thy hand grenade that, with it, Thou mayest blow Thine enemies to tiny bits in Thy mercy.\" And the Lord spake, saying: \"First shalt thou take out the Holy Pin. Then, shalt thou count to three. No more. No less. Three shalt be the number thou shalt count, and the number of the counting shall be three. Four shalt thou not count, nor either count thou two, excepting that thou then proceed to three. Five is right out. Once the number three, being the third number, be reached, then, lobbest thou thy Holy Hand Grenade of Antioch towards thy foe, who, being naughty in My sight, shall snuff it.\""; /* Monty Python and the Holy Grail */ } daggerofvenom @@ -6636,6 +6809,7 @@ daggerofvenom WieldedBitmapPos = 160, 288; EnchantmentPlusChance = 2; DamageFlags = SLASH|PIERCE; + DescriptiveInfo = "Invented by Vulcan-Loki, an accomplished assassin and a grandmaster of the Guild, this dagger is always poisoned and ready for a kill."; Config BROKEN; { @@ -6672,6 +6846,7 @@ weepblade WieldedBitmapPos = 160, 16; EnchantmentPlusChance = 2; DamageFlags = SLASH|PIERCE; + DescriptiveInfo = "A single caustic tear of Scabies was trapped within this blade. It shall spill its acidic sorrow upon your adversaries."; Config BROKEN; { @@ -6706,6 +6881,7 @@ acidshield /* armor->shield-> */ AcidResistance = 3; NameSingular = "acid shield"; Alias == "shield of spitting"; + DescriptiveInfo = "A single caustic tear of Scabies was trapped within this shield. It shall spill its acidic sorrow upon your adversaries."; BitmapPos = 32, 336; WeaponCategory = SHIELDS; CanBeBroken = true; @@ -6761,6 +6937,7 @@ pantheonbook /* holybook-> */ AttachedGod = NONE; /* it's about them all, but none in particular */ WieldedBitmapPos = 176, 144; ReadDifficulty = 1500; + DescriptiveInfo = "The most famous theosophical work of professor Keathos of the Tweraif University, this book discusses the manifold, often-times contradictory myths and legends associated with the divine pantheon."; Alias = { 2, "tome", "knowledge"; } } @@ -6835,6 +7012,7 @@ firstbornchild Adjective = "first-born"; AttachedGod = SILVA; CanBeBurned = false; + DescriptiveInfo = "Stinky and scared."; Config MALE; { @@ -6877,6 +7055,7 @@ darkaxe /* meleeweapon-> */ WieldedBitmapPos = 160, 176; EnchantmentPlusChance = 2; DamageFlags = SLASH; + DescriptiveInfo = "Pure darkness drips from the blade of this axe."; Config BROKEN; { @@ -6918,6 +7097,7 @@ slowaxe /* meleeweapon-> */ WieldedBitmapPos = 160, 176; EnchantmentPlusChance = 2; DamageFlags = SLASH; + DescriptiveInfo = "Rime glazes the blade of this axe and your fingers stiffen a bit when you touch it."; Config BROKEN; { @@ -6961,6 +7141,7 @@ terrorscythe /* meleeweapon-> */ WieldedBitmapPos = 176, 48; EnchantmentPlusChance = 2; DamageFlags = SLASH; + DescriptiveInfo = "The hair on the back of your neck stand up as you grip the scythe."; Config BROKEN; { @@ -7001,6 +7182,7 @@ bansheesickle /* meleeweapon-> */ WieldedBitmapPos = 176, 32; EnchantmentPlusChance = 2; DamageFlags = SLASH; + DescriptiveInfo = "Every little movement of this curved blade elicits a muffled scream. You can only imagine how striking an enemy with full force would sound like."; Config BROKEN; { @@ -7037,6 +7219,7 @@ rustscythe /* meleeweapon-> */ WieldedBitmapPos = 176, 48; EnchantmentPlusChance = 2; DamageFlags = SLASH; + DescriptiveInfo = "A shower of tiny rust flakes falls from the scythe as you grip it, even though the blade seems no worse for wear."; Config BROKEN; { @@ -7078,6 +7261,7 @@ sharpaxe /* meleeweapon-> */ WieldedBitmapPos = 160, 176; EnchantmentPlusChance = 2; DamageFlags = SLASH; + DescriptiveInfo = "Do not try the sharpness of this axe with your thumb, or you might loose it along with the whole arm."; Config BROKEN; { @@ -7114,10 +7298,11 @@ celestialmonograph /* holybook-> */ Roundness = 70; IsTwoHanded = true; CreateDivineConfigurations = false; - AttachedGod = NONE; /* or more accurately Solicitus, the toppled god of atheism and hopeless situations */ + AttachedGod = NONE; /* or more accurately SOLICITUS */ WieldedBitmapPos = 176, 144; ReadDifficulty = 1500; Alias = { 3, "insight", "revelation", "truth"; } + DescriptiveInfo = "The forbidden holy book of Solicitus, the toppled god of atheism and hopeless situations. Even the most ardent of believers are said to loose their faith upon reading these sacrilegious words, though heretics might cherish the book as a form of absolution."; } materialmanual /* holybook-> */ @@ -7141,16 +7326,13 @@ materialmanual /* holybook-> */ AttachedGod = LORICATUS; WieldedBitmapPos = 176, 144; ReadDifficulty = 500; + DescriptiveInfo = "Morgo is the leading expert in the field of materials science and author of several best-selling popular science books such as Morgo's Magical Metamorphoses and Morgo's Mysteries Mastered (for Morons). His books have been published in nearly thirty kingdoms and have received starred reviews in Bazzarian Times and Sophos Recommends. Also included is an exclusive discount coupon for the upcoming Morgo's Mesmerizing Memoires where he recounts his early life as a Khaz-zadm survivor who lost the whole family in the Dwarven Wars. Preorder now!"; Alias = { 2, "materials", "material"; } } ullrbone { - /* - * Once upon a time an ancient orcish wizard was said to travel the seas - * on a magical bone inscribed with foul magic and wreak havoc on all lands - * he visited. - */ + DescriptiveInfo = "Once upon a time an ancient orcish wizard was said to travel the seas on a magical bone inscribed with foul magic and wreak havoc on all lands he visited."; Possibility = 1; DefaultMainVolume = 500; StrengthModifier = 100; @@ -7211,6 +7393,7 @@ taiaha /* meleeweapon-> */ MinCharges = 2; MaxCharges = 8; TeleportPriority = 1000; + DescriptiveInfo = "The weapon of a Sophite Knight. Not as random or as clumsy as a wand, an elegant weapon for a more civilized age. It can channel magical energies into a spark of raw destruction."; Config BROKEN; { @@ -7285,6 +7468,7 @@ trinket AttachedGod = SCABIES; IsSadistWeapon = true; DamageFlags = PIERCE; + DescriptiveInfo = "It's probably Echinocactus platyacanthus. Or maybe Mammillaria dixanthocentron? You're not really sure."; } Config POTTED_PLANT; @@ -7302,40 +7486,7 @@ trinket SecondaryMaterialConfig == PLANT_FIBER; Roundness = 60; AttachedGod = SILVA; - } - - Config DEAD_FISH; - { - Category = FOOD; - DefaultSize = 30; - DefaultMainVolume = 250; - HasSecondaryMaterial = false; - BitmapPos = 80, 336; - FormModifier = 25; - StrengthModifier = 75; - Adjective = "dead"; - NameSingular = "fish"; - /* one more "fish" to avoid conflicts with "fish bone" */ - Alias = { 2, "fish", "sardine"; } - MainMaterialConfig == SARDINE; - Roundness = 15; - AttachedGod = SEGES; - } - - Config BONE_FISH; - { - DefaultSize = 30; - DefaultMainVolume = 150; - HasSecondaryMaterial = false; - BitmapPos = 96, 336; - FormModifier = 25; - StrengthModifier = 75; - Adjective = "fish"; - NameSingular = "skeleton"; - Alias = { 2, "fish bone", "fishbone"; } - MainMaterialConfig == BONE; - Roundness = 15; - AttachedGod = SCABIES; + DescriptiveInfo = "It needs watering."; } Config SMALL_CLOCK; @@ -7353,6 +7504,7 @@ trinket SecondaryMaterialConfig == OAK_WOOD; Roundness = 75; AttachedGod = ATAVUS; + DescriptiveInfo = "Tick tock, goes the clock..."; } Config LARGE_CLOCK; @@ -7374,6 +7526,47 @@ trinket } } +fish +{ + Category = MISC; + IsAbstract = true; + CanBeBroken = false; + WieldedBitmapPos = 176, 144; + DefaultSize = 30; + FormModifier = 25; + StrengthModifier = 75; + Roundness = 15; + + Config DEAD_FISH; + { + Category = FOOD; + DefaultMainVolume = 250; + Possibility = 250; + BitmapPos = 80, 336; + Adjective = "dead"; + NameSingular = "fish"; + /* one more "fish" to avoid conflicts with "fish bone" */ + Alias = { 2, "fish", "sardine"; } + MainMaterialConfig == SARDINE; + AttachedGod = SEGES; + AllowedDungeons = { 7, + ASLONA_CASTLE, REBEL_CAMP, FUNGAL_CAVE, GOBLIN_FORT, PYRAMID, + MONDEDR, IRINOX; } + DescriptiveInfo = "Dead fish, dead fish, lovely lovely dead fish! Eat them up! Yum!"; + } + + Config BONE_FISH; + { + DefaultMainVolume = 150; + BitmapPos = 96, 336; + Adjective = "fish"; + NameSingular = "skeleton"; + Alias = { 2, "fish bone", "fishbone"; } + MainMaterialConfig == BONE; + AttachedGod = SCABIES; + } +} + gastrap /*materialcontainer-> */ { DefaultSize = 65; @@ -7387,11 +7580,12 @@ gastrap /*materialcontainer-> */ Adjective = "dwarven"; NameSingular = "gas trap"; MainMaterialConfig == IRON; - SecondaryMaterialConfig = { 8, + SecondaryMaterialConfig = { 12, MUSTARD_GAS, SKUNK_SMELL, FART, MAGIC_VAPOUR, SLEEPING_GAS, - EVIL_WONDER_STAFF_VAPOUR, SMOKE, TELEPORT_GAS; } - MaterialConfigChances = { 8, - 20, 30, 1, 15, 10, 15, 5, 10; } + EVIL_WONDER_STAFF_VAPOUR, SMOKE, TELEPORT_GAS, LAUGHING_GAS, + ECTOPLASM, ACID_GAS, FIRE_GAS; } + MaterialConfigChances = { 12, + 20, 30, 1, 15, 10, 15, 5, 10, 5, 1, 10, 10; } Roundness = 50; Price = 25; CanBePiled = false; @@ -7496,7 +7690,7 @@ eptyron /* meleeweapon-> */ CanBePiled = false; Alias == "Eptyron"; Price = 2500; - AttachedGod = NONE; /* or more accurately Solicitus, the toppled god of atheism and hopeless situations */ + AttachedGod = NONE; /* or more accurately SOLICITUS */ WieldedBitmapPos = 160, 176; EnchantmentPlusChance = 2; DamageFlags = SLASH; @@ -7546,9 +7740,234 @@ locationmap /* scroll-> */ IsAbstract = true; IsPolymorphSpawnable = false; CanBeCloned = false; + DescriptiveInfo = "A detailed map of a secret location."; Config BLACK_MARKET; { PostFix = "of black market"; } } + +nuke /* materialcontainer-> */ +{ + DefaultSize = 200; + Possibility = 0; + Category = MISC; + DefaultMainVolume = 10000; + DefaultSecondaryVolume = 1309; + BitmapPos = 112, 0; + WieldedBitmapPos = 160, 144; + Roundness = 100; + Price = 500; + Adjective = "thaumic"; + NameSingular = "bomb"; + ArticleMode = FORCE_THE; + CanBeWished = false; + IsQuestItem = true; + IsMaterialChangeable = false; + IsPolymorphable = false; + IsPolymorphSpawnable = false; + CanBeCloned = false; + CanBeBroken = false; + CanBeBurned = false; + CanBePiled = false; + IsKamikazeWeapon = true; + IsTwoHanded = true; + MainMaterialConfig == STEEL; + SecondaryMaterialConfig == URANIUM; + AttachedGod = MORTIFER; + Alias == "nuke"; +} + +weepobsidian /* stone-> */ +{ + Possibility = 0; + Adjective = "weeping obsidian"; + NameSingular = "shard"; + Price = 500; + MainMaterialConfig == OBSIDIAN; + ArticleMode = FORCE_THE; + CanBeWished = false; + IsQuestItem = true; + IsValuable = true; + IsMaterialChangeable = false; + IsPolymorphable = false; + IsPolymorphSpawnable = false; + CanBeCloned = false; + CanBeBroken = false; + CanBeBurned = false; + CanBePiled = false; + AttachedGod = SILVA; + DescriptiveInfo = "A single tear of Silva, petrified into the form of an obsidian shard. It brings torrential rains, growth and rejuvenation."; +} + +muramasa /* meleeweapon-> */ +{ + DefaultSize = 100; + Possibility = 0; + DefaultMainVolume = 110; + DefaultSecondaryVolume = 30; + WeaponCategory = LARGE_SWORDS; + BitmapPos = 112, 192; + FormModifier = 330; + StrengthModifier = 175; + Adjective = "wicked"; + NameSingular = "katana"; + PostFix = "named Asa'marum"; + MainMaterialConfig == BLACK_JADE; + SecondaryMaterialConfig == WHITE_JADE; + BaseEnchantment = 9; + Roundness = 50; + Price = 1500; + WieldedBitmapPos = 176, 112; + AttachedGod = SCABIES; + ArticleMode = FORCE_THE; + DamageFlags = SLASH|PIERCE; + IsTwoHanded = true; + IsPolymorphable = false; + IsPolymorphSpawnable = false; + IsMaterialChangeable = false; + IsDestroyable = false; + IsQuestItem = true; + CanBeWished = false; + CanBeCloned = false; + CanBeMirrored = true; + CanBeBroken = false; + CanBeBurned = false; + CanBePiled = false; + Alias == "Muramasa"; + DescriptiveInfo = "The legend goes that in the days of ancient past, two master artificers approached the queen of Aslona and knelt before her, asking her to judge their masterworks and declare the better weaponsmith. They each presented an enchanted sword of immaculate craft and extraordinary might. One was named E-numa sa-am, a katana so noble and pure that the mere sight of it struck fear into the hearts of all evil-doers. The other was named Asamarum, a wickedly sharp katana that killed with the smallest of scratches. The queen, being shrewd and cunning as she was, scrutinized both weapons for a long time and then ordered both of the artificers executed, for the swords were flawless and she didn't wish for them to create such a weapon for anyone else. From then on, the two swords served as the regalia of Aslonian sovereigns and each new king brandished them both during their coronation."; +} + +masamune /* meleeweapon-> */ +{ + DefaultSize = 100; + Possibility = 0; + DefaultMainVolume = 110; + DefaultSecondaryVolume = 30; + WeaponCategory = LARGE_SWORDS; + BitmapPos = 112, 192; + FormModifier = 175; + StrengthModifier = 130; + Adjective = "noble"; + NameSingular = "katana"; + PostFix = "named E-numa sa-am"; + MainMaterialConfig == WHITE_JADE; + SecondaryMaterialConfig == BLACK_JADE; + BaseEnchantment = 9; + Roundness = 50; + Price = 1500; + WieldedBitmapPos = 176, 112; + AttachedGod = SEGES; + ArticleMode = FORCE_THE; + DamageFlags = SLASH|PIERCE; + IsTwoHanded = true; + IsPolymorphable = false; + IsPolymorphSpawnable = false; + IsMaterialChangeable = false; + IsDestroyable = false; + IsQuestItem = true; + CanBeWished = false; + CanBeCloned = false; + CanBeMirrored = true; + CanBeBroken = false; + CanBeBurned = false; + CanBePiled = false; + Alias == "Masamune"; + DescriptiveInfo = "The legend goes that in the days of ancient past, two master artificers approached the queen of Aslona and knelt before her, asking her to judge their masterworks and declare the better weaponsmith. They each presented an enchanted sword of immaculate craft and extraordinary might. One was named E-numa sa-am, a katana so noble and pure that the mere sight of it struck fear into the hearts of all evil-doers. The other was named Asamarum, a wickedly sharp katana that killed with the smallest of scratches. The queen, being shrewd and cunning as she was, scrutinized both weapons for a long time and then ordered both of the artificers executed, for the swords were flawless and she didn't wish for them to create such a weapon for anyone else. From then on, the two swords served as the regalia of Aslonian sovereigns and each new king brandished them both during their coronation."; +} + +magestaff /* meleeweapon-> */ +{ + DefaultSize = 220; + NameSingular = "staff"; + NamePlural = "staves"; + WeaponCategory = BLUNT_WEAPONS; + StrengthModifier = 150; + BitmapPos = 64, 240; + FormModifier = 125; + DefaultMainVolume = 2000; + DefaultSecondaryVolume = 200; + Roundness = 20; + IsTwoHanded = true; + IsAbstract = true; + CanBePiled = false; + AttachedGod = INFUSCOR; + WieldedBitmapPos = 160, 304; + Price = 500; + EnchantmentPlusChance = 5; + DescriptiveInfo = "A staff imbued with arcane powers."; + + Config ROYAL_STAFF; + { + DescriptiveInfo = "Two hundred years ago, the hellish Dwarven Wars came to a close when the joint forces of Kharaz-arad and Khaz-zadm broke through the defences of Khazad-durr and ended their enemy in gunpowder explosions and clouds of mustard gas. Yet it is said that the sovereign of Khazad-durr was never captured, instead transforming into a gargantuan spider and scuttling away. His regalia vanished along with him, including the royal staff bearing the name Y'yter Durr."; + Possibility = 1; + Adjective = "royal"; + PostFix = "named Y'yter Durr"; + Alias == "Y'yter Durr"; + MainMaterialConfig == OCTIRON; + SecondaryMaterialConfig == GREEN_CRYSTAL; + AttachedGod = SCABIES; + EnchantmentPlusChance = 2; + BeamRange = 1; + BeamColor = RANDOM_COLOR; + BeamEffect = BEAM_POLYMORPH; + ArticleMode = FORCE_THE; + CanBeWished = false; + IsMaterialChangeable = false; + IsPolymorphable = false; + IsPolymorphSpawnable = false; + CanBeCloned = false; + CanBeMirrored = true; + GearStates = POLYMORPH|POLYMORPH_CONTROL; + } + + Config BROKEN|ROYAL_STAFF; + { + Possibility = 1; + BitmapPos = 64, 256; + WieldedBitmapPos = 176, 304; + GearStates = 0; + } +} + +pica +{ + DefaultSize = 40; + Possibility = 1; + DefaultMainVolume = 25; + DefaultSecondaryVolume = 25; + WeaponCategory = SMALL_SWORDS; + BitmapPos = 48, 256; + WieldedBitmapPos = 160, 288; + FormModifier = 75; + StrengthModifier = 80; + Adjective = "notched"; + NameSingular = "throwing knife"; + PostFix = "named Pica"; + MainMaterialConfig == STAR_METAL; + SecondaryMaterialConfig == MOON_SILVER; + BaseEnchantment = 1; + Roundness = 60; + Price = 500; + AttachedGod = CLEPTIA; + DamageFlags = PIERCE; + ArticleMode = FORCE_THE; + IsPolymorphable = false; + IsPolymorphSpawnable = false; + IsMaterialChangeable = false; + CanBeWished = false; + CanBeCloned = false; + CanBeMirrored = true; + CanBePiled = false; + DescriptiveInfo = "There was once a thief who challenged a god to a wager: If she can steal the very Moon from the sky, he will turn over his divinity. She won."; + Alias == "Pica"; + + Config BROKEN; + { + Possibility = 1; + BitmapPos = 48, 288; + WieldedBitmapPos = 176, 288; + EnchantmentPlusChance = 15; + } +} diff --git a/Script/material.dat b/Script/material.dat index ee740f2a9..8a318ea78 100644 --- a/Script/material.dat +++ b/Script/material.dat @@ -31,7 +31,7 @@ material /* Obligatory: Color */ PriceModifier = 0; Emitation = 0; - NutritionValue = 0; + NutritionValue = 10; ConsumeWisdomLimit = NO_LIMIT; /* Obligatory: NameStem */ /* NameStem by default: AdjectiveStem */ @@ -46,6 +46,7 @@ material /* Obligatory: AttachedGod */ StepInWisdomLimit = NO_LIMIT; Acidicity = 0; + Hotness = 0; NaturalForm = lump; HardenedMaterial = NONE; SoftenedMaterial = NONE; @@ -610,7 +611,7 @@ solid Config LINEN_CLOTH; { StrengthValue = 5; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 150; Color = rgb16(255, 236, 139); NameStem = "linen cloth"; @@ -627,7 +628,7 @@ solid Config HEMP_CLOTH; { StrengthValue = 5; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 150; Color = rgb16(80, 80, 176); NameStem = "hemp cloth"; @@ -644,7 +645,7 @@ solid Config WOOL_CLOTH; { StrengthValue = 5; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 250; Color = rgb16(250, 240, 230); NameStem = "wool cloth"; @@ -661,7 +662,7 @@ solid Config FELT; { StrengthValue = 5; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 250; Color = rgb16(111, 74, 37); NameStem = "felt"; @@ -677,7 +678,7 @@ solid Config FABRIC; { StrengthValue = 5; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 200; Color = rgb16(176, 0, 0); NameStem = "expensive fabric"; @@ -695,7 +696,7 @@ solid Config ELF_CLOTH; { StrengthValue = 20; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 300; Color = rgb16(39, 119, 20); NameStem = "elven cloth"; @@ -713,7 +714,7 @@ solid Config NYMPH_HAIR; { StrengthValue = 35; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 300; Color = rgb16(100, 100, 200); NameStem = "nymph hair"; @@ -730,7 +731,7 @@ solid Config OMMEL_HAIR; { StrengthValue = 60; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 400; Color = rgb16(160, 0, 0); NameStem = "ommel hair"; @@ -749,7 +750,7 @@ solid Config TROLL_WOOL; { StrengthValue = 95; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 500; Color = rgb16(233, 216, 180); NameStem = "troll wool"; @@ -767,7 +768,7 @@ solid Config ANGEL_HAIR; { StrengthValue = 140; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 300; Color = rgb16(200, 200, 0); NameStem = "angel hair"; @@ -787,7 +788,7 @@ solid Config SPIDER_SILK; { StrengthValue = 180; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 300; Color = rgb16(160, 160, 160); NameStem = "spider silk"; @@ -806,7 +807,7 @@ solid Config GOSSAMER; { StrengthValue = 220; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MAGIC; Density = 200; Color = rgb16(191, 62, 255); NameStem = "gossamer"; @@ -825,7 +826,7 @@ solid Config SPIRIT_CLOTH; { StrengthValue = 260; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MAGIC; Density = 50; Color = rgb16(154, 50, 205); Emitation = rgb24(100, 100, 160); @@ -846,7 +847,7 @@ solid Config HUMAN_SKIN; { StrengthValue = 15; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 600; Color = rgb16(255, 193, 193); NameStem = "human skin"; @@ -862,7 +863,7 @@ solid Config WOLF_SKIN; { StrengthValue = 20; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 1500; Color = rgb16(168, 147, 125); NameStem = "wolf pelt"; @@ -879,7 +880,7 @@ solid Config BEAR_SKIN; { StrengthValue = 30; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 2000; Color = rgb16(247, 247, 247); NameStem = "polar bear fur"; @@ -896,7 +897,7 @@ solid Config SNAKE_SKIN; { StrengthValue = 20; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 500; Color = rgb16(143, 188, 143); NameStem = "snake skin"; @@ -913,7 +914,7 @@ solid Config CROCODILE_SKIN; { StrengthValue = 30; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 800; Color = rgb16(110, 139, 61); NameStem = "crocodile skin"; @@ -932,7 +933,7 @@ solid Config BASILISK_SKIN; { StrengthValue = 60; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 1000; Color = rgb16(0, 205, 102); NameStem = "basilisk skin"; @@ -951,7 +952,7 @@ solid Config NAGA_SKIN; { StrengthValue = 90; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 1200; Color = rgb16(127, 255, 0); NameStem = "naga scale"; @@ -970,7 +971,7 @@ solid Config WYVERN_HIDE; { StrengthValue = 120; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 1500; Color = rgb16(84, 139, 84); NameStem = "wyvern hide"; @@ -989,7 +990,7 @@ solid Config HYDRA_HIDE; { StrengthValue = 140; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 2000; Color = rgb16(78, 238, 148); NameStem = "hydra hide"; @@ -1008,7 +1009,7 @@ solid Config LEATHER; { StrengthValue = 20; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 800; Color = rgb16(111, 64, 37); NameStem = "leather"; @@ -1025,7 +1026,7 @@ solid Config BLACK_LEATHER; { StrengthValue = 20; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 800; Color = rgb16(40, 40, 40); NameStem = "black leather"; @@ -1042,7 +1043,7 @@ solid Config HARDENED_LEATHER; { StrengthValue = 35; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 1200; Color = rgb16(100, 50, 30); NameStem = "hardened leather"; @@ -1059,7 +1060,7 @@ solid Config BOILED_LEATHER; { StrengthValue = 35; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 1200; Color = rgb16(80, 50, 30); NameStem = "boiled leather"; @@ -1076,7 +1077,7 @@ solid Config QUILTED_LEATHER; { StrengthValue = 35; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 1200; Color = rgb16(100, 70, 30); NameStem = "quilted leather"; @@ -1093,7 +1094,7 @@ solid Config STUDDED_LEATHER; { StrengthValue = 35; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 1200; Color = rgb16(100, 50, 0); NameStem = "studded leather"; @@ -1110,7 +1111,7 @@ solid Config TROLL_HIDE; { StrengthValue = 45; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 2000; Color = rgb16(128, 100, 32); NameStem = "troll hide"; @@ -1127,7 +1128,7 @@ solid Config IMP_HIDE; { StrengthValue = 95; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 2000; Color = rgb16(100, 0, 0); NameStem = "imp hide"; @@ -1144,7 +1145,7 @@ solid Config DRAGON_HIDE; { StrengthValue = 160; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 2500; Color = rgb16(160, 60, 32); NameStem = "dragon hide"; @@ -1163,7 +1164,7 @@ solid Config OUROBOROS_HIDE; { StrengthValue = 240; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 3000; Color = rgb16(205, 102, 0); NameStem = "ouroboros hide"; @@ -1184,7 +1185,7 @@ solid Config OSTRICH_FEATHER; { StrengthValue = 15; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 500; Color = rgb16(185, 176, 161); NameStem = "ostrich feather"; @@ -1201,7 +1202,7 @@ solid Config HARPY_FEATHER; { StrengthValue = 30; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 400; Color = rgb16(210, 180, 140); NameStem = "harpy feather"; @@ -1218,7 +1219,7 @@ solid Config GRIFFON_FEATHER; { StrengthValue = 60; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 300; Color = rgb16(218, 165, 32); NameStem = "griffon feather"; @@ -1235,7 +1236,7 @@ solid Config PHOENIX_FEATHER; { StrengthValue = 80; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 200; Color = rgb16(220, 220, 0); NameStem = "phoenix feather"; @@ -1254,7 +1255,7 @@ solid Config STYMPHALIAN_FEATHER; { StrengthValue = 150; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 1200; Color = rgb16(150, 100, 50); NameStem = "stymphalian feather"; @@ -1273,7 +1274,7 @@ solid Config GOLDEN_EAGLE_FEATHER; { StrengthValue = 300; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 100; Color = rgb16(80, 80, 176); NameStem = "golden eagle feather"; @@ -1292,7 +1293,7 @@ solid Config SEA_WEED; { StrengthValue = 15; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 800; Color = rgb16(46, 139, 87); NameStem = "seaweed"; @@ -1308,7 +1309,7 @@ solid Config FISH_SCALE; { StrengthValue = 20; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 600; Color = rgb16(193, 205, 193); NameStem = "fish scale"; @@ -1324,7 +1325,7 @@ solid Config MERMAID_HAIR; { StrengthValue = 30; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 200; Color = rgb16(0, 191, 255); NameStem = "mermaid hair"; @@ -1341,7 +1342,7 @@ solid Config SELKIE_SKIN; { StrengthValue = 75; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 1000; Color = rgb16(119, 136, 153); NameStem = "selkie skin"; @@ -1358,7 +1359,7 @@ solid Config SEA_SERPENT_SCALE; { StrengthValue = 115; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 1100; Color = rgb16(0, 0, 139); NameStem = "sea serpent scale"; @@ -1377,7 +1378,7 @@ solid Config KRAKEN_HIDE; { StrengthValue = 150; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 1300; Color = rgb16(205, 155, 155); NameStem = "kraken hide"; @@ -1396,7 +1397,7 @@ solid Config LEVIATHAN_HIDE; { StrengthValue = 175; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 1700; Color = rgb16(112, 128, 144); NameStem = "leviathan hide"; @@ -1416,7 +1417,7 @@ solid Config DREAM_CLOTH; { StrengthValue = 55; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MAGIC; Density = 150; Color = rgb16(255, 20, 147); NameStem = "dream cloth"; @@ -1435,7 +1436,7 @@ solid Config RAINBOW_CLOTH; { StrengthValue = 60; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MAGIC; Density = 150; Color = rgb16(135, 206, 250); NameStem = "rainbow cloth"; @@ -1454,7 +1455,7 @@ solid Config SHADOW_CLOTH; { StrengthValue = 50; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MAGIC; Density = 150; Color = rgb16(138, 121, 93); NameStem = "shadow cloth"; @@ -1869,6 +1870,7 @@ solid StrengthValue = 75; ConsumeType = CT_METAL; Density = 19100; + NutritionValue = 2000; Color = rgb16(27, 226, 21); Emitation = rgb24(50, 160, 50); PriceModifier = 1500; @@ -1890,6 +1892,7 @@ solid StrengthValue = 95; ConsumeType = CT_METAL; Density = 40000; + NutritionValue = 4000; Color = rgb16(236, 255, 0); Emitation = rgb24(160, 160, 100); PriceModifier = 2500; @@ -1911,6 +1914,7 @@ solid StrengthValue = 115; ConsumeType = CT_METAL; Density = 100000; + NutritionValue = 8000; Color = rgb16(70, 173, 212); Emitation = rgb24(70, 120, 160); PriceModifier = 5000; @@ -2149,7 +2153,7 @@ solid Config GRASS; { StrengthValue = 2; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 100; Color = rgb16(32, 110, 32); NameStem = "grass"; @@ -2167,7 +2171,7 @@ solid Config DARK_GRASS; { StrengthValue = 2; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 100; Color = rgb16(75, 100, 75); NameStem = "grass"; @@ -2185,7 +2189,7 @@ solid Config MYCELIUM; { StrengthValue = 2; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 100; Color = rgb16(47, 79, 79); NameStem = "mycelium"; @@ -2202,7 +2206,7 @@ solid Config LEAF; { StrengthValue = 3; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 500; Color = rgb16(0, 160, 0); NameStem = "leaf"; @@ -2218,7 +2222,7 @@ solid Config PALM_LEAF; { StrengthValue = 5; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 500; Color = rgb16(0, 160, 0); NameStem = "palm leaf"; @@ -2234,7 +2238,7 @@ solid Config WITCH_BARK; { StrengthValue = 50; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 700; Color = rgb16(128, 128, 0); NameStem = "witchbark"; @@ -2251,7 +2255,7 @@ solid Config DARK_PETAL; { StrengthValue = 75; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 300; Color = rgb16(0, 60, 0); NameStem = "dark petal"; @@ -2269,7 +2273,7 @@ solid Config STEEL_LEAF; { StrengthValue = 90; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 900; Color = rgb16(143, 188, 143); NameStem = "steeleaf"; @@ -2287,7 +2291,7 @@ solid Config CHLOROPHYTE; { StrengthValue = 125; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 1200; Color = rgb16(118, 169, 18); NameStem = "chlorophyte"; @@ -2307,7 +2311,7 @@ solid Config PAPYRUS; { StrengthValue = 3; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 800; Color = rgb16(200, 200, 200); NameStem = "papyrus"; @@ -2324,7 +2328,7 @@ solid Config PARCHMENT; { StrengthValue = 5; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 800; Color = rgb16(200, 200, 200); NameStem = "parchment"; @@ -2341,7 +2345,7 @@ solid Config VELLUM; { StrengthValue = 7; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 800; Color = rgb16(200, 200, 200); NameStem = "vellum"; @@ -2358,7 +2362,7 @@ solid Config PAPER_BOARD; { StrengthValue = 15; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 900; Color = rgb16(222, 222, 222); NameStem = "paperboard"; @@ -2377,7 +2381,7 @@ solid Config FUNGI_WOOD; { StrengthValue = 20; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 200; Color = rgb16(133, 99, 99); NameStem = "fungiwood"; @@ -2394,7 +2398,7 @@ solid Config SPRUCE_WOOD; { StrengthValue = 25; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 450; Color = rgb16(0, 89, 96); NameStem = "spruce wood"; @@ -2412,7 +2416,7 @@ solid Config SYCAMORE_WOOD; { StrengthValue = 32; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 600; Color = rgb16(102, 51, 0); NameStem = "sycamore wood"; @@ -2430,7 +2434,7 @@ solid Config ELM_WOOD; { StrengthValue = 40; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 700; Color = rgb16(130, 82, 1); NameStem = "elm wood"; @@ -2449,7 +2453,7 @@ solid Config ASH_WOOD; { StrengthValue = 45; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 850; Color = rgb16(159, 150, 136); NameStem = "ash wood"; @@ -2468,7 +2472,7 @@ solid Config CYPRESS_WOOD; { StrengthValue = 50; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 500; Color = rgb16(166, 128, 100); NameStem = "cypress wood"; @@ -2488,7 +2492,7 @@ solid Config HOLLY_WOOD; { StrengthValue = 20; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 750; Color = rgb16(163, 105, 95); NameStem = "holly wood"; @@ -2506,7 +2510,7 @@ solid Config YEW_WOOD; { StrengthValue = 25; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 700; Color = rgb16(147, 95, 86); NameStem = "yew wood"; @@ -2524,7 +2528,7 @@ solid Config BEECH_WOOD; { StrengthValue = 35; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 650; Color = rgb16(87, 51, 32); NameStem = "beech wood"; @@ -2542,7 +2546,7 @@ solid Config BAMBOO_WOOD; { StrengthValue = 45; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 400; Color = rgb16(83, 226, 86); NameStem = "bamboo"; @@ -2560,7 +2564,7 @@ solid Config MAHOGANY_WOOD; { StrengthValue = 55; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 850; Color = rgb16(103, 10, 10); NameStem = "mahogany wood"; @@ -2581,7 +2585,7 @@ solid Config BALSA_WOOD; { StrengthValue = 20; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 250; Color = rgb16(160, 108, 50); NameStem = "balsa wood"; @@ -2599,7 +2603,7 @@ solid Config PINE_WOOD; { StrengthValue = 25; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 400; Color = rgb16(208, 108, 42); NameStem = "pine wood"; @@ -2617,7 +2621,7 @@ solid Config FIR_WOOD; { StrengthValue = 30; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 500; Color = rgb16(140, 96, 48); NameStem = "fir wood"; @@ -2635,7 +2639,7 @@ solid Config BIRCH_WOOD; { StrengthValue = 35; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 600; Color = rgb16(130, 70, 32); NameStem = "birch wood"; @@ -2653,7 +2657,7 @@ solid Config OAK_WOOD; { StrengthValue = 45; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 700; Color = rgb16(124, 50, 16); NameStem = "oak wood"; @@ -2672,7 +2676,7 @@ solid Config TEAK_WOOD; { StrengthValue = 50; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 750; Color = rgb16(80, 40, 30); NameStem = "teak wood"; @@ -2691,7 +2695,7 @@ solid Config EBONY_WOOD; { StrengthValue = 65; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 1000; Color = rgb16(48, 48, 48); NameStem = "ebony wood"; @@ -2713,7 +2717,7 @@ solid Config KAURI_WOOD; { StrengthValue = 95; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 800; Color = rgb16(140, 92, 42); NameStem = "kauri wood"; @@ -2734,7 +2738,7 @@ solid Config RATA_WOOD; { StrengthValue = 155; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 700; Color = rgb16(166, 44, 24); NameStem = "rata wood"; @@ -2755,7 +2759,7 @@ solid Config SIDGURE_WOOD; /* magical mango world-tree wood */ { StrengthValue = 225; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 600; Color = rgb16(198, 110, 46); NameStem = "sidgure wood"; @@ -2931,7 +2935,7 @@ solid Config WAX; { StrengthValue = 2; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_ANIMAL; Density = 900; Color = rgb16(240, 238, 176); NameStem = "wax"; @@ -3302,7 +3306,7 @@ solid Config AMBER; { StrengthValue = 95; - ConsumeType = CT_MINERAL; /* Or CT_MISC_ORGANIC? */ + ConsumeType = CT_MINERAL; /* Or CT_MISC_ANIMAL? */ Density = 1100; Color = rgb16(255, 191, 0); NameStem = "amber"; @@ -3716,7 +3720,7 @@ solid NameStem = "black jade"; IntelligenceRequirement = 11; PriceModifier = 3000; - AttachedGod = SILVA; + AttachedGod = INFUSCOR; NaturalForm = stone; SoftenedMaterial = NEPHRITE; HardenedMaterial = RED_JADE; @@ -3734,7 +3738,7 @@ solid NameStem = "red jade"; IntelligenceRequirement = 19; PriceModifier = 3500; - AttachedGod = SILVA; + AttachedGod = CRUENTUS; NaturalForm = stone; SoftenedMaterial = BLACK_JADE; HardenedMaterial = GREEN_JADE; @@ -3752,7 +3756,7 @@ solid NameStem = "green jade"; IntelligenceRequirement = 25; PriceModifier = 4500; - AttachedGod = SILVA; + AttachedGod = SCABIES; NaturalForm = stone; SoftenedMaterial = RED_JADE; HardenedMaterial = BLUE_JADE; @@ -3770,7 +3774,7 @@ solid NameStem = "blue jade"; IntelligenceRequirement = 30; PriceModifier = 5500; - AttachedGod = SILVA; + AttachedGod = CLEPTIA; NaturalForm = stone; SoftenedMaterial = GREEN_JADE; HardenedMaterial = WHITE_JADE; @@ -3788,9 +3792,10 @@ solid NameStem = "white jade"; IntelligenceRequirement = 35; PriceModifier = 6500; - AttachedGod = SILVA; + AttachedGod = NEFAS; NaturalForm = stone; SoftenedMaterial = BLUE_JADE; + HardenedMaterial = BLACK_DIAMOND; CommonFlags = Base|IS_VALUABLE; CategoryFlags = Base|IS_GOLEM_MATERIAL; InteractionFlags = Base&~CAN_DISSOLVE; @@ -3894,6 +3899,46 @@ solid AttachedGod = SILVA; NaturalForm = stone; SoftenedMaterial = GREEN_CRYSTAL; + HardenedMaterial = CRYSTEEL; + CommonFlags = Base|IS_VALUABLE; + CategoryFlags = Base|IS_SPARKLING|IS_GOLEM_MATERIAL; + InteractionFlags = Base&~CAN_DISSOLVE; + } + + Config CRYSTEEL; + { + StrengthValue = 200; + ConsumeType = CT_MINERAL; + Density = 2000; + Color = rgb16(100, 149, 237); + Emitation = rgb24(135, 135, 160); + NameStem = "crysteel"; + Alpha = 150; + PriceModifier = 9000; + IntelligenceRequirement = 26; + AttachedGod = SILVA; + NaturalForm = stone; + SoftenedMaterial = SUN_CRYSTAL; + HardenedMaterial = FLAWLESS_CRYSTEEL; + CommonFlags = Base|IS_VALUABLE; + CategoryFlags = Base|IS_SPARKLING|IS_GOLEM_MATERIAL; + InteractionFlags = Base&~CAN_DISSOLVE; + } + + Config FLAWLESS_CRYSTEEL; + { + StrengthValue = 255; + ConsumeType = CT_MINERAL; + Density = 2000; + Color = rgb16(70, 130, 180); + Emitation = rgb24(135, 135, 160); + NameStem = "flawless crysteel"; + Alpha = 150; + PriceModifier = 10000; + IntelligenceRequirement = 35; + AttachedGod = SILVA; + NaturalForm = stone; + SoftenedMaterial = CRYSTEEL; CommonFlags = Base|IS_VALUABLE; CategoryFlags = Base|IS_SPARKLING|IS_GOLEM_MATERIAL; InteractionFlags = Base&~CAN_DISSOLVE; @@ -3957,6 +4002,7 @@ solid AttachedGod = INFUSCOR; HardenedMaterial = BRIM_STONE; CategoryFlags = Base|IS_GOLEM_MATERIAL; + InteractionFlags = Base|CAN_BURN; } Config BRIM_STONE; @@ -3970,6 +4016,7 @@ solid AttachedGod = INFUSCOR; SoftenedMaterial = SULFUR; CategoryFlags = Base|IS_GOLEM_MATERIAL; + InteractionFlags = Base|CAN_BURN; } /* Clays */ @@ -4139,6 +4186,7 @@ solid organic /* Substances that spoil but are not flesh. */ { + NutritionValue = 20; SpoilModifier = 10000; BodyFlags = Base&~USE_MATERIAL_ATTRIBUTES; @@ -4147,7 +4195,7 @@ organic /* Substances that spoil but are not flesh. */ Config PLANT_FIBER; { StrengthValue = 7; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 500; Color = rgb16(111, 64, 37); NameStem = "plant fiber"; @@ -4163,7 +4211,7 @@ organic /* Substances that spoil but are not flesh. */ Config MUTANT_PLANT_FIBER; { StrengthValue = 7; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 500; Color = rgb16(111, 64, 37); NameStem = "mutant plant fiber"; @@ -4180,7 +4228,7 @@ organic /* Substances that spoil but are not flesh. */ Config BANANA_PEEL; { StrengthValue = 5; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MISC_PLANT; Density = 500; Color = rgb16(224, 224, 0); NameStem = "banana peel"; @@ -4619,7 +4667,7 @@ organic /* Substances that spoil but are not flesh. */ Config RESIDUUM; { StrengthValue = 250; - ConsumeType = CT_MISC_ORGANIC; + ConsumeType = CT_MAGIC; Density = 10000; Color = rgb16(107, 255, 251); NameStem = "residuum"; @@ -4696,6 +4744,8 @@ gas Alpha = 150; AttachedGod = MORTIFER; HardenedMaterial = SOUL_STEEL; + EffectStrength = 3; + Effect = EFFECT_PHASE; NameFlags = Base|USE_AN; BodyFlags = Base|CAN_REGENERATE&~USE_MATERIAL_ATTRIBUTES; } @@ -4742,6 +4792,7 @@ gas Alpha = 150; AttachedGod = SILVA; BreatheMessage = "Ugh. It smells horrible here."; + HardenedMaterial = MUSTARD_GAS; CategoryFlags = Base|IS_SCARY; InteractionFlags = Base|CAN_EXPLODE; } @@ -4755,6 +4806,7 @@ gas BreatheMessage = "It smells like mustard here."; Effect = EFFECT_MUSTARD_GAS; StepInWisdomLimit = 8; + CategoryFlags = Base|IS_SCARY; } Config SLEEPING_GAS; @@ -4777,6 +4829,46 @@ gas HardenedMaterial = TELEPORT_FLUID; StepInWisdomLimit = 10; } + + Config LAUGHING_GAS; + { + Color = rgb16(255, 105, 180); + NameStem = "laughing gas"; + Alpha = 100; + AttachedGod = DULCIS; + Effect = EFFECT_LAUGH; + BreatheMessage = "You laugh like a madman."; + StepInWisdomLimit = 10; + } + + Config ACID_GAS; + { + Color = rgb16(160, 160, 0); + NameStem = "caustic fumes"; + Alpha = 150; + AttachedGod = SCABIES; + HardenedMaterial = SULPHURIC_ACID; + Effect = EFFECT_ACID_GAS; + EffectStrength = 25; + Acidicity = 50; + BreatheMessage = "It burns!"; + StepInWisdomLimit = 3; + } + + Config FIRE_GAS; + { + Color = rgb16(225, 25, 25); + Emitation = rgb24(150, 120, 90); + NameStem = "burning vapors"; + Alpha = 100; + AttachedGod = LEGIFER; + HardenedMaterial = NAPALM; + Effect = EFFECT_FIRE_GAS; + EffectStrength = 50; + Hotness = 75; + BreatheMessage = "It burns!"; + StepInWisdomLimit = 3; + } } liquid @@ -4792,7 +4884,7 @@ liquid Config ANTIDOTE_LIQUID; { StrengthValue = 1; - NutritionValue = 100; + NutritionValue = 10; Color = rgb16(128, 0, 128); NameStem = "antidote"; Effect = EFFECT_ANTIDOTE; @@ -4826,7 +4918,7 @@ liquid Config HEALING_LIQUID; { StrengthValue = 1; - NutritionValue = 250; + NutritionValue = 50; Color = rgb16(180, 0, 0); PriceModifier = 1500; NameStem = "healing potion"; @@ -4844,12 +4936,14 @@ liquid StrengthValue = 50; Flexibility = 50; Density = 50; - NutritionValue = 1; + NutritionValue = 0; RustModifier = 0; Color = rgb16(220, 30, 220); Emitation = rgb24(139, 0, 139); NameStem = "raw liquefied magic"; Alpha = 150; + EffectStrength = 250; + PriceModifier = 500; AttachedGod = SOPHOS; Effect = EFFECT_GOOD_WONDER_STAFF_VAPOUR; SoftenedMaterial = MAGIC_VAPOUR; @@ -4862,17 +4956,34 @@ liquid { StrengthValue = 1; Density = 50; - NutritionValue = 1; + NutritionValue = 0; RustModifier = 0; Color = rgb16(153, 50, 204); NameStem = "warp fluid"; Alpha = 150; + PriceModifier = 500; AttachedGod = SOPHOS; Effect = EFFECT_TELEPORTATION; SoftenedMaterial = TELEPORT_GAS; CommonFlags = Base|IS_VALUABLE; } + Config POLYMORPHINE; + { + StrengthValue = 1; + Density = 50; + NutritionValue = 0; + RustModifier = 0; + Color = rgb16(255, 20, 147); + NameStem = "polymorphine"; + Alpha = 50; + PriceModifier = 500; + AttachedGod = SCABIES; + Effect = EFFECT_POLYJUICE; + SoftenedMaterial = CHAMELEON_FLESH; + CommonFlags = Base|IS_VALUABLE; + } + Config PEA_SOUP; { StrengthValue = 5; @@ -4892,7 +5003,7 @@ liquid NutritionValue = 1500; Color = rgb16(244, 164, 96); NameStem = "chicken soup"; - Effect = EFFECT_HEAL; /* Chiken soup is good hwne you're ill! */ + Effect = EFFECT_HEAL; /* Chicken soup is good when you're ill! */ ConsumeEndMessage = CEM_HEALING_LIQUID; HitMessage = HM_HEALING_LIQUID; EffectStrength = 50; @@ -4974,6 +5085,7 @@ liquid Color = rgb16(0, 15, 85); NameStem = "ink"; Alpha = 175; + PriceModifier = 1000; AttachedGod = SOPHOS; CategoryFlags = Base|IS_GOLEM_MATERIAL; } @@ -4984,6 +5096,8 @@ liquid NutritionValue = -10; Color = rgb16(244, 244, 244); NameStem = "brine"; + Effect = EFFECT_PUKE; + EffectStrength = 50; Alpha = 175; AttachedGod = SILVA; SoftenedMaterial = WATER; @@ -5019,6 +5133,7 @@ liquid CommonFlags = Base|IS_VALUABLE; CategoryFlags = Base|IS_BEVERAGE; InteractionFlags = Base|CAN_EXPLODE|EFFECT_IS_GOOD; + DisablesPanicWhenConsumed = true; } Config VODKA; @@ -5055,6 +5170,7 @@ liquid CommonFlags = Base|IS_VALUABLE; CategoryFlags = Base|IS_BEVERAGE; InteractionFlags = Base|EFFECT_IS_GOOD; + DisablesPanicWhenConsumed = true; } Config WHITE_WINE; @@ -5072,6 +5188,7 @@ liquid CommonFlags = Base|IS_VALUABLE; CategoryFlags = Base|IS_BEVERAGE; InteractionFlags = Base|EFFECT_IS_GOOD; + DisablesPanicWhenConsumed = true; } Config RED_WINE; @@ -5089,12 +5206,13 @@ liquid CommonFlags = Base|IS_VALUABLE; CategoryFlags = Base|IS_BEVERAGE; InteractionFlags = Base|EFFECT_IS_GOOD; + DisablesPanicWhenConsumed = true; } Config BEER; { StrengthValue = 1; - NutritionValue = 80; + NutritionValue = 150; Color = rgb16(216, 120, 0); ConsumeWisdomLimit = 10; NameStem = "beer"; @@ -5106,12 +5224,13 @@ liquid CommonFlags = Base|IS_VALUABLE; CategoryFlags = Base|IS_BEVERAGE; InteractionFlags = Base|EFFECT_IS_GOOD; + DisablesPanicWhenConsumed = true; } Config ELF_ALE; { StrengthValue = 1; - NutritionValue = 80; + NutritionValue = 150; Color = rgb16(247, 148, 0); ConsumeWisdomLimit = 10; NameStem = "elven ale"; @@ -5129,7 +5248,7 @@ liquid Config DWARF_BEER; { StrengthValue = 1; - NutritionValue = 80; + NutritionValue = 150; Color = rgb16(192, 72, 0); ConsumeWisdomLimit = 10; NameStem = "dwarven stout"; @@ -5211,7 +5330,7 @@ liquid Effect = EFFECT_PEPSI; ConsumeEndMessage = CEM_PEPSI; HitMessage = HM_PEPSI; - AttachedGod = MELLIS; + AttachedGod = MORTIFER; RustModifier = 200; Acidicity = 10; PriceModifier = 10000; @@ -5242,19 +5361,20 @@ liquid StrengthValue = 1; NutritionValue = 10; Color = rgb16(144, 144, 240); - NameStem = "mustard gas in liquid form"; + NameStem = "liquefied mustard gas"; Alpha = 150; AttachedGod = CRUENTUS; Effect = EFFECT_MUSTARD_GAS_LIQUID; SoftenedMaterial = MUSTARD_GAS; ConsumeWisdomLimit = 3; + PriceModifier = 300; } Config LIQUID_DARKNESS; { StrengthValue = 1; Density = 10; - NutritionValue = 1; + NutritionValue = -100; RustModifier = 0; Color = rgb16(0, 0, 0); NameStem = "liquid darkness"; @@ -5271,7 +5391,7 @@ liquid { StrengthValue = 1; Density = 10; - NutritionValue = 1; + NutritionValue = 0; RustModifier = 0; Color = rgb16(40, 80, 40); NameStem = "liquid fear"; @@ -5289,28 +5409,30 @@ liquid { StrengthValue = 5; Density = 2300; - NutritionValue = 0; + NutritionValue = 10000; Color = rgb16(40, 43, 42); NameStem = "asphalt"; Flexibility = 25; AttachedGod = LORICATUS; Stickiness = 250; + Hotness = 10; ConsumeWisdomLimit = 3; StepInWisdomLimit = 4; + ConsumeType = CT_MINERAL; CategoryFlags = Base|IS_GOLEM_MATERIAL; } Config VINEGAR; { StrengthValue = 1; - NutritionValue = 1000; + NutritionValue = 80; Color = rgb16(100, 100, 0); NameStem = "vinegar"; Alpha = 150; Flexibility = 45; AttachedGod = SCABIES; RustModifier = 150; - Acidicity = 75; + Acidicity = 50; ConsumeWisdomLimit = 6; PriceModifier = 100; InteractionFlags = Base|AFFECT_INSIDE; @@ -5328,6 +5450,7 @@ liquid Alpha = 175; Flexibility = 35; AttachedGod = SCABIES; + Effect = EFFECT_PUKE; RustModifier = 200; Acidicity = 10; InteractionFlags = Base|AFFECT_INSIDE; @@ -5362,12 +5485,13 @@ liquid Config BLOOD; { StrengthValue = 1; - NutritionValue = 50; + NutritionValue = 500; Color = rgb16(100, 0, 0); NameStem = "blood"; Alpha = 175; Flexibility = 40; AttachedGod = CRUENTUS; + HardenedMaterial = RED_ICE; RustModifier = 1; CategoryFlags = Base|IS_BLOOD|IS_GOLEM_MATERIAL; } @@ -5375,7 +5499,7 @@ liquid Config GREEN_BLOOD; { StrengthValue = 1; - NutritionValue = 50; + NutritionValue = 500; Color = rgb16(0, 128, 0); NameStem = "green blood"; /* goblin blood */ Alpha = 175; @@ -5385,10 +5509,23 @@ liquid CategoryFlags = IS_BLOOD; } + Config BLACK_BLOOD; + { + StrengthValue = 1; + NutritionValue = 500; + Color = rgb16(40, 40, 40); + NameStem = "black blood"; /* orcish blood */ + Alpha = 175; + Flexibility = 40; + AttachedGod = CRUENTUS; + RustModifier = 1; + CategoryFlags = IS_BLOOD; + } + Config BLUE_BLOOD; { StrengthValue = 1; - NutritionValue = 50; + NutritionValue = 500; Color = rgb16(0, 0, 128); NameStem = "blue blood"; /* noble blood */ Alpha = 175; @@ -5396,12 +5533,13 @@ liquid AttachedGod = CRUENTUS; RustModifier = 1; CategoryFlags = IS_BLOOD; + PriceModifier = 1000; } Config PLANT_SAP; { StrengthValue = 1; - NutritionValue = 150; + NutritionValue = 250; Color = rgb16(126, 255, 126); NameStem = "sap"; /* plant blood */ Alpha = 175; @@ -5472,7 +5610,7 @@ liquid AttachedGod = CRUENTUS; CategoryFlags = IS_BLOOD; Effect = EFFECT_POISON; - HardenedMaterial = POISON_LIQUID; + HardenedMaterial = SICK_BLOOD; EffectStrength = 1000; ConsumeWisdomLimit = 3; } @@ -5493,6 +5631,7 @@ liquid InteractionFlags = Base|AFFECT_INSIDE; StepInWisdomLimit = 10; NameFlags = Base|USE_AN; + PriceModifier = 1000; } Config SICK_BLOOD; @@ -5508,7 +5647,7 @@ liquid InteractionFlags = Base|AFFECT_INSIDE; RustModifier = 100; Acidicity = 100; - Effect = EFFECT_POISON; + Effect = EFFECT_SICKNESS; EffectStrength = 1000; ConsumeWisdomLimit = 3; } @@ -5516,7 +5655,7 @@ liquid Config LIGHT_FROG_BLOOD; { StrengthValue = 1; - NutritionValue = 40; + NutritionValue = 400; Color = rgb16(150, 0, 0); Emitation = rgb24(160, 100, 100); NameStem = "light frog blood"; @@ -5528,6 +5667,7 @@ liquid Effect = EFFECT_ANTIDOTE; ConsumeEndMessage = CEM_ANTIDOTE; EffectStrength = 100; + PriceModifier = 1500; } Config OMMEL_BLOOD; @@ -5639,6 +5779,7 @@ liquid Alpha = 150; AttachedGod = SCABIES; Effect = EFFECT_POISON; + EffectStrength = 500; CategoryFlags = Base|IS_METAL|IS_GOLEM_MATERIAL; CommonFlags = Base|CAN_BE_WISHED; } @@ -5647,6 +5788,7 @@ liquid { StrengthValue = 30; Density = 11500; + NutritionValue = 5000; Flexibility = 40; RustModifier = 0; PriceModifier = 750; @@ -5658,7 +5800,6 @@ liquid IntelligenceRequirement = 11; Alpha = 175; AttachedGod = NEFAS; - Effect = EFFECT_POISON; CategoryFlags = Base|IS_METAL|IS_SPARKLING|IS_GOLEM_MATERIAL; CommonFlags = Base|IS_VALUABLE|CAN_BE_WISHED; } @@ -5694,7 +5835,45 @@ liquid RustModifier = 0; } - /* LAVA will require some further code. */ + /* Hot stuff */ + + Config LAVA; + { + StrengthValue = 20; + Density = 2400; + Flexibility = 20; + RustModifier = 0; + PriceModifier = 750; + ConsumeType = CT_MINERAL; + Color = rgb16(207, 16, 32); + Emitation = rgb24(150, 120, 90); + NameStem = "lava"; + AttachedGod = INFUSCOR; + HardenedMaterial = OBSIDIAN; + Hotness = 500; + Stickiness = 75; + ConsumeWisdomLimit = 2; + StepInWisdomLimit = 4; + CategoryFlags = Base|IS_GOLEM_MATERIAL; + InteractionFlags = Base|AFFECT_INSIDE; + } + + Config NAPALM; + { + StrengthValue = 1; + NutritionValue = 5; + RustModifier = 0; + Color = rgb16(255, 127, 0); + NameStem = "napalm"; + Alpha = 150; + Flexibility = 45; + AttachedGod = INFUSCOR; + Hotness = 150; + ConsumeWisdomLimit = 4; + PriceModifier = 100; + InteractionFlags = Base|AFFECT_INSIDE; + StepInWisdomLimit = 8; + } } flesh @@ -5709,7 +5888,7 @@ flesh SpoilModifier = 10000; PriceModifier = 60; BodyFlags = Base|IS_ALIVE|IS_WARM|IS_WARM_BLOODED|CAN_HAVE_PARASITE|CAN_REGENERATE&~USE_MATERIAL_ATTRIBUTES; - InteractionFlags = Base|IS_AFFECTED_BY_MUSTARD_GAS; + InteractionFlags = Base|IS_AFFECTED_BY_MUSTARD_GAS|CAN_BURN; FireResistance = 0; /* Food meat */ @@ -5803,7 +5982,6 @@ flesh PriceModifier = 40; AttachedGod = MORTIFER; BodyFlags = Base&~IS_WARM_BLOODED; - InteractionFlags = Base|CAN_BURN; } Config FROG_FLESH; @@ -5840,7 +6018,6 @@ flesh NameStem = "human flesh"; AttachedGod = MORTIFER; CategoryFlags = Base|IS_GOLEM_MATERIAL; - InteractionFlags = Base|CAN_BURN; } Config DOLPHIN_FLESH; @@ -5917,7 +6094,6 @@ flesh EffectStrength = 10; ConsumeWisdomLimit = 10; AttachedGod = MORTIFER; - InteractionFlags = Base|CAN_BURN; } Config KOBOLD_FLESH; @@ -5930,7 +6106,6 @@ flesh HitMessage = HM_KOBOLD_FLESH; PriceModifier = 0; AttachedGod = MORTIFER; - InteractionFlags = Base|CAN_BURN; BodyFlags = Base&~IS_WARM_BLOODED; } @@ -5938,7 +6113,6 @@ flesh { NameStem = "gibberling flesh"; AttachedGod = MORTIFER; - InteractionFlags = Base|CAN_BURN; BodyFlags = Base&~IS_WARM_BLOODED; } @@ -5974,7 +6148,6 @@ flesh NameStem = "dwarf flesh"; AttachedGod = MORTIFER; CategoryFlags = Base|IS_GOLEM_MATERIAL; - InteractionFlags = Base|CAN_BURN; } Config DAEMON_FLESH; @@ -6060,7 +6233,6 @@ flesh NutritionValue = 15; ConsumeWisdomLimit = 15; CategoryFlags = Base|IS_GOLEM_MATERIAL; - InteractionFlags = Base|CAN_BURN; } Config OSTRICH_FLESH; @@ -6104,7 +6276,6 @@ flesh PriceModifier = 100; CategoryFlags = Base|IS_GOLEM_MATERIAL; BodyFlags = Base&~IS_WARM&~IS_WARM_BLOODED; - InteractionFlags = Base|CAN_BURN; } Config MOOSE_FLESH; @@ -6171,7 +6342,6 @@ flesh EffectStrength = 20; PriceModifier = 150; BodyFlags = Base&~IS_WARM&~IS_WARM_BLOODED; - InteractionFlags = Base|CAN_BURN; } Config SICK_SPIDER_FLESH; @@ -6199,7 +6369,6 @@ flesh ConsumeWisdomLimit = 15; EffectStrength = 20; CategoryFlags = Base|IS_GOLEM_MATERIAL; - InteractionFlags = Base|CAN_BURN; } } diff --git a/Script/olterra.dat b/Script/olterra.dat index 3c1a4c5b8..496d18b05 100644 --- a/Script/olterra.dat +++ b/Script/olterra.dat @@ -164,6 +164,16 @@ wall BitmapPos = 48, 288; HPModifier = 25; } + + Config TENT_WALL; + { + OKVisualEffects = MIRROR; + DigMessage = "The tear down the tent wall."; + MainMaterialConfig == FELT; + NameSingular = "tent wall"; + BitmapPos = 0, 432; + HPModifier = 20; + } } decoration @@ -713,6 +723,14 @@ door /* olterrain-> */ NameSingular = "door"; Adjective = "very secret"; } + + Config CURTAIN; + { + DigMessage = "You tear at the curtain."; + BitmapPos = 0, 112; + NameSingular = "curtain"; + MainMaterialConfig == FELT; + } } stairs /* olterrain-> */ @@ -877,6 +895,14 @@ brokendoor /* olterrain->door-> */ NameSingular = "door"; Adjective = "broken very secret"; } + + Config CURTAIN; + { + DigMessage = "You tear down the curtain."; + BitmapPos = 0, 96; + Adjective = "torn"; + NameSingular = "curtain"; + } } boulder /* olterrain-> */ @@ -963,7 +989,7 @@ monsterportal coffin /* olterrain->decoration->olterraincontainer */ { - Walkability = ETHEREAL; + Walkability = ANY_MOVE; DigMessage = "You smash the coffin into pieces. The spirits are not happy."; CanBeDestroyed = true; MainMaterialConfig == FIR_WOOD; diff --git a/Script/owterra.dat b/Script/owterra.dat index 1da2e2543..e0a115e28 100644 --- a/Script/owterra.dat +++ b/Script/owterra.dat @@ -50,7 +50,7 @@ attnam elpuricave { BitmapPos = 16, 48; - NameStem = "hideous cave entry radiating pure evil"; + NameStem = "hideous cave entrance radiating pure evil"; UsesLongArticle = false; AttachedDungeon = ELPURI_CAVE; CanBeGenerated = false; /* Overridden by the world map generator */ @@ -88,9 +88,62 @@ underwatertunnelexit NativeGTerrainType = JUNGLE; } +aslonacastle +{ + BitmapPos = 112, 96; + NameStem = "mighty seaside castle"; + UsesLongArticle = false; + AttachedDungeon = ASLONA_CASTLE; + CanBeGenerated = true; + HideLocationInitially = true; + NativeGTerrainType = LEAFY_FOREST; +} + +rebelcamp +{ + BitmapPos = 16, 64; + NameStem = "hidden camp"; + UsesLongArticle = false; + AttachedDungeon = REBEL_CAMP; + CanBeGenerated = true; + HideLocationInitially = true; + NativeGTerrainType = STEPPE; +} + +goblinfort +{ + BitmapPos = 80, 96; + NameStem = "overgrown ruins of ancient fortress"; + UsesLongArticle = true; + AttachedDungeon = GOBLIN_FORT; + CanBeGenerated = true; + HideLocationInitially = true; + NativeGTerrainType = EVERGREEN_FOREST; +} + +fungalcave +{ + BitmapPos = 32, 64; + NameStem = "damp and dank cave entrance"; + AttachedDungeon = FUNGAL_CAVE; + CanBeGenerated = true; + HideLocationInitially = true; + NativeGTerrainType = JUNGLE; +} + +pyramid +{ + BitmapPos = 96, 128; + NameStem = "crumbling pyramid"; + AttachedDungeon = PYRAMID; + CanBeGenerated = true; + HideLocationInitially = true; + NativeGTerrainType = DESERT; +} + blackmarket { - BitmapPos = 64, 64; + BitmapPos = 80, 80; NameStem = "old, decrepit building"; UsesLongArticle = true; AttachedDungeon = BLACK_MARKET; @@ -104,7 +157,7 @@ locationAA BitmapPos = 0, 64; NameStem = "empty area"; UsesLongArticle = true; - AttachedDungeon = EMPTY_AREA; + AttachedDungeon = MONDEDR; AttachedArea = 0; CanBeGenerated = false; NativeGTerrainType = JUNGLE; @@ -115,7 +168,7 @@ locationAB BitmapPos = 0, 64; NameStem = "empty area"; UsesLongArticle = true; - AttachedDungeon = EMPTY_AREA; + AttachedDungeon = IRINOX; AttachedArea = 0; CanBeGenerated = false; NativeGTerrainType = JUNGLE; @@ -126,7 +179,7 @@ locationAC BitmapPos = 0, 64; NameStem = "empty area"; UsesLongArticle = true; - AttachedDungeon = EMPTY_AREA; + AttachedDungeon = DARK_FOREST; AttachedArea = 0; CanBeGenerated = false; NativeGTerrainType = JUNGLE; diff --git a/audio/MIDICodes.h b/audio/MIDICodes.h index b584fbea4..ab9a51b56 100644 --- a/audio/MIDICodes.h +++ b/audio/MIDICodes.h @@ -27,8 +27,8 @@ #define OCTAVE_OFFSET (1) #define MIDI_SYSTEM_COMMON_MSG (0xF0) -#define MIDI_MSG_TYPE_MASK (0xF0) -#define MIDI_CHANNEL_MASK (0x0F) +#define MIDI_MSG_TYPE_MASK (0xF0) +#define MIDI_CHANNEL_MASK (0x0F) #define MIDI_DATA_READY (0x01) #define MIDI_DATA_NOT_READY (0x00) @@ -141,268 +141,268 @@ typedef enum { } GENERAL_MIDI_PERCUSSION; typedef enum { - BANK_SELECT = 0, - MODULATION_WHEEL = 1, - BREATH_CONTROLLER = 2, - UNDEFINED3 = 3, - FOOT_CONTROLLER = 4, - PORTAMENTO_TIME = 5, - DATA_ENTRY_MSB = 6, - CHANNEL_VOLUME = 7, - BALANCE = 8, - UNDEFINED9 = 9, - PAN_POSITION = 10, - EXPRESSION_CONTROLLER = 11, - EFFECT_CONTROL_1 = 12, - EFFECT_CONTROL_2 = 13, - UNDEFINED14 = 14, - UNDEFINED15 = 15, - GENERAL_PURPOSE_CONTROLLER_1 = 16, - GENERAL_PURPOSE_CONTROLLER_2 = 17, - GENERAL_PURPOSE_CONTROLLER_3 = 18, - GENERAL_PURPOSE_CONTROLLER_4 = 19, - UNDEFINED20 = 20, - UNDEFINED21 = 21, - UNDEFINED22 = 22, - UNDEFINED23 = 23, - UNDEFINED24 = 24, - UNDEFINED25 = 25, - UNDEFINED26 = 26, - UNDEFINED27 = 27, - UNDEFINED28 = 28, - UNDEFINED29 = 29, - UNDEFINED30 = 30, - UNDEFINED31 = 31, - BANK_SELECT_LSB = 32, - MODULATION_WHEEL_LSB = 33, - BREATH_CONTROLLER_LSB = 34, - UNDEFINED35 = 35, - FOOT_CONTROLLER_LSB = 36, - PORTAMENTO_TIME_LSB = 37, - DATA_ENTRY_LSB = 38, - CHANNEL_VOLUME_LSB = 39, - BALANCE_LSB = 40, - UNDEFINED41 = 41, - PAN_POSITION_LSB = 42, - EXPRESSION_CONTROLLER_LSB = 43, - EFFECT_CONTROL_1_LSB = 44, - EFFECT_CONTROL_2_LSB = 45, - UNDEFINED46 = 46, - UNDEFINED47 = 47, - GENERAL_PURPOSE_CONTROLLER_1_LSB = 48, - GENERAL_PURPOSE_CONTROLLER_2_LSB = 49, - GENERAL_PURPOSE_CONTROLLER_3_LSB = 50, - GENERAL_PURPOSE_CONTROLLER_4_LSB = 51, - UNDEFINED52 = 52, - UNDEFINED53 = 53, - UNDEFINED54 = 54, - UNDEFINED55 = 55, - UNDEFINED56 = 56, - UNDEFINED57 = 57, - UNDEFINED58 = 58, - UNDEFINED59 = 59, - UNDEFINED60 = 60, - UNDEFINED61 = 61, - UNDEFINED62 = 62, - UNDEFINED63 = 63, - SUSTAIN = 64, - PORTAMENTO = 65, - SOSTENUTO = 66, - SOFT_PEDAL = 67, - LEGATO_FOOTSWITCH = 68, - HOLD_2 = 69, - SOUND_VARIATION = 70, - RESONANCE = 71, - SOUND_RELEASE_TIME = 72, - SOUND_ATTACK_TIME = 73, - FREQUENCY_CUTOFF = 74, - DECAY_TIME = 75, - VIBRATO_RATE = 76, - VIBRATO_DEPTH = 77, - VIBRATO_DELAY = 78, - UNDEFINED79 = 79, - GENERAL_PURPOSE_CONTROLLER_5 = 80, - GENERAL_PURPOSE_CONTROLLER_6 = 81, - GENERAL_PURPOSE_CONTROLLER_7 = 82, - GENERAL_PURPOSE_CONTROLLER_8 = 83, - PORTAMENTO_CONTROL = 84, - UNDEFINED85 = 85, - UNDEFINED86 = 86, - UNDEFINED87 = 87, - HIGH_RESOLUTION_VELOCITY_PREFIX = 88, - UNDEFINED89 = 89, - UNDEFINED90 = 90, - REVERB_LEVEL = 91, - TREMOLO_LEVEL = 92, - CHORUS_DEPTH = 93, - DETUNE_DEPTH = 94, - PHASER_LEVEL = 95, - DATA_INCREMENT = 96, - DATA_DECREMENT = 97, - NON_REGISTERED_PARAMETER_LSB = 98, - NON_REGISTERED_PARAMETER_MSB = 99, - REGISTERED_PARAMETER_LSB = 100, - REGISTERED_PARAMETER_MSB = 101, - UNDEFINED102 = 102, - UNDEFINED103 = 103, - UNDEFINED104 = 104, - UNDEFINED105 = 105, - UNDEFINED106 = 106, - UNDEFINED107 = 107, - UNDEFINED108 = 108, - UNDEFINED109 = 109, - UNDEFINED110 = 110, - UNDEFINED111 = 111, - UNDEFINED112 = 112, - UNDEFINED113 = 113, - UNDEFINED114 = 114, - UNDEFINED115 = 115, - UNDEFINED116 = 116, - UNDEFINED117 = 117, - UNDEFINED118 = 118, - UNDEFINED119 = 119, - ALL_SOUND_OFF = 120, - RESET_ALL_CONTROLLERS = 121, - LOCAL_CONTROL = 122, - ALL_NOTES_OFF = 123, - OMNI_OFF = 124, - OMNI_ON = 125, - MONO_ON = 126, - POLY_ON = 127, + BANK_SELECT = 0, + MODULATION_WHEEL = 1, + BREATH_CONTROLLER = 2, + UNDEFINED3 = 3, + FOOT_CONTROLLER = 4, + PORTAMENTO_TIME = 5, + DATA_ENTRY_MSB = 6, + CHANNEL_VOLUME = 7, + BALANCE = 8, + UNDEFINED9 = 9, + PAN_POSITION = 10, + EXPRESSION_CONTROLLER = 11, + EFFECT_CONTROL_1 = 12, + EFFECT_CONTROL_2 = 13, + UNDEFINED14 = 14, + UNDEFINED15 = 15, + GENERAL_PURPOSE_CONTROLLER_1 = 16, + GENERAL_PURPOSE_CONTROLLER_2 = 17, + GENERAL_PURPOSE_CONTROLLER_3 = 18, + GENERAL_PURPOSE_CONTROLLER_4 = 19, + UNDEFINED20 = 20, + UNDEFINED21 = 21, + UNDEFINED22 = 22, + UNDEFINED23 = 23, + UNDEFINED24 = 24, + UNDEFINED25 = 25, + UNDEFINED26 = 26, + UNDEFINED27 = 27, + UNDEFINED28 = 28, + UNDEFINED29 = 29, + UNDEFINED30 = 30, + UNDEFINED31 = 31, + BANK_SELECT_LSB = 32, + MODULATION_WHEEL_LSB = 33, + BREATH_CONTROLLER_LSB = 34, + UNDEFINED35 = 35, + FOOT_CONTROLLER_LSB = 36, + PORTAMENTO_TIME_LSB = 37, + DATA_ENTRY_LSB = 38, + CHANNEL_VOLUME_LSB = 39, + BALANCE_LSB = 40, + UNDEFINED41 = 41, + PAN_POSITION_LSB = 42, + EXPRESSION_CONTROLLER_LSB = 43, + EFFECT_CONTROL_1_LSB = 44, + EFFECT_CONTROL_2_LSB = 45, + UNDEFINED46 = 46, + UNDEFINED47 = 47, + GENERAL_PURPOSE_CONTROLLER_1_LSB = 48, + GENERAL_PURPOSE_CONTROLLER_2_LSB = 49, + GENERAL_PURPOSE_CONTROLLER_3_LSB = 50, + GENERAL_PURPOSE_CONTROLLER_4_LSB = 51, + UNDEFINED52 = 52, + UNDEFINED53 = 53, + UNDEFINED54 = 54, + UNDEFINED55 = 55, + UNDEFINED56 = 56, + UNDEFINED57 = 57, + UNDEFINED58 = 58, + UNDEFINED59 = 59, + UNDEFINED60 = 60, + UNDEFINED61 = 61, + UNDEFINED62 = 62, + UNDEFINED63 = 63, + SUSTAIN = 64, + PORTAMENTO = 65, + SOSTENUTO = 66, + SOFT_PEDAL = 67, + LEGATO_FOOTSWITCH = 68, + HOLD_2 = 69, + SOUND_VARIATION = 70, + RESONANCE = 71, + SOUND_RELEASE_TIME = 72, + SOUND_ATTACK_TIME = 73, + FREQUENCY_CUTOFF = 74, + DECAY_TIME = 75, + VIBRATO_RATE = 76, + VIBRATO_DEPTH = 77, + VIBRATO_DELAY = 78, + UNDEFINED79 = 79, + GENERAL_PURPOSE_CONTROLLER_5 = 80, + GENERAL_PURPOSE_CONTROLLER_6 = 81, + GENERAL_PURPOSE_CONTROLLER_7 = 82, + GENERAL_PURPOSE_CONTROLLER_8 = 83, + PORTAMENTO_CONTROL = 84, + UNDEFINED85 = 85, + UNDEFINED86 = 86, + UNDEFINED87 = 87, + HIGH_RESOLUTION_VELOCITY_PREFIX = 88, + UNDEFINED89 = 89, + UNDEFINED90 = 90, + REVERB_LEVEL = 91, + TREMOLO_LEVEL = 92, + CHORUS_DEPTH = 93, + DETUNE_DEPTH = 94, + PHASER_LEVEL = 95, + DATA_INCREMENT = 96, + DATA_DECREMENT = 97, + NON_REGISTERED_PARAMETER_LSB = 98, + NON_REGISTERED_PARAMETER_MSB = 99, + REGISTERED_PARAMETER_LSB = 100, + REGISTERED_PARAMETER_MSB = 101, + UNDEFINED102 = 102, + UNDEFINED103 = 103, + UNDEFINED104 = 104, + UNDEFINED105 = 105, + UNDEFINED106 = 106, + UNDEFINED107 = 107, + UNDEFINED108 = 108, + UNDEFINED109 = 109, + UNDEFINED110 = 110, + UNDEFINED111 = 111, + UNDEFINED112 = 112, + UNDEFINED113 = 113, + UNDEFINED114 = 114, + UNDEFINED115 = 115, + UNDEFINED116 = 116, + UNDEFINED117 = 117, + UNDEFINED118 = 118, + UNDEFINED119 = 119, + ALL_SOUND_OFF = 120, + RESET_ALL_CONTROLLERS = 121, + LOCAL_CONTROL = 122, + ALL_NOTES_OFF = 123, + OMNI_OFF = 124, + OMNI_ON = 125, + MONO_ON = 126, + POLY_ON = 127, } CONTROL_CHANGE_CODES; typedef enum { - ACC_GRAND_PIANO = 0 , - BRIGHT_ACC_PIANO = 1 , - ELEC_GRAND_PIANO = 2 , - HONKY_TONK_PIANO = 3 , - ELECTRIC_PIANO_1 = 4 , - ELECTRIC_PIANO_2 = 5 , - HARPSICHORD = 6 , - CLAVI = 7 , - CELESTA = 8 , - GLOCKENSPIEL = 9 , - MUSIC_BOX = 10 , - VIBRAPHONE = 11 , - MARIMBA = 12 , - XYLOPHONE = 13 , - TUBULAR_BELLS = 14 , - DULCIMER = 15 , - DRAWBAR_ORGAN = 16 , - PERCUSSIVE_ORGAN = 17 , - ROCK_ORGAN = 18 , - CHURCH_ORGAN = 19 , - REED_ORGAN = 20 , - ACCORDION = 21 , - HARMONICA = 22 , - TANGO_ACCORDION = 23 , - NYLON_GUITAR = 24 , - STEEL_GUITAR = 25 , - JAZZ_GUITAR = 26 , - CLEAN_GUITAR = 27 , - MUTED_GUITAR = 28 , - OVERDRIVEN_GUITAR = 29 , - DISTORTION_GUITAR = 30 , - GUITAR_HARMONICS = 31 , - ACOUSTIC_BASS = 32 , - FINGERED_BASS = 33 , - PICKED_BASS = 34 , - FRETLESS_BASS = 35 , - SLAP_BASS_1 = 36 , - SLAP_BASS_2 = 37 , - SYNTH_BASS_1 = 38 , - SYNTH_BASS_2 = 39 , - VIOLIN = 40 , - VIOLA = 41 , - CELLO = 42 , - CONTRABASS = 43 , - TREMOLO_STRINGS = 44 , - PIZZICATO_STRINGS = 45 , - ORCHESTRAL_HARP = 46 , - TIMPANI = 47 , - STRING_ENSEMBLE_1 = 48 , - STRING_ENSEMBLE_2 = 49 , - SYNTHSTRINGS_1 = 50 , - SYNTHSTRINGS_2 = 51 , - CHOIR_AAHS = 52 , - VOICE_OOHS = 53 , - SYNTH_VOICE = 54 , - ORCHESTRA_HIT = 55 , - TRUMPET = 56 , - TROMBONE = 57 , - TUBA = 58 , - MUTED_TRUMPET = 59 , - FRENCH_HORN = 60 , - BRASS_SECTION = 61 , - SYNTHBRASS_1 = 62 , - SYNTHBRASS_2 = 63 , - SOPRANO_SAX = 64 , - ALTO_SAX = 65 , - TENOR_SAX = 66 , - BARITONE_SAX = 67 , - OBOE = 68 , - ENGLISH_HORN = 69 , - BASSOON = 70 , - CLARINET = 71 , - PICCOLO = 72 , - FLUTE = 73 , - RECORDER = 74 , - PAN_FLUTE = 75 , - BLOWN_BOTTLE = 76 , - SHAKUHACHI = 77 , - WHISTLE = 78 , - OCARINA = 79 , - SQUARE_WAVE = 80 , - SAW_WAVE = 81 , - CALLIOPE = 82 , - CHIFF = 83 , - CHARANG = 84 , - VOICE = 85 , - FIFTH_WAVE = 86 , - BASS_LEAD = 87 , - NEW_AGE = 88 , - WARM = 89 , - POLYSYNTH = 90 , - CHOIR = 91 , - BOWED = 92 , - METALLIC = 93 , - HALO = 94 , - SWEEP = 95 , - RAIN = 96 , - SOUNDTRACK = 97 , - CRYSTAL = 98 , - ATMOSPHERE = 99 , - BRIGHTNESS = 100 , - GOBLINS = 101 , - ECHOES = 102 , - SCI_FI = 103 , - SITAR = 104 , - BANJO = 105 , - SHAMISEN = 106 , - KOTO = 107 , - KALIMBA = 108 , - BAG_PIPE = 109 , - FIDDLE = 110 , - SHANAI = 111 , - TINKLE_BELL = 112 , - AGOGO = 113 , - STEEL_DRUMS = 114 , - WOODBLOCK = 115 , - TAIKO_DRUM = 116 , - MELODIC_TOM = 117 , - SYNTH_DRUM = 118 , - REVERSE_CYMBAL = 119 , - GUITAR_FRET_NOISE = 120 , - BREATH_NOISE = 121 , - SEASHORE = 122 , - BIRD_TWEET = 123 , - TELEPHONE_RING = 124 , - HELICOPTER = 125 , - APPLAUSE = 126 , - GUNSHOT = 127 , + ACC_GRAND_PIANO = 0 , + BRIGHT_ACC_PIANO = 1 , + ELEC_GRAND_PIANO = 2 , + HONKY_TONK_PIANO = 3 , + ELECTRIC_PIANO_1 = 4 , + ELECTRIC_PIANO_2 = 5 , + HARPSICHORD = 6 , + CLAVI = 7 , + CELESTA = 8 , + GLOCKENSPIEL = 9 , + MUSIC_BOX = 10 , + VIBRAPHONE = 11 , + MARIMBA = 12 , + XYLOPHONE = 13 , + TUBULAR_BELLS = 14 , + DULCIMER = 15 , + DRAWBAR_ORGAN = 16 , + PERCUSSIVE_ORGAN = 17 , + ROCK_ORGAN = 18 , + CHURCH_ORGAN = 19 , + REED_ORGAN = 20 , + ACCORDION = 21 , + HARMONICA = 22 , + TANGO_ACCORDION = 23 , + NYLON_GUITAR = 24 , + STEEL_GUITAR = 25 , + JAZZ_GUITAR = 26 , + CLEAN_GUITAR = 27 , + MUTED_GUITAR = 28 , + OVERDRIVEN_GUITAR = 29 , + DISTORTION_GUITAR = 30 , + GUITAR_HARMONICS = 31 , + ACOUSTIC_BASS = 32 , + FINGERED_BASS = 33 , + PICKED_BASS = 34 , + FRETLESS_BASS = 35 , + SLAP_BASS_1 = 36 , + SLAP_BASS_2 = 37 , + SYNTH_BASS_1 = 38 , + SYNTH_BASS_2 = 39 , + VIOLIN = 40 , + VIOLA = 41 , + CELLO = 42 , + CONTRABASS = 43 , + TREMOLO_STRINGS = 44 , + PIZZICATO_STRINGS = 45 , + ORCHESTRAL_HARP = 46 , + TIMPANI = 47 , + STRING_ENSEMBLE_1 = 48 , + STRING_ENSEMBLE_2 = 49 , + SYNTHSTRINGS_1 = 50 , + SYNTHSTRINGS_2 = 51 , + CHOIR_AAHS = 52 , + VOICE_OOHS = 53 , + SYNTH_VOICE = 54 , + ORCHESTRA_HIT = 55 , + TRUMPET = 56 , + TROMBONE = 57 , + TUBA = 58 , + MUTED_TRUMPET = 59 , + FRENCH_HORN = 60 , + BRASS_SECTION = 61 , + SYNTHBRASS_1 = 62 , + SYNTHBRASS_2 = 63 , + SOPRANO_SAX = 64 , + ALTO_SAX = 65 , + TENOR_SAX = 66 , + BARITONE_SAX = 67 , + OBOE = 68 , + ENGLISH_HORN = 69 , + BASSOON = 70 , + CLARINET = 71 , + PICCOLO = 72 , + FLUTE = 73 , + RECORDER = 74 , + PAN_FLUTE = 75 , + BLOWN_BOTTLE = 76 , + SHAKUHACHI = 77 , + WHISTLE = 78 , + OCARINA = 79 , + SQUARE_WAVE = 80 , + SAW_WAVE = 81 , + CALLIOPE = 82 , + CHIFF = 83 , + CHARANG = 84 , + VOICE = 85 , + FIFTH_WAVE = 86 , + BASS_LEAD = 87 , + NEW_AGE = 88 , + WARM = 89 , + POLYSYNTH = 90 , + CHOIR = 91 , + BOWED = 92 , + METALLIC = 93 , + HALO = 94 , + SWEEP = 95 , + RAIN = 96 , + SOUNDTRACK = 97 , + CRYSTAL = 98 , + ATMOSPHERE = 99 , + BRIGHTNESS = 100 , + GOBLINS = 101 , + ECHOES = 102 , + SCI_FI = 103 , + SITAR = 104 , + BANJO = 105 , + SHAMISEN = 106 , + KOTO = 107 , + KALIMBA = 108 , + BAG_PIPE = 109 , + FIDDLE = 110 , + SHANAI = 111 , + TINKLE_BELL = 112 , + AGOGO = 113 , + STEEL_DRUMS = 114 , + WOODBLOCK = 115 , + TAIKO_DRUM = 116 , + MELODIC_TOM = 117 , + SYNTH_DRUM = 118 , + REVERSE_CYMBAL = 119 , + GUITAR_FRET_NOISE = 120 , + BREATH_NOISE = 121 , + SEASHORE = 122 , + BIRD_TWEET = 123 , + TELEPHONE_RING = 124 , + HELICOPTER = 125 , + APPLAUSE = 126 , + GUNSHOT = 127 , } MIDI_GM_INSTRUMENTS; diff --git a/audio/MIDIUtils.cpp b/audio/MIDIUtils.cpp index 9c0e9bd21..e4858d1bb 100644 --- a/audio/MIDIUtils.cpp +++ b/audio/MIDIUtils.cpp @@ -25,265 +25,265 @@ #include "MIDICodes.h" #include "MIDIUtils.h" -const char ACC_GRAND_PIANO_STRING[] = "Acc. Grand Piano"; -const char BRIGHT_ACC_PIANO_STRING[] = "Bright Acc. Piano"; -const char ELEC_GRAND_PIANO_STRING[] = "Elec. Grand Piano"; -const char HONKY_TONK_PIANO_STRING[] = "Honky-tonk Piano"; -const char ELECTRIC_PIANO_1_STRING[] = "Electric Piano 1"; -const char ELECTRIC_PIANO_2_STRING[] = "Electric Piano 2"; -const char HARPSICHORD_STRING[] = "Harpsichord"; -const char CLAVI_STRING[] = "Clavi"; -const char CELESTA_STRING[] = "Celesta"; -const char GLOCKENSPIEL_STRING[] = "Glockenspiel"; -const char MUSIC_BOX_STRING[] = "Music Box"; -const char VIBRAPHONE_STRING[] = "Vibraphone"; -const char MARIMBA_STRING[] = "Marimba"; -const char XYLOPHONE_STRING[] = "Xylophone"; -const char TUBULAR_BELLS_STRING[] = "Tubular Bells"; -const char DULCIMER_STRING[] = "Dulcimer"; -const char DRAWBAR_ORGAN_STRING[] = "Drawbar Organ"; -const char PERCUSSIVE_ORGAN_STRING[] = "Percussive Organ"; -const char ROCK_ORGAN_STRING[] = "Rock Organ"; -const char CHURCH_ORGAN_STRING[] = "Church Organ"; -const char REED_ORGAN_STRING[] = "Reed Organ"; -const char ACCORDION_STRING[] = "Accordion"; -const char HARMONICA_STRING[] = "Harmonica"; -const char TANGO_ACCORDION_STRING[] = "Tango Accordion"; -const char NYLON_GUITAR_STRING[] = "Nylon Guitar"; -const char STEEL_GUITAR_STRING[] = "Steel Guitar"; -const char JAZZ_GUITAR_STRING[] = "Jazz Guitar"; -const char CLEAN_GUITAR_STRING[] = "Clean Guitar"; -const char MUTED_GUITAR_STRING[] = "Muted Guitar"; -const char OVERDRIVEN_GUITAR_STRING[] = "Overdriven Guitar"; -const char DISTORTION_GUITAR_STRING[] = "Distortion Guitar"; -const char GUITAR_HARMONICS_STRING[] = "Guitar harmonics"; -const char ACOUSTIC_BASS_STRING[] = "Acoustic Bass"; -const char FINGERED_BASS_STRING[] = "Fingered Bass"; -const char PICKED_BASS_STRING[] = "Picked Bass"; -const char FRETLESS_BASS_STRING[] = "Fretless Bass"; -const char SLAP_BASS_1_STRING[] = "Slap Bass 1"; -const char SLAP_BASS_2_STRING[] = "Slap Bass 2"; -const char SYNTH_BASS_1_STRING[] = "Synth Bass 1"; -const char SYNTH_BASS_2_STRING[] = "Synth Bass 2"; -const char VIOLIN_STRING[] = "Violin"; -const char VIOLA_STRING[] = "Viola"; -const char CELLO_STRING[] = "Cello"; -const char CONTRABASS_STRING[] = "Contrabass"; -const char TREMOLO_STRINGS_STRING[] = "Tremolo Strings"; -const char PIZZICATO_STRINGS_STRING[] = "Pizzicato Strings"; -const char ORCHESTRAL_HARP_STRING[] = "Orchestral Harp"; -const char TIMPANI_STRING[] = "Timpani"; -const char STRING_ENSEMBLE_1_STRING[] = "String Ensemble 1"; -const char STRING_ENSEMBLE_2_STRING[] = "String Ensemble 2"; -const char SYNTHSTRINGS_1_STRING[] = "SynthStrings 1"; -const char SYNTHSTRINGS_2_STRING[] = "SynthStrings 2"; -const char CHOIR_AAHS_STRING[] = "Choir Aahs"; -const char VOICE_OOHS_STRING[] = "Voice Oohs"; -const char SYNTH_VOICE_STRING[] = "Synth Voice"; -const char ORCHESTRA_HIT_STRING[] = "Orchestra Hit"; -const char TRUMPET_STRING[] = "Trumpet"; -const char TROMBONE_STRING[] = "Trombone"; -const char TUBA_STRING[] = "Tuba"; -const char MUTED_TRUMPET_STRING[] = "Muted Trumpet"; -const char FRENCH_HORN_STRING[] = "French Horn"; -const char BRASS_SECTION_STRING[] = "Brass Section"; -const char SYNTHBRASS_1_STRING[] = "SynthBrass 1"; -const char SYNTHBRASS_2_STRING[] = "SynthBrass 2"; -const char SOPRANO_SAX_STRING[] = "Soprano Sax"; -const char ALTO_SAX_STRING[] = "Alto Sax"; -const char TENOR_SAX_STRING[] = "Tenor Sax"; -const char BARITONE_SAX_STRING[] = "Baritone Sax"; -const char OBOE_STRING[] = "Oboe"; -const char ENGLISH_HORN_STRING[] = "English Horn"; -const char BASSOON_STRING[] = "Bassoon"; -const char CLARINET_STRING[] = "Clarinet"; -const char PICCOLO_STRING[] = "Piccolo"; -const char FLUTE_STRING[] = "Flute"; -const char RECORDER_STRING[] = "Recorder"; -const char PAN_FLUTE_STRING[] = "Pan Flute"; -const char BLOWN_BOTTLE_STRING[] = "Blown Bottle"; -const char SHAKUHACHI_STRING[] = "Shakuhachi"; -const char WHISTLE_STRING[] = "Whistle"; -const char OCARINA_STRING[] = "Ocarina"; -const char SQUARE_WAVE_STRING[] = "Square Wave"; -const char SAW_WAVE_STRING[] = "Saw Wave"; -const char CALLIOPE_STRING[] = "Calliope"; -const char CHIFF_STRING[] = "Chiff"; -const char CHARANG_STRING[] = "Charang"; -const char VOICE_STRING[] = "Voice"; -const char FIFTH_WAVE_STRING[] = "5th Wave"; -const char BASS_LEAD_STRING[] = "Bass Lead"; -const char NEW_AGE_STRING[] = "New Age"; -const char WARM_STRING[] = "Warm"; -const char POLYSYNTH_STRING[] = "PolySynth"; -const char CHOIR_STRING[] = "Choir"; -const char BOWED_STRING[] = "Bowed"; -const char METALLIC_STRING[] = "Metallic"; -const char HALO_STRING[] = "Halo"; -const char SWEEP_STRING[] = "Sweep"; -const char RAIN_STRING[] = "Rain"; -const char SOUNDTRACK_STRING[] = "Soundtrack"; -const char CRYSTAL_STRING[] = "Crystal"; -const char ATMOSPHERE_STRING[] = "Atmosphere"; -const char BRIGHTNESS_STRING[] = "Brightness"; -const char GOBLINS_STRING[] = "Goblins"; -const char ECHOES_STRING[] = "Echoes"; -const char SCI_FI_STRING[] = "Sci-Fi"; -const char SITAR_STRING[] = "Sitar"; -const char BANJO_STRING[] = "Banjo"; -const char SHAMISEN_STRING[] = "Shamisen"; -const char KOTO_STRING[] = "Koto"; -const char KALIMBA_STRING[] = "Kalimba"; -const char BAG_PIPE_STRING[] = "Bag pipe"; -const char FIDDLE_STRING[] = "Fiddle"; -const char SHANAI_STRING[] = "Shanai"; -const char TINKLE_BELL_STRING[] = "Tinkle Bell"; -const char AGOGO_STRING[] = "Agogo"; -const char STEEL_DRUMS_STRING[] = "Steel Drums"; -const char WOODBLOCK_STRING[] = "Woodblock"; -const char TAIKO_DRUM_STRING[] = "Taiko Drum"; -const char MELODIC_TOM_STRING[] = "Melodic Tom"; -const char SYNTH_DRUM_STRING[] = "Synth Drum"; -const char REVERSE_CYMBAL_STRING[] = "Reverse Cymbal"; -const char GUITAR_FRET_NOISE_STRING[] = "Guitar Fret Noise"; -const char BREATH_NOISE_STRING[] = "Breath Noise"; -const char SEASHORE_STRING[] = "Seashore"; -const char BIRD_TWEET_STRING[] = "Bird Tweet"; -const char TELEPHONE_RING_STRING[] = "Telephone Ring"; -const char HELICOPTER_STRING[] = "Helicopter"; -const char APPLAUSE_STRING[] = "Applause"; -const char GUNSHOT_STRING[] = "Gunshot"; +const char ACC_GRAND_PIANO_STRING[] = "Acc. Grand Piano"; +const char BRIGHT_ACC_PIANO_STRING[] = "Bright Acc. Piano"; +const char ELEC_GRAND_PIANO_STRING[] = "Elec. Grand Piano"; +const char HONKY_TONK_PIANO_STRING[] = "Honky-tonk Piano"; +const char ELECTRIC_PIANO_1_STRING[] = "Electric Piano 1"; +const char ELECTRIC_PIANO_2_STRING[] = "Electric Piano 2"; +const char HARPSICHORD_STRING[] = "Harpsichord"; +const char CLAVI_STRING[] = "Clavi"; +const char CELESTA_STRING[] = "Celesta"; +const char GLOCKENSPIEL_STRING[] = "Glockenspiel"; +const char MUSIC_BOX_STRING[] = "Music Box"; +const char VIBRAPHONE_STRING[] = "Vibraphone"; +const char MARIMBA_STRING[] = "Marimba"; +const char XYLOPHONE_STRING[] = "Xylophone"; +const char TUBULAR_BELLS_STRING[] = "Tubular Bells"; +const char DULCIMER_STRING[] = "Dulcimer"; +const char DRAWBAR_ORGAN_STRING[] = "Drawbar Organ"; +const char PERCUSSIVE_ORGAN_STRING[] = "Percussive Organ"; +const char ROCK_ORGAN_STRING[] = "Rock Organ"; +const char CHURCH_ORGAN_STRING[] = "Church Organ"; +const char REED_ORGAN_STRING[] = "Reed Organ"; +const char ACCORDION_STRING[] = "Accordion"; +const char HARMONICA_STRING[] = "Harmonica"; +const char TANGO_ACCORDION_STRING[] = "Tango Accordion"; +const char NYLON_GUITAR_STRING[] = "Nylon Guitar"; +const char STEEL_GUITAR_STRING[] = "Steel Guitar"; +const char JAZZ_GUITAR_STRING[] = "Jazz Guitar"; +const char CLEAN_GUITAR_STRING[] = "Clean Guitar"; +const char MUTED_GUITAR_STRING[] = "Muted Guitar"; +const char OVERDRIVEN_GUITAR_STRING[] = "Overdriven Guitar"; +const char DISTORTION_GUITAR_STRING[] = "Distortion Guitar"; +const char GUITAR_HARMONICS_STRING[] = "Guitar harmonics"; +const char ACOUSTIC_BASS_STRING[] = "Acoustic Bass"; +const char FINGERED_BASS_STRING[] = "Fingered Bass"; +const char PICKED_BASS_STRING[] = "Picked Bass"; +const char FRETLESS_BASS_STRING[] = "Fretless Bass"; +const char SLAP_BASS_1_STRING[] = "Slap Bass 1"; +const char SLAP_BASS_2_STRING[] = "Slap Bass 2"; +const char SYNTH_BASS_1_STRING[] = "Synth Bass 1"; +const char SYNTH_BASS_2_STRING[] = "Synth Bass 2"; +const char VIOLIN_STRING[] = "Violin"; +const char VIOLA_STRING[] = "Viola"; +const char CELLO_STRING[] = "Cello"; +const char CONTRABASS_STRING[] = "Contrabass"; +const char TREMOLO_STRINGS_STRING[] = "Tremolo Strings"; +const char PIZZICATO_STRINGS_STRING[] = "Pizzicato Strings"; +const char ORCHESTRAL_HARP_STRING[] = "Orchestral Harp"; +const char TIMPANI_STRING[] = "Timpani"; +const char STRING_ENSEMBLE_1_STRING[] = "String Ensemble 1"; +const char STRING_ENSEMBLE_2_STRING[] = "String Ensemble 2"; +const char SYNTHSTRINGS_1_STRING[] = "SynthStrings 1"; +const char SYNTHSTRINGS_2_STRING[] = "SynthStrings 2"; +const char CHOIR_AAHS_STRING[] = "Choir Aahs"; +const char VOICE_OOHS_STRING[] = "Voice Oohs"; +const char SYNTH_VOICE_STRING[] = "Synth Voice"; +const char ORCHESTRA_HIT_STRING[] = "Orchestra Hit"; +const char TRUMPET_STRING[] = "Trumpet"; +const char TROMBONE_STRING[] = "Trombone"; +const char TUBA_STRING[] = "Tuba"; +const char MUTED_TRUMPET_STRING[] = "Muted Trumpet"; +const char FRENCH_HORN_STRING[] = "French Horn"; +const char BRASS_SECTION_STRING[] = "Brass Section"; +const char SYNTHBRASS_1_STRING[] = "SynthBrass 1"; +const char SYNTHBRASS_2_STRING[] = "SynthBrass 2"; +const char SOPRANO_SAX_STRING[] = "Soprano Sax"; +const char ALTO_SAX_STRING[] = "Alto Sax"; +const char TENOR_SAX_STRING[] = "Tenor Sax"; +const char BARITONE_SAX_STRING[] = "Baritone Sax"; +const char OBOE_STRING[] = "Oboe"; +const char ENGLISH_HORN_STRING[] = "English Horn"; +const char BASSOON_STRING[] = "Bassoon"; +const char CLARINET_STRING[] = "Clarinet"; +const char PICCOLO_STRING[] = "Piccolo"; +const char FLUTE_STRING[] = "Flute"; +const char RECORDER_STRING[] = "Recorder"; +const char PAN_FLUTE_STRING[] = "Pan Flute"; +const char BLOWN_BOTTLE_STRING[] = "Blown Bottle"; +const char SHAKUHACHI_STRING[] = "Shakuhachi"; +const char WHISTLE_STRING[] = "Whistle"; +const char OCARINA_STRING[] = "Ocarina"; +const char SQUARE_WAVE_STRING[] = "Square Wave"; +const char SAW_WAVE_STRING[] = "Saw Wave"; +const char CALLIOPE_STRING[] = "Calliope"; +const char CHIFF_STRING[] = "Chiff"; +const char CHARANG_STRING[] = "Charang"; +const char VOICE_STRING[] = "Voice"; +const char FIFTH_WAVE_STRING[] = "5th Wave"; +const char BASS_LEAD_STRING[] = "Bass Lead"; +const char NEW_AGE_STRING[] = "New Age"; +const char WARM_STRING[] = "Warm"; +const char POLYSYNTH_STRING[] = "PolySynth"; +const char CHOIR_STRING[] = "Choir"; +const char BOWED_STRING[] = "Bowed"; +const char METALLIC_STRING[] = "Metallic"; +const char HALO_STRING[] = "Halo"; +const char SWEEP_STRING[] = "Sweep"; +const char RAIN_STRING[] = "Rain"; +const char SOUNDTRACK_STRING[] = "Soundtrack"; +const char CRYSTAL_STRING[] = "Crystal"; +const char ATMOSPHERE_STRING[] = "Atmosphere"; +const char BRIGHTNESS_STRING[] = "Brightness"; +const char GOBLINS_STRING[] = "Goblins"; +const char ECHOES_STRING[] = "Echoes"; +const char SCI_FI_STRING[] = "Sci-Fi"; +const char SITAR_STRING[] = "Sitar"; +const char BANJO_STRING[] = "Banjo"; +const char SHAMISEN_STRING[] = "Shamisen"; +const char KOTO_STRING[] = "Koto"; +const char KALIMBA_STRING[] = "Kalimba"; +const char BAG_PIPE_STRING[] = "Bag pipe"; +const char FIDDLE_STRING[] = "Fiddle"; +const char SHANAI_STRING[] = "Shanai"; +const char TINKLE_BELL_STRING[] = "Tinkle Bell"; +const char AGOGO_STRING[] = "Agogo"; +const char STEEL_DRUMS_STRING[] = "Steel Drums"; +const char WOODBLOCK_STRING[] = "Woodblock"; +const char TAIKO_DRUM_STRING[] = "Taiko Drum"; +const char MELODIC_TOM_STRING[] = "Melodic Tom"; +const char SYNTH_DRUM_STRING[] = "Synth Drum"; +const char REVERSE_CYMBAL_STRING[] = "Reverse Cymbal"; +const char GUITAR_FRET_NOISE_STRING[] = "Guitar Fret Noise"; +const char BREATH_NOISE_STRING[] = "Breath Noise"; +const char SEASHORE_STRING[] = "Seashore"; +const char BIRD_TWEET_STRING[] = "Bird Tweet"; +const char TELEPHONE_RING_STRING[] = "Telephone Ring"; +const char HELICOPTER_STRING[] = "Helicopter"; +const char APPLAUSE_STRING[] = "Applause"; +const char GUNSHOT_STRING[] = "Gunshot"; -const char* MIDI_GM_INSTRUMENT_NAMES[] = { - ACC_GRAND_PIANO_STRING, - BRIGHT_ACC_PIANO_STRING, - ELEC_GRAND_PIANO_STRING, - HONKY_TONK_PIANO_STRING, - ELECTRIC_PIANO_1_STRING, - ELECTRIC_PIANO_2_STRING, - HARPSICHORD_STRING, - CLAVI_STRING, - CELESTA_STRING, - GLOCKENSPIEL_STRING, - MUSIC_BOX_STRING, - VIBRAPHONE_STRING, - MARIMBA_STRING, - XYLOPHONE_STRING, - TUBULAR_BELLS_STRING, - DULCIMER_STRING, - DRAWBAR_ORGAN_STRING, - PERCUSSIVE_ORGAN_STRING, - ROCK_ORGAN_STRING, - CHURCH_ORGAN_STRING, - REED_ORGAN_STRING, - ACCORDION_STRING, - HARMONICA_STRING, - TANGO_ACCORDION_STRING, - NYLON_GUITAR_STRING, - STEEL_GUITAR_STRING, - JAZZ_GUITAR_STRING, - CLEAN_GUITAR_STRING, - MUTED_GUITAR_STRING, - OVERDRIVEN_GUITAR_STRING, - DISTORTION_GUITAR_STRING, - GUITAR_HARMONICS_STRING, - ACOUSTIC_BASS_STRING, - FINGERED_BASS_STRING, - PICKED_BASS_STRING, - FRETLESS_BASS_STRING, - SLAP_BASS_1_STRING, - SLAP_BASS_2_STRING, - SYNTH_BASS_1_STRING, - SYNTH_BASS_2_STRING, - VIOLIN_STRING, - VIOLA_STRING, - CELLO_STRING, - CONTRABASS_STRING, - TREMOLO_STRINGS_STRING, - PIZZICATO_STRINGS_STRING, - ORCHESTRAL_HARP_STRING, - TIMPANI_STRING, - STRING_ENSEMBLE_1_STRING, - STRING_ENSEMBLE_2_STRING, - SYNTHSTRINGS_1_STRING, - SYNTHSTRINGS_2_STRING, - CHOIR_AAHS_STRING, - VOICE_OOHS_STRING, - SYNTH_VOICE_STRING, - ORCHESTRA_HIT_STRING, - TRUMPET_STRING, - TROMBONE_STRING, - TUBA_STRING, - MUTED_TRUMPET_STRING, - FRENCH_HORN_STRING, - BRASS_SECTION_STRING, - SYNTHBRASS_1_STRING, - SYNTHBRASS_2_STRING, - SOPRANO_SAX_STRING, - ALTO_SAX_STRING, - TENOR_SAX_STRING, - BARITONE_SAX_STRING, - OBOE_STRING, - ENGLISH_HORN_STRING, - BASSOON_STRING, - CLARINET_STRING, - PICCOLO_STRING, - FLUTE_STRING, - RECORDER_STRING, - PAN_FLUTE_STRING, - BLOWN_BOTTLE_STRING, - SHAKUHACHI_STRING, - WHISTLE_STRING, - OCARINA_STRING, - SQUARE_WAVE_STRING, - SAW_WAVE_STRING, - CALLIOPE_STRING, - CHIFF_STRING, - CHARANG_STRING, - VOICE_STRING, - FIFTH_WAVE_STRING, - BASS_LEAD_STRING, - NEW_AGE_STRING, - WARM_STRING, - POLYSYNTH_STRING, - CHOIR_STRING, - BOWED_STRING, - METALLIC_STRING, - HALO_STRING, - SWEEP_STRING, - RAIN_STRING, - SOUNDTRACK_STRING, - CRYSTAL_STRING, - ATMOSPHERE_STRING, - BRIGHTNESS_STRING, - GOBLINS_STRING, - ECHOES_STRING, - SCI_FI_STRING, - SITAR_STRING, - BANJO_STRING, - SHAMISEN_STRING, - KOTO_STRING, - KALIMBA_STRING, - BAG_PIPE_STRING, - FIDDLE_STRING, - SHANAI_STRING, - TINKLE_BELL_STRING, - AGOGO_STRING, - STEEL_DRUMS_STRING, - WOODBLOCK_STRING, - TAIKO_DRUM_STRING, - MELODIC_TOM_STRING, - SYNTH_DRUM_STRING, - REVERSE_CYMBAL_STRING, - GUITAR_FRET_NOISE_STRING, - BREATH_NOISE_STRING, - SEASHORE_STRING, - BIRD_TWEET_STRING, - TELEPHONE_RING_STRING, - HELICOPTER_STRING, - APPLAUSE_STRING, - GUNSHOT_STRING, +const char* MIDI_GM_INSTRUMENT_NAMES[] = { + ACC_GRAND_PIANO_STRING, + BRIGHT_ACC_PIANO_STRING, + ELEC_GRAND_PIANO_STRING, + HONKY_TONK_PIANO_STRING, + ELECTRIC_PIANO_1_STRING, + ELECTRIC_PIANO_2_STRING, + HARPSICHORD_STRING, + CLAVI_STRING, + CELESTA_STRING, + GLOCKENSPIEL_STRING, + MUSIC_BOX_STRING, + VIBRAPHONE_STRING, + MARIMBA_STRING, + XYLOPHONE_STRING, + TUBULAR_BELLS_STRING, + DULCIMER_STRING, + DRAWBAR_ORGAN_STRING, + PERCUSSIVE_ORGAN_STRING, + ROCK_ORGAN_STRING, + CHURCH_ORGAN_STRING, + REED_ORGAN_STRING, + ACCORDION_STRING, + HARMONICA_STRING, + TANGO_ACCORDION_STRING, + NYLON_GUITAR_STRING, + STEEL_GUITAR_STRING, + JAZZ_GUITAR_STRING, + CLEAN_GUITAR_STRING, + MUTED_GUITAR_STRING, + OVERDRIVEN_GUITAR_STRING, + DISTORTION_GUITAR_STRING, + GUITAR_HARMONICS_STRING, + ACOUSTIC_BASS_STRING, + FINGERED_BASS_STRING, + PICKED_BASS_STRING, + FRETLESS_BASS_STRING, + SLAP_BASS_1_STRING, + SLAP_BASS_2_STRING, + SYNTH_BASS_1_STRING, + SYNTH_BASS_2_STRING, + VIOLIN_STRING, + VIOLA_STRING, + CELLO_STRING, + CONTRABASS_STRING, + TREMOLO_STRINGS_STRING, + PIZZICATO_STRINGS_STRING, + ORCHESTRAL_HARP_STRING, + TIMPANI_STRING, + STRING_ENSEMBLE_1_STRING, + STRING_ENSEMBLE_2_STRING, + SYNTHSTRINGS_1_STRING, + SYNTHSTRINGS_2_STRING, + CHOIR_AAHS_STRING, + VOICE_OOHS_STRING, + SYNTH_VOICE_STRING, + ORCHESTRA_HIT_STRING, + TRUMPET_STRING, + TROMBONE_STRING, + TUBA_STRING, + MUTED_TRUMPET_STRING, + FRENCH_HORN_STRING, + BRASS_SECTION_STRING, + SYNTHBRASS_1_STRING, + SYNTHBRASS_2_STRING, + SOPRANO_SAX_STRING, + ALTO_SAX_STRING, + TENOR_SAX_STRING, + BARITONE_SAX_STRING, + OBOE_STRING, + ENGLISH_HORN_STRING, + BASSOON_STRING, + CLARINET_STRING, + PICCOLO_STRING, + FLUTE_STRING, + RECORDER_STRING, + PAN_FLUTE_STRING, + BLOWN_BOTTLE_STRING, + SHAKUHACHI_STRING, + WHISTLE_STRING, + OCARINA_STRING, + SQUARE_WAVE_STRING, + SAW_WAVE_STRING, + CALLIOPE_STRING, + CHIFF_STRING, + CHARANG_STRING, + VOICE_STRING, + FIFTH_WAVE_STRING, + BASS_LEAD_STRING, + NEW_AGE_STRING, + WARM_STRING, + POLYSYNTH_STRING, + CHOIR_STRING, + BOWED_STRING, + METALLIC_STRING, + HALO_STRING, + SWEEP_STRING, + RAIN_STRING, + SOUNDTRACK_STRING, + CRYSTAL_STRING, + ATMOSPHERE_STRING, + BRIGHTNESS_STRING, + GOBLINS_STRING, + ECHOES_STRING, + SCI_FI_STRING, + SITAR_STRING, + BANJO_STRING, + SHAMISEN_STRING, + KOTO_STRING, + KALIMBA_STRING, + BAG_PIPE_STRING, + FIDDLE_STRING, + SHANAI_STRING, + TINKLE_BELL_STRING, + AGOGO_STRING, + STEEL_DRUMS_STRING, + WOODBLOCK_STRING, + TAIKO_DRUM_STRING, + MELODIC_TOM_STRING, + SYNTH_DRUM_STRING, + REVERSE_CYMBAL_STRING, + GUITAR_FRET_NOISE_STRING, + BREATH_NOISE_STRING, + SEASHORE_STRING, + BIRD_TWEET_STRING, + TELEPHONE_RING_STRING, + HELICOPTER_STRING, + APPLAUSE_STRING, + GUNSHOT_STRING, }; @@ -291,8 +291,8 @@ const char* MIDI_GM_INSTRUMENT_NAMES[] = { uint8_t MIDIUtils_GetMusicNote(uint8_t note) { - note = note - (NOTE_COUNT * MIDIUtils_GetOctave(note)); - return note; + note = note - (NOTE_COUNT * MIDIUtils_GetOctave(note)); + return note; } @@ -305,7 +305,7 @@ uint8_t MIDIUtils_GetOctave(uint8_t note) const char* MIDIUtils_GetInstrumentName(uint8_t programNumber) { - return MIDI_GM_INSTRUMENT_NAMES[programNumber]; + return MIDI_GM_INSTRUMENT_NAMES[programNumber]; } diff --git a/audio/RtMidi.cpp b/audio/RtMidi.cpp index ec59d0955..144db1e64 100644 --- a/audio/RtMidi.cpp +++ b/audio/RtMidi.cpp @@ -1225,7 +1225,7 @@ static void *alsaMidiHandler( void *ptr ) if ( !( data->ignoreFlags & 0x04 ) ) doDecode = true; break; - case SND_SEQ_EVENT_SYSEX: + case SND_SEQ_EVENT_SYSEX: if ( (data->ignoreFlags & 0x01) ) break; if ( ev->data.ext.len > apiData->bufferSize ) { apiData->bufferSize = ev->data.ext.len; @@ -1572,11 +1572,11 @@ void MidiInAlsa :: openVirtualPort( std::string portName ) snd_seq_port_info_t *pinfo; snd_seq_port_info_alloca( &pinfo ); snd_seq_port_info_set_capability( pinfo, - SND_SEQ_PORT_CAP_WRITE | - SND_SEQ_PORT_CAP_SUBS_WRITE ); + SND_SEQ_PORT_CAP_WRITE | + SND_SEQ_PORT_CAP_SUBS_WRITE ); snd_seq_port_info_set_type( pinfo, - SND_SEQ_PORT_TYPE_MIDI_GENERIC | - SND_SEQ_PORT_TYPE_APPLICATION ); + SND_SEQ_PORT_TYPE_MIDI_GENERIC | + SND_SEQ_PORT_TYPE_APPLICATION ); snd_seq_port_info_set_midi_channels(pinfo, 16); #ifndef AVOID_TIMESTAMPING snd_seq_port_info_set_timestamping(pinfo, 1); @@ -1688,7 +1688,7 @@ void MidiOutAlsa :: initialize( const std::string& clientName ) errorString_ = "MidiOutAlsa::initialize: error creating ALSA sequencer client object."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; - } + } // Set client name. snd_seq_set_client_name( seq, clientName.c_str() ); @@ -1721,8 +1721,8 @@ void MidiOutAlsa :: initialize( const std::string& clientName ) unsigned int MidiOutAlsa :: getPortCount() { - snd_seq_port_info_t *pinfo; - snd_seq_port_info_alloca( &pinfo ); + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca( &pinfo ); AlsaMidiData *data = static_cast (apiData_); return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, -1 ); @@ -1771,8 +1771,8 @@ void MidiOutAlsa :: openPort( unsigned int portNumber, const std::string portNam return; } - snd_seq_port_info_t *pinfo; - snd_seq_port_info_alloca( &pinfo ); + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca( &pinfo ); AlsaMidiData *data = static_cast (apiData_); if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) == 0 ) { std::ostringstream ost; diff --git a/audio/audio.cpp b/audio/audio.cpp index 879cee87a..4e278a012 100644 --- a/audio/audio.cpp +++ b/audio/audio.cpp @@ -451,7 +451,7 @@ void SendMIDIEvent(MIDI_CHAN_EVENT_t* event) message.push_back(event->parameter1); if( ((event->eventType & MIDI_MSG_TYPE_MASK) != MIDI_PROGRAM_CHANGE) && ((event->eventType & MIDI_MSG_TYPE_MASK) != MIDI_CHANNEL_PRESSURE)) { - message.push_back(event->parameter2); + message.push_back(event->parameter2); } audio::SendMIDIEvent( &message ); } diff --git a/audio/audio_stack.h b/audio/audio_stack.h index ae7250e78..9356674d3 100644 --- a/audio/audio_stack.h +++ b/audio/audio_stack.h @@ -26,7 +26,7 @@ #include -#define STACK_OVERFLOW (NULL) +#define STACK_OVERFLOW (NULL) /* Size has to be a size of power of 2 */ typedef struct diff --git a/audio/linkedlist.cpp b/audio/linkedlist.cpp index 36730c379..905f641f0 100644 --- a/audio/linkedlist.cpp +++ b/audio/linkedlist.cpp @@ -28,29 +28,29 @@ void LL_DeleteList(LINKED_LIST_t* linkedList) { - LIST_NODE_t* node; - node = linkedList->first; - - while(node) - { - LL_Remove(linkedList, node); - node = linkedList->first; - } + LIST_NODE_t* node; + node = linkedList->first; + + while(node) + { + LL_Remove(linkedList, node); + node = linkedList->first; + } } void LL_DeleteListAndData(LINKED_LIST_t* linkedList) { - LIST_NODE_t* node; - node = linkedList->first; - - while(node) - { - LL_Free(node->data); - LL_Remove(linkedList, node); - node = linkedList->first; - } + LIST_NODE_t* node; + node = linkedList->first; + + while(node) + { + LL_Free(node->data); + LL_Remove(linkedList, node); + node = linkedList->first; + } } @@ -167,7 +167,7 @@ void LL_Remove(LINKED_LIST_t* linkedList, LIST_NODE_t* node) linkedList->first = (LIST_NODE_t*)node->next; if( node->next ) { - node->prev = NULL; + node->prev = NULL; } } else @@ -183,7 +183,7 @@ void LL_Remove(LINKED_LIST_t* linkedList, LIST_NODE_t* node) linkedList->last = (LIST_NODE_t*)node->prev; if( node->prev ) { - node->next = NULL; + node->next = NULL; } } else @@ -194,7 +194,7 @@ void LL_Remove(LINKED_LIST_t* linkedList, LIST_NODE_t* node) } if( node != NULL) { - LL_Free(node); + LL_Free(node); } node = NULL; } @@ -202,54 +202,54 @@ void LL_Remove(LINKED_LIST_t* linkedList, LIST_NODE_t* node) uint16_t LL_Count(LINKED_LIST_t* linkedList) { - LIST_NODE_t* tmp; - uint16_t count = 0; + LIST_NODE_t* tmp; + uint16_t count = 0; - tmp = linkedList->first; - while(tmp != NULL) - { - count++; - tmp = tmp->next; - } + tmp = linkedList->first; + while(tmp != NULL) + { + count++; + tmp = tmp->next; + } - return count; + return count; } LIST_NODE_t* LL_ReturnNodeFromIndex(LINKED_LIST_t* linkedList, uint16_t item) { - LIST_NODE_t* tmp; - uint16_t count = LL_Count(linkedList); + LIST_NODE_t* tmp; + uint16_t count = LL_Count(linkedList); - if(item >= count) - { - return NULL; - } + if(item >= count) + { + return NULL; + } - tmp = linkedList->first; - while( (tmp != NULL) && (item) ) - { - item--; - tmp = tmp->next; - } + tmp = linkedList->first; + while( (tmp != NULL) && (item) ) + { + item--; + tmp = tmp->next; + } - return tmp; + return tmp; } void* LL_ReturnNodeDataFromIndex(LINKED_LIST_t* linkedList, uint16_t item) { - LIST_NODE_t* tmp; + LIST_NODE_t* tmp; - tmp = LL_ReturnNodeFromIndex(linkedList, item); + tmp = LL_ReturnNodeFromIndex(linkedList, item); - if( tmp != NULL ) - { - return tmp->data; - } - return NULL; + if( tmp != NULL ) + { + return tmp->data; + } + return NULL; } diff --git a/audio/linkedlist.h b/audio/linkedlist.h index 5aa690e4c..b5d0e299f 100644 --- a/audio/linkedlist.h +++ b/audio/linkedlist.h @@ -32,8 +32,8 @@ extern "C" { typedef struct LIST_NODE_t { - struct LIST_NODE_t* next; - struct LIST_NODE_t* prev; + struct LIST_NODE_t* next; + struct LIST_NODE_t* prev; void* data; } LIST_NODE_t; @@ -60,11 +60,11 @@ void* LL_ReturnNodeDataFromIndex(LINKED_LIST_t* linkedList, uint16_t item); //For FreeRTOS -//#define LL_Malloc(size) pvPortMalloc(size) -//#define LL_Free(handle) vPortFree(handle) +//#define LL_Malloc(size) pvPortMalloc(size) +//#define LL_Free(handle) vPortFree(handle) -#define LL_Malloc(size) malloc(size) -#define LL_Free(handle) free(handle) +#define LL_Malloc(size) malloc(size) +#define LL_Free(handle) free(handle) #endif diff --git a/audio/midiparser.h b/audio/midiparser.h index f26381d85..03765dc86 100644 --- a/audio/midiparser.h +++ b/audio/midiparser.h @@ -111,21 +111,21 @@ typedef struct { typedef struct { - uint8_t eventType; + uint8_t eventType; uint8_t parameter1; uint8_t parameter2; } MIDI_CHAN_EVENT_t; typedef struct { - uint8_t eventType; + uint8_t eventType; uint32_t length; uint8_t* data; } MIDI_SYSEX_EVENT_t; typedef struct { - uint8_t eventType; + uint8_t eventType; uint8_t metaType; uint32_t length; uint8_t* data; @@ -136,10 +136,10 @@ typedef struct uint32_t deltaTime; union { - uint8_t eventType; - MIDI_CHAN_EVENT_t chanEvent; - MIDI_SYSEX_EVENT_t sysExEvent; - MIDI_META_EVENT_t metaEvent; + uint8_t eventType; + MIDI_CHAN_EVENT_t chanEvent; + MIDI_SYSEX_EVENT_t sysExEvent; + MIDI_META_EVENT_t metaEvent; } event; } MIDI_EVENT_t; @@ -174,35 +174,35 @@ typedef struct uint8_t keyScale; } MIDI_CURRENT_TRACK_STATE_t; -#define FAST_FWD_ACTIVE (0x1) +#define FAST_FWD_ACTIVE (0x1) #define FAST_FWD_STATUS_MASK (0x07) -#define FAST_FWD_IGNORE_CHANNEL_MASK (0x80) +#define FAST_FWD_IGNORE_CHANNEL_MASK (0x80) -#define FAST_FWD_IGNORE_PARAM1 (0xFF) -#define FAST_FWD_IGNORE_PARAM2 (0xFF) -#define FAST_FWD_NON_ZERO_PARAM2 (FAST_FWD_IGNORE_PARAM2-1) +#define FAST_FWD_IGNORE_PARAM1 (0xFF) +#define FAST_FWD_IGNORE_PARAM2 (0xFF) +#define FAST_FWD_NON_ZERO_PARAM2 (FAST_FWD_IGNORE_PARAM2-1) typedef enum { FAST_FW_FIND_NULL = 0, - FAST_FWD_FIND_COMMAND = 1, - FAST_FWD_FIND_PARAM1, - FAST_FWD_FIND_PARAM2, + FAST_FWD_FIND_COMMAND = 1, + FAST_FWD_FIND_PARAM1, + FAST_FWD_FIND_PARAM2, } MPB_FF_MODE_t; typedef struct { - MPB_FF_MODE_t foundEventStatus :3; //MPB_FF_MODE_t type - MPB_FF_MODE_t searchMode :3; //MPB_FF_MODE_t type - uint8_t foundEventFlag :2; + MPB_FF_MODE_t foundEventStatus :3; //MPB_FF_MODE_t type + MPB_FF_MODE_t searchMode :3; //MPB_FF_MODE_t type + uint8_t foundEventFlag :2; } MPB_FastFwd_t; typedef struct { - //Number of notes per track - uint16_t noteCount; - //Even with multiple program changes, it records the first one - uint8_t programNumber; + //Number of notes per track + uint16_t noteCount; + //Even with multiple program changes, it records the first one + uint8_t programNumber; } MIDI_CHANNEL_STATS_t; #define MIDI_MAX_FILENAME (32) @@ -232,7 +232,7 @@ typedef struct MidiPlaybackState_t playbackState; int8_t transpose; - MPB_FastFwd_t FastFwd_Status; + MPB_FastFwd_t FastFwd_Status; MIDI_CHAN_EVENT_t FastFwd_Event; uint16_t channelStateBitmap; diff --git a/audio/midiplayback.cpp b/audio/midiplayback.cpp index 1f7583c75..0aee40a03 100644 --- a/audio/midiplayback.cpp +++ b/audio/midiplayback.cpp @@ -618,10 +618,10 @@ MIDI_EVENT_t* MPB_ConfirmEventTx(void) return event; } -#define MIN_PPQ (48) -#define MIN_BMP (20) +#define MIN_PPQ (48) +#define MIN_BMP (20) #define MIN_BMP_PPQ_PRODUCT (MIN_BMP*MIN_PPQ) -#define MIN_BMP_PPQ_PRESCALER (64) +#define MIN_BMP_PPQ_PRESCALER (64) uint16_t MPB_SetTickRate(uint16_t bpm, uint16_t PPQ) { //*to send a Timing signal we need to send 24 0xF8's per quarter note @@ -840,8 +840,8 @@ void MPB_OutputMIDIChanEvent(MIDI_CHAN_EVENT_t* chanEvent) } } -#define MPB_IGNORE_BYTE (0xFF) -#define MPB_SAVE_BUFFER_SIZE (16) +#define MPB_IGNORE_BYTE (0xFF) +#define MPB_SAVE_BUFFER_SIZE (16) //#define MPB_STATUS_BUFFER_ARRAY diff --git a/fantasyname/namegen.h b/fantasyname/namegen.h index c90fa30e0..bd415744a 100644 --- a/fantasyname/namegen.h +++ b/fantasyname/namegen.h @@ -140,126 +140,126 @@ namespace NameGen { class Generator { - typedef enum wrappers { - capitalizer, - reverser - } wrappers_t; + typedef enum wrappers { + capitalizer, + reverser + } wrappers_t; - typedef enum group_types { - symbol, - literal - } group_types_t; + typedef enum group_types { + symbol, + literal + } group_types_t; - class Group { - std::stack wrappers; - std::vector> set; + class Group { + std::stack wrappers; + std::vector> set; - public: - group_types_t type; + public: + group_types_t type; - Group(group_types_t type_); + Group(group_types_t type_); std::unique_ptr produce(); - void split(); - void wrap(wrappers_t type); - void add(std::unique_ptr&& g); + void split(); + void wrap(wrappers_t type); + void add(std::unique_ptr&& g); - virtual void add(char c); - }; + virtual void add(char c); + }; - class GroupSymbol : public Group { - public: - GroupSymbol(); - void add(char c); - }; + class GroupSymbol : public Group { + public: + GroupSymbol(); + void add(char c); + }; - class GroupLiteral : public Group { - public: - GroupLiteral(); - }; + class GroupLiteral : public Group { + public: + GroupLiteral(); + }; protected: - std::vector> generators; + std::vector> generators; public: - static const std::unordered_map>& SymbolMap(); + static const std::unordered_map>& SymbolMap(); - Generator(); - Generator(const std::string& pattern, bool collapse_triples=true); - Generator(std::vector>&& generators_); + Generator(); + Generator(const std::string& pattern, bool collapse_triples=true); + Generator(std::vector>&& generators_); - virtual ~Generator() = default; + virtual ~Generator() = default; - virtual size_t combinations(); - virtual size_t min(); - virtual size_t max(); - virtual std::string toString(); + virtual size_t combinations(); + virtual size_t min(); + virtual size_t max(); + virtual std::string toString(); - void add(std::unique_ptr&& g); + void add(std::unique_ptr&& g); }; class Random : public Generator { public: - Random(); - Random(std::vector>&& generators_); + Random(); + Random(std::vector>&& generators_); - size_t combinations(); - size_t min(); - size_t max(); - std::string toString(); + size_t combinations(); + size_t min(); + size_t max(); + std::string toString(); }; class Sequence : public Generator { public: - Sequence(); - Sequence(std::vector>&& generators_); + Sequence(); + Sequence(std::vector>&& generators_); }; class Literal : public Generator { - std::string value; + std::string value; public: - Literal(const std::string& value_); + Literal(const std::string& value_); - size_t combinations(); - size_t min(); - size_t max(); - std::string toString(); + size_t combinations(); + size_t min(); + size_t max(); + std::string toString(); }; class Reverser : public Generator { public: - Reverser(std::unique_ptr&& g); + Reverser(std::unique_ptr&& g); - std::string toString(); + std::string toString(); }; class Capitalizer : public Generator { public: - Capitalizer(std::unique_ptr&& g); + Capitalizer(std::unique_ptr&& g); - std::string toString(); + std::string toString(); }; class Collapser : public Generator { public: - Collapser(std::unique_ptr&& g); + Collapser(std::unique_ptr&& g); - std::string toString(); + std::string toString(); }; } diff --git a/xbrzscale/xbrz/xbrz.cpp b/xbrzscale/xbrz/xbrz.cpp index b2f66149b..91c01fd42 100755 --- a/xbrzscale/xbrz/xbrz.cpp +++ b/xbrzscale/xbrz/xbrz.cpp @@ -235,7 +235,7 @@ struct DistYCbCrBuffer //30% perf boost compared to distYCbCr()! //if (pix1 == pix2) -> 8% perf degradation! // return 0; //if (pix1 > pix2) - // std::swap(pix1, pix2); -> 30% perf degradation!!! + // std::swap(pix1, pix2); -> 30% perf degradation!!! const int r_diff = static_cast(getRed (pix1)) - getRed (pix2); const int g_diff = static_cast(getGreen(pix1)) - getGreen(pix2); @@ -356,7 +356,7 @@ DEF_GETTER(g, c) DEF_GETTER(h, b) DEF_GETTER(i, a) #define DEF_GETTER(x, y) template <> inline uint32_t get_##x(const Kernel_3x3& ker) { return ker.y; } DEF_GETTER(a, c) DEF_GETTER(b, f) DEF_GETTER(c, i) DEF_GETTER(d, b) DEF_GETTER(e, e) DEF_GETTER(f, h) -DEF_GETTER(g, a) DEF_GETTER(h, d) DEF_GETTER(i, g) +DEF_GETTER(g, a) DEF_GETTER(h, d) DEF_GETTER(i, g) #undef DEF_GETTER @@ -1040,9 +1040,9 @@ struct ColorDistanceARGB /* Requirements for a color distance handling alpha channel: with a1, a2 in [0, 1] - 1. if a1 = a2, distance should be: a1 * distYCbCr() - 2. if a1 = 0, distance should be: a2 * distYCbCr(black, white) = a2 * 255 - 3. if a1 = 1, ??? maybe: 255 * (1 - a2) + a2 * distYCbCr() + 1. if a1 = a2, distance should be: a1 * distYCbCr() + 2. if a1 = 0, distance should be: a2 * distYCbCr(black, white) = a2 * 255 + 3. if a1 = 1, ??? maybe: 255 * (1 - a2) + a2 * distYCbCr() */ //return std::min(a1, a2) * DistYCbCrBuffer::dist(pix1, pix2) + 255 * abs(a1 - a2);