mirror of
https://github.com/UOX3DevTeam/UOX3
synced 2026-08-13 12:27:04 -04:00
Fixed an issue where the color of NPC corpse names would not always match up with the NPC's flag color Fixed a bug where the baseRange and maxRange properties of ranged weapons would not get saved to world files. These values are now saved in the format of RANGE=baseRange,maxRange Changed the way items are added to containers, to ensure that the order in which the items are displayed to the player always reflects the order in which they were added; now the most recent item added/moved in a container will always be rendered on top of older items. Made adjustments to how items are randomly added to containers; should use more of a given container gump's available area now Exposed Item property to JS engine, which contains serial of the creator of an item: .creator // contains serial of the creator of an item. If set, maker's marks will show up in the item's tooltip Fixed an issue where JS engine would lose track of script context if one script used CreateDFNItem() or SpawnNPC() functions and the new objects had events that triggered upon creation. UOX3 now restores the original script context at the tail end of these functions. Added new JS Event to allow inserting custom tooltip text for objects, which will be displayed in tooltips right after the object name. Any text returned from the event will be displayed, and the text can make use of the same HTML tags as gumps to make changes to color, font, etc. onTooltip( myObj ) // Triggers for objects right before the object's tooltip properties are sent to client Updated 'tweak and 'set commands to refresh item being modified if movable state of item changes, so the change is reflected in nearby clients Fixed an issue with NPC vendors and items bought from players not being properly removed from the vendor's "bought container" when players buy them again Ported Item Identification skill from code to JS (js/skill/itemid.js) and removed hard-coded version Implemented magic item generator in JS (js/item/magic_item.js). When this script is attached to an NPC, it has a chance to generate magic weapons, armors, wands/staffs and rings as loot on the NPC's corpse when slain. The chance of getting quality magical items increases with the fame level of the NPC in question. The types of magical items generated follows the pattern of such items as implemented in UO prior to the AoS expansion. Examples: exceedingly accurate war hammer of vanquishing substantial, accurate axe of power and Daemon's Breath silver long sword massive platemail arms of Protection metal shield of defense durable ringmail tunic of guarding All magic items have a chance to have spell effects attached (with rings/wands being guaranteed to have these), with a limited amount of charges available. For weapons, these effects activate on successful hits in combat, while for armors they activate on equip, and then periodically as long as the item is worn. Rings and wands/staffs have to be manually activated and targeted - with the exception of rings of invisibility, which activate the moment you equip them! Related scripts: js/item/magic_armor_equipeffects.js js/item/magic_weapon_accbonus.js js/item/magic_weapon_equip.js js/item/magic_weapon_spell_attack.js The magic item loot generation script has been attached to a number of different NPCs, all of which now have a chance to drop magic items as loot when defeated. A bonus magical item - the glacial staff - has also been implemented (js/item/magic_glacial_staff.js), and has a chance to drop as loot from Giant Ice Serpents! Fixed an issue with CustomTarget where the target message would not be displayed if the cursorType parameter was provided Fixed bug with 'rename command, which referenced a non-existing variable that caused a script crash Added additional object properties to 'get and 'set commands: .shouldSave // Determines if an item should be saved in worldfiles or not .baseRange // Determines the base range of a ranged weapon, less than which it becomes less effective .maxRange // Determines the max range of a ranged weapon, beyond which it cannot reach its target The feature that allows stats like Strength, Dexterity and Intelligence to provide bonuses to skill checks has been turned into a UOX.INI setting, which is disabled by default. The bonuses have also been nerfed; they will no longer contribute more to the success of a skill check than the actual skill being checked! STATSAFFECTSKILLCHECKS=0/1 // If enabled, stats can provide bonuses to skill checks based on the weighting for those stats as setup in dfndata/skills/skills.dfn Updated damage tracking code to include the type of damage that was dealt (PHYSICAl vs HEAT vs COLD, etc) Updated Character JS Method Damage() to include a new parameter that can specify the type of damage that was dealt to the character. The new parameter must always be included if other optional parameters are used. Note that updates might be required for scripts making use of this method.Updated syntax: .Damage( amount ) .Damage( amount, damageType ) .Damage( amount, damageType, attacker ) .Damage( amount, damageType, attacker, doRepsys ) Supported damageTypes: PHYSICAL = 1 LIGHT = 2 RAIN = 3 COLD = 4 HEAT = 5 LIGHTNING = 6 // magic damage POISON = 7 SNOW = 8 Updated JS Event OnDamage to include an additional parameter that describes the type of damage that was dealt. New syntax: onDamage( damaged, attacker, damageValue, damageType ) Updated JS Event OnSpellTarget to allow rejecting a spell being cast on a target by returning a value of 2 from the script Split the character priv flag for magic reflection into a temporary one (one-time magic reflection spell) and a permanent one (innate permanent reflection ability). The temporary effect is put in place by Magic Reflection spell, and is removed after a successful spell reflect. The permanent one is an innate ability of a character and is not removed even after a spell is reflected. Added new Character JS property to get/set state of a character's permanent magic reflect ability: .permanentMagicReflect // 0 = disable, 1 = enable Added special abilities/effects for the following NPCs: Acid Elementals now have a chance to damage the melee weapons of their attackers Dull Copper Elementals now explode on death Shadow Iron Elemental is immune to targeted spell damage Copper Elementals have permanent magic reflect, and reflect some physical damage back at their attacker Bronze Elementals deal passive area damage to nearby players every 5 to 10 seconds Valorite Elementals have permanent magic reflect, reflect some physical damage back at their attacker Snow/Ice Elementals deal passive area damage to nearby players every 5 to 10 seconds Lava Serpents deal passive area damage to nearby players every 5 to 10 seconds Phoenixes deal passive area damage to nearby players every 5 to 10 seconds Pixies have a chance to cast a random spell upon receiving a death blow: Bless (target) Curse (target) Explosion (target) Greater Poison (target) Greater Heal (self, prevents death) Ethereal Warriors will now resurrect dead players with positive karma Ethereal Warriors now have a chance to drain target's health, stamina or mana on hit Fire Breath special ability added to the following NPCs, with the random ability damage scaling with the NPCs current health: Hell Cat, 5 to 8 dmg at max health Fire Steed, 6 to 9 dmg at max health Hell Hounds, 8 to 11 dmg at max health Lava Lizard, 8 to 11 dmg at max health Predator Hell Cat, 9 to 14 dmg at max health Sea Serpent, 11 to 17 dmg at max health Swamp Dragon, 15 to 22 dmg at max health Armored Swamp Dragon, 15 to 22 dmg at max health Fire Gargoyle, 20 to 30 dmg at max health Deep Sea Serpent, 21 to 32 dmg at max health Serpentine Dragon, 22 to 32 dmg at max health Drake (Gray), 22 to 32 dmg at max health Drake (Red), 22 to 32 dmg at max health Nightmare, 26 to 39 dmg at max health Dark Steed, 26 to 39 dmg at max health Silver Steed, 26 to 39 dmg at max health Kraken, 39 to 59 dmg at max health Dragon (Red), 41 to 62 dmg at max health Dragon (Gray), 41 to 62 dmg at max health Shadow Wyrm, 50 to 75 dmg at max health Reptalon, 51 to 77 dmg at max health Skeletal Dragon, 52 to 77 dmg at max health Ancient Wyrm, 60 to 90 dmg at max health Update stats, skills and loot for all currently implemented NPCs Removed an ancient piece of code that prevented corpses from being generated for various elementals and blade spirits on death. This was originally put in place because these creatures had no corpses, and would replace it with a backpack instead, but these creatures now all have corpses in all client versions supported by UOX3 Updated potions script (js/item/potion.js) with updated formula for amount of hitpoints healed by healing potions, and added restrictions for using them when at full health and/or if poisoned (Dragon Slayer) Fixed a bug where creatures (and humans, if FORCENEWANIMATIONPACKET was disabled in uox.ini) would play animations with wrong frame count, causing the animations to either freeze for a few frames at the end, or get cut off a couple of frames early Implemented bonus hit chance for Archery skill, based on mention of such a bonus in Publish 5 patch notes and related UO House of Commons chat. This has been exposed as a UOX.INI setting where this bonus can be tweaked: ARCHERYHITBONUS=10 // Bonus hit chance for Archery skill added to regular hit chance in combat. Defaults to 10% Implemented optional extra delay between moving and being able to shoot with ranged weapons in combat (in addition to whatever delay is there due to speed of the ranged weapon). This is exposed as a UOX.INI setting: ARCHERYSHOOTDELAY=0.5 // Minimum delay in seconds from a player stops moving until they can start to fire their ranged weapon. Defaults to 0.5s Fixed a bug where players who never entered combat mode could fire ranged weapons while moving Updated Add-menu with some improvements for usability: Menu now directly on the Objects (previously "Shard") tab for quicker access, with a small welcome text and quick-link button for UOX3 docs Settings tab contains some (persistent, will be saved with character) options for how the Add-menu behaves, along with a few quick-access buttons to some useful commands. Available options: Option to add chosen item at specific location instead of in GM's backpack Option to add chosen item repeatedly until cancelled Option to automatically reopen Add-menu on last menu that was open when a selection is made Option to force decayable state of all added items to either off (doesn't decay), on (decays) or nothing (use item default) Option to force movable state of all added items to either off (not movable), on (movable) or nothing (use item default) Added a Home button at the bottom of the menu, that takes the user back to the front page of the Add-menu regardless of which menu page they're on Updated Character/Socket JS Method SysMessage() with an optional parameter to specify the color used to display the system message. Updated syntax: .SysMessage( "Text" ) // Display "Text" to user as a system message with default system message color from ini .SysMessage( "Text %s %s", txtArg1, txtArg2 ) // Display "Text" to player with string arguments injected into text .SysMessage( txtColor, "Text" ) // Display "Text" to user with a specified color .SysMessage( txtColor, "Text %s %s", txtArg1, txtArg2 ) // Combination of text color, text and string arguments Fixed a bug where hair/beard items could show up inside corpses Fixed a bug where items could sometimes vanish (visually) from containers when bouncing back because player was not able to pick them up Fixed a bug where players would be unable to pick up a freely movable item from a locked down container Updated handling of item bouncing on pickup to use UOX3 dictionary messages instead of hard-coded client messages Added support for new Dictionary language: dictionary.POL - Polish (SERVERLANGUAGE=8 in uox.ini, or client language 83 if SERVERLANGUAGE is set to 0) Added taming restrictions for Unicorn, Ki-Rin and Cu Sidhe (js/skill/taming.js) Added modification of some creatures stats after they've been tamed (js/skill/taming.js) Added restrictions for who can ride certain creatures (Unicorn, Ki-Rin, Cu Sidhe) Updated words of power for Meteor Swarm spell to follow the same logic as other spells using Kal (Summon): Kal Des Flam Ylem Added new Item/Character JS Methods to get/set temporary custom tags which don't persist across worldsaves (or across reconnects, for players): .GetTempTag( "tagName" ) .SetTempTag( "tagName", tagValue ) When applying HPMAX, STAMINAMAX AND MANAMAX DFN tags to new NPCs, their current HP, STAMINA and MANA properties will now be automatically updated to match Fixed a server crash caused by empty speech messages sent from certain clients Fixed a bug that prevented teleport locations in Felucca/Trammel from working properly Updated calculations in code for duration and damage of existing poison strengths (1 - Lesser, 2 - Normal, 3 - Greater, 4 - Deadly) to match with ~Publish 15 (LBR/pre-AoS), and added another poison strength for monster usage (5 - Lethal) The strength of the Poison and Poison Field spells is now based on the average of the caster's Magery and Poisoning skills, the distance from the target (Poison spell only) and the target's Resisting Spells skill. If caster is further away than 2 tiles from target, the poison strength always equals Lesser Poison, otherwise the following rules apply: If caster's combined skill is higher than 100.0, poison strength equals Greater Poison, with 5% chance of Deadly Poison (if Poison spell) or Deadly Poison (if Poison Field spell) If caster's combined skill is higher than 70.2, poison strength equals Greater Poison If caster's combined skill is higher than 30.2, poison strength equals Normal Poison If caster's combined skill is 30.2 or lower, poison strength equals Lesser Poison If target resists spell, poison strength is reduced by 1 level, unless it's already at the lowest level Fixed an issue that would would let players move faster than they should have been allowed to Fixed misc issues with Party System: Mana and Stamina should now update for Party Members when added to the party, when going in/out of range and when their stats update while in range Players who get disconnected or relog should now find themselves back in the same party they were in previously Added system messages to inform party members about updates to their party, and to let invited players know they've successfully joined a party (or rejoined, if relogging) World saves now operate on the principle of only saving changes done since the last world save. Combined with the fact that NPCs in UOX3 are only active if there are players in the same/neighbouring map regions, shard owners may expect to see a decrease in world save times ranging from ~11% to ~99%, depending on how active and how spread out their player base is. If no changes have taken place since last save, saves are virtually instantaneous!
1076 lines
38 KiB
C++
1076 lines
38 KiB
C++
#include "uox3.h"
|
|
#include "cSpawnRegion.h"
|
|
#include "cServerDefinitions.h"
|
|
#include "ssection.h"
|
|
#include "mapstuff.h"
|
|
#include "classes.h"
|
|
#include "Dictionary.h"
|
|
#include "StringUtility.hpp"
|
|
#include <sstream>
|
|
#include <iostream>
|
|
#include <regex>
|
|
|
|
const TIMERVAL DEFSPAWN_NEXTTIME = 0;
|
|
const UI16 DEFSPAWN_CALL = 1;
|
|
const SI16 DEFSPAWN_X1 = 0;
|
|
const SI16 DEFSPAWN_X2 = 0;
|
|
const SI16 DEFSPAWN_Y1 = 0;
|
|
const SI16 DEFSPAWN_Y2 = 0;
|
|
const size_t DEFSPAWN_MAXCSPAWN = 0;
|
|
const size_t DEFSPAWN_MAXISPAWN = 0;
|
|
const UI08 DEFSPAWN_MAXTIME = 0;
|
|
const UI08 DEFSPAWN_MINTIME = 0;
|
|
const SI32 DEFSPAWN_CURCSPAWN = 0;
|
|
const SI32 DEFSPAWN_CURISPAWN = 0;
|
|
const UI08 DEFSPAWN_WORLDNUM = 0;
|
|
const SI08 DEFSPAWN_PREFZ = 18;
|
|
const bool DEFSPAWN_ONLYOUTSIDE = false;
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Class - CSpawnRegion()
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Constructor for CSpawnRegion class
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
CSpawnRegion::CSpawnRegion( UI16 spawnregion ) : regionnum( spawnregion ), maxcspawn( DEFSPAWN_MAXCSPAWN ), maxispawn( DEFSPAWN_MAXISPAWN ),
|
|
curcspawn( DEFSPAWN_CURCSPAWN ), curispawn( DEFSPAWN_CURISPAWN ), mintime( DEFSPAWN_MINTIME ), maxtime( DEFSPAWN_MAXTIME ),
|
|
nexttime( DEFSPAWN_NEXTTIME ), x1( DEFSPAWN_X1 ), x2( DEFSPAWN_X2 ), y1( DEFSPAWN_Y1 ), y2( DEFSPAWN_Y2 ),
|
|
call( DEFSPAWN_CALL ), worldNumber( DEFSPAWN_WORLDNUM ), prefZ( DEFSPAWN_PREFZ ), onlyOutside( DEFSPAWN_ONLYOUTSIDE )
|
|
{
|
|
sItems.resize( 0 );
|
|
sNpcs.resize( 0 );
|
|
name = Dictionary->GetEntry( 1117 );
|
|
// note: doesn't go here, but i'll see it here. when an item is spawned, as soon as it's moved it needs to lose it's
|
|
// spawn setting. If not, then when people pick up spawned items, they will disappear (on region spawns)
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - ~CSpawnRegion()
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Deconstructor for CSpawnRegion class
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
CSpawnRegion::~CSpawnRegion()
|
|
{
|
|
sItems.resize( 0 );
|
|
sNpcs.resize( 0 );
|
|
// Wipe out all items and npcs
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - const std::string GetName( void ) const
|
|
//| void SetName( const std::string& newName )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Gets/Sets spawn region's name
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
const std::string CSpawnRegion::GetName( void ) const
|
|
{
|
|
return name;
|
|
}
|
|
void CSpawnRegion::SetName( const std::string& newName )
|
|
{
|
|
name = newName;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - SI32 MaxSpawn( void )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Maximum amount of objects a region can spawn
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
size_t CSpawnRegion::GetMaxSpawn( void ) const
|
|
{
|
|
return ( maxcspawn + maxispawn );
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - size_t GetMaxCharSpawn( void ) const
|
|
//| void SetMaxCharSpawn( size_t newVal )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Gets/Sets maximum amount of characters to spawn
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
size_t CSpawnRegion::GetMaxCharSpawn( void ) const
|
|
{
|
|
return maxcspawn;
|
|
}
|
|
void CSpawnRegion::SetMaxCharSpawn( size_t newVal )
|
|
{
|
|
maxcspawn = newVal;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - size_t GetMaxItemSpawn( void ) const
|
|
//| void SetMaxItemSpawn( size_t newVal )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Gets/Sets maximum amount of items to spawn
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
size_t CSpawnRegion::GetMaxItemSpawn( void ) const
|
|
{
|
|
return maxispawn;
|
|
}
|
|
void CSpawnRegion::SetMaxItemSpawn( size_t newVal )
|
|
{
|
|
maxispawn = newVal;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - SI32 Current( void )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Current amount of objects spawned
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
SI32 CSpawnRegion::GetCurrent( void ) const
|
|
{
|
|
return ( curcspawn + curispawn );
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - SI32 GetCurrentCharAmt( void ) const
|
|
//| void IncCurrentCharAmt( SI16 incAmt )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Gets/Sets current amount of characters spawned
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
SI32 CSpawnRegion::GetCurrentCharAmt( void ) const
|
|
{
|
|
return curcspawn;
|
|
}
|
|
void CSpawnRegion::IncCurrentCharAmt( SI16 incAmt )
|
|
{
|
|
curcspawn += incAmt;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - SI32 GetCurrentItemAmt( void ) const
|
|
//| void IncCurrentItemAmt( SI16 incAmt )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Gets/Sets current amount of items spawned
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
SI32 CSpawnRegion::GetCurrentItemAmt( void ) const
|
|
{
|
|
return curispawn;
|
|
}
|
|
void CSpawnRegion::IncCurrentItemAmt( SI16 incAmt )
|
|
{
|
|
curispawn += incAmt;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - UI16 GetRegionNum( void ) const
|
|
//| void SetRegionNum( UI16 newVal )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Gets/Sets spawn region number
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
UI16 CSpawnRegion::GetRegionNum( void ) const
|
|
{
|
|
return regionnum;
|
|
}
|
|
void CSpawnRegion::SetRegionNum( UI16 newVal )
|
|
{
|
|
regionnum = newVal;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - UI08 GetMinTime( void ) const
|
|
//| void SetMinTime( UI08 newVal )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Gets/Sets minimum amount of time to pass before a spawnregion
|
|
//| spawns a new object
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
UI08 CSpawnRegion::GetMinTime( void ) const
|
|
{
|
|
return mintime;
|
|
}
|
|
void CSpawnRegion::SetMinTime( UI08 newVal )
|
|
{
|
|
mintime = newVal;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - UI08 GetMaxTime( void ) const
|
|
//| void SetMaxTime( UI08 newVal )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Gets/Sets maximum amount of time to pass before a spawnregion
|
|
//| spawns a new object
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
UI08 CSpawnRegion::GetMaxTime( void ) const
|
|
{
|
|
return maxtime;
|
|
}
|
|
void CSpawnRegion::SetMaxTime( UI08 newVal )
|
|
{
|
|
maxtime = newVal;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - TIMERVAL GetNextTime( void ) const
|
|
//| void SetNextTime( TIMERVAL newVal )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Gets/Sets time for when a spawnregion will next spawn a new object
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
TIMERVAL CSpawnRegion::GetNextTime( void ) const
|
|
{
|
|
return nexttime;
|
|
}
|
|
void CSpawnRegion::SetNextTime( TIMERVAL newVal )
|
|
{
|
|
nexttime = newVal;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void SetPrefZ( SI08 newVal )
|
|
//| SI08 GetPrefZ( void ) const
|
|
//| Date - 04/22/2002
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Gets/Sets Z Level of the Spawn Region
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void CSpawnRegion::SetPrefZ( SI08 newVal )
|
|
{
|
|
prefZ = newVal;
|
|
}
|
|
SI08 CSpawnRegion::GetPrefZ( void ) const
|
|
{
|
|
return prefZ;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - SI16 GetY1( void ) const
|
|
//| void SetY1( SI16 newVal )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Gets/Sets the y pos of the top corner of the spawnregion
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
SI16 CSpawnRegion::GetY1( void ) const
|
|
{
|
|
return y1;
|
|
}
|
|
void CSpawnRegion::SetY1( SI16 newVal )
|
|
{
|
|
y1 = newVal;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - SI16 GetX1( void ) const
|
|
//| void SetX1( SI16 newVal )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Gets/Sets the x pos of the top corner of the spawnregion
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
SI16 CSpawnRegion::GetX1( void ) const
|
|
{
|
|
return x1;
|
|
}
|
|
void CSpawnRegion::SetX1( SI16 newVal )
|
|
{
|
|
x1 = newVal;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - SI16 GetY2( void ) const
|
|
//| void SetY2( SI16 newVal )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Gets/Sets the y pos of the bottom corner of the spawn region
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
SI16 CSpawnRegion::GetY2( void ) const
|
|
{
|
|
return y2;
|
|
}
|
|
void CSpawnRegion::SetY2( SI16 newVal )
|
|
{
|
|
y2 = newVal;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - SI16 GetX2( void ) const
|
|
//| void SetX2( SI16 newVal )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Gets/Sets the x pos of the bottom corner of the spawn region
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
SI16 CSpawnRegion::GetX2( void ) const
|
|
{
|
|
return x2;
|
|
}
|
|
void CSpawnRegion::SetX2( SI16 newVal )
|
|
{
|
|
x2 = newVal;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - UI08 WorldNumber( void ) const
|
|
//| void WorldNumber( UI08 newVal )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Gets/Sets the world number of the spawnregion
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
UI08 CSpawnRegion::WorldNumber( void ) const
|
|
{
|
|
return worldNumber;
|
|
}
|
|
void CSpawnRegion::WorldNumber( UI08 newVal )
|
|
{
|
|
worldNumber = newVal;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - UI16 GetInstanceID( void ) const
|
|
//| void SetInstanceID( UI16 newVal )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Gets/Sets the instanceID of the spawnregion
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
UI16 CSpawnRegion::GetInstanceID( void ) const
|
|
{
|
|
return instanceID;
|
|
}
|
|
void CSpawnRegion::SetInstanceID( UI16 newVal )
|
|
{
|
|
instanceID = newVal;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - bool GetOnlyOutside( void ) const
|
|
//| void SetOnlyOutside( bool newVal )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Gets/Sets whether NPCs should only spawn outside buildings
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
bool CSpawnRegion::GetOnlyOutside( void ) const
|
|
{
|
|
return onlyOutside;
|
|
}
|
|
void CSpawnRegion::SetOnlyOutside( bool newVal )
|
|
{
|
|
onlyOutside = newVal;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - UI16 GetCall( void ) const
|
|
//| void SetCall( UI16 newVal )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Gets/Sets number of objects that should be spawned in each call for a spawnregion
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
UI16 CSpawnRegion::GetCall( void ) const
|
|
{
|
|
return call;
|
|
}
|
|
void CSpawnRegion::SetCall( UI16 newVal )
|
|
{
|
|
call = newVal;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - STRINGLIST GetNPC( void ) const
|
|
//| void SetNPC( STRINGLIST newVal )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Gets/Sets stringlist of individual NPCs to spawn in a spawnregion
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
STRINGLIST CSpawnRegion::GetNPC( void ) const
|
|
{
|
|
return sNpcs;
|
|
}
|
|
void CSpawnRegion::SetNPC( const std::string &newVal )
|
|
{
|
|
// Clear old entries to make room for new ones
|
|
sNpcs.clear();
|
|
sNpcs.push_back( newVal );
|
|
}
|
|
void CSpawnRegion::SetNPCList( std::string newVal )
|
|
{
|
|
// Clear old entries to make room for new ones
|
|
sNpcs.clear();
|
|
|
|
// Check if a comma delimited string was provided
|
|
if( newVal.find( "," ) != std::string::npos )
|
|
{
|
|
// Strip whitespace from string
|
|
std::regex r( "\\s+" );
|
|
newVal = std::regex_replace( newVal, r, "" );
|
|
|
|
// Add section of the string to the sNpcs list with the help of a stringstream
|
|
std::stringstream s_stream(newVal);
|
|
while( s_stream.good() ) {
|
|
std::string substr;
|
|
getline( s_stream, substr, ',' );
|
|
sNpcs.push_back( substr );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Assume an actual NPClist was provided
|
|
LoadNPCList( newVal );
|
|
}
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - STRINGLIST GetItem( void ) const
|
|
//| void SetItem( STRINGLIST newVal )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Gets/Sets stringlist of individual Items to spawn in a spawnregion
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
STRINGLIST CSpawnRegion::GetItem( void ) const
|
|
{
|
|
return sItems;
|
|
}
|
|
void CSpawnRegion::SetItem( const std::string &newVal )
|
|
{
|
|
// Clear old entries to make room for new ones
|
|
sItems.clear();
|
|
sItems.push_back( newVal );
|
|
}
|
|
void CSpawnRegion::SetItemList( std::string newVal )
|
|
{
|
|
// Clear old entries to make room for new ones
|
|
sItems.clear();
|
|
|
|
// Check if a comma delimited string was provided
|
|
if( newVal.find( "," ) != std::string::npos )
|
|
{
|
|
// Strip whitespace from string
|
|
std::regex r( "\\s+" );
|
|
newVal = std::regex_replace( newVal, r, "" );
|
|
|
|
// Add section of the string to the sItems list with the help of a stringstream
|
|
std::stringstream s_stream(newVal);
|
|
while( s_stream.good() ) {
|
|
std::string substr;
|
|
getline( s_stream, substr, ',' );
|
|
sItems.push_back( substr );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Assume an actual ItemList was provided
|
|
LoadItemList( newVal );
|
|
}
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void LoadNPCList( const std::string &npcList )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Loads NPCLIST from DFNs with list of NPCs to spawn for a spawnregion
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void CSpawnRegion::LoadNPCList( const std::string &npcList )
|
|
{
|
|
std::string sect = "NPCLIST " + npcList;
|
|
ScriptSection *CharList = FileLookup->FindEntry( sect, npc_def );
|
|
if( CharList != nullptr )
|
|
{
|
|
for( std::string npc = CharList->First() ; !CharList->AtEnd(); npc = CharList->Next() )
|
|
{
|
|
if( strutil::upper( npc ) == "NPCLIST" )
|
|
{
|
|
LoadNPCList( CharList->GrabData() );
|
|
}
|
|
else
|
|
{
|
|
sNpcs.push_back( npc );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void LoadItemList( const std::string &itemList )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Loads ITEMLIST from DFNs with list of Items to spawn for a spawnregion
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void CSpawnRegion::LoadItemList( const std::string &itemList )
|
|
{
|
|
std::string sect = "ITEMLIST " + itemList;
|
|
ScriptSection *ItemList = FileLookup->FindEntry( sect, items_def );
|
|
if( ItemList != nullptr )
|
|
{
|
|
for( std::string itm = ItemList->First() ; !ItemList->AtEnd(); itm = ItemList->Next() )
|
|
{
|
|
if( strutil::upper( itm ) == "ITEMLIST" )
|
|
{
|
|
LoadItemList( ItemList->GrabData() );
|
|
}
|
|
else
|
|
{
|
|
sItems.push_back( itm );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void Load( ScriptSection *toScan )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Loads the spawnregion from spawn.dfn script entry
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void CSpawnRegion::Load( ScriptSection *toScan )
|
|
{
|
|
std::string sect;
|
|
std::string data;
|
|
std::string UTag;
|
|
|
|
for( std::string tag = toScan->First(); !toScan->AtEnd(); tag = toScan->Next() )
|
|
{
|
|
if( !tag.empty() )
|
|
{
|
|
UTag = strutil::upper( tag );
|
|
data = toScan->GrabData();
|
|
|
|
// Default to instanceID 0, in case nothing else is specified in DFN
|
|
SetInstanceID( 0 );
|
|
|
|
if( UTag == "NPCLIST" )
|
|
{
|
|
LoadNPCList( data );
|
|
}
|
|
else if( UTag == "NPC" )
|
|
{
|
|
sNpcs.push_back( data );
|
|
}
|
|
else if( UTag == "ITEMLIST" )
|
|
{
|
|
LoadItemList( data );
|
|
}
|
|
else if( UTag == "ITEM" )
|
|
{
|
|
sItems.push_back( data );
|
|
}
|
|
else if( UTag == "MAXITEMS" )
|
|
{
|
|
maxispawn = static_cast<UI32>(std::stoul(data, nullptr, 0));
|
|
}
|
|
else if( UTag == "MAXNPCS" )
|
|
{
|
|
maxcspawn = static_cast<UI32>(std::stoul(data, nullptr, 0));
|
|
}
|
|
else if( UTag == "X1" )
|
|
{
|
|
x1 = static_cast<SI16>(std::stoi(data, nullptr, 0));
|
|
}
|
|
else if( UTag == "X2" )
|
|
{
|
|
x2 = static_cast<SI16>(std::stoi(data, nullptr, 0));
|
|
}
|
|
else if( UTag == "Y1" )
|
|
{
|
|
y1 = static_cast<SI16>(std::stoi(data, nullptr, 0));
|
|
}
|
|
else if( UTag == "Y2" )
|
|
{
|
|
y2 = static_cast<SI16>(std::stoi(data, nullptr, 0));
|
|
}
|
|
else if( UTag == "MINTIME" )
|
|
{
|
|
mintime = static_cast<UI08>(std::stoul(data, nullptr, 0));
|
|
}
|
|
else if( UTag == "MAXTIME" )
|
|
{
|
|
maxtime = static_cast<UI08>(std::stoul(data, nullptr, 0));
|
|
}
|
|
else if( UTag == "NAME" )
|
|
{
|
|
name = data;
|
|
}
|
|
else if( UTag == "CALL" )
|
|
{
|
|
call = static_cast<UI16>(std::stoul(data, nullptr, 0));
|
|
}
|
|
else if( UTag == "WORLD" )
|
|
{
|
|
worldNumber = static_cast<UI16>(std::stoul(data, nullptr, 0));
|
|
}
|
|
else if( UTag == "INSTANCEID" )
|
|
{
|
|
instanceID = static_cast<UI16>(std::stoul(data, nullptr, 0));
|
|
}
|
|
else if( UTag == "PREFZ" )
|
|
{
|
|
prefZ = static_cast<SI08>(std::stoi(data, nullptr, 0));
|
|
}
|
|
else if( UTag == "ONLYOUTSIDE" )
|
|
{
|
|
onlyOutside = (static_cast<SI08>(std::stoi(data, nullptr, 0)) == 1);
|
|
}
|
|
else if( UTag == "VALIDLANDPOS" )
|
|
{
|
|
data = strutil::simplify( data );
|
|
auto csecs = strutil::sections( data, "," );
|
|
if( csecs.size() == 3 )
|
|
{
|
|
validLandPos.push_back( point3( static_cast<UI16>(std::stoul(strutil::trim(strutil::removeTrailing( csecs[0],"//") ),nullptr,0)), static_cast<UI16>(std::stoul(strutil::trim(strutil::removeTrailing( csecs[1],"//") ), nullptr, 0)), static_cast<UI08>(std::stoul(strutil::trim( strutil::removeTrailing( csecs[0], "//" )), nullptr, 0))));
|
|
validLandPosCheck[ static_cast<UI16>(std::stoul(strutil::trim(strutil::removeTrailing( csecs[1],"//") ),nullptr,0)) + ( static_cast<UI16>(std::stoul(strutil::trim(strutil::removeTrailing( csecs[0],"//") ), nullptr, 0)) << 16 ) ] = static_cast<UI08>(std::stoul(strutil::trim( strutil::removeTrailing( csecs[2], "//" )), nullptr, 0));
|
|
}
|
|
}
|
|
else if( UTag == "VALIDWATERPOS" )
|
|
{
|
|
data = strutil::simplify( data );
|
|
auto csecs = strutil::sections( data, "," );
|
|
if( csecs.size() == 3 )
|
|
{
|
|
validWaterPos.push_back( point3( static_cast<UI16>(std::stoul(strutil::trim(strutil::removeTrailing( csecs[0],"//") ), nullptr, 0)), static_cast<UI16>(std::stoul(strutil::trim(strutil::removeTrailing( csecs[1],"//") ), nullptr, 0)), static_cast<UI08>(std::stoul(strutil::trim( strutil::removeTrailing( csecs[0], "//" )), nullptr, 0))));
|
|
validWaterPosCheck[ static_cast<UI16>(std::stoul(strutil::trim(strutil::removeTrailing( csecs[1],"//") ), nullptr, 0)) + ( static_cast<UI16>(std::stoul(strutil::trim(strutil::removeTrailing( csecs[0],"//") ), nullptr, 0)) << 16 ) ] = static_cast<UI08>(std::stoul(strutil::trim( strutil::removeTrailing( csecs[2], "//" )), nullptr, 0));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void doRegionSpawn( UI16& itemsSpawned, UI16& npcsSpawned )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Do spawn stuff for spawnregion
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void CSpawnRegion::doRegionSpawn( UI16& itemsSpawned, UI16& npcsSpawned )
|
|
{
|
|
if( sNpcs.empty() )
|
|
maxcspawn = 0;
|
|
if( sItems.empty() )
|
|
maxispawn = 0;
|
|
|
|
bool shouldSpawnChars = ( !sNpcs.empty() && maxcspawn > spawnedChars.Num() );
|
|
bool shouldSpawnItems = ( !sItems.empty() && maxispawn > spawnedItems.Num() );
|
|
if( shouldSpawnChars || shouldSpawnItems )
|
|
{
|
|
CChar *spawnChar = nullptr;
|
|
CItem *spawnItem = nullptr;
|
|
const UI08 spawnChars = (shouldSpawnChars?0:50);
|
|
const UI08 spawnItems = (shouldSpawnItems?100:49);
|
|
for( UI16 i = 0; i < call && ( shouldSpawnItems || shouldSpawnChars ); ++i )
|
|
{
|
|
if( RandomNum( static_cast<std::uint16_t>(spawnChars), static_cast<std::uint16_t>(spawnItems) ) > 49 )
|
|
{
|
|
if( shouldSpawnItems )
|
|
{
|
|
spawnItem = RegionSpawnItem();
|
|
if( ValidateObject( spawnItem ) )
|
|
{
|
|
spawnedItems.Add( spawnItem );
|
|
++itemsSpawned;
|
|
shouldSpawnItems = (spawnedItems.Num() < maxispawn);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( shouldSpawnChars )
|
|
{
|
|
spawnChar = RegionSpawnChar();
|
|
if( ValidateObject( spawnChar ) )
|
|
{
|
|
spawnedChars.Add( spawnChar );
|
|
++npcsSpawned;
|
|
shouldSpawnChars = (spawnedChars.Num() < maxcspawn);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SetNextTime( BuildTimeValue( (R32)( RandomNum( static_cast<std::uint16_t>(GetMinTime()*60), static_cast<std::uint16_t>(GetMaxTime()*60) ) ) ) );
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - CChar *RegionSpawnChar( void )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Do a char spawn
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
CChar *CSpawnRegion::RegionSpawnChar( void )
|
|
{
|
|
std::string ourNPC = strutil::trim( strutil::removeTrailing( sNpcs[RandomNum( static_cast<size_t>( 0 ), sNpcs.size() - 1 )], "//" ));
|
|
ScriptSection *npcCreate = FileLookup->FindEntry( ourNPC, npc_def );
|
|
if( npcCreate == nullptr )
|
|
return nullptr;
|
|
|
|
std::string cdata;
|
|
SI32 ndata = -1, odata = -1;
|
|
UI16 npcID = 0;
|
|
|
|
for( DFNTAGS tag = npcCreate->FirstTag(); !npcCreate->AtEndTags(); tag = npcCreate->NextTag() )
|
|
{
|
|
cdata = npcCreate->GrabData( ndata, odata );
|
|
switch( tag )
|
|
{
|
|
case DFNTAG_ID:
|
|
npcID = static_cast<UI16>( ndata );
|
|
goto foundNpcID;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
foundNpcID:
|
|
|
|
bool waterCreature = false;
|
|
bool amphiCreature = false;
|
|
if( npcID > 0 )
|
|
{
|
|
waterCreature = cwmWorldState->creatures[npcID].IsWater();
|
|
amphiCreature = cwmWorldState->creatures[npcID].IsAmphibian();
|
|
}
|
|
|
|
SI16 x, y;
|
|
SI08 z;
|
|
if( FindCharSpotToSpawn( x, y, z, waterCreature, amphiCreature ) )
|
|
{
|
|
CChar *CSpawn = nullptr;
|
|
CSpawn = Npcs->CreateBaseNPC( ourNPC, false );
|
|
|
|
if( CSpawn != nullptr )
|
|
{
|
|
// NPCs should always wander the whole spawnregion
|
|
CSpawn->SetNpcWander( WT_BOX );
|
|
CSpawn->SetFx( x1, 0 );
|
|
CSpawn->SetFx( x2, 1 );
|
|
CSpawn->SetFy( y1, 0 );
|
|
CSpawn->SetFy( y2, 1 );
|
|
CSpawn->SetLocation( x, y, z, worldNumber, instanceID );
|
|
CSpawn->SetSpawned( true );
|
|
CSpawn->ShouldSave( false );
|
|
CSpawn->SetSpawn( static_cast<UI32>( regionnum ) );
|
|
Npcs->PostSpawnUpdate( CSpawn );
|
|
IncCurrentCharAmt();
|
|
return CSpawn;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.warning( strutil::format("Unable to find valid location to spawn NPC in region %i", this->GetRegionNum() ));
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - CItem *RegionSpawnItem( void )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Do an item spawn
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
CItem *CSpawnRegion::RegionSpawnItem( void )
|
|
{
|
|
CItem *ISpawn = nullptr;
|
|
SI16 x, y;
|
|
SI08 z;
|
|
if( FindItemSpotToSpawn( x, y, z ) )
|
|
{
|
|
ISpawn = Items->CreateBaseScriptItem( nullptr, sItems[RandomNum( static_cast< size_t >(0), sItems.size() - 1 )], worldNumber, 1, instanceID, OT_ITEM, 0xFFFF, false );
|
|
if( ISpawn != nullptr )
|
|
{
|
|
ISpawn->SetLocation( x, y, z );
|
|
ISpawn->SetSpawn( static_cast<UI32>(regionnum) );
|
|
ISpawn->SetSpawned( true );
|
|
ISpawn->ShouldSave( false );
|
|
IncCurrentItemAmt();
|
|
}
|
|
}
|
|
return ISpawn;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - bool FindCharSpotToSpawn( SI16 &x, SI16 &y, SI08 &z, bool &waterCreature, bool &hiCreature )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Find a random spot within a region valid for dropping an item
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
bool CSpawnRegion::FindCharSpotToSpawn( SI16 &x, SI16 &y, SI08 &z, bool &waterCreature, bool &hiCreature )
|
|
{
|
|
bool rvalue = false;
|
|
point3 currLoc;
|
|
std::map<UI32, SI08>::const_iterator checkValid;
|
|
SI08 z2 = ILLEGAL_Z;
|
|
const size_t landPosSize = validLandPos.size();
|
|
const size_t waterPosSize = validWaterPos.size();
|
|
|
|
// By default, let's attempt - at most - this many times to find a valid spawn point for the NPC
|
|
UI08 maxSpawnAttempts = 100;
|
|
|
|
// However, if we have found some valid spawn points already, reduce the amount of attempts to find MORE
|
|
// valid spawn points, and increase the chance of using those valid spots instead! Always make some attempts to
|
|
// find new spots, though
|
|
if( !waterCreature && landPosSize > 0 ) // land creature
|
|
maxSpawnAttempts = std::max( static_cast<SI16>( 25 ), static_cast<SI16>( maxSpawnAttempts - landPosSize ) );
|
|
else if(( waterCreature || amphiCreature ) && waterPosSize > 0 ) // water or amphibian creature
|
|
maxSpawnAttempts = std::max( static_cast<SI16>( 25 ), static_cast<SI16>( maxSpawnAttempts - waterPosSize ) );
|
|
|
|
for( UI08 a = 0; a < maxSpawnAttempts; ++a )
|
|
{
|
|
x = RandomNum( x1, x2 );
|
|
y = RandomNum( y1, y2 );
|
|
z = Map->MapElevation( x, y, worldNumber );
|
|
|
|
const SI08 dynz = Map->DynamicElevation( x, y, z, worldNumber, prefZ, instanceID );
|
|
if( ILLEGAL_Z != dynz )
|
|
z = dynz;
|
|
else
|
|
{
|
|
const SI08 staticz = Map->StaticTop( x, y, z, worldNumber, prefZ );
|
|
if( ILLEGAL_Z != staticz )
|
|
z = staticz;
|
|
}
|
|
|
|
// Continue with the next spawn attempt if z is illegal
|
|
if( z == ILLEGAL_Z )
|
|
continue;
|
|
|
|
// First go through the lists of already valid spawn locations on land,
|
|
// and see if the chosen location has already been validated
|
|
if( !waterCreature )
|
|
{
|
|
if( !validLandPosCheck.empty() && landPosSize > 0 )
|
|
{
|
|
checkValid = validLandPosCheck.find( y + (x<<16));
|
|
if( checkValid != validLandPosCheck.end() )
|
|
z2 = (*checkValid).second;
|
|
|
|
if(z2 == z && z != ILLEGAL_Z)
|
|
{
|
|
rvalue = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// No luck, so let's try the list of already valid water tiles instead (if NPC can move on water)
|
|
if( waterCreature || amphiCreature )
|
|
{
|
|
if( !validWaterPosCheck.empty() && waterPosSize > 0 )
|
|
{
|
|
checkValid = validWaterPosCheck.find( y + (x<<16));
|
|
if( checkValid != validWaterPosCheck.end() )
|
|
z2 = (*checkValid).second;
|
|
|
|
if(z2 == z && z != ILLEGAL_Z)
|
|
{
|
|
rvalue = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Since our chosen location has not already been validated, lets validate it with a land-based creature in mind
|
|
if( !waterCreature && Map->ValidSpawnLocation( x, y, z, worldNumber, instanceID ))
|
|
{
|
|
if( onlyOutside == false || !Map->inBuilding( x, y, z, worldNumber, instanceID ) )
|
|
{
|
|
if( z != ILLEGAL_Z )
|
|
{
|
|
rvalue = true;
|
|
validLandPos.push_back( point3( x, y, z ) );
|
|
validLandPosCheck[ y + ( x << 16) ] = z;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Otherwise, validate it with a water-based creature in mind instead
|
|
if(( waterCreature || amphiCreature ) && Map->ValidSpawnLocation( x, y, z, worldNumber, instanceID, false ))
|
|
{
|
|
if( onlyOutside == false || !Map->inBuilding( x, y, z, worldNumber, instanceID ) )
|
|
{
|
|
rvalue = true;
|
|
validWaterPos.push_back( point3( x, y, z ) );
|
|
validWaterPosCheck[ y + ( x << 16) ] = z;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we haven't found a valid location pick a random location from the stored ones
|
|
if( !rvalue && !waterCreature && !validLandPos.empty() && landPosSize > 0)
|
|
{
|
|
currLoc = validLandPos[RandomNum( static_cast<size_t>(0), (landPosSize-1) )];
|
|
x = static_cast<SI16>(currLoc.x);
|
|
y = static_cast<SI16>(currLoc.y);
|
|
z = static_cast<SI08>(currLoc.z);
|
|
// Recalculate the z coordinate to see whether something has changed
|
|
z2 = Map->MapElevation( x, y, worldNumber );
|
|
const SI08 dynz = Map->DynamicElevation( x, y, z, worldNumber, prefZ, instanceID );
|
|
if( ILLEGAL_Z != dynz )
|
|
z2 = dynz;
|
|
else
|
|
{
|
|
const SI08 staticz = Map->StaticTop( x, y, z, worldNumber, prefZ );
|
|
if( ILLEGAL_Z != staticz )
|
|
z2 = staticz;
|
|
}
|
|
|
|
if(z2 == z)
|
|
rvalue = true;
|
|
}
|
|
else if( !rvalue && ( waterCreature || amphiCreature) && !validWaterPos.empty() && waterPosSize > 0 )
|
|
{
|
|
currLoc = validWaterPos[RandomNum( static_cast<size_t>(0), (waterPosSize-1) )];
|
|
x = static_cast<SI16>(currLoc.x);
|
|
y = static_cast<SI16>(currLoc.y);
|
|
z = static_cast<SI08>(currLoc.z);
|
|
// Recalculate the z coordinate to see whether something has changed
|
|
z2 = Map->MapElevation( x, y, worldNumber );
|
|
const SI08 dynz = Map->DynamicElevation( x, y, z, worldNumber, prefZ, instanceID );
|
|
if( ILLEGAL_Z != dynz )
|
|
z2 = dynz;
|
|
else
|
|
{
|
|
const SI08 staticz = Map->StaticTop( x, y, z, worldNumber, prefZ );
|
|
if( ILLEGAL_Z != staticz )
|
|
z2 = staticz;
|
|
}
|
|
|
|
if(z2 == z)
|
|
rvalue = true;
|
|
}
|
|
return rvalue;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - bool FindItemSpotToSpawn( SI16 &x, SI16 &y, SI08 &z )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Find a random spot within a region valid for dropping an item
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
bool CSpawnRegion::FindItemSpotToSpawn( SI16 &x, SI16 &y, SI08 &z )
|
|
{
|
|
bool rvalue = false;
|
|
point3 currLoc;
|
|
SI08 z2 = ILLEGAL_Z;
|
|
std::map<UI32, SI08>::const_iterator checkValid;
|
|
const size_t landPosSize = validLandPos.size();
|
|
|
|
// By default, let's try - at most - this many times to find a valid spawn point for the item
|
|
UI08 maxSpawnAttempts = 100;
|
|
|
|
// However, if we have found some valid spawn points already, reduce the amount of attempts to find MORE
|
|
// valid spawn points, and increase the chance of using those valid spots instead!
|
|
if( landPosSize > 0 )
|
|
maxSpawnAttempts = std::max( static_cast<SI16>( 25 ), static_cast<SI16>( maxSpawnAttempts - landPosSize ) );
|
|
|
|
for( UI08 a = 0; a < maxSpawnAttempts; ++a )
|
|
{
|
|
x = RandomNum( x1, x2 );
|
|
y = RandomNum( y1, y2 );
|
|
z = Map->MapElevation( x, y, worldNumber );
|
|
|
|
const SI08 dynz = Map->DynamicElevation( x, y, z, worldNumber, prefZ, instanceID );
|
|
if( ILLEGAL_Z != dynz )
|
|
z = dynz;
|
|
else
|
|
{
|
|
const SI08 staticz = Map->StaticTop( x, y, z, worldNumber, prefZ );
|
|
if( ILLEGAL_Z != staticz )
|
|
z = staticz;
|
|
}
|
|
|
|
// First go through the lists of already stored good locations
|
|
if( !validLandPosCheck.empty() && landPosSize > 0 )
|
|
{
|
|
checkValid = validLandPosCheck.find( y + (x<<16));
|
|
if( checkValid != validLandPosCheck.end() )
|
|
z2 = (*checkValid).second;
|
|
|
|
if(z2 == z && z != ILLEGAL_Z )
|
|
{
|
|
rvalue = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( Map->ValidSpawnLocation( x, y, z, worldNumber, instanceID ) )
|
|
{
|
|
if( onlyOutside == false || !Map->inBuilding( x, y, z, worldNumber, instanceID ) )
|
|
{
|
|
rvalue = true;
|
|
validLandPos.push_back( point3( x, y, z ) );
|
|
validLandPosCheck[ y + ( x << 16) ] = z;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we haven't found a valid location pick a random location from the stored ones
|
|
if( !rvalue && !validLandPos.empty() && landPosSize > 0)
|
|
{
|
|
currLoc = validLandPos[RandomNum( static_cast<size_t>(0), (landPosSize-1) )];
|
|
x = static_cast<SI16>(currLoc.x);
|
|
y = static_cast<SI16>(currLoc.y);
|
|
z = static_cast<SI08>(currLoc.z);
|
|
// Recalculate the z coordinate to see whether something has changed
|
|
z2 = Map->MapElevation( x, y, worldNumber );
|
|
const SI08 dynz = Map->DynamicElevation( x, y, z, worldNumber, prefZ, instanceID );
|
|
if( ILLEGAL_Z != dynz )
|
|
z2 = dynz;
|
|
else
|
|
{
|
|
const SI08 staticz = Map->StaticTop( x, y, z, worldNumber, prefZ );
|
|
if( ILLEGAL_Z != staticz )
|
|
z2 = staticz;
|
|
}
|
|
|
|
if(z2 == z)
|
|
rvalue = true;
|
|
}
|
|
|
|
return rvalue;
|
|
}
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void checkSpawned( void )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Check if an item/npc should be removed from spawnlist and saved
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void CSpawnRegion::checkSpawned( void )
|
|
{
|
|
for( CChar *cCheck = spawnedChars.First(); !spawnedChars.Finished(); cCheck = spawnedChars.Next() )
|
|
{
|
|
if( ValidateObject( cCheck ) )
|
|
{
|
|
if( ValidateObject( cCheck->GetOwnerObj() ) )
|
|
{
|
|
cCheck->ShouldSave( true );
|
|
spawnedChars.Remove( cCheck );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.warning( "Invalid Object found in CSpawnRegion character list, AutoCorrecting." );
|
|
spawnedChars.Remove( cCheck );
|
|
}
|
|
}
|
|
|
|
for( CItem *iCheck = spawnedItems.First(); !spawnedItems.Finished(); iCheck = spawnedItems.Next() )
|
|
{
|
|
if( ValidateObject( iCheck ) )
|
|
{
|
|
if( ValidateObject( iCheck->GetCont() ) )
|
|
{
|
|
iCheck->ShouldSave( true );
|
|
spawnedItems.Remove( iCheck );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.warning( "Invalid Object found in CSpawnRegion item list, AutoCorrecting." );
|
|
spawnedItems.Remove( iCheck );
|
|
}
|
|
}
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void deleteSpawnedChar( CChar *toDelete )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Delete an npc from spawnlist
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void CSpawnRegion::deleteSpawnedChar( CChar *toDelete )
|
|
{
|
|
if( spawnedChars.Remove( toDelete ) )
|
|
IncCurrentCharAmt( -1 );
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void deleteSpawnedItem( CItem *toDelete )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Delete an item from spawnlist
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void CSpawnRegion::deleteSpawnedItem( CItem *toDelete )
|
|
{
|
|
if( spawnedItems.Remove( toDelete ) )
|
|
IncCurrentItemAmt( -1 );
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - GenericList< CItem * > * GetSpawnedItemsList( void )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Gets list of spawned Items for spawnregion
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
GenericList< CItem * > * CSpawnRegion::GetSpawnedItemsList( void )
|
|
{
|
|
return &spawnedItems;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - GenericList< CChar * > * GetSpawnedCharsList( void )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Gets list of spawned NPCs for spawnregion
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
GenericList< CChar * > * CSpawnRegion::GetSpawnedCharsList( void )
|
|
{
|
|
return &spawnedChars;
|
|
}
|