~ubuntu-branches/ubuntu/natty/spring/natty

« back to all changes in this revision

Viewing changes to installer/builddata/springcontent/LuaGadgets/Gadgets/unit_script.lua

  • Committer: Bazaar Package Importer
  • Author(s): Scott Ritchie
  • Date: 2010-09-23 18:56:03 UTC
  • mfrom: (3.1.9 experimental)
  • Revision ID: james.westby@ubuntu.com-20100923185603-st97s5chplo42y7w
Tags: 0.82.5.1+dfsg1-1ubuntu1
* Latest upstream version for online play
* debian/control: Replace (rather than conflict) spring-engine
  - spring-engine will be a dummy package (LP: #612905)
  - also set maintainer to MOTU

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
 
2
 
--[[
3
 
Please, think twice before editing this file. Compared to most gadgets, there
4
 
are some complex things going on.  A good understanding of Lua's coroutines is
5
 
required to make nontrivial modifications to this file.
6
 
 
7
 
In other words, HERE BE DRAGONS =)
8
 
 
9
 
Known issues:
10
 
- {Query,AimFrom,Aim,Fire}{Primary,Secondary,Tertiary} are not handled.
11
 
  (use {Query,AimFrom,Aim,Fire}{Weapon1,Weapon2,Weapon3} instead!)
12
 
- Errors in callins which aren't wrapped in a thread do not show a traceback.
13
 
- Which callins are wrapped in a thread and which aren't is a bit arbitrary.
14
 
- MoveFinished, TurnFinished and Destroy are overwritten by the framework.
15
 
- There is no way to reload the script of a single unit. (use /luarules reload)
16
 
- Error checking is lacking.  (In particular for incorrect unitIDs.)
17
 
 
18
 
To do:
19
 
- Test real world performance (compared to COB)
20
 
]]--
21
 
 
22
 
--------------------------------------------------------------------------------
23
 
--------------------------------------------------------------------------------
24
 
 
25
 
function gadget:GetInfo()
26
 
        return {
27
 
                name      = "Lua unit script framework",
28
 
                desc      = "Manages Lua unit scripts",
29
 
                author    = "Tobi Vollebregt",
30
 
                date      = "2 September 2009",
31
 
                license   = "GPL v2",
32
 
                layer     = 0,
33
 
                enabled   = true --  loaded by default?
34
 
        }
35
 
end
36
 
 
37
 
 
38
 
if (not gadgetHandler:IsSyncedCode()) then
39
 
        return false
40
 
end
41
 
 
42
 
 
43
 
local thread_wrap = {
44
 
        --"StartMoving",
45
 
        --"StopMoving",
46
 
        --"Activate",
47
 
        --"Deactivate",
48
 
        --"WindChanged",
49
 
        --"ExtractionRateChanged",
50
 
        "RockUnit",
51
 
        --"HitByWeapon",
52
 
        --"MoveRate",
53
 
        --"setSFXoccupy",
54
 
        --"QueryLandingPad",
55
 
        "Falling",
56
 
        "Landed",
57
 
        "BeginTransport",
58
 
        --"QueryTransport",
59
 
        "TransportPickup",
60
 
        "StartUnload",
61
 
        "EndTransport",
62
 
        "TransportDrop",
63
 
        "StartBuilding",
64
 
        "StopBuilding",
65
 
        --"QueryNanoPiece",
66
 
        --"QueryBuildInfo",
67
 
        --"QueryWeapon",
68
 
        --"AimFromWeapon",
69
 
        "FireWeapon",
70
 
        --"EndBurst",
71
 
        --"Shot",
72
 
        --"BlockShot",
73
 
        --"TargetWeight",
74
 
}
75
 
 
76
 
local weapon_funcs = {
77
 
        "QueryWeapon",
78
 
        "AimFromWeapon",
79
 
        "AimWeapon",
80
 
        "AimShield",
81
 
        "FireWeapon",
82
 
        "Shot",
83
 
        "EndBurst",
84
 
        "BlockShot",
85
 
        "TargetWeight",
86
 
}
87
 
 
88
 
local default_return_values = {
89
 
        QueryWeapon = -1,
90
 
        AimFromWeapon = -1,
91
 
        AimWeapon = false,
92
 
        AimShield = false,
93
 
        BlockShot = false,
94
 
        TargetWeight = 1,
95
 
}
96
 
 
97
 
