• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Custom script doesn't work

Status
Not open for further replies.
Level 1
Joined
Apr 17, 2019
Messages
3
Hello All,

I tried to make an ability to reflect back any spell that has been cast to a certain unit. Its probably the custom script that fails cause I know almost nothing about Jass. Any help will be appreciated. Thanks in advance.


mirror
Events
Unit - A unit Begins casting an ability
Conditions
(Level of Mirror for (Target unit of ability being cast)) Greater than 0
Actions
Set Ab_Mirror_Target = (Triggering unit)
Set Ab_Mirror_Target_Pos = (Position of Ab_Mirror_Target)
Set Ab_Mirror_Caster = (Target unit of ability being cast)
Set Ab_Mirror_Caster_Pos = (Position of Ab_Mirror_Caster)
Set Ab_Mirror_Target_Spell = (Ability being cast)
Unit - Create 1 dummy for (Owner of Ab_Mirror_Caster) at Ab_Mirror_Caster_Pos facing Default building facing degrees
Unit - Add Ab_Mirror_Target_Spell to (Last created unit)
Unit - For Unit (Last created unit), Set mana cost of ability (Ability being cast), Level: 1 to 0
Custom script: call IssueTargetOrderById(GetLastCreatedUnit(), GetSpellAbilityId(), GetTriggerUnit())
Unit - Add a 1.00 second Generic expiration timer to (Last created unit)
Custom script: call RemoveLocation (udg_Mirror_Caster_Pos)
Custom script: call RemoveLocation (udg_Mirror_Target_Pos)
 
Level 39
Joined
Feb 27, 2007
Messages
5,013
You didn't copy the spell's cast level, if that matters to you. And instead of using Begins Casting you should use the Starts the Effect event, since Begins Casting is fired before cooldown/mana cost happen and the spell can still be cancelled by spamming Stop. Currently you're not even using Target_Pos so there's no need to set it.

You are correct that the problem is in the IssueTargetOrderById() call; spell rawcodes (what GetSpellAbilityId() returns) are not orders you can issue. Orders are themselves integers (with string equivalents for many), yes, but they're not the same as a spell's rawcode. If you make a test trigger to print them both you can see that. In order to properly order the dummy to cast the spell, you will need to use something like LastOrder to retrieve the order the casting unit was actually given to cast the spell. At the moment the spell is successfully cast it should be the 1st last order, though it's possible it might be the 2nd last if the player has queued something to happen immediately after the spell, IDK. With LastOrder implemented in your map (and the changes I suggested above) the trigger should look like this:

  • mirror
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Level of Mirror for (Target unit of ability being cast)) Greater than 0
    • Actions
      • Set Ab_Mirror_Target = (Triggering unit)
      • Set Ab_Mirror_Caster = (Target unit of ability being cast)
      • Set Ab_Mirror_Caster_Pos = (Position of Ab_Mirror_Caster)
      • Set Ab_Mirror_Target_Spell = (Ability being cast)
      • Unit - Create 1 dummy for (Owner of Ab_Mirror_Caster) at Ab_Mirror_Caster_Pos facing Default building facing degrees
      • Set Ab_Mirror_Dummy = (Last created unit)
      • Unit - Add a 1.00 second Generic expiration timer to Ab_Mirror_Dummy
      • Unit - Add Ab_Mirror_Target_Spell to Ab_Mirror_Dummy
      • Unit - Set level of Ab_Mirror_Target_Spell for Ab_Mirror_Dummy to (Level of Ab_Mirror_Target_Spell for Ab_Mirror_Target)
      • Unit - For Unit Ab_Mirror_Dummy, Set mana cost of ability (Ability being cast), Level: (Level of Ab_Mirror_Target_Spell for Ab_Mirror_Target) to 0
      • Custom script: call IssueTargetOrderById(udg_Ab_Mirror_Dummy, GetLastOrderId(udg_Ab_Mirror_Target), udg_Ab_Mirror_Target)
      • Custom script: call RemoveLocation (udg_Mirror_Caster_Pos)
 
Level 1
Joined
Apr 17, 2019
Messages
3
Hi Pyrogasm

Thanks a lot for the detailed explanation. I appreciate it. Your code makes much more sense and I made all the necessary changes. I couldn't register to the wc3c.net to download LastOrder but I will google it further. Thanks again.
 
