Mission development wiki
Moderator: Moderators
Mission development wiki
Is their anybody who can explane Mission development better then the wiki?
I would like to try to build some easy missions.
Update guide is up
http://spring.clan-sy.com/wiki/Mission_ ... ment_Guide
I would like to try to build some easy missions.
Update guide is up
http://spring.clan-sy.com/wiki/Mission_ ... ment_Guide
Last edited by gamer17 on 02 Jun 2007, 16:34, edited 1 time in total.
Wait for The Next Version (TM). Seriously, it will make mission making a lot easier.
Man I would love a tower defense map for Spring. I did some testing, the pathfinding is up to it, it's just a matter or some (much) scripting. But oh god would it be cool.
In case you don't know what tower defense is, play a simple online version here.
Man I would love a tower defense map for Spring. I did some testing, the pathfinding is up to it, it's just a matter or some (much) scripting. But oh god would it be cool.
In case you don't know what tower defense is, play a simple online version here.
http://spring.clan-sy.com/phpbb/viewtopic.php?t=10505RogerN wrote:Ooh, fun game. Use group name Spring when submitting your scores!In case you don't know what tower defense is, play a simple online version here.
The best way to start learning it is to take a look at the missiontest lua. That has the basic structure for a really simple mission. I've made one mission, and thor's made a bunch and all of them are based off of that design.
http://spring.unknown-files.net/file/27 ... Onslaught/
http://spring.unknown-files.net/file/28 ... uillotine/
They are for nota and are quite good.
To make a simple mission first make a copy of the missiontest and give it a new name. Then open the file and do a search and replace for MissionTest and give it your new name, say PeeweeIsland. That will give you a new startscript to work with. Then clean out every thing inside the SlowUpdate function, the Setup function, and the SetupDelayed function.
To start making the mission load up spring from spring.exe and choose mission builder from the list. Cheat and build a friendly base with all the guys you want, and an enemy base. Then select all the guys on once side and type in the command savesel or savelist. It's important to save the lists of friendly and enemy units separately. If you don't it'll just save them all together and you won't be able to figure out which side which ones are on.
Once you save the units, quit and open up the file infolog. It'll have the code for loading in the units in there. Don't open up spring again before you copy it out or you'll lose everything.
Cut out the unit loading code from that file paste it into the PeeweeIsland:Setup() function. You still have to set the team manually for each unit for some reason. For example if it says
that 0 means team 0. If it's supposed to be an enemy unit you have to set it to team 1. Once you paste everything in and change all the enemy units to 1's you can try it out. It won't do anything, but everything should be there.
The center of your mission script is the update function. It gets called by the game 30 times per second. You call all the other functions from that function. We haven't tried bringing in AI, but that should be possible. A lot of the missions we've made run on a time counter.
The setup function only runs once, the first frame when the game starts and never again. That's where you want to put in all the code for loading the starting units and bases
The parts at the bottom are functions that get called after x amount of time. 10 seconds in the game prints a message. SetupDelayed() gets called after 20 seconds. ForwardTanks1() is called after 60 and ForwardTanks2() is called after 75.
This is the code for ForwardTanks1. It loads a bunch of units near the edge of the map where the enemy base is and tells them to move up to near the front just out of range or your defenses. You get the load unit code for them the same way you do for loading the starting units in the setup function.
That's the basics of it. If you want to refer to a group of guys later on, somewhere else in the script you have to give them a name. For example.
now that group of guys is know as panzer1 and you could put them on a patrol route going somewhere else later on.
The slowupdate function is best used for checking on triggers.
that checks if the unit named snipertarget1 is alive or not.
http://spring.unknown-files.net/file/27 ... Onslaught/
http://spring.unknown-files.net/file/28 ... uillotine/
They are for nota and are quite good.
To make a simple mission first make a copy of the missiontest and give it a new name. Then open the file and do a search and replace for MissionTest and give it your new name, say PeeweeIsland. That will give you a new startscript to work with. Then clean out every thing inside the SlowUpdate function, the Setup function, and the SetupDelayed function.
To start making the mission load up spring from spring.exe and choose mission builder from the list. Cheat and build a friendly base with all the guys you want, and an enemy base. Then select all the guys on once side and type in the command savesel or savelist. It's important to save the lists of friendly and enemy units separately. If you don't it'll just save them all together and you won't be able to figure out which side which ones are on.
Once you save the units, quit and open up the file infolog. It'll have the code for loading in the units in there. Don't open up spring again before you copy it out or you'll lose everything.
Cut out the unit loading code from that file paste it into the PeeweeIsland:Setup() function. You still have to set the team manually for each unit for some reason. For example if it says
Code: Select all
units.Load("CORAK", float3(1200, 80, 200), 0, false)
The center of your mission script is the update function. It gets called by the game 30 times per second. You call all the other functions from that function. We haven't tried bringing in AI, but that should be possible. A lot of the missions we've made run on a time counter.
Code: Select all
-- This function is executed every simulated frame (30 times/sec)
function GullyOnslaught:Update()
-- Perform initialization
if self.state == 0 then
self.state = 1
self:Setup()
print("Intel reports the arm is planning a major offensive. " ..
"Defend our mining operation in this region. " ..
"We will bring in what support we can.")
end
-- Run SlowUpdate once every second
if math.mod(gs.frameNum, 30) == 0 then
self:SlowUpdate()
end
-- Do some stuff a bit later
if gs.frameNum == 30*10 then
print("There are 4 nuclear mines placed along the central pass. " ..
"If you have to, fall back and detonate them behind you.")
end
if gs.frameNum == 30*20 then
self:SetupDelayed()
end
if gs.frameNum == 30*60 then
self:ForwardTanks1()
end
if gs.frameNum == 30*75 then
self:ForwardTanks2()
end
end
The parts at the bottom are functions that get called after x amount of time. 10 seconds in the game prints a message. SetupDelayed() gets called after 20 seconds. ForwardTanks1() is called after 60 and ForwardTanks2() is called after 75.
This is the code for ForwardTanks1. It loads a bunch of units near the edge of the map where the enemy base is and tells them to move up to near the front just out of range or your defenses. You get the load unit code for them the same way you do for loading the starting units in the setup function.
Code: Select all
function GullyOnslaught:ForwardTanks1()
local unitList = {
units.Load("ARMSTUMP", float3(4294.4, 119.7, 569.2), 1, false),
units.Load("ARMSTUMP", float3(4375.3, 121.6, 555.5), 1, false),
units.Load("ARMSTUMP", float3(4424.2, 126.5, 621.0), 1, false),
units.Load("ARMSTUMP", float3(4498.7, 119.6, 509.9), 1, false),
units.Load("ARMSAM", float3(4294.2, 115.2, 449.7), 1, false),
units.Load("ARMSTUMP", float3(4555.6, 118.8, 486.7), 1, false),
units.Load("ARMSTUMP", float3(4612.9, 118.8, 471.7), 1, false),
units.Load("ARMSTUMP", float3(4298.7, 117.6, 515.2), 1, false),
units.Load("ARMSAM", float3(4332.7, 114.1, 335.6), 1, false),
units.Load("ARMSTUMP", float3(4345.2, 116.9, 497.9), 1, false),
units.Load("ARMSTUMP", float3(4445.7, 120.3, 506.3), 1, false),
units.Load("ARMSAM", float3(4472.5, 114.1, 391.7), 1, false),
units.Load("ARMSAM", float3(4571.3, 115.2, 358.5), 1, false),
units.Load("ARMSTUMP", float3(4472.8, 115.6, 450.8), 1, false),
units.Load("ARMSAM", float3(4260.4, 114.1, 396.6), 1, false),
units.Load("ARMBULL", float3(4579.6, 117.6, 427.1), 1, false)
}
local c = Command()
c.id = Command.MOVE
c:AddParam(4284)
c:AddParam(132)
c:AddParam(3886)
for i = 1, table.getn(unitList) do
unitList[i]:GiveCommand(c)
end
end
Code: Select all
self.panzer1 = {
units.Load("ARMSTUMP", float3(2054.0, 109.4, 594.0), 1, false),
units.Load("ARMMART", float3(2271.0, 108.2, 606.0), 1, false),
units.Load("ARMMART", float3(2303.0, 108.2, 574.0), 1, false),
units.Load("ARMYORK", float3(2065.0, 108.2, 480.0), 1, false),
units.Load("ARMMART", float3(2207.0, 109.1, 638.0), 1, false),
units.Load("ARMYORK", float3(2113.0, 108.2, 528.0), 1, false),
units.Load("ARMYORK", float3(2113.0, 107.0, 480.0), 1, false),
units.Load("ARMSTUMP", float3(2126.0, 111.4, 606.0), 1, false),
units.Load("ARMMART", float3(2239.0, 108.2, 638.0), 1, false),
units.Load("ARMSTUMP", float3(2094.0, 110.5, 606.0), 1, false),
units.Load("ARMBULL", float3(2250.0, 108.2, 503.0), 1, false),
units.Load("ARMSTUMP", float3(2136.0, 113.8, 638.0), 1, false),
units.Load("ARMSTUMP", float3(2168.0, 111.4, 638.0), 1, false),
units.Load("ARMSTUMP", float3(2022.0, 111.1, 594.0), 1, false),
units.Load("ARMMART", float3(2239.0, 108.2, 606.0), 1, false),
units.Load("ARMYORK", float3(2065.0, 108.2, 528.0), 1, false),
units.Load("ARMMART", float3(2207.0, 108.2, 606.0), 1, false),
units.Load("ARMMART", float3(2207.0, 108.2, 574.0), 1, false),
units.Load("ARMMART", float3(2239.0, 108.2, 574.0), 1, false),
units.Load("ARMMART", float3(2271.0, 108.2, 574.0), 1, false),
units.Load("ARMYORK", float3(2161.0, 108.2, 528.0), 1, false),
units.Load("ARMYORK", float3(2161.0, 108.2, 480.0), 1, false),
units.Load("ARMMART", float3(2303.0, 109.4, 606.0), 1, false),
units.Load("ARMSTUMP", float3(2077.0, 113.0, 652.0), 1, false),
units.Load("ARMSTUMP", float3(2109.0, 115.2, 652.0), 1, false)
}
local c = Command()
c.id = Command.PATROL
c:AddParam(4645)
c:AddParam(140)
c:AddParam(8926)
for i = 1, table.getn(self.panzer1) do
self.panzer1[i]:GiveCommand(c)
end
The slowupdate function is best used for checking on triggers.
Code: Select all
if( self.snipertarget1.IsValid(self.snipertarget1) ) then
--still alive
else
--ding dong the witch is dead
end
Last edited by smartie on 26 May 2007, 19:54, edited 1 time in total.
I'm guessing your If statement is occurring before self.hit1 has been defined. Are you loading the unit in the setup function? If so, then make sure your if statement takes place in any function other than the update function (so it won't occur first, probably you'll want it in slowupdate). If not, that is if you want to load the unit later in the game, then you need to create a variable that you will change when the unit has been spawned and check for it first.
For example: (in slowupdate)
with
in the setup function and set to 1 wherever you spawned the unit.
Edit: an important point not mentioned in smartie's post is that anytime you reference a unit after it has been loaded, whether to give it an order or to target it by another unit, you MUST check first to see if said unit still is alive. The game will crash if it attempts to give an order to a dead unit. As long as you are giving the order in the same function that the unit is spawned, you don't need to check, but otherwise you must. If you get in game crashes, this is almost certainly the cause.
Also, if (or rather, when, because it will happen) spring crashes before you make it to the menu, you probably have either a missing(or extra) "end" somewhere, or you have forgotten to put commas at the end of units loading as part of a group.
For example: (in slowupdate)
Code: Select all
if self.hit1spawned == 1 then
if (self.hit1.IsValid(self.hit1) ) then
--still alive
else
print ("Good job")
end
end
Code: Select all
self.hit1spawned = 0
Edit: an important point not mentioned in smartie's post is that anytime you reference a unit after it has been loaded, whether to give it an order or to target it by another unit, you MUST check first to see if said unit still is alive. The game will crash if it attempts to give an order to a dead unit. As long as you are giving the order in the same function that the unit is spawned, you don't need to check, but otherwise you must. If you get in game crashes, this is almost certainly the cause.
Also, if (or rather, when, because it will happen) spring crashes before you make it to the menu, you probably have either a missing(or extra) "end" somewhere, or you have forgotten to put commas at the end of units loading as part of a group.
No problem, and yeah, feel absolutely free to update the wiki.
A couple other things that come to mind:
Resources. By default you start with 200,000 metal and energy storage. Currently there is no way to change this in the script, which is why my first few missions did not give you any builders. Obviously it is harder to make interesting missions this way because all that's left for the player to do is micromanage the limited number of units that you give him. The way we got around this was by adding a duplicate unit to the mod with negative 199,000 storage. The unit could be a commander or nanotower (which is what we did), or it could be some invisible unit that you spawn off the map and the player never knows about. Either way the death of this unit should cause the game to end so the player can't cheatingly get his storage back.
A slowerupdate function. I found this to be very useful in operation guillotine (linked above) and my remake of convoy rescue. Basically, while slowupdate occurs every second and is good for checking things like victory conditions, a slowerupdate function can be set to happen every 10 seconds or so, and is very handy for updating enemy unit's orders. For example, in Guillotine, the enemy bombers have three possible targets: 2 shipyards and the nuclear cannon. Every 10 seconds they are given a new attack order based on which targets are still alive. Giving an order every second would be unnecessary and would probably screw up their flight patterns. Same with the missile cruisers. Once their escort is killed, they will retreat, and the short, random delay gives the appearance of reaction time rather than being an obvious trigger. I also prefer this for things like infinitely respawning units, because it is called less often, the numbers are smaller, and it's just easier to work with.
This might be somewhat obvious, but I think it's worth mentioning. You don't have to call functions from the time-counter. A mission might have more than one distinct phase, so that one objective has to be completed before the sequence of later triggers begins happening. To do this, use a counter variable in slowupdate that adds to itself each second, beginning only when the first objective has been completed. You can then call functions based on the value of the counter, rather than calling them by the overall game time counter. I use this in convoy rescue: the enemy army only begins its attacks once you reach the outpost. All functions after that point are called based on the counter variable.
For more advanced things like checking how many units are left in a group or loading/unloading groups of units, look at the two missions linked above. And I think that's about it, good luck!
A couple other things that come to mind:
Resources. By default you start with 200,000 metal and energy storage. Currently there is no way to change this in the script, which is why my first few missions did not give you any builders. Obviously it is harder to make interesting missions this way because all that's left for the player to do is micromanage the limited number of units that you give him. The way we got around this was by adding a duplicate unit to the mod with negative 199,000 storage. The unit could be a commander or nanotower (which is what we did), or it could be some invisible unit that you spawn off the map and the player never knows about. Either way the death of this unit should cause the game to end so the player can't cheatingly get his storage back.
A slowerupdate function. I found this to be very useful in operation guillotine (linked above) and my remake of convoy rescue. Basically, while slowupdate occurs every second and is good for checking things like victory conditions, a slowerupdate function can be set to happen every 10 seconds or so, and is very handy for updating enemy unit's orders. For example, in Guillotine, the enemy bombers have three possible targets: 2 shipyards and the nuclear cannon. Every 10 seconds they are given a new attack order based on which targets are still alive. Giving an order every second would be unnecessary and would probably screw up their flight patterns. Same with the missile cruisers. Once their escort is killed, they will retreat, and the short, random delay gives the appearance of reaction time rather than being an obvious trigger. I also prefer this for things like infinitely respawning units, because it is called less often, the numbers are smaller, and it's just easier to work with.
This might be somewhat obvious, but I think it's worth mentioning. You don't have to call functions from the time-counter. A mission might have more than one distinct phase, so that one objective has to be completed before the sequence of later triggers begins happening. To do this, use a counter variable in slowupdate that adds to itself each second, beginning only when the first objective has been completed. You can then call functions based on the value of the counter, rather than calling them by the overall game time counter. I use this in convoy rescue: the enemy army only begins its attacks once you reach the outpost. All functions after that point are called based on the counter variable.
For more advanced things like checking how many units are left in a group or loading/unloading groups of units, look at the two missions linked above. And I think that's about it, good luck!
That's because it's running through that bit of code every time SlowUpdate is called. If you add in a variable that tells it to stop checking once it's been triggered you'll only get the message once.
I'm pretty sure the variable automatically is initialized as 0, but I dunno if you can always count on that. It's proper to set it yourself anyways, so in the setup function put in.
self.lltKilled = 0
Code: Select all
function new:message()
if (self.lltKilled == 0) then
if( self.hit1.IsValid(self.hit1) ) then
--still alive
else
self.lltKilled = 1
print ("Good Job")
end
end
end
self.lltKilled = 0
corntow_evil is the version of the nanotower that we added in nota 1.22 with negative 199,000 resource storage. This is the only way we were able to get the player's resources at a limited, reasonable level. Theoretically it should have been possible to set the storage, which is what those two commented lines were supposed to do, but it doesn't seem to work.
I do not knnow of any way to call functions from another lua.
I do not knnow of any way to call functions from another lua.