--------------------------------------------------------------------------------
98
 
--------------------------------------------------------------------------------
99
 
 
100
 
local pairs = pairs
101
 
 
102
 
local co_create = coroutine.create
103
 
local co_resume = coroutine.resume
104
 
local co_yield = coroutine.yield
105
 
local co_running = coroutine.running
106
 
 
107
 
local bit_and = math.bit_and
108
 
local floor = math.floor
109
 
 
110
 
local sp_GetGameFrame = Spring.GetGameFrame
111
 
local sp_GetUnitWeaponState = Spring.GetUnitWeaponState
112
 
local sp_SetUnitWeaponState = Spring.SetUnitWeaponState
113
 
local sp_SetUnitShieldState = Spring.SetUnitShieldState
114
 
 
115
 
local sp_CallAsUnit  = Spring.UnitScript.CallAsUnit
116
 
local sp_WaitForMove = Spring.UnitScript.WaitForMove
117
 
local sp_WaitForTurn = Spring.UnitScript.WaitForTurn
118
 
local sp_SetPieceVisibility = Spring.UnitScript.SetPieceVisibility
119
 
local sp_SetDeathScriptFinished = Spring.UnitScript.SetDeathScriptFinished
120
 
 
121
 
 
122
 
local UNITSCRIPT_DIR = (UNITSCRIPT_DIR or "scripts/"):lower()
123
 
local VFSMODE = VFS.ZIP_ONLY
124
 
if (Spring.IsDevLuaEnabled()) then
125
 
        VFSMODE = VFS.RAW_ONLY
126
 
end
127
 
 
128
 
VFS.Include('LuaGadgets/system.lua', nil, VFSMODE)
129
 
VFS.Include('gamedata/VFSUtils.lua', nil, VFSMODE)
130
 
 
131
 
--------------------------------------------------------------------------------
132
 
--------------------------------------------------------------------------------
133
 
 
134
 
--[[
135
 
Data structure to administrate the threads of each managed unit.
136
 
We store a set of all threads for each unit, and in two separate tables
137
 
the threads which are waiting for a turn or move animation to finish.
138
 
 
139
 
The 'thread' stored in waitingForMove/waitingForTurn/sleepers is the table
140
 
wrapping the actual coroutine object.  This way the signal_mask etc. is
141
 
available too.
142
 
 
143
 
The threads table is a weak table.  This saves us from having to manually clean
144
 
up dead threads: any thread which is not sleeping or waiting is in none of
145
 
(sleepers,waitingForMove,waitingForTurn) => it is only in the threads table
146
 
=> garbage collector will harvest it because the table is weak.
147
 
 
148
 
Beware the threads are indexed by thread (coroutine), so careless
149
 
iteration of threads WILL cause desync!
150
 
 
151
 
Format: {
152
 
        [unitID] = {
153
 
                env = {},  -- the unit's environment table
154
 
                waitingForMove = { [piece*3+axis] = thread, ... },
155
 
                waitingForTurn = { [piece*3+axis] = thread, ... },
156
 
                threads = {
157
 
                        [thread] = {
158
 
                                thread = thread,      -- the coroutine object
159
 
                                signal_mask = number, -- see Signal/SetSignalMask
160
 
                                unitID = number,      -- 'owner' of the thread
161
 
                                onerror = function,   -- called after thread died due to an error
162
 
                        },
163
 
                        ...
164
 
                },
165
 
        },
166
 
}
167
 
--]]
168
 
local units = {}
169
 
 
170
 
local activeUnit
171
 
 
172
 
--[[
173
 
This is the bed, it stores all the sleeping threads,
174
 
indexed by the frame in which they need to be woken up.
175
 
 
176
 
Format: {
177
 
        [framenum] = { thread1, thread2, ... },
178
 
}
179
 
--]]
180
 
local sleepers = {}
181
 
 
182
 
--------------------------------------------------------------------------------
183
 
--------------------------------------------------------------------------------
184
 
 
185
 
local function Remove(tab, item)
186
 
        local n = #tab
187
 
        for i = 1,n do
188
 
                if (tab[i] == item) then
189
 
                        tab[i] = tab[n]
190
 
                        tab[n] = nil
191
 
                        return
192
 
                end
193
 
        end
194
 
end
195
 
 
196
 
local function Destroy()
197
 
        if activeUnit then
198
 
                for _,thread in pairs(activeUnit.threads) do
199
 
                        if thread.container then
200
 
                                Remove(thread.container, thread)
201
 
                        end
202
 
                end
203
 
                units[activeUnit.unitID] = nil
204
 
                activeUnit = nil
205
 
        end
206
 
end
207
 
 
208
 
local function RunOnError(thread)
209
 
        local fun = thread.onerror
210
 
        if fun then
211
 
                local good, err = pcall(fun, err)
212
 
                if (not good) then
213
 
                        Spring.Echo("error in error handler: " .. err)
214
 
                end
215
 
        end
216
 
end
217
 
 
218
 
local function WakeUp(thread, ...)
219
 
        thread.container = nil
220
 
        local co = thread.thread
221
 
        local good, err = co_resume(co, ...)
222
 
        if (not good) then
223
 
                Spring.Echo(err)
224
 
                Spring.Echo(debug.traceback(co))
225
 
                RunOnError(thread)
226
 
        end
227
 
end
228
 
 
229
 
local function AnimFinished(threads, waitingForAnim, piece, axis)
230
 
        local index = piece * 3 + axis
231
 
        local threads = waitingForAnim[index]
232
 
        if threads then
233
 
                waitingForAnim[index] = {}
234
 
                for i=1,#threads do
235
 
                        WakeUp(threads[i])
236
 
                end
237
 
        end
238
 
end
239
 
 
240
 
local function MoveFinished(piece, axis)
241
 
        return AnimFinished(activeUnit.threads, activeUnit.waitingForMove, piece, axis)
242
 
end
243
 
 
244
 
local function TurnFinished(piece, axis)
245
 
        return AnimFinished(activeUnit.threads, activeUnit.waitingForTurn, piece, axis)
246
 
end
247
 
 
248
 
--------------------------------------------------------------------------------
249
 
--------------------------------------------------------------------------------
250
 
 
251
 
function Spring.UnitScript.CallAsUnit(unitID, fun, ...)
252
 
        local oldActiveUnit = activeUnit
253
 
        activeUnit = units[unitID]
254
 
        local ret = {sp_CallAsUnit(unitID, fun, ...)}
255
 
        activeUnit = oldActiveUnit
256
 
        return unpack(ret)
257
 
end
258
 
 
259
 
local function CallAsUnitNoReturn(unitID, fun, ...)
260
 
        local oldActiveUnit = activeUnit
261
 
        activeUnit = units[unitID]
262
 
        sp_CallAsUnit(unitID, fun, ...)
263
 
        activeUnit = oldActiveUnit
264
 
end
265
 
 
266
 
local function WaitForAnim(threads, waitingForAnim, piece, axis)
267
 
        local index = piece * 3 + axis
268
 
        local wthreads = waitingForAnim[index]
269
 
        if (not wthreads) then
270
 
                wthreads = {}
271
 
                waitingForAnim[index] = wthreads
272
 
        end
273
 
        local thread = threads[co_running() or error("not in a thread", 2)]