Level 39
Joined
Feb 27, 2007
Messages
5,013
It's just the code in the post, there's no need to download the demo map. Put this into a trigger in your map:
JASS:
library LastOrder initializer Init
//******************************************************************************
//* BY: Rising_Dusk
//*
//* LastOrder is a library that was designed to allow interfacing with the last
//* N orders any unit on your map has received. This library was also designed
//* to be used as a means to reissue lost orders to a unit either after
//* preventing a spell cast or in many other situations.
//*
//* There are two configuration constants for you to play with in using this
//* script. ORDERS_TO_HOLD is basically the size of the game's memory for each
//* individual unit. The larger the number is, the further back you can retrace
//* a unit's order list. Setting this value to 3 suffices for all actual
//* mechanics purposes. Raise it only if you have a specific application where
//* you need more. Lowering it to 2 covers most cases, but may miss a few, and
//* lowering it to 1 prevents you from adequately canceling spells and properly
//* reissuing previous orders. I recommend leaving it alone at 3. The MAX_ORDERS
//* constant is the number of orders that the system holds over all units at a
//* given time. If you are worried about running out, go ahead and increase it,
//* but be aware that it will use big arrays (which are slower) if you use a
//* number over 8191. Don't lower it under 8191, there's no reason to. Don't
//* change the non-private constants, there's no reason to.
//*
//*     function GetPastOrder takes unit u, integer whichOrder returns order
//*     function GetPastOrderId takes unit u, integer whichOrder returns integer
//*     function GetPastOrderString takes unit u, integer whichOrder returns string
//*     function GetPastOrderType takes unit u, integer whichOrder returns integer
//*     function GetPastOrderX takes unit u, integer whichOrder returns real
//*     function GetPastOrderY takes unit u, integer whichOrder returns real
//*     function GetPastOrderTarget takes unit u, integer whichOrder returns widget
//*
//* The above API is the main list of functions the user can use to interface
//* with past orders. If you want the immediate last order for a player, use 1
//* for the whichOrder integer. The 'order' returned by GetPastOrder is a struct
//* that contains all information of an issued order. If you want to interface
//* directly with the struct and skip all of the interfacing wrappers, you have
//* access to them via the following commands:
//*
//*     [unit]     .u       The unit being ordered.
//*     [integer]  .id      The order id of the past order.
//*     [integer]  .typ     The type of the past order. (See constants)
//*     [boolean]  .fin     A flag indicating whether the order was finished.
//*     [widget]   .tar     The target widget of the past order.
//*     [real]     .x       The x coordinate of the target point of the order.
//*     [real]     .y       The y coordinate of the target point of the order.
//*
//* There is also a sizable API for backwards compatibility with older versions
//* of the library. This API assumes that you're talking about a specific past
//* order, either the lastmost order or the second lastmost. In most cases,
//* these are still useful because they remove an argument from the call.
//*
//*     function GetLastOrder takes unit u returns order
//*     function GetLastOrderId takes unit u returns integer
//*     function GetLastOrderString takes unit u returns string
//*     function GetLastOrderType takes unit u returns integer
//*     function GetLastOrderX takes unit u returns real
//*     function GetLastOrderY takes unit u returns real
//*     function GetLastOrderTarget takes unit u returns widget
//*     function IsLastOrderFinished takes unit u returns boolean
//*
//* Besides being able to get information about all of the past orders a unit
//* has been issued, the most useful part of this system is actually reissuing
//* those orders as a means to fix lost orders or intercept and prevent spell
//* casting. The following API is then available to the user:
//*
//*     function IssuePastOrder takes unit u, integer whichOrder returns boolean
//*     function IssueLastOrder takes unit u returns boolean
//*     function IssueSecondLastOrder takes unit u returns boolean
//*     function AbortOrder takes unit u returns boolean
//*
//* If you want to reissue a past order for a given unit, IssuePastOrder is the
//* function that you'll want to use on a unit. To issue the last or second last
//* orders, there are functions for those as well. (They mostly exist for
//* backwards compatibility) AbortOrder is a means for anyone using this script
//* to stop a unit from running any given order that they happen to have at any
//* moment. AbortOrder is used in conjunction with a supplementary library,
//* AbortSpell, to seamlessly replicate WC3's spell error messages.
//*
//*     function IssueArbitraryOrder takes unit u, order o returns boolean
//*
//* IssueArbitraryOrder is special in that it ignores many of the normal checks
//* and automatically forces a unit to take on a specific order. This can be
//* convenient if you want to make one unit take on another unit's order, for
//* instance. Be forewarned that this overwrites whatever the unit was doing and
//* issues the order no matter what, so use only as needed.
//*
//* If you have any further questions regarding LastOrder or how to use it, feel
//* free to visit [url]www.wc3c.net[/url] and ask questions there. This library should only
//* ever be released at WC3C and at no other site. Please give credits if this
//* library finds its way into your maps, and otherwise thanks for reading!
//*
//* Enjoy!
//*
globals   
   //Order type variables
           constant integer ORDER_TYPE_TARGET    = 1
           constant integer ORDER_TYPE_POINT     = 2
           constant integer ORDER_TYPE_IMMEDIATE = 3
   
   //System constants
   private constant integer ORDERS_TO_HOLD       = 3    //How many past orders the
                                                        //system holds for each unit.
                                                        //Should be at least 2.
   private constant integer MAX_ORDERS           = 8191 //The max number of orders
                                                        //the system can maintain.
                                                        //Going over 8191 uses big
                                                        //arrays, which are slower
                                                        //than normal arrays.
