Mission development wiki

Mission development wiki

Various things about Spring that do not fit in any of the other forums listed below, including forum rules.

Moderator: Moderators

User avatar
gamer17
Posts: 235
Joined: 21 Feb 2007, 23:51

Mission development wiki

Post by gamer17 »

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
Last edited by gamer17 on 02 Jun 2007, 16:34, edited 1 time in total.
User avatar
Boirunner
Kernel Panic Co-Developer
Posts: 811
Joined: 05 Feb 2007, 14:24

Post by Boirunner »

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.
User avatar
smoth
Posts: 22309
Joined: 13 Jan 2005, 00:46

Post by smoth »

I love tower defense and survival missions where there is a counter and at a certain time you get attacked with wave after wave.
User avatar
RogerN
Posts: 238
Joined: 24 Jul 2006, 23:29

Post by RogerN »

In case you don't know what tower defense is, play a simple online version here.
Ooh, fun game. Use group name Spring when submitting your scores!
User avatar
Boirunner
Kernel Panic Co-Developer
Posts: 811
Joined: 05 Feb 2007, 14:24

Post by Boirunner »

RogerN wrote:
In case you don't know what tower defense is, play a simple online version here.
Ooh, fun game. Use group name Spring when submitting your scores!
http://spring.clan-sy.com/phpbb/viewtopic.php?t=10505
User avatar
smartie
NOTA Developer
Posts: 146
Joined: 23 Jun 2005, 19:29

Post by smartie »

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

Code: Select all

units.Load("CORAK", float3(1200, 80, 200), 0, false)
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.

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 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.

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
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.

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
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.

Code: Select all

 
if( self.snipertarget1.IsValid(self.snipertarget1) ) then
   --still alive
else
   --ding dong the witch is dead
end
that checks if the unit named snipertarget1 is alive or not.
Last edited by smartie on 26 May 2007, 19:54, edited 1 time in total.
User avatar
gamer17
Posts: 235
Joined: 21 Feb 2007, 23:51

Post by gamer17 »

Thanks this helped a lot
User avatar
gamer17
Posts: 235
Joined: 21 Feb 2007, 23:51

Post by gamer17 »

Am working on a Tutorial mission, is there any way to have dragon teeth spawn as well?
User avatar
Thor
NOTA Developer
Posts: 291
Joined: 05 Mar 2006, 10:26

Post by Thor »

Type .help in the mission builder and it will give you the command to save features. Also, one thing to add is you can set the team of the units you're saving by typing .savesel 1 for example if you want them on team 1.
User avatar
gamer17
Posts: 235
Joined: 21 Feb 2007, 23:51

Post by gamer17 »

what does

startscripts\template.lua:56: attempt to index global `self' (a nil value) mean

I think this line is causing the error

if( self.hit1.IsValid(self.hit1) ) then
--still alive
else
print ("Good job")


hit1 is
self.hit1 = units.Load("armllt", float3(1904.0, 45.6, 2240.0), 1, false)
User avatar
Thor
NOTA Developer
Posts: 291
Joined: 05 Mar 2006, 10:26

Post by Thor »

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)

Code: Select all

if self.hit1spawned == 1 then
if (self.hit1.IsValid(self.hit1) ) then 
--still alive 
else 
print ("Good job") 
end
end
with

Code: Select all

self.hit1spawned = 0
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.
User avatar
gamer17
Posts: 235
Joined: 21 Feb 2007, 23:51

Post by gamer17 »

thanks man
User avatar
gamer17
Posts: 235
Joined: 21 Feb 2007, 23:51

Post by gamer17 »

Is it ok if I write a small guide on the wiki using this info, I'll give you credit of course?
User avatar
smoth
Posts: 22309
Joined: 13 Jan 2005, 00:46

Post by smoth »

Thanks gamer17 and Thor, I was looking into missions for my project. I'll be reading through all of this tonight!
User avatar
Thor
NOTA Developer
Posts: 291
Joined: 05 Mar 2006, 10:26

Post by Thor »

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!
User avatar
gamer17
Posts: 235
Joined: 21 Feb 2007, 23:51

Post by gamer17 »

I got this function to work with slow update


function new:message()

if( self.hit1.IsValid(self.hit1) ) then
--still alive
else
print ("Good Job")

end
end

Now it keeps saying good job, is there a better way to do this?
User avatar
smartie
NOTA Developer
Posts: 146
Joined: 23 Jun 2005, 19:29

Post by smartie »

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.

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
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
User avatar
gamer17
Posts: 235
Joined: 21 Feb 2007, 23:51

Post by gamer17 »

Thanks
User avatar
gamer17
Posts: 235
Joined: 21 Feb 2007, 23:51

Post by gamer17 »

What does this mean?

self.corntow = units.Load("CORNTOW_EVIL", float3(4567.0, 125.8, 8289.0), 0, false)
--self.corntow.metalstorage = -999000 -- cant get it working. Don't know why. Use evil for now
--self.corntow.energystorage = -999000



And can I call functions from another lua?
User avatar
Thor
NOTA Developer
Posts: 291
Joined: 05 Mar 2006, 10:26

Post by Thor »

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.
Post Reply

Return to “General Discussion”