polserver/pol-core/pol/spells.cpp
turleypol 6a2bd7c705 Merge remote-tracking branch 'origin/master' into vector_classes
* origin/master: (93 commits)
  Add support for dictionary creation (#277)
  Add do-while statement (#276)
  Add foreach loops (#275)
  New compiler: array initialization (#274)
  Disambiguator: figure out if label is a case statement dispatch selector, or should apply to a statement within the case dispatch group's block. (#273)
  New compiler: Add support for case statements (#272)
  Change "logical" to "bitwise". (#258)
  Fix divide-by-zero errors, and mask FormatError if it were thrown in a destructor. (#271)
  Add break and continue statements. (#270)
  New compiler: assignment to local and global variables. (#269)
  Add support for while loops (#268)
  Optimize if statements, and emit module functions in legacy order (#267)
  Optimizations: binary operators (#266)
  Add binary operators (#265)
  Add support for const declarations (#264)
  Handle default parameter values, pass-by-name, and return statements in functions. (#263)
  Add limited support for user functions (#262)
  New compiler: add return and exit statements (#261)
  Add support for if-then-else statements (#259)
  OG compiler: set module to Mod_Basic when a token is an identifier (#257)
  ...
2020-09-06 20:14:26 +02:00

515 lines
13 KiB
C++

/** @file
*
* @par History
* - 2009/09/20 MuadDib: docast no longer unhides. Let scripts handle this.
* - 2010/01/15 Turley: (Tomi) SpeakPowerWords font and color params
*/
#include "spells.h"
#include <stdlib.h>
#include <time.h>
#include "../bscript/eprog.h"
#include "../clib/cfgelem.h"
#include "../clib/cfgfile.h"
#include "../clib/fileutil.h"
#include "../clib/logfacility.h"
#include "../clib/refptr.h"
#include "../plib/pkg.h"
#include "../plib/systemstate.h"
#include "containr.h"
#include "globals/state.h"
#include "globals/uvars.h"
#include "item/item.h"
#include "item/itemdesc.h"
#include "layers.h"
#include "regions/miscrgn.h"
#include "mkscrobj.h"
#include "mobile/charactr.h"
#include "network/client.h"
#include "network/pktin.h"
#include "polclass.h"
#include "polsig.h"
#include "scrstore.h"
#include "skillid.h"
#include "spelbook.h"
#include "syshook.h"
#include "ufunc.h"
#include "umanip.h"
#include "vital.h"
#include <format/format.h>
namespace Pol
{
namespace Core
{
static bool nocast_here( Mobile::Character* chr )
{
NoCastRegion* rgn = gamestate.nocastdef->getregion( chr->pos() );
if ( rgn == nullptr )
{
return false;
}
else
{
return rgn->nocast();
}
}
bool knows_spell( Mobile::Character* chr, u16 spellid )
{
// copied code from Character::spellbook to support multiple spellbooks in the pack
Items::Item* item = chr->wornitem( LAYER_HAND1 );
if ( item != nullptr && item->script_isa( POLCLASS_SPELLBOOK ) )
{
Spellbook* book = static_cast<Spellbook*>( item );
if ( book->has_spellid( spellid ) )
return true;
}
UContainer* cont = chr->backpack();
if ( cont != nullptr )
{
for ( UContainer::const_iterator itr = cont->begin(), end = cont->end(); itr != end; ++itr )
{
const Items::Item* _item = GET_ITEM_PTR( itr );
if ( _item != nullptr && _item->script_isa( POLCLASS_SPELLBOOK ) )
{
const Spellbook* book = static_cast<const Spellbook*>( _item );
if ( book->has_spellid( spellid ) )
return true;
}
}
}
return false;
}
bool hands_are_free( Mobile::Character* chr )
{
Items::Item* item;
item = chr->wornitem( LAYER_HAND1 );
if ( item != nullptr )
{
const Items::ItemDesc& id = item->itemdesc();
if ( id.blocks_casting_if_in_hand )
return false;
}
item = chr->wornitem( LAYER_HAND2 );
if ( item != nullptr )
{
const Items::ItemDesc& id = item->itemdesc();
if ( id.blocks_casting_if_in_hand )
return false;
}
return true;
}
USpellParams::USpellParams() : manacost( 0 ), difficulty( 0 ), pointvalue( 0 ), delay( 0 ) {}
USpellParams::USpellParams( Clib::ConfigElem& elem )
: manacost( elem.remove_ushort( "MANA" ) ),
difficulty( elem.remove_ushort( "DIFFICULTY" ) ),
pointvalue( elem.remove_ushort( "POINTVALUE" ) ),
delay( elem.remove_ushort( "DELAY" ) )
{
}
SpellCircle::SpellCircle( Clib::ConfigElem& elem ) : params( elem ) {}
USpell::USpell( Clib::ConfigElem& elem, Plib::Package* pkg )
: pkg_( pkg ),
spellid_( elem.remove_ushort( "SPELLID" ) ),
name_( elem.remove_string( "NAME" ) ),
power_words_( elem.remove_string( "POWERWORDS" ) ),
scriptdef_( elem.remove_string( "SCRIPT", "" ), pkg, "scripts/" )
{
unsigned short action;
if ( elem.remove_prop( "ANIMATION", &action ) )
{
if ( UACTION_IS_VALID( action ) )
{
action_ = static_cast<UACTION>( action );
}
else
{
elem.throw_error( "Animation is out of range" );
}
}
else
{
action_ = ACTION_CAST_SPELL1;
}
unsigned short circle;
if ( elem.remove_prop( "CIRCLE", &circle ) )
{
if ( circle < 1 || circle > gamestate.spellcircles.size() ||
gamestate.spellcircles[circle - 1] == nullptr )
{
ERROR_PRINT << "Error reading spell " << name_ << ": Circle " << circle
<< " is not defined.\n";
throw std::runtime_error( "Config file error" );
}
params_ = gamestate.spellcircles[circle - 1]->params;
}
else
{
params_ = USpellParams( elem );
}
std::string reagent_name;
while ( elem.remove_prop( "Reagent", &reagent_name ) )
{
unsigned int reagent = Items::get_objtype_from_string( reagent_name );
reglist_.push_back( reagent );
}
}
size_t USpell::estimateSize() const
{
size_t size = sizeof( Plib::Package* ) /*pkg_*/
+ sizeof( unsigned short ) /*spellid_*/
+ name_.capacity() + power_words_.capacity() + sizeof( UACTION ) /*action_*/
+ 3 * sizeof( unsigned int* ) + reglist_.capacity() * sizeof( unsigned int ) +
sizeof( USpellParams ) /*params_*/
+ scriptdef_.estimatedSize();
return size;
}
void USpell::cast( Mobile::Character* chr )
{
if ( nocast_here( chr ) )
{
if ( chr->client != nullptr )
send_sysmessage( chr->client, "Spells cannot be cast here." );
return;
}
if ( !scriptdef_.empty() )
{
ref_ptr<Bscript::EScriptProgram> prog =
find_script2( scriptdef_, true, Plib::systemstate.config.cache_interactive_scripts );
if ( prog.get() != nullptr )
{
if ( chr->start_spell_script( prog.get(), this ) )
return;
}
}
if ( chr->client != nullptr )
send_sysmessage( chr->client, "That spell doesn't seem to work." );
}
bool USpell::consume_reagents( Mobile::Character* chr )
{
UContainer* bp = chr->backpack();
if ( bp == nullptr )
return false;
for ( RegList::iterator itr = reglist_.begin(), end = reglist_.end(); itr != end; ++itr )
{
Items::Item* item = bp->find_objtype_noninuse( *itr );
if ( item == nullptr )
return false;
subtract_amount_from_item( item, 1 );
}
return true;
}
bool USpell::check_mana( Mobile::Character* chr )
{
return ( chr->vital( gamestate.pVitalMana->vitalid ).current_ones() >= manacost() );
}
void USpell::consume_mana( Mobile::Character* chr )
{
chr->consume( gamestate.pVitalMana, chr->vital( gamestate.pVitalMana->vitalid ), manacost() * 100,
Mobile::Character::VitalDepletedReason::SCRIPT );
}
void USpell::speak_power_words( Mobile::Character* chr, unsigned short font, unsigned short color )
{
if ( chr->client != nullptr && chr->hidden() )
{
private_say_above( chr, chr, power_words_.c_str(), font, color );
}
else if ( !chr->hidden() )
{
say_above( chr, power_words_.c_str(), font, color );
}
}
SpellTask::SpellTask( OneShotTask** handle, polclock_t run_when_clock, Mobile::Character* caster,
USpell* spell, bool /*dummy*/ )
: OneShotTask( handle, run_when_clock ), caster_( caster ), spell_( spell )
{
}
void SpellTask::on_run()
{
THREAD_CHECKPOINT( tasks, 900 );
Mobile::Character* caster = caster_.get();
if ( !caster->orphan() )
{
THREAD_CHECKPOINT( tasks, 911 );
spell_->cast( caster );
THREAD_CHECKPOINT( tasks, 912 );
}
THREAD_CHECKPOINT( tasks, 999 );
}
void do_cast( Network::Client* client, u16 spellid )
{
if ( gamestate.system_hooks.on_cast_hook )
{
if ( gamestate.system_hooks.on_cast_hook->call( make_mobileref( client->chr ),
new Bscript::BLong( spellid ) ) )
return;
}
// CHECKME should this look at spellnum, instead? static_cast behavior undefined if out of range.
if ( spellid >= gamestate.spells.size() )
return;
USpell* spell = gamestate.spells[spellid];
if ( spell == nullptr )
{
ERROR_PRINT << "Spell " << spellid << " is not implemented.\n";
send_sysmessage( client, "That spell does not function." );
return;
}
// Let scripts handle this.
// if (client->chr->hidden())
// client->chr->unhide();
if ( client->chr->frozen() )
{
private_say_above( client->chr, client->chr, "I am frozen and cannot cast spells" );
return;
}
if ( client->chr->paralyzed() )
{
private_say_above( client->chr, client->chr, "I am paralyzed and cannot cast spells" );
return;
}
if ( client->chr->skill_ex_active() )
{
send_sysmessage( client, "You are already doing something else." );
return;
}
if ( client->chr->casting_spell() )
{
send_sysmessage( client, "You are already casting a spell." );
return;
}
if ( nocast_here( client->chr ) )
{
send_sysmessage( client, "Spells cannot be cast here." );
return;
}
if ( Plib::systemstate.config.require_spellbooks )
{
if ( !knows_spell( client->chr, spellid ) )
{
send_sysmessage( client, "You don't know that spell." );
return;
}
}
#if 1
client->chr->schedule_spell( spell );
#else
spell->cast( client );
client->restart();
#endif
}
void handle_cast_spell( Network::Client* client, PKTIN_12* msg )
{
u16 spellnum = static_cast<u16>( strtoul( (char*)msg->data, nullptr, 10 ) );
do_cast( client, spellnum );
}
void handle_open_spellbook( Network::Client* client, PKTIN_12* /*msg*/ )
{
if ( gamestate.system_hooks.open_spellbook_hook != nullptr )
{
if ( gamestate.system_hooks.open_spellbook_hook->call( make_mobileref( client->chr ) ) )
return;
}
if ( client->chr->dead() )
{
send_sysmessage( client, "I am dead and cannot do that." );
return;
}
Items::Item* spellbook = client->chr->wornitem( LAYER_HAND1 );
if ( spellbook == nullptr )
{
UContainer* backpack = client->chr->backpack();
if ( backpack != nullptr )
{
spellbook = backpack->find_toplevel_polclass( POLCLASS_SPELLBOOK );
//
// Client crashes if the pack isn't open and you don't tell him
// about the spellbook
//
if ( spellbook != nullptr )
send_put_in_container( client, spellbook );
}
}
if ( spellbook != nullptr )
{
spellbook->double_click( client );
}
}
void register_spell( USpell* spell, unsigned short spellid )
{
if ( spellid >= gamestate.spells.size() )
{
gamestate.spells.resize( spellid + 1, nullptr );
}
if ( gamestate.spells[spellid] )
{
USpell* origspell = gamestate.spells[spellid];
fmt::Writer tmp;
tmp << "Spell ID " << spellid << " (" << origspell->name() << ") multiply defined\n";
if ( origspell->pkg_ != nullptr )
{
tmp << " Spell originally defined in package '" << origspell->pkg_->name() << "' ("
<< origspell->pkg_->dir() << ")\n";
}
else
{
tmp << " Spell originally defined in main\n";
}
if ( spell->pkg_ != nullptr )
{
tmp << " Spell redefined in package '" << spell->pkg_->name() << "' (" << spell->pkg_->dir()
<< ")\n";
}
else
{
tmp << " Spell redefined in main\n";
}
ERROR_PRINT << tmp.str();
throw std::runtime_error( "Spell ID multiply defined" );
}
gamestate.spells[spellid] = spell;
}
void load_circle_data()
{
if ( !Clib::FileExists( "config/circles.cfg" ) )
{
if ( Plib::systemstate.config.loglevel > 1 )
INFO_PRINT << "File config/circles not found, skipping.\n";
return;
}
Clib::ConfigFile cf( "config/circles.cfg", "Circle" );
Clib::ConfigElem elem;
while ( cf.read( elem ) )
{
int index = strtoul( elem.rest(), nullptr, 0 ) - 1;
if ( index < 0 || index >= 100 )
{
ERROR_PRINT << "Error in CIRCLES.CFG: Circle must fall between 1 and 100\n";
throw std::runtime_error( "Config file error" );
}
gamestate.spellcircles.resize( index + 1, nullptr );
if ( gamestate.spellcircles[index] != nullptr )
{
ERROR_PRINT << "Error in CIRCLES.CFG: Circle " << index + 1 << " is multiply defined.\n";
throw std::runtime_error( "Config file error" );
}
gamestate.spellcircles[index] = new SpellCircle( elem );
}
}
void load_spells_cfg( const char* path, Plib::Package* pkg )
{
Clib::ConfigFile cf( path, "Spell" );
Clib::ConfigElem elem;
while ( cf.read( elem ) )
{
std::unique_ptr<USpell> spell( new USpell( elem, pkg ) );
unsigned short spellid = spell->spell_id();
register_spell( spell.release(), spellid );
}
}
void load_spell_data()
{
load_circle_data();
if ( Clib::FileExists( "config/spells.cfg" ) )
load_spells_cfg( "config/spells.cfg", nullptr );
else if ( Plib::systemstate.config.loglevel > 1 )
INFO_PRINT << "File config/spells.cfg not found, skipping\n";
for ( Plib::Packages::iterator itr = Plib::systemstate.packages.begin();
itr != Plib::systemstate.packages.end(); ++itr )
{
Plib::Package* pkg = ( *itr );
std::string filename = Plib::GetPackageCfgPath( pkg, "spells.cfg" );
if ( Clib::FileExists( filename.c_str() ) )
{
load_spells_cfg( filename.c_str(), pkg );
}
}
}
void clean_spells()
{
std::vector<SpellCircle*>::iterator c_iter = gamestate.spellcircles.begin();
for ( ; c_iter != gamestate.spellcircles.end(); ++c_iter )
{
delete *c_iter;
*c_iter = nullptr;
}
gamestate.spellcircles.clear();
std::vector<USpell*>::iterator s_iter = gamestate.spells.begin();
for ( ; s_iter != gamestate.spells.end(); ++s_iter )
{
delete *s_iter;
*s_iter = nullptr;
}
gamestate.spells.clear();
}
} // namespace Core
} // namespace Pol