I'm working on a defense-type map where waves of Zerg will continually spawn and attack. Each wave has a score value based on the strength of the composite units and the frequency with which the wave will spawn and the game is given a budget with which to pick a wave to start spawning. Once a wave starts spawning, it will continue to spawn til the end.
Periodically, the game will get an increased budget and add a new wave to the mix. The same wave can be picked more than once, up to a max number of times, but will be less and less likely to be picked again. Also the game will favor picking stronger waves over weaker ones in order to max out its budget and maintain an overall level of challenge while still being unpredictable.
When units spawn, the game picks a random point in a given region. I believe random points may cause memory leaks so I set it to pick up to a number of random points and reuse points.
In addition, there are wave evolutions. When a wave evolves into another wave, all existing instances of the wave are replaced with the wave. Any wave can have any number of evolutionary waves and once a wave is evolved the evolution is permanent for the rest of the game. For instance, if the game were setup to have Zerglings be able to evolve into Raptors or Swarmlings, then at the start, neither Raptors nor Swarmlings would be eligible for spawning. They would become eligible once at least 1 wave of Zerglings has started spawning. If the game then had 2 waves of Zerglings spawning and chose to start spawning Raptors, both Zergling waves would be replaced with Raptor waves and neither Zerglings nor Swarmlings would be able to spawn for the rest of the game.
To further mix things up, while a wave has a set frequency, the actual frequency can be adjusted by up to 10% in either direction, and the wave can be delayed by up to 10 seconds to further keep things random. Otherwise you'd also have a wave starting at the same time and if there were multiples of a wave they'd always end up spawning the same amount of time apart from each other.. this mixes it up enough so that they might spawn together or far apart or anywhere in-between and it'll change each time.
I've tested the system and I'm satisfied with the randomness and how everything works. I'd like to clean up the code and add better comments, but I need to get back to my day job and I was too anxious to get this posted.
I'm primarily posting because I'm interested in any technical feedback. This is my first time writing something in galaxy script and I'm not sure that it's really as good as it could be. For instance, I'm using a timer for each instance of each wave. Would potentially having that many timers ever be an issue (imagine if there might be up to 100 waves)? I'm also unsure of how to "clean up" timers. I didn't see a TimerDestroy method and I'm unsure if setting them to null might cause a memory leak. Also I created a separate trigger function for each spawn, which seems redundant, but it also seemed the best way to potentially support more varied waves, like 5 zerglings + 1 queen as a wave.
I'd also just simply like to share this with the community. I feel the system works well and can be tweaked to suit many games that have periodically spawning units. I may write it as a library and/or tutorial if there's interest in it.
Thanks for any feedback, positive or negative! I hope someone can find use of the system.
regionBASE_REGION=RegionFromId(3);pointBASE_POINT=RegionGetCenter(BASE_REGION);constintMAX_SPAWN_POINTS=20;constintMAX_SPAWN_WAVE_DUPES=5;constintSPAWN_OWNER=13;regionSPAWN_REGION_AIR=RegionFromId(4);regionSPAWN_REGION_GROUND=RegionFromId(2);intwaveCount=0;voiddebug(stringmessage){UIDisplayMessage(PlayerGroupAll(),c_messageAreaChat,StringToText(message));}structspawnWave{fixedfrequency;stringspawnTriggerName;intvalue;// calculated value is sum of units spawning multipled by spawns per minuteintbaseWaveIndex;int[2]evolvedWaveIndex;intevolvedWaveCount;intcount;// same wave can spawn up to 5 times, store random delay between spawnstimer[MAX_SPAWN_WAVE_DUPES]waveTimer;triggerspawnTrigger;intchance;};spawnWave[20]spawnWaves;intspawnWaveCount=0;intspawnWaveBudget=600;point[MAX_SPAWN_POINTS]spawnPointsAir;point[MAX_SPAWN_POINTS]spawnPointsGround;intCreateSpawnWave(fixedfrequency,stringspawnTriggerName,intvalue){intindex=spawnWaveCount;spawnWaveCount=spawnWaveCount+1;spawnWaves[index].frequency=frequency;spawnWaves[index].spawnTriggerName=spawnTriggerName;spawnWaves[index].value=value;spawnWaves[index].baseWaveIndex=-1;spawnWaves[index].evolvedWaveCount=0;spawnWaves[index].count=0;debug("Added "+spawnTriggerName+" at index "+IntToString(index)+", count: "+IntToString(spawnWaveCount));returnindex;}voidAddSpawnWaveEvolution(intbaseWaveIndex,intevolutionWaveIndex){intcount=spawnWaves[baseWaveIndex].evolvedWaveCount;spawnWaves[baseWaveIndex].evolvedWaveIndex[count]=evolutionWaveIndex;spawnWaves[baseWaveIndex].evolvedWaveCount+=1;spawnWaves[evolutionWaveIndex].baseWaveIndex=baseWaveIndex;debug(spawnWaves[evolutionWaveIndex].spawnTriggerName+" now evolves from "+spawnWaves[baseWaveIndex].spawnTriggerName);}voidRemoveSpawnWave(intindex){inti;for(i=spawnWaves[index].count-1;i>=0;i=i-1){spawnWaveBudget+=spawnWaves[index].value;if(spawnWaves[index].waveTimer[i]!=null){TimerPause(spawnWaves[index].waveTimer[i],true);}}if(spawnWaves[index].spawnTrigger!=null){TriggerDestroy(spawnWaves[index].spawnTrigger);}spawnWaves[index].count=0;}voidAddSpawnWave(intindex){intcount;intnumWaves;intbaseWaveIndex=spawnWaves[index].baseWaveIndex;if(baseWaveIndex>=0){// Is an evolveed, check if evolving...if(spawnWaves[baseWaveIndex].baseWaveIndex==-1){// Evolving a wave...spawnWaves[baseWaveIndex].baseWaveIndex=-2;numWaves=spawnWaves[baseWaveIndex].count;RemoveSpawnWave(baseWaveIndex);for(count=0;count<numWaves;count+=1){AddSpawnWave(index);}return;}}spawnWaveBudget-=spawnWaves[index].value;count=spawnWaves[index].count;if(spawnWaves[index].waveTimer[count]==null){spawnWaves[index].waveTimer[count]=TimerCreate();}spawnWaves[index].count=spawnWaves[index].count+1;Wait(RandomFixed(0,10.0),c_timeGame);TimerStart(spawnWaves[index].waveTimer[count],spawnWaves[index].frequency*RandomFixed(0.9,1.1),true,c_timeGame);if(spawnWaves[index].spawnTrigger==null){spawnWaves[index].spawnTrigger=TriggerCreate(spawnWaves[index].spawnTriggerName);}TriggerExecute(spawnWaves[index].spawnTrigger,true,false);TriggerAddEventTimer(spawnWaves[index].spawnTrigger,spawnWaves[index].waveTimer[count]);debug("Added "+spawnWaves[index].spawnTriggerName+" "+IntToString(spawnWaves[index].count));}intSetRandomSpawnWaveChance(intindex,intcost){intbaseChance=MAX_SPAWN_WAVE_DUPES-spawnWaves[index].count;fixedfavor;if(cost<spawnWaveBudget&&baseChance>0){spawnWaves[index].chance=spawnWaves[index].value*baseChance;}debug(spawnWaves[index].spawnTriggerName+": "+IntToString(spawnWaves[index].chance));returnspawnWaves[index].chance;}voidAddRandomSpawnWave(){intcount=0;intbaseWaveCost=0;intevolvedWaveCost=0;intchances=0;intrandomPick=0;inti;intii;inte;for(i=0;i<spawnWaveCount;i+=1){spawnWaves[i].chance=0;}for(i=0;i<spawnWaveCount;i+=1){if(spawnWaves[i].count==0){// Add a new wave (can only add base spawn waves that haven't evolved)if(spawnWaves[i].baseWaveIndex==-1){chances+=SetRandomSpawnWaveChance(i,spawnWaves[i].value);}}else{// Add to an existing base or evolved wavechances+=SetRandomSpawnWaveChance(i,spawnWaves[i].value);// If unevolved base wave, add chance for evolved wavesif(spawnWaves[i].baseWaveIndex==-1){count=spawnWaves[i].count;baseWaveCost=count*spawnWaves[i].value;for(ii=0;ii<spawnWaves[i].evolvedWaveCount;ii+=1){e=spawnWaves[i].evolvedWaveIndex[ii];evolvedWaveCost=count*spawnWaves[e].value;chances+=SetRandomSpawnWaveChance(e,evolvedWaveCost-baseWaveCost);}}}}debug("Chances: "+IntToString(chances));if(chances>0){randomPick=RandomInt(0,chances);debug("Random Pick: "+IntToString(randomPick));chances=0;Wait(2,c_timeGame);for(i=0;i<spawnWaveCount;i+=1){if(spawnWaves[i].chance>0){chances=chances+spawnWaves[i].chance;debug("Checking: "+spawnWaves[i].spawnTriggerName+" "+IntToString(chances));if(randomPick<chances){debug("Picking: "+spawnWaves[i].spawnTriggerName);AddSpawnWave(i);break;}}}}debug("Remaining Budget: "+IntToString(spawnWaveBudget));}pointGetRandomSpawnPointAir(){intindex=RandomInt(0,MAX_SPAWN_POINTS-1);if(spawnPointsAir[index]==null){spawnPointsAir[index]=RegionRandomPoint(SPAWN_REGION_AIR);}returnspawnPointsAir[index];}pointGetRandomSpawnPointGround(){intindex=RandomInt(0,MAX_SPAWN_POINTS-1);if(spawnPointsGround[index]==null){spawnPointsGround[index]=RegionRandomPoint(SPAWN_REGION_GROUND);}returnspawnPointsGround[index];}voidSpawnAttackingUnits(intcount,stringunitType,boolgroundUnits){unitgroupunits;pointspawnPoint;if(groundUnits){spawnPoint=GetRandomSpawnPointGround();}else{spawnPoint=GetRandomSpawnPointAir();}units=UnitCreate(count,unitType,0,SPAWN_OWNER,spawnPoint,225.0);UnitGroupIssueOrder(units,OrderTargetingPoint(AbilityCommand("attack",0),BASE_POINT),c_orderQueueAddToEnd);}boolBanelingWaveTrigger(booltestConditions,boolrunActions){SpawnAttackingUnits(2,"Baneling",true);returntrue;}boolHydraliskWaveTrigger(booltestConditions,boolrunActions){SpawnAttackingUnits(3,"Hydralisk",true);returntrue;}boolMutaliskWaveTrigger(booltestConditions,boolrunActions){SpawnAttackingUnits(3,"Mutalisk",false);returntrue;}boolRoachWaveTrigger(booltestConditions,boolrunActions){SpawnAttackingUnits(2,"Roach",true);returntrue;}boolUltraliskWaveTrigger(booltestConditions,boolrunActions){SpawnAttackingUnits(1,"Ultralisk",true);returntrue;}boolZerglingWaveTrigger(booltestConditions,boolrunActions){SpawnAttackingUnits(5,"Zergling",true);returntrue;}boolAddSpawnWavesTrigger(booltestConditions,boolrunActions){waveCount+=1;spawnWaveBudget=spawnWaveBudget+(400*waveCount);debug("Budget: "+IntToString(spawnWaveBudget));AddRandomSpawnWave();returntrue;}voidinitSpawnWaves(){timert;intbaseWaveIndex;intevolWaveIndex;baseWaveIndex=CreateSpawnWave(25.0,"RoachWaveTrigger",240);baseWaveIndex=CreateSpawnWave(20.0,"BanelingWaveTrigger",300);baseWaveIndex=CreateSpawnWave(25.0,"HydraliskWaveTrigger",360);baseWaveIndex=CreateSpawnWave(15.0,"ZerglingWaveTrigger",500);evolWaveIndex=CreateSpawnWave(15.0,"MutaliskWaveTrigger",2400);AddSpawnWaveEvolution(baseWaveIndex,evolWaveIndex);evolWaveIndex=CreateSpawnWave(20.0,"UltraliskWaveTrigger",1500);AddSpawnWaveEvolution(baseWaveIndex,evolWaveIndex);t=TimerCreate();TimerStart(t,30,true,c_timeGame);TriggerAddEventTimer(TriggerCreate("AddSpawnWavesTrigger"),t);Wait(10.0,c_timeGame);AddRandomSpawnWave();}
I'm working on a defense-type map where waves of Zerg will continually spawn and attack. Each wave has a score value based on the strength of the composite units and the frequency with which the wave will spawn and the game is given a budget with which to pick a wave to start spawning. Once a wave starts spawning, it will continue to spawn til the end.
Periodically, the game will get an increased budget and add a new wave to the mix. The same wave can be picked more than once, up to a max number of times, but will be less and less likely to be picked again. Also the game will favor picking stronger waves over weaker ones in order to max out its budget and maintain an overall level of challenge while still being unpredictable.
When units spawn, the game picks a random point in a given region. I believe random points may cause memory leaks so I set it to pick up to a number of random points and reuse points.
In addition, there are wave evolutions. When a wave evolves into another wave, all existing instances of the wave are replaced with the wave. Any wave can have any number of evolutionary waves and once a wave is evolved the evolution is permanent for the rest of the game. For instance, if the game were setup to have Zerglings be able to evolve into Raptors or Swarmlings, then at the start, neither Raptors nor Swarmlings would be eligible for spawning. They would become eligible once at least 1 wave of Zerglings has started spawning. If the game then had 2 waves of Zerglings spawning and chose to start spawning Raptors, both Zergling waves would be replaced with Raptor waves and neither Zerglings nor Swarmlings would be able to spawn for the rest of the game.
To further mix things up, while a wave has a set frequency, the actual frequency can be adjusted by up to 10% in either direction, and the wave can be delayed by up to 10 seconds to further keep things random. Otherwise you'd also have a wave starting at the same time and if there were multiples of a wave they'd always end up spawning the same amount of time apart from each other.. this mixes it up enough so that they might spawn together or far apart or anywhere in-between and it'll change each time.
I've tested the system and I'm satisfied with the randomness and how everything works. I'd like to clean up the code and add better comments, but I need to get back to my day job and I was too anxious to get this posted.
I'm primarily posting because I'm interested in any technical feedback. This is my first time writing something in galaxy script and I'm not sure that it's really as good as it could be. For instance, I'm using a timer for each instance of each wave. Would potentially having that many timers ever be an issue (imagine if there might be up to 100 waves)? I'm also unsure of how to "clean up" timers. I didn't see a TimerDestroy method and I'm unsure if setting them to null might cause a memory leak. Also I created a separate trigger function for each spawn, which seems redundant, but it also seemed the best way to potentially support more varied waves, like 5 zerglings + 1 queen as a wave.
I'd also just simply like to share this with the community. I feel the system works well and can be tweaked to suit many games that have periodically spawning units. I may write it as a library and/or tutorial if there's interest in it.
Thanks for any feedback, positive or negative! I hope someone can find use of the system.