274
 
        wthreads[#wthreads+1] = thread
275
 
        thread.container = wthreads
276
 
        -- yield the running thread:
277
 
        -- it will be resumed once the wait finished (in AnimFinished).
278
 
        co_yield()
279
 
end
280
 
 
281
 
function Spring.UnitScript.WaitForMove(piece, axis)
282
 
        if sp_WaitForMove(piece, axis) then
283
 
                return WaitForAnim(activeUnit.threads, activeUnit.waitingForMove, piece, axis)
284
 
        end
285
 
end
286
 
 
287
 
function Spring.UnitScript.WaitForTurn(piece, axis)
288
 
        if sp_WaitForTurn(piece, axis) then
289
 
                return WaitForAnim(activeUnit.threads, activeUnit.waitingForTurn, piece, axis)
290
 
        end
291
 
end
292
 
 
293
 
function Spring.UnitScript.Sleep(milliseconds)
294
 
        local n = floor(milliseconds / 33)
295
 
        if (n <= 0) then n = 1 end
296
 
        n = n + sp_GetGameFrame()
297
 
        local zzz = sleepers[n]
298
 
        if (not zzz) then
299
 
                zzz = {}
300
 
                sleepers[n] = zzz
301
 
        end
302
 
        local thread = activeUnit.threads[co_running() or error("not in a thread", 2)]
303
 
        zzz[#zzz+1] = thread
304
 
        thread.container = zzz
305
 
        -- yield the running thread:
306
 
        -- it will be resumed in frame #n (in gadget:GameFrame).
307
 
        co_yield()
308
 
end
309
 
 
310
 
function Spring.UnitScript.StartThread(fun, ...)
311
 
        local co = co_create(fun)
312
 
        local thread = {
313
 
                thread = co,
314
 
                -- signal_mask is inherited from current thread, if any
315
 
                signal_mask = (co_running() and activeUnit.threads[co_running()].signal_mask or 0),
316
 
                unitID = activeUnit.unitID,
317
 
        }
318
 
        activeUnit.threads[co] = thread
319
 
        -- COB doesn't start thread immediately: it only sets up stack and
320
 
        -- pushes parameters on it for first time the thread is scheduled.
321
 
        -- Here it is easier however to start thread immediately, so we don't need
322
 
        -- to remember the parameters for the first co_resume call somewhere.
323
 
        -- I think in practice the difference in behavior isn't an issue.
324
 
        return WakeUp(thread, ...)
325
 
end
326
 
 
327
 
local function SetOnError(fun)
328
 
        local thread = activeUnit.threads[co_running()]
329
 
        if thread then
330
 
                thread.onerror = fun
331
 
        end
332
 
end
333
 
 
334
 
function Spring.UnitScript.SetSignalMask(mask)
335
 
        local thread = activeUnit.threads[co_running()]
336
 
        if thread then
337
 
                thread.signal_mask = mask
338
 
        end
339
 
end
340
 
 
341
 
function Spring.UnitScript.Signal(mask)
342
 
        -- beware, unsynced loop order
343
 
        -- (doesn't matter here as long as all threads get removed)
344
 
        for _,thread in pairs(activeUnit.threads) do
345
 
                if (bit_and(thread.signal_mask, mask) ~= 0) then
346
 
                        if thread.container then
347
 
                                Remove(thread.container, thread)
348
 
                        end
349
 
                end
350
 
        end
351
 
end
352
 
 
353
 
function Spring.UnitScript.Hide(piece)
354
 
        return sp_SetPieceVisibility(piece, false)
355
 
end
356
 
 
357
 
function Spring.UnitScript.Show(piece)
358
 
        return sp_SetPieceVisibility(piece, true)
359
 
end
360
 
 
361
 
function Spring.UnitScript.GetScriptEnv(unitID)
362
 
        return units[unitID].env
363
 
end
364
 
 
365
 
function Spring.UnitScript.GetLongestReloadTime(unitID)
366
 
        local longest = 0
367
 
        for i=0,31 do
368
 
                local reloadTime = sp_GetUnitWeaponState(unitID, i, "reloadTime")
369
 
                if (not reloadTime) then break end
370
 
                if (reloadTime > longest) then longest = reloadTime end
371
 
        end
372
 
        return 1000 * longest
373
 
end
374
 
 
375
 
--------------------------------------------------------------------------------
376
 
--------------------------------------------------------------------------------
377
 
 
378
 
local scriptHeader = VFS.LoadFile("gamedata/unit_script_header.lua", VFSMODE)
379
 
 
380
 
scriptHeader = scriptHeader:gsub("%-%-[^\r\n]*", ""):gsub("[\r\n]", " ")
381
 
 
382
 
 
383
 
--[[
384
 
Dictionary mapping script name (without path or extension) to a Lua chunk which
385
 
returns a new closure (read; instance) of this unitscript.
386
 
 
387
 
Format: {
388
 
        [unitID] = chunk,
389
 
}
390
 
--]]
391
 
local scripts = {}
392
 
 
393
 
 
394
 
local prototypeEnv
395
 
do
396
 
        local script = {}
397
 
        for k,v in pairs(System) do
398
 
                script[k] = v
399
 
        end
400
 
        script._G = _G  -- the global table
401
 
        script.GG = GG  -- the shared table (shared with gadgets!)
402
 
        prototypeEnv = script
403
 
end
404
 
 
405
 
 
406
 
local function Basename(filename)
407
 
        return filename:match("[^\\/:]*$") or filename
408
 
end
409
 
 
410
 
 
411
 
local function LoadChunk(filename)
412
 
        local text = VFS.LoadFile(filename, VFSMODE)
413
 
        if (text == nil) then
414
 
                Spring.Echo("Failed to load: " .. filename)
415
 
                return nil
416
 
        end
417
 
        local chunk, err = loadstring(scriptHeader .. text, filename)
418
 
        if (chunk == nil) then
419
 
                Spring.Echo("Failed to load: " .. Basename(filename) .. "  (" .. err .. ")")
420
 
                return nil
421
 
        end
422
 
        return chunk
423
 
end
424
 
 
425
 
 
426
 
local function LoadScript(scriptName, filename)
427
 
        local chunk = LoadChunk(filename)
428
 
        scripts[scriptName] = chunk
429
 
        return chunk
430
 
end
431
 
 
432
 
 
433
 
function gadget:Initialize()
434
 
        Spring.Echo(string.format("Loading gadget: %-18s  <%s>", ghInfo.name, ghInfo.basename))
435
 
 
436
 
        -- This initialization code has following properties:
437
 
        --  * all used scripts are loaded => early syntax error detection
438
 
        --  * unused scripts aren't loaded
439
 
        --  * files can be arbitrarily ordered in subdirs (like defs)
440
 
        --  * exact path doesn't need to be specified
441
 
        --  * exact path can be specified to resolve ambiguous basenames
442
 
        --  * engine default scriptName (with .cob extension) works
443
 
 
444
 
        -- Recursively collect files below UNITSCRIPT_DIR.
445
 
        local scriptFiles = {}
446
 
        for _,filename in ipairs(RecursiveFileSearch(UNITSCRIPT_DIR, "*.lua", VFSMODE)) do
447
 
                local basename = Basename(filename)
448
 
                scriptFiles[filename] = filename  -- for exact match
449
 
                scriptFiles[basename] = filename  -- for basename match
450
 
        end
451
 
 
452
 
        -- Go through all UnitDefs and load scripts.
453
 
        -- Names are tested in following order:
454
 
        --  * exact match
455
 
        --  * basename match
456
 
        --  * exact match where .cob->.lua
457
 
        --  * basename match where .cob->.lua
458
 
        for i=1,#UnitDefs do
459
 
                local unitDef = UnitDefs[i]
460
 
                if (unitDef and not scripts[unitDef.scriptName]) then
461
 
                        local fn  = UNITSCRIPT_DIR .. unitDef.scriptName:lower()
462
 
                        local bn  = Basename(fn)
463
 
                        local cfn = fn:gsub("%.cob$", "%.lua")
464
 
                        local cbn = bn:gsub("%.cob$", "%.lua")
465
 
                        local filename = scriptFiles[fn] or scriptFiles[bn] or
466
 
                                         scriptFiles[cfn] or scriptFiles[cbn]
467
 
                        if filename then
468
 
                                Spring.Echo("  Loading unit script: " .. filename)
469
 
                                LoadScript(unitDef.scriptName, filename)
470
 
                        end
471
 
                end
472
 
        end
473
 
 
474
 
        -- Fake UnitCreated events for existing units. (for '/luarules reload')
475
 
        local allUnits = Spring.GetAllUnits()
476
 
        for i=1,#allUnits do
477
 
                local unitID = allUnits[i]
478
 
                gadget:UnitCreated(unitID, Spring.GetUnitDefID(unitID))
479
 
        end
480
 
end
481
 
 
482
 
--------------------------------------------------------------------------------
483
 
 
484
 
local StartThread = Spring.UnitScript.StartThread
485
 
 
486
 
 
487
 
local function Wrap_AimWeapon(unitID, callins)
488
 
        local AimWeapon = callins["AimWeapon"]
489
 
        if (not AimWeapon) then return end
490
 
 
491
 
        -- SetUnitShieldState wants true or false, while
492
 
        -- SetUnitWeaponState wants 1 or 0, niiice =)
493
 
        local function AimWeaponThread(weaponNum, heading, pitch)
494
 
                if AimWeapon(weaponNum, heading, pitch) then
495
 
                        -- SetUnitWeaponState counts weapons from 0
496
 
                        return sp_SetUnitWeaponState(unitID, weaponNum - 1, "aimReady", 1)
497
 
                end
498
 
        end
499
 
 
500
 
        callins["AimWeapon"] = function(weaponNum, heading, pitch)
501
 
                return StartThread(AimWeaponThread, weaponNum, heading, pitch)
502
 
        end
503
 
end
504
 
 
505
 
 
506
 
local function Wrap_AimShield(unitID, callins)
507
 
        local AimShield = callins["AimShield"]
508
 
        if (not AimShield) then return end
509
 
 
510
 
        -- SetUnitShieldState wants true or false, while
511
 
        -- SetUnitWeaponState wants 1 or 0, niiice =)
512
 
        local function AimShieldThread(weaponNum)
513
 
                local enabled = AimShield(weaponNum) and true or false
514
 
                -- SetUnitShieldState counts weapons from 0
515
 
                return sp_SetUnitShieldState(unitID, weaponNum - 1, enabled)
516
 
        end
517
 
 
518
 
        callins["AimShield"] = function(weaponNum)
519
 
                return StartThread(AimShieldThread, weaponNum)
520
 
        end
521
 
end
522
 
 
523
 
 
524
 
local function Wrap_Killed(unitID, callins)
525
 
        local Killed = callins["Killed"]
526
 
        if (not Killed) then return end
527
 
 
528
 
        local function KilledThread(recentDamage, maxHealth)
529
 
                -- It is *very* important the sp_SetDeathScriptFinished is executed, even on error.
530
 
                SetOnError(sp_SetDeathScriptFinished)
531
 
                local wreckLevel = Killed(recentDamage, maxHealth)
532
 
                sp_SetDeathScriptFinished(wreckLevel)
533
 
        end
534
 
 
535
 
        callins["Killed"] = function(recentDamage, maxHealth)
536
 
                StartThread(KilledThread, recentDamage, maxHealth)
537
 
                return -- no return value signals Spring to wait for SetDeathScriptFinished call.
538
 
        end
539
 
end
540
 
 
541
 
 
542
 
local function Wrap(callins, name)
543
 
        local fun = callins[name]
544
 
        if (not fun) then return end
545
 
 
546
 
        callins[name] = function(...)
547
 
                return StartThread(fun, ...)
548
 
        end
549
 
end
550
 
 
551
 
--------------------------------------------------------------------------------
552
 
 
553
 
--[[
554
 
Storage for MemoizedInclude.
555
 
Format: { [filename] = chunk }
556
 
--]]
557
 
local include_cache = {}
558
 
 
559
 
 
560
 
local function ScriptInclude(filename)
561
 
        --Spring.Echo("  Loading include: " .. UNITSCRIPT_DIR .. filename)
562
 
        local chunk = LoadChunk(UNITSCRIPT_DIR .. filename)
563
 
        if chunk then
564
 
                include_cache[filename] = chunk
565
 
                return chunk
566
 
        end
567
 
end
568
 
 
569
 
 
570
 
local function MemoizedInclude(filename, env)
571
 
        local chunk = include_cache[filename] or ScriptInclude(filename)
572
 
        if chunk then
573
 
                --overwrite environment so it access environment of current unit
574
 
                setfenv(chunk, env)
575
 
                return chunk()
576
 
        end
577
 
end
578
 
 
579
 
--------------------------------------------------------------------------------
580
 
 
581
 
function gadget:UnitCreated(unitID, unitDefID)
582
 
        local ud = UnitDefs[unitDefID]
583
 
        local chunk = scripts[ud.scriptName]
584
 
        if (not chunk) then return end
585
 
 
586
 
        -- Global variables in the script are still per unit.
587
 
        -- Set up a new environment that is an instance of the prototype
588
 
        -- environment, so we don't need to copy all globals for every unit.
589
 
 
590
 
        -- This means of course, that global variable accesses are a bit more
591
 
        -- expensive inside unit scripts, but this can be worked around easily
592
 
        -- by localizing the necessary globals.
593
 
 
594
 
        local pieces = Spring.GetUnitPieceMap(unitID)
595
 
        local env = {
596
 
                unitID = unitID,
597
 
                unitDefID = unitDefID,
598
 
                script = {},     -- will store the callins
599
 
        }
600
 
 
601
 
        env.include = function(f)
602
 
                return MemoizedInclude(f, env)
603
 
        end
604
 
 
605
 
        env.piece = function(...)
606
 
                local p = {}
607
 
                for _,name in ipairs{...} do
608
 
                        p[#p+1] = pieces[name] or error("piece not found: " .. tostring(name), 2)
609
 
                end
610
 
                return unpack(p)
611
 
        end
612
 
 
613
 
        setmetatable(env, { __index = prototypeEnv })
614
 
        setfenv(chunk, env)
615
 
 
616
 
        -- Execute the chunk. This puts the callins in env.script
617
 
        CallAsUnitNoReturn(unitID, chunk)
618
 
        local callins = env.script
619
 
 
620
 
        -- Add framework callins.
621
 
        callins.MoveFinished = MoveFinished
622
 
        callins.TurnFinished = TurnFinished
623
 
        callins.Destroy = Destroy
624
 
 
625
 
        -- AimWeapon/AimShield is required for a functional weapon/shield,
626
 
        -- so it doesn't hurt to not check other weapons.
627
 
        if ((not callins.AimWeapon and callins.AimWeapon1) or
628
 
            (not callins.AimShield and callins.AimShield1)) then
629
 
                for j=1,#weapon_funcs do
630
 
                        local name = weapon_funcs[j]
631
 
                        local dispatch = {}
632
 
                        local n = 0
633
 
                        for i=1,ud.weapons.n do
634
 
                                local fun = callins[name .. i]
635
 
                                if fun then
636
 
                                        dispatch[i] = fun
637
 
                                        n = n + 1
638
 
                                end
639
 
                        end
640
 
                        if (n == ud.weapons.n) then
641
 
                                -- optimized case
642
 
                                callins[name] = function(w, ...)
643
 
                                        return dispatch[w](...)
644
 
                                end
645
 
                        elseif (n > 0) then
646
 
                                -- needed for QueryWeapon / AimFromWeapon to return -1
647
 
                                -- while AimWeapon / AimShield should return false, etc.
648
 
                                local ret = default_return_values[name]
649
 
                                callins[name] = function(w, ...)
650
 
                                        local fun = dispatch[w]
651
 
                                        if fun then return fun(...) end
652
 
                                        return ret
653
 
                                end
654
 
                        end
655
 
                end
656
 
        end
657
 
 
658
 
        -- Wrap certain callins in a thread and/or safety net.
659
 
        for i=1,#thread_wrap do
660
 
                Wrap(callins, thread_wrap[i])
661
 
        end
662
 
        Wrap_AimWeapon(unitID, callins)
663
 
        Wrap_AimShield(unitID, callins)
664
 
        Wrap_Killed(unitID, callins)
665
 
 
666
 
        -- Wrap everything so activeUnit get's set properly.
667
 
        for k,v in pairs(callins) do
668
 
                local fun = callins[k]
669
 
                callins[k] = function(...)
670
 
                        activeUnit = units[unitID]
671
 
                        return fun(...)
672
 
                end
673
 
        end
674
 
 
675
 
        -- Register the callins with Spring.
676
 
        Spring.UnitScript.CreateScript(unitID, callins)
677
 
 
678
 
        -- Register (must be last: it shouldn't be done in case of error.)
679
 
        units[unitID] = {
680
 
                env = env,
681
 
                unitID = unitID,
682
 
                waitingForMove = {},
683
 
                waitingForTurn = {},
684
 
                threads = setmetatable({}, {__mode = "kv"}), -- weak table
685
 
        }
686
 
 
687
 
        -- Now it's safe to start a thread which will run Create().
688
 
        -- (Spring doesn't run it, and if it did, it would do so too early to be useful.)
689
 
        if callins.Create then
690
 
                CallAsUnitNoReturn(unitID, StartThread, callins.Create)
691
 
        end
692
 
end
693
 
 
694
 
 
695
 
function gadget:GameFrame(n)
696
 
        local zzz = sleepers[n]
697
 
        if zzz then
698
 
                sleepers[n] = nil
699
 
                -- Wake up the lazy bastards.
700
 
                for i=1,#zzz do
701
 
                        local unitID = zzz[i].unitID
702
 
                        activeUnit = units[unitID]
703
 
                        sp_CallAsUnit(unitID, WakeUp, zzz[i])
704
 
                end
705
 
        end
706
 
end
707
 
 
708
 
--------------------------------------------------------------------------------
709
 
--------------------------------------------------------------------------------