endglobals

globals
   private hashtable ht = InitHashtable()
endglobals

struct order[MAX_ORDERS]
   unit    u
   integer id
   integer typ
   boolean fin
   widget  tar
   real    x
   real    y
   static method create takes unit ordered, integer ordid, integer ordtyp, widget target, real ordx, real ordy returns order
       local order   o   = order.allocate()
       local integer i   = ORDERS_TO_HOLD
       local integer hid = GetHandleId(ordered)
       
       set o.u   = ordered
       set o.id  = ordid
       set o.typ = ordtyp
       set o.fin = false
       set o.tar = target
       set o.x   = ordx
       set o.y   = ordy
       //Handle stored orders in the hashtable
       loop
           //We hold up to the constant ORDERS_TO_HOLD in the table
           exitwhen i == 1
           //Moves the N-1th order to the Nth slot, etc. except storing new last order
           if HaveSavedInteger(ht, hid, i-1) then
               if i == ORDERS_TO_HOLD and HaveSavedInteger(ht, hid, i) then
                   //Destroy lastmost order struct
                   call order.destroy(order(LoadInteger(ht, hid, i)))
               endif
               //Can only do this if the N-1th order exists
               call SaveInteger(ht, hid, i, LoadInteger(ht, hid, i-1))
           endif
           set i = i - 1
       endloop
       //Store the new order to the hashtable as 1th last order
       call SaveInteger(ht, hid, 1, integer(o))
       return o
   endmethod
endstruct

//******************************************************************************

//! textmacro LastOrderDebug takes ORDER, RETURN
   debug if $ORDER$ > ORDERS_TO_HOLD then
   debug     call BJDebugMsg(SCOPE_PREFIX+"Error: Order out of range")
   debug     return $RETURN$
   debug endif
   debug if not HaveSavedInteger(ht, GetHandleId(u), $ORDER$) then
   debug     call BJDebugMsg(SCOPE_PREFIX+"Error: The "+I2S($ORDER$)+"th order doesn't exist")
   debug     return $RETURN$
   debug endif
//! endtextmacro

function GetPastOrder takes unit u, integer whichOrder returns order
   //! runtextmacro LastOrderDebug("whichOrder", "0")
   return order(LoadInteger(ht, GetHandleId(u), whichOrder))
endfunction
function GetPastOrderId takes unit u, integer whichOrder returns integer
   //! runtextmacro LastOrderDebug("whichOrder", "0")
   return order(LoadInteger(ht, GetHandleId(u), whichOrder)).id
endfunction
function GetPastOrderString takes unit u, integer whichOrder returns string
   //! runtextmacro LastOrderDebug("whichOrder", "\"\"")
   return OrderId2String(order(LoadInteger(ht, GetHandleId(u), whichOrder)).id)
endfunction
function GetPastOrderType takes unit u, integer whichOrder returns integer
   //! runtextmacro LastOrderDebug("whichOrder", "0")
   return order(LoadInteger(ht, GetHandleId(u), whichOrder)).typ
endfunction
function GetPastOrderX takes unit u, integer whichOrder returns real
   //! runtextmacro LastOrderDebug("whichOrder", "0.")
   return order(LoadInteger(ht, GetHandleId(u), whichOrder)).x
