diff --git a/OpenRA.sln b/OpenRA.sln index fccae752f4..0b4fd830d1 100644 --- a/OpenRA.sln +++ b/OpenRA.sln @@ -37,6 +37,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common Lua scripts", "Commo mods\common\lua\supportpowers.lua = mods\common\lua\supportpowers.lua mods\common\lua\team.lua = mods\common\lua\team.lua mods\common\lua\utils.lua = mods\common\lua\utils.lua + mods\common\lua\facing.lua = mods\common\lua\facing.lua + mods\common\lua\production.lua = mods\common\lua\production.lua EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tiberian Dawn Lua scripts", "Tiberian Dawn Lua scripts", "{62FCD0D0-6D24-435D-9DD8-3CCADCF7ECAB}" @@ -48,6 +50,24 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tiberian Dawn Lua scripts", mods\cnc\maps\nod03a\nod03a.lua = mods\cnc\maps\nod03a\nod03a.lua mods\cnc\maps\nod03b\nod03b.lua = mods\cnc\maps\nod03b\nod03b.lua mods\cnc\maps\shellmap\shellmap.lua = mods\cnc\maps\shellmap\shellmap.lua + mods\cnc\maps\gdi04a\gdi04a.lua = mods\cnc\maps\gdi04a\gdi04a.lua + mods\cnc\maps\gdi04b\gdi04b.lua = mods\cnc\maps\gdi04b\gdi04b.lua + mods\cnc\maps\gdi04c\gdi04c.lua = mods\cnc\maps\gdi04c\gdi04c.lua + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Red Alert Lua scripts", "Red Alert Lua scripts", "{B35D533F-BEB6-4674-A466-324EEFD97259}" + ProjectSection(SolutionItems) = preProject + mods\ra\maps\allies-01-classic\allies01.lua = mods\ra\maps\allies-01-classic\allies01.lua + mods\ra\maps\allies-02-classic\allies02.lua = mods\ra\maps\allies-02-classic\allies02.lua + mods\ra\maps\desert-shellmap\desert-shellmap.lua = mods\ra\maps\desert-shellmap\desert-shellmap.lua + mods\ra\maps\fort-lonestar\fort-lonestar.lua = mods\ra\maps\fort-lonestar\fort-lonestar.lua + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "System Lua scripts", "System Lua scripts", "{A4D6AEA4-8009-4256-903B-8D227E50452B}" + ProjectSection(SolutionItems) = preProject + lua\sandbox.lua = lua\sandbox.lua + lua\scriptwrapper.lua = lua\scriptwrapper.lua + lua\stacktraceplus.lua = lua\stacktraceplus.lua EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenRA.CrashDialog", "OpenRA.CrashDialog\OpenRA.CrashDialog.csproj", "{47F1B0EE-EB35-47F2-93E4-273C70909157}" diff --git a/lua/stacktraceplus.lua b/lua/stacktraceplus.lua index 8eba226946..fd11736506 100644 --- a/lua/stacktraceplus.lua +++ b/lua/stacktraceplus.lua @@ -1,411 +1,411 @@ --- tables -local _G = _G -local string, io, debug, coroutine = string, io, debug, coroutine - --- functions -local tostring, print, require = tostring, print, require -local next, assert = next, assert -local pcall, type, pairs, ipairs = pcall, type, pairs, ipairs -local error = error - -assert(debug, "debug table must be available at this point") - -local io_open = io.open -local string_gmatch = string.gmatch -local string_sub = string.sub -local table_concat = table.concat - -local _M = { - max_tb_output_len = 70 -- controls the maximum length of the 'stringified' table before cutting with ' (more...)' -} - --- this tables should be weak so the elements in them won't become uncollectable -local m_known_tables = { [_G] = "_G (global table)" } -local function add_known_module(name, desc) - local ok, mod = pcall(require, name) - if ok then - m_known_tables[mod] = desc - end -end - -add_known_module("string", "string module") -add_known_module("io", "io module") -add_known_module("os", "os module") -add_known_module("table", "table module") -add_known_module("math", "math module") -add_known_module("package", "package module") -add_known_module("debug", "debug module") -add_known_module("coroutine", "coroutine module") - --- lua5.2 -add_known_module("bit32", "bit32 module") --- luajit -add_known_module("bit", "bit module") -add_known_module("jit", "jit module") - - -local m_user_known_tables = {} - -local m_known_functions = {} -for _, name in ipairs{ - -- Lua 5.2, 5.1 - "assert", - "collectgarbage", - "dofile", - "error", - "getmetatable", - "ipairs", - "load", - "loadfile", - "next", - "pairs", - "pcall", - "print", - "rawequal", - "rawget", - "rawlen", - "rawset", - "require", - "select", - "setmetatable", - "tonumber", - "tostring", - "type", - "xpcall", - - -- Lua 5.1 - "gcinfo", - "getfenv", - "loadstring", - "module", - "newproxy", - "setfenv", - "unpack", - -- TODO: add table.* etc functions -} do - if _G[name] then - m_known_functions[_G[name]] = name - end -end - - - -local m_user_known_functions = {} - -local function safe_tostring (value) - local ok, err = pcall(tostring, value) - if ok then return err else return (": '%s'"):format(err) end -end - --- Private: --- Parses a line, looking for possible function definitions (in a very naïve way) --- Returns '(anonymous)' if no function name was found in the line -local function ParseLine(line) - assert(type(line) == "string") - --print(line) - local match = line:match("^%s*function%s+(%w+)") - if match then - --print("+++++++++++++function", match) - return match - end - match = line:match("^%s*local%s+function%s+(%w+)") - if match then - --print("++++++++++++local", match) - return match - end - match = line:match("^%s*local%s+(%w+)%s+=%s+function") - if match then - --print("++++++++++++local func", match) - return match - end - match = line:match("%s*function%s*%(") -- this is an anonymous function - if match then - --print("+++++++++++++function2", match) - return "(anonymous)" - end - return "(anonymous)" -end - --- Private: --- Tries to guess a function's name when the debug info structure does not have it. --- It parses either the file or the string where the function is defined. --- Returns '?' if the line where the function is defined is not found -local function GuessFunctionName(info) - --print("guessing function name") - if type(info.source) == "string" and info.source:sub(1,1) == "@" then - local file, err = io_open(info.source:sub(2), "r") - if not file then - print("file not found: "..tostring(err)) -- whoops! - return "?" - end - local line - for i = 1, info.linedefined do - line = file:read("*l") - end - if not line then - print("line not found") -- whoops! - return "?" - end - return ParseLine(line) - else - local line - local lineNumber = 0 - for l in string_gmatch(info.source, "([^\n]+)\n-") do - lineNumber = lineNumber + 1 - if lineNumber == info.linedefined then - line = l - break - end - end - if not line then - print("line not found") -- whoops! - return "?" - end - return ParseLine(line) - end -end - ---- --- Dumper instances are used to analyze stacks and collect its information. --- -local Dumper = {} - -Dumper.new = function(thread) - local t = { lines = {} } - for k,v in pairs(Dumper) do t[k] = v end - - t.dumping_same_thread = (thread == coroutine.running()) - - -- if a thread was supplied, bind it to debug.info and debug.get - -- we also need to skip this additional level we are introducing in the callstack (only if we are running - -- in the same thread we're inspecting) - if type(thread) == "thread" then - t.getinfo = function(level, what) - if t.dumping_same_thread and type(level) == "number" then - level = level + 1 - end - return debug.getinfo(thread, level, what) - end - t.getlocal = function(level, loc) - if t.dumping_same_thread then - level = level + 1 - end - return debug.getlocal(thread, level, loc) - end - else - t.getinfo = debug.getinfo - t.getlocal = debug.getlocal - end - - return t -end - --- helpers for collecting strings to be used when assembling the final trace -function Dumper:add (text) - self.lines[#self.lines + 1] = text -end -function Dumper:add_f (fmt, ...) - self:add(fmt:format(...)) -end -function Dumper:concat_lines () - return table_concat(self.lines) -end - ---- --- Private: --- Iterates over the local variables of a given function. --- --- @param level The stack level where the function is. --- -function Dumper:DumpLocals (level) - local prefix = "\t " - local i = 1 - - if self.dumping_same_thread then - level = level + 1 - end - - local name, value = self.getlocal(level, i) - if not name then - return - end - self:add("\tLocal variables:\r\n") - while name do - if type(value) == "number" then - self:add_f("%s%s = number: %g\r\n", prefix, name, value) - elseif type(value) == "boolean" then - self:add_f("%s%s = boolean: %s\r\n", prefix, name, tostring(value)) - elseif type(value) == "string" then - self:add_f("%s%s = string: %q\r\n", prefix, name, value) - elseif type(value) == "userdata" then - self:add_f("%s%s = %s\r\n", prefix, name, safe_tostring(value)) - elseif type(value) == "nil" then - self:add_f("%s%s = nil\r\n", prefix, name) - elseif type(value) == "table" then - if m_known_tables[value] then - self:add_f("%s%s = %s\r\n", prefix, name, m_known_tables[value]) - elseif m_user_known_tables[value] then - self:add_f("%s%s = %s\r\n", prefix, name, m_user_known_tables[value]) - else - local txt = "{" - for k,v in pairs(value) do - txt = txt..safe_tostring(k)..":"..safe_tostring(v) - if #txt > _M.max_tb_output_len then - txt = txt.." (more...)" - break - end - if next(value, k) then txt = txt..", " end - end - self:add_f("%s%s = %s %s\r\n", prefix, name, safe_tostring(value), txt.."}") - end - elseif type(value) == "function" then - local info = self.getinfo(value, "nS") - local fun_name = info.name or m_known_functions[value] or m_user_known_functions[value] - if info.what == "C" then - self:add_f("%s%s = C %s\r\n", prefix, name, (fun_name and ("function: " .. fun_name) or tostring(value))) - else - local source = info.short_src - if source:sub(2,7) == "string" then - source = source:sub(9) - end - --for k,v in pairs(info) do print(k,v) end - fun_name = fun_name or GuessFunctionName(info) - self:add_f("%s%s = Lua function '%s' (defined at line %d of chunk %s)\r\n", prefix, name, fun_name, info.linedefined, source) - end - elseif type(value) == "thread" then - self:add_f("%sthread %q = %s\r\n", prefix, name, tostring(value)) - end - i = i + 1 - name, value = self.getlocal(level, i) - end -end - - ---- --- Public: --- Collects a detailed stack trace, dumping locals, resolving function names when they're not available, etc. --- This function is suitable to be used as an error handler with pcall or xpcall --- --- @param thread An optional thread whose stack is to be inspected (defaul is the current thread) --- @param message An optional error string or object. --- @param level An optional number telling at which level to start the traceback (default is 1) --- --- Returns a string with the stack trace and a string with the original error. --- -function _M.stacktrace(thread, message, level) - if type(thread) ~= "thread" then - -- shift parameters left - thread, message, level = nil, thread, message - end - - thread = thread or coroutine.running() - - level = level or 1 - - local dumper = Dumper.new(thread) - - local original_error - - if type(message) == "table" then - dumper:add("an error object {\r\n") - local first = true - for k,v in pairs(message) do - if first then - dumper:add(" ") - first = false - else - dumper:add(",\r\n ") - end - dumper:add(safe_tostring(k)) - dumper:add(": ") - dumper:add(safe_tostring(v)) - end - dumper:add("\r\n}") - original_error = dumper:concat_lines() - elseif type(message) == "string" then - dumper:add(message) - original_error = message - end - - dumper:add("\r\n") - dumper:add[[ -Stack Traceback -=============== -]] - --print(error_message) - - local level_to_show = level - if dumper.dumping_same_thread then level = level + 1 end - - local info = dumper.getinfo(level, "nSlf") - while info do - if info.what == "main" then - if string_sub(info.source, 1, 1) == "@" then - dumper:add_f("(%d) main chunk of file '%s' at line %d\r\n", level_to_show, string_sub(info.source, 2), info.currentline) - else - dumper:add_f("(%d) main chunk of %s at line %d\r\n", level_to_show, info.short_src, info.currentline) - end - elseif info.what == "C" then - --print(info.namewhat, info.name) - --for k,v in pairs(info) do print(k,v, type(v)) end - local function_name = m_user_known_functions[info.func] or m_known_functions[info.func] or info.name or tostring(info.func) - dumper:add_f("(%d) %s C function '%s'\r\n", level_to_show, info.namewhat, function_name) - --dumper:add_f("%s%s = C %s\r\n", prefix, name, (m_known_functions[value] and ("function: " .. m_known_functions[value]) or tostring(value))) - elseif info.what == "tail" then - --print("tail") - --for k,v in pairs(info) do print(k,v, type(v)) end--print(info.namewhat, info.name) - dumper:add_f("(%d) tail call\r\n", level_to_show) - dumper:DumpLocals(level) - elseif info.what == "Lua" then - local source = info.short_src - local function_name = m_user_known_functions[info.func] or m_known_functions[info.func] or info.name - if source:sub(2, 7) == "string" then - source = source:sub(9) - end - local was_guessed = false - if not function_name or function_name == "?" then - --for k,v in pairs(info) do print(k,v, type(v)) end - function_name = GuessFunctionName(info) - was_guessed = true - end - -- test if we have a file name - local function_type = (info.namewhat == "") and "function" or info.namewhat - if info.source and info.source:sub(1, 1) == "@" then - dumper:add_f("(%d) Lua %s '%s' at file '%s:%d'%s\r\n", level_to_show, function_type, function_name, info.source:sub(2), info.currentline, was_guessed and " (best guess)" or "") - elseif info.source and info.source:sub(1,1) == '#' then - dumper:add_f("(%d) Lua %s '%s' at template '%s:%d'%s\r\n", level_to_show, function_type, function_name, info.source:sub(2), info.currentline, was_guessed and " (best guess)" or "") - else - dumper:add_f("(%d) Lua %s '%s' at line %d of chunk '%s'\r\n", level_to_show, function_type, function_name, info.currentline, source) - end - dumper:DumpLocals(level) - else - dumper:add_f("(%d) unknown frame %s\r\n", level_to_show, info.what) - end - - level = level + 1 - level_to_show = level_to_show + 1 - info = dumper.getinfo(level, "nSlf") - end - - return dumper:concat_lines(), original_error -end - --- --- Adds a table to the list of known tables -function _M.add_known_table(tab, description) - if m_known_tables[tab] then - error("Cannot override an already known table") - end - m_user_known_tables[tab] = description -end - --- --- Adds a function to the list of known functions -function _M.add_known_function(fun, description) - if m_known_functions[fun] then - error("Cannot override an already known function") - end - m_user_known_functions[fun] = description -end - -return _M +-- tables +local _G = _G +local string, io, debug, coroutine = string, io, debug, coroutine + +-- functions +local tostring, print, require = tostring, print, require +local next, assert = next, assert +local pcall, type, pairs, ipairs = pcall, type, pairs, ipairs +local error = error + +assert(debug, "debug table must be available at this point") + +local io_open = io.open +local string_gmatch = string.gmatch +local string_sub = string.sub +local table_concat = table.concat + +local _M = { + max_tb_output_len = 70 -- controls the maximum length of the 'stringified' table before cutting with ' (more...)' +} + +-- this tables should be weak so the elements in them won't become uncollectable +local m_known_tables = { [_G] = "_G (global table)" } +local function add_known_module(name, desc) + local ok, mod = pcall(require, name) + if ok then + m_known_tables[mod] = desc + end +end + +add_known_module("string", "string module") +add_known_module("io", "io module") +add_known_module("os", "os module") +add_known_module("table", "table module") +add_known_module("math", "math module") +add_known_module("package", "package module") +add_known_module("debug", "debug module") +add_known_module("coroutine", "coroutine module") + +-- lua5.2 +add_known_module("bit32", "bit32 module") +-- luajit +add_known_module("bit", "bit module") +add_known_module("jit", "jit module") + + +local m_user_known_tables = {} + +local m_known_functions = {} +for _, name in ipairs{ + -- Lua 5.2, 5.1 + "assert", + "collectgarbage", + "dofile", + "error", + "getmetatable", + "ipairs", + "load", + "loadfile", + "next", + "pairs", + "pcall", + "print", + "rawequal", + "rawget", + "rawlen", + "rawset", + "require", + "select", + "setmetatable", + "tonumber", + "tostring", + "type", + "xpcall", + + -- Lua 5.1 + "gcinfo", + "getfenv", + "loadstring", + "module", + "newproxy", + "setfenv", + "unpack", + -- TODO: add table.* etc functions +} do + if _G[name] then + m_known_functions[_G[name]] = name + end +end + + + +local m_user_known_functions = {} + +local function safe_tostring (value) + local ok, err = pcall(tostring, value) + if ok then return err else return (": '%s'"):format(err) end +end + +-- Private: +-- Parses a line, looking for possible function definitions (in a very naïve way) +-- Returns '(anonymous)' if no function name was found in the line +local function ParseLine(line) + assert(type(line) == "string") + --print(line) + local match = line:match("^%s*function%s+(%w+)") + if match then + --print("+++++++++++++function", match) + return match + end + match = line:match("^%s*local%s+function%s+(%w+)") + if match then + --print("++++++++++++local", match) + return match + end + match = line:match("^%s*local%s+(%w+)%s+=%s+function") + if match then + --print("++++++++++++local func", match) + return match + end + match = line:match("%s*function%s*%(") -- this is an anonymous function + if match then + --print("+++++++++++++function2", match) + return "(anonymous)" + end + return "(anonymous)" +end + +-- Private: +-- Tries to guess a function's name when the debug info structure does not have it. +-- It parses either the file or the string where the function is defined. +-- Returns '?' if the line where the function is defined is not found +local function GuessFunctionName(info) + --print("guessing function name") + if type(info.source) == "string" and info.source:sub(1,1) == "@" then + local file, err = io_open(info.source:sub(2), "r") + if not file then + print("file not found: "..tostring(err)) -- whoops! + return "?" + end + local line + for i = 1, info.linedefined do + line = file:read("*l") + end + if not line then + print("line not found") -- whoops! + return "?" + end + return ParseLine(line) + else + local line + local lineNumber = 0 + for l in string_gmatch(info.source, "([^\n]+)\n-") do + lineNumber = lineNumber + 1 + if lineNumber == info.linedefined then + line = l + break + end + end + if not line then + print("line not found") -- whoops! + return "?" + end + return ParseLine(line) + end +end + +--- +-- Dumper instances are used to analyze stacks and collect its information. +-- +local Dumper = {} + +Dumper.new = function(thread) + local t = { lines = {} } + for k,v in pairs(Dumper) do t[k] = v end + + t.dumping_same_thread = (thread == coroutine.running()) + + -- if a thread was supplied, bind it to debug.info and debug.get + -- we also need to skip this additional level we are introducing in the callstack (only if we are running + -- in the same thread we're inspecting) + if type(thread) == "thread" then + t.getinfo = function(level, what) + if t.dumping_same_thread and type(level) == "number" then + level = level + 1 + end + return debug.getinfo(thread, level, what) + end + t.getlocal = function(level, loc) + if t.dumping_same_thread then + level = level + 1 + end + return debug.getlocal(thread, level, loc) + end + else + t.getinfo = debug.getinfo + t.getlocal = debug.getlocal + end + + return t +end + +-- helpers for collecting strings to be used when assembling the final trace +function Dumper:add (text) + self.lines[#self.lines + 1] = text +end +function Dumper:add_f (fmt, ...) + self:add(fmt:format(...)) +end +function Dumper:concat_lines () + return table_concat(self.lines) +end + +--- +-- Private: +-- Iterates over the local variables of a given function. +-- +-- @param level The stack level where the function is. +-- +function Dumper:DumpLocals (level) + local prefix = "\t " + local i = 1 + + if self.dumping_same_thread then + level = level + 1 + end + + local name, value = self.getlocal(level, i) + if not name then + return + end + self:add("\tLocal variables:\r\n") + while name do + if type(value) == "number" then + self:add_f("%s%s = number: %g\r\n", prefix, name, value) + elseif type(value) == "boolean" then + self:add_f("%s%s = boolean: %s\r\n", prefix, name, tostring(value)) + elseif type(value) == "string" then + self:add_f("%s%s = string: %q\r\n", prefix, name, value) + elseif type(value) == "userdata" then + self:add_f("%s%s = %s\r\n", prefix, name, safe_tostring(value)) + elseif type(value) == "nil" then + self:add_f("%s%s = nil\r\n", prefix, name) + elseif type(value) == "table" then + if m_known_tables[value] then + self:add_f("%s%s = %s\r\n", prefix, name, m_known_tables[value]) + elseif m_user_known_tables[value] then + self:add_f("%s%s = %s\r\n", prefix, name, m_user_known_tables[value]) + else + local txt = "{" + for k,v in pairs(value) do + txt = txt..safe_tostring(k)..":"..safe_tostring(v) + if #txt > _M.max_tb_output_len then + txt = txt.." (more...)" + break + end + if next(value, k) then txt = txt..", " end + end + self:add_f("%s%s = %s %s\r\n", prefix, name, safe_tostring(value), txt.."}") + end + elseif type(value) == "function" then + local info = self.getinfo(value, "nS") + local fun_name = info.name or m_known_functions[value] or m_user_known_functions[value] + if info.what == "C" then + self:add_f("%s%s = C %s\r\n", prefix, name, (fun_name and ("function: " .. fun_name) or tostring(value))) + else + local source = info.short_src + if source:sub(2,7) == "string" then + source = source:sub(9) + end + --for k,v in pairs(info) do print(k,v) end + fun_name = fun_name or GuessFunctionName(info) + self:add_f("%s%s = Lua function '%s' (defined at line %d of chunk %s)\r\n", prefix, name, fun_name, info.linedefined, source) + end + elseif type(value) == "thread" then + self:add_f("%sthread %q = %s\r\n", prefix, name, tostring(value)) + end + i = i + 1 + name, value = self.getlocal(level, i) + end +end + + +--- +-- Public: +-- Collects a detailed stack trace, dumping locals, resolving function names when they're not available, etc. +-- This function is suitable to be used as an error handler with pcall or xpcall +-- +-- @param thread An optional thread whose stack is to be inspected (defaul is the current thread) +-- @param message An optional error string or object. +-- @param level An optional number telling at which level to start the traceback (default is 1) +-- +-- Returns a string with the stack trace and a string with the original error. +-- +function _M.stacktrace(thread, message, level) + if type(thread) ~= "thread" then + -- shift parameters left + thread, message, level = nil, thread, message + end + + thread = thread or coroutine.running() + + level = level or 1 + + local dumper = Dumper.new(thread) + + local original_error + + if type(message) == "table" then + dumper:add("an error object {\r\n") + local first = true + for k,v in pairs(message) do + if first then + dumper:add(" ") + first = false + else + dumper:add(",\r\n ") + end + dumper:add(safe_tostring(k)) + dumper:add(": ") + dumper:add(safe_tostring(v)) + end + dumper:add("\r\n}") + original_error = dumper:concat_lines() + elseif type(message) == "string" then + dumper:add(message) + original_error = message + end + + dumper:add("\r\n") + dumper:add[[ +Stack Traceback +=============== +]] + --print(error_message) + + local level_to_show = level + if dumper.dumping_same_thread then level = level + 1 end + + local info = dumper.getinfo(level, "nSlf") + while info do + if info.what == "main" then + if string_sub(info.source, 1, 1) == "@" then + dumper:add_f("(%d) main chunk of file '%s' at line %d\r\n", level_to_show, string_sub(info.source, 2), info.currentline) + else + dumper:add_f("(%d) main chunk of %s at line %d\r\n", level_to_show, info.short_src, info.currentline) + end + elseif info.what == "C" then + --print(info.namewhat, info.name) + --for k,v in pairs(info) do print(k,v, type(v)) end + local function_name = m_user_known_functions[info.func] or m_known_functions[info.func] or info.name or tostring(info.func) + dumper:add_f("(%d) %s C function '%s'\r\n", level_to_show, info.namewhat, function_name) + --dumper:add_f("%s%s = C %s\r\n", prefix, name, (m_known_functions[value] and ("function: " .. m_known_functions[value]) or tostring(value))) + elseif info.what == "tail" then + --print("tail") + --for k,v in pairs(info) do print(k,v, type(v)) end--print(info.namewhat, info.name) + dumper:add_f("(%d) tail call\r\n", level_to_show) + dumper:DumpLocals(level) + elseif info.what == "Lua" then + local source = info.short_src + local function_name = m_user_known_functions[info.func] or m_known_functions[info.func] or info.name + if source:sub(2, 7) == "string" then + source = source:sub(9) + end + local was_guessed = false + if not function_name or function_name == "?" then + --for k,v in pairs(info) do print(k,v, type(v)) end + function_name = GuessFunctionName(info) + was_guessed = true + end + -- test if we have a file name + local function_type = (info.namewhat == "") and "function" or info.namewhat + if info.source and info.source:sub(1, 1) == "@" then + dumper:add_f("(%d) Lua %s '%s' at file '%s:%d'%s\r\n", level_to_show, function_type, function_name, info.source:sub(2), info.currentline, was_guessed and " (best guess)" or "") + elseif info.source and info.source:sub(1,1) == '#' then + dumper:add_f("(%d) Lua %s '%s' at template '%s:%d'%s\r\n", level_to_show, function_type, function_name, info.source:sub(2), info.currentline, was_guessed and " (best guess)" or "") + else + dumper:add_f("(%d) Lua %s '%s' at line %d of chunk '%s'\r\n", level_to_show, function_type, function_name, info.currentline, source) + end + dumper:DumpLocals(level) + else + dumper:add_f("(%d) unknown frame %s\r\n", level_to_show, info.what) + end + + level = level + 1 + level_to_show = level_to_show + 1 + info = dumper.getinfo(level, "nSlf") + end + + return dumper:concat_lines(), original_error +end + +-- +-- Adds a table to the list of known tables +function _M.add_known_table(tab, description) + if m_known_tables[tab] then + error("Cannot override an already known table") + end + m_user_known_tables[tab] = description +end + +-- +-- Adds a function to the list of known functions +function _M.add_known_function(fun, description) + if m_known_functions[fun] then + error("Cannot override an already known function") + end + m_user_known_functions[fun] = description +end + +return _M diff --git a/mods/ra/maps/allies-01-classic/mission.lua b/mods/ra/maps/allies-01-classic/allies01.lua similarity index 100% rename from mods/ra/maps/allies-01-classic/mission.lua rename to mods/ra/maps/allies-01-classic/allies01.lua diff --git a/mods/ra/maps/allies-01-classic/map.yaml b/mods/ra/maps/allies-01-classic/map.yaml index dcb3e119e7..8640ce3e31 100644 --- a/mods/ra/maps/allies-01-classic/map.yaml +++ b/mods/ra/maps/allies-01-classic/map.yaml @@ -579,7 +579,7 @@ Rules: -SpawnMPUnits: -MPStartLocations: LuaScriptInterface: - LuaScripts: mission.lua + LuaScripts: allies01.lua TRAN.Extraction: Inherits: TRAN RenderUnit: diff --git a/mods/ra/maps/allies-02-classic/mission.lua b/mods/ra/maps/allies-02-classic/allies02.lua similarity index 100% rename from mods/ra/maps/allies-02-classic/mission.lua rename to mods/ra/maps/allies-02-classic/allies02.lua diff --git a/mods/ra/maps/allies-02-classic/map.yaml b/mods/ra/maps/allies-02-classic/map.yaml index 0f69327aa6..62af7a2df5 100644 --- a/mods/ra/maps/allies-02-classic/map.yaml +++ b/mods/ra/maps/allies-02-classic/map.yaml @@ -874,7 +874,7 @@ Rules: -SpawnMPUnits: -MPStartLocations: LuaScriptInterface: - LuaScripts: mission.lua + LuaScripts: allies02.lua ^Infantry: MustBeDestroyed: ^Tank: diff --git a/mods/ra/maps/fort-lonestar/fort-lonestar.lua b/mods/ra/maps/fort-lonestar/fort-lonestar.lua new file mode 100644 index 0000000000..2af00765bd --- /dev/null +++ b/mods/ra/maps/fort-lonestar/fort-lonestar.lua @@ -0,0 +1,148 @@ +Patrol = { "e1", "e2", "e1" } +Infantry = { "e4", "e1", "e1", "e2", "e1", "e2" } +Vehicles = { "arty", "ftrk", "ftrk", "apc", "apc" } +Tank = { "3tnk" } +LongRange = { "v2rl" } +Boss = { "4tnk" } + +SovietEntryPoints = { Entry1, Entry2, Entry3, Entry4, Entry5, Entry6, Entry7, Entry8 } +PatrolWaypoints = { Patrol1, Patrol2, Patrol3, Patrol4 } +ParadropWaypoints = { Paradrop1, Paradrop2, Paradrop3, Paradrop4 } +OilDerricks = { OilDerrick1, OilDerrick2, OilDerrick3, OilDerrick4 } +SpawnPoints = { Spawn1, Spawn2, Spawn3, Spawn4 } +Snipers = { Sniper1, Sniper2, Sniper3, Sniper4, Sniper5, Sniper6, Sniper7, Sniper8, Sniper9, Sniper10, Sniper11, Sniper12 } + +Wave = 0 +Waves = +{ + { 500, SovietEntryPoints, Infantry, SpawnPoints }, + + { 750, PatrolWaypoints, Patrol, ParadropWaypoints }, + + { 750, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Vehicles, SpawnPoints }, + + { 1500, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + + { 1500, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Vehicles, SpawnPoints }, + + { 1500, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Tank, SpawnPoints }, + { 1, SovietEntryPoints, Vehicles, SpawnPoints }, + + { 1500, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Tank, SpawnPoints }, + { 1, SovietEntryPoints, Tank, SpawnPoints }, + + { 1500, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, LongRange, SpawnPoints }, + + { 1500, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, LongRange, SpawnPoints }, + { 1, SovietEntryPoints, Tank, SpawnPoints }, + { 1, SovietEntryPoints, LongRange, SpawnPoints }, + + { 1500, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, LongRange, SpawnPoints }, + { 1, SovietEntryPoints, LongRange, SpawnPoints }, + { 1, SovietEntryPoints, Tank, SpawnPoints }, + { 1, SovietEntryPoints, Tank, SpawnPoints }, + { 1, SovietEntryPoints, Vehicles, SpawnPoints }, + + { 1500, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Infantry, SpawnPoints }, + { 1, SovietEntryPoints, Boss, SpawnPoints } +} + +SendUnits = function(entryCell, unitTypes, interval, targetCell) + local i = 0 + Utils.Do(unitTypes, function(type) + local a = Actor.Create(type, false, { Owner = soviets, Location = entryCell }) + Trigger.OnIdle(a, function(a) a.AttackMove(targetCell) end) + Trigger.AfterDelay(i * interval, function() a.IsInWorld = true end) + i = i + 1 + end) + + if (Wave < #Waves) then + SendWave() + else + Trigger.AfterDelay(3000, SovietsRetreating) + end +end + +SendWave = function() + Wave = Wave + 1 + local wave = Waves[Wave] + + local delay = wave[1] + local entry = Utils.Random(wave[2]).Location + local units = wave[3] + local target = Utils.Random(wave[4]).Location + + print(string.format("Sending wave %i in %i.", Wave, delay)) + Trigger.AfterDelay(delay, function() SendUnits(entry, units, 40, target) end) +end + +SovietsRetreating = function() + Utils.Do(Snipers, function(a) + if not a.IsDead and a.Owner == soviets then + a.Destroy() + end + end) +end + +WorldLoaded = function() + soviets = Player.GetPlayer("Soviets") + + Utils.Do(Snipers, function(a) + if a.Owner == soviets then + a.Invulnerable = true + end + end) + + SendWave() +end \ No newline at end of file diff --git a/mods/ra/maps/fort-lonestar/map.bin b/mods/ra/maps/fort-lonestar/map.bin new file mode 100644 index 0000000000..f3a2b6011f Binary files /dev/null and b/mods/ra/maps/fort-lonestar/map.bin differ diff --git a/mods/ra/maps/fort-lonestar/map.yaml b/mods/ra/maps/fort-lonestar/map.yaml new file mode 100644 index 0000000000..18b9cf23e9 --- /dev/null +++ b/mods/ra/maps/fort-lonestar/map.yaml @@ -0,0 +1,959 @@ +Selectable: True + +MapFormat: 6 + +RequiresMod: ra + +Title: Fort Lonestar + +Description: Survive multiple waves of attacking enemies. + +Author: Nuke'm Bro. + +Tileset: TEMPERAT + +MapSize: 96,96 + +Bounds: 8,8,48,48 + +UseAsShellmap: False + +Type: Minigame + +Options: + Fog: True + Shroud: True + AllyBuildRadius: False + FragileAlliances: False + StartingCash: 50 + ConfigurableStartingUnits: False + +Players: + PlayerReference@Neutral: + Name: Neutral + OwnsWorld: True + NonCombatant: True + Race: allies + PlayerReference@Multi0: + Name: Multi0 + Playable: True + AllowBots: False + LockTeam: True + Allies: Multi1,Multi2,Multi3 + Enemies: Soviets + PlayerReference@Multi1: + Name: Multi1 + Playable: True + AllowBots: False + LockTeam: True + Allies: Multi0,Multi2,Multi3 + Enemies: Soviets + PlayerReference@Multi2: + Name: Multi2 + Playable: True + AllowBots: False + LockTeam: True + Allies: Multi0,Multi1,Multi3 + Enemies: Soviets + PlayerReference@Multi3: + Name: Multi3 + Playable: True + AllowBots: False + LockTeam: True + Allies: Multi0,Multi1,Multi2 + Enemies: Soviets + PlayerReference@Soviets: + Name: Soviets + Race: soviet + ColorRamp: 0,255,128 + Enemies: Multi0,Multi1,Multi2,Multi3 + +Actors: + Actor76: t11 + Location: 47,27 + Owner: Neutral + Actor47: t12 + Location: 55,24 + Owner: Neutral + Actor25: tc03 + Location: 54,49 + Owner: Neutral + Actor14: tc04 + Location: 24,7 + Owner: Neutral + Actor10: tc04 + Location: 38,53 + Owner: Neutral + Actor0: t14 + Location: 32,16 + Owner: Neutral + Actor75: tc04 + Location: 45,25 + Owner: Neutral + Actor72: tc01 + Location: 49,16 + Owner: Neutral + Actor58: t10 + Location: 22,54 + Owner: Neutral + Actor26: tc01 + Location: 54,41 + Owner: Neutral + Actor37: tc01 + Location: 54,21 + Owner: Neutral + Actor60: tc05 + Location: 7,16 + Owner: Neutral + Actor69: t17 + Location: 23,8 + Owner: Neutral + Actor4: tc04 + Location: 29,45 + Owner: Neutral + Actor6: t17 + Location: 33,43 + Owner: Neutral + Actor16: tc04 + Location: 53,16 + Owner: Neutral + Actor21: tc03 + Location: 8,14 + Owner: Neutral + Actor8: t14 + Location: 49,37 + Owner: Neutral + Actor29: tc01 + Location: 8,42 + Owner: Neutral + Actor49: t15 + Location: 54,47 + Owner: Neutral + Actor62: tc05 + Location: 6,43 + Owner: Neutral + Actor73: tc05 + Location: 16,34 + Owner: Neutral + Actor30: barr + Location: 36,26 + Owner: Multi0 + Actor84: brik + Location: 35,25 + Owner: Neutral + Actor32: tc01 + Location: 8,24 + Owner: Neutral + Actor59: tc05 + Location: 7,39 + Owner: Neutral + Actor5: tc01 + Location: 44,44 + Owner: Neutral + Actor67: brik + Location: 29,25 + Owner: Neutral + Actor41: brik + Location: 25,25 + Owner: Neutral + Actor56: brik + Location: 26,25 + Owner: Neutral + Actor85: brik + Location: 39,26 + Owner: Neutral + Actor81: brik + Location: 38,25 + Owner: Neutral + Actor65: brik + Location: 27,25 + Owner: Neutral + Actor66: brik + Location: 28,25 + Owner: Neutral + Actor51: t08 + Location: 55,46 + Owner: Neutral + Actor57: t12 + Location: 19,54 + Owner: Neutral + Actor54: t11 + Location: 26,54 + Owner: Neutral + Actor33: tc01 + Location: 21,7 + Owner: Neutral + Actor27: tc01 + Location: 42,54 + Owner: Neutral + Actor23: tc03 + Location: 16,54 + Owner: Neutral + Actor15: tc04 + Location: 38,8 + Owner: Neutral + Actor19: tc03 + Location: 18,7 + Owner: Neutral + Actor17: tc03 + Location: 54,26 + Owner: Neutral + Actor83: brik + Location: 36,25 + Owner: Neutral + Actor197: brik + Location: 39,37 + Owner: Neutral + Actor11: tc04 + Location: 20,53 + Owner: Neutral + Actor9: tc04 + Location: 53,37 + Owner: Neutral + Actor7: tc02 + Location: 44,36 + Owner: Neutral + Actor3: t15 + Location: 19,25 + Owner: Neutral + Actor1: t05 + Location: 29,16 + Owner: Neutral + Actor82: brik + Location: 37,25 + Owner: Neutral + Actor86: brik + Location: 39,27 + Owner: Neutral + Actor80: brik + Location: 39,25 + Owner: Neutral + Actor71: tc02 + Location: 15,40 + Owner: Neutral + Actor64: t06 + Location: 18,16 + Owner: Neutral + Actor63: t14 + Location: 8,22 + Owner: Neutral + Actor53: t08 + Location: 41,55 + Owner: Neutral + Actor61: tc05 + Location: 54,39 + Owner: Neutral + Actor48: t01 + Location: 54,23 + Owner: Neutral + Actor50: t17 + Location: 54,44 + Owner: Neutral + Actor36: tc01 + Location: 42,8 + Owner: Neutral + Actor52: t12 + Location: 44,53 + Owner: Neutral + Actor87: brik + Location: 39,28 + Owner: Neutral + Actor91: brik + Location: 37,39 + Owner: Neutral + Actor196: brik + Location: 39,38 + Owner: Neutral + Actor195: brik + Location: 39,39 + Owner: Neutral + Actor45: brik + Location: 25,28 + Owner: Neutral + Actor92: brik + Location: 39,29 + Owner: Neutral + Actor194: brik + Location: 38,39 + Owner: Neutral + Actor55: brik + Location: 25,39 + Owner: Neutral + Actor200: brik + Location: 28,39 + Owner: Neutral + Actor233: brik + Location: 29,39 + Owner: Neutral + Actor240: brik + Location: 39,35 + Owner: Neutral + Actor199: brik + Location: 27,39 + Owner: Neutral + Spawn1: mpspawn + Location: 36,28 + Owner: Neutral + Actor28: tc01 + Location: 24,54 + Owner: Neutral + Actor22: tc03 + Location: 7,46 + Owner: Neutral + Actor24: tc03 + Location: 45,54 + Owner: Neutral + Actor18: tc03 + Location: 45,8 + Owner: Neutral + Actor12: tc04 + Location: 7,37 + Owner: Neutral + Actor13: tc04 + Location: 8,19 + Owner: Neutral + Actor2: tc04 + Location: 14,23 + Owner: Neutral + Actor74: t06 + Location: 19,36 + Owner: Neutral + Actor46: t11 + Location: 54,19 + Owner: Neutral + Actor42: t16 + Location: 53,19 + Owner: Neutral + Actor68: tc05 + Location: 35,7 + Owner: Neutral + Actor239: brik + Location: 39,36 + Owner: Neutral + Actor89: brik + Location: 35,39 + Owner: Neutral + Actor90: brik + Location: 36,39 + Owner: Neutral + OilDerrick1: oilb + Location: 32,30 + Owner: Multi0 + Actor237: brik + Location: 25,35 + Owner: Neutral + Actor236: brik + Location: 25,36 + Owner: Neutral + Actor235: brik + Location: 25,37 + Owner: Neutral + Actor234: brik + Location: 25,38 + Owner: Neutral + Actor44: brik + Location: 25,27 + Owner: Neutral + Actor43: brik + Location: 25,26 + Owner: Neutral + Actor198: brik + Location: 26,39 + Owner: Neutral + Actor88: brik + Location: 25,29 + Owner: Neutral + Entry1: waypoint + Location: 8,8 + Owner: Neutral + Entry2: waypoint + Location: 31,8 + Owner: Neutral + Entry3: waypoint + Location: 55,8 + Owner: Neutral + Entry4: waypoint + Location: 55,32 + Owner: Neutral + Entry5: waypoint + Location: 55,55 + Owner: Neutral + Entry6: waypoint + Location: 32,55 + Owner: Neutral + Entry7: waypoint + Location: 8,55 + Owner: Neutral + Entry8: waypoint + Location: 8,32 + Owner: Neutral + Paradrop1: waypoint + Location: 32,28 + Owner: Neutral + Paradrop2: waypoint + Location: 27,32 + Owner: Neutral + Paradrop3: waypoint + Location: 32,36 + Owner: Neutral + Paradrop4: waypoint + Location: 36,32 + Owner: Neutral + Patrol3: waypoint + Location: 32,46 + Owner: Neutral + Patrol4: waypoint + Location: 46,32 + Owner: Neutral + Patrol2: waypoint + Location: 17,32 + Owner: Neutral + Patrol1: waypoint + Location: 32,18 + Owner: Neutral + Sniper1: sniper + Location: 9,26 + Owner: Soviets + Sniper2: sniper + Location: 10,21 + Owner: Soviets + Sniper3: sniper + Location: 26,9 + Owner: Soviets + Sniper4: sniper + Location: 40,10 + Owner: Soviets + Sniper5: sniper + Location: 53,21 + Owner: Soviets + Sniper6: sniper + Location: 54,25 + Owner: Soviets + Sniper7: sniper + Location: 53,40 + Owner: Soviets + Sniper8: sniper + Location: 54,43 + Owner: Soviets + Sniper9: sniper + Location: 54,46 + Owner: Soviets + Sniper10: sniper + Location: 43,53 + Owner: Soviets + Sniper11: sniper + Location: 8,36 + Owner: Soviets + Sniper12: sniper + Location: 28,55 + Owner: Soviets + Actor100: barr + Location: 27,26 + Owner: Multi3 + Actor99: barr + Location: 27,36 + Owner: Multi2 + Actor31: barr + Location: 36,36 + Owner: Multi1 + OilDerrick2: oilb + Location: 32,32 + Owner: Multi1 + OilDerrick3: oilb + Location: 30,32 + Owner: Multi2 + OilDerrick4: oilb + Location: 30,30 + Owner: Multi3 + Spawn2: mpspawn + Location: 27,28 + Owner: Neutral + Spawn3: mpspawn + Location: 27,38 + Owner: Neutral + Spawn4: mpspawn + Location: 36,38 + Owner: Neutral + +Smudges: + +Rules: + World: + CrateSpawner: + Maximum: 1 + SpawnInterval: 100 + -SpawnMPUnits: + -MPStartLocations: + LuaScript: + Scripts: fort-lonestar.lua + CRATE: + -LevelUpCrateAction: + -GiveMcvCrateAction: + -RevealMapCrateAction: + -HideMapCrateAction: + -ExplodeCrateAction@nuke: + -ExplodeCrateAction@boom: + -ExplodeCrateAction@fire: + -GiveUnitCrateAction@jeep: + -GiveUnitCrateAction@arty: + -GiveUnitCrateAction@v2rl: + -GiveUnitCrateAction@1tnk: + -GiveUnitCrateAction@2tnk: + -GiveUnitCrateAction@3tnk: + -GiveUnitCrateAction@4tnk: + SupportPowerCrateAction@parabombs: + SelectionShares: 30 + HealUnitsCrateAction: + SelectionShares: 30 + GiveCashCrateAction: + Amount: 400 + UseCashTick: yes + SelectionShares: 30 + GiveUnitCrateAction@e7: + Unit: e7 + SelectionShares: 10 + Player: + PlayerResources: + InitialCash: 50 + ClassicProductionQueue@Infantry: + Type: Infantry + BuildSpeed: 1 + LowPowerSlowdown: 3 + ClassicProductionQueue@Defense: + Type: Defense + BuildSpeed: .4 + LowPowerSlowdown: 3 + ^Infantry: + MustBeDestroyed: + ^Vehicle: + MustBeDestroyed: + ^Tank: + MustBeDestroyed: + OILB: + Health: + HP: 3000 + Armor: + Type: Wood + Bib: + RevealsShroud: + Range: 3c0 + CashTrickler: + Period: 250 + Amount: 50 + BARR: + Buildable: + Owner: allies,soviet + Building: + Power: 0 + Health: + HP: 1000 + Production: + Produces: Defense,Infantry + -Sellable: + BaseProvider: + Range: 12 + FTUR: + Building: + Power: 0 + Valued: + Cost: 400 + PBOX: + Building: + Power: 0 + Buildable: + Prerequisites: barr,oilb + Valued: + Cost: 400 + Health: + HP: 200 + Armor: + Type: Heavy + HBOX: + Building: + Power: 0 + GUN: + Buildable: + Owner: None + SAM: + Buildable: + Owner: None + SBAG: + Buildable: + Owner: None + FENC: + Buildable: + Owner: None + MSLO: + Buildable: + Owner: None + GAP: + Buildable: + Owner: None + IRON: + Buildable: + Owner: None + PDOX: + Buildable: + Owner: None + AGUN: + Buildable: + Owner: None + TSLA: + Buildable: + Owner: None + DOG: + Buildable: + Owner: soviet,allies + Prerequisites: barr,oilb + Valued: + Cost: 20 + E1: + Buildable: + Owner: soviet,allies + Prerequisites: barr,oilb + Valued: + Cost: 20 + E2: + Buildable: + Owner: soviet,allies + Prerequisites: barr,oilb + Valued: + Cost: 40 + Explodes: + Weapon: UnitExplodeSmall + Chance: 20 + E3: + Buildable: + Owner: soviet,allies + Prerequisites: barr,oilb + Valued: + Cost: 60 + E4: + Buildable: + Owner: soviet,allies + Prerequisites: barr,oilb + Valued: + Cost: 100 + E6: + Buildable: + Owner: soviet,allies + Prerequisites: barr + Valued: + Cost: 100 + E7: + Buildable: + Owner: soviet,allies + Prerequisites: barr,oilb + Valued: + Cost: 750 + 3TNK: + Inherits: ^Tank + Armament: + Weapon: TankNapalm + Recoil: 200 + RecoilRecovery: 38 + LocalOffset: 0,85,0, 0,-85,0 + MECH: + Buildable: + Owner: None + HIJACKER: + Buildable: + Owner: None + MEDI: + Buildable: + Owner: soviet,allies + Prerequisites: barr,oilb + Valued: + Cost: 100 + SHOK: + Buildable: + Owner: soviet,allies + Prerequisites: barr,oilb + Valued: + Cost: 150 + SNIPER: + Valued: + Cost: 200 + Buildable: + Owner: soviet, allies + Prerequisites: barr,oilb + Health: + HP: 200 + AutoTarget: + InitialStance: Defend + ScriptInvulnerable: + SPY: + Inherits: ^Infantry + Buildable: + Queue: Infantry + BuildPaletteOrder: 60 + Prerequisites: barr,oilb + Owner: allies, soviet + Valued: + Cost: 300 + ARTY: + Inherits: ^Tank + Valued: + Cost: 600 + Health: + HP: 75 + RevealsShroud: + Range: 7c0 + V2RL: + Health: + HP: 100 + 4TNK: + Health: + HP: 2500 + Armor: + Type: Heavy + Mobile: + Speed: 56 + RevealsShroud: + Range: 14c0 + Turreted: + ROT: 1 + AttackTurreted: + PrimaryWeapon: 120mm + SecondaryWeapon: MammothTusk + PrimaryLocalOffset: -4,-5,0,0,0, 4,-5,0,0,0 + SecondaryLocalOffset: -7,2,0,0,25, 7,2,0,0,-25 + PrimaryRecoil: 8 + PrimaryRecoilRecovery: 0.7 + SecondaryRecoil: 2 + Explodes: + Weapon: napalm + EmptyWeapon: napalm + SelfHealing: + Step: 2 + Ticks: 1 + HealIfBelow: 40% + DamageCooldown: 150 + BRIK: + Buildable: + Owner: None + BADR.Bomber: + Inherits: ^Plane + AttackBomber: + Armament: + Weapon: ParaBomb + Health: + HP: 60 + Armor: + Type: Light + Plane: + ROT: 5 + Speed: 280 + LimitedAmmo: + Ammo: 30 + RenderUnit: + Image: mig + WithShadow: + -Selectable: + -GainsExperience: + Tooltip: + Name: Mig Bomber + -EjectOnDeath: + +Sequences: + +VoxelSequences: + +Weapons: + 120mm: + ROF: 150 + Range: 10c0 + Report: CANNON1.AUD + Burst: 6 + Projectile: Bullet + Speed: 204 + High: true + Inaccuracy: 1c682 + Image: 120MM + ContrailLength: 50 + Warhead: + Spread: 256 + Versus: + None: 75% + Wood: 75% + Light: 75% + Concrete: 100% + Explosion: self_destruct + WaterExplosion: self_destruct + InfDeath: 4 + SmudgeType: Crater + Damage: 150 + MammothTusk: + ROF: 300 + Range: 10c0 + Report: MISSILE6.AUD + Burst: 2 + ValidTargets: Ground, Air + Projectile: Missile + Speed: 128 + Arm: 2 + High: true + Shadow: false + Proximity: true + Trail: smokey + ContrailLength: 150 + Inaccuracy: 853 + Image: DRAGON + ROT: 10 + RangeLimit: 80 + Warhead: + Spread: 640 + Versus: + None: 125% + Wood: 110% + Light: 110% + Heavy: 100% + Concrete: 200% + Explosion: nuke + WaterExplosion: nuke + InfDeath: 3 + SmudgeType: Crater + Damage: 250 + TankNapalm: + ROF: 40 + Range: 8c0 + Report: AACANON3.AUD + ValidTargets: Ground + Burst: 6 + BurstDelay: 1 + Projectile: Bullet + Speed: 426 + Image: 120MM + Inaccuracy: 2c512 + Trail: smokey + ContrailLength: 2 + Warhead: + Spread: 341 + Versus: + None: 90% + Wood: 170% + Light: 100% + Heavy: 100% + Concrete: 100% + Explosion: small_explosion + WaterExplosion: small_explosion + InfDeath: 4 + SmudgeType: Scorch + ImpactSound: firebl3.aud + Damage: 15 + ParaBomb: + ROF: 5 + Range: 5c0 + Report: CHUTE1.AUD + Projectile: GravityBomb + Image: BOMBLET + Warhead: + Spread: 426 + Versus: + None: 125% + Wood: 100% + Light: 60% + Concrete: 25% + Explosion: napalm + ImpactSound: firebl3.aud + WaterExplosion: napalm + InfDeath: 5 + SmudgeType: Crater + Damage: 200 + 155mm: + ROF: 10 + Range: 7c5 + Burst: 20 + MinRange: 3c0 + -Report: + Projectile: Bullet + Speed: 170 + Trail: fb4 + Image: fb3 + High: true + Angle: 30 + Inaccuracy: 1c682 + ContrailLength: 2 + Warhead: + Spread: 426 + Versus: + None: 80% + Wood: 100% + Light: 60% + Heavy: 75% + Concrete: 35% + Explosion: small_napalm + WaterExplosion: small_napalm + InfDeath: 5 + SmudgeType: Scorch + ImpactSound: firebl3.aud + Damage: 10 + FLAK-23: + ROF: 10 + Range: 8c0 + Report: AACANON3.AUD + ValidTargets: Air,Ground + Projectile: Bullet + Speed: 1c682 + High: true + Warhead: + Spread: 213 + Versus: + None: 35% + Wood: 30% + Light: 30% + Heavy: 40% + Concrete: 30% + Explosion: med_explosion + Damage: 25 + SCUD: + ROF: 280 + Range: 7c0 + MinRange: 3c0 + Report: MISSILE1.AUD + Projectile: Bullet + Speed: 170 + Arm: 10 + High: true + Shadow: false + Proximity: true + Trail: smokey + Inaccuracy: 426 + Image: V2 + Angle: 216 + Warhead: + Spread: 853 + Versus: + None: 100% + Wood: 90% + Light: 80% + Heavy: 70% + Explosion: nuke + WaterExplosion: large_splash + InfDeath: 3 + SmudgeType: Crater + Damage: 500 + ImpactSound: kaboom1.aud + WaterImpactSound: kaboom1.aud + SilencedPPK: + ROF: 80 + Range: 25c0 + Report: silppk.aud + Projectile: Bullet + Speed: 1c682 + Warhead: + Spread: 128 + Versus: + Wood: 0% + Light: 0% + Heavy: 50% + Concrete: 0% + Explosion: piffs + InfDeath: 2 + Damage: 150 + +Voices: + +Notifications: + +Translations: