////////////////////////////////////////////////////////////////////////////// // // File : aura_enchant.skrit // Author(s): Witness (Lisa Hui) // Purpose : basic aura active state detection and tracker management // // Versions : v4.1 [November 1, 2004] // - changed tracker goclonereq to ForceClientAllowed, to get rid of // MP error with server-side-only omni // // v4.0 [August 8, 2004] // - allow enchantment toggling for targets to come from this object rather than the tracker // and auto-sets enchant_user$ false when both it and enchant_target$ are true while // the source qualifies as a target; target enchantments are handled by the target tracker // - added required magic level checks and rudimentary enchantment refreshing when magic level changes // - added life and mana cost handling // - fixed target type specification // // v3.0 [August 2, 2004] // - auras now use omni trackers to apply sfx because SiegeFx.SStopScript( Goid id, const char* sName ) // doesn't work (the sfx only stops when the effect owner is deleted or a sfxsid is specified); list // format has been adjusted to save and parse target:tracker associations // - revised ApplyAura$ and RemoveAura$ to both work with a global string, targets_search$ // - added immunity checks // // v2.0 [June 20, 2004] // - merged aura_apply.skrit with aura.skrit // // v1.0 [June 18, 2004] // - tracker list saved as a space-delimited list of goid integers // - first release // //---------------------------------------------------------------------------- // Version: 4.1 Date: November 1, 2004 //---------------------------------------------------------------------------- ////////////////////////////////////////////////////////////////////////////// property int enchant_source$ = 0 doc = ""; property bool enchant_source_scales$ = false doc = ""; property int enchant_target$ = 0 doc = ""; property bool enchant_target_scales$ = false doc = ""; property string sfx_source$ = "" doc = "source sfx"; property string sfx_source_params$ = "" doc = "target sfx parameters"; property string sfx_target$ = "" doc = "target sfx"; property string sfx_target_params$ = "" doc = "target sfx parameters"; property string sfx_replay$ = "" doc = "replay sfx"; property string sfx_replay_params$ = "" doc = "replay sfx parameters"; property float sfx_replay_interval$ = 0.0 doc = "replay sfx delay"; property string tracker$ = "aura_tag" doc = "the tracker manages target visuals and ancillary effects"; property string tracker_component$ = "aura_tag" doc = "the (skrit) component that drives the tracker GO"; property string immunity$ = "" doc = "if a target contains this membership then it is immune to this aura"; property float interval$ = 2.0 doc = "check for target changes, in seconds"; property float radius$ = 3.0 doc = "radius can be a float or one of the following macros: ,, (place the actual formula in either the duration, cast_experience, or mana cost modifier fields of the magic block respectively)"; property bool costs_health$ = false doc = "health cost should be provided in the cast_experience field"; property bool costs_mana$ = false doc = "mana cost is the sum of mana_cost and mana_cost_modifier"; //property string requirements$ = "" doc = "additional skill requirements for activation...StringTool.GetDelimitedString does not parse spaces correctly, so don't use skill names that have spaces!"; property eTargetTypeFlags target_type_flags$ = TT_NONE doc = "aura target flags"; property eQueryTrait target_type_hint$ = QT_ANY doc = "a query hint to optimize radius content checks"; property eQueryTrait target_type_hint2$ = QT_ANY doc = "a query hint to optimize radius content checks"; property eQueryTrait target_type_hint3$ = QT_ANY doc = "a query hint to optimize radius content checks"; owner = GoSkritComponent; ////////////////////////////////////////////////////////////////////////////// // GLOBALS ////////////////////////////////////////////////////////////////////////////// Goid source$, parent$; int hints$; void debug$ (string msg$) { //report.screenf("%s (%d)> %s", owner.Go.TemplateName, MakeInt(owner.Goid), msg$); } ////////////////////////////////////////////////////////////////////////////// // STATES ////////////////////////////////////////////////////////////////////////////// startup State Init$ { event OnEnterState$ { hints$ = ( target_type_hint$ == QT_ANY ? 0 : ( target_type_hint2$ == QT_ANY ? 1 : ( target_type_hint3$ == QT_ANY ? 2 : 3 ) ) ); SetState Inactive$; } } State Inactive$ { event OnEnterState$ { debug$("AuraInactive$ - onenterstate$"); } event OnGoHandleMessage$( eWorldEvent e$, WorldMessage msg$ ) { if ( e$ == WE_TRIGGER_ACTIVATE ) { source$ = MakeGoid( msg$.Data1 ); if ( !source$.IsValid ) { return; } parent$ = msg$.SendFrom; SetState CheckLevel$; } } } State CheckLevel$ { event OnEnterState$ { debug$("AuraCheckLevel$ - onenterstate$"); //requirements check out ok? if ( owner.Go.Magic.GetMagicLevel( source$.Go ) >= owner.Go.Magic.RequiredCastLevel ) { //deduct health if ( costs_health$ ) { if ( source$.Go.Aspect.CurrentLife < owner.Go.Magic.EvaluateCastExperience( source$.Go, source$.Go ) ) { debug$("user does not meet health activity cost"); this.CreateTimer( 1, 1.0 ); this.SetTimerRepeatCount( 1, -1 ); return; } } //deduct mana if ( costs_mana$ ) { if ( source$.Go.Aspect.CurrentMana < owner.Go.Magic.EvaluateManaCost( source$.Go, source$.Go ) ) { debug$("user does not meet mana activity cost"); this.CreateTimer( 1, 1.0 ); this.SetTimerRepeatCount( 1, -1 ); return; } } SetState Active$; return; } //waiting... if ( source$.Go.Actor.CanLevelUp ) { debug$(MakeStringF("user does not meet requirements (%f vs %f)",owner.Go.Magic.GetMagicLevel( source$.Go ), owner.Go.Magic.RequiredCastLevel)); this.CreateTimer( 1, 1.0 ); this.SetTimerRepeatCount( 1, -1 ); return; } //level won't ever change, go back to inactive state SetState Inactive$; } trigger OnTimer$( 1 ) { debug$(MakeStringF("AuraCheckLevel$ - check interval %f vs %f", owner.Go.Magic.GetMagicLevel( source$.Go ), owner.Go.Magic.RequiredCastLevel )); if ( owner.Go.Magic.GetMagicLevel( source$.Go ) >= owner.Go.Magic.RequiredCastLevel ) { //deduct health if ( costs_health$ ) { if ( source$.Go.Aspect.CurrentLife < owner.Go.Magic.EvaluateCastExperience( source$.Go, source$.Go ) ) { return; } } //deduct mana if ( costs_mana$ ) { if ( source$.Go.Aspect.CurrentMana < owner.Go.Magic.EvaluateManaCost( source$.Go, source$.Go ) ) { return; } } SetState Active$; } } transition -> Inactive$ : OnGoHandleMessage( WE_TRIGGER_DEACTIVATE ); } State Active$ { SFxSID source_sfx_id$; float source_level$; string targets$, targets_new$, targets_search$; //requires: targets_search$ to be initialized with search space target:target_tracker pairs prior to call //modifies: targets_search$, targets_new$ int ApplyAura$( Goid target$ ) { //debug$("ApplyAura$"); //state tagging if ( target$.Go.HasActor() ) { if ( owner.Go.Magic.StateName != "" ) { target$.Go.Actor.SAddGenericState( owner.Go.Magic.StateName, "", 0, source$, owner.Goid, owner.Go.Magic.GetMagicLevel( source$.Go ) ); } } //iterate through the targets to check if it is already on the list int index$ = StringTool.GetNumDelimitedValues( targets_search$, ' ' ) - 1; bool found$ = false; string targets_unmatched$ = ""; Goid target_match$, target_tracker$; string pair$; int tracker_goid$; while ( index$ > 0 ) { pair$ = StringTool.GetDelimitedString( targets_search$, index$, ' ', "" ); if ( pair$ != "" ) { target_match$ = MakeGoid( StringTool.GetDelimitedInt( pair$, 0, ':', 0 ) ); if ( target_match$.IsValid ) { if ( target$ != target_match$ ) { StringTool.AppendF( targets_unmatched$, " %s", pair$ ); } else { //debug$(MakeStringF("ApplyAura$ found target already on the list: %s (%d)", target$.Go.TemplateName, MakeInt(target$))); found$ = true; tracker_goid$ = StringTool.GetDelimitedInt( pair$, 1, ':', 0 ); } } else { target_tracker$ = MakeGoid( StringTool.GetDelimitedInt( pair$, 1, ':', 0 ) ); if ( target_tracker$.IsValid ) { PostWorldMessage( WE_REQ_DELETE, target_tracker$, target_tracker$, 0 ); } } } index$ -= 1; } //debug$(MakeStringF("ApplyAura$ - targets_search %s", targets_search$)); //debug$(MakeStringF("ApplyAura$ - targets_unmatched %s", targets_unmatched$)); //optimize subsequent searches by removing matched values from the search list targets_search$ = targets_unmatched$; //debug$(MakeStringF("ApplyAura$ - search space: %s",targets_search$)); //if the target is not on the list... if ( !found$ ) { //debug$(MakeStringF("ApplyAura$ did not find target on the list: %s (%d)", target$.Go.TemplateName, MakeInt(target$))); //create the tracker GoCloneReq req$ = MakeGoCloneReq( tracker$ ); req$.Omni = true; req$.ForceClientAllowed = true; target_tracker$ = GoDb.SCloneGo( req$ ); //configure the tracker target_tracker$.Go.SetComponentString( tracker_component$, "sfx_target", sfx_target$ ); target_tracker$.Go.SetComponentString( tracker_component$, "sfx_target_params", sfx_target_params$ ); target_tracker$.Go.SetComponentString( tracker_component$, "sfx_replay", sfx_replay$ ); target_tracker$.Go.SetComponentString( tracker_component$, "sfx_replay_params", sfx_replay_params$ ); target_tracker$.Go.SetComponentFloat( tracker_component$, "sfx_replay_interval", sfx_replay_interval$ ); target_tracker$.Go.SetComponentInt( tracker_component$, "enchant_target", enchant_target$ ); //activate the tracker PostWorldMessage( WE_REQ_ACTIVATE, owner.Goid, target_tracker$, MakeInt( target$ ), 0 ); //add target:tracker to the list of new targets, but not the search list StringTool.AppendF( targets_new$, " %d:%d", MakeInt( target$ ), MakeInt( target_tracker$ ) ); tracker_goid$ = MakeInt( target_tracker$ ); //debug$( MakeStringF( " ApplyAura$ - new occupant %s (%d:%d)", target$.Go.TemplateName, MakeInt( target$ ), MakeInt( target_tracker$ ) ) ); } return tracker_goid$; } //requires: targets_remove$ is a space delimited positive-indexed list of goids //modifies: targets$ void RemoveAura$( string targets_remove$, bool notify_tracker$ ) { //debug$(MakeStringF("RemoveAura$ - list: %s", targets_remove$)); if ( targets_search$ == "" ) { //debug$("RemoveAura$ - no search space!"); return; } int index$ = StringTool.GetNumDelimitedValues( targets_search$, ' ' ) - 1; if ( targets_remove$ != "" ) { int target$; Goid target_tracker$; string pair$; while ( index$ > 0 ) { pair$ = StringTool.GetDelimitedString( targets_search$, index$, ' ', "" ); if ( pair$ != "" ) { target$ = StringTool.GetDelimitedInt( pair$, 0, ':', 0 ); //cycle through all the items in the removal list and check for matches bool found$ = false; int index_remove$ = StringTool.GetNumDelimitedValues( targets_remove$, ' ' ) - 1; string buffer_remove$ = ""; int target_match$; while ( index_remove$ > 0 ) { target_match$ = StringTool.GetDelimitedInt( targets_remove$, index_remove$, ' ', -1 ); if ( target$ == target_match$ ) { found$ = true; //remove the state tag Goid target_remove$ = MakeGoid( target$ ); if ( target_remove$.IsValid ) { if ( target_remove$.Go.HasActor() ) { target_remove$.Go.Actor.SRemoveGenericState( owner.Go.Magic.StateName ); } } //remove the tracker if ( notify_tracker$ ) { target_tracker$ = MakeGoid( StringTool.GetDelimitedInt( pair$, 1, ':', 0 ) ); if ( target_tracker$.IsValid ) { PostWorldMessage( WE_REQ_DEACTIVATE, owner.Goid, target_tracker$, 0 ); } } //debug$(MakeStringF("RemoveAura$ - removing %s (%d)", target_remove$.Go.TemplateName, MakeInt(target_remove$ ) ) ); } else { StringTool.AppendF( buffer_remove$, " %d", target_match$ ); } index_remove$ -= 1; } if ( found$ ) { targets_remove$ = buffer_remove$; } } index$ -= 1; } } else { //remove all the targets in the search space if ( notify_tracker$ ) { Goid target_remove$, target_tracker$; string pair$; while ( index$ > 0 ) { pair$ = StringTool.GetDelimitedString( targets_search$, index$, ' ', "" ); if ( pair$ != "" ) { target_remove$ = MakeGoid( StringTool.GetDelimitedInt( pair$, 0, ':', 0 ) ); if ( target_remove$.IsValid ) { if ( target_remove$.Go.HasActor() ) { target_remove$.Go.Actor.SRemoveGenericState( owner.Go.Magic.StateName ); } } target_tracker$ = MakeGoid( StringTool.GetDelimitedInt( pair$, 1, ':', 0 ) ); if ( target_tracker$.IsValid ) { PostWorldMessage( WE_REQ_DEACTIVATE, owner.Goid, target_tracker$, 0 ); } } index$ -= 1; } } targets_search$ = ""; } } ToggleSourceEnchantments$() { debug$("toggle SOURCE ENCHANTMENTS"); int index$ = enchant_source$; while ( index$ > 0 ) { parent$.Go.Magic.SApplyEnchantmentsByName( source$, source$, MakeStringF( "%s_%d", parent$.Go.Magic.StateName, index$ ) ); index$ -= 1; } } ToggleTargetEnchantments$() { debug$("toggle TARGET ENCHANTMENTS"); //cycle through all the targets... int index$ = StringTool.GetNumDelimitedValues( targets$, ' ' ) - 1; int source_goid$ = MakeInt( source$ ); Goid tracker$; string pair$; while ( index$ > 0 ) { pair$ = StringTool.GetDelimitedString( targets$, index$, ' ', "" ); if ( pair$ != "" ) { tracker$ = MakeGoid( StringTool.GetDelimitedInt( pair$, 1, ':', 0 ) ); if ( tracker$.IsValid ) { PostWorldMessage( WE_TRIGGER_ACTIVATE, owner.Goid, tracker$, source_goid$, 0 ); } } index$ -= 1; } } ToggleEnchantments$( bool scale_update$ ) { if ( enchant_target$ > 0 ) { if ( scale_update$ && !enchant_target_scales$ ) {} else { //cycle through all the items in the tracker list and notify the trackers to toggle enchantments int index$ = StringTool.GetNumDelimitedValues( targets$, ' ' ) - 1; Goid target$; string pair$; while ( index$ > 0 ) { pair$ = StringTool.GetDelimitedString( targets$, index$, ' ', "" ); if ( pair$ != "" ) { target$ = MakeGoid( StringTool.GetDelimitedInt( pair$, 1, ':', 0 ) ); if ( target$.IsValid ) { int index$ = enchant_target$; while ( index$ > 0 ) { owner.Go.Magic.SApplyEnchantmentsByName( target$, source$, MakeStringF( "%s_%d", owner.Go.Magic.StateName, index$ ) ); index$ -= 1; } } } index$ -= 1; } } } if ( enchant_source$ > 0 ) { if ( scale_update$ && !enchant_source_scales$ ) { return; } //report.screen("//////////////////////////////////APPLYING ENCHANTMENTS"); int index$ = enchant_source$; while ( index$ > 0 ) { parent$.Go.Magic.SApplyEnchantmentsByName( source$, source$, MakeStringF( "%s_%d", parent$.Go.Magic.StateName, index$ ) ); index$ -= 1; } } } event OnEnterState$ { debug$("AuraActive$ - onenterstate$"); if ( source$.IsValid ) { if ( sfx_source$ != "" ) { source_sfx_id$ = SiegeFx.SRunScript( sfx_source$, source$, source$, sfx_source_params$, owner.Goid, WE_REQ_ACTIVATE ); } source_level$ = 0.0; if ( enchant_source$ > 0 ) { ToggleSourceEnchantments$(); } //ToggleEnchantments$( false ); this.CreateTimer( 1, interval$ ); this.SetTimerRepeatCount( 1, -1 ); } } trigger OnTimer$( 1 ) { //report.screenf("check interval"); //check the surroundings if ( source$.IsValid ) { //first, check if the user still meets the aura requirements float level$ = owner.Go.Magic.GetMagicLevel( source$.Go ); if ( level$ < owner.Go.Magic.RequiredCastLevel ) { SetState CheckLevel$; return; } //deduct health if ( costs_health$ ) { //report.screenf("costs health"); float cost$ = owner.Go.Magic.EvaluateCastExperience( source$.Go, source$.Go ); if ( source$.Go.Aspect.CurrentLife < cost$ ) { //Rules.ChangeLife( source$, 0 - cost$ ); SetState CheckLevel$; return; } } //deduct mana if ( costs_mana$ ) { float cost$ = owner.Go.Magic.EvaluateManaCost( source$.Go, source$.Go ); //report.screenf("costs mana: %f (current mana: %f)", cost$, source$.Go.Aspect.CurrentMana); if ( source$.Go.Aspect.CurrentMana < cost$ ) { //report.screenf("not enough mana"); //Rules.ChangeMana( source$, 0 - cost$ ); SetState CheckLevel$; return; } } //toggle enchantments if the enchantments are supposed to scale by #magic //there's a first time for everything... if ( source_level$ > 0.0 ) { source_level$ = Math.Round( source_level$ ); if ( Math.Round( level$ ) != source_level$ ) { bool scale_update$ = false; if ( enchant_source_scales$ ) { scale_update$ = true; ToggleSourceEnchantments$(); } if ( enchant_target_scales$ ) { scale_update$ = true; ToggleTargetEnchantments$(); } if ( scale_update$ ) { this.CreateTimer( 3, 0.1 ); source_level$ = level$; } } } //recompute radius if it is a dynamic value float radius_value$ = ( radius$ <= 0.0 ? owner.Go.Magic.EvaluateAttackDamageModifierMin( source$.Go, source$.Go ) : radius$ ); debug$(MakeStringF("check - radius value %.2f", radius_value$)); //debug$(MakeStringF("check - radius value secondary %.2f", owner.Go.Magic.EvaluateAttackDamageModifierMin( source$.Go, source$.Go ))); //debug$(MakeStringF("check - radius value tertiary %.2f", parent$.Go.Magic.EvaluateAttackDamageModifierMin( source$.Go, source$.Go ))); //initialize search space targets_search$ = targets$; //initialize variables GopColl occupants$ = source$.Go.Mind.TempGopColl1; occupants$.Clear(); if ( AIQuery.GetOccupantsInSphere( source$.Go.Placement.Position, radius_value$, occupants$ ) ) { GopColl potentials$ = source$.Go.Mind.TempGopColl2; potentials$.Clear(); //apply any specified filtering hints bool filtered$; if ( hints$ == 0 ) { filtered$ = true; potentials$ = occupants$; } else if ( hints$ == 1 ) { filtered$ = AIQuery.Get( source$.Go, target_type_hint$, occupants$, potentials$ ); } else { QtColl filter$ = source$.Go.Mind.TempQtColl1; filter$.Clear(); filter$.Add( target_type_hint$ ); filter$.Add( target_type_hint2$ ); if ( hints$ == 3 ) { filter$.Add( target_type_hint3$ ); } filtered$ = AIQuery.Get( source$.Go, filter$, occupants$, potentials$ ); } //analyze the filtered list of aura targets if there are any left if ( filtered$ ) { //iterate through the current potential target list int index$ = potentials$.Size() - 1; string targets_current$ = ""; Go target$; if ( immunity$ == "" ) { while ( index$ >= 0 ) { target$ = potentials$.Get( index$ ); if ( target$ != NULL ) { //debug$(MakeStringF("checking owner.IsCastableOn: %s",(owner.Go.Magic.IsCastableOn( target$ )? "yes,castable": "no,not castable"))); //debug$(MakeStringF("checking parent.IsCastableOn: %s",(parent$.Go.Magic.IsCastableOn( target$ )? "yes,castable": "no,not castable"))); if ( owner.Go.Magic.IsCastableOn( target$ ) ) { //check whether or not this is a new target int target_tracker$ = ApplyAura$( target$.Goid ); if ( target_tracker$ > 0 ) { //debug$(MakeStringF("AuraActive$ - Adding new target to the list: %s (%d)", target$.TemplateName, MakeInt( target$.Goid ) )); //format it into the current target list StringTool.AppendF( targets_current$, " %d:%d", MakeInt( target$.Goid ), target_tracker$ ); } } } index$ -= 1; } } else { while ( index$ >= 0 ) { target$ = potentials$.Get( index$ ); if ( target$ != NULL ) { if ( !target$.Common.Membership.Contains( immunity$ ) ) { if ( owner.Go.Magic.IsCastableOn( target$ ) ) { //check whether or not this is a new target //ApplyAura$ modifies the target search (targets_left$) by eliminating checked targets from the existing list at every pass to optimize the search int target_tracker$ = ApplyAura$( target$.Goid ); if ( target_tracker$ > 0 ) { //debug$(MakeStringF("AuraActive$ - Adding new target to the list: %s (%d)", target$.TemplateName, MakeInt( target$.Goid ) )); //format it into the current target list StringTool.AppendF( targets_current$, " %d:%d", MakeInt( target$.Goid ), target_tracker$ ); } } } } index$ -= 1; } } //debug$(targets_current$); //update the target list targets$ = targets_current$; } else { targets$ = ""; } } else { targets$ = ""; } //the targets not removed during the search are not in range RemoveAura$( "", true ); } else { SetState Inactive$; } } event OnGoHandleMessage$( eWorldEvent e$, WorldMessage msg$ ) { if ( e$ == WE_TRIGGER_DEACTIVATE ) { //the player deactivated the aura SetState Inactive$; } else if ( e$ == WE_REQ_DEACTIVATE ) { //only a tracker should send a deactivation message Goid victim$ = MakeGoid( msg$.Data1 ); if ( victim$.IsValid ) { RemoveAura$( MakeStringF( " %d", msg$.Data1 ), false ); } } } trigger OnTimer$( 3 ) { ToggleEnchantments$( true ); } event OnExitState$ { //stop checking this.DestroyTimer( 1 ); //get rid of source aura effects if ( sfx_source$ != "" ) { SiegeFx.SStopScript( source_sfx_id$ ); } ToggleSourceEnchantments$(); //get rid of all target trackers targets_search$ = targets$; targets$ = ""; RemoveAura$( "", true ); } }