endfunction
function GetPastOrderY takes unit u, integer whichOrder returns real
   //! runtextmacro LastOrderDebug("whichOrder", "0.")
   return order(LoadInteger(ht, GetHandleId(u), whichOrder)).y
endfunction
function GetPastOrderTarget takes unit u, integer whichOrder returns widget
   //! runtextmacro LastOrderDebug("whichOrder", "null")
   return order(LoadInteger(ht, GetHandleId(u), whichOrder)).tar
endfunction

//******************************************************************************

function GetLastOrder takes unit u returns order
   return GetPastOrder(u, 1)
endfunction
function GetLastOrderId takes unit u returns integer
   return GetPastOrderId(u, 1)
endfunction
function GetLastOrderString takes unit u returns string
   return GetPastOrderString(u, 1)
endfunction
function GetLastOrderType takes unit u returns integer
   return GetPastOrderType(u, 1)
endfunction
function GetLastOrderX takes unit u returns real
   return GetPastOrderX(u, 1)
endfunction
function GetLastOrderY takes unit u returns real
   return GetPastOrderY(u, 1)
endfunction
function GetLastOrderTarget takes unit u returns widget
   return GetPastOrderTarget(u, 1)
endfunction
function IsLastOrderFinished takes unit u returns boolean
   //! runtextmacro LastOrderDebug("1", "false")
   return GetUnitCurrentOrder(u) == 0 or order(LoadInteger(ht, GetHandleId(u), 1)).fin
endfunction

//******************************************************************************

private function OrderFilter takes unit u, integer id returns boolean
   //* Excludes specific orders or unit types from registering with the system
   //*
   //* 851972: stop
   //*         Stop is excluded from the system because it is the order that
   //*         tells a unit to do nothing. It should be ignored by the system.
   //*
   //* 851971: smart
   //* 851986: move
   //* 851983: attack
   //* 851984: attackground
   //* 851990: patrol
   //* 851993: holdposition
   //*         These are the UI orders that are passed to the system.
   //*
   //* 851973: stunned
   //*         This order is issued when a unit is stunned onto the stunner
   //*         It's ignored by the system, since you'd never want to reissue it
   //*
   //* >= 852055, <= 852762
   //*         These are all spell IDs from defend to incineratearrowoff with
   //*         a bit of leeway at the ends for orders with no strings.
   //*
   return id == 851971 or id == 851986 or id == 851983 or id == 851984 or id == 851990 or id == 851993 or (id >= 852055 and id <= 852762)
endfunction

private function IssuePastOrderFilter takes unit u, integer whichOrder returns boolean
   //* Some criteria for whether or not a unit's last order should be given
   //*
   //* INSTANT type orders are excluded because generally, reissuing an instant
   //* order doesn't make sense. You can remove that check below if you'd like,
   //* though.
   //*
   //* The Type check is really just to ensure that no spell recursion can
   //* occur with IssueLastOrder. The problem with intercepting the spell cast
   //* event is that it happens after the order is 'caught' and registered to
   //* this system. Therefore, to just IssueLastOrder tells it to recast the
   //* spell! That's a problem, so we need a method to eliminate it.
   //*
   return GetUnitTypeId(u) != 0 and not IsUnitType(u, UNIT_TYPE_DEAD) and GetPastOrderType(u, whichOrder) != 0 and GetPastOrderType(u, whichOrder) != ORDER_TYPE_IMMEDIATE
endfunction

//******************************************************************************

function IssuePastOrder takes unit u, integer whichOrder returns boolean
   local order o = GetPastOrder(u, whichOrder)
   if IssuePastOrderFilter(u, whichOrder) and not o.fin then
       if o.typ == ORDER_TYPE_TARGET then
           return IssueTargetOrderById(u, o.id, o.tar)
       elseif o.typ == ORDER_TYPE_POINT then
           if o.id == 851971 then
               //This adjusts for a bug in the point order's boolean return
               //when issuing a smart order
               call IssuePointOrderById(u, o.id, o.x, o.y)
               return true
           else
               return IssuePointOrderById(u, o.id, o.x, o.y)
           endif
       elseif o.typ == ORDER_TYPE_IMMEDIATE then
           return IssueImmediateOrderById(u, o.id)
       endif
   endif
   return false
endfunction

function IssueLastOrder takes unit u returns boolean
   return IssuePastOrder(u, 1)
endfunction

function IssueSecondLastOrder takes unit u returns boolean
   return IssuePastOrder(u, 2)
endfunction

function IssueArbitraryOrder takes unit u, order o returns boolean
   if o.typ == ORDER_TYPE_TARGET then
       return IssueTargetOrderById(u, o.id, o.tar)
   elseif o.typ == ORDER_TYPE_POINT then
       if o.id == 851971 then
           //This adjusts for a bug in the point order's boolean return
           //when issuing a smart order
           call IssuePointOrderById(u, o.id, o.x, o.y)
           return true
       else
           return IssuePointOrderById(u, o.id, o.x, o.y)
       endif
   elseif o.typ == ORDER_TYPE_IMMEDIATE then
       return IssueImmediateOrderById(u, o.id)
   endif
   return false
endfunction

function AbortOrder takes unit u returns boolean
   if IsUnitPaused(u) then
       return false
   else
       call PauseUnit(u, true)
       call IssueImmediateOrder(u, "stop")
       call PauseUnit(u, false)
   endif
   return true
endfunction

//**********************************************************

private function Conditions takes nothing returns boolean
   return OrderFilter(GetTriggerUnit(), GetIssuedOrderId())
endfunction

private function Actions takes nothing returns nothing
   local unit    u   = GetTriggerUnit()
   local widget  t   = GetOrderTarget()
   local integer oid = GetIssuedOrderId()
   local integer oty = 0
   
   if GetTriggerEventId() == EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER then
       call order.create(u, oid, ORDER_TYPE_TARGET, t, GetWidgetX(t), GetWidgetY(t))
   elseif GetTriggerEventId() == EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER then
       call order.create(u, oid, ORDER_TYPE_POINT, null, GetOrderPointX(), GetOrderPointY())
   elseif GetTriggerEventId() == EVENT_PLAYER_UNIT_ISSUED_ORDER then
       call order.create(u, oid, ORDER_TYPE_IMMEDIATE, null, GetUnitX(u), GetUnitY(u))
   debug else
       debug call BJDebugMsg(SCOPE_PREFIX+"Error: Invalid order type")
   endif
   
   set u = null
   set t = null
endfunction

//**********************************************************

private function SpellActions takes nothing returns nothing
   local order o = GetPastOrder(GetTriggerUnit(), 1)
   set o.fin = true
endfunction

//**********************************************************

private function OnAdd takes nothing returns boolean
   local integer hid = GetHandleId(GetFilterUnit())
   local integer i   = ORDERS_TO_HOLD
   
   //Handle stored orders in the hashtable
   loop
       //We hold up to the constant ORDERS_TO_HOLD in the table
       exitwhen i == 0
       //If any of the N orders exist for this handle id, kill them all
       if HaveSavedInteger(ht, hid, i) then
           call order.destroy(order(LoadInteger(ht, hid, i)))
           call RemoveSavedInteger(ht, hid, i)
       endif
       set i = i - 1
   endloop
   
   return false
endfunction

//**********************************************************

private function Init takes nothing returns nothing
   local trigger trg = CreateTrigger()
   local region  re  = CreateRegion()
   local rect    m   = GetWorldBounds()
   
   //Main order catching trigger
   call TriggerAddAction(trg, function Actions)
   call TriggerAddCondition(trg, Condition(function Conditions))
   call TriggerRegisterAnyUnitEventBJ(trg, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
   call TriggerRegisterAnyUnitEventBJ(trg, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
   call TriggerRegisterAnyUnitEventBJ(trg, EVENT_PLAYER_UNIT_ISSUED_ORDER)
   
   //Spell trigger to set a flag that indicates a spell order's completion
   set trg = CreateTrigger()
   call TriggerAddAction(trg, function SpellActions)
   call TriggerRegisterAnyUnitEventBJ(trg, EVENT_PLAYER_UNIT_SPELL_EFFECT)
   
   //Entering world trigger that clears old data from handle ids
   set trg = CreateTrigger()
   call RegionAddRect(re, m)
   call TriggerRegisterEnterRegion(trg, re, Condition(function OnAdd))
   call RemoveRect(m)
   
   set trg = null
   set re  = null
   set m   = null
endfunction
endlibrary
 
Level 1
Joined
Apr 17, 2019
Messages
3
I tried that but it gives syntax errors. I'm using Bribe's damage engine. Maybe there is a conflict between them?
 
Status
Not open for further replies.
Top