| 05-11-2009, 05:36 AM | #1 |
This script and OE data mimics the Starcraft Reaver style of unit - one that has to build things to attack. Plus a little extra, of course. Calibration can take a really long time, due to the number of variables involved, but other than that it's pretty easy to use, I think. Any suggestions on how to streamline this or make it more portable (do I use too many weird systems?) would be much appreciated. Edit: To clarify, a "Reaver" is a unit that builds "Scarabs," which are automatically loaded into the unit. The Reaver uses the Scarabs to attack, so each time the Reaver attacks it loses a Scarab. To be able to keep attacking, the Reaver must continually be building Scarabs. Note: There are still a number of things that are not easy to calibrate, mostly because there's far too many global thingies as is. Note 2: Even though I'm posting the code, I recommend that you download and run the test map to see how it actually works, because a lot of what's involved relies on object data. Without further ado, here's the spell. Reaver:// ************************************************************* // * Reaver -- Version 1.20 // * by cosmicat // ************************************************************* // * // * CREDITS: // * - Alevice (Suggested alternative methods for scarab construction) // * - Anitarf (Suggested use of the "Charge Gold and Lumber" ability) // * - Archmage Owenalacaster (Extraordinary insight and guidance in fine-tuning the code) // * - Deaod (TTBars, and I'm ripping this credits template off of his) // * - Kyrbi0 (Suggested more versatile method for scarab usage) // * - Pipedream (Grimoire) // * - PitzerMike (JassNewGenPack) // * - Szythe (Found a bug) // * - Vexorian (JassHelper, ARGB, Table, TimerUtils, SimError) // * // * REQUIREMENTS: // * - JassHelper v0.9.E.0 (it's likely that other versions work, but this was the one I used) // * - SimError [url]http://www.wc3c.net/showthread.php?t=101260[/url] // * - Table [url]http://www.wc3c.net/showthread.php?t=101246[/url] // * - TimerUtils [url]http://www.wc3c.net/showthread.php?t=101322[/url] // * - ARGB [url]http://www.wc3c.net/showthread.php?t=101858[/url] // * - TTBars [url]http://www.hiveworkshop.com/forums/resource.php?t=121269[/url] // * // * HOW TO USE: // * * Create the following in the object editor: // * - A dummy unit to fill the reaver with // * !! This unit MUST be a Ground unit !! // * - A dummy caster to use for the "scarab" effect // * - Some kind of ability for the dummy caster to use // * - An ability based on Charge Gold and Lumber, used to "build" scarabs. // * - An ability based on Cargo Hold (Goblin Zeppelin). It MUST be this one. Make sure it's allowed to hold your Scarab units and nothing else. // * - An ability based on Battle Stations (Orc Burrow). Make sure it's allowed to hold your Scarab units and nothing else. // * - A spellbook containing your Cargo Hold and Battle Stations abilities, along with whatever other invisible passives you want to give your Reaver. // * - A reaver-type unit, with an enabled attack, the spellbook, the "build scarab" ability, and whatever else you want to put on it. // * // * * Calibrate the constant globals in the Setup section - these should be self-explanatory but I'll go over them anyway, line by line. // * // * CHANGES: // * * 1.00 (5/10/09) // * - Created the "spell." // * * 1.10 (5/11/09) // * - Added NO_DUMPING feature to (optionally) replace any scarabs a player tries to "unload." // * - Fixed bugs with SINGLE_FILE and resource costs. // * - Fixed note on scarabs: Locust is okay, but the unit must travel on "Foot" or "Horse." // * - Cleaned up a few leaks I'd missed the first few times around. // * * 1.20 (5/15/09) // * - Changed SINGLE_FILE to MULTI_BUILD, since that seems to make more sense. // * - Scrapped triggered gold/lumber costs in favor of a much better ability (thanks Anitarf!) // * - Fixed one "leak". // * - Scarabs now enter reavers even if they are hidden (if needed, I can change this to pause their build timers when hidden instead - later). // * - Changed the way the reaver's attack works! // * - Removed lingering references to "fireworks" and "pandaren" (ho hum). // * - Credited all those nice people who gave helpful feedback. // * // ************************************************************* scope Reaver initializer Init //=========================================================================== // SETUP //=========================================================================== globals // The object ID of the spellbook you use to hold the reaver's HIDDEN passives. private constant integer REAVER_BOOK = 'A00C' // The object ID of the active ability used to create scarabs. private constant integer SCARAB_BUILD = 'Asca' // The object ID of the dummy unit used to fill the reaver's cargo hold. private constant integer SCARAB_FILLER = 'pdm2' // The object ID of the dummy unit used to cast the reaver's attack effect. private constant integer SCARAB_CASTER = 'pdm1' // The object ID of the active ability the dummy caster should be casting. private constant integer SCARAB_SPELL = 'A003' // The order string for the active ability used by the dummy caster. private constant string SCARAB_ORDER = "selfdestruct" // The amount of time it should take to build a scarab. private constant real BUILD_TIME = 4. // The attack cooldown for the reaver. private constant real ATTK_COOL = 4. // The maximum number of scarabs that can be trained at one time (note: if multi-build is disabled, this does nothing) private constant integer MAX_TRAINING = 5 // The cargo hold capacity of the reaver. private constant integer MAX_SCARABS = 10 // Whether or not the reaver should be able to build multiple scarabs at the same time. private constant boolean MULTI_BUILD = false // Whether or not the reaver should be prevented from "wasting" scarabs by unloading them. private constant boolean NO_DUMPING = true // The error message to display when the player tries to queue more scarabs than can be held. private constant string TOO_MANY_SCARABS_MSG = "Already carrying the maximum number of scarabs." // The error message to display when the player tries to train/queue too many scarabs at once. private constant string TOO_MANY_TRAINING_MSG = "Can only train 5 scarabs at one time." endglobals private function ScarabCast takes unit caster, widget target returns nothing // Any interaction between scarab and target unit can go here. call IssueTargetOrder(caster, SCARAB_ORDER, target) endfunction private function CasterDeath takes unit caster returns nothing // Any effects you want to add on caster death may go here. call ShowUnit(caster, false) // Hides the caster from view so no corpse is left. call KillUnit(caster) // Kills the caster, removing it from the game. endfunction //=========================================================================== // STRUCTS 'N' STUFF //=========================================================================== private struct buildtimer TTBar bar unit u timer progress timer finish static method create takes TTBar bar, unit u returns buildtimer local buildtimer d = buildtimer.allocate() set d.bar = bar set d.u = u set d.progress = NewTimer() set d.finish = NewTimer() call SetTimerData(d.progress, d) call SetTimerData(d.finish, d) return d endmethod method onDestroy takes nothing returns nothing call ReleaseTimer(this.progress) call ReleaseTimer(this.finish) call this.bar.destroy() endmethod endstruct globals private HandleTable scarabTable private constant integer SMART = 851971 private constant integer STOP = 851972 private trigger NoUnload endglobals private struct scarabs group g unit reaver private integer numUnits private integer iterator private integer numTraining private integer trainingQueue private timer attack private boolean canAttack static method enable takes nothing returns nothing local scarabs me = GetTimerData(GetExpiredTimer()) set me.canAttack = true endmethod static method create takes unit reaver returns scarabs local scarabs gw = scarabs.allocate() set gw.numUnits = 0 set gw.iterator = 0 set gw.numTraining = 0 set gw.trainingQueue = 0 set gw.g = CreateGroup() set gw.reaver = reaver set scarabTable[reaver] = gw set gw.attack = NewTimer() call SetTimerData(gw.attack, gw) call TimerStart(gw.attack, ATTK_COOL, false, function scarabs.enable) return gw endmethod private method onDestroy takes nothing returns nothing local integer index = this.numUnits local unit s call scarabTable.flush(this.reaver) loop exitwhen index <= 0 set s = FirstOfGroup(this.g) call ReleaseTimer(GetExpiredTimer()) call scarabTable.flush(s) call ShowUnit(s, false) call KillUnit(s) call GroupRemoveUnit(this.g, s) set index = index - 1 endloop call DestroyGroup(this.g) call ReleaseTimer(this.attack) set s = null endmethod private static method scarabProgress takes nothing returns nothing local buildtimer d = GetTimerData(GetExpiredTimer()) set d.bar.Value = d.bar.Value + 1 endmethod private static method timedAdd takes nothing returns nothing local buildtimer d = GetTimerData(GetExpiredTimer()) local unit newScarab = CreateUnit(GetOwningPlayer(d.u),SCARAB_FILLER,GetUnitX(d.u),GetUnitY(d.u),bj_UNIT_FACING) local scarabs me = scarabTable[d.u] call GroupAddUnit(me.g, newScarab) set scarabTable[newScarab] = me if IsUnitHidden(d.u) then call ShowUnit(d.u,true) call IssueTargetOrderById(newScarab,SMART,d.u) call ShowUnit(d.u,false) else call IssueTargetOrderById(newScarab,SMART,d.u) endif set newScarab = null set me.numUnits = me.numUnits + 1 set me.numTraining = me.numTraining - 1 if not MULTI_BUILD and scarabs(scarabTable[d.u]).trainingQueue > 0 then set me.trainingQueue = me.trainingQueue - 1 call me.addScarab(true) endif call d.destroy() endmethod method addScarab takes boolean queued returns nothing local TTBar progbar local buildtimer d local player owner = GetOwningPlayer(this.reaver) if (this.numUnits + this.numTraining + this.trainingQueue) >= MAX_SCARABS then call SimError(owner,TOO_MANY_SCARABS_MSG) elseif MAX_TRAINING != 0 and this.numTraining >= MAX_TRAINING then call SimError(owner,TOO_MANY_TRAINING_MSG) elseif this.numTraining > 0 and not MULTI_BUILD then set this.trainingQueue = this.trainingQueue + 1 else set progbar = TTBar.create("|", 100, 5, 0, 0, 0) set d = buildtimer.create(progbar, this.reaver) call progbar.LockToUnit(d.u, -96, 48, 90 + (30 * this.iterator)) call progbar.SetBackground(0xFF000055) call progbar.SetForeground(0xFFFFFF00) set this.numTraining = this.numTraining + 1 call TimerStart(d.finish, BUILD_TIME, false, function scarabs.timedAdd) call TimerStart(d.progress, BUILD_TIME * .01, true, function scarabs.scarabProgress) if MULTI_BUILD then set this.iterator = this.iterator + 1 if this.iterator >= MAX_TRAINING then set this.iterator = 0 endif endif endif set owner = null endmethod method scarabAttack takes widget target returns nothing local unit s = FirstOfGroup(this.g) local unit c if this.canAttack then set this.canAttack = false call TimerStart(this.attack,ATTK_COOL,false,function scarabs.enable) call ScarabCast( CreateUnit(GetOwningPlayer(s),SCARAB_CASTER,GetUnitX(this.reaver),GetUnitY(this.reaver),bj_UNIT_FACING), target ) call ShowUnit(s, false) call KillUnit(s) call GroupRemoveUnit(this.g, s) set this.numUnits = this.numUnits - 1 endif set c = null set s = null endmethod static method lostScarab takes unit s returns nothing local scarabs me = scarabTable[s] if NO_DUMPING then call IssueTargetOrderById(s,SMART,me.reaver) else call ShowUnit(s, false) call KillUnit(s) call GroupRemoveUnit(me.g, s) set me.numUnits = me.numUnits - 1 endif endmethod endstruct //=========================================================================== // LOAD SCARABS //=========================================================================== private function Load takes nothing returns boolean if GetSpellAbilityId() == SCARAB_BUILD then call scarabs(scarabTable[GetSpellAbilityUnit()]).addScarab(false) endif return true endfunction //=========================================================================== // REMOVE SCARABS //=========================================================================== private function Remove takes nothing returns boolean if GetUnitAbilityLevel(GetAttacker(),SCARAB_BUILD) > 0 then call scarabs(scarabTable[GetAttacker()]).scarabAttack(GetTriggerUnit()) endif return true endfunction //=========================================================================== // CREATE REAVER DATA //=========================================================================== private function Setup takes nothing returns nothing if GetUnitAbilityLevel(GetEnumUnit(),SCARAB_BUILD) > 0 then call scarabs.create(GetEnumUnit()) endif endfunction private function MakeReaver takes nothing returns boolean call scarabs.create(GetEnteringUnit()) return true endfunction //=========================================================================== // DESTROY REAVER DATA //=========================================================================== private function DyingReaver takes nothing returns boolean if GetUnitAbilityLevel(GetDyingUnit(),SCARAB_BUILD) > 0 then call scarabs(scarabTable[GetDyingUnit()]).destroy() endif return true endfunction private function DyingScarab takes nothing returns boolean if GetUnitTypeId(GetDyingUnit()) == SCARAB_FILLER then call scarabTable.flush(GetDyingUnit()) endif return true endfunction private function KillCaster takes nothing returns boolean if GetUnitTypeId(GetSpellAbilityUnit()) == SCARAB_CASTER then call CasterDeath(GetSpellAbilityUnit()) endif return true endfunction //=========================================================================== // NO UNLOADING! //=========================================================================== private function Unloaded takes nothing returns boolean if GetUnitTypeId(GetOrderedUnit()) == SCARAB_FILLER and GetIssuedOrderId() == STOP then call scarabs.lostScarab(GetOrderedUnit()) endif return true endfunction //=========================================================================== private function IsUnitReaver takes nothing returns boolean return GetUnitAbilityLevel(GetFilterUnit(),SCARAB_BUILD) > 0 endfunction private function DisableBook takes nothing returns nothing call SetPlayerAbilityAvailable(GetEnumPlayer(), REAVER_BOOK, false) endfunction private function Init takes nothing returns nothing local region map = CreateRegion() local group allUnits = CreateGroup() local trigger t = CreateTrigger() set scarabTable = HandleTable.create() call RegionAddRect(map, bj_mapInitialPlayableArea) call GroupEnumUnitsInRect(allUnits, bj_mapInitialPlayableArea,Filter(function IsUnitReaver)) call ForGroup(allUnits, function Setup) call ForForce(GetPlayersAll(), function DisableBook) set t = CreateTrigger( ) call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT ) call TriggerAddCondition(t, Condition(function Load)) set t = CreateTrigger( ) call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_ATTACKED ) call TriggerAddCondition( t, Condition(function Remove) ) set t = CreateTrigger( ) call TriggerRegisterEnterRegion( t, map, Filter(function IsUnitReaver) ) call TriggerAddCondition( t, Condition(function MakeReaver) ) set t = CreateTrigger( ) call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_DEATH ) call TriggerAddCondition( t, Condition(function DyingReaver) ) call TriggerAddCondition( t, Condition(function DyingScarab) ) set t = CreateTrigger( ) call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_ENDCAST ) call TriggerAddCondition( t, Condition(function KillCaster) ) set NoUnload = CreateTrigger( ) call TriggerRegisterAnyUnitEventBJ( NoUnload, EVENT_PLAYER_UNIT_ISSUED_ORDER ) call TriggerAddCondition( NoUnload, Condition(function Unloaded) ) call RemoveRegion(map) call DestroyGroup(allUnits) set t = null set map = null set allUnits = null endfunction endscope Edit: Odd. It seems vBulletin wants to apply URL tags to stuff inside Jass tags. Edit: Updated with a few bug fixes and some useful features. |
| 05-14-2009, 07:44 AM | #2 |
I think most of your global booleans can be moved below the configurable block, or at least in a sub-block in the configuration globals. That is, I can't imagine why a person would want to permit multi-building of scarabs. But I guess the option should be available. By the way, have you tested scarab multi-building? You could replace the region events with either unit summon & death events. Or the former may be unnecessary; the enter region event would be unnecessary if the unit is created by script, because then it could be handled by the spell effect event trigger. Some of your globals could be included in the structs, I think, as private static members. |
| 05-14-2009, 09:34 AM | #3 |
Couldn't you base the build scarab ability on charge gold&lumber or something? Why the triggered gold and lumber costs? |
| 05-14-2009, 06:45 PM | #4 |
Holy cow. <3 |
| 05-14-2009, 07:30 PM | #5 |
nice thing. but you should improve one thing: instead of using an ability, to build new rockets make it sell a unit. this way it would use the real game error, when you don´t have enough gold/lumber. |
| 05-14-2009, 08:10 PM | #6 |
No, Hans, it wouldn't display production due to the cargo hold display, would give the rally point ability to the unit, and it would disable abilities while in production. EDIT: Ah, you mean selling instead of training. Sale is instant and scarabs are meant to have a production time. EDIT 2: Levi convinced me selling a scarab unit, hiding & pausing it, running the progress display bar, then unhiding/pausing it may be viable. |
| 05-14-2009, 08:28 PM | #7 |
If a unit dies while it's making a "scarab", the process is not stopped and the unit continues to make and store scarabs. |
| 05-14-2009, 09:12 PM | #8 | |
Quote:
You could even recycle them in case the reaver dies! |
| 05-14-2009, 10:39 PM | #9 |
What I meant, was just another way of registering an event, when the player wants to build new rockets. The unit at sale is just a dummy, that gets removed instantly. It would work the exact same way as now, just with 'casting unit' replaced by 'selling unit' and 'unit casts spell event' with 'unit is sold event' in the trigger. |
| 05-15-2009, 12:39 AM | #10 | ||
Wow. I've always loved this idea (heck, all Starcraft -> Wc3 ideas ^_^), so it's good to see this working. No less in something that makes so much sense... Anyway, just to clarify: Quote:
Quote:
|
| 05-15-2009, 02:29 AM | #11 |
Ah, good point. That gives the player more options where graphics are concerned as well, since a trail effect can be triggered for such an "attack type." Edit: It also removes the need for at least one timer (scarab killing), since the reaver doesn't need to complete its attack for a scarab to launch. Edit2: It also allows me to have more fun with Scarab capabilities; since I've been trained (in a very Pavlovian sense) to use global constants wherever practical, it's certainly possible to have the Scarabs cast a *spell* effect (with an arc) instead of exploding. Once a scarab has finished casting (hopefully near-instantaneous assuming no backswing or cast time), it can die and be removed from the group. Yay! |
| 05-15-2009, 10:06 PM | #12 |
Significant update. |
