├── lib ├── natives-3095a │ ├── g.lua │ └── init.lua ├── constructor ├── quaternionLib.lua ├── xmlhandler │ └── tree.lua ├── ScaleformLib.lua ├── inspect.lua ├── json.lua ├── iniparser.lua ├── auto-updater.lua └── xml2lua.lua └── README.md /lib/natives-3095a/g.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexarobi/stand-lua-constructor/HEAD/lib/natives-3095a/g.lua -------------------------------------------------------------------------------- /lib/natives-3095a/init.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexarobi/stand-lua-constructor/HEAD/lib/natives-3095a/init.lua -------------------------------------------------------------------------------- /lib/constructor/constructor_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexarobi/stand-lua-constructor/HEAD/lib/constructor/constructor_logo.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Constructor 2 | 3 | Constructor is a Lua Script for Stand that allows for creating fun vehicles, maps and player skins by combining existing GTA assets. 4 | 5 | ![Constructpr](https://raw.githubusercontent.com/hexarobi/stand-lua-constructor/main/lib/constructor/constructor_logo.png) 6 | 7 | ## Easy Install 8 | 9 | Install from the Stand Lua Script Respository at `Stand > Lua Scripts > Repository > Constructor`. Check the box to install the script. 10 | 11 | ## How to run Constructor 12 | 13 | Within the Stand menu, goto `Stand>Lua Scripts>Constructor>Start Script` 14 | 15 | ## How to load Constructs 16 | 17 | Goto `Stand>Lua Scripts>Constructor>Load Constructs`. This will browse the content of your `Stand/Constructs` folder, as well as your Stand Garage (`Stand/Vehicles`) 18 | 19 | Constructor can load many kinds of construct files including JSON, XML, INI, and TXT. Put any files you want to make loadable into `Stand/Constructs`. You may organize with sub-folders as desired. 20 | 21 | ## How to find more Construct files 22 | 23 | Constructor comes with a curated collection of constructs to get started with. You can attempt to auto-install these by going to `Constructor>Load Constructs>Options>Auto-Install Curated Constructs`. If that fails you can manually download and install the [curated collection](https://github.com/hexarobi/stand-curated-constructs). 24 | 25 | You can find additional compatible files lots of places: 26 | * Discord - Stand's official discord, as well the Constructor discord (Hexa's Scripts) 27 | * Web - Menyoo XML files are most popular on GTA5-Mods.com 28 | * Other Menus - INI files from PhantomX and 2take1 can be loaded, if you have a source for those files 29 | 30 | ## How to build your own Constructs 31 | 32 | Start by creating a root object. This can be a vehicle, an object, NPC, or player skin. For a vehicle you can choose from your current vehicle, from a list, or entering the model name directly. For a map you can start with a construction cone, or search for any object. For a ped you can start with your current player model, or select from a list, or enter a ped model name. 33 | 34 | Once you have created a root construct, you can edit its various options such as position, visibility or collision. Vehicles can modify LS Customs options, as well as things like speed mods. Peds you can change clothes or weapons, or give animations. 35 | 36 | Most importantly, you can attach other objects, vehicles, peds to your root object. And then the attach more to them. You can browse some curated attachments, but the full object list is also searchable. You can also browse GTA object online at [PlebMasters](https://forge.plebmasters.de/) 37 | 38 | Building a construct is like building with lego bricks. Let your imagination run wild! And don't forget to save and share your creations with others. =) 39 | 40 | # Troubleshooting 41 | 42 | ## Why can’t other players see my construct? 43 | 44 | Since constructs only use assets available in the game, they ARE generally visible to other players. Stand blocks World Object Sync for all users by default, so if your friends use Stand make sure they relax that protection. 45 | 46 | If your construct is very large it may not network to everyone properly. The exact limits aren’t clear, but usually up to about 50 or so objects, vehicles and peds should be ok. Larger constructs may sync better if you clear the map first (Constuctor>Settings>CleanUp) 47 | -------------------------------------------------------------------------------- /lib/quaternionLib.lua: -------------------------------------------------------------------------------- 1 | -- quaternionLib 2 | -- by Murten 3 | 4 | --#region quaternion lib 5 | quaternionLib = {} 6 | 7 | local pi = math.pi 8 | local deg2rad = pi/180 9 | local rad2deg = 180/pi 10 | local abs = math.abs 11 | local sqrt = math.sqrt 12 | local exp = math.exp 13 | local log = math.log 14 | local sin = math.sin 15 | local cos = math.cos 16 | local min = math.min 17 | local max = math.max 18 | local acos = math.acos 19 | local atan = math.atan 20 | 21 | --prints the quaternion 22 | function quaternionLib:print() 23 | print(self:to_string()) 24 | end 25 | 26 | --toasts the quaternion 27 | function quaternionLib:toast() 28 | util.toast(self:to_string()) 29 | end 30 | --toasts the quaternion 31 | function quaternionLib:to_string() 32 | return "X:" .. self.x .. " Y:" .. self.y .. " Z:" .. self.z .. " W:" .. self.w 33 | end 34 | --function to create a new quaternion 35 | function quaternionLib.new(x, y, z, w) 36 | q = { 37 | x = x or 1, 38 | y = y or 0, 39 | z = z or 0, 40 | w = w or 0 41 | } 42 | setmetatable(q, quaternionLib) 43 | quaternionLib.__index = quaternionLib 44 | return q 45 | end 46 | 47 | function quaternionLib:copy() 48 | return quaternionLib.new(self.x, self.y, self.z, self.w) 49 | end 50 | 51 | local q_pointer = memory.alloc(8 * 4) 52 | function quaternionLib.from_entity(ent) 53 | ENTITY.GET_ENTITY_QUATERNION(ent, q_pointer, q_pointer + 8, q_pointer + 16, q_pointer + 24) 54 | return quaternionLib.new( 55 | memory.read_float(q_pointer), 56 | memory.read_float(q_pointer + 8), 57 | memory.read_float(q_pointer + 16), 58 | memory.read_float(q_pointer + 24)) 59 | end 60 | 61 | --Wikipedia go brrrrrr 62 | function quaternionLib.from_euler(x, y, z) 63 | x = x*deg2rad*0.5 64 | y = y*deg2rad*0.5 65 | z = z*deg2rad*0.5 66 | 67 | local cr = cos(x) 68 | local sr = sin(x) 69 | local cp = cos(y) 70 | local sp = sin(y) 71 | local cy = cos(z) 72 | local sy = sin(z) 73 | 74 | local q = quaternionLib.new() 75 | 76 | q.w = cr * cp * cy + sr * sp * sy; 77 | q.x = sr * cp * cy - cr * sp * sy; 78 | q.y = cr * sp * cy + sr * cp * sy; 79 | q.z = cr * cp * sy - sr * sp * cy; 80 | return q 81 | end 82 | 83 | --Wikipedia go brrrrrr 84 | function quaternionLib:to_euler() 85 | local angles = v3.new() 86 | 87 | local sinr_cosp = 2 * (q.w * q.x + q.y * q.z); 88 | local cosr_cosp = 1 - 2 * (q.x * q.x + q.y * q.y); 89 | angles.x = atan(sinr_cosp, cosr_cosp) * rad2deg; 90 | 91 | local sinp = sqrt(1 + 2 * (q.w * q.y - q.x * q.z)); 92 | local cosp = sqrt(1 - 2 * (q.w * q.y - q.x * q.z)); 93 | angles.y = (2 * atan(sinp, cosp) - pi / 2) * rad2deg; 94 | 95 | local siny_cosp = 2 * (q.w * q.z + q.x * q.y); 96 | local cosy_cosp = 1 - 2 * (q.y * q.y + q.z * q.z); 97 | angles.z = atan(siny_cosp, cosy_cosp) * rad2deg; 98 | 99 | return angles; 100 | end 101 | 102 | --return the inverse of the given quaternion 103 | function quaternionLib:inverse() 104 | local quat = quaternionLib.new( 105 | -self.x, 106 | -self.y, 107 | -self.z, 108 | self.w) 109 | 110 | return quat 111 | end 112 | 113 | -- function to multiply two quaternions 114 | function quaternionLib:mul(q2) 115 | local w1, x1, y1, z1 = self.x, self.y, self.z, self.w 116 | local w2, x2, y2, z2 = q2.x, q2.y, q2.z, q2.w 117 | return quaternionLib.new( 118 | w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2, 119 | w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2, 120 | w1 * y2 + y1 * w2 + z1 * x2 - x1 * z2, 121 | w1 * z2 + z1 * w2 + x1 * y2 - y1 * x2 122 | ) 123 | end 124 | 125 | function quaternionLib:from_entity_pointer(pointer) 126 | return quaternionLib.from_euler(entities.get_rotation(pointer):get()) 127 | end 128 | 129 | function quaternionLib:mul_v3(vec) 130 | local new_vec = v3.new(vec) 131 | self:mul_v3_non_alloc(new_vec) 132 | return new_vec 133 | end 134 | 135 | 136 | -- function to multiply a quaternion with a vector 3 (I stole this code. Sue me.) 137 | local temp_store_vec = v3.new() 138 | function quaternionLib:mul_v3_non_alloc(vec) 139 | local num = self.x * 2 140 | local num2 = self.y * 2 141 | local num3 = self.z * 2 142 | local num4 = self.x * num 143 | local num5 = self.y * num2 144 | local num6 = self.z * num3 145 | local num7 = self.x * num2 146 | local num8 = self.x * num3 147 | local num9 = self.y * num3 148 | local num10 = self.w * num 149 | local num11 = self.w * num2 150 | local num12 = self.w * num3 151 | 152 | temp_store_vec.x = vec.x 153 | temp_store_vec.y = vec.y 154 | temp_store_vec.z = vec.z 155 | vec.x = (((1 - (num5 + num6)) * temp_store_vec.x) + ((num7 - num12) * temp_store_vec.y)) + ((num8 + num11) * temp_store_vec.z) 156 | vec.y = (((num7 + num12) * temp_store_vec.x) + ((1 - (num4 + num6)) * temp_store_vec.y)) + ((num9 - num10) * temp_store_vec.z) 157 | vec.z = (((num8 - num11) * temp_store_vec.x) + ((num9 + num10) * temp_store_vec.y)) + ((1 - (num4 + num5)) * temp_store_vec.z) 158 | end 159 | 160 | -- rotate a quaternion by the given eulerAngles 161 | function quaternionLib:rotate(x, y , z) 162 | --create a quaternion from the euler angles and multiply it with the given quaternion. Return the result 163 | return quaternionLib.from_euler(x, y, z):mul(self) 164 | end 165 | 166 | --#endregion 167 | return quaternionLib 168 | -------------------------------------------------------------------------------- /lib/xmlhandler/tree.lua: -------------------------------------------------------------------------------- 1 | local function init() 2 | local obj = { 3 | root = {}, 4 | options = {noreduce = {}} 5 | } 6 | 7 | obj._stack = {obj.root} 8 | return obj 9 | end 10 | 11 | --- @module XML Tree Handler. 12 | -- Generates a lua table from an XML content string. 13 | -- It is a simplified handler which attempts 14 | -- to generate a more 'natural' table based structure which 15 | -- supports many common XML formats. 16 | -- 17 | -- The XML tree structure is mapped directly into a recursive 18 | -- table structure with node names as keys and child elements 19 | -- as either a table of values or directly as a string value 20 | -- for text. Where there is only a single child element this 21 | -- is inserted as a named key - if there are multiple 22 | -- elements these are inserted as a vector (in some cases it 23 | -- may be preferable to always insert elements as a vector 24 | -- which can be specified on a per element basis in the 25 | -- options). Attributes are inserted as a child element with 26 | -- a key of '_attr'. 27 | -- 28 | -- Only Tag/Text & CDATA elements are processed - all others 29 | -- are ignored. 30 | -- 31 | -- This format has some limitations - primarily 32 | -- 33 | -- * Mixed-Content behaves unpredictably - the relationship 34 | -- between text elements and embedded tags is lost and 35 | -- multiple levels of mixed content does not work 36 | -- * If a leaf element has both a text element and attributes 37 | -- then the text must be accessed through a vector (to 38 | -- provide a container for the attribute) 39 | -- 40 | -- In general however this format is relatively useful. 41 | -- 42 | -- It is much easier to understand by running some test 43 | -- data through 'testxml.lua -simpletree' than to read this) 44 | -- 45 | -- Options 46 | -- ======= 47 | -- options.noreduce = { = bool,.. } 48 | -- - Nodes not to reduce children vector even if only 49 | -- one child 50 | -- 51 | -- License: 52 | -- ======== 53 | -- 54 | -- This code is freely distributable under the terms of the [MIT license](LICENSE). 55 | -- 56 | --@author Paul Chakravarti (paulc@passtheaardvark.com) 57 | --@author Manoel Campos da Silva Filho 58 | local tree = init() 59 | 60 | ---Instantiates a new handler object. 61 | --Each instance can handle a single XML. 62 | --By using such a constructor, you can parse 63 | --multiple XML files in the same application. 64 | --@return the handler instance 65 | function tree:new() 66 | local obj = init() 67 | 68 | obj.__index = self 69 | setmetatable(obj, self) 70 | 71 | return obj 72 | end 73 | 74 | --- Recursively removes redundant vectors for nodes 75 | -- with single child elements 76 | function tree:reduce(node, key, parent) 77 | for k,v in pairs(node) do 78 | if type(v) == 'table' then 79 | self:reduce(v,k,node) 80 | end 81 | end 82 | if #node == 1 and not self.options.noreduce[key] and 83 | node._attr == nil and type(parent) == "table" then 84 | parent[key] = node[1] 85 | end 86 | end 87 | 88 | 89 | --- If an object is not an array, 90 | -- creates an empty array and insert that object as the 1st element. 91 | -- 92 | -- It's a workaround for duplicated XML tags outside an inner tag. Check issue #55 for details. 93 | -- It checks if a given tag already exists on the parsing stack. 94 | -- In such a case, if that tag is represented as a single element, 95 | -- an array is created and that element is inserted on it. 96 | -- The existing tag is then replaced by the created array. 97 | -- For instance, if we have a tag x = {attr1=1, attr2=2} 98 | -- and another x tag is found, the previous entry will be changed to an array 99 | -- x = {{attr1=1, attr2=2}}. This way, the duplicated tag will be 100 | -- inserted into this array as x = {{attr1=1, attr2=2}, {attr1=3, attr2=4}} 101 | -- https://github.com/manoelcampos/xml2lua/issues/55 102 | -- 103 | -- @param obj the object to try to convert to an array 104 | -- @return the same object if it's already an array or a new array with the object 105 | -- as the 1st element. 106 | local function convertObjectToArray(obj) 107 | --#obj == 0 verifies if the field is not an array 108 | if #obj == 0 then 109 | local array = {} 110 | table.insert(array, obj) 111 | return array 112 | end 113 | 114 | return obj 115 | end 116 | 117 | ---Parses a start tag. 118 | -- @param tag a {name, attrs} table 119 | -- where name is the name of the tag and attrs 120 | -- is a table containing the atributtes of the tag 121 | function tree:starttag(tag) 122 | local node = {} 123 | if self.parseAttributes == true then 124 | node._attr=tag.attrs 125 | end 126 | 127 | --Table in the stack representing the tag being processed 128 | local current = self._stack[#self._stack] 129 | 130 | if current[tag.name] then 131 | local array = convertObjectToArray(current[tag.name]) 132 | table.insert(array, node) 133 | current[tag.name] = array 134 | else 135 | current[tag.name] = {node} 136 | end 137 | 138 | table.insert(self._stack, node) 139 | end 140 | 141 | ---Parses an end tag. 142 | -- @param tag a {name, attrs} table 143 | -- where name is the name of the tag and attrs 144 | -- is a table containing the atributtes of the tag 145 | function tree:endtag(tag, s) 146 | --Table in the stack representing the tag being processed 147 | --Table in the stack representing the containing tag of the current tag 148 | local prev = self._stack[#self._stack-1] 149 | if not prev[tag.name] then 150 | error("XML Error - Unmatched Tag ["..s..":"..tag.name.."]\n") 151 | end 152 | if prev == self.root then 153 | -- Once parsing complete, recursively reduce tree 154 | self:reduce(prev, nil, nil) 155 | end 156 | 157 | table.remove(self._stack) 158 | end 159 | 160 | ---Parses a tag content. 161 | -- @param t text to process 162 | function tree:text(text) 163 | local current = self._stack[#self._stack] 164 | table.insert(current, text) 165 | end 166 | 167 | ---Parses CDATA tag content. 168 | tree.cdata = tree.text 169 | tree.__index = tree 170 | return tree 171 | -------------------------------------------------------------------------------- /lib/ScaleformLib.lua: -------------------------------------------------------------------------------- 1 | -- ScaleformLib by aaronlink127#0127 2 | 3 | -- local natives, so we dont need to require natives (even though most scripts that require this would probably require them already, so there would be no cost to using them here, i just like this) 4 | local function HAS_SCALEFORM_MOVIE_LOADED(scaleformHandle)native_invoker.begin_call()native_invoker.push_arg_int(scaleformHandle)native_invoker.end_call("85F01B8D5B90570E")return native_invoker.get_return_value_bool()end 5 | local function DRAW_SCALEFORM_MOVIE(scaleformHandle,x,y,width,height,red,green,blue,alpha,unk)native_invoker.begin_call()native_invoker.push_arg_int(scaleformHandle)native_invoker.push_arg_float(x)native_invoker.push_arg_float(y)native_invoker.push_arg_float(width)native_invoker.push_arg_float(height)native_invoker.push_arg_int(red)native_invoker.push_arg_int(green)native_invoker.push_arg_int(blue)native_invoker.push_arg_int(alpha)native_invoker.push_arg_int(unk)native_invoker.end_call("54972ADAF0294A93")end 6 | local function DRAW_SCALEFORM_MOVIE_FULLSCREEN(scaleform,red,green,blue,alpha,unk)native_invoker.begin_call()native_invoker.push_arg_int(scaleform)native_invoker.push_arg_int(red)native_invoker.push_arg_int(green)native_invoker.push_arg_int(blue)native_invoker.push_arg_int(alpha)native_invoker.push_arg_int(unk)native_invoker.end_call("0DF606929C105BE1")end 7 | local function DRAW_SCALEFORM_MOVIE_3D(scaleform,posX,posY,posZ,rotX,rotY,rotZ,p7,p8,p9,scaleX,scaleY,scaleZ,p13)native_invoker.begin_call()native_invoker.push_arg_int(scaleform)native_invoker.push_arg_float(posX)native_invoker.push_arg_float(posY)native_invoker.push_arg_float(posZ)native_invoker.push_arg_float(rotX)native_invoker.push_arg_float(rotY)native_invoker.push_arg_float(rotZ)native_invoker.push_arg_float(p7)native_invoker.push_arg_float(p8)native_invoker.push_arg_float(p9)native_invoker.push_arg_float(scaleX)native_invoker.push_arg_float(scaleY)native_invoker.push_arg_float(scaleZ)native_invoker.push_arg_int(p13)native_invoker.end_call("87D51D72255D4E78")end 8 | local function DRAW_SCALEFORM_MOVIE_3D_SOLID(scaleform,posX,posY,posZ,rotX,rotY,rotZ,p7,p8,p9,scaleX,scaleY,scaleZ,p13)native_invoker.begin_call()native_invoker.push_arg_int(scaleform)native_invoker.push_arg_float(posX)native_invoker.push_arg_float(posY)native_invoker.push_arg_float(posZ)native_invoker.push_arg_float(rotX)native_invoker.push_arg_float(rotY)native_invoker.push_arg_float(rotZ)native_invoker.push_arg_float(p7)native_invoker.push_arg_float(p8)native_invoker.push_arg_float(p9)native_invoker.push_arg_float(scaleX)native_invoker.push_arg_float(scaleY)native_invoker.push_arg_float(scaleZ)native_invoker.push_arg_int(p13)native_invoker.end_call("1CE592FDC749D6F5")end 9 | local function SET_SCALEFORM_MOVIE_AS_NO_LONGER_NEEDED(scaleformHandle)native_invoker.begin_call()native_invoker.push_arg_pointer(scaleformHandle)native_invoker.end_call("1D132D614DD86811")end 10 | local function REQUEST_SCALEFORM_MOVIE(scaleformName)native_invoker.begin_call()native_invoker.push_arg_string(scaleformName)native_invoker.end_call("11FE353CF9733E6F")return native_invoker.get_return_value_int()end 11 | local function BEGIN_SCALEFORM_MOVIE_METHOD(scaleform,methodName)native_invoker.begin_call()native_invoker.push_arg_int(scaleform)native_invoker.push_arg_string(methodName)native_invoker.end_call("F6E48914C7A8694E")return native_invoker.get_return_value_bool()end 12 | local function END_SCALEFORM_MOVIE_METHOD()native_invoker.begin_call()native_invoker.end_call("C6796A8FFA375E53")end 13 | -- again, localized natives, except in a table so that i can give different types different push functions automatically 14 | local scaleform_types={ 15 | ["number"]=function(value)native_invoker.begin_call()native_invoker.push_arg_float(value)native_invoker.end_call("D69736AAE04DB51A")end, 16 | ["string"]=function(value)native_invoker.begin_call()native_invoker.push_arg_string(value)native_invoker.end_call("E83A3E3557A56640")end, 17 | ["boolean"]=function(value)native_invoker.begin_call()native_invoker.push_arg_bool(value)native_invoker.end_call("C58424BA936EB458")end 18 | } 19 | -- generic call scaleform function 20 | local function CallScaleformMethod(sf, method, ...) 21 | local args = {...} 22 | if BEGIN_SCALEFORM_MOVIE_METHOD(sf, method) then 23 | for i=1,#args do 24 | local arg = args[i] 25 | local type = type(arg) 26 | local push_f = scaleform_types[type] 27 | if push_f then 28 | push_f(arg) 29 | else 30 | error("Invalid type passed to scaleform method: "..type) 31 | end 32 | end 33 | END_SCALEFORM_MOVIE_METHOD() 34 | --[[ this error, while nice to have for debugging, is probably not nice for release code 35 | else 36 | error("Scaleform movie method fail (Scaleform not loaded?)") ]] 37 | end 38 | end 39 | local ScaleformFunctions = { 40 | draw=function(self, x, y, w, h) 41 | DRAW_SCALEFORM_MOVIE(self.id, x, y, w, h, 255, 255, 255, 255, 1) 42 | end, 43 | draw_fullscreen=function(self) 44 | DRAW_SCALEFORM_MOVIE_FULLSCREEN(self.id, 255, 255, 255, 255, 1) 45 | end, 46 | draw_3d=function(self, pos, rot, size) 47 | pos = pos or {x=0,y=0,z=0} 48 | rot = rot or {x=0,y=0,z=0} 49 | size = size or {x=1,y=1,z=1} 50 | DRAW_SCALEFORM_MOVIE_3D(self.id, pos.x, pos.y, pos.z, rot.x, rot.y, rot.z, 0, 2, 0, size.x, size.y, size.z, 1) 51 | end, 52 | draw_3d_solid=function(self, pos, rot, size) 53 | pos = pos or {x=0,y=0,z=0} 54 | rot = rot or {x=0,y=0,z=0} 55 | size = size or {x=1,y=1,z=1} 56 | DRAW_SCALEFORM_MOVIE_3D_SOLID(self.id, pos.x, pos.y, pos.z, rot.x, rot.y, rot.z, 0, 2, 0, size.x, size.y, size.z, 1) 57 | end, 58 | delete=function(self) 59 | local mem = memory.alloc(4) 60 | memory.write_int(mem, self.id) 61 | -- part of the hacky solution 62 | util.spoof_script("stats_controller",function() 63 | SET_SCALEFORM_MOVIE_AS_NO_LONGER_NEEDED(mem) 64 | end) 65 | memory.free(mem) 66 | end 67 | } 68 | local metaScaleform = { 69 | __index=function(self, key) 70 | return ScaleformFunctions[key] or function(...) 71 | CallScaleformMethod(self.id, key, ...) 72 | end 73 | end 74 | } 75 | local function Scaleform(id) 76 | if type(id) == "string" then 77 | -- this spoof script is a hacky solution to certain problems (mainly with instructional_buttons) but should work 78 | util.spoof_script("stats_controller",function() 79 | id = REQUEST_SCALEFORM_MOVIE(id) 80 | end) 81 | while not HAS_SCALEFORM_MOVIE_LOADED(id) do 82 | util.yield() 83 | end 84 | end 85 | local tbl = {id=id} 86 | setmetatable(tbl,metaScaleform) 87 | return tbl 88 | end 89 | return Scaleform -------------------------------------------------------------------------------- /lib/inspect.lua: -------------------------------------------------------------------------------- 1 | local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local math = _tl_compat and _tl_compat.math or math; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table 2 | local inspect = {Options = {}, } 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | inspect._VERSION = 'inspect.lua 3.1.0' 21 | inspect._URL = 'http://github.com/kikito/inspect.lua' 22 | inspect._DESCRIPTION = 'human-readable representations of tables' 23 | inspect._LICENSE = [[ 24 | MIT LICENSE 25 | 26 | Copyright (c) 2022 Enrique García Cota 27 | 28 | Permission is hereby granted, free of charge, to any person obtaining a 29 | copy of this software and associated documentation files (the 30 | "Software"), to deal in the Software without restriction, including 31 | without limitation the rights to use, copy, modify, merge, publish, 32 | distribute, sublicense, and/or sell copies of the Software, and to 33 | permit persons to whom the Software is furnished to do so, subject to 34 | the following conditions: 35 | 36 | The above copyright notice and this permission notice shall be included 37 | in all copies or substantial portions of the Software. 38 | 39 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 40 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 41 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 42 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 43 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 44 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 45 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 46 | ]] 47 | inspect.KEY = setmetatable({}, { __tostring = function() return 'inspect.KEY' end }) 48 | inspect.METATABLE = setmetatable({}, { __tostring = function() return 'inspect.METATABLE' end }) 49 | 50 | local tostring = tostring 51 | local rep = string.rep 52 | local match = string.match 53 | local char = string.char 54 | local gsub = string.gsub 55 | local fmt = string.format 56 | 57 | local function rawpairs(t) 58 | return next, t, nil 59 | end 60 | 61 | 62 | 63 | local function smartQuote(str) 64 | if match(str, '"') and not match(str, "'") then 65 | return "'" .. str .. "'" 66 | end 67 | return '"' .. gsub(str, '"', '\\"') .. '"' 68 | end 69 | 70 | 71 | local shortControlCharEscapes = { 72 | ["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", 73 | ["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v", ["\127"] = "\\127", 74 | } 75 | local longControlCharEscapes = { ["\127"] = "\127" } 76 | for i = 0, 31 do 77 | local ch = char(i) 78 | if not shortControlCharEscapes[ch] then 79 | shortControlCharEscapes[ch] = "\\" .. i 80 | longControlCharEscapes[ch] = fmt("\\%03d", i) 81 | end 82 | end 83 | 84 | local function escape(str) 85 | return (gsub(gsub(gsub(str, "\\", "\\\\"), 86 | "(%c)%f[0-9]", longControlCharEscapes), 87 | "%c", shortControlCharEscapes)) 88 | end 89 | 90 | local function isIdentifier(str) 91 | return type(str) == "string" and not not str:match("^[_%a][_%a%d]*$") 92 | end 93 | 94 | local flr = math.floor 95 | local function isSequenceKey(k, sequenceLength) 96 | return type(k) == "number" and 97 | flr(k) == k and 98 | 1 <= (k) and 99 | k <= sequenceLength 100 | end 101 | 102 | local defaultTypeOrders = { 103 | ['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4, 104 | ['function'] = 5, ['userdata'] = 6, ['thread'] = 7, 105 | } 106 | 107 | local function sortKeys(a, b) 108 | local ta, tb = type(a), type(b) 109 | 110 | 111 | if ta == tb and (ta == 'string' or ta == 'number') then 112 | return (a) < (b) 113 | end 114 | 115 | local dta = defaultTypeOrders[ta] or 100 116 | local dtb = defaultTypeOrders[tb] or 100 117 | 118 | 119 | return dta == dtb and ta < tb or dta < dtb 120 | end 121 | 122 | local function getKeys(t) 123 | 124 | local seqLen = 1 125 | while rawget(t, seqLen) ~= nil do 126 | seqLen = seqLen + 1 127 | end 128 | seqLen = seqLen - 1 129 | 130 | local keys, keysLen = {}, 0 131 | for k in rawpairs(t) do 132 | if not isSequenceKey(k, seqLen) then 133 | keysLen = keysLen + 1 134 | keys[keysLen] = k 135 | end 136 | end 137 | table.sort(keys, sortKeys) 138 | return keys, keysLen, seqLen 139 | end 140 | 141 | local function countCycles(x, cycles) 142 | if type(x) == "table" then 143 | if cycles[x] then 144 | cycles[x] = cycles[x] + 1 145 | else 146 | cycles[x] = 1 147 | for k, v in rawpairs(x) do 148 | countCycles(k, cycles) 149 | countCycles(v, cycles) 150 | end 151 | countCycles(getmetatable(x), cycles) 152 | end 153 | end 154 | end 155 | 156 | local function makePath(path, a, b) 157 | local newPath = {} 158 | local len = #path 159 | for i = 1, len do newPath[i] = path[i] end 160 | 161 | newPath[len + 1] = a 162 | newPath[len + 2] = b 163 | 164 | return newPath 165 | end 166 | 167 | 168 | local function processRecursive(process, 169 | item, 170 | path, 171 | visited) 172 | if item == nil then return nil end 173 | if visited[item] then return visited[item] end 174 | 175 | local processed = process(item, path) 176 | if type(processed) == "table" then 177 | local processedCopy = {} 178 | visited[item] = processedCopy 179 | local processedKey 180 | 181 | for k, v in rawpairs(processed) do 182 | processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited) 183 | if processedKey ~= nil then 184 | processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited) 185 | end 186 | end 187 | 188 | local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited) 189 | if type(mt) ~= 'table' then mt = nil end 190 | setmetatable(processedCopy, mt) 191 | processed = processedCopy 192 | end 193 | return processed 194 | end 195 | 196 | local function puts(buf, str) 197 | buf.n = buf.n + 1 198 | buf[buf.n] = str 199 | end 200 | 201 | 202 | 203 | local Inspector = {} 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | local Inspector_mt = { __index = Inspector } 215 | 216 | local function tabify(inspector) 217 | puts(inspector.buf, inspector.newline .. rep(inspector.indent, inspector.level)) 218 | end 219 | 220 | function Inspector:getId(v) 221 | local id = self.ids[v] 222 | local ids = self.ids 223 | if not id then 224 | local tv = type(v) 225 | id = (ids[tv] or 0) + 1 226 | ids[v], ids[tv] = id, id 227 | end 228 | return tostring(id) 229 | end 230 | 231 | function Inspector:putValue(v) 232 | local buf = self.buf 233 | local tv = type(v) 234 | if tv == 'string' then 235 | puts(buf, smartQuote(escape(v))) 236 | elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or 237 | tv == 'cdata' or tv == 'ctype' then 238 | puts(buf, tostring(v)) 239 | elseif tv == 'table' and not self.ids[v] then 240 | local t = v 241 | 242 | if t == inspect.KEY or t == inspect.METATABLE then 243 | puts(buf, tostring(t)) 244 | elseif self.level >= self.depth then 245 | puts(buf, '{...}') 246 | else 247 | if self.cycles[t] > 1 then puts(buf, fmt('<%d>', self:getId(t))) end 248 | 249 | local keys, keysLen, seqLen = getKeys(t) 250 | 251 | puts(buf, '{') 252 | self.level = self.level + 1 253 | 254 | for i = 1, seqLen + keysLen do 255 | if i > 1 then puts(buf, ',') end 256 | if i <= seqLen then 257 | puts(buf, ' ') 258 | self:putValue(t[i]) 259 | else 260 | local k = keys[i - seqLen] 261 | tabify(self) 262 | if isIdentifier(k) then 263 | puts(buf, k) 264 | else 265 | puts(buf, "[") 266 | self:putValue(k) 267 | puts(buf, "]") 268 | end 269 | puts(buf, ' = ') 270 | self:putValue(t[k]) 271 | end 272 | end 273 | 274 | local mt = getmetatable(t) 275 | if type(mt) == 'table' then 276 | if seqLen + keysLen > 0 then puts(buf, ',') end 277 | tabify(self) 278 | puts(buf, ' = ') 279 | self:putValue(mt) 280 | end 281 | 282 | self.level = self.level - 1 283 | 284 | if keysLen > 0 or type(mt) == 'table' then 285 | tabify(self) 286 | elseif seqLen > 0 then 287 | puts(buf, ' ') 288 | end 289 | 290 | puts(buf, '}') 291 | end 292 | 293 | else 294 | puts(buf, fmt('<%s %d>', tv, self:getId(v))) 295 | end 296 | end 297 | 298 | 299 | 300 | 301 | function inspect.inspect(root, options) 302 | options = options or {} 303 | 304 | local depth = options.depth or (math.huge) 305 | local newline = options.newline or '\n' 306 | local indent = options.indent or ' ' 307 | local process = options.process 308 | 309 | if process then 310 | root = processRecursive(process, root, {}, {}) 311 | end 312 | 313 | local cycles = {} 314 | countCycles(root, cycles) 315 | 316 | local inspector = setmetatable({ 317 | buf = { n = 0 }, 318 | ids = {}, 319 | cycles = cycles, 320 | depth = depth, 321 | level = 0, 322 | newline = newline, 323 | indent = indent, 324 | }, Inspector_mt) 325 | 326 | inspector:putValue(root) 327 | 328 | return table.concat(inspector.buf) 329 | end 330 | 331 | setmetatable(inspect, { 332 | __call = function(_, root, options) 333 | return inspect.inspect(root, options) 334 | end, 335 | }) 336 | 337 | return inspect 338 | 339 | -------------------------------------------------------------------------------- /lib/json.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- json.lua 3 | -- 4 | -- Copyright (c) 2020 rxi 5 | -- 6 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | -- this software and associated documentation files (the "Software"), to deal in 8 | -- the Software without restriction, including without limitation the rights to 9 | -- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 | -- of the Software, and to permit persons to whom the Software is furnished to do 11 | -- so, subject to the following conditions: 12 | -- 13 | -- The above copyright notice and this permission notice shall be included in all 14 | -- copies or substantial portions of the Software. 15 | -- 16 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | -- SOFTWARE. 23 | -- 24 | 25 | local json = { _version = "0.1.2" } 26 | 27 | ------------------------------------------------------------------------------- 28 | -- Encode 29 | ------------------------------------------------------------------------------- 30 | 31 | local encode 32 | 33 | local escape_char_map = { 34 | [ "\\" ] = "\\", 35 | [ "\"" ] = "\"", 36 | [ "\b" ] = "b", 37 | [ "\f" ] = "f", 38 | [ "\n" ] = "n", 39 | [ "\r" ] = "r", 40 | [ "\t" ] = "t", 41 | } 42 | 43 | local escape_char_map_inv = { [ "/" ] = "/" } 44 | for k, v in pairs(escape_char_map) do 45 | escape_char_map_inv[v] = k 46 | end 47 | 48 | 49 | local function escape_char(c) 50 | return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) 51 | end 52 | 53 | 54 | local function encode_nil(val) 55 | return "null" 56 | end 57 | 58 | 59 | local function encode_table(val, stack) 60 | local res = {} 61 | stack = stack or {} 62 | 63 | -- Circular reference? 64 | if stack[val] then error("circular reference") end 65 | 66 | stack[val] = true 67 | 68 | if rawget(val, 1) ~= nil or next(val) == nil then 69 | -- Treat as array -- check keys are valid and it is not sparse 70 | local n = 0 71 | for k in pairs(val) do 72 | if type(k) ~= "number" then 73 | error("invalid table: mixed or invalid key types") 74 | end 75 | n = n + 1 76 | end 77 | if n ~= #val then 78 | error("invalid table: sparse array") 79 | end 80 | -- Encode 81 | for i, v in ipairs(val) do 82 | table.insert(res, encode(v, stack)) 83 | end 84 | stack[val] = nil 85 | return "[" .. table.concat(res, ",") .. "]" 86 | 87 | else 88 | -- Treat as an object 89 | for k, v in pairs(val) do 90 | if type(k) ~= "string" then 91 | error("invalid table: mixed or invalid key types") 92 | end 93 | table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) 94 | end 95 | stack[val] = nil 96 | return "{" .. table.concat(res, ",") .. "}" 97 | end 98 | end 99 | 100 | 101 | local function encode_string(val) 102 | return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' 103 | end 104 | 105 | 106 | local function encode_number(val) 107 | -- Check for NaN, -inf and inf 108 | if val ~= val or val <= -math.huge or val >= math.huge then 109 | error("unexpected number value '" .. tostring(val) .. "'") 110 | end 111 | return string.format("%.14g", val) 112 | end 113 | 114 | 115 | local type_func_map = { 116 | [ "nil" ] = encode_nil, 117 | [ "table" ] = encode_table, 118 | [ "string" ] = encode_string, 119 | [ "number" ] = encode_number, 120 | [ "boolean" ] = tostring, 121 | } 122 | 123 | 124 | encode = function(val, stack) 125 | local t = type(val) 126 | local f = type_func_map[t] 127 | if f then 128 | return f(val, stack) 129 | end 130 | error("unexpected type '" .. t .. "'") 131 | end 132 | 133 | 134 | function json.encode(val) 135 | return ( encode(val) ) 136 | end 137 | 138 | 139 | ------------------------------------------------------------------------------- 140 | -- Decode 141 | ------------------------------------------------------------------------------- 142 | 143 | local parse 144 | 145 | local function create_set(...) 146 | local res = {} 147 | for i = 1, select("#", ...) do 148 | res[ select(i, ...) ] = true 149 | end 150 | return res 151 | end 152 | 153 | local space_chars = create_set(" ", "\t", "\r", "\n") 154 | local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") 155 | local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") 156 | local literals = create_set("true", "false", "null") 157 | 158 | local literal_map = { 159 | [ "true" ] = true, 160 | [ "false" ] = false, 161 | [ "null" ] = nil, 162 | } 163 | 164 | 165 | local function next_char(str, idx, set, negate) 166 | for i = idx, #str do 167 | if set[str:sub(i, i)] ~= negate then 168 | return i 169 | end 170 | end 171 | return #str + 1 172 | end 173 | 174 | 175 | local function decode_error(str, idx, msg) 176 | local line_count = 1 177 | local col_count = 1 178 | for i = 1, idx - 1 do 179 | col_count = col_count + 1 180 | if str:sub(i, i) == "\n" then 181 | line_count = line_count + 1 182 | col_count = 1 183 | end 184 | end 185 | error( string.format("%s at line %d col %d", msg, line_count, col_count) ) 186 | end 187 | 188 | 189 | local function codepoint_to_utf8(n) 190 | -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa 191 | local f = math.floor 192 | if n <= 0x7f then 193 | return string.char(n) 194 | elseif n <= 0x7ff then 195 | return string.char(f(n / 64) + 192, n % 64 + 128) 196 | elseif n <= 0xffff then 197 | return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) 198 | elseif n <= 0x10ffff then 199 | return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, 200 | f(n % 4096 / 64) + 128, n % 64 + 128) 201 | end 202 | error( string.format("invalid unicode codepoint '%x'", n) ) 203 | end 204 | 205 | 206 | local function parse_unicode_escape(s) 207 | local n1 = tonumber( s:sub(1, 4), 16 ) 208 | local n2 = tonumber( s:sub(7, 10), 16 ) 209 | -- Surrogate pair? 210 | if n2 then 211 | return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) 212 | else 213 | return codepoint_to_utf8(n1) 214 | end 215 | end 216 | 217 | 218 | local function parse_string(str, i) 219 | local res = "" 220 | local j = i + 1 221 | local k = j 222 | 223 | while j <= #str do 224 | local x = str:byte(j) 225 | 226 | if x < 32 then 227 | decode_error(str, j, "control character in string") 228 | 229 | elseif x == 92 then -- `\`: Escape 230 | res = res .. str:sub(k, j - 1) 231 | j = j + 1 232 | local c = str:sub(j, j) 233 | if c == "u" then 234 | local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) 235 | or str:match("^%x%x%x%x", j + 1) 236 | or decode_error(str, j - 1, "invalid unicode escape in string") 237 | res = res .. parse_unicode_escape(hex) 238 | j = j + #hex 239 | else 240 | if not escape_chars[c] then 241 | decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") 242 | end 243 | res = res .. escape_char_map_inv[c] 244 | end 245 | k = j + 1 246 | 247 | elseif x == 34 then -- `"`: End of string 248 | res = res .. str:sub(k, j - 1) 249 | return res, j + 1 250 | end 251 | 252 | j = j + 1 253 | end 254 | 255 | decode_error(str, i, "expected closing quote for string") 256 | end 257 | 258 | 259 | local function parse_number(str, i) 260 | local x = next_char(str, i, delim_chars) 261 | local s = str:sub(i, x - 1) 262 | local n = tonumber(s) 263 | if not n then 264 | decode_error(str, i, "invalid number '" .. s .. "'") 265 | end 266 | return n, x 267 | end 268 | 269 | 270 | local function parse_literal(str, i) 271 | local x = next_char(str, i, delim_chars) 272 | local word = str:sub(i, x - 1) 273 | if not literals[word] then 274 | decode_error(str, i, "invalid literal '" .. word .. "'") 275 | end 276 | return literal_map[word], x 277 | end 278 | 279 | 280 | local function parse_array(str, i) 281 | local res = {} 282 | local n = 1 283 | i = i + 1 284 | while 1 do 285 | local x 286 | i = next_char(str, i, space_chars, true) 287 | -- Empty / end of array? 288 | if str:sub(i, i) == "]" then 289 | i = i + 1 290 | break 291 | end 292 | -- Read token 293 | x, i = parse(str, i) 294 | res[n] = x 295 | n = n + 1 296 | -- Next token 297 | i = next_char(str, i, space_chars, true) 298 | local chr = str:sub(i, i) 299 | i = i + 1 300 | if chr == "]" then break end 301 | if chr ~= "," then decode_error(str, i, "expected ']' or ','") end 302 | end 303 | return res, i 304 | end 305 | 306 | 307 | local function parse_object(str, i) 308 | local res = {} 309 | i = i + 1 310 | while 1 do 311 | local key, val 312 | i = next_char(str, i, space_chars, true) 313 | -- Empty / end of object? 314 | if str:sub(i, i) == "}" then 315 | i = i + 1 316 | break 317 | end 318 | -- Read key 319 | if str:sub(i, i) ~= '"' then 320 | decode_error(str, i, "expected string for key") 321 | end 322 | key, i = parse(str, i) 323 | -- Read ':' delimiter 324 | i = next_char(str, i, space_chars, true) 325 | if str:sub(i, i) ~= ":" then 326 | decode_error(str, i, "expected ':' after key") 327 | end 328 | i = next_char(str, i + 1, space_chars, true) 329 | -- Read value 330 | val, i = parse(str, i) 331 | -- Set 332 | res[key] = val 333 | -- Next token 334 | i = next_char(str, i, space_chars, true) 335 | local chr = str:sub(i, i) 336 | i = i + 1 337 | if chr == "}" then break end 338 | if chr ~= "," then decode_error(str, i, "expected '}' or ','") end 339 | end 340 | return res, i 341 | end 342 | 343 | 344 | local char_func_map = { 345 | [ '"' ] = parse_string, 346 | [ "0" ] = parse_number, 347 | [ "1" ] = parse_number, 348 | [ "2" ] = parse_number, 349 | [ "3" ] = parse_number, 350 | [ "4" ] = parse_number, 351 | [ "5" ] = parse_number, 352 | [ "6" ] = parse_number, 353 | [ "7" ] = parse_number, 354 | [ "8" ] = parse_number, 355 | [ "9" ] = parse_number, 356 | [ "-" ] = parse_number, 357 | [ "t" ] = parse_literal, 358 | [ "f" ] = parse_literal, 359 | [ "n" ] = parse_literal, 360 | [ "[" ] = parse_array, 361 | [ "{" ] = parse_object, 362 | } 363 | 364 | 365 | parse = function(str, idx) 366 | local chr = str:sub(idx, idx) 367 | local f = char_func_map[chr] 368 | if f then 369 | return f(str, idx) 370 | end 371 | decode_error(str, idx, "unexpected character '" .. chr .. "'") 372 | end 373 | 374 | 375 | function json.decode(str) 376 | if type(str) ~= "string" then 377 | error("expected argument of type string, got " .. type(str)) 378 | end 379 | local res, idx = parse(str, next_char(str, 1, space_chars, true)) 380 | idx = next_char(str, idx, space_chars, true) 381 | if idx <= #str then 382 | decode_error(str, idx, "trailing garbage") 383 | end 384 | return res 385 | end 386 | 387 | 388 | return json 389 | -------------------------------------------------------------------------------- /lib/iniparser.lua: -------------------------------------------------------------------------------- 1 | local ini = { 2 | version = "0.2.10", 3 | __debug = false 4 | } 5 | 6 | -- Localizing things we'll use often, for faster access. 7 | -- This allows Lua to index the stack instead of performing a hash lookup each time we make a call to _G. 8 | local type, 9 | f_open, 10 | str_sub, 11 | str_len, 12 | str_gmatch = type, io.open, string.sub, string.len, string.gmatch 13 | 14 | ------------------------- 15 | -- Universal Functions -- 16 | ------------------------- 17 | 18 | local og_tostring = tostring 19 | local function tostring(...) 20 | local old = os.setlocale(nil, "all") 21 | os.setlocale("en_US.UTF-8", "numeric") 22 | local ok, res = pcall(og_tostring, ...) 23 | os.setlocale(old, "all") 24 | if not ok then 25 | error(res) 26 | end 27 | return res 28 | end 29 | 30 | local og_tonumber = tonumber 31 | local function tonumber(...) 32 | local old = os.setlocale(nil, "all", "numeric") 33 | os.setlocale("en_US.UTF-8", "numeric") 34 | local ok, res = pcall(og_tonumber, ...) 35 | os.setlocale(old, "all") 36 | if not ok then 37 | error(res) 38 | end 39 | return res 40 | end 41 | 42 | ----------------------- 43 | -- Debug Utilities -- 44 | ----------------------- 45 | function ini._Case_Ini_Recurse(tab, tab_name) 46 | for key, val in pairs(tab) do 47 | if type(val) == "table" then 48 | ini._Case_Ini_Recurse(val, key) 49 | else 50 | tab = tostring(tab) 51 | print("KEY: "..key.."\nTYPE: "..type(val).."\nVALUE: "..tostring(val).."\nTABLE: "..(tab_name or tab).."\n") 52 | end 53 | end 54 | end 55 | 56 | ------------------------- 57 | -- Beginning of Parser -- 58 | ------------------------- 59 | 60 | -- str:sub previously in parseStringIntoLuaValue created a new string * amount of lines in the file. 61 | -- I learned during profiling that I created upwards of 55,000 dead-strings, along with using tonumber on all of them. 62 | -- So, this table looks less visually appealing, but it's significantly better for medium to large files, should they ever exist. 63 | local luaBoolValues = { 64 | ["nil"] = "nil", 65 | [" nil"] = "nil", 66 | ["nil "] = nil, 67 | ["true"] = true, 68 | [" true"] = true, 69 | ["true "] = true, 70 | ["false"] = false, 71 | [" false"] = false, 72 | ["false "] = false 73 | } 74 | 75 | -- A lot of local functions could be removed because they existed only to describe the action. 76 | -- This can't be 'inlined' because it's called several different times in several different locations. 77 | local function serializeKey(str) 78 | if str_sub(str, -1) == " " then 79 | str = str_sub(str, 1, #str - 1) 80 | end 81 | 82 | return str 83 | end 84 | 85 | -- This skips parsing, which makes it significantly faster, but far less useful. 86 | -- The use of `ini.match` is to match key_name=desired_value, so if you wish to affirm values directly, and only affirm them, use this. 87 | -- This will not automatically render desired_value into a number or boolean, so you will always need to pass a string. 88 | -- 89 | -- Notes: 90 | -- Reading how Kek's Lua handled INI files inspired me to create `ini.parse`, but the original use of how Kektram affirmed values was still useful. 91 | -- As a result, `ini.match` brings the best of both worlds, between value affirmation and parsing INI into a Lua table. 92 | -- 93 | -- Unlike `ini.parse` this will not give you the option to treat your file as if it was a Lua table. 94 | function ini.match(path, key_name, desired_value, use_cache) 95 | local handle = f_open(path, "r") 96 | local content = handle:read("*a") 97 | handle:close() 98 | 99 | return content:match(key_name.."=(%a%a%a%a)") == desired_value 100 | end 101 | 102 | -- Parse an INI file from `path`. 103 | -- `options` is an optional table parameter, that may contain the following keys: 104 | -- - "cwd" to specify the current working directory. Useful for relative importing. 105 | -- For compatibility, the `options` parameter may be a string, in which case it's implied to be this "cwd" value. 106 | -- - "commaCompat" to enable compatibility for files that use ',' as a decimal separator to read floats correctly 107 | function ini.parse(path, options) 108 | assert(type(options) == "table" or type(options) == "string" or type(options) == "nil", "ini.parse 'options' must be a table.") 109 | assert(type(path) == "string", "ini.parse 'path' must be a string.") 110 | 111 | if options == nil then 112 | options = {} 113 | elseif type(options) == "string" then 114 | options = { 115 | cwd = options 116 | } 117 | end 118 | if options.cwd ~= nil then 119 | path = options.cwd .. path 120 | end 121 | 122 | local res, 123 | cache, 124 | section = {}, { lines = {}, values = {}, keys = {} }, nil -- Inline cache gave smaller execution times. 125 | 126 | local file = f_open(path) 127 | 128 | -- This gets messy because we must minimize function calls; they're very expensive in Lua. 129 | for str in str_gmatch(file:read("*a"), "[^\n\r\x80-\xFF]+") do 130 | local lc = str_sub(str, -1) -- Last character. 131 | local fc = str_sub(str, 1, 1) -- First character. 132 | 133 | ------------------------------------------------------------- 134 | -- Only continue if the line meets the following conditions: 135 | -- 1. It's not a comment. 136 | -- 2. It isn't just a new-line. 137 | ------------------------------------------------------------- 138 | if not (fc == ";" or fc == "\n") then 139 | -- This line is a section. 140 | if fc == "[" and lc == "]" then 141 | local name = str_sub(str, 2, -2) 142 | 143 | if section ~= name then 144 | section = serializeKey(name) 145 | end 146 | else 147 | -------------------------------------- 148 | -- Checking if this line is cached: -- 149 | -------------------------------------- 150 | local cache_line = cache.lines[str] 151 | if cache_line then 152 | if section then 153 | if not res[section] then 154 | res[section] = {} 155 | end 156 | 157 | res[section][cache_line.key] = cache_line.value 158 | else 159 | res[cache_line.key] = cache_line.value 160 | end 161 | else 162 | ------------------------------------------------ 163 | -- This line is not cached, let's process it. -- 164 | ------------------------------------------------ 165 | for key, value in str_gmatch(str, "%s*([^=]*)=([^=]*)%f[%s%z]") do 166 | local serialized_val, serialized_key 167 | 168 | -- Check if the key has been cached: 169 | local cache_key = cache.keys[key] 170 | if cache_key then 171 | serialized_key = cache_key 172 | else 173 | serialized_key = serializeKey(key) 174 | cache.keys[key] = serialized_key 175 | end 176 | 177 | -- Check if the value has been cached: 178 | local cache_val = cache.values[value] 179 | if cache_val then 180 | serialized_val = cache_val 181 | else 182 | local nVal 183 | if options.commaCompat then 184 | local tmp = value:gsub(",", ".") 185 | nVal = tonumber(tmp) 186 | else 187 | nVal = tonumber(value) 188 | end 189 | local bVal = luaBoolValues[value] 190 | 191 | if nVal then 192 | serialized_val = nVal 193 | elseif bVal ~= nil then 194 | if bVal == "nil" then 195 | serialized_val = nil 196 | else 197 | serialized_val = bVal 198 | end 199 | else 200 | serialized_val = value 201 | end 202 | 203 | cache.values[value] = serialized_val 204 | end 205 | 206 | -- Insert into category if one exists, otherwise insert as-is. 207 | if section then 208 | if not res[section] then 209 | res[section] = {} 210 | end 211 | 212 | res[section][serialized_key] = serialized_val 213 | else 214 | res[serialized_key] = serialized_val 215 | end 216 | 217 | -- Cache this line. 218 | cache.lines[str] = { key = serialized_key, value = serialized_val } 219 | end 220 | end 221 | end 222 | end 223 | end 224 | 225 | file:close() 226 | 227 | res.save = function (_path, skip_keys) 228 | local cats, 229 | resl, 230 | skip = {}, {}, {} 231 | 232 | if type(skip_keys) == "table" then 233 | for _, k in next, skip_keys do 234 | skip[k] = 0 235 | end 236 | end 237 | 238 | for key, value in pairs(res) do 239 | if not skip[key] and type(value) == "table" then 240 | table.insert(cats, key) 241 | end 242 | end 243 | 244 | for key, value in pairs(res) do 245 | if not skip[key] and type(value) ~= "table" and type(value) ~= "function" then 246 | key = tostring(key) 247 | value = tostring(value) 248 | 249 | if value:sub(1, 1) == " " then 250 | value = value:gsub(2, #value) 251 | end 252 | 253 | resl[#resl + 1] = key .. "=" .. value 254 | end 255 | end 256 | 257 | for _, tab in pairs(cats) do 258 | resl[#resl + 1] = "[" .. tab .. "]" 259 | 260 | for key, value in pairs(res[tab]) do 261 | key = tostring(key) 262 | value = tostring(value) 263 | 264 | if value:sub(1, 1) == " " then 265 | value = value:gsub(2, #value) 266 | end 267 | 268 | resl[#resl + 1] = key .. "=" .. value 269 | end 270 | end 271 | 272 | local status, _ = pcall(function () 273 | local f = f_open(_path or path, "w+") 274 | if f then 275 | f:write(table.concat(resl, "\n")) 276 | f:close() 277 | else 278 | error("ini.parse.save path invalid: ".._path or path) 279 | end 280 | end) 281 | 282 | return status 283 | end 284 | 285 | -- QoL Improvement; it allows `cfg.new_category.key = value` to be used when `new_category` doesn't actually exist yet. 286 | setmetatable(res, { 287 | __index = function (_, k) 288 | res[k] = {} 289 | 290 | setmetatable(res[k], { 291 | __newindex = function (tab, key, val) 292 | local mt, __newindex = getmetatable(tab), nil 293 | 294 | if mt then 295 | __newindex = mt.__newindex 296 | mt.__newindex = nil 297 | end 298 | 299 | res[k][key] = val 300 | 301 | if __newindex then 302 | mt.__newindex = __newindex 303 | end 304 | end 305 | }) 306 | 307 | return res[k] 308 | end 309 | }) 310 | 311 | return res 312 | end 313 | 314 | return ini 315 | -------------------------------------------------------------------------------- /lib/auto-updater.lua: -------------------------------------------------------------------------------- 1 | -- Auto-Updater v2.9 2 | -- by Hexarobi 3 | -- For Lua Scripts for the Stand Mod Menu for GTA5 4 | -- https://github.com/hexarobi/stand-lua-auto-updater 5 | 6 | local config = { 7 | debug_mode = false, 8 | http_check_delay = 250, 9 | extraction_ignored_filenames = { 10 | "readme", 11 | "readme.txt", 12 | "readme.md", 13 | "license", 14 | "license.txt", 15 | "license.md", 16 | "version", 17 | "version.txt", 18 | "version.lua", 19 | } 20 | } 21 | 22 | --- 23 | --- Dependencies 24 | --- 25 | 26 | --util.ensure_package_is_installed('lua/json') 27 | --local status_json, json = pcall(require, "json") 28 | --if not status_json then error("Could not load json lib. Make sure it is selected under Stand > Lua Scripts > Repository > json") end 29 | 30 | local status_crypto, crypto = pcall(require, "crypto") 31 | if not status_crypto then util.log("Could not load crypto lib") end 32 | 33 | --local status_inspect, inspect = pcall(require, "inspect") 34 | --if not status_inspect then util.log("Could not load inspect lib") end 35 | 36 | --- 37 | --- Utilities 38 | --- 39 | 40 | local function parse_url_host(url) 41 | return url:match("://(.-)/") 42 | end 43 | 44 | local function parse_url_path(url) 45 | return "/"..url:match("://.-/(.*)") 46 | end 47 | 48 | local function modify_github_url_branch(url, switch_to_branch) 49 | local root, path = url:match("^(https://raw.githubusercontent.com/[^/]+/[^/]+)/[^/]+/([^/].*)$") 50 | return root.."/"..switch_to_branch.."/"..path 51 | end 52 | 53 | local function debug_log(message) 54 | if config.debug_mode then 55 | util.log("[auto-updater] "..message) 56 | end 57 | end 58 | 59 | --- 60 | --- Version File 61 | --- 62 | 63 | local function save_version_data(auto_update_config) 64 | local file = io.open(auto_update_config.version_file, "wb") 65 | if file == nil then util.toast("Error opening version file for writing: "..auto_update_config.version_file, TOAST_ALL) return end 66 | file:write(soup.json.encode(auto_update_config.version_data)) 67 | file:close() 68 | end 69 | 70 | local function load_version_data(auto_update_config) 71 | local file = io.open(auto_update_config.version_file) 72 | if file then 73 | local version = file:read() 74 | file:close() 75 | local status, version_data = pcall(soup.json.decode, version) 76 | if not status and type(version) == "string" then 77 | version_data = {version_id=version} 78 | end 79 | auto_update_config.version_data = version_data 80 | --util.toast("Loaded version data "..inspect(auto_update_config.version_data), TOAST_ALL) 81 | else 82 | auto_update_config.version_data = {} 83 | --util.toast("Created new version data "..inspect(auto_update_config.version_data), TOAST_ALL) 84 | end 85 | end 86 | 87 | local function update_version_last_checked_time(auto_update_config) 88 | load_version_data(auto_update_config) 89 | auto_update_config.version_data.last_checked = util.current_unix_time_seconds() 90 | save_version_data(auto_update_config) 91 | end 92 | 93 | local function update_version_id(auto_update_config, version_id, file_hash) 94 | local script_version = auto_update_config.version_data.script_version 95 | load_version_data(auto_update_config) 96 | auto_update_config.version_data.version_id = version_id 97 | auto_update_config.version_data.file_hash = file_hash 98 | auto_update_config.version_data.fresh_update = true 99 | auto_update_config.version_data.last_checked = util.current_unix_time_seconds() 100 | auto_update_config.version_data.script_version = script_version 101 | save_version_data(auto_update_config) 102 | end 103 | 104 | local function process_version(auto_update_config, result, headers) 105 | local file_hash 106 | if crypto then 107 | file_hash = crypto.md5(result) 108 | end 109 | if headers then 110 | for header_key, header_value in pairs(headers) do 111 | if header_key:lower() == "etag" then 112 | update_version_id(auto_update_config, header_value, file_hash) 113 | end 114 | end 115 | end 116 | end 117 | 118 | --- 119 | --- Replacer 120 | --- 121 | 122 | local function update_file(path, content) 123 | debug_log("Updating file "..path) 124 | local dirpath = path:match("(.-)([^\\/]-%.?)$") 125 | filesystem.mkdirs(dirpath) 126 | local file = io.open(path, "wb") 127 | if file == nil then 128 | util.toast("Error updating "..path..". Could not open file for writing.", TOAST_ALL) 129 | return false 130 | end 131 | file:write(content) 132 | file:close() 133 | debug_log("Updated file "..path) 134 | return true 135 | end 136 | 137 | local function replace_current_script(auto_update_config, content) 138 | if update_file(auto_update_config.script_path, content) then 139 | auto_update_config.script_updated = true 140 | end 141 | end 142 | 143 | local function parse_script_version(auto_update_config, script) 144 | auto_update_config.version_data.script_version = script:match('SCRIPT_VERSION = "([^ ]+)"') 145 | end 146 | 147 | --- 148 | --- Full Restarter 149 | --- 150 | 151 | local function force_full_restart(auto_update_config) 152 | local menu_script_path = "Stand>Lua Scripts>"..auto_update_config.script_name 153 | if not menu.is_ref_valid(menu.ref_by_path(menu_script_path)) then 154 | error("Failed to restart. Menu script path is invalid.") 155 | end 156 | local script_body = "\ 157 | util.yield(50)\ 158 | local script_stop_command_ref = menu.ref_by_path(\""..menu_script_path..">Stop Script\")\ 159 | if menu.is_ref_valid(script_stop_command_ref) then\ 160 | menu.focus(script_stop_command_ref)\ 161 | util.yield(50)\ 162 | menu.trigger_command(script_stop_command_ref)\ 163 | end\ 164 | util.yield(50)\ 165 | local script_command_ref = menu.ref_by_path(\""..menu_script_path..">Start Script\")\ 166 | if menu.is_ref_valid(script_command_ref) then\ 167 | menu.focus(script_command_ref)\ 168 | util.yield(50)\ 169 | menu.trigger_command(script_command_ref)\ 170 | end\ 171 | io.remove(filesystem.scripts_dir()..SCRIPT_RELPATH)\ 172 | util.stop_script()\ 173 | " 174 | update_file(filesystem.scripts_dir().."\\restartscript.lua", script_body) 175 | 176 | local menu_item 177 | menu_item = menu.ref_by_path("Stand>Lua Scripts") 178 | if menu.is_ref_valid(menu_item) then 179 | menu.focus(menu_item) 180 | menu.trigger_command(menu_item) 181 | util.yield(50) 182 | end 183 | 184 | local restart_script = menu.ref_by_path("Stand>Lua Scripts>restartscript>Start Script") 185 | if menu.is_ref_valid(restart_script) then 186 | menu.focus(restart_script) 187 | util.yield(50) 188 | menu.trigger_command(restart_script) 189 | end 190 | util.stop_script() 191 | end 192 | 193 | --- 194 | --- Zip Extractor 195 | --- 196 | 197 | local function escape_pattern(text) 198 | return text:gsub("([^%w])", "%%%1") 199 | end 200 | 201 | local function is_ignored_filename(filename) 202 | local filename_lower = filename:lower() 203 | for _, ignored_filename in pairs(config.extraction_ignored_filenames) do 204 | if ignored_filename == filename_lower then 205 | return true 206 | end 207 | end 208 | return false 209 | end 210 | 211 | local function build_script_run_name(script_name) 212 | if script_name ~= nil then 213 | return script_name:gsub("_", ""):gsub(" ", ""):gsub("-", "") 214 | end 215 | end 216 | 217 | local function extract_zip(auto_update_config) 218 | debug_log("Extracting zip file "..auto_update_config.script_path) 219 | if auto_update_config.extracted_files == nil then auto_update_config.extracted_files = {} end 220 | local first_lua_file 221 | local fr = soup.FileReader(auto_update_config.script_path) 222 | local zr = soup.ZipReader(fr) 223 | for _, f in zr:getFileList() do 224 | for _2, extraction in pairs(auto_update_config.extractions) do 225 | local pattern = "^"..escape_pattern(extraction.from).."/(.*[^/])$" 226 | local relative_path = f.name:match(pattern) 227 | if relative_path and not is_ignored_filename(relative_path) then 228 | local output_filepath = filesystem.stand_dir() .. extraction.to .. "/" .. relative_path 229 | debug_log("Extracting file "..output_filepath) 230 | local expand_status, content = pcall(zr.getFileContents, zr, f) 231 | if not expand_status then 232 | debug_log("Failed to extract "..f.name..": "..content) 233 | else 234 | local lua_filename = f.name:match("([^/]+)%.lua$") 235 | if first_lua_file == nil and lua_filename then 236 | debug_log("Found first lua filename "..lua_filename) 237 | first_lua_file = lua_filename 238 | if auto_update_config.script_run_name == nil then 239 | auto_update_config.script_run_name = build_script_run_name(lua_filename) 240 | end 241 | if auto_update_config.script_filepath == nil then 242 | auto_update_config.script_filepath = output_filepath 243 | end 244 | end 245 | update_file(output_filepath, content) 246 | debug_log("Extracted file "..output_filepath) 247 | table.insert(auto_update_config.extracted_files, output_filepath) 248 | end 249 | else 250 | debug_log("Skipping file due to name doesnt match pattern "..pattern.." "..f.name) 251 | end 252 | end 253 | util.yield() 254 | end 255 | end 256 | 257 | --- 258 | --- Uninstaller 259 | --- 260 | 261 | local function delete_file(filepath) 262 | if filepath == nil or not filesystem.exists(filepath) then return end 263 | debug_log("Deleting file "..filepath) 264 | io.remove(filepath) 265 | end 266 | 267 | local function uninstall(auto_update_config) 268 | delete_file(auto_update_config.script_path) 269 | if auto_update_config.extracted_files ~= nil then 270 | for _, extracted_file in pairs(auto_update_config.extracted_files) do 271 | delete_file(extracted_file) 272 | end 273 | end 274 | end 275 | 276 | --- 277 | --- GitHub API 278 | --- 279 | 280 | local function parse_github_user_and_project(url) 281 | local user, project = url:match("^https://github%.com/([^/]+)/([^/]+)/?$") 282 | if not user or not project then 283 | error("Invalid project url (must be a GitHub.com project): "..url) 284 | end 285 | return user, project 286 | end 287 | 288 | local function fetch_json(url) 289 | local fetch_complete = false 290 | local response 291 | async_http.init(parse_url_host(url), parse_url_path(url), function(result, headers, status_code) 292 | response = soup.json.decode(result) 293 | fetch_complete = true 294 | end, function() 295 | util.toast("Error fetching "..url, TOAST_ALL) 296 | fetch_complete = true 297 | end) 298 | async_http.dispatch() 299 | while fetch_complete ~= true do util.yield() end 300 | return response 301 | end 302 | 303 | local function get_default_branch(auto_update_config) 304 | local user, project = parse_github_user_and_project(auto_update_config.project_url) 305 | local response = fetch_json("https://api.github.com/repos/"..user.."/"..project.."/branches") 306 | if response == nil or response[1] == nil then 307 | error("Error fetching project default branch. Make sure the project URL is correct, and use CloudFlare DNS 1.1.1.1") 308 | end 309 | return response[1].name 310 | end 311 | 312 | 313 | --- 314 | --- Config Defaults 315 | --- 316 | 317 | local function initial_project_config(auto_update_config) 318 | -- Faster config assumes main branch for now, will use final_project_config to detect actual branch name 319 | if auto_update_config.project_url ~= nil then 320 | local user, project = parse_github_user_and_project(auto_update_config.project_url) 321 | if auto_update_config.author == nil then 322 | auto_update_config.author = user 323 | end 324 | if auto_update_config.source_url == nil then 325 | auto_update_config.source_url = auto_update_config.project_url 326 | auto_update_config.is_project_archive = true 327 | auto_update_config.script_relpath = user .. "-" .. project .. "-main.zip" 328 | end 329 | end 330 | end 331 | 332 | local function final_project_config(auto_update_config) 333 | -- Slower config makes API call to get proper branch name, only used at install time 334 | if auto_update_config.project_url ~= nil and auto_update_config.is_project_archive then 335 | if auto_update_config.branch == nil then 336 | -- Get branch from API 337 | auto_update_config.branch = get_default_branch(auto_update_config) 338 | debug_log("Set branch "..auto_update_config.branch) 339 | end 340 | local user, project = parse_github_user_and_project(auto_update_config.project_url) 341 | auto_update_config.source_url = "https://codeload.github.com/"..user.."/"..project.."/zip/refs/heads/" .. auto_update_config.branch 342 | local filename = user .. "-" .. project .. "-" .. auto_update_config.branch .. ".zip" 343 | auto_update_config.script_relpath = filename 344 | auto_update_config.script_path = filesystem.store_dir() .. "auto-updater/compressed/" .. filename 345 | if auto_update_config.extractions == nil then 346 | auto_update_config.extractions = { 347 | { 348 | from=project .. "-" .. auto_update_config.branch, 349 | to="Lua Scripts\\", 350 | } 351 | } 352 | end 353 | end 354 | end 355 | 356 | local function expand_auto_update_config(auto_update_config) 357 | initial_project_config(auto_update_config) 358 | auto_update_config.script_relpath = auto_update_config.script_relpath:gsub("\\", "/") 359 | if auto_update_config.script_path == nil then 360 | auto_update_config.script_path = filesystem.scripts_dir() .. auto_update_config.script_relpath 361 | end 362 | if auto_update_config.script_filename == nil then 363 | auto_update_config.script_filename = ("/"..auto_update_config.script_relpath):match("^.*/(.+)$") 364 | end 365 | if auto_update_config.script_name == nil then 366 | auto_update_config.script_name = auto_update_config.script_filename:match(".-([^\\/]-%.?)[.]lua$") 367 | if auto_update_config.script_name == nil then 368 | auto_update_config.script_name = auto_update_config.script_filename:match(".-([^\\/]-%.?)[.]pluto$") 369 | end 370 | end 371 | if auto_update_config.name == nil then 372 | auto_update_config.name = auto_update_config.script_filename 373 | end 374 | if auto_update_config.script_run_name == nil and auto_update_config.script_name then 375 | auto_update_config.script_run_name = build_script_run_name(auto_update_config.script_name) 376 | end 377 | auto_update_config.script_reldirpath = ("/"..auto_update_config.script_relpath):match("^(.*)/[^/]+$") 378 | filesystem.mkdirs(filesystem.scripts_dir() .. auto_update_config.script_reldirpath) 379 | if auto_update_config.version_file == nil then 380 | auto_update_config.version_store_dir = filesystem.store_dir() .. "auto-updater/versions" .. auto_update_config.script_reldirpath 381 | filesystem.mkdirs(auto_update_config.version_store_dir) 382 | auto_update_config.version_file = auto_update_config.version_store_dir .. "/" .. auto_update_config.script_filename .. ".version" 383 | end 384 | if auto_update_config.source_url == nil then -- For backward compatibility with older configs 385 | auto_update_config.source_url = "https://" .. auto_update_config.source_host .. "/" .. auto_update_config.source_path 386 | end 387 | if auto_update_config.switch_to_branch ~= nil then 388 | auto_update_config.source_url = modify_github_url_branch(auto_update_config.source_url, auto_update_config.switch_to_branch) 389 | end 390 | --if auto_update_config.restart_delay == nil then 391 | -- auto_update_config.restart_delay = 100 392 | --end 393 | if auto_update_config.http_timeout == nil then 394 | auto_update_config.http_timeout = 45000 395 | end 396 | if auto_update_config.expected_status_code == nil then 397 | auto_update_config.expected_status_code = 200 398 | end 399 | if auto_update_config.check_interval == nil then 400 | auto_update_config.check_interval = 86400 -- Daily = 86400 seconds 401 | end 402 | load_version_data(auto_update_config) 403 | end 404 | 405 | --- 406 | --- Downloader 407 | --- 408 | 409 | local is_download_complete 410 | 411 | local function is_result_valid(auto_update_config, result, headers, status_code) 412 | if status_code == 304 then 413 | -- No update found 414 | update_version_last_checked_time(auto_update_config) 415 | is_download_complete = true 416 | return false 417 | end 418 | if status_code == 302 then 419 | util.toast("Error updating "..auto_update_config.name..": Unexpected redirection from "..auto_update_config.source_url.." to "..headers["Location"], TOAST_ALL) 420 | is_download_complete = false 421 | return false 422 | end 423 | if status_code ~= auto_update_config.expected_status_code then 424 | util.toast("Error updating "..auto_update_config.name..": Unexpected status code: "..status_code .. " for URL "..auto_update_config.source_url, TOAST_ALL) 425 | is_download_complete = false 426 | return false 427 | end 428 | if not result or result == "" then 429 | util.toast("Error updating "..auto_update_config.name..": Empty content", TOAST_ALL) 430 | is_download_complete = false 431 | return false 432 | end 433 | if auto_update_config.verify_file_begins_with ~= nil then 434 | if not string.startswith(result, auto_update_config.verify_file_begins_with) then 435 | util.toast("Error updating "..auto_update_config.name..": Found invalid content", TOAST_ALL) 436 | is_download_complete = false 437 | return false 438 | end 439 | end 440 | if auto_update_config.verify_file_begins_with == nil and auto_update_config.verify_file_does_not_begin_with == nil then 441 | auto_update_config.verify_file_does_not_begin_with = "<" 442 | end 443 | if auto_update_config.verify_file_does_not_begin_with ~= nil 444 | and string.startswith(result, auto_update_config.verify_file_does_not_begin_with) then 445 | util.toast("Error updating "..auto_update_config.name..": Found invalid content", TOAST_ALL) 446 | is_download_complete = false 447 | return false 448 | end 449 | return true 450 | end 451 | 452 | local function process_auto_update(auto_update_config) 453 | final_project_config(auto_update_config) 454 | async_http.init(parse_url_host(auto_update_config.source_url), parse_url_path(auto_update_config.source_url), function(result, headers, status_code) 455 | if not is_result_valid(auto_update_config, result, headers, status_code) then 456 | return 457 | end 458 | replace_current_script(auto_update_config, result) 459 | parse_script_version(auto_update_config, result) 460 | process_version(auto_update_config, result, headers) 461 | if auto_update_config.extractions ~= nil then 462 | extract_zip(auto_update_config) 463 | end 464 | is_download_complete = true 465 | if not auto_update_config.silent_updates then 466 | util.toast("Updated "..auto_update_config.name, TOAST_ALL) 467 | end 468 | end, function() 469 | util.toast("Error updating "..auto_update_config.name..": Update failed to download.", TOAST_ALL) 470 | is_download_complete = true 471 | end) 472 | -- Only use cached version if this is not a clean reinstall, and if the file still exists on disk 473 | if auto_update_config.clean_reinstall ~= true and filesystem.exists(auto_update_config.script_path) then 474 | -- Use ETags to only fetch files if they have been updated 475 | -- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag 476 | if auto_update_config.version_data.version_id then 477 | --debug_log("Adding existing etag "..inspect(auto_update_config.version_data.version_id)) 478 | async_http.add_header("If-None-Match", auto_update_config.version_data.version_id) 479 | end 480 | end 481 | async_http.dispatch() 482 | end 483 | 484 | local function is_due_for_update_check(auto_update_config) 485 | return ( 486 | auto_update_config == nil 487 | or auto_update_config.clean_reinstall == true 488 | or auto_update_config.version_data == nil 489 | or auto_update_config.version_data.last_checked == nil 490 | or auto_update_config.check_interval == 0 491 | or ((util.current_unix_time_seconds() - auto_update_config.version_data.last_checked) > auto_update_config.check_interval) 492 | or (not filesystem.exists(auto_update_config.script_path)) 493 | ) 494 | end 495 | 496 | local function is_update_disabled() 497 | return not async_http.have_access() 498 | end 499 | 500 | --- 501 | --- Require with Auto Update (for libs) 502 | --- 503 | 504 | local function require_with_auto_update(auto_update_config) 505 | auto_update_config.lib_require_path = auto_update_config.script_relpath:gsub("[.]lua$", ""):gsub("[.]pluto$", "") 506 | --if auto_update_config.auto_restart == nil then auto_update_config.auto_restart = false end 507 | run_auto_update(auto_update_config) 508 | local auto_loaded_lib_status, loaded_lib = pcall(require, auto_update_config.lib_require_path) 509 | if not auto_loaded_lib_status then 510 | util.toast("Failed to load required file: "..auto_update_config.script_relpath.."\n"..tostring(loaded_lib), TOAST_ALL) 511 | return 512 | end 513 | auto_update_config.loaded_lib = loaded_lib 514 | return loaded_lib 515 | end 516 | 517 | --- 518 | --- Auto Update Check 519 | --- 520 | 521 | function run_auto_update(auto_update_config) 522 | expand_auto_update_config(auto_update_config) 523 | debug_log("Running auto-update on "..auto_update_config.script_filename.."...") 524 | if is_update_disabled() then 525 | util.toast("Cannot auto-update due to disabled internet access. To enable updates, please stop the script then uncheck the `Disable Internet Access` option.", TOAST_ALL) 526 | return false 527 | end 528 | local busy_menu 529 | if not auto_update_config.is_dependency then 530 | util.set_busy(true) 531 | busy_menu = menu.divider(menu.my_root(), "Please wait...") 532 | end 533 | if is_due_for_update_check(auto_update_config) then 534 | is_download_complete = nil 535 | util.create_thread(function() 536 | process_auto_update(auto_update_config) 537 | end) 538 | local i = 1 539 | while (is_download_complete == nil and i < (auto_update_config.http_timeout / config.http_check_delay)) do 540 | util.yield(config.http_check_delay) 541 | i = i + 1 542 | debug_log("Checking "..auto_update_config.script_filename.."...") 543 | end 544 | if is_download_complete == nil then 545 | util.toast("Error updating "..auto_update_config.script_filename..": HTTP Timeout. This error can often be resolved by using Cloudflare DNS settings: 1.1.1.1 and 1.0.0.1 For more info visit http://1.1.1.1/dns/", TOAST_ALL) 546 | return false 547 | end 548 | if (auto_update_config.script_updated and not auto_update_config.is_dependency) and auto_update_config.auto_restart ~= false then 549 | debug_log("Restarting...") 550 | if auto_update_config.restart_delay then util.yield(auto_update_config.restart_delay) end 551 | force_full_restart(auto_update_config) 552 | return 553 | end 554 | end 555 | local dependency_updated = false 556 | if auto_update_config.dependencies ~= nil then 557 | for _, dependency in pairs(auto_update_config.dependencies) do 558 | dependency.is_dependency = true 559 | if dependency.silent_updates == nil then dependency.silent_updates = auto_update_config.silent_updates end 560 | if (is_due_for_update_check(auto_update_config) or auto_update_config.script_updated or auto_update_config.version_data.fresh_update) then dependency.check_interval = 0 end 561 | if dependency.is_required and (dependency.script_relpath:match("(.*)[.]lua$") or dependency.script_relpath:match("(.*)[.]pluto$")) then 562 | require_with_auto_update(dependency) 563 | else 564 | run_auto_update(dependency) 565 | end 566 | if dependency.script_updated then dependency_updated = true end 567 | end 568 | end 569 | if auto_update_config.version_data.fresh_update and not auto_update_config.is_dependency then 570 | -- TODO: Show changelog 571 | if auto_update_config.version_data.script_version then 572 | util.toast("Updated "..auto_update_config.script_filename.." to "..tostring(auto_update_config.version_data.script_version), TOAST_ALL) 573 | end 574 | auto_update_config.version_data.fresh_update = false 575 | save_version_data(auto_update_config) 576 | end 577 | if (dependency_updated) and auto_update_config.auto_restart ~= false then 578 | debug_log("Dependency updated. Restarting...") 579 | if auto_update_config.restart_delay then util.yield(auto_update_config.restart_delay) end 580 | force_full_restart(auto_update_config) 581 | return 582 | end 583 | if not auto_update_config.is_dependency then 584 | util.set_busy(false) 585 | if menu.is_ref_valid(busy_menu) then 586 | menu.delete(busy_menu) 587 | end 588 | end 589 | return true 590 | end 591 | 592 | 593 | --- 594 | --- Legacy Compatibility 595 | --- 596 | 597 | -- Wrapper for old function names 598 | function auto_update(auto_update_config) 599 | run_auto_update(auto_update_config) 600 | end 601 | 602 | --- 603 | --- Self-Update 604 | --- 605 | 606 | util.create_thread(function() 607 | run_auto_update({ 608 | source_url="https://raw.githubusercontent.com/hexarobi/stand-lua-auto-updater/main/auto-updater.lua", 609 | script_relpath="lib/auto-updater.lua", 610 | auto_restart = false, 611 | verify_file_begins_with="--", 612 | check_interval = 86400, 613 | }) 614 | end) 615 | 616 | --- 617 | --- Return Object 618 | --- 619 | 620 | return { 621 | run_auto_update = run_auto_update, 622 | require_with_auto_update = require_with_auto_update, 623 | expand_auto_update_config = expand_auto_update_config, 624 | uninstall = uninstall, 625 | } 626 | -------------------------------------------------------------------------------- /lib/xml2lua.lua: -------------------------------------------------------------------------------- 1 | -- @module Module providing a non-validating XML stream parser in Lua. 2 | -- 3 | -- Features: 4 | -- ========= 5 | -- 6 | -- * Tokenises well-formed XML (relatively robustly) 7 | -- * Flexible handler based event API (see below) 8 | -- * Parses all XML Infoset elements - ie. 9 | -- - Tags 10 | -- - Text 11 | -- - Comments 12 | -- - CDATA 13 | -- - XML Decl 14 | -- - Processing Instructions 15 | -- - DOCTYPE declarations 16 | -- * Provides limited well-formedness checking 17 | -- (checks for basic syntax & balanced tags only) 18 | -- * Flexible whitespace handling (selectable) 19 | -- * Entity Handling (selectable) 20 | -- 21 | -- Limitations: 22 | -- ============ 23 | -- 24 | -- * Non-validating 25 | -- * No charset handling 26 | -- * No namespace support 27 | -- * Shallow well-formedness checking only (fails 28 | -- to detect most semantic errors) 29 | -- 30 | -- API: 31 | -- ==== 32 | -- 33 | -- The parser provides a partially object-oriented API with 34 | -- functionality split into tokeniser and handler components. 35 | -- 36 | -- The handler instance is passed to the tokeniser and receives 37 | -- callbacks for each XML element processed (if a suitable handler 38 | -- function is defined). The API is conceptually similar to the 39 | -- SAX API but implemented differently. 40 | -- 41 | -- XML data is passed to the parser instance through the 'parse' 42 | -- method (Note: must be passed a single string currently) 43 | -- 44 | -- License: 45 | -- ======== 46 | -- 47 | -- This code is freely distributable under the terms of the [MIT license](LICENSE). 48 | -- 49 | -- 50 | --@author Paul Chakravarti (paulc@passtheaardvark.com) 51 | --@author Manoel Campos da Silva Filho 52 | local xml2lua = {_VERSION = "1.5-2"} 53 | 54 | local function load_XmlParser() 55 | --- @module Class providing the actual XML parser. 56 | -- Available options are: 57 | -- * stripWS 58 | -- Strip non-significant whitespace (leading/trailing) 59 | -- and do not generate events for empty text elements 60 | -- 61 | -- * expandEntities 62 | -- Expand entities (standard entities + single char 63 | -- numeric entities only currently - could be extended 64 | -- at runtime if suitable DTD parser added elements 65 | -- to table (see obj._ENTITIES). May also be possible 66 | -- to expand multibyre entities for UTF-8 only 67 | -- 68 | -- * errorHandler 69 | -- Custom error handler function 70 | -- 71 | -- NOTE: Boolean options must be set to 'nil' not '0' 72 | 73 | ---Converts the decimal code of a character to its corresponding char 74 | --if it's a graphical char, otherwise, returns the HTML ISO code 75 | --for that decimal value in the format &#code 76 | --@param code the decimal value to convert to its respective character 77 | local function decimalToHtmlChar(code) 78 | local num = tonumber(code) 79 | if num >= 0 and num < 256 then 80 | return string.char(num) 81 | end 82 | 83 | return "&#"..code..";" 84 | end 85 | 86 | ---Converts the hexadecimal code of a character to its corresponding char 87 | --if it's a graphical char, otherwise, returns the HTML ISO code 88 | --for that hexadecimal value in the format ode 89 | --@param code the hexadecimal value to convert to its respective character 90 | local function hexadecimalToHtmlChar(code) 91 | local num = tonumber(code, 16) 92 | if num >= 0 and num < 256 then 93 | return string.char(num) 94 | end 95 | 96 | return "&#x"..code..";" 97 | end 98 | 99 | local XmlParser = { 100 | -- Private attribures/functions 101 | _XML = '^([^<]*)<(%/?)([^>]-)(%/?)>', 102 | _ATTR1 = '([%w-:_]+)%s*=%s*"(.-)"', 103 | _ATTR2 = '([%w-:_]+)%s*=%s*\'(.-)\'', 104 | _CDATA = '<%!%[CDATA%[(.-)%]%]>', 105 | _PI = '<%?(.-)%?>', 106 | _COMMENT = '', 107 | _TAG = '^(.-)%s.*', 108 | _LEADINGWS = '^%s+', 109 | _TRAILINGWS = '%s+$', 110 | _WS = '^%s*$', 111 | _DTD1 = '', 112 | _DTD2 = '', 113 | --_DTD3 = '', 114 | _DTD3 = '', 115 | _DTD4 = '', 116 | _DTD5 = '', 117 | 118 | --Matches an attribute with non-closing double quotes (The equal sign is matched non-greedly by using =+?) 119 | _ATTRERR1 = '=+?%s*"[^"]*$', 120 | --Matches an attribute with non-closing single quotes (The equal sign is matched non-greedly by using =+?) 121 | _ATTRERR2 = '=+?%s*\'[^\']*$', 122 | --Matches a closing tag such as or the end of a openning tag such as 123 | _TAGEXT = '(%/?)>', 124 | 125 | _errstr = { 126 | xmlErr = "Error Parsing XML", 127 | declErr = "Error Parsing XMLDecl", 128 | declStartErr = "XMLDecl not at start of document", 129 | declAttrErr = "Invalid XMLDecl attributes", 130 | piErr = "Error Parsing Processing Instruction", 131 | commentErr = "Error Parsing Comment", 132 | cdataErr = "Error Parsing CDATA", 133 | dtdErr = "Error Parsing DTD", 134 | endTagErr = "End Tag Attributes Invalid", 135 | unmatchedTagErr = "Unbalanced Tag", 136 | incompleteXmlErr = "Incomplete XML Document", 137 | }, 138 | 139 | _ENTITIES = { 140 | ["<"] = "<", 141 | [">"] = ">", 142 | ["&"] = "&", 143 | ["""] = '"', 144 | ["'"] = "'", 145 | ["&#(%d+);"] = decimalToHtmlChar, 146 | ["&#x(%x+);"] = hexadecimalToHtmlChar, 147 | }, 148 | } 149 | 150 | --- Instantiates a XmlParser object. 151 | --@param _handler Handler module to be used to convert the XML string 152 | -- to another formats. See the available handlers at the handler directory. 153 | -- Usually you get an instance to a handler module using, for instance: 154 | -- local handler = require("xmlhandler/tree"). 155 | --@param _options Options for this XmlParser instance. 156 | --@see XmlParser.options 157 | function XmlParser.new(_handler, _options) 158 | local obj = { 159 | handler = _handler, 160 | options = _options, 161 | _stack = {} 162 | } 163 | 164 | setmetatable(obj, XmlParser) 165 | obj.__index = XmlParser 166 | return obj; 167 | end 168 | 169 | ---Checks if a function/field exists in a table or in its metatable 170 | --@param table the table to check if it has a given function 171 | --@param elementName the name of the function/field to check if exists 172 | --@return true if the function/field exists, false otherwise 173 | local function fexists(table, elementName) 174 | if table == nil then 175 | return false 176 | end 177 | 178 | if table[elementName] == nil then 179 | return fexists(getmetatable(table), elementName) 180 | else 181 | return true 182 | end 183 | end 184 | 185 | local function err(self, errMsg, pos) 186 | if self.options.errorHandler then 187 | self.options.errorHandler(errMsg,pos) 188 | end 189 | end 190 | 191 | --- Removes whitespaces 192 | local function stripWS(self, s) 193 | if self.options.stripWS then 194 | s = string.gsub(s,'^%s+','') 195 | s = string.gsub(s,'%s+$','') 196 | end 197 | return s 198 | end 199 | 200 | local function parseEntities(self, s) 201 | if self.options.expandEntities then 202 | for k,v in pairs(self._ENTITIES) do 203 | s = string.gsub(s,k,v) 204 | end 205 | end 206 | 207 | return s 208 | end 209 | 210 | --- Parses a string representing a tag. 211 | --@param s String containing tag text 212 | --@return a {name, attrs} table 213 | -- where name is the name of the tag and attrs 214 | -- is a table containing the atributtes of the tag 215 | local function parseTag(self, s) 216 | local tag = { 217 | name = string.gsub(s, self._TAG, '%1'), 218 | attrs = {} 219 | } 220 | 221 | local parseFunction = function (k, v) 222 | tag.attrs[k] = parseEntities(self, v) 223 | tag.attrs._ = 1 224 | end 225 | 226 | string.gsub(s, self._ATTR1, parseFunction) 227 | string.gsub(s, self._ATTR2, parseFunction) 228 | 229 | if tag.attrs._ then 230 | tag.attrs._ = nil 231 | else 232 | tag.attrs = nil 233 | end 234 | 235 | return tag 236 | end 237 | 238 | local function parseXmlDeclaration(self, xml, f) 239 | -- XML Declaration 240 | f.match, f.endMatch, f.text = string.find(xml, self._PI, f.pos) 241 | if not f.match then 242 | err(self, self._errstr.declErr, f.pos) 243 | end 244 | 245 | if f.match ~= 1 then 246 | -- Must be at start of doc if present 247 | err(self, self._errstr.declStartErr, f.pos) 248 | end 249 | 250 | local tag = parseTag(self, f.text) 251 | -- TODO: Check if attributes are valid 252 | -- Check for version (mandatory) 253 | if tag.attrs and tag.attrs.version == nil then 254 | err(self, self._errstr.declAttrErr, f.pos) 255 | end 256 | 257 | if fexists(self.handler, 'decl') then 258 | self.handler:decl(tag, f.match, f.endMatch) 259 | end 260 | 261 | return tag 262 | end 263 | 264 | local function parseXmlProcessingInstruction(self, xml, f) 265 | local tag = {} 266 | 267 | -- XML Processing Instruction (PI) 268 | f.match, f.endMatch, f.text = string.find(xml, self._PI, f.pos) 269 | if not f.match then 270 | err(self, self._errstr.piErr, f.pos) 271 | end 272 | if fexists(self.handler, 'pi') then 273 | -- Parse PI attributes & text 274 | tag = parseTag(self, f.text) 275 | local pi = string.sub(f.text, string.len(tag.name)+1) 276 | if pi ~= "" then 277 | if tag.attrs then 278 | tag.attrs._text = pi 279 | else 280 | tag.attrs = { _text = pi } 281 | end 282 | end 283 | self.handler:pi(tag, f.match, f.endMatch) 284 | end 285 | 286 | return tag 287 | end 288 | 289 | local function parseComment(self, xml, f) 290 | f.match, f.endMatch, f.text = string.find(xml, self._COMMENT, f.pos) 291 | if not f.match then 292 | err(self, self._errstr.commentErr, f.pos) 293 | end 294 | 295 | if fexists(self.handler, 'comment') then 296 | f.text = parseEntities(self, stripWS(self, f.text)) 297 | self.handler:comment(f.text, next, f.match, f.endMatch) 298 | end 299 | end 300 | 301 | local function _parseDtd(self, xml, pos) 302 | -- match,endMatch,root,type,name,uri,internal 303 | local dtdPatterns = {self._DTD1, self._DTD2, self._DTD3, self._DTD4, self._DTD5} 304 | 305 | for _, dtd in pairs(dtdPatterns) do 306 | local m,e,r,t,n,u,i = string.find(xml, dtd, pos) 307 | if m then 308 | return m, e, {_root=r, _type=t, _name=n, _uri=u, _internal=i} 309 | end 310 | end 311 | 312 | return nil 313 | end 314 | 315 | local function parseDtd(self, xml, f) 316 | f.match, f.endMatch, _ = _parseDtd(self, xml, f.pos) 317 | if not f.match then 318 | err(self, self._errstr.dtdErr, f.pos) 319 | end 320 | 321 | if fexists(self.handler, 'dtd') then 322 | local tag = {name="DOCTYPE", value=string.sub(xml, f.match+10, f.endMatch-1)} 323 | self.handler:dtd(tag, f.match, f.endMatch) 324 | end 325 | end 326 | 327 | local function parseCdata(self, xml, f) 328 | f.match, f.endMatch, f.text = string.find(xml, self._CDATA, f.pos) 329 | if not f.match then 330 | err(self, self._errstr.cdataErr, f.pos) 331 | end 332 | 333 | if fexists(self.handler, 'cdata') then 334 | self.handler:cdata(f.text, nil, f.match, f.endMatch) 335 | end 336 | end 337 | 338 | --- Parse a Normal tag 339 | -- Need check for embedded '>' in attribute value and extend 340 | -- match recursively if necessary eg. 341 | local function parseNormalTag(self, xml, f) 342 | --Check for errors 343 | while 1 do 344 | --If there isn't an attribute without closing quotes (single or double quotes) 345 | --then breaks to follow the normal processing of the tag. 346 | --Otherwise, try to find where the quotes close. 347 | f.errStart, f.errEnd = string.find(f.tagstr, self._ATTRERR1) 348 | 349 | if f.errEnd == nil then 350 | f.errStart, f.errEnd = string.find(f.tagstr, self._ATTRERR2) 351 | if f.errEnd == nil then 352 | break 353 | end 354 | end 355 | 356 | f.extStart, f.extEnd, f.endt2 = string.find(xml, self._TAGEXT, f.endMatch+1) 357 | f.tagstr = f.tagstr .. string.sub(xml, f.endMatch, f.extEnd-1) 358 | if not f.match then 359 | err(self, self._errstr.xmlErr, f.pos) 360 | end 361 | f.endMatch = f.extEnd 362 | end 363 | 364 | -- Extract tag name and attrs 365 | local tag = parseTag(self, f.tagstr) 366 | 367 | if (f.endt1=="/") then 368 | if fexists(self.handler, 'endtag') then 369 | if tag.attrs then 370 | -- Shouldn't have any attributes in endtag 371 | err(self, string.format("%s (/%s)", self._errstr.endTagErr, tag.name), f.pos) 372 | end 373 | if table.remove(self._stack) ~= tag.name then 374 | err(self, string.format("%s (/%s)", self._errstr.unmatchedTagErr, tag.name), f.pos) 375 | end 376 | self.handler:endtag(tag, f.match, f.endMatch) 377 | end 378 | else 379 | table.insert(self._stack, tag.name) 380 | 381 | if fexists(self.handler, 'starttag') then 382 | self.handler:starttag(tag, f.match, f.endMatch) 383 | end 384 | 385 | -- Self-Closing Tag 386 | if (f.endt2=="/") then 387 | table.remove(self._stack) 388 | if fexists(self.handler, 'endtag') then 389 | self.handler:endtag(tag, f.match, f.endMatch) 390 | end 391 | end 392 | end 393 | 394 | return tag 395 | end 396 | 397 | local function parseTagType(self, xml, f) 398 | -- Test for tag type 399 | if string.find(string.sub(f.tagstr, 1, 5), "?xml%s") then 400 | parseXmlDeclaration(self, xml, f) 401 | elseif string.sub(f.tagstr, 1, 1) == "?" then 402 | parseXmlProcessingInstruction(self, xml, f) 403 | elseif string.sub(f.tagstr, 1, 3) == "!--" then 404 | parseComment(self, xml, f) 405 | elseif string.sub(f.tagstr, 1, 8) == "!DOCTYPE" then 406 | parseDtd(self, xml, f) 407 | elseif string.sub(f.tagstr, 1, 8) == "![CDATA[" then 408 | parseCdata(self, xml, f) 409 | else 410 | parseNormalTag(self, xml, f) 411 | end 412 | end 413 | 414 | --- Get next tag (first pass - fix exceptions below). 415 | --@return true if the next tag could be got, false otherwise 416 | local function getNextTag(self, xml, f) 417 | f.match, f.endMatch, f.text, f.endt1, f.tagstr, f.endt2 = string.find(xml, self._XML, f.pos) 418 | if not f.match then 419 | if string.find(xml, self._WS, f.pos) then 420 | -- No more text - check document complete 421 | if #self._stack ~= 0 then 422 | err(self, self._errstr.incompleteXmlErr, f.pos) 423 | else 424 | return false 425 | end 426 | else 427 | -- Unparsable text 428 | err(self, self._errstr.xmlErr, f.pos) 429 | end 430 | end 431 | 432 | f.text = f.text or '' 433 | f.tagstr = f.tagstr or '' 434 | f.match = f.match or 0 435 | 436 | return f.endMatch ~= nil 437 | end 438 | 439 | --Main function which starts the XML parsing process 440 | --@param xml the XML string to parse 441 | --@param parseAttributes indicates if tag attributes should be parsed or not. 442 | -- If omitted, the default value is true. 443 | function XmlParser:parse(xml, parseAttributes) 444 | if type(self) ~= "table" or getmetatable(self) ~= XmlParser then 445 | error("You must call xmlparser:parse(parameters) instead of xmlparser.parse(parameters)") 446 | end 447 | 448 | if parseAttributes == nil then 449 | parseAttributes = true 450 | end 451 | 452 | self.handler.parseAttributes = parseAttributes 453 | 454 | --Stores string.find results and parameters 455 | --and other auxiliar variables 456 | local f = { 457 | --string.find return 458 | match = 0, 459 | endMatch = 0, 460 | -- text, end1, tagstr, end2, 461 | 462 | --string.find parameters and auxiliar variables 463 | pos = 1, 464 | -- startText, endText, 465 | -- errStart, errEnd, extStart, extEnd, 466 | } 467 | 468 | while f.match do 469 | if not getNextTag(self, xml, f) then 470 | break 471 | end 472 | 473 | -- Handle leading text 474 | f.startText = f.match 475 | f.endText = f.match + string.len(f.text) - 1 476 | f.match = f.match + string.len(f.text) 477 | f.text = parseEntities(self, stripWS(self, f.text)) 478 | if f.text ~= "" and fexists(self.handler, 'text') then 479 | self.handler:text(f.text, nil, f.match, f.endText) 480 | end 481 | 482 | parseTagType(self, xml, f) 483 | f.pos = f.endMatch + 1 484 | end 485 | end 486 | 487 | XmlParser.__index = XmlParser 488 | return XmlParser 489 | 490 | end 491 | 492 | local XmlParser = load_XmlParser() 493 | 494 | ---Recursivelly prints a table in an easy-to-ready format 495 | --@param tb The table to be printed 496 | --@param level the indentation level to start with 497 | local function printableInternal(tb, level) 498 | if tb == nil then 499 | return 500 | end 501 | 502 | level = level or 1 503 | local spaces = string.rep(' ', level*2) 504 | for k,v in pairs(tb) do 505 | if type(v) == "table" then 506 | print(spaces .. k) 507 | printableInternal(v, level+1) 508 | else 509 | print(spaces .. k..'='..v) 510 | end 511 | end 512 | end 513 | 514 | ---Instantiates a XmlParser object to parse a XML string 515 | --@param handler Handler module to be used to convert the XML string 516 | --to another formats. See the available handlers at the handler directory. 517 | -- Usually you get an instance to a handler module using, for instance: 518 | -- local handler = require("xmlhandler/tree"). 519 | --@return a XmlParser object used to parse the XML 520 | --@see XmlParser 521 | function xml2lua.parser(handler) 522 | if handler == xml2lua then 523 | error("You must call xml2lua.parse(handler) instead of xml2lua:parse(handler)") 524 | end 525 | 526 | local options = { 527 | --Indicates if whitespaces should be striped or not 528 | stripWS = 1, 529 | expandEntities = 1, 530 | errorHandler = function(errMsg, pos) 531 | error(string.format("%s [char=%d]\n", errMsg or "Parse Error", pos)) 532 | end 533 | } 534 | 535 | return XmlParser.new(handler, options) 536 | end 537 | 538 | ---Recursivelly prints a table in an easy-to-ready format 539 | --@param tb The table to be printed 540 | function xml2lua.printable(tb) 541 | printableInternal(tb) 542 | end 543 | 544 | ---Handler to generate a string prepresentation of a table 545 | --Convenience function for printHandler (Does not support recursive tables). 546 | --@param t Table to be parsed 547 | --@return a string representation of the table 548 | function xml2lua.toString(t) 549 | local sep = '' 550 | local res = '' 551 | if type(t) ~= 'table' then 552 | return t 553 | end 554 | 555 | for k,v in pairs(t) do 556 | if type(v) == 'table' then 557 | v = xml2lua.toString(v) 558 | end 559 | res = res .. sep .. string.format("%s=%s", k, v) 560 | sep = ',' 561 | end 562 | res = '{'..res..'}' 563 | 564 | return res 565 | end 566 | 567 | --- Loads an XML file from a specified path 568 | -- @param xmlFilePath the path for the XML file to load 569 | -- @return the XML loaded file content 570 | function xml2lua.loadFile(xmlFilePath) 571 | local f, e = io.open(xmlFilePath, "r") 572 | if f then 573 | --Gets the entire file content and stores into a string 574 | local content = f:read("*a") 575 | f:close() 576 | return content 577 | end 578 | 579 | error(e) 580 | end 581 | 582 | ---Gets an _attr element from a table that represents the attributes of an XML tag, 583 | --and generates a XML String representing the attibutes to be inserted 584 | --into the openning tag of the XML 585 | -- 586 | --@param attrTable table from where the _attr field will be got 587 | --@return a XML String representation of the tag attributes 588 | local function attrToXml(attrTable) 589 | local s = "" 590 | attrTable = attrTable or {} 591 | 592 | for k, v in pairs(attrTable) do 593 | s = s .. " " .. k .. "=" .. '"' .. v .. '"' 594 | end 595 | return s 596 | end 597 | 598 | ---Gets the first key of a given table 599 | local function getFirstKey(tb) 600 | if type(tb) == "table" then 601 | for k, _ in pairs(tb) do 602 | return k 603 | end 604 | return nil 605 | end 606 | 607 | return tb 608 | end 609 | 610 | --- Parses a given entry in a lua table 611 | -- and inserts it as a XML string into a destination table. 612 | -- Entries in such a destination table will be concatenated to generated 613 | -- the final XML string from the origin table. 614 | -- @param xmltb the destination table where the XML string from the parsed key will be inserted 615 | -- @param tagName the name of the table field that will be used as XML tag name 616 | -- @param fieldValue a field from the lua table to be recursively parsed to XML or a primitive value that will be enclosed in a tag name 617 | -- @param level a int value used to include indentation in the generated XML from the table key 618 | local function parseTableKeyToXml(xmltb, tagName, fieldValue, level) 619 | local spaces = string.rep(' ', level*2) 620 | 621 | local strValue, attrsStr = "", "" 622 | if type(fieldValue) == "table" then 623 | attrsStr = attrToXml(fieldValue._attr) 624 | fieldValue._attr = nil 625 | --If after removing the _attr field there is just one element inside it, 626 | --the tag was enclosing a single primitive value instead of other inner tags. 627 | strValue = #fieldValue == 1 and spaces..tostring(fieldValue[1]) or xml2lua.toXml(fieldValue, tagName, level+1) 628 | strValue = '\n'..strValue..'\n'..spaces 629 | else 630 | strValue = tostring(fieldValue) 631 | end 632 | 633 | table.insert(xmltb, spaces..'<'..tagName.. attrsStr ..'>'..strValue..'') 634 | end 635 | 636 | ---Converts a Lua table to a XML String representation. 637 | --@param tb Table to be converted to XML 638 | --@param tableName Name of the table variable given to this function, 639 | -- to be used as the root tag. If a value is not provided 640 | -- no root tag will be created. 641 | --@param level Only used internally, when the function is called recursively to print indentation 642 | -- 643 | --@return a String representing the table content in XML 644 | function xml2lua.toXml(tb, tableName, level) 645 | level = level or 1 646 | local firstLevel = level 647 | tableName = tableName or '' 648 | local xmltb = (tableName ~= '' and level == 1) and {'<'..tableName..attrToXml(tb._attr)..'>'} or {} 649 | tb._attr = nil 650 | 651 | for k, v in pairs(tb) do 652 | if type(v) == 'table' then 653 | -- If the key is a number, the given table is an array and the value is an element inside that array. 654 | -- In this case, the name of the array is used as tag name for each element. 655 | -- So, we are parsing an array of objects, not an array of primitives. 656 | if type(k) == 'number' then 657 | parseTableKeyToXml(xmltb, tableName, v, level) 658 | else 659 | level = level + 1 660 | -- If the type of the first key of the value inside the table 661 | -- is a number, it means we have a HashTable-like structure, 662 | -- in this case with keys as strings and values as arrays. 663 | if type(getFirstKey(v)) == 'number' then 664 | for sub_k, sub_v in pairs(v) do 665 | if sub_k ~= '_attr' then 666 | local sub_v_with_attr = type(v._attr) == 'table' and { sub_v, _attr = v._attr } or sub_v 667 | parseTableKeyToXml(xmltb, k, sub_v_with_attr, level) 668 | end 669 | end 670 | else 671 | -- Otherwise, the "HashTable" values are objects 672 | parseTableKeyToXml(xmltb, k, v, level) 673 | end 674 | end 675 | else 676 | -- When values are primitives: 677 | -- If the type of the key is number, the value is an element from an array. 678 | -- In this case, uses the array name as the tag name. 679 | if type(k) == 'number' then 680 | k = tableName 681 | end 682 | parseTableKeyToXml(xmltb, k, v, level) 683 | end 684 | end 685 | 686 | if tableName ~= '' and firstLevel == 1 then 687 | table.insert(xmltb, '\n') 688 | end 689 | 690 | return table.concat(xmltb, '\n') 691 | end 692 | 693 | local function load_TreeHandler() 694 | 695 | local function init() 696 | local obj = { 697 | root = {}, 698 | options = {noreduce = {}} 699 | } 700 | 701 | obj._stack = {obj.root} 702 | return obj 703 | end 704 | 705 | -- @module XML Tree Handler. 706 | -- Generates a lua table from an XML content string. 707 | -- It is a simplified handler which attempts 708 | -- to generate a more 'natural' table based structure which 709 | -- supports many common XML formats. 710 | -- 711 | -- The XML tree structure is mapped directly into a recursive 712 | -- table structure with node names as keys and child elements 713 | -- as either a table of values or directly as a string value 714 | -- for text. Where there is only a single child element this 715 | -- is inserted as a named key - if there are multiple 716 | -- elements these are inserted as a vector (in some cases it 717 | -- may be preferable to always insert elements as a vector 718 | -- which can be specified on a per element basis in the 719 | -- options). Attributes are inserted as a child element with 720 | -- a key of '_attr'. 721 | -- 722 | -- Only Tag/Text & CDATA elements are processed - all others 723 | -- are ignored. 724 | -- 725 | -- This format has some limitations - primarily 726 | -- 727 | -- * Mixed-Content behaves unpredictably - the relationship 728 | -- between text elements and embedded tags is lost and 729 | -- multiple levels of mixed content does not work 730 | -- * If a leaf element has both a text element and attributes 731 | -- then the text must be accessed through a vector (to 732 | -- provide a container for the attribute) 733 | -- 734 | -- In general however this format is relatively useful. 735 | -- 736 | -- It is much easier to understand by running some test 737 | -- data through 'testxml.lua -simpletree' than to read this) 738 | -- 739 | -- Options 740 | -- ======= 741 | -- options.noreduce = { = bool,.. } 742 | -- - Nodes not to reduce children vector even if only 743 | -- one child 744 | -- 745 | -- License: 746 | -- ======== 747 | -- 748 | -- This code is freely distributable under the terms of the [MIT license](LICENSE). 749 | -- 750 | --@author Paul Chakravarti (paulc@passtheaardvark.com) 751 | --@author Manoel Campos da Silva Filho 752 | local tree = init() 753 | 754 | ---Instantiates a new handler object. 755 | --Each instance can handle a single XML. 756 | --By using such a constructor, you can parse 757 | --multiple XML files in the same application. 758 | --@return the handler instance 759 | function tree:new() 760 | local obj = init() 761 | 762 | obj.__index = self 763 | setmetatable(obj, self) 764 | 765 | return obj 766 | end 767 | 768 | --- Recursively removes redundant vectors for nodes 769 | -- with single child elements 770 | function tree:reduce(node, key, parent) 771 | for k,v in pairs(node) do 772 | if type(v) == 'table' then 773 | self:reduce(v,k,node) 774 | end 775 | end 776 | if #node == 1 and not self.options.noreduce[key] and 777 | node._attr == nil then 778 | parent[key] = node[1] 779 | end 780 | end 781 | 782 | 783 | --- If an object is not an array, 784 | -- creates an empty array and insert that object as the 1st element. 785 | -- 786 | -- It's a workaround for duplicated XML tags outside an inner tag. Check issue #55 for details. 787 | -- It checks if a given tag already exists on the parsing stack. 788 | -- In such a case, if that tag is represented as a single element, 789 | -- an array is created and that element is inserted on it. 790 | -- The existing tag is then replaced by the created array. 791 | -- For instance, if we have a tag x = {attr1=1, attr2=2} 792 | -- and another x tag is found, the previous entry will be changed to an array 793 | -- x = {{attr1=1, attr2=2}}. This way, the duplicated tag will be 794 | -- inserted into this array as x = {{attr1=1, attr2=2}, {attr1=3, attr2=4}} 795 | -- https://github.com/manoelcampos/xml2lua/issues/55 796 | -- 797 | -- @param obj the object to try to convert to an array 798 | -- @return the same object if it's already an array or a new array with the object 799 | -- as the 1st element. 800 | local function convertObjectToArray(obj) 801 | --#obj == 0 verifies if the field is not an array 802 | if #obj == 0 then 803 | local array = {} 804 | table.insert(array, obj) 805 | return array 806 | end 807 | 808 | return obj 809 | end 810 | 811 | ---Parses a start tag. 812 | -- @param tag a {name, attrs} table 813 | -- where name is the name of the tag and attrs 814 | -- is a table containing the atributtes of the tag 815 | function tree:starttag(tag) 816 | local node = {} 817 | if self.parseAttributes == true then 818 | node._attr=tag.attrs 819 | end 820 | 821 | --Table in the stack representing the tag being processed 822 | local current = self._stack[#self._stack] 823 | 824 | if current[tag.name] then 825 | local array = convertObjectToArray(current[tag.name]) 826 | table.insert(array, node) 827 | current[tag.name] = array 828 | else 829 | current[tag.name] = {node} 830 | end 831 | 832 | table.insert(self._stack, node) 833 | end 834 | 835 | ---Parses an end tag. 836 | -- @param tag a {name, attrs} table 837 | -- where name is the name of the tag and attrs 838 | -- is a table containing the atributtes of the tag 839 | function tree:endtag(tag, s) 840 | --Table in the stack representing the tag being processed 841 | --Table in the stack representing the containing tag of the current tag 842 | local prev = self._stack[#self._stack-1] 843 | if not prev[tag.name] then 844 | error("XML Error - Unmatched Tag ["..s..":"..tag.name.."]\n") 845 | end 846 | if prev == self.root then 847 | -- Once parsing complete, recursively reduce tree 848 | self:reduce(prev, nil, nil) 849 | end 850 | 851 | table.remove(self._stack) 852 | end 853 | 854 | ---Parses a tag content. 855 | -- @param t text to process 856 | function tree:text(text) 857 | local current = self._stack[#self._stack] 858 | table.insert(current, text) 859 | end 860 | 861 | ---Parses CDATA tag content. 862 | tree.cdata = tree.text 863 | tree.__index = tree 864 | return tree 865 | 866 | end 867 | 868 | xml2lua.TreeHandler = load_TreeHandler() 869 | 870 | return xml2lua 871 | -------------------------------------------------------------------------------- /lib/constructor/translations.lua: -------------------------------------------------------------------------------- 1 | -- Constructor Translations 2 | 3 | local SCRIPT_VERSION = "0.49" 4 | local translations = {} 5 | 6 | --- 7 | --- Debug Log 8 | --- 9 | 10 | local function debug_log(message, additional_details) 11 | if CONSTRUCTOR_CONFIG.debug_mode then 12 | if CONSTRUCTOR_CONFIG.debug_mode == 2 and additional_details ~= nil then 13 | message = message .. "\n" .. inspect(additional_details) 14 | end 15 | util.log("[constructor_translations] "..message) 16 | end 17 | end 18 | 19 | --- 20 | --- Translation Helpers 21 | --- 22 | 23 | translations.lang = {} 24 | translations.current_translations = {} 25 | translations.missing_translations = {} 26 | local LANG_STRING_NOT_FOUND = "/!\\ STRING NOT FOUND /!\\" 27 | 28 | function CONSTRUCTOR_TRANSLATE_FUNCTION(text) 29 | local translated_string = translations.current_translations[text] 30 | if translated_string ~= nil and translated_string ~= LANG_STRING_NOT_FOUND then 31 | --debug_log("Found local translation for '"..text.."'") 32 | return translated_string 33 | end 34 | local label_id = lang.find(text, "en") 35 | if label_id then 36 | --debug_log("Found global translation for '"..text.."'") 37 | translated_string = lang.get_string(label_id, lang.get_current()) 38 | if translated_string ~= LANG_STRING_NOT_FOUND then 39 | return translated_string 40 | end 41 | else 42 | --debug_log("Missing translation: "..text) 43 | translations.missing_translations[text] = text 44 | end 45 | return text 46 | end 47 | 48 | translations.inject_custom_translations = function() 49 | for lang_id, language_key in pairs(translations.GAME_LANGUAGE_IDS) do 50 | if translations.lang[lang_id] ~= nil then 51 | --debug_log("Processing translations language "..lang_id) 52 | lang.set_translate(lang_id) 53 | for english_string, translated_string in pairs(translations.lang[lang_id]) do 54 | local label_id = lang.find(english_string, "en") 55 | --debug_log("Found label for '"..english_string.."' as label "..label_id) 56 | if (not label_id) or label_id == 0 then 57 | label_id = lang.register(english_string) 58 | --debug_log("Registered '"..english_string.."' as label "..label_id) 59 | end 60 | local existing_translation = lang.get_string(label_id, lang_id) 61 | if (not existing_translation) or existing_translation == english_string or existing_translation == LANG_STRING_NOT_FOUND then 62 | --debug_log("Adding translation for "..lang_id.." '"..english_string.."' ["..label_id.."] as '"..translated_string.."' Existing translation: '"..existing_translation.."'") 63 | if label_id > 0 then 64 | local translate_status, translate_response = pcall(lang.translate, label_id, translated_string) 65 | if not translate_status then 66 | debug_log("Failed to add translation '"..english_string.."' as label "..label_id) 67 | end 68 | else 69 | --debug_log("Cannot translate internal label") 70 | end 71 | if lang_id == lang.get_current() then 72 | translations.current_translations[english_string] = translated_string 73 | end 74 | else 75 | --debug_log("Found translation for "..lang_id.." '"..english_string.."' ["..label_id.."] as '"..existing_translation.."'") 76 | end 77 | end 78 | end 79 | end 80 | end 81 | 82 | translations.log_missing_translations = function() 83 | util.toast("Logged "..#translations.missing_translations.." missing translations", TOAST_ALL) 84 | util.log(inspect(translations.missing_translations)) 85 | end 86 | 87 | translations.GAME_LANGUAGE_IDS = { 88 | ["en"] = "en-US", 89 | ["fr"] = "fr-FR", 90 | ["de"] = "de-DE", 91 | ["it"] = "it-IT", 92 | ["es"] = "es-ES", 93 | ["pt"] = "pt-BR", 94 | ["pl"] = "pl-PL", 95 | ["ru"] = "ru-RU", 96 | ["ko"] = "ko-KR", 97 | ["zh"] = "zh-TW", 98 | ["ja"] = "ja-JP", 99 | } 100 | translations.LANGUAGE_NAMES = { 101 | ["en-US"] = "English", 102 | ["fr-FR"] = "French", 103 | ["de-DE"] = "German", 104 | ["it-IT"] = "Italian", 105 | ["es-ES"] = "Spanish", 106 | ["pt-BR"] = "Brazilian", 107 | ["pl-PL"] = "Polish", 108 | ["ru-RU"] = "Russian", 109 | ["ko-KR"] = "Korean", 110 | ["zh-TW"] = "Chinese (Traditional)", 111 | ["ja-JP"] = "Japanese", 112 | ["es-MX"] = "Spanish (Mexican)", 113 | ["zh-CN"] = "Chinese (Simplified)" 114 | } 115 | 116 | translations.lang = {} 117 | 118 | --- Chinese (Simplified) 119 | --- By Zelda Two 120 | 121 | translations.lang["zh"] = { 122 | ["More stable, but updated less often."] = "更稳定,但更新频率较低.", 123 | ["Cutting edge updates, but less stable."] = "最新版本,但是不太稳定", 124 | ["Loading..."] = "加载...", 125 | ["Installing auto-updater..."] = "安装自动更新...", 126 | ["Error downloading auto-updater: "] = "自动更新下载错误: ", 127 | ["Found empty file."] = "发现空文件.", 128 | ["Could not open file for writing."] = "无法打开文件进行写入.", 129 | ["Successfully installed auto-updater lib"] = "成功安装自动更新到lib", 130 | ["Error downloading auto-updater lib. Update failed to download."] = "自动更新下载lib错误.更新下载失败.", 131 | ["Error downloading auto-updater lib. HTTP Request timeout"] = "下载lib时出错.HTTP请求超时", 132 | ["Invalid auto-updater lib. Please delete your Stand/Lua Scripts/lib/auto-updater.lua and try again"] = "无效自动更新lib.请删除你的Stand/Lua Scripts/lib/auto-updater.lua 然后在试一次", 133 | ["translations"] = "翻译", 134 | ["Missing translation: "] = "缺少翻译: ", 135 | ["Adding translations for language "] = "添加语言翻译 ", 136 | ["Found label for "] = "建立标签 ", 137 | ["Registered "] = "记录 ", 138 | ["Saving translation for "] = "保存翻译 ", 139 | ["Could not natives lib. Make sure it is selected under Stand > Lua Scripts > Repository > natives-1663599433"] = "没有lib.确保已经下载 Stand > Lua Scripts > Repository > natives-1663599433", 140 | ["Max table depth reached"] = "达到最大组合数量?", 141 | ["Adding attachment to construct "] = "添加附件到构造 ", 142 | ["Removing preview "] = "删除预览 ", 143 | ["Adding a preview "] = "添加预览", 144 | ["Building construct plan description "] = "建造构造平面图说明", 145 | ["Adding preview for construct plan "] = "为模型添加平面图 ", 146 | ["Shoot (or press J) to add "] = "射击 (或按 J) 到添加 ", 147 | ["Attaching "] = "附加 ", 148 | ["Adding spawned construct to list "] = "添加生成的构造到列表中 ", 149 | ["Creating construct from vehicle handle "] = "从载具手把中创建构造 ", 150 | ["Vehicle is already a construct"] = "载具已经是构造", 151 | ["Saving construct "] = "保存构造 ", 152 | ["Cannot write to file "] = "无法写入文件 ", 153 | ["Cannot save vehicle: Error serializing."] = "无法保存载具:序列化错误.", 154 | ["Deleting construct "] = "删除构造", 155 | ["Spawning construct from plan "] = "从计划生成构造 ", 156 | ["Failed to spawn construct from plan "] = "无法从平面图中生成构造 ", 157 | ["Building construct from plan name="] = "从平面图名称中生成构造=", 158 | ["Clean up on close"] = "关闭脚本时清理", 159 | ["Rebuilding "] = "重建 ", 160 | ["Invalid construct file. "] = "无效构造文件. ", 161 | ["Could not read file '"] = "无法读取文件 '", 162 | ["Failed to load XML file: "] = "无法加载 XML 文件: ", 163 | ["Failed to load INI file: "] = "无法加载 INI 文件: ", 164 | ["Failed to load JSON file: "] = "无法加载 JSON 文件: ", 165 | ["Loading construct plan file from "] = "从文件路径加载构造平面图文件 ", 166 | ["Failed to load construct from file "] = "无法从文件加载构造 ", 167 | ["Loaded construct plan "] = "加载构造平面图 ", 168 | ["Animating peds "] = "动画 peds ", 169 | ["Rebuilding ped "] = "重建 ped ", 170 | ["Rebuilding reattach to menu "] = "重新附加到菜单 ", 171 | ["Max depth reached while reattaching"] = "最大地下水位深度重新连接?", 172 | ["Reattaching "] = "重新连接 ", 173 | ["Load More"] = "加载更多", 174 | ["Attachment missing handle"] = "附件缺少手把", 175 | ["Rebuilding attachment menu "] = "重建附件菜单 ", 176 | ["Name"] = "名称", 177 | ["Set name of the attachment"] = "此组件名称", 178 | ["Position"] = "位置", 179 | ["Offset"] = "偏移", 180 | ["X: Left / Right"] = "X: 左/右", 181 | ["Y: Forward / Back"] = "Y: 前/后", 182 | ["Z: Up / Down"] = "Z: 上/下", 183 | ["Rotation"] = "旋转", 184 | ["X: Pitch"] = "X: 俯仰", 185 | ["Y: Roll"] = "Y: 翻滚", 186 | ["Z: Yaw"] = "Z: 偏摆", 187 | ["World Position"] = "世界:偏移", 188 | ["World Rotation"] = "世界:旋转", 189 | ["Hold SHIFT to fine tune"] = "按住SHIFT键进行微调", 190 | ["Options"] = "其他选项", 191 | ["Visible"] = "可见", 192 | ["Will the attachment be visible, or invisible"] = "附件是可见还是不可见", 193 | ["Collision"] = "碰撞", 194 | ["Will the attachment collide with things, or pass through them"] = "附件是会与物体碰撞还是穿过他们", 195 | ["Invincible"] = "无敌", 196 | ["Will the attachment be impervious to damage, or be damageable. AKA Godmode."] = "附件是不是无敌的.", 197 | ["Gravity"] = "重力", 198 | ["Will the attachment be effected by gravity, or be weightless"] = "附件是受重力影响还是失重", 199 | ["Frozen"] = "冻结", 200 | ["Will the attachment be frozen in place, or allowed to move freely"] = "附件会被冻结在原地,还是允许自由移动", 201 | ["VEHICLE"] = "载具", 202 | ["Vehicle Options"] = "载具选项", 203 | ["Additional options available for all vehicle entities"] = "载具实体选项", 204 | ["Engine Always On"] = "引擎启动", 205 | ["Radio Loud"] = "载具电台", 206 | ["If enabled, vehicle radio will play loud enough to be heard outside the vehicle."] = "如果开启载具电台声音大到可以在车外听到.", 207 | ["Sirens"] = "警笛", 208 | ["Off"] = "关", 209 | ["Lights Only"] = "仅灯光", 210 | ["Sirens and Lights"] = "警笛和灯光", 211 | ["Invis Wheels"] = "隐形轮胎", 212 | ["Door Lock Status"] = "门锁状态", 213 | ["Vehicle door locks"] = "载具门锁", 214 | ["Dirt Level"] = "污垢等级", 215 | ["How dirty is the vehicle"] = "载具有多脏", 216 | ["Bullet Proof Tires"] = "防弹轮胎", 217 | ["Burst Tires"] = "爆胎", 218 | ["Are tires burst"] = "车胎爆破", 219 | ["Broken Doors"] = "拆除车门", 220 | ["Remove doors and trunks"] = "移除车门和后备箱", 221 | ["Break Door: Front Left"] = "拆除门:左前", 222 | ["Break Door: Back Left"] = "拆除门:左后", 223 | ["Break Door: Front Right"] = "拆除门:右前", 224 | ["Break Door: Back Right"] = "拆除门:右后", 225 | ["Break Door: Hood"] = "拆除门: 引擎盖", 226 | ["Break Door: Trunk"] = "拆除门: 后备箱", 227 | ["Break Door: Trunk2"] = "拆除门: 后备箱2", 228 | ["Remove door."] = "拆除门.", 229 | ["Ped Options"] = "人物选项", 230 | ["Can Rag Doll"] = "布娃娃", 231 | ["If enabled, the ped can go limp."] = "如果启用,ped将没有布娃娃系统.", 232 | ["Armor"] = "护甲", 233 | ["How much armor does the ped have."] = "ped有多少护甲", 234 | ["On Fire"] = "着火", 235 | ["Will the ped be burning on fire, or not"] = "ped会不会着火", 236 | ["Clothes"] = "衣服", 237 | ["Props"] = "道具", 238 | ["Attachment Options"] = "附件选项", 239 | ["Additional options available for all entities attached to a parent object."] = "附加到父级的实体选项.", 240 | ["Attached"] = "附加", 241 | ["Is this child physically attached to the parent, or does it move freely on its own."] = "这个附着物是附加在身体上还是父级,还是自由移动.", 242 | ["Change Parent"] = "更改父级", 243 | ["Select a new parent for this child. Construct will be rebuilt to accommodate changes."] = "为这个附着物更换一个新的父级,构造将被重建.", 244 | ["Bone Index"] = "骨骼附加位置", 245 | ["Which bone of the parent should this entity be attached to"] = "此实体应附加到父级的哪个骨骼", 246 | ["Bone Index Picker"] = "骨骼搜索", 247 | ["Some common bones can be selected by name"] = "可以按常见名称来搜索骨骼", 248 | ["Soft Pinning"] = "软固定", 249 | ["Will the attachment detach when repaired"] = "维修时附件会分离吗", 250 | ["Separate"] = "分离", 251 | ["Detach attachment from construct to create a new construct"] = "从构造中分离附件以创建新构造", 252 | ["Copy to Me"] = "复制到我", 253 | ["Attach a copy of this object to your Ped."] = "将此Ped的附件附加到你身上.", 254 | ["Making ped into "] = "将 ped 变成 ", 255 | ["More Options"] = "更多选项", 256 | ["LoD Distance"] = "细节距离", 257 | ["Level of Detail draw distance"] = "细节绘制距离", 258 | ["Blip"] = "图标", 259 | ["Blip Sprite"] = "地图图标", 260 | ["Icon to show on mini map for this construct"] = "在小地图上显示该构造的图标", 261 | ["Blip Color"] = "图标颜色", 262 | ["Mini map icon color"] = "小地图的图标颜色", 263 | ["Blip Reference"] = "图标参考", 264 | ["Reference website for blip details"] = "参考网站地图图标的详细信息", 265 | ["Lights"] = "灯", 266 | ["Light On"] = "开灯", 267 | ["If attachment is a light, it will be on and lit (many lights only work during night time)."] = "如果附附件是灯,它是否会被打开(很多灯只会在晚上工作).", 268 | ["Light Disabled"] = "禁灯", 269 | ["If attachment is a light, it will be ALWAYS off, regardless of others settings."] = "如果附件是灯,它将被关闭,只要你开了这个无论这么设置都是关闭.", 270 | ["Proofs"] = "防护", 271 | ["Bullet Proof"] = "防弹", 272 | ["If attachment is impervious to damage from bullets."] = "附件不受子弹伤害", 273 | ["Fire Proof"] = "防火", 274 | ["If attachment is impervious to damage from fire."] = "附件不受火焰伤害", 275 | ["Explosion Proof"] = "防爆炸", 276 | ["If attachment is impervious to damage from explosions."] = "附件不受爆炸伤害", 277 | ["Melee Proof"] = "防近战", 278 | ["If attachment is impervious to damage from melee attacks."] = "附件不受近战伤害", 279 | ["Attachments"] = "附件", 280 | ["Add Attachment"] = "添加附件", 281 | ["Options for attaching other entities to this construct"] = "添加其他附件到该组件内.", 282 | ["Curated"] = "附件大全", 283 | ["Browse a curated collection of attachments"] = "浏览精心挑选的附件", 284 | ["Search"] = "搜索", 285 | ["Search for a prop by name"] = "按名称搜索大致附件", 286 | ["Add by Name"] = "搜索添加", 287 | ["Add an object, vehicle, or ped by exact name."] = "搜索准确名称添加物体,车辆或人物.", 288 | ["Object by Name"] = "物体名称", 289 | ["Add an in-game object by exact name. To search for objects try https://gta-objects.xyz/"] = "搜索准确名称添加", 290 | ["Ped by Name"] = "人物名称", 291 | ["Add a vehicle by exact name."] = "搜索准确名称添加", 292 | ["Open gta-objects.xyz"] = "打开用于搜索模型名称的网站", 293 | ["Website for browsing and searching for props"] = "用于浏览和搜索附件的网站", 294 | ["Add Attachment Gun"] = "附件添加枪", 295 | ["Anything you shoot with this enabled will be added to the current construct"] = "启用此功能射击的任何物体都将添加到当前构造中", 296 | ["Edit Attachments"] = "编辑附件", 297 | ["Clone"] = "克隆", 298 | ["Clone (In Place)"] = "克隆 (原处)", 299 | ["Clone Reflection: X:Left/Right"] = "克隆映像:X:左/右", 300 | ["Clone Reflection: Y:Front/Back"] = "克隆映像:Y:前/后", 301 | ["Clone Reflection: Z:Up/Down"] = "克隆映像:Z:上/下", 302 | ["Actions"] = "行动", 303 | ["Teleport"] = "传送", 304 | ["Teleport Into Vehicle"] = "传送到载具", 305 | ["Move your player into the vehicle driver seat"] = "传送人物到驾驶位上", 306 | ["Teleport Me to Construct"] = "传送我到 构造", 307 | ["Teleport Construct to Me"] = "构造 传送到我", 308 | ["Debug Info"] = "调试信息", 309 | ["Rebuild"] = "重新创建", 310 | ["Delete construct (if it still exists), then recreate a new one from scratch."] = "删除当前组件并重新生成.", 311 | ["Save As"] = "另存为", 312 | ["Save construct to disk"] = "将构造保存到磁盘", 313 | ["Delete"] = "删除", 314 | ["Delete construct and all attachments. Cannot be reconstructed unless saved."] = "删除构造和所有附件.除非保存,否则无法重建.", 315 | ["Are you sure you want to delete this construct? "] = "您确定要删除此构造吗? ", 316 | [" children will also be deleted."] = " 附件也将被删除.", 317 | --["Rebuilding attachment menu "] = "重建附件菜单 ", 318 | ["Focusing on attachment menu "] = "调整附件菜单 ", 319 | ["Create New Construct"] = "创建新构造", 320 | ["Vehicle"] = "载具", 321 | ["From Current"] = "当前载具", 322 | ["Create a new construct based on current (or last in) vehicle"] = "在当前载具(或最后一辆)进行构建", 323 | ["Error: You must be (or recently been) in a vehicle to create a construct from it"] = "错误:你必须在坐过当前载具(或最后一辆)才能进行构造", 324 | ["From Vehicle Name"] = "载具名称", 325 | ["Create a new construct from an exact vehicle name"] = "搜索载具名称并生成", 326 | ["Structure (Map)"] = "构造(地图)", 327 | ["From New Construction Cone"] = "物体构造", 328 | ["Create a new stationary construct"] = "搜索并创建一个新的固定模型", 329 | ["From Object Name"] = "物体名称", 330 | ["Create a new stationary construct from an exact object name"] = "从物体名称搜索一个新的固定模型", 331 | ["Ped (Player Skin)"] = "玩家模型(玩家皮肤)", 332 | ["From Me"] = "构造自己", 333 | ["Create a new construct from your player Ped"] = "对当前模型进行自我构造", 334 | ["Player is already a construct"] = "玩家已经是一个构造体", 335 | ["From Ped Name"] = "人物名称", 336 | ["Create a new Ped construct from exact name"] = "搜索创建一个新的人物模型", 337 | ["Load Construct"] = "加载构造体", 338 | ["Load a previously saved or shared construct into the world"] = "将保存或共享的构造加载到世界中", 339 | ["Loaded Constructs"] = "已加载构造", 340 | ["View and edit already loaded constructs"] = "查看和编辑已经加载的构造", 341 | ["Search all your construct files"] = "搜索文件并生成", 342 | ["Edit your search query"] = "搜索构造体", 343 | ["No results found"] = "没有搜索结果", 344 | ["Missing downloaded file "] = "缺少下载文件 ", 345 | ["File downloaded "] = "文件已下载 ", 346 | ["Unzipping"] = "正在解压缩", 347 | ["Unzipped"] = "已解压缩", 348 | ["Open Constructs Folder"] = "打开模组文件夹", 349 | ["Open constructs folder. Share your creations or add new creations here."] = "打开模组文件夹.在此处分享您的作品或添加新作品.", 350 | ["Download Curated Constructs"] = "下载精选模组", 351 | ["Download a curated collection of constructs."] = "下载精选的模组集合", 352 | ["Drive Spawned Vehicles"] = "驾驶生成载具", 353 | ["When spawning vehicles, automatically place you into the drivers seat."] = "生成载具时,自动将您置于驾驶员座位.", 354 | ["Wear Spawned Peds"] = "穿上生成的人物模型", 355 | ["When spawning peds, replace your player skin with the ped."] = "生成人物模型后将用人物模型的皮肤代替玩家的皮肤.", 356 | ["Browse"] = "浏览", 357 | ["Global Configs"] = "全局配置", 358 | ["Edit Offset Step"] = "编辑偏移量", 359 | ["The amount of change each time you edit an attachment offset (hold SHIFT for fine tuning)"] = "每次编辑附件偏移时的变化量(按住SHIFT键进行微调)", 360 | ["Edit Rotation Step"] = "编辑旋转量", 361 | ["The amount of change each time you edit an attachment rotation (hold SHIFT for fine tuning)"] = "每次编辑附件旋转时的变化量(按住SHIFT键进行微调)", 362 | ["Show Previews"] = "显示预览", 363 | ["Show previews when adding attachments"] = "选择附件时显示预览", 364 | ["Preview Display Delay"] = "显示预览延迟", 365 | ["After browsing to a construct or attachment, wait this long before showing the preview."] = "在浏览一个组件或单附件,需要多久才会显示出来.", 366 | ["Delete All on Unload"] = "停止脚本时删除所有生成", 367 | ["Deconstruct all spawned constructs when unloading Constructor"] = "停止脚本时解构所有生成的组件或附件", 368 | ["Clean Up"] = "清理", 369 | ["Remove nearby vehicles, objects and peds. Useful to delete any leftover construction debris."] = "移除附近所有生成的载具,物体和ped组件", 370 | ["Script Meta"] = "脚本信息", 371 | ["Constructor"] = "[构造]Constructor", 372 | ["Version"] = "版本", 373 | ["Release Branch"] = "更新发布处", 374 | ["Switch from main to dev to get cutting edge updates, but also potentially more bugs."] = "从main[稳定]切换到dev[开发]可能会有更多bug.", 375 | ["Check for Update"] = "检查更新", 376 | ["The script will automatically check for updates at most daily, but you can manually check using this option anytime."] = "脚本会每天自动检查更新,但是你也可以用此项手动检查.", 377 | ["No updates found"] = "没有更新", 378 | ["Clean Reinstall"] = "清除重新安装", 379 | ["Force an update to the latest version, regardless of current version."] = "强制更新到最新版本,而不考虑当前版本.", 380 | ["Debug Mode"] = "调试模式", 381 | ["Log additional details about Constructors actions."] = "记录构造者调试信息到Stand Log", 382 | ["Log Missing Translations"] = "记录缺失翻译", 383 | ["Log any newly found missing translations"] = "记录任何缺失的翻译", 384 | ["Github Source"] = "Github资源", 385 | ["View source files on Github"] = "在Github上查看源文件", 386 | ["Discord"] = "官方Discord", 387 | ["Open Discord Server"] = "打开Discord服务器", 388 | ["Credits"] = "鸣谢", 389 | ["Developers"] = "开发", 390 | ["Main developer"] = "本脚本开发", 391 | ["Development, Testing, Suggestions and Support"] = "测试,建议和支持", 392 | ["Inspirations"] = "启示", 393 | ["Much of Constructor is based on code originally copied from Jackz Vehicle Builder and this script wouldn't be possible without it. Constructor is just my own copy of Jackz's amazing work. Thank you Jackz!"] = "大部分Constructor都是基于Jackz Vehicle Builder的代码,没有它.这个脚本就不可能实现.构造者只是我对Jackz惊人作品的复制品.谢谢你,Jackz!", 394 | ["LanceSpooner is also a huge inspiration to this script. Thanks Lance!"] = "构造者要感谢LanceSpooner的启发,谢谢你Lance!", 395 | ["Install Curated Constructs"] = "安装作者推荐精选模组", 396 | ["Download and install a curated collection of constructs from https://github.com/hexarobi/stand-curated-constructs"] = "从以下网站下载并安装精选的构造合集 https://github.com/hexarobi/stand-curated-constructs", 397 | ["Development, Ped Curation"] = "开发, Ped策划", 398 | ["Focus Menu on Spawned Constructs"] = "生成组件自动切换父级菜单", 399 | ["When spawning a construct, focus Stands menu on the newly spawned construct. Otherwise, stay in the Load Constructs menu."] = "生成组件时自动帮你切换到已加载的构造菜单中", 400 | ["From Nearby"] = "附近载具", 401 | ["Create a new construct based on nearby vehicle"] = "从附近载具创建新构造", 402 | ["Entity Options"] = "实体选项", 403 | ["Additional options available for all entities."] = "实体选择选", 404 | ["Alpha"] = "透明度", 405 | ["The amount of transparency the object has. Local only!"] = "该物体透明度.仅限本地!", 406 | ["Mission Entity"] = "任务实体", 407 | ["If attachment is treated as a mission entity."] = "附件被视为任务实体", 408 | ["Give Weapon"] = "给予武器", 409 | ["Give the ped a weapon."] = "给予该ped武器", 410 | ["Vehicle by Name"] = "载具名称", 411 | ["From Current Vehicle"] = "当前载具", 412 | ["From Vehicle List"] = "载具列表", 413 | ["Create a new construct from a list of vehicles"] = "从载具列表中生成新的载具", 414 | ["From Object Search"] = "物体名称", 415 | ["Create a new map by searching for a object"] = "搜索物体名称并生成", 416 | ["From Current Ped"] = "自我构造", 417 | ["From Ped List"] = "人物列表", 418 | ["Create a new construct from a list of peds"] = "从人物列表中生成新的人物", 419 | ["Spawn Entity Delay"] = "生成实体延迟", 420 | ["Pause after spawning any object. Useful for preventing issues when spawning large constructs with many objects."] = "每个实体生成之间的延迟.", 421 | ["Freeze Position"] = "冻结位置", 422 | ["Will the construct be frozen in place, or allowed to move freely"] = "附件是否被冻结在原地.", 423 | ["Particle Effects"] = "粒子特效", 424 | ["Browse a curated collection of particle effects"] = "预览精品粒子特效", 425 | ["Clean Up Distance"] = "清理距离", 426 | ["How far away the cleanup command will reach to delete entities."] = "实体的清除距离是多少", 427 | ["Info"] = "信息", 428 | ["Set name of the player that created this construct"] = "创建此模组的作者名字", 429 | ["Description"] = "描述", 430 | ["Set text to describe this construct"] = "此模组的描述", 431 | ["Create a new construct from a base vehicle, object, or ped. Then extend it with attachments. Finally save your creation and share it with others."] = "从一个车辆、物体或人物创建一个新的组合.然后用附件来拼接它.最后保存你的创作并与他人分享.", 432 | ["Set global configuration options."] = "设置全局配置选项", 433 | ["Information and options about the Constructor script itself."] = "关于此脚本的相关信息和选项", 434 | ["Chat Spawnable Dir"] = "聊天生成目录", 435 | ["Set a Constructs sub-folder to be spawnable by name. Only available for users with permission to use Spawn Commands. See Online>Chat>Commands"] = "将Constructs子文件夹设置为名称生成.仅适用于有权使用“生成命令”的用户.请参见线上>聊天>命令", 436 | ["Constructs Allowed Per Player"] = "构造体上限", 437 | ["CqCq and Zelda Two"] = "Zelda Two", 438 | ["The number of constructs any one player can spawn at a time. When a player tried to spawn additional constructs past this limit, the oldest spawned construct will be deleted."] = "玩家可生成的构造数,当生成指定数后将删除老的构造.", 439 | ["Editing"] = "编辑", 440 | ["Set configuration options relating to editing constructs."] = "设置编辑与constructs有关的配置选项", 441 | ["Previews"] = "预览", 442 | ["Set configuration options relating to previewing constructs."] = "设置预览constructs有关的配置选项", 443 | ["Debug"] = "调试", 444 | ["Chat Spawn Commands"] = "聊天生成命令", 445 | ["Hold SHIFT to fine tune, or hold CONTROL to move ten steps at once."] = "按住SHIFT键进行微调,或按住Ctrl键一次移动10倍.", 446 | ["Set configuration options relating to debugging the menu."] = "设置调试菜单有关的配置选项", 447 | ["Set configuration options relating to spawning constructs."] = "设置生成constructs有关的配置选项", 448 | ["Translators"] = "翻译", 449 | ["Auto-Update"] = "自动更新", 450 | ["Rotation Order"] = "旋转顺序", 451 | ["Set the order that rotations should be applied."] = "设置旋转顺序", 452 | ["Additional options available for Ped entities."] = "人物可用选项.", 453 | ["Make a new copy of this entity, at the same location as the original"] = "为该实体制作一个新的副本,位置与原实体相同", 454 | ["Make a new copy of this entity, but at the mirror location about the X-axis"] = "为该实体制作一个新的副本,位置在X轴镜像位置.", 455 | ["Make a new copy of this entity, but at the mirror location about the Y-axis"] = "为该实体制作一个新的副本,位置在Y轴镜像位置.", 456 | ["Make a new copy of this entity, but at the mirror location about the Z-axis"] = "为该实体制作一个新的副本,位置在Z轴镜像位置.", 457 | ["Move your player to the construct, or vice versa."] = "玩家传到构造或构造传送到玩家.", 458 | ["Information about the construct"] = "有关该构造的信息", 459 | ["Position and Rotation options"] = "位置和旋转选项", 460 | ["Configuration options for this entity"] = "该实体的配置选项", 461 | ["Modify other entities attached to this entity"] = "修改附加到此实体的其他实体", 462 | ["Make a copy of this attachment"] = "复制此附件", 463 | ["Move your player nearby the construct, but not inside of it."] = "将玩家传送到构造附近", 464 | ["Move the construct to be nearby your player"] = "将构造移动到玩家附近", 465 | ["Free Edit"] = "自由编辑", 466 | ["Position this object using controller or mouse"] = "使用手柄或鼠标拖动该物体", 467 | ["Always Spawn at Position"] = "始终在位置上生成", 468 | ["Other Construct"] = "其他构造", 469 | ["Attach another construct to the current construct"] = "将另一个结构体附加到当前结构体上", 470 | ["Object Tint"] = "物体色调", 471 | ["The color of the object"] = "物体颜色", 472 | ["LS Customs"] = "载具定制", 473 | ["Vehicle modifications normally available in Los Santos Customs"] = "载具改装定制服务", 474 | ["Steering Bias"] = "转向偏差", 475 | ["Set wheel position. Must be driving to set, but will stay when you exit until someone else drives."] = "设置轮胎左右偏差", 476 | ["Siren Control"] = "警笛控制", 477 | ["If enabled, and this vehicle has a siren, then siren controls will effect this vehicle. Has no effect on vehicles without a siren."] = "如果启用,并且这辆车有警笛,那么警笛控制将影响这辆车.对没有警笛的车辆没有影响.", 478 | ["Engine Sound"] = "引擎声音", 479 | ["Set vehicle engine sound from another vehicle name."] = "从另一个载具名称设置载具引擎声音", 480 | ["Invisible Wheels"] = "隐形轮胎", 481 | ["If enabled, the vehicle wheels will be invisible"] = "如果启用,车轮将不可见", 482 | ["Drift Tires"] = "漂移轮胎", 483 | ["If enabled, the vehicle tires will have low grip"] = "如果启用,车辆轮胎将具有低抓地力", 484 | ["Paint Fade"] = "车漆掉色", 485 | ["Windows Rolled Down"] = "窗户摇下", 486 | ["Roll up and down windows"] = "摇上摇下窗户", 487 | ["Windows Broken"] = "窗户破碎", 488 | ["Loop Timer"] = "循环时间", 489 | ["How often should the effect repeat. If this is 0 then it should try to loop forever."] = "粒子特效应该多久循环一次,为0时将一直循环.", 490 | ["Particle Color"] = "粒子颜色", 491 | ["Particle effect size"] = "粒子大小比例", 492 | ["Ignore Events"] = "忽略事件", 493 | ["If enabled, the ped will ignore events going on that might otherwise cause the ped to flee or cower."] = "如果开启,NPC就无视周围的事件,避免NPC逃跑.", 494 | ["Animation"] = "动画", 495 | ["Dictionary"] = "代码", 496 | ["Set the animation dictionary available for the animation name to load from."] = "设置动画名称对应的动画代码", 497 | ["Set the animation name to load from the dictionary"] = "设置动画代码对应的动画名称", 498 | ["Animation List"] = "动画列表", 499 | ["Scenario"] = "场景", 500 | ["Set the animation scenario, separate from dictionary and name."] = "设置动画场景,与代码和名称分开", 501 | ["Scenario List"] = "场景列表", 502 | ["Will this construct always spawn at the same world position it's in now (for stationary maps) or in front of the player (for props or small structures)"] = "该建筑是否总是在它现在所处的世界位置(对于静止的地图)或在玩家面前(对于道具或小型结构)生成?", 503 | ["Automatically install updates as they are released. Disable if you cannot successfully fetch updates as normal."] = "作者更新lua时会自动进行更新,如果你无法更新,请取消自动更新.", 504 | --["From Vehicle List"] = "载具列表", 505 | --["From Ped List"] = "人物列表", 506 | ["About Constructor"] = "关于Constructor", 507 | ["Gizmo Edit Mode"] = "箭头编辑模式", 508 | ["Enabled mouse-based edit mode. Click any world object to enable edit handles."] = "启用基于鼠标的编辑模式.单击任何世界物体以启用编辑模式.", 509 | ["Select gizmo arrow"] = "按住旋转视角", 510 | ["Hold to spin camera"] = "箭头点击物体", 511 | ["Edit Modes"] = "编辑模式", 512 | ["Gizmo Edit"] = "箭头编辑", 513 | ["Position this object using clickable arrow handles"] = "点击箭头方向拖动物体位置", 514 | --["Free Edit"] = "自由编辑", 515 | --["Position this object using controller or mouse"] = "用鼠标或手柄来移动物体位置", 516 | ["Attached to Parent"] = "附加到父级", 517 | ["Seat"] = "座位", 518 | ["If attached to a vehicle, which seat should this ped occupy."] = "NPC如果连接到车上应该在哪个座椅上.", 519 | ["Unseated"] = "没有", 520 | ["Any free seat"] = "副驾驶", 521 | ["Passenger"] = "乘客", 522 | ["Backseat Driver Side"] = "驾驶后排", 523 | ["Backseat Passenger Side"] = "乘客后排", 524 | ["Head Overlays"] = "头部覆盖", 525 | ["Eye Color"] = "眼睛颜色", 526 | ["Configure animation options"] = "配置动画选项", 527 | ["Current"] = "当前动画", 528 | ["Refresh Timer"] = "动画刷新时间", 529 | ["How often should the animation be totally reset. 0 means never."] = "动画刷新频率是多少.0等于循环播放", 530 | ["Immediately stops the current animation"] = "停止当前动画", 531 | ["Browse Animations"] = "浏览动画", 532 | ["Set by Name"] = "名称设置", 533 | ["Set a specific animation dictionary+clip or scenario"] = "设置特定的场景或剪辑动画", 534 | ["Dictionary and Clip"] = "场景或剪辑", 535 | ["Set the animation dictionary and clip (separated by a space)."] = "场景或剪辑动画(用空格分开).", 536 | ["Dictionary and Clip List"] = "场景或剪辑列表", 537 | ["Set the animation scenario."] = "设置动画场景", 538 | ["Special Physics"] = "特殊物理", 539 | ["Allow for special physics overrides"] = "允许特殊物理覆盖", 540 | --["Browse"] = "浏览", 541 | --["More Options"] = "更多选项", 542 | ["Add Current Vehicle"] = "添加当前载具", 543 | ["Attach your current vehicle to the construct"] = "将你当前载具添加到构造中", 544 | ["Saved Constructs"] = "保存构造", 545 | --["Attach another construct to the current construct"] = "将保存的构造添加到当前的构造中", 546 | } 547 | 548 | 549 | --- Spanish 550 | --- By Tryce 551 | 552 | translations.lang["es"] = { 553 | ["More stable, but updated less often."] = "Tendras mayor estabilidad pero con actualizaciones menos frecuentes.", 554 | ["Cutting edge updates, but less stable."] = "Tendras las ultimas actualizaciones pero con menor estabilidad", 555 | ["Loading..."] = "Esperar tu debes...", 556 | ["Installing auto-updater..."] = "Instalando actualizador automatico...", 557 | ["Error downloading auto-updater: "] = "La descarga del actualizador automatico ha fallado uu : ", 558 | ["Found empty file."] = "Se encontró un archivo vacio.", 559 | ["Could not open file for writing."] = "No se puedo abrir el archivo para escribir.", 560 | ["Successfully installed auto-updater lib"] = "Instalada correctamente la libreria del actualizador automatico", 561 | ["Error downloading auto-updater lib. Update failed to download."] = "La libreria del actualizador automatico ha fallado. No se ha podido descargar la actualización", 562 | ["Error downloading auto-updater lib. HTTP Request timeout"] = "La libreria del actualizador automatico ha fallado. Se ha superado el tiempo de espera de solicitud HTTP", 563 | ["Invalid auto-updater lib. Please delete your Stand/Lua Scripts/lib/auto-updater.lua and try again"] = "La librería del actualizador automatico no es valida. Por favor elimine Stand/Lua Scripts/lib/auto-updater.lua y vuelva a intentarlo", 564 | ["translations"] = "Traducciones", 565 | ["Missing translation: "] = "Traduccion perdida: ", 566 | ["Adding translations for language "] = "Agregar traducciones para el lenguaje ", 567 | ["Found label for "] = "Etiqueta encontrada para ", 568 | ["Registered "] = "Registrado ", 569 | ["Saving translation for "] = "Guardando traduccion para ", 570 | ["Could not natives lib. Make sure it is selected under Stand > Lua Scripts > Repository > natives-1663599433"] = "La libreria de nativos no fue encontrada, asegurate de haber seleccionado Stand > Scripts en lua > Repositorio > natives-1663599433", 571 | ["Max table depth reached"] = "Se ha alcanzado el maximo numero de combinaciones", 572 | ["Adding attachment to construct "] = "Se añadió el Accesorio al Constructo", 573 | ["Removing preview "] = "Quitar vista previa ", 574 | ["Adding a preview "] = "Agregar una vista previa", 575 | ["Building construct plan description "] = "Construyendo la descripcion del modelo del constructo ", 576 | ["Adding preview for construct plan "] = "Añadiendo vista previa al modelo del constructo ", 577 | ["Shoot (or press J) to add "] = "Dispara (o presiona J) para añadir ", 578 | ["Attaching "] = "Fusionando Accesorio ", 579 | ["Adding spawned construct to list "] = "Añadiendo a la lista el constructo Generado ", 580 | ["Creating construct from vehicle handle "] = "Crear el constructo a partir del vehiculo que estas manejando ", 581 | ["Vehicle is already a construct"] = "El vehiculo ya es un constructo", 582 | ["Saving construct "] = "Guardando constructo ", 583 | ["Cannot write to file "] = "No se puede escribir en el archivo ", 584 | ["Cannot save vehicle: Error serializing."] = "El vehiculo no puede ser guardado: error al serializar.", 585 | ["Deleting construct "] = "Borrando constructo", 586 | ["Spawning construct from plan "] = "Generando el Constructo desde el modelo", 587 | ["Failed to spawn construct from plan "] = "Error al generar el modelo del constructo ", 588 | ["Building construct from plan name="] = "Armando el constructo desde su nombre de modelo", 589 | ["Clean up on close"] = "Limpiar al cerrar", 590 | ["Rebuilding "] = "Rearmar ", 591 | ["Invalid construct file. "] = "Archivo de constructo invalido. ", 592 | ["Could not read file '"] = "No se pudo leer el archivo '", 593 | ["Failed to load XML file: "] = "Error al cargar el archivo XML: ", 594 | ["Failed to load INI file: "] = "Error al cargar el archivo INI: ", 595 | ["Failed to load JSON file: "] = "Error al cargar el archivo JSON: ", 596 | ["Loading construct plan file from "] = "Cargando el constructo desde el archivo del modelo ", 597 | ["Failed to load construct from file "] = "Error al cargar el constructo desde el archivo ", 598 | ["Loaded construct plan "] = "Constructo cargado ", 599 | ["Animating peds "] = "Animando ped ", 600 | ["Rebuilding ped "] = "Reconstruyendo ped ", 601 | ["Rebuilding reattach to menu "] = "Reconstruyendo Accesorio al menu ", 602 | ["Max depth reached while reattaching"] = "Se alcanzó la profundidad maxima mientras se reconstruia", 603 | ["Reattaching "] = "Fusionando el Accesorio otra vez ", 604 | ["Load More"] = "Cargar mas", 605 | ["Attachment missing handle"] = "Manejando Accesorio faltante ", 606 | ["Rebuilding attachment menu "] = "Reconstruyendo menu de Accesorios ", 607 | ["Name"] = "Nombre", 608 | ["Set name of the attachment"] = "Selecciona el nombre del Accesorio", 609 | ["Position"] = "Posición", 610 | ["Offset"] = "Desfase", 611 | ["X: Left / Right"] = "X: Izquierda/Derecha", 612 | ["Y: Forward / Back"] = "Y: Adelante/Atras", 613 | ["Z: Up / Down"] = "Z: Arriba/Abajo", 614 | ["Rotation"] = "Rotacion", 615 | ["X: Pitch"] = "eje: X", 616 | ["Y: Roll"] = "eje: Y", 617 | ["Z: Yaw"] = "eje: Z", 618 | ["World Position"] = "Posicion en el mundo", 619 | ["World Rotation"] = "Rotacion en el mundo", 620 | ["Hold SHIFT to fine tune"] = "Manten presionado SHIFT para ajustar con precisión", 621 | ["Options"] = "Opciones", 622 | ["Visible"] = "Visible", 623 | ["Will the attachment be visible, or invisible"] = "El Accesorio va a ser Visible o Invisible", 624 | ["Collision"] = "Colision", 625 | ["Will the attachment collide with things, or pass through them"] = "El Accesorio tendrá colisiones, o pasará a traves de ellos", 626 | ["Invincible"] = "Invencibilidad", 627 | ["Will the attachment be impervious to damage, or be damageable. AKA Godmode."] = "El Accesorio podrá recibir daño, o será invulnerable. Casi como modo dios.", 628 | ["Gravity"] = "Gravedad", 629 | ["Will the attachment be effected by gravity, or be weightless"] = "El Accesorio será afectado por la gravedad, o no tendrá peso", 630 | ["Frozen"] = "Congelar", 631 | ["Will the attachment be frozen in place, or allowed to move freely"] = "El Accesorio sera congelado en su posición, o tendrá libre movimiento", 632 | ["VEHICLE"] = "Vehiculo", 633 | ["Vehicle Options"] = "Opciones de vehiculo", 634 | ["Engine Always On"] = "Motor siempre encendido", 635 | ["Radio Loud"] = "Volumen alto de la radio", 636 | ["If enabled, vehicle radio will play loud enough to be heard outside the vehicle."] = "Si esta opcion está habilitada, la radio del vehiculo se reproducirá lo suficientemente fuerte como para ser escuchada fuera del vehiculo.", 637 | ["Sirens"] = "Sirenas", 638 | ["Off"] = "Apagado", 639 | ["Lights Only"] = "Solo luces", 640 | ["Sirens and Lights"] = "Sirenas y luces", 641 | ["Invis Wheels"] = "Ruedas invisibles", 642 | ["Door Lock Status"] = "Estado con puertas bloqueadas", 643 | ["Vehicle door locks"] = "Bloquear puertas de vehiculos", 644 | ["Dirt Level"] = "Nivel de suciedad", 645 | ["How dirty is the vehicle"] = "Que tan sucio está el vehiculo", 646 | ["Bullet Proof Tires"] = "Ruedas anti disparos", 647 | ["Burst Tires"] = "Explotar ruedas", 648 | ["Are tires burst"] = "¿Estan reventadas las ruedas?", 649 | ["Broken Doors"] = "Puertas rotas", 650 | ["Remove doors and trunks"] = "Quitar puertas y cofres(capo)", 651 | ["Break Door: Front Left"] = "Romper puerta: Delantera Izquierda", 652 | ["Break Door: Front Right"] = "Romper puerta: Delantera Derecha", 653 | ["Break Door: Back Right"] = "Romper puerta: Trasera Derecha", 654 | ["Break Door: Hood"] = "Romper puerta: Capo", 655 | ["Break Door: Trunk"] = "Romper puerta: Maletero 1", 656 | ["Break Door: Trunk2"] = "Romper puerta: Maletero 2", 657 | ["Remove door."] = "Remover puerta.", 658 | ["Ped Options"] = "Opciones de ped", 659 | ["Can Rag Doll"] = "Pueden caer con fisicas(ragdoll)", 660 | ["If enabled, the ped can go limp."] = "Si la opcion está activada, el ped puede cojear .", 661 | ["Armor"] = "Armadura", 662 | ["How much armor does the ped have."] = "Que tanta armadura pueden llevar los peds", 663 | ["On Fire"] = "El vengador fantasma", 664 | ["Will the ped be burning on fire, or not"] = "El ped se irá a quemar, o no", 665 | ["Clothes"] = "Prendas de ropa", 666 | ["Props"] = "Agregados", 667 | ["Attachment Options"] = "Opciones de Accesorio", 668 | ["Attached"] = "Accesorio añadido", 669 | ["Is this child physically attached to the parent, or does it move freely on its own."] = "Está pequeño pegado al Padre, o puede moverse con libertad.", 670 | ["Change Parent"] = "Cambiar pariente", 671 | ["Select a new parent for this child. Construct will be rebuilt to accommodate changes."] = "Elegir un nuevo pariente para el pequeño. El constructo se rearmará para actualizar los cambios.", 672 | ["Bone Index"] = "Posicion de union ", 673 | ["Which bone of the parent should this entity be attached to"] = "Cual union del padre se debe agregar esta entidad", 674 | ["Bone Index Picker"] = "Selector de posicion de union", 675 | ["Some common bones can be selected by name"] = "Algunas uniones comunes se pueden seleccionar por nombre", 676 | ["Soft Pinning"] = "Fijación suave", 677 | ["Will the attachment detach when repaired"] = "Se eliminará el Accesorio cuando se repare", 678 | ["Separate"] = "Separar", 679 | ["Detach attachment from construct to create a new construct"] = "Quitar accesorios del constructo para crear uno nuevo", 680 | ["Copy to Me"] = "Copiar para mi", 681 | ["Attach a copy of this object to your Ped."] = "fusionar una copia de este objeto a tu personaje.", 682 | ["Making ped into "] = "Hacer el ped en ", 683 | ["More Options"] = "Mas opciones", 684 | ["LoD Distance"] = "Distancia del detallado", 685 | ["Level of Detail draw distance"] = "Distancia de dibujo del nivel de detalle", 686 | ["Blip"] = "Punto en el mini mapa", 687 | ["Blip Sprite"] = "Diseño del punto", 688 | ["Icon to show on mini map for this construct"] = "Icono del constructo a mostrar en el minimapa", 689 | ["Blip Color"] = "Color del punto", 690 | ["Mini map icon color"] = "Color del icono del minimapa", 691 | ["Blip Reference"] = "Referencia del punto", 692 | ["Reference website for blip details"] = "Sitio web de referencia para detalles sobre el punto del mini mapa", 693 | ["Lights"] = "Luces", 694 | ["Light On"] = "Luces encendidas", 695 | ["If attachment is a light, it will be on and lit (many lights only work during night time)."] = "Si el accesorio es una luz, estara encendida e iluminada(algunas luces solo funcionan por la noche).", 696 | ["Light Disabled"] = "Luces desactivadas", 697 | ["If attachment is a light, it will be ALWAYS off, regardless of others settings."] = "Si el accesorio es una luz, estara apagada, independientemente de otros ajustes.", 698 | ["Proofs"] = "Protecciones", 699 | ["Bullet Proof"] = "Proteccion contra balas", 700 | ["If attachment is impervious to damage from bullets."] = "Si el accesorio es invulnerable a las balas", 701 | ["Fire Proof"] = "Proteccion contra el fuego", 702 | ["If attachment is impervious to damage from fire."] = "Si el accesorio es invulnerable al daño por fuego", 703 | ["Explosion Proof"] = "Proteccion contra explosiones", 704 | ["If attachment is impervious to damage from explosions."] = "Si el accesorio es invulnerable a las explosiones", 705 | ["Melee Proof"] = "Protecciones contra ataques cuerpo a cuerpo", 706 | ["If attachment is impervious to damage from melee attacks."] = "Si el accesorio es invulnerable al daño cuerpo a cuerpo", 707 | ["Attachments"] = "Accesorios", 708 | ["Add Attachment"] = "Añadir accesorio", 709 | ["Options for attaching other entities to this construct"] = "Opciones para fusionar otras entidades al constructo.", 710 | ["Curated"] = "Refinados", 711 | ["Browse a curated collection of attachments"] = "Buscar coleccion de accesorios refinados", 712 | ["Search"] = "Buscar", 713 | ["Search for a prop by name"] = "Buscar un accesorio por nombre", 714 | ["Add by Name"] = "Añadir el nombre", 715 | ["Add an object, vehicle, or ped by exact name."] = "Añadir un objeto, vehiculo o ped por su nombre exacto.", 716 | ["Object by Name"] = "Objeto por su nombre", 717 | ["Add an in-game object by exact name. To search for objects try https://gta-objects.xyz/"] = "Añadir, durante el juego, un objeto por su nombre exacto. Usa esta pagina para buscar objetos: https://gta-objects.xyz/ ", 718 | ["Ped by Name"] = "Ped por su nombre", 719 | ["Add a vehicle by exact name."] = "Añadir vehiculo por su nombre exacto", 720 | ["Open gta-objects.xyz"] = "Abrir gta-objects.xyz", 721 | ["Website for browsing and searching for props"] = "Pagina para explorar y buscar accesorios", 722 | ["Add Attachment Gun"] = "Pistola para añadir accesorios", 723 | ["Anything you shoot with this enabled will be added to the current construct"] = "Mientras tengas esta opcion activada, todo a lo que dispares sera agregado al constructo", 724 | ["Edit Attachments"] = "Editar accesorios", 725 | ["Clone"] = "Clonar", 726 | ["Clone (In Place)"] = "Clonar(en la misma posicion)", 727 | ["Clone Reflection: X:Left/Right"] = "Refleccion del clon sobre el eje:X:Izquierda/Derecha", 728 | ["Clone Reflection: Y:Front/Back"] = "Refleccion del clon sobre el eje:Y:Adelante/Atras", 729 | ["Clone Reflection: Z:Up/Down"] = "Refleccion del clon sobre el eje:Z:Arriba/Abajo", 730 | ["Actions"] = "Acciones", 731 | ["Teleport"] = "Teletransportar", 732 | ["Teleport Into Vehicle"] = "Teletransportar al vehiculo", 733 | ["Teleport Me to Construct"] = "Teletransportar a mi constructo", 734 | ["Teleport Construct to Me"] = "Teletransportar el constructo a mi", 735 | ["Debug Info"] = "Informacion de depuración", 736 | ["Rebuild"] = "Reconstruir", 737 | ["Delete construct (if it still exists), then recreate a new one from scratch."] = "Borrar el constructo(si este aun existe) y crear uno nuevo desde 0.", 738 | ["Save As"] = "Guardar como", 739 | ["Save construct to disk"] = "Guardar el constructo en el disco", 740 | ["Delete"] = "Borrar", 741 | ["Delete construct and all attachments. Cannot be reconstructed unless saved."] = "Borrar el constructo y todos sus accesorios. no podrá ser reconstruido a menos que se guarde.", 742 | ["Are you sure you want to delete this construct? "] = "¿Estas seguro que quieres borrar este constructo? ", 743 | [" children will also be deleted."] = " El pequeño tambien será borrado", 744 | --["Edit Attachments"] = "Editar accesorios", 745 | --["Rebuilding attachment menu "] = "Reconstruir menu de accesorios ", 746 | ["Focusing on attachment menu "] = "Enfocar en el menu de accesorios ", 747 | ["Create New Construct"] = "Crear nuevo constructo", 748 | ["Vehicle"] = "Vehiculo", 749 | ["From Current"] = "Del actual", 750 | ["Create a new construct based on current (or last in) vehicle"] = "Crear un nuevo constructo basado en el actual(o ultimo) vehiculo", 751 | ["Error: You must be (or recently been) in a vehicle to create a construct from it"] = "No se pudo hacer el constructo, Debes estar(o haber estado) en un vehiculo para crear un constructo a partir de aquel", 752 | ["From Vehicle Name"] = "Del nombre de vehiculo", 753 | ["Create a new construct from an exact vehicle name"] = "Crear un nuevo constructo a partir del nombre exacto del vehiculo", 754 | ["Structure (Map)"] = "Estructura(mapa)", 755 | ["From New Construction Cone"] = "A partir de un cono para el nuevo constructo", 756 | ["Create a new stationary construct"] = "Crear un nuevo constructo estacionario", 757 | ["From Object Name"] = "A partir del nombre del objeto", 758 | ["Create a new stationary construct from an exact object name"] = "Crear un nuevo constructo estacionario a partir del nombre exacto del objeto", 759 | ["Ped (Player Skin)"] = "Ped(modelo del jugador)", 760 | ["From Me"] = "A partir de mi", 761 | ["Create a new construct from your player Ped"] = "Crear un nuevo constructo a partir de mi personaje", 762 | ["Player is already a construct"] = "El jugador ya es parte de un constructo", 763 | ["From Ped Name"] = "A partir del nombre de un ped", 764 | ["Create a new Ped construct from exact name"] = "Crear un nuevo constructo con ped a partir de su nombre escrito(del ped)", 765 | ["Load Construct"] = "Cargar constructo", 766 | ["Load a previously saved or shared construct into the world"] = "Cargar un constructo al mundo previamente guardado o compartido", 767 | ["Loaded Constructs"] = "Constructos cargados", 768 | ["View and edit already loaded constructs"] = "Ver y editar los constructos cargados", 769 | ["Search all your construct files"] = "Buscar todos tus archivos de constructos", 770 | ["Edit your search query"] = "Editar tu consulta de busqueda", 771 | ["No results found"] = "No hay nada...", 772 | ["Missing downloaded file "] = "Falta el archivo descargado ", 773 | ["File downloaded "] = "Archivo descargado ", 774 | ["Unzipping"] = "Descomprimiendo", 775 | ["Unzipped"] = "Descomprimido", 776 | ["Open Constructs Folder"] = "Abrir carpeta de constructos", 777 | ["Open constructs folder. Share your creations or add new creations here."] = "Abrir carpeta de constructos. Compartir tus creaciones o añadir nuevas aquí.", 778 | ["Download Curated Constructs"] = "Descargar constructos artisticos", 779 | ["Download a curated collection of constructs."] = "Descargar coleccion de constructos artisticos", 780 | ["Drive Spawned Vehicles"] = "Manejar vehiculos spawneados", 781 | ["When spawning vehicles, automatically place you into the drivers seat."] = "Cuando spawneas un vehiculo, automaticamente apareces manejandolo.", 782 | ["Wear Spawned Peds"] = "Usar peds spawneados", 783 | ["When spawning peds, replace your player skin with the ped."] = "Cuando spawnees un ped, se reemplazará tu skin por la del ped.", 784 | ["Browse"] = "Navegar", 785 | ["Global Configs"] = "Configuración global", 786 | ["Edit Offset Step"] = "Editar pasos del desfase", 787 | ["The amount of change each time you edit an attachment offset (hold SHIFT for fine tuning)"] = "La cantidad de cambio cada vez que se edita el desplazamiento del accesorio (mantener presionado SHIFT para un desplazamiento preciso)", 788 | ["Edit Rotation Step"] = "Editar Rotación", 789 | ["The amount of change each time you edit an attachment rotation (hold SHIFT for fine tuning)"] = "La cantidad de cambio cada vez que editas la rotación del accesorio (mantener presionado SHIFT para un desplazamiento preciso)", 790 | ["Show Previews"] = "Mostrar imagen previa", 791 | ["Show previews when adding attachments"] = "Mostrar vista previa cuando se agregan accesorios", 792 | ["Preview Display Delay"] = "Duración de la Vista Previa", 793 | ["After browsing to a construct or attachment, wait this long before showing the preview."] = "Despues de buscar un constructo o accesorio, espera este tiempo para mostrar la vista previa", 794 | ["Delete All on Unload"] = "Borrar todo al dejar de cargar", 795 | ["Deconstruct all spawned constructs when unloading Constructor"] = "Deshacer todos los constructos spawneados cuando dejes de cargar el script Constructor", 796 | ["Clean Up"] = "Limpiar", 797 | ["Remove nearby vehicles, objects and peds. Useful to delete any leftover construction debris."] = "Eliminar todos los peds, objetos y vehiculos cercanos. Es util para eliminar cualquier residuo del constructo", 798 | ["Script Meta"] = "Meta del script", 799 | ["Constructor"] = "Constructor", 800 | ["Version"] = "Version", 801 | ["Release Branch"] = "Rama de entendimiento", 802 | ["Switch from main to dev to get cutting edge updates, but also potentially more bugs."] = "Cambiar de usuario a desarrollador para recibir actualizaciones en prueba, pero habrá mayor posibilidad de errores", 803 | ["Check for Update"] = "Verificar si hay una Actualización", 804 | ["The script will automatically check for updates at most daily, but you can manually check using this option anytime."] = "El script revisará automaticamente las actualizaciones diariamente, pero puedes verificar manualmente las actualizaciones en cualquier momento.", 805 | ["No updates found"] = "No se encontraron actualizaciones", 806 | ["Clean Reinstall"] = "Reinstalar de forma limpia", 807 | ["Force an update to the latest version, regardless of current version."] = "Actualizar de manera forzada a la ultima actualizacion, independientemente de la version actual", 808 | ["Debug Mode"] = "Modo en Depuración", 809 | ["Log additional details about Constructors actions."] = "Logear detalles adicionales de las acciones del script constructor ", 810 | ["Log Missing Translations"] = "Logear traducciones faltantes", 811 | ["Log any newly found missing translations"] = "Logear cualquier traduccion faltante que se encuentre", 812 | ["Github Source"] = "Fuente en Github", 813 | ["View source files on Github"] = "Ver los archivos fuente en Github", 814 | ["Discord"] = "Discord", 815 | ["Open Discord Server"] = "Abrir servidor de Discord", 816 | ["Credits"] = "Creditos y agradecimientos", 817 | ["Developers"] = "Desarroladores", 818 | ["Main developer"] = "Desarrolladores principales", 819 | ["Development, Testing, Suggestions and Support"] = "Desarrollo, testeo, sugerencias y soporte", 820 | ["Inspirations"] = "Inspiraciones", 821 | ["Much of Constructor is based on code originally copied from Jackz Vehicle Builder and this script wouldn't be possible without it. Constructor is just my own copy of Jackz's amazing work. Thank you Jackz!"] = "Gran parte de este script ha sido basado en el codigo original copiado del script Jack's Vehicle Builder y este script no hubiera sido posible sin el. Constructor es mi propia copia del increible trabajo del Jack's, Muchas gracias Jackz!", 822 | ["LanceSpooner is also a huge inspiration to this script. Thanks Lance!"] = "LanceSpooner fue igualmente una gran inspiración para este script. Muchas gracias Lance!", 823 | ["Install Curated Constructs"] = "Instalar constructos refinados", 824 | ["Download and install a curated collection of constructs from https://github.com/hexarobi/stand-curated-constructs"] = "Descargar e instalar una coleccion de constructos bien trabajados desde https://github.com/hexarobi/stand-curated-constructs", 825 | ["Development, Ped Curation"] = "Desarrollo, refinado de los ped", 826 | ["Focus Menu on Spawned Constructs"] = "Enfocar menu en los constructos spawneados", 827 | ["When spawning a construct, focus Stands menu on the newly spawned construct. Otherwise, stay in the Load Constructs menu."] = "Cuando spawnees un constructo, el menu stand se enfocará en el nuevo constructo spawneado, de otra forma seguira en el apartado de constructos cargados", 828 | ["From Nearby"] = "Desde los cercanos", 829 | ["Create a new construct based on nearby vehicle"] = "Crear un nuevo constructo basado en un vehiculo cercano", 830 | ["Entity Options"] = "Opciones para las entidades", 831 | ["Additional options available for all entities."] = "Opciones adicionales para todas las entidades", 832 | ["Alpha"] = "Transparencia", 833 | ["The amount of transparency the object has. Local only!"] = "La cantidad de transparencia que el objeto tendrá. Solo lo podrás ver tu", 834 | ["Mission Entity"] = "Entidad de mision", 835 | ["If attachment is treated as a mission entity."] = "Si el accesorio es tratado como una entidad de mision", 836 | ["Give Weapon"] = "Dar arma", 837 | ["Give the ped a weapon."] = "Darle un arma al ped", 838 | ["Vehicle by Name"] = "Vehiculo por nombre", 839 | ["From Current Vehicle"] = "Desde el Vehiculo Actual", 840 | ["From Vehicle List"] = "Desde la Lista de Vehiculos", 841 | ["Create a new construct from a list of vehicles"] = "Crear un nuevo Constructo desde la lista de Vehiculos", 842 | ["From Object Search"] = "Desde el buscador de objetos", 843 | ["Create a new map by searching for a object"] = "Crear un nuevo mapa desde el objeto buscado", 844 | ["From Current Ped"] = "Desde el ped actual", 845 | ["From Ped List"] = "Desde la lista de peds", 846 | ["Create a new construct from a list of peds"] = "Crear un nuevo constructo desde la lista de peds", 847 | ["Spawn Entity Delay"] = "Demora para spawnear una entidad", 848 | ["Pause after spawning any object. Useful for preventing issues when spawning large constructs with many objects."] = "Pausa luego de spawnear cualquier objeto. Util cuando se spawnean constructos con muchos objetos.", 849 | ["Freeze Position"] = "Congelar Posicion", 850 | ["Will the construct be frozen in place, or allowed to move freely"] = "El constructo estará inmovil en su posición o podrá moverse libremente.", 851 | ["Particle Effects"] = "Efectos de particulas", 852 | ["Browse a curated collection of particle effects"] = "Buscar coleccion de efectos de particulas interesantes", 853 | ["Clean Up Distance"] = "Distancia de limpieza", 854 | ["How far away the cleanup command will reach to delete entities."] = "Que tan lejos el comando limpieza alcanzará a borrar las entidades", 855 | ["Info"] = "Informacion", 856 | ["Set name of the player that created this construct"] = "Poner el nombre del jugador que ha creado este constructo", 857 | ["Description"] = "Descripcion", 858 | ["Set text to describe this construct"] = "Poner el texto para describir el constructo", 859 | ["Create a new construct from a base vehicle, object, or ped. Then extend it with attachments. Finally save your creation and share it with others."] = "Crear un nuevo constructo a partir de un vehiculo, objeto o ped. Luego diseñalo con accesorios. Finalmente guarda tu creación y compartela con otros", 860 | ["Set global configuration options."] = "Seleccionar las opciones de configuración global", 861 | ["Information and options about the Constructor script itself."] = "Informacion sobre las opciones de este script", 862 | ["Chat Spawnable Dir"] = "Direccion del chat generador", 863 | ["Set a Constructs sub-folder to be spawnable by name. Only available for users with permission to use Spawn Commands. See Online>Chat>Commands"] = "Seleccionar la sub-carpeta de constructos que son generados por nombre. Solo estará disponible para usuarios con el permiso para usar comandos en chat. Lo puedes ver en Online>Chat>Comandos", 864 | ["Constructs Allowed Per Player"] = "Constructos permitidos por jugador", 865 | ["Tryce"] = "Tryce", 866 | ["Spanish"] = "Español", 867 | ["The number of constructs any one player can spawn at a time. When a player tried to spawn additional constructs past this limit, the oldest spawned construct will be deleted."] = "El numero maximo de constructos que cada jugador puede generar. Si intenta generar mas del limite, se eliminará el constructo mas antiguo", 868 | ["Editing"] = "Editando", 869 | ["Set configuration options relating to editing constructs."] = "Establecer las opciones de configuracion sobre la edicion de los constructos ", 870 | ["Previews"] = "Vistas Previas", 871 | ["Set configuration options relating to previewing constructs."] = "Establecer las opciones de configuracion sobre las vistas previas de los constructos", 872 | ["Debug"] = "Depuracion", 873 | ["Chat Spawn Commands"] = "Comando de chat para generar(spawn)", 874 | ["Hold SHIFT to fine tune, or hold CONTROL to move ten steps at once."] = "Mantener SHIFT para ajustar con precision, o mantener CONTROL para mover 10 pasos a la vez .", 875 | ["Set configuration options relating to debugging the menu."] = "Establecer las opciones de configuracion sobre la depuracion del menu", 876 | ["Set configuration options relating to spawning constructs."] = "Establecer las opciones de configuracion sobre la generacion de constructos", 877 | ["Translators"] = "Traductores", 878 | ["Auto-Update"] = "Actualizador automatico", 879 | ["Rotation Order"] = "Orden de rotacion", 880 | ["Set the order that rotations should be applied."] = "Establecer el orden de rotacion que debería ser aplicado", 881 | ["Additional options available for Ped entities."] = "Opciones adicionales para las entidades de ped disponibles.", 882 | ["Make a new copy of this entity, at the same location as the original"] = "Hacer una nueva copia de esta entidad, en la misma ubicación que la original", 883 | ["Make a new copy of this entity, but at the mirror location about the X-axis"] = "Hacer una nueva copia de esta entidad, pero reflejada sobre el eje X.", 884 | ["Make a new copy of this entity, but at the mirror location about the Y-axis"] = "Hacer una nueva copia de esta entidad, pero reflejada sobre el eje Y.", 885 | ["Make a new copy of this entity, but at the mirror location about the Z-axis"] = "Hacer una nueva copia de esta entidad, pero reflejada sobre el eje Z.", 886 | ["Move your player to the construct, or vice versa."] = "Mover el personaje hacia el constructo o en viceversa(de forma contraria) .", 887 | ["Information about the construct"] = "Información acerca del constructo", 888 | ["Position and Rotation options"] = "Opciones de posicion y Rotacion", 889 | ["Configuration options for this entity"] = "Opciones de configuracion para esta entidad", 890 | ["Modify other entities attached to this entity"] = "Modificar otras entidades fusionadas a esta entidad", 891 | ["Make a copy of this attachment"] = "Hacer una copia de este accesorio", 892 | ["Move your player nearby the construct, but not inside of it."] = "Mover a tu personaje cerca del constructo pero no adentro de el", 893 | ["Move the construct to be nearby your player"] = "Mover el constructo cerca de tu personaje", 894 | ["Automatically install updates as they are released. Disable if you cannot successfully fetch updates as normal."] = "Instalar automaticamente las actualizaciones cuando son publicadas, Desactiva esta opcion si las actualizaciones no se instalan correctamente.", 895 | } 896 | 897 | --- French (Canada) 898 | --- By ukn 899 | 900 | translations.lang["fr"] = { 901 | ["More stable, but updated less often."] = "Plus stable, mais moins mis à jour.", 902 | ["Cutting edge updates, but less stable."] = "Actualisé, mais moins stable.", 903 | ["Loading..."] = "Chargement...", 904 | ["Installing auto-updater..."] = "Installation de la mise à jour automatique...", 905 | ["Error downloading auto-updater: "] = "Erreur de l'installation de la mis à jour automatique. ", 906 | ["Found empty file."] = "Fichier vide trouvé.", 907 | ["Could not open file for writing."] = "Incapable d'ouvrir le fichier pour l'écriture.", 908 | ["Successfully installed auto-updater lib"] = "Installation avec succès du logiciel de mis à jour automatique.", 909 | ["Error downloading auto-updater lib. Update failed to download."] = "Erreur de l'installation du logiciel de mis à jour automatique. La mise à jour n'a pas été installée.", 910 | ["Error downloading auto-updater lib. HTTP Request timeout"] = "Erreur de l'installation du logiciel de mis à jour automatique. La requête HTTP à mise trop de temps à répondre.", 911 | ["Invalid auto-updater lib. Please delete your Stand/Lua Scripts/lib/auto-updater.lua and try again"] = "Fichier clé de l'installateur automatique non-trouvable. Veuillez supprimer votre Stand/Lua Scripts/lib/auto-updater.lua et ré-essayer.", 912 | ["translations"] = "Traductions", 913 | ["Missing translation: "] = "Traductions manquantes: ", 914 | ["Adding translations for language "] = "Ajout des traductions pour les langues ", 915 | ["Found label for "] = "Nom trouvé pour ", 916 | ["Registered "] = "Enregistrement de ", 917 | ["Saving translation for "] = "Enregistrement des traductions pour ", 918 | ["Could not natives lib. Make sure it is selected under Stand > Lua Scripts > Repository > natives-1663599433"] = "Incapable de trouver la liste des natives. Assurez-vous que Stand > Lua Scripts > Repository > natives-1663599433 est sélectionné.", 919 | ["Max table depth reached"] = "Limite du tableau atteinte.", 920 | ["Adding attachment to construct "] = "Ajout du fichier joint à Construct ", 921 | ["Removing preview "] = "Aperçu retiré ", 922 | ["Adding a preview "] = "Aperçu ajouté ", 923 | ["Building construct plan description "] = "Création du plan de description de Construct", 924 | ["Adding preview for construct plan "] = "Ajout d'un aperçu au plan de Construct ", 925 | ["Shoot (or press J) to add "] = "Tirez (ou appuyez sur J) pour ajouter ", 926 | ["Attaching "] = "Joindre ", 927 | ["Adding spawned construct to list "] = "Ajout du Construct généré à la liste ", 928 | ["Creating construct from vehicle handle "] = "Création du Construct à partir d'un véhicule ", 929 | ["Vehicle is already a construct"] = "Le véhicule est déjà un Construct", 930 | ["Saving construct "] = "Sauvegarde du Construct ", 931 | ["Cannot write to file "] = "Incapable d'écrire dans le fichier ", 932 | ["Cannot save vehicle: Error serializing."] = "Incapable de sauvegarder: Problème de série.", 933 | ["Deleting construct "] = "Suppression du Construct ", 934 | ["Spawning construct from plan "] = "Génération du Construct à partir de plan ", 935 | ["Failed to spawn construct from plan "] = "Incapable de générer le Construct à partir du plan ", 936 | ["Building construct from plan name="] = "Création du construct à partir du nom de plan=", 937 | ["Clean up on close"] = "Nettoyer à l'arrêt", 938 | ["Rebuilding "] = "Reconstruction ", 939 | ["Invalid construct file. "] = "Fichier Construct invalide. ", 940 | ["Could not read file '"] = "Erreur de lecture du fichier '", 941 | ["Failed to load XML file: "] = "Incapable de charger le fichier XML: ", 942 | ["Failed to load INI file: "] = "Incapable de charger le fichier INI: ", 943 | ["Failed to load JSON file: "] = "Incapable de charger le fichier JSON: ", 944 | ["Loading construct plan file from "] = "Chargement du plan de Construct à partir de ", 945 | ["Failed to load construct from file "] = "Incapable de charger le Construct à partir du fichier ", 946 | ["Loaded construct plan "] = "Chargement du plan Construct ", 947 | ["Animating peds "] = "Animation des corps ", 948 | ["Rebuilding ped "] = "Recréation des corps ", 949 | ["Rebuilding reattach to menu "] = "Recréation de la jonction au menu ", 950 | ["Max depth reached while reattaching"] = "Atteinte de la limite pendant le processus de jonction", 951 | ["Reattaching "] = "Re-jonction ", 952 | ["Load More"] = "Voir plus", 953 | ["Attachment missing handle"] = "Manque du guide des joints", 954 | ["Rebuilding attachment menu "] = "Recréation du menu de joints ", 955 | ["Name"] = "Nom", 956 | ["Set name of the attachment"] = "Nom de la jonction", 957 | ["Position"] = "Position", 958 | ["Offset"] = "Décalage", 959 | ["X: Left / Right"] = "X: Droite/Gauche", 960 | ["Y: Forward / Back"] = "Y: Devant/Derrière", 961 | ["Z: Up / Down"] = "Z: Haut/Bas", 962 | ["Rotation"] = "Rotation", 963 | ["X: Pitch"] = "X: Tangage", 964 | ["Y: Roll"] = "Y: Roulement", 965 | ["Z: Yaw"] = "Z: Embardée", 966 | ["World Position"] = "Position du monde", 967 | ["World Rotation"] = "Rotation du monde", 968 | ["Hold SHIFT to fine tune"] = "Appuyez sur SHIFT pour améliorer la précision", 969 | ["Options"] = "Options", 970 | ["Visible"] = "Visible", 971 | ["Will the attachment be visible, or invisible"] = "Est-ce que la jonction sera visible, ou invisible", 972 | ["Collision"] = "Collision", 973 | ["Will the attachment collide with things, or pass through them"] = "Est-ce que la jonction aura des collisions, ou est-ce qu'elle sera traversable", 974 | ["Invincible"] = "Invincible", 975 | ["Will the attachment be impervious to damage, or be damageable. AKA Godmode."] = "Est-ce que la jonction sera immunisé au dégâts, ou non. EN GROS Invincible.", 976 | ["Gravity"] = "Gravité", 977 | ["Will the attachment be effected by gravity, or be weightless"] = "Est-ce que la jonction sera affectée par la gravité, ou sera-t-elle sans poids", 978 | ["Frozen"] = "Gel", 979 | ["Will the attachment be frozen in place, or allowed to move freely"] = "Est-ce que la jonction sera gelée sur place, ou capable de mouvement", 980 | ["VEHICLE"] = "VÉHICULES", 981 | ["Vehicle Options"] = "Options du véhicule", 982 | ["Engine Always On"] = "Moteur toujours enclenché", 983 | ["Radio Loud"] = "Radio forte", 984 | ["If enabled, vehicle radio will play loud enough to be heard outside the vehicle."] = "Activé, cette option permet que la radio soit jouée en dehors du véhicule.", 985 | ["Sirens"] = "Sirènes", 986 | ["Off"] = "Désactivé", 987 | ["Lights Only"] = "Lumières seulement", 988 | ["Sirens and Lights"] = "Lumières et sirènes", 989 | ["Invis Wheels"] = "Pneus invisibles", 990 | ["Door Lock Status"] = "Statut du verrouillage des portes", 991 | ["Vehicle door locks"] = "Verrouillage des portes", 992 | ["Dirt Level"] = "Niveau sur-terre", 993 | ["How dirty is the vehicle"] = "Niveau de saleté du véhicule", 994 | ["Bullet Proof Tires"] = "Pneus parre-balles", 995 | ["Burst Tires"] = "Éclater les pneus", 996 | ["Are tires burst"] = "Niveau d'éclat des pneus", 997 | ["Broken Doors"] = "Portes brisées", 998 | ["Remove doors and trunks"] = "Supprimer toutes les portes", 999 | ["Break Door: Front Left"] = "Détruire la porte: Haut-Droite", 1000 | ["Break Door: Front Right"] = "Détruire la porte: Haut-Gauche", 1001 | ["Break Door: Back Right"] = "Détruire la porte: Bas-Droite", 1002 | ["Break Door: Hood"] = "Détruire la porte: Capot", 1003 | ["Break Door: Trunk"] = "Détruire la porte: Coffre", 1004 | ["Break Door: Trunk2"] = "Détruire la porte: Coffre2", 1005 | ["Remove door."] = "Supprimer la porte.", 1006 | ["Ped Options"] = "Options de corps.", 1007 | ["Can Rag Doll"] = "Capable de mouvement ragdoll.", 1008 | ["If enabled, the ped can go limp."] = "Si activé, le corps deviendra mou", 1009 | ["Armor"] = "Armure", 1010 | ["How much armor does the ped have."] = "Combien d'armure est-ce que le corps possède", 1011 | ["On Fire"] = "En feu", 1012 | ["Will the ped be burning on fire, or not"] = "Est-ce que le corps est inflammable", 1013 | ["Clothes"] = "Vêtements", 1014 | ["Props"] = "Objets", 1015 | ["Attachment Options"] = "Options de jonction", 1016 | ["Attached"] = "Joint", 1017 | ["Is this child physically attached to the parent, or does it move freely on its own."] = "Est-ce que l'objet doit être attaché à son ascendant, ou est-ce qu'il est capable de bouger seul", 1018 | ["Change Parent"] = "Changer l'ordre d'ascendance", 1019 | ["Select a new parent for this child. Construct will be rebuilt to accommodate changes."] = "Sélectionner un nouvel ascendant pour cet objet. Construct se ré-écrira afin d'adopter les changements.", 1020 | ["Bone Index"] = "Index des os", 1021 | ["Which bone of the parent should this entity be attached to"] = "À quel os de l'ascendant est-ce que l'entité doit se joindre", 1022 | ["Bone Index Picker"] = "Index de séléction des os", 1023 | ["Some common bones can be selected by name"] = "Certains os peuvent être sélectionné par nom", 1024 | ["Soft Pinning"] = "Épinglement facile", 1025 | ["Will the attachment detach when repaired"] = "Est-ce que la jonction se détachera quand elle sera réparée", 1026 | ["Separate"] = "Séparer", 1027 | ["Detach attachment from construct to create a new construct"] = "Détacher la jonction du Construct pour créer un nouveau Construct", 1028 | ["Copy to Me"] = "Copier à Moi", 1029 | ["Attach a copy of this object to your Ped."] = "Joindre une copie de l'objet au corps.", 1030 | ["Making ped into "] = "Transformer le corps en ", 1031 | ["More Options"] = "Plus d'options", 1032 | ["LoD Distance"] = "Distance d'affichage", 1033 | ["Level of Detail draw distance"] = "Distance du niveau de détails", 1034 | ["Blip"] = "Point", 1035 | ["Blip Sprite"] = "Icône du point", 1036 | ["Icon to show on mini map for this construct"] = "Icône de représentation du Construct sous forme de point sur la carte", 1037 | ["Blip Color"] = "Couleur du point", 1038 | ["Mini map icon color"] = "Couleur de point en mini-carte", 1039 | ["Blip Reference"] = "Référence du point", 1040 | ["Reference website for blip details"] = "Site web de référence pour les détails du point", 1041 | ["Lights"] = "Lumières", 1042 | ["Light On"] = "Lumière activée", 1043 | ["If attachment is a light, it will be on and lit (many lights only work during night time)."] = "Si la jonction est une lumière, elle sera toujours allumée.", 1044 | ["Light Disabled"] = "Lumière désactivée", 1045 | ["If attachment is a light, it will be ALWAYS off, regardless of others settings."] = "Si la jonction est une lumière, elle sera toujours éteinte.", 1046 | ["Proofs"] = "Protections", 1047 | ["Bullet Proof"] = "Protection des balles", 1048 | ["If attachment is impervious to damage from bullets."] = "Si la jonction est parre-balles.", 1049 | ["Fire Proof"] = "Protection du feu", 1050 | ["If attachment is impervious to damage from fire."] = "Si la jonction est incapable de brûler.", 1051 | ["Explosion Proof"] = "Protection des explosions", 1052 | ["If attachment is impervious to damage from explosions."] = "Si la jonction est résistante aux explosions", 1053 | ["Melee Proof"] = "Protections de courte portée", 1054 | ["If attachment is impervious to damage from melee attacks."] = "Si la jonction résiste aux attaques de courte portée", 1055 | ["Attachments"] = "Jonctions", 1056 | ["Add Attachment"] = "Ajouter un joint", 1057 | ["Options for attaching other entities to this construct"] = "Options de jonction d'une entité au Construct.", 1058 | ["Curated"] = "Pré-fabriqué", 1059 | ["Browse a curated collection of attachments"] = "Visualiser une collection pré-fabriquée de jonctions", 1060 | ["Search"] = "Rechercher", 1061 | ["Search for a prop by name"] = "Chercher un objet par son nom", 1062 | ["Add by Name"] = "Ajouter par nom", 1063 | ["Add an object, vehicle, or ped by exact name."] = "Ajouter un objet, un véhicule ou un corps par son nom exact", 1064 | ["Object by Name"] = "Nom de l'objet", 1065 | ["Add an in-game object by exact name. To search for objects try https://gta-objects.xyz/"] = "Ajouter un objet en jeu par son nom. Afin de trouver des noms, essayez https://gta-objects.xyz/", 1066 | ["Ped by Name"] = "Nom du corps", 1067 | ["Add a vehicle by exact name."] = "Ajouter un véhicule par son nom exact", 1068 | ["Open gta-objects.xyz"] = "Ouvrir gta-objects.xyz", 1069 | ["Website for browsing and searching for props"] = "Site web pour naviguer et rechercher des objets", 1070 | ["Add Attachment Gun"] = "Ajouter une Arme de Jonction", 1071 | ["Anything you shoot with this enabled will be added to the current construct"] = "Tout sur cedont vous tirez sera ajouté au Construct actuel.", 1072 | ["Edit Attachments"] = "Modifier les jonctions", 1073 | ["Clone"] = "Clôner", 1074 | ["Clone (In Place)"] = "Clôner (sur place)", 1075 | ["Clone Reflection: X:Left/Right"] = "Reflet du Clône:X:Droite/Gauche", 1076 | ["Clone Reflection: Y:Front/Back"] = "Reflet du Clône::Y:Avant/Arrière", 1077 | ["Clone Reflection: Z:Up/Down"] = "Reflet du Clône::Z:Haut/Bas", 1078 | ["Actions"] = "Actions", 1079 | ["Teleport"] = "Téléportation", 1080 | ["Teleport Into Vehicle"] = "Téléportation dans le véhicule", 1081 | ["Teleport Me to Construct"] = "Téléportation directe au Construct", 1082 | ["Teleport Construct to Me"] = "Téléportation du Construct vers moi", 1083 | ["Debug Info"] = "Informations de débogage", 1084 | ["Rebuild"] = "Reconstruction", 1085 | ["Delete construct (if it still exists), then recreate a new one from scratch."] = "Supprimer le Construct (s'il existe toujours), puis recréer un nouveau.", 1086 | ["Save As"] = "Enregistrer sous", 1087 | ["Save construct to disk"] = "Sauvegarde du Construct sur le disque", 1088 | ["Delete"] = "Supprimer", 1089 | ["Delete construct and all attachments. Cannot be reconstructed unless saved."] = "Supprime le Construct et toutes les jonctions. Ne peut pas être reconstruits s'il n'est pas sauvegardé.", 1090 | ["Are you sure you want to delete this construct? "] = "Êtes-vous sûr de vouloir supprimer le Construct? ", 1091 | [" children will also be deleted."] = " descendants seront aussi supprimés.", 1092 | --["Edit Attachments"] = "Modifier les jonctions", 1093 | --["Rebuilding attachment menu "] = "Reconstruction du menu de jonctions ", 1094 | ["Focusing on attachment menu "] = "Mise en évidence du menu de jonctions ", 1095 | ["Create New Construct"] = "Créer un nouveau Construct", 1096 | ["Vehicle"] = "Véhicule", 1097 | ["From Current"] = "D'après le vôtre", 1098 | ["Create a new construct based on current (or last in) vehicle"] = "Créer un nouveau Construct d'après votre véhicule actuel", 1099 | ["Error: You must be (or recently been) in a vehicle to create a construct from it"] = "Erreur: Vous devez être (ou avoir été) dans un véhicule afin de créer un Construct d'après ce-dernier.", 1100 | ["From Vehicle Name"] = "D'après le nom du véhicule", 1101 | ["Create a new construct from an exact vehicle name"] = "Créer un nouveau Construct d'après le nom exact d'un véhicule", 1102 | ["Structure (Map)"] = "Structure de la carte", 1103 | ["From New Construction Cone"] = "D'après le Nouveau Cône de Construction", 1104 | ["Create a new stationary construct"] = "D'après un Construct stationnaire", 1105 | ["From Object Name"] = "D'après le nom d'un objet", 1106 | ["Create a new stationary construct from an exact object name"] = "Créer un nouveau Construct stationnaire d'après le nom exact d'un objet", 1107 | ["Ped (Player Skin)"] = "Corps (Apparence du joueur)", 1108 | ["From Me"] = "D'après moi", 1109 | ["Create a new construct from your player Ped"] = "Créer un nouveau Construct d'après le corps de vôtre joueur.", 1110 | ["Player is already a construct"] = "Le joueur est déjà un Construct", 1111 | ["From Ped Name"] = "D'après le nom d'un corps", 1112 | ["Create a new Ped construct from exact name"] = "Créer un nouveau corps d'après un nom exact", 1113 | ["Load Construct"] = "Charger Construct", 1114 | ["Load a previously saved or shared construct into the world"] = "Charger un Construct anciennement sauvegardé ou partagé dans le monde", 1115 | ["Loaded Constructs"] = "Chargement des Constructs", 1116 | ["View and edit already loaded constructs"] = "Voir et moidifier des Constructs actuellements chargés", 1117 | ["Search all your construct files"] = "Rechercher ", 1118 | ["Edit your search query"] = "Modifier vos paramètres de recherche", 1119 | ["No results found"] = "Aucun résultat trouvé", 1120 | ["Missing downloaded file "] = "Fichier téléchargé manquant ", 1121 | ["File downloaded "] = "Téléchargement du fichier ", 1122 | ["Unzipping"] = "Décompression", 1123 | ["Unzipped"] = "Décompressé", 1124 | ["Open Constructs Folder"] = "Ouvrir Construct", 1125 | ["Open constructs folder. Share your creations or add new creations here."] = "Ouvrir le fichier de Construct. Partagez vos créations et ajoutez-en des nouvelles.", 1126 | ["Download Curated Constructs"] = "Télécharger les Constructs pré-générés", 1127 | ["Download a curated collection of constructs."] = "Télécharger une collection pré-générée de Constructs.", 1128 | ["Drive Spawned Vehicles"] = "Conduire les véhicules générés", 1129 | ["When spawning vehicles, automatically place you into the drivers seat."] = "Quand le véhicule est généré, automatiquement placer le joueur dans la place de conducteur.", 1130 | ["Wear Spawned Peds"] = "Quand le corps est généré", 1131 | ["When spawning peds, replace your player skin with the ped."] = "Quand un corps est généré, remplacer votre apparence par celui du corps", 1132 | ["Browse"] = "Rechercher", 1133 | ["Global Configs"] = "Configurations globales", 1134 | ["Edit Offset Step"] = "Modifier les paramètres de décalage", 1135 | ["The amount of change each time you edit an attachment offset (hold SHIFT for fine tuning)"] = "Le nombre de changements d'après le décalage de la rotation (enfoncer SHIFT pour davantage de précision)", 1136 | ["Edit Rotation Step"] = "Modifier la précision de la Rotation", 1137 | ["The amount of change each time you edit an attachment rotation (hold SHIFT for fine tuning)"] = "Le nombre de changements d'après la rotation (enfoncer SHIFT pour davantage de précision)", 1138 | ["Show Previews"] = "Montrer les aperçus", 1139 | ["Show previews when adding attachments"] = "Montrer les aperçus pendant l'ajout des jonctions", 1140 | ["Preview Display Delay"] = "Délai d'affichage des aperçus", 1141 | ["After browsing to a construct or attachment, wait this long before showing the preview."] = "Après la recherche d'un Construct ou d'une jonction, attendre avant de montrer l'aperçu.", 1142 | ["Delete All on Unload"] = "Tout supprimer en déchargement", 1143 | ["Deconstruct all spawned constructs when unloading Constructor"] = "Déconstruire tout les objets générés quand Constructor est déchargé", 1144 | ["Clean Up"] = "Nettoyer", 1145 | ["Remove nearby vehicles, objects and peds. Useful to delete any leftover construction debris."] = "Retirer les véhicules proches", 1146 | ["Script Meta"] = "Méta du Script", 1147 | ["Constructor"] = "Constructor", 1148 | ["Version"] = "Version", 1149 | ["Release Branch"] = "Branche de publication", 1150 | ["Switch from main to dev to get cutting edge updates, but also potentially more bugs."] = "Changer de branche afin de bénéficier de plus de mises à jour, mais être aussi susceptible à plus de bogues.", 1151 | ["Check for Update"] = "Chercher une mise à jour", 1152 | ["The script will automatically check for updates at most daily, but you can manually check using this option anytime."] = "Le script va rechercher une mise à jour automatiquement & régulièrement, mais vous pouvez aussi le faire en utilisant cette option.", 1153 | ["No updates found"] = "Aucune mise à jour trouvée", 1154 | ["Clean Reinstall"] = "Réinstallation complète", 1155 | ["Force an update to the latest version, regardless of current version."] = "Force une réinitialisation à la dernière version, peu importe votre version actuelle.", 1156 | ["Debug Mode"] = "Mode de débogage", 1157 | ["Log additional details about Constructors actions."] = "Enregistrer davantage de détails sur les actions de Constuct", 1158 | ["Log Missing Translations"] = "Enregistrer les traductions manquantes", 1159 | ["Log any newly found missing translations"] = "Enregistrer chaque traductions manquantes", 1160 | ["Github Source"] = "Github", 1161 | ["View source files on Github"] = "Voir le code source sur Github", 1162 | ["Discord"] = "Discord", 1163 | ["Open Discord Server"] = "Accéder au Discord", 1164 | ["Credits"] = "Crédits", 1165 | ["Developers"] = "Développeurs", 1166 | ["Main developer"] = "Développeur en chef", 1167 | ["Development, Testing, Suggestions and Support"] = "Développement, test, suggestions et aide", 1168 | ["Inspirations"] = "Inspirations", 1169 | ["Much of Constructor is based on code originally copied from Jackz Vehicle Builder and this script wouldn't be possible without it. Constructor is just my own copy of Jackz's amazing work. Thank you Jackz!"] = "Beaucoup de ce code avait été copié du Jackz Vehicle Builder et ce script n'aurait pas vu le jour sans lui. Constructor est seulement ma copie du grand travail de Jack. Merci beaucoup Jackz!", 1170 | ["LanceSpooner is also a huge inspiration to this script. Thanks Lance!"] = "LanceSpooner est une grande inspiration pour ce script. Merci, Lance!", 1171 | ["Install Curated Constructs"] = "Installer les listes de Constructs", 1172 | ["Download and install a curated collection of constructs from https://github.com/hexarobi/stand-curated-constructs"] = "Télécharger et installer une collection pré-fabriquée de constructs depuis https://github.com/hexarobi/stand-curated-constructs", 1173 | ["Development, Ped Curation"] = "Développement, algorithme des corps", 1174 | ["Focus Menu on Spawned Constructs"] = "Focaliser le menu sur les Constructs générés", 1175 | ["When spawning a construct, focus Stands menu on the newly spawned construct. Otherwise, stay in the Load Constructs menu."] = "Quand un Construct est généré, focaliser le menu Stand sur le nouveau Construct. Sinon, rester sur le menu de Chargement du Construct.", 1176 | ["From Nearby"] = "D'un rapproché", 1177 | ["Create a new construct based on nearby vehicle"] = "Créer un nouveau Construct à partir d'un véhicule proche", 1178 | ["Entity Options"] = "Options d'entité", 1179 | ["Additional options available for all entities."] = "Davantage d'options pour toutes les entités.", 1180 | ["Alpha"] = "Transparence", 1181 | ["The amount of transparency the object has. Local only!"] = "La transparence de cette objet. Seulement pour vous!", 1182 | ["Mission Entity"] = "Entité de mission", 1183 | ["If attachment is treated as a mission entity."] = "Si la jonction est traitée comme une entité de mission.", 1184 | ["Give Weapon"] = "Donner une arme", 1185 | ["Give the ped a weapon."] = "Donner une arme au corps", 1186 | ["Vehicle by Name"] = "Nom du véhicule", 1187 | ["From Current Vehicle"] = "D'après ce véhicule", 1188 | ["From Vehicle List"] = "D'après la liste de véhicule", 1189 | ["Create a new construct from a list of vehicles"] = "Créer un nouveau construct à partir d'une liste de véhicule", 1190 | ["From Object Search"] = "De la recherche d'objet", 1191 | ["Create a new map by searching for a object"] = "Créer une nouvelle carte en cherchant un objet", 1192 | ["From Current Ped"] = "D'après ce corps", 1193 | ["From Ped List"] = "D'après une liste de corps", 1194 | ["Create a new construct from a list of peds"] = "Créer un nouveau Construct d'après une liste de corps", 1195 | ["Spawn Entity Delay"] = "Délai de génération des entités", 1196 | ["Pause after spawning any object. Useful for preventing issues when spawning large constructs with many objects."] = "每个实体生成之间的延迟.", 1197 | ["Freeze Position"] = "Gel de position", 1198 | ["Will the construct be frozen in place, or allowed to move freely"] = "Est-ce que les Constructs sont gelés, ou seront-ils capables de bouger.", 1199 | ["Particle Effects"] = "Effets de particules", 1200 | ["Browse a curated collection of particle effects"] = "Rechercher une collection pré-fabriquée d'effets de particules", 1201 | ["Clean Up Distance"] = "Distance de nettoyage", 1202 | ["How far away the cleanup command will reach to delete entities."] = "L'étendu maximale de la commande de nettoyage afin de supprimer les entités.", 1203 | ["Info"] = "Informations", 1204 | ["Set name of the player that created this construct"] = "Mettre le nom du joueur qui a fabriqué ce Construct", 1205 | ["Description"] = "Description", 1206 | ["Set text to describe this construct"] = "Mettre une description du Construct", 1207 | ["Create a new construct from a base vehicle, object, or ped. Then extend it with attachments. Finally save your creation and share it with others."] = "Créer un novueau Construct pour ce véhicule, cet objet ou ce corps. Étendez-le avec des jonctions. Ensuite, partagez-le avec les autres.", 1208 | ["Set global configuration options."] = "Ajuster les paramètres globaux.", 1209 | ["Information and options about the Constructor script itself."] = "Informations & options du script Constructor.", 1210 | ["Chat Spawnable Dir"] = "Message du répertoire de génération", 1211 | ["Set a Constructs sub-folder to be spawnable by name. Only available for users with permission to use Spawn Commands. See Online>Chat>Commands"] = "Faire que les Constructs du fichier puissent être générés par nom. Seulement les joueurs aux permissions requises pourront utiliser les commandes. Regardez Online>Chat> Commands", 1212 | ["Constructs Allowed Per Player"] = "Constructs autorisés par joueurs", 1213 | ["CqCq and Zelda Two"] = "CqCq et Zelda Two", 1214 | ["Chinese"] = "Chinois", 1215 | ["The number of constructs any one player can spawn at a time. When a player tried to spawn additional constructs past this limit, the oldest spawned construct will be deleted."] = "Le nombre de Construct qu'un joueur peut créer. Quand un jouer tentera de générer plus de Constructs, le plus ancien sera retiré.", 1216 | ["Editing"] = "Édition", 1217 | ["Set configuration options relating to editing constructs."] = "Mettre en place une configuration pour l'édition des Constructs", 1218 | ["Previews"] = "Aperçu", 1219 | ["Set configuration options relating to previewing constructs."] = "Mettre en place une configuration pour l'aperçu des Constructs", 1220 | ["Debug"] = "Débogage", 1221 | ["Chat Spawn Commands"] = "Génération des commandes textes", 1222 | ["Hold SHIFT to fine tune, or hold CONTROL to move ten steps at once."] = "Enfoncer SHIFT pour améliorer la précision, ou CONTROL afin de se déplacer de 10 cases à la fois.", 1223 | ["Set configuration options relating to debugging the menu."] = "Configurer les options du débogage du menu.", 1224 | ["Set configuration options relating to spawning constructs."] = "Configurer les options pour la génération des Constructs.", 1225 | ["Translators"] = "Traducteurs", 1226 | ["Auto-Update"] = "Mise à jour automatique", 1227 | ["Rotation Order"] = "Ordre de rotation", 1228 | ["Set the order that rotations should be applied."] = "Faire que les rotations suivent un ordre précis.", 1229 | ["Additional options available for Ped entities."] = "Davantages d'options disponibles pour les entités.", 1230 | ["Make a new copy of this entity, at the same location as the original"] = "Créer une nouvelle copie de l'entité, à la même position que l'originale.", 1231 | ["Make a new copy of this entity, but at the mirror location about the X-axis"] = "Créer une nouvelle copie de l'entité, à la position de l'entité (miroir de l'axe X)", 1232 | ["Make a new copy of this entity, but at the mirror location about the Y-axis"] = "Créer une nouvelle copie de l'entité, à la position de l'entité (miroir de l'axe Y)", 1233 | ["Make a new copy of this entity, but at the mirror location about the Z-axis"] = "Créer une nouvelle copie de l'entité, à la position de l'entité (miroir de l'axe Z)", 1234 | ["Move your player to the construct, or vice versa."] = "Déplacer votre joueur au Construct, ou vice-versa.", 1235 | ["Information about the construct"] = "Informations du Construct", 1236 | ["Position and Rotation options"] = "Options de position et de rotation", 1237 | ["Configuration options for this entity"] = "Options configurables de l'entité", 1238 | ["Modify other entities attached to this entity"] = "Modifier", 1239 | ["Make a copy of this attachment"] = "Faire une copie de la jonction", 1240 | ["Move your player nearby the construct, but not inside of it."] = "Déplacer le joueur proche du Construct, mais pas à l'intérieur", 1241 | ["Move the construct to be nearby your player"] = "Déplacer le Construct proche du joueur", 1242 | ["Automatically install updates as they are released. Disable if you cannot successfully fetch updates as normal."] = "Télécharger automatiquement les mises à jour quand elles sont disponibles. Désactivez si vous ne pouvez pas les obtenir normalement.", 1243 | } 1244 | 1245 | --- 1246 | --- Return 1247 | --- 1248 | 1249 | translations.inject_custom_translations() 1250 | return translations 1251 | 1252 | 1253 | --------------------------------------------------------------------------------