├── README.md ├── WiriScript.lua ├── lib ├── natives-1663599433.lua ├── pretty │ ├── json.lua │ └── json │ │ ├── constant.lua │ │ ├── parser.lua │ │ └── serializer.lua └── wiriscript │ ├── functions.lua │ ├── guided_missile.lua │ ├── homing_missiles.lua │ ├── orbital_cannon.lua │ ├── ped_list.lua │ └── ufo.lua └── resources └── WiriTextures.ytd /README.md: -------------------------------------------------------------------------------- 1 | # WiriScript 2 | 3 | This script includes all the features you need. Have fun with lots of trolling options, surprise your friends with weapon effects, use vehicle weapons, use an UFO tractor beam, create bodyguards and much, much more. WiriScript, made for fun. 4 | 5 | **Download and enjoy**. 6 | 7 | ## Installation 8 | 9 | ### From Repository (recommended) 10 | - In your menu go to `Stand > Lua Scripts > Repository > WiriScript`, this will install the script and keep it up to date 11 | - Run WiriScript as usual 12 | 13 | ### Manual 14 | 15 | - [Download] the required files (WiriScript.lua, resources and lib) 16 | - Drag those files into `Stand Folder > Lua Scripts` 17 | - In Stand, go to `Stand > Lua Scripts` and run `WiriScript` 18 | 19 | [Download]: https://github.com/nowiry/WiriScript/archive/refs/heads/main.zip 20 | [fanclub]: https://cutt.ly/wiriscript-fanclub 21 | -------------------------------------------------------------------------------- /lib/pretty/json.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2018, Souche Inc. 2 | 3 | local Constant = require "pretty.json.constant" 4 | local Serializer = require "pretty.json.serializer" 5 | local Parser = require "pretty.json.parser" 6 | 7 | local json = { 8 | _VERSION = "0.1", 9 | null = Constant.NULL 10 | } 11 | 12 | function json.stringify(obj, replacer, space, print_address) 13 | if type(space) ~= "number" then space = 0 end 14 | 15 | return Serializer({ 16 | print_address = print_address, 17 | stream = { 18 | fragments = {}, 19 | write = function(self, ...) 20 | for i = 1, #{...} do 21 | self.fragments[#self.fragments + 1] = ({...})[i] 22 | end 23 | end, 24 | toString = function(self) 25 | return table.concat(self.fragments) 26 | end 27 | } 28 | }):json(obj, replacer, space, space):toString() 29 | end 30 | 31 | function json.parse(str, without_null) 32 | return Parser({ without_null = without_null }):json(str, 1) 33 | end 34 | 35 | return json 36 | -------------------------------------------------------------------------------- /lib/pretty/json/constant.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2019, Souche Inc. 2 | 3 | local Constant = { 4 | ESC_MAP = { 5 | ["\\"] = [[\]], 6 | ["\""] = [[\"]], 7 | ["/"] = [[\/]], 8 | ["\b"] = [[\b]], 9 | ["\f"] = [[\f]], 10 | ["\n"] = [[\n]], 11 | ["\r"] = [[\r]], 12 | ["\t"] = [[\t]], 13 | ["\a"] = [[\u0007]], 14 | ["\v"] = [[\u000b]] 15 | }, 16 | 17 | UN_ESC_MAP = { 18 | b = "\b", 19 | f = "\f", 20 | n = "\n", 21 | r = "\r", 22 | t = "\t", 23 | u0007 = "\a", 24 | u000b = "\v" 25 | }, 26 | 27 | NULL = setmetatable({}, { 28 | __tostring = function() return "null" end 29 | }) 30 | } 31 | 32 | return Constant 33 | -------------------------------------------------------------------------------- /lib/pretty/json/parser.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2019, Souche Inc. 2 | 3 | local Constant = require "pretty.json.constant" 4 | 5 | local NULL = Constant.NULL 6 | local UN_ESC_MAP = Constant.UN_ESC_MAP 7 | 8 | local function next_char(str, pos) 9 | pos = pos + #str:match("^%s*", pos) 10 | return str:sub(pos, pos), pos 11 | end 12 | 13 | local function syntax_error(str, pos) 14 | return error("Invalid json syntax starting at position " .. pos .. ": " .. str:sub(pos, pos + 10)) 15 | end 16 | 17 | local Parser = {} 18 | 19 | setmetatable(Parser, { 20 | __call = function(self, opts) 21 | local parser = { 22 | without_null = opts.without_null 23 | } 24 | 25 | setmetatable(parser, { __index = Parser }) 26 | 27 | return parser 28 | end 29 | }) 30 | 31 | function Parser:number(str, pos) 32 | local num = str:match("^-?%d+%.?%d*[eE]?[+-]?%d*", pos) 33 | local val = tonumber(num) 34 | 35 | if not val then 36 | syntax_error(str, pos) 37 | else 38 | return val, pos + #num 39 | end 40 | end 41 | 42 | function Parser:string(str, pos) 43 | pos = pos + 1 44 | 45 | local i = 1 46 | local chars = {} 47 | while(pos <= #str) do 48 | local c = str:sub(pos, pos) 49 | 50 | if c == "\"" then 51 | return table.concat(chars, ""), pos + 1 52 | elseif c == "\\" then 53 | local j = pos + 1 54 | 55 | local next_c = str:sub(j, j) 56 | for k, v in pairs(UN_ESC_MAP) do 57 | if str:sub(j, j + #k - 1) == k then 58 | next_c = v 59 | j = j + #k - 1 60 | end 61 | end 62 | 63 | c = next_c 64 | pos = j 65 | end 66 | 67 | chars[i] = c 68 | i = i + 1 69 | pos = pos + 1 70 | end 71 | 72 | syntax_error(str, pos) 73 | end 74 | 75 | function Parser:array(str, pos) 76 | local arr = {} 77 | local val 78 | local i = 1 79 | local c 80 | 81 | pos = pos + 1 82 | while true do 83 | val, pos = self:json(str, pos) 84 | arr[i] = val 85 | i = i + 1 86 | 87 | c, pos = next_char(str, pos) 88 | if (c == ",") then 89 | pos = pos + 1 90 | elseif (c == "]") then 91 | return arr, pos + 1 92 | else 93 | syntax_error(str, pos) 94 | end 95 | end 96 | 97 | return arr 98 | end 99 | 100 | function Parser:table(str, pos) 101 | local obj = {} 102 | local key 103 | local val 104 | local c 105 | 106 | pos = pos + 1 107 | while true do 108 | c, pos = next_char(str, pos) 109 | 110 | if c == "}" then return obj, pos + 1 111 | elseif c == "\"" then key, pos = self:string(str, pos) 112 | else syntax_error(str, pos) end 113 | 114 | c, pos = next_char(str, pos) 115 | if c ~= ":" then syntax_error(str, pos) end 116 | 117 | val, pos = self:json(str, pos + 1) 118 | obj[key] = val 119 | 120 | c, pos = next_char(str, pos) 121 | if c == "}" then 122 | return obj, pos + 1 123 | elseif c == "," then 124 | pos = pos + 1 125 | else 126 | syntax_error(str, pos) 127 | end 128 | end 129 | end 130 | 131 | function Parser:json(str, pos) 132 | local first = false 133 | local val 134 | local c 135 | 136 | if not pos or pos == 1 then first = true end 137 | pos = pos or 1 138 | 139 | if type(str) ~= "string" then error("str should be a string") 140 | elseif pos > #str then error("Reached unexpected end of input") end 141 | 142 | c, pos = next_char(str, pos) 143 | if c == "{" then 144 | val, pos = self:table(str, pos) 145 | elseif c == "[" then 146 | val, pos = self:array(str, pos) 147 | elseif c == "\"" then 148 | val, pos = self:string(str, pos) 149 | elseif c == "-" or c:match("%d") then 150 | val, pos = self:number(str, pos) 151 | else 152 | for k, v in pairs({ ["true"] = true, ["false"] = false, ["null"] = NULL }) do 153 | if (str:sub(pos, pos + #k - 1) == k) then 154 | val, pos = v, pos + #k 155 | break 156 | end 157 | end 158 | 159 | if val == nil then syntax_error(str, pos) end 160 | end 161 | 162 | if first and pos <= #str then syntax_error(str, pos) end 163 | if self.without_null and val == NULL then val = nil end 164 | 165 | return val, pos 166 | end 167 | 168 | return Parser 169 | -------------------------------------------------------------------------------- /lib/pretty/json/serializer.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2018, Souche Inc. 2 | 3 | local Constant = require "pretty.json.constant" 4 | 5 | local NULL = Constant.NULL 6 | local ESC_MAP = Constant.ESC_MAP 7 | 8 | local function kind_of(obj) 9 | if type(obj) ~= "table" then return type(obj) end 10 | if obj == NULL then return "nil" end 11 | 12 | local i = 1 13 | for _ in pairs(obj) do 14 | if obj[i] ~= nil then i = i + 1 else return "table" end 15 | end 16 | 17 | if i == 1 then 18 | return "table" 19 | else 20 | return "array" 21 | end 22 | end 23 | 24 | local function escape_str(s) 25 | for k, v in pairs(ESC_MAP) do 26 | s = s:gsub(k, v) 27 | end 28 | 29 | return s 30 | end 31 | 32 | local Serializer = { 33 | print_address = false, 34 | max_depth = 700 35 | } 36 | 37 | setmetatable(Serializer, { 38 | __call = function(self, opts) 39 | local serializer = { 40 | depth = 0, 41 | max_depth = opts.max_depth, 42 | print_address = opts.print_address, 43 | stream = opts.stream 44 | } 45 | 46 | setmetatable(serializer, { __index = Serializer }) 47 | 48 | return serializer 49 | end 50 | }) 51 | 52 | function Serializer:space(n) 53 | local stream = self.stream 54 | for i = 1, n or 0 do 55 | stream:write(" ") 56 | end 57 | 58 | return self 59 | end 60 | 61 | function Serializer:key(key) 62 | local stream = self.stream 63 | local kind = kind_of(key) 64 | 65 | if kind == "array" then 66 | error("Can't encode array as key.") 67 | elseif kind == "table" then 68 | error("Can't encode table as key.") 69 | elseif kind == "string" then 70 | stream:write("\"", escape_str(key), "\"") 71 | elseif kind == "number" then 72 | stream:write("\"", tostring(key), "\"") 73 | elseif self.print_address then 74 | stream:write(tostring(key)) 75 | else 76 | error("Unjsonifiable type: " .. kind .. ".") 77 | end 78 | 79 | return self 80 | end 81 | 82 | function Serializer:array(arr, replacer, indent, space) 83 | local stream = self.stream 84 | 85 | stream:write("[") 86 | for i, v in ipairs(arr) do 87 | if replacer then v = replacer(k, v) end 88 | 89 | stream:write(i == 1 and "" or ",") 90 | stream:write(space > 0 and "\n" or "") 91 | self:space(indent) 92 | self:json(v, replacer, indent + space, space) 93 | end 94 | if #arr > 0 then 95 | stream:write(space > 0 and "\n" or "") 96 | self:space(indent - space) 97 | end 98 | stream:write("]") 99 | 100 | return self 101 | end 102 | 103 | function Serializer:table(obj, replacer, indent, space) 104 | local stream = self.stream 105 | 106 | stream:write("{") 107 | local len = 0 108 | for k, v in pairs(obj) do 109 | if replacer then v = replacer(k, v) end 110 | 111 | if v ~= nil then 112 | stream:write(len == 0 and "" or ",") 113 | stream:write(space > 0 and "\n" or "") 114 | self:space(indent) 115 | self:key(k) 116 | stream:write(space > 0 and ": " or ":") 117 | self:json(v, replacer, indent + space, space) 118 | len = len + 1 119 | end 120 | end 121 | if len > 0 then 122 | stream:write(space > 0 and "\n" or "") 123 | self:space(indent - space) 124 | end 125 | stream:write("}") 126 | 127 | return self 128 | end 129 | 130 | function Serializer:json(obj, replacer, indent, space) 131 | local stream = self.stream 132 | local kind = kind_of(obj) 133 | 134 | self.depth = self.depth + 1 135 | if self.depth > self.max_depth then error("Reach max depth: " .. tostring(self.max_depth)) end 136 | 137 | if kind == "array" then 138 | self:array(obj, replacer, indent, space) 139 | elseif kind == "table" then 140 | self:table(obj, replacer, indent, space) 141 | elseif kind == "string" then 142 | stream:write("\"", escape_str(obj), "\"") 143 | elseif kind == "number" then 144 | stream:write(tostring(obj)) 145 | elseif kind == "boolean" then 146 | stream:write(tostring(obj)) 147 | elseif kind == "nil" then 148 | stream:write("null") 149 | elseif self.print_address then 150 | stream:write(tostring(obj)) 151 | else 152 | error("Unjsonifiable type: " .. kind) 153 | end 154 | 155 | return self 156 | end 157 | 158 | function Serializer:toString() 159 | return self.stream:toString() 160 | end 161 | 162 | return Serializer 163 | -------------------------------------------------------------------------------- /lib/wiriscript/functions.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | -------------------------------- 3 | THIS FILE IS PART OF WIRISCRIPT 4 | Nowiry#2663 5 | -------------------------------- 6 | ]] 7 | 8 | json = require "pretty.json" 9 | local self = {} 10 | self.version = 29 11 | 12 | Config = { 13 | controls = { 14 | vehicleweapons = 86, 15 | airstrikeaircraft = 86 16 | }, 17 | general = { 18 | standnotifications = false, 19 | displayhealth = true, 20 | language = "english", 21 | developer = false, -- developer flag (enables/disables some debug features) 22 | showintro = true 23 | }, 24 | ufo = { 25 | disableboxes = false, -- determines if boxes are drawn on players to show their position 26 | targetplayer = false -- wether tractor beam only targets players or not 27 | }, 28 | vehiclegun = { 29 | disablepreview = false, 30 | }, 31 | healthtxtpos = { 32 | x = 0.03, 33 | y = 0.05 34 | }, 35 | handlingAutoload = {} 36 | } 37 | 38 | ---@alias HudColour integer 39 | 40 | HudColour = 41 | { 42 | pureWhite = 0, 43 | white = 1, 44 | black = 2, 45 | grey = 3, 46 | greyLight = 4, 47 | greyDrak = 5, 48 | red = 6, 49 | redLight = 7, 50 | redDark = 8, 51 | blue = 9, 52 | blueLight = 10, 53 | blueDark = 11, 54 | yellow = 12, 55 | yellowLight = 13, 56 | yellowDark = 14, 57 | orange = 15, 58 | orangeLight = 16, 59 | orangeDark = 17, 60 | green = 18, 61 | greenLight = 19, 62 | greenDark = 20, 63 | purple = 21, 64 | purpleLight = 22, 65 | purpleDark = 23, 66 | radarHealth = 25, 67 | radarArmour = 26, 68 | friendly = 118, 69 | } 70 | 71 | local NULL = 0 72 | 73 | -------------------------- 74 | -- NOTIFICATION 75 | -------------------------- 76 | 77 | ---@class Notification 78 | notification = 79 | { 80 | txdDict = "DIA_ZOMBIE1", 81 | txdName = "DIA_ZOMBIE1", 82 | title = "WiriScript", 83 | subtitle = "~c~" .. util.get_label_text("PM_PANE_FEE") .. "~s~", 84 | defaultColour = HudColour.black 85 | } 86 | 87 | ---@param msg string 88 | function notification.stand(msg) 89 | assert(type(msg) == "string", "msg must be a string, got " .. type(msg)) 90 | msg = msg:gsub('~[%w_]-~', ""):gsub('(.-)', '%1') 91 | util.toast("[WiriScript] " .. msg) 92 | end 93 | 94 | 95 | ---@param format string 96 | ---@param colour? HudColour 97 | function notification:help(format, colour, ...) 98 | assert(type(format) == "string", "msg must be a string, got " .. type(format)) 99 | 100 | local msg = string.format(format, ...) 101 | if Config.general.standnotifications then 102 | return self.stand(msg) 103 | end 104 | 105 | HUD.THEFEED_SET_BACKGROUND_COLOR_FOR_NEXT_POST(colour or self.defaultColour) 106 | util.BEGIN_TEXT_COMMAND_THEFEED_POST("~BLIP_INFO_ICON~ " .. msg) 107 | HUD.END_TEXT_COMMAND_THEFEED_POST_TICKER_WITH_TOKENS(true, true) 108 | end 109 | 110 | 111 | ---@param format string 112 | ---@param colour? HudColour 113 | function notification:normal(format, colour, ...) 114 | assert(type(format) == "string", "msg must be a string, got " .. type(format)) 115 | 116 | local msg = string.format(format, ...) 117 | if Config.general.standnotifications then 118 | return self.stand(msg) 119 | end 120 | 121 | HUD.THEFEED_SET_BACKGROUND_COLOR_FOR_NEXT_POST(colour or self.defaultColour) 122 | util.BEGIN_TEXT_COMMAND_THEFEED_POST(msg) 123 | HUD.END_TEXT_COMMAND_THEFEED_POST_MESSAGETEXT(self.txdDict, self.txdName, true, 4, self.title, self.subtitle) 124 | HUD.END_TEXT_COMMAND_THEFEED_POST_TICKER(false, false) 125 | end 126 | 127 | -------------------------- 128 | -- MENU 129 | -------------------------- 130 | 131 | Features = {} 132 | Translation = {} 133 | 134 | ---@param section string 135 | ---@param name string 136 | ---@return string 137 | function translate(section, name) 138 | Features[section] = Features[section] or {} 139 | Features[section][name] = Features[section][name] or "" 140 | if Config.general.language == "english" then 141 | return name 142 | end 143 | Translation[section] = Translation[section] or Features[section] 144 | if not Translation[section][name] then 145 | Translation[section][name] = "" 146 | return name 147 | end 148 | if Translation[section][name] == "" then 149 | return name 150 | end 151 | return Translation[section][name] 152 | end 153 | 154 | 155 | ---@param value any 156 | ---@param e string 157 | function type_match (value, e) 158 | local t = type(value) 159 | for w in e:gmatch('[^|]+') do 160 | if t == w then return true end 161 | end 162 | local msg = "must be %s, got %s" 163 | return false, msg:format(e:gsub('|', " or "), t) 164 | end 165 | 166 | 167 | ---@param tbl table 168 | ---@param types {[1]: string, [2]:string} 169 | ---@return boolean 170 | ---@return string? errmsg 171 | local check_table_types = function (tbl, types) 172 | if type(tbl) ~= "table" then 173 | return false, "tbl must be a tble" 174 | end 175 | for key, value in pairs(tbl) do 176 | local ok, errmsg = type_match(key, types[1]) 177 | if not ok then return false, "field " .. key .. ' ' .. errmsg end 178 | 179 | local ok, errmsg = type_match(value, types[2]) 180 | if not ok then return false, "field " .. key .. ' ' .. errmsg end 181 | end 182 | return true 183 | end 184 | 185 | 186 | ---@param obj table 187 | ---@return boolean 188 | ---@return string? errmsg 189 | function is_translation_valid (obj) 190 | for sect_name, section in pairs(obj) do 191 | if type(sect_name) ~= "string" then 192 | return false, "got unexpected key type: " .. type(sect_name) 193 | end 194 | 195 | if type(section) ~= "table" then 196 | return false, "field " .. sect_name .. " must be a table, got " .. type(section) 197 | end 198 | 199 | local ok, err = check_table_types(section, {"string", "string"}) 200 | if not ok then return false, err end 201 | end 202 | return true 203 | end 204 | 205 | 206 | ---@param language string 207 | ---@return boolean 208 | ---@return string? errmsg 209 | function load_translation(language) 210 | local path = filesystem.scripts_dir() .. "WiriScript\\language\\" .. language 211 | if not filesystem.exists(path) then 212 | return false, "no such a file" 213 | end 214 | 215 | local ok, result = json.parse(path, false) 216 | if not ok then 217 | return false, result 218 | end 219 | 220 | local ok, errmsg = is_translation_valid(result) 221 | if not ok then 222 | return false, errmsg 223 | end 224 | 225 | Translation = result 226 | util.log("Translation file successfully loaded: %s", language) 227 | return true 228 | end 229 | 230 | -------------------------- 231 | -- FILE 232 | -------------------------- 233 | 234 | Ini = {} 235 | 236 | ---Saves a table with key-value pairs in an ini format file. 237 | ---@param fileName string 238 | ---@param obj table 239 | function Ini.save(fileName, obj) 240 | local file = assert(io.open(fileName, "w"), "error loading file") 241 | local s = {} 242 | for section, tbl in pairs(obj) do 243 | assert(type(tbl) == "table", "expected field " .. section .. " to be a table, got " .. type(tbl)) 244 | local l = {} 245 | table.insert(l, string.format("[%s]", section)) 246 | for k, v in pairs(tbl) do table.insert(l, string.format("%s=%s", k, v)) end 247 | table.insert(s, table.concat(l, '\n') .. '\n') 248 | end 249 | file:write(table.concat(s, '\n')) 250 | end 251 | 252 | 253 | ---Parses a table from an ini format file. 254 | ---@param fileName any 255 | ---@return table 256 | function Ini.load(fileName) 257 | assert(type(fileName) == "string", "fileName must be a string") 258 | local file = assert(io.open(fileName, "r"), "error loading file: " .. fileName) 259 | local data = {} 260 | local section 261 | for line in io.lines(fileName) do 262 | local tempSection = string.match(line, '^%[([^%]]+)%]$') 263 | 264 | if tempSection ~= nil then 265 | section = tonumber(tempSection) and tonumber(tempSection) or tempSection 266 | data[section] = data[section] or {} 267 | end 268 | 269 | local param, value = string.match(line, '^([%w_]+)%s*=%s*(.+)$') 270 | if section ~= nil and param and value ~= nil then 271 | if value == "true" then 272 | value = true 273 | elseif value == "false" then 274 | value = false 275 | elseif tonumber(value) then 276 | value = tonumber(value) 277 | end 278 | data[section][tonumber(param) or param] = value 279 | end 280 | end 281 | return data 282 | end 283 | 284 | 285 | local parseJson = json.parse 286 | 287 | ---@param filePath string 288 | ---@param withoutNull? boolean 289 | ---@return boolean 290 | ---@return string|table 291 | json.parse = function (filePath, withoutNull) 292 | local file = assert(io.open(filePath, "r"), filePath .. " does not exist") 293 | local content = file:read("a") 294 | local fileName = string.match(filePath, '^.+\\(.+)') 295 | if #content == 0 then 296 | return false, fileName .. " is empty" 297 | end 298 | return pcall(parseJson, content, withoutNull) 299 | end 300 | 301 | -------------------------- 302 | -- EFFECT 303 | -------------------------- 304 | 305 | ---@class Effect 306 | Effect = {asset = "", name = "", scale = 1.0} 307 | Effect.__index = Effect 308 | 309 | ---@param asset string 310 | ---@param name string 311 | ---@param scale? number 312 | ---@return Effect 313 | function Effect.new(asset, name, scale) 314 | local inst = setmetatable({}, Effect) 315 | inst.name = name 316 | inst.asset = asset 317 | inst.scale = scale 318 | return inst 319 | end 320 | 321 | -------------------------- 322 | -- SOUND 323 | -------------------------- 324 | 325 | ---@class Sound 326 | Sound = {Id = -1, name = "", reference = ""} 327 | Sound.__index = Sound 328 | 329 | ---@alias nullptr 0 330 | 331 | ---@param name string|nullptr 332 | ---@param reference string|nullptr 333 | ---@return Sound 334 | function Sound.new(name, reference) 335 | local inst = setmetatable({}, Sound) 336 | inst.name = name 337 | inst.reference = reference 338 | return inst 339 | end 340 | 341 | function Sound:play() 342 | if self.Id == -1 then 343 | self.Id = AUDIO.GET_SOUND_ID() 344 | AUDIO.PLAY_SOUND_FRONTEND(self.Id, self.name, self.reference, true) 345 | end 346 | end 347 | 348 | function Sound:stop() 349 | if self.Id ~= -1 then 350 | AUDIO.STOP_SOUND(self.Id) 351 | AUDIO.RELEASE_SOUND_ID(self.Id) 352 | self.Id = -1 353 | end 354 | end 355 | 356 | function Sound:hasFinished() 357 | return AUDIO.HAS_SOUND_FINISHED(self.Id) 358 | end 359 | 360 | function Sound:playFromEntity(entity) 361 | if self.Id == -1 then 362 | self.Id = AUDIO.GET_SOUND_ID() 363 | AUDIO.PLAY_SOUND_FROM_ENTITY(self.Id, self.name, entity, self.reference, true, 0) 364 | end 365 | end 366 | 367 | -------------------------- 368 | -- COLOUR 369 | -------------------------- 370 | 371 | ---@class Colour 372 | ---@field r number | integer 373 | ---@field g number | integer 374 | ---@field b number | integer 375 | ---@field a number | integer 376 | 377 | function new_colour(r, g, b, a) 378 | return {r = r, g = g, b = b, a = a} 379 | end 380 | 381 | 382 | ---@return Colour 383 | function get_random_colour() 384 | local colour = {a = 255} 385 | colour.r = math.random(0,255) 386 | colour.g = math.random(0,255) 387 | colour.b = math.random(0,255) 388 | return colour 389 | end 390 | 391 | 392 | ---@param hudColour HudColour 393 | ---@return {r: integer, g: integer, b: integer, a: integer} 394 | function get_hud_colour(hudColour) 395 | local r = memory.alloc(1) 396 | local g = memory.alloc(1) 397 | local b = memory.alloc(1) 398 | local a = memory.alloc(1) 399 | HUD.GET_HUD_COLOUR(hudColour, r, g, b, a) 400 | return {r = memory.read_int(r), g = memory.read_int(g), b = memory.read_int(b), a = memory.read_int(a)} 401 | end 402 | 403 | 404 | ---@param colour Colour 405 | function rainbow_colour(colour) 406 | if colour.r > 0 and colour.b == 0 then 407 | colour.r = colour.r - 1 408 | colour.g = colour.g + 1 409 | end 410 | 411 | if colour.g > 0 and colour.r == 0 then 412 | colour.g = colour.g - 1 413 | colour.b = colour.b + 1 414 | end 415 | 416 | if colour.b > 0 and colour.g == 0 then 417 | colour.r = colour.r + 1 418 | colour.b = colour.b - 1 419 | end 420 | end 421 | 422 | 423 | ---@param perc number 424 | ---@return Colour 425 | function get_blended_colour(perc) 426 | local colour = {a = 255} 427 | local r, g, b 428 | 429 | if perc <= 0.5 then 430 | r = 1.0 431 | g = interpolate(0.0, 1.0, perc/0.5) 432 | b = 0.0 433 | else 434 | r = interpolate(1.0, 0, (perc - 0.5)/0.5) 435 | g = 1.0 436 | b = 0.0 437 | end 438 | 439 | colour.r = math.ceil(r * 255) 440 | colour.g = math.ceil(g * 255) 441 | colour.b = math.ceil(b * 255) 442 | return colour 443 | end 444 | 445 | -------------------------- 446 | -- INSTRUCTIONAL 447 | -------------------------- 448 | 449 | Instructional = {scaleform = 0} 450 | 451 | ---@return boolean 452 | function Instructional:begin () 453 | if GRAPHICS.HAS_SCALEFORM_MOVIE_LOADED(self.scaleform) then 454 | GRAPHICS.BEGIN_SCALEFORM_MOVIE_METHOD(self.scaleform, "CLEAR_ALL") 455 | GRAPHICS.END_SCALEFORM_MOVIE_METHOD() 456 | 457 | GRAPHICS.BEGIN_SCALEFORM_MOVIE_METHOD(self.scaleform, "TOGGLE_MOUSE_BUTTONS") 458 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_BOOL(true) 459 | GRAPHICS.END_SCALEFORM_MOVIE_METHOD() 460 | 461 | self.position = 0 462 | return true 463 | else 464 | self.scaleform = request_scaleform_movie("instructional_buttons") 465 | return false 466 | end 467 | end 468 | 469 | 470 | ---@param index integer 471 | ---@param name string 472 | ---@param button string 473 | function Instructional:add_data_slot(index, name, button) 474 | GRAPHICS.BEGIN_SCALEFORM_MOVIE_METHOD(self.scaleform, "SET_DATA_SLOT") 475 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_INT(self.position) 476 | 477 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_PLAYER_NAME_STRING(button) 478 | if HUD.DOES_TEXT_LABEL_EXIST(name) then 479 | GRAPHICS.BEGIN_TEXT_COMMAND_SCALEFORM_STRING(name) 480 | GRAPHICS.END_TEXT_COMMAND_SCALEFORM_STRING() 481 | else 482 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_TEXTURE_NAME_STRING(name) 483 | end 484 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_BOOL(false) 485 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_INT(index) 486 | GRAPHICS.END_SCALEFORM_MOVIE_METHOD() 487 | self.position = self.position + 1 488 | end 489 | 490 | 491 | ---@param index integer 492 | ---@param name string 493 | function Instructional.add_control(index, name) 494 | local button = PAD.GET_CONTROL_INSTRUCTIONAL_BUTTONS_STRING(2, index, true) 495 | Instructional:add_data_slot(index, name, button) 496 | end 497 | 498 | 499 | ---@param index integer 500 | ---@param name string 501 | function Instructional.add_control_group (index, name) 502 | local button = PAD.GET_CONTROL_GROUP_INSTRUCTIONAL_BUTTONS_STRING(2, index, true) 503 | Instructional:add_data_slot(index, name, button) 504 | end 505 | 506 | 507 | ---@param r integer 508 | ---@param g integer 509 | ---@param b integer 510 | ---@param a integer 511 | function Instructional:set_background_colour(r, g, b, a) 512 | GRAPHICS.BEGIN_SCALEFORM_MOVIE_METHOD(self.scaleform, "SET_BACKGROUND_COLOUR") 513 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_INT(r) 514 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_INT(g) 515 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_INT(b) 516 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_INT(a) 517 | GRAPHICS.END_SCALEFORM_MOVIE_METHOD() 518 | end 519 | 520 | 521 | function Instructional:draw () 522 | GRAPHICS.BEGIN_SCALEFORM_MOVIE_METHOD(self.scaleform, "DRAW_INSTRUCTIONAL_BUTTONS") 523 | GRAPHICS.END_SCALEFORM_MOVIE_METHOD() 524 | 525 | GRAPHICS.DRAW_SCALEFORM_MOVIE_FULLSCREEN(self.scaleform, 255, 255, 255, 220, 0) 526 | self.position = 0 527 | end 528 | 529 | -------------------------- 530 | -- TIMER 531 | -------------------------- 532 | 533 | ---@class Timer 534 | ---@field elapsed fun(): integer 535 | ---@field reset fun() 536 | ---@field isEnabled fun(): boolean 537 | ---@field disable fun() 538 | 539 | ---@return Timer 540 | function newTimer() 541 | local self = { 542 | start = util.current_time_millis(), 543 | m_enabled = false, 544 | } 545 | 546 | local function reset() 547 | self.start = util.current_time_millis() 548 | self.m_enabled = true 549 | end 550 | 551 | local function elapsed() 552 | return util.current_time_millis() - self.start 553 | end 554 | 555 | local function disable() self.m_enabled = false end 556 | local function isEnabled() return self.m_enabled end 557 | 558 | return 559 | { 560 | isEnabled = isEnabled, 561 | reset = reset, 562 | elapsed = elapsed, 563 | disable = disable, 564 | } 565 | end 566 | 567 | -------------------------- 568 | -- ENTITIES 569 | -------------------------- 570 | 571 | function SetBit(bits, place) 572 | return (bits | (1 << place)) 573 | end 574 | 575 | function ClearBit(bits, place) 576 | return (bits & ~(1 << place)) 577 | end 578 | 579 | function BitTest(bits, place) 580 | return (bits & (1 << place)) ~= 0 581 | end 582 | 583 | 584 | ---@param entity Entity 585 | ---@param value boolean 586 | function set_explosion_proof(entity, value) 587 | local pEntity = entities.handle_to_pointer(entity) 588 | if pEntity == 0 then return end 589 | local damageBits = memory.read_uint(pEntity + 0x188) 590 | damageBits = value and SetBit(damageBits, 11) or ClearBit(damageBits, 11) 591 | memory.write_uint(pEntity + 0x188, damageBits) 592 | end 593 | 594 | 595 | ---@param entity Entity 596 | ---@param target Entity 597 | ---@param usePitch? boolean 598 | function set_entity_face_entity(entity, target, usePitch) 599 | local pos1 = ENTITY.GET_ENTITY_COORDS(entity, false) 600 | local pos2 = ENTITY.GET_ENTITY_COORDS(target, false) 601 | local rel = v3.new(pos2) 602 | rel:sub(pos1) 603 | local rot = rel:toRot() 604 | if not usePitch then 605 | ENTITY.SET_ENTITY_HEADING(entity, rot.z) 606 | else 607 | ENTITY.SET_ENTITY_ROTATION(entity, rot.x, rot.y, rot.z, 2, false) 608 | end 609 | end 610 | 611 | 612 | ---@param entity Entity 613 | ---@param blipSprite integer 614 | ---@param colour integer 615 | ---@return Blip 616 | function add_blip_for_entity(entity, blipSprite, colour) 617 | local blip = HUD.ADD_BLIP_FOR_ENTITY(entity) 618 | HUD.SET_BLIP_SPRITE(blip, blipSprite) 619 | HUD.SET_BLIP_COLOUR(blip, colour) 620 | HUD.SHOW_HEIGHT_ON_BLIP(blip, false) 621 | 622 | util.create_tick_handler(function () 623 | if not ENTITY.DOES_ENTITY_EXIST(entity)or ENTITY.IS_ENTITY_DEAD(entity, false) then 624 | util.remove_blip(blip) 625 | return false 626 | elseif not HUD.DOES_BLIP_EXIST(blip) then 627 | return false 628 | else 629 | local heading = ENTITY.GET_ENTITY_HEADING(entity) 630 | HUD.SET_BLIP_ROTATION(blip, math.ceil(heading)) 631 | end 632 | end) 633 | 634 | return blip 635 | end 636 | 637 | 638 | ---@param blip Blip 639 | ---@param name string 640 | ---@param isLabel? boolean 641 | function set_blip_name(blip, name, isLabel) 642 | HUD.BEGIN_TEXT_COMMAND_SET_BLIP_NAME("STRING") 643 | if not isLabel then 644 | HUD.ADD_TEXT_COMPONENT_SUBSTRING_PLAYER_NAME(name) 645 | else 646 | HUD.ADD_TEXT_COMPONENT_SUBSTRING_TEXT_LABEL(name) 647 | end 648 | HUD.END_TEXT_COMMAND_SET_BLIP_NAME(blip) 649 | end 650 | 651 | 652 | ---@param entity Entity 653 | ---@return boolean 654 | function request_control_once(entity) 655 | if not NETWORK.NETWORK_IS_IN_SESSION() then 656 | return true 657 | end 658 | local netId = NETWORK.NETWORK_GET_NETWORK_ID_FROM_ENTITY(entity) 659 | NETWORK.SET_NETWORK_ID_CAN_MIGRATE(netId, true) 660 | return NETWORK.NETWORK_REQUEST_CONTROL_OF_ENTITY(entity) 661 | end 662 | 663 | 664 | ---@param entity Entity 665 | ---@param timeOut? integer #time in `ms` trying to get control 666 | ---@return boolean 667 | function request_control(entity, timeOut) 668 | if not ENTITY.DOES_ENTITY_EXIST(entity) then 669 | return false 670 | end 671 | timeOut = timeOut or 500 672 | local start = newTimer() 673 | while not request_control_once(entity) and start.elapsed() < timeOut do 674 | util.yield_once() 675 | end 676 | return start.elapsed() < timeOut 677 | end 678 | 679 | 680 | ---@param ped Ped 681 | ---@param maxPeds? integer 682 | ---@param ignore? integer 683 | ---@return Entity[] 684 | function get_ped_nearby_peds(ped, maxPeds, ignore) 685 | maxPeds = maxPeds or 16 686 | local pEntityList = memory.alloc((maxPeds + 1) * 8) 687 | memory.write_int(pEntityList, maxPeds) 688 | local pedsList = {} 689 | for i = 1, PED.GET_PED_NEARBY_PEDS(ped, pEntityList, ignore or -1), 1 do 690 | pedsList[i] = memory.read_int(pEntityList + i*8) 691 | end 692 | return pedsList 693 | end 694 | 695 | 696 | ---@param ped Ped 697 | ---@param maxVehicles? integer 698 | ---@return Entity[] 699 | function get_ped_nearby_vehicles(ped, maxVehicles) 700 | maxVehicles = maxVehicles or 16 701 | local pVehicleList = memory.alloc((maxVehicles + 1) * 8) 702 | memory.write_int(pVehicleList, maxVehicles) 703 | local vehiclesList = {} 704 | for i = 1, PED.GET_PED_NEARBY_VEHICLES(ped, pVehicleList) do 705 | vehiclesList[i] = memory.read_int(pVehicleList + i*8) 706 | end 707 | return vehiclesList 708 | end 709 | 710 | 711 | ---@param ped Ped 712 | ---@return Entity[] 713 | function get_ped_nearby_entities(ped) 714 | local peds = get_ped_nearby_peds(ped) 715 | local vehicles = get_ped_nearby_vehicles(ped) 716 | local entities = peds 717 | for i = 1, #vehicles do table.insert(entities, vehicles[i]) end 718 | return entities 719 | end 720 | 721 | 722 | ---@param player Player 723 | ---@param radius number 724 | ---@return Entity[] 725 | function get_peds_in_player_range(player, radius) 726 | local peds = {} 727 | local playerPed = PLAYER.GET_PLAYER_PED_SCRIPT_INDEX(player) 728 | local pos = players.get_position(player) 729 | for _, ped in ipairs(entities.get_all_peds_as_handles()) do 730 | if ped ~= playerPed and not PED.IS_PED_FATALLY_INJURED(ped) then 731 | local pedPos = ENTITY.GET_ENTITY_COORDS(ped, true) 732 | if pos:distance(pedPos) <= radius then table.insert(peds, ped) end 733 | end 734 | end 735 | return peds 736 | end 737 | 738 | 739 | ---@param player Player 740 | ---@param radius number 741 | ---@return Entity[] 742 | function get_vehicles_in_player_range(player, radius) 743 | local vehicles = {} 744 | local pos = players.get_position(player) 745 | for _, vehicle in ipairs(entities.get_all_vehicles_as_handles()) do 746 | local vehPos = ENTITY.GET_ENTITY_COORDS(vehicle, true) 747 | if pos:distance(vehPos) <= radius then table.insert(vehicles, vehicle) end 748 | end 749 | return vehicles 750 | end 751 | 752 | 753 | ---@param pId Player 754 | ---@param radius number 755 | ---@return Entity[] 756 | function get_entities_in_player_range(pId, radius) 757 | local peds = get_peds_in_player_range(pId, radius) 758 | local vehicles = get_vehicles_in_player_range(pId, radius) 759 | local entities = peds 760 | for i = 1, #vehicles do table.insert(entities, vehicles[i]) end 761 | return entities 762 | end 763 | 764 | 765 | ---@param start v3 766 | ---@param to v3 767 | ---@param colour Colour 768 | local draw_line = function (start, to, colour) 769 | GRAPHICS.DRAW_LINE(start.x, start.y, start.z, to.x, to.y, to.z, colour.r, colour.g, colour.b, colour.a) 770 | end 771 | 772 | 773 | ---@param pos0 v3 774 | ---@param pos1 v3 775 | ---@param pos2 v3 776 | ---@param pos3 v3 777 | ---@param colour Colour 778 | local draw_rect = function (pos0, pos1, pos2, pos3, colour) 779 | GRAPHICS.DRAW_POLY(pos0.x, pos0.y, pos0.z, pos1.x, pos1.y, pos1.z, pos3.x, pos3.y, pos3.z, colour.r, colour.g, colour.b, colour.a) 780 | GRAPHICS.DRAW_POLY(pos3.x, pos3.y, pos3.z, pos2.x, pos2.y, pos2.z, pos0.x, pos0.y, pos0.z, colour.r, colour.g, colour.b, colour.a) 781 | end 782 | 783 | 784 | ---@param entity Entity 785 | ---@param showPoly? boolean 786 | ---@param colour? Colour 787 | function draw_bounding_box(entity, showPoly, colour) 788 | if not ENTITY.DOES_ENTITY_EXIST(entity) then 789 | return 790 | end 791 | colour = colour or {r = 255, g = 0, b = 0, a = 255} 792 | local min = v3.new() 793 | local max = v3.new() 794 | MISC.GET_MODEL_DIMENSIONS(ENTITY.GET_ENTITY_MODEL(entity), min, max) 795 | min:abs(); max:abs() 796 | 797 | local upperLeftRear = ENTITY.GET_OFFSET_FROM_ENTITY_IN_WORLD_COORDS(entity, -max.x, -max.y, max.z) 798 | local upperRightRear = ENTITY.GET_OFFSET_FROM_ENTITY_IN_WORLD_COORDS(entity, min.x, -max.y, max.z) 799 | local lowerLeftRear = ENTITY.GET_OFFSET_FROM_ENTITY_IN_WORLD_COORDS(entity, -max.x, -max.y, -min.z) 800 | local lowerRightRear = ENTITY.GET_OFFSET_FROM_ENTITY_IN_WORLD_COORDS(entity, min.x, -max.y, -min.z) 801 | 802 | draw_line(upperLeftRear, upperRightRear, colour) 803 | draw_line(lowerLeftRear, lowerRightRear, colour) 804 | draw_line(upperLeftRear, lowerLeftRear, colour) 805 | draw_line(upperRightRear, lowerRightRear, colour) 806 | 807 | local upperLeftFront = ENTITY.GET_OFFSET_FROM_ENTITY_IN_WORLD_COORDS(entity, -max.x, min.y, max.z) 808 | local upperRightFront = ENTITY.GET_OFFSET_FROM_ENTITY_IN_WORLD_COORDS(entity, min.x, min.y, max.z) 809 | local lowerLeftFront = ENTITY.GET_OFFSET_FROM_ENTITY_IN_WORLD_COORDS(entity, -max.x, min.y, -min.z) 810 | local lowerRightFront = ENTITY.GET_OFFSET_FROM_ENTITY_IN_WORLD_COORDS(entity, min.x, min.y, -min.z) 811 | 812 | draw_line(upperLeftFront, upperRightFront, colour) 813 | draw_line(lowerLeftFront, lowerRightFront, colour) 814 | draw_line(upperLeftFront, lowerLeftFront, colour) 815 | draw_line(upperRightFront, lowerRightFront, colour) 816 | 817 | draw_line(upperLeftRear, upperLeftFront, colour) 818 | draw_line(upperRightRear, upperRightFront, colour) 819 | draw_line(lowerLeftRear, lowerLeftFront, colour) 820 | draw_line(lowerRightRear, lowerRightFront, colour) 821 | 822 | if type(showPoly) ~= "boolean" or showPoly then 823 | draw_rect(lowerLeftRear, upperLeftRear, lowerLeftFront, upperLeftFront, colour) 824 | draw_rect(upperRightRear, lowerRightRear, upperRightFront, lowerRightFront, colour) 825 | 826 | draw_rect(lowerLeftFront, upperLeftFront, lowerRightFront, upperRightFront, colour) 827 | draw_rect(upperLeftRear, lowerLeftRear, upperRightRear, lowerRightRear, colour) 828 | 829 | draw_rect(upperRightRear, upperRightFront, upperLeftRear, upperLeftFront, colour) 830 | draw_rect(lowerRightFront, lowerRightRear, lowerLeftFront, lowerLeftRear, colour) 831 | end 832 | end 833 | 834 | 835 | ---@param entity Entity 836 | ---@param flag integer 837 | function set_decor_flag(entity, flag) 838 | DECORATOR.DECOR_SET_INT(entity, "Casino_Game_Info_Decorator", flag) 839 | end 840 | 841 | 842 | ---@param entity Entity 843 | ---@param flag integer 844 | ---@return boolean 845 | function is_decor_flag_set(entity, flag) 846 | if ENTITY.DOES_ENTITY_EXIST(entity) and 847 | DECORATOR.DECOR_EXIST_ON(entity, "Casino_Game_Info_Decorator") then 848 | local value = DECORATOR.DECOR_GET_INT(entity, "Casino_Game_Info_Decorator") 849 | return (value & flag) ~= 0 850 | end 851 | return false 852 | end 853 | 854 | 855 | ---@param entity Entity 856 | function remove_decor(entity) 857 | DECORATOR.DECOR_REMOVE(entity, "Casino_Game_Info_Decorator") 858 | end 859 | 860 | 861 | ---@param ped Ped 862 | ---@param forcedOn boolean 863 | ---@param hasCone boolean 864 | ---@param noticeRange number 865 | ---@param colour integer 866 | ---@param sprite integer 867 | function add_ai_blip_for_ped(ped, forcedOn, hasCone, noticeRange, colour, sprite) 868 | if colour == -1 then 869 | HUD.SET_PED_HAS_AI_BLIP(ped, true) 870 | else 871 | HUD.SET_PED_HAS_AI_BLIP_WITH_COLOUR(ped, true, colour) 872 | end 873 | HUD.SET_PED_AI_BLIP_NOTICE_RANGE(ped, noticeRange) 874 | if sprite ~= -1 then HUD.SET_PED_AI_BLIP_SPRITE(ped, sprite) end 875 | HUD.SET_PED_AI_BLIP_HAS_CONE(ped, hasCone) 876 | HUD.SET_PED_AI_BLIP_FORCED_ON(ped, forcedOn) 877 | end 878 | 879 | 880 | ---@param entity Entity 881 | ---@param minDistance number 882 | ---@param maxDistance number 883 | ---@return v3 884 | function get_random_offset_from_entity(entity, minDistance, maxDistance) 885 | local pos = ENTITY.GET_ENTITY_COORDS(entity, false) 886 | return get_random_offset_in_range(pos, minDistance, maxDistance) 887 | end 888 | 889 | 890 | ---@param coords v3 891 | ---@param minDistance number 892 | ---@param maxDistance number 893 | ---@return v3 894 | function get_random_offset_in_range(coords, minDistance, maxDistance) 895 | local radius = random_float(minDistance, maxDistance) 896 | local angle = random_float(0, 2 * math.pi) 897 | local delta = v3.new(math.cos(angle), math.sin(angle), 0.0) 898 | delta:mul(radius) 899 | coords:add(delta) 900 | return coords 901 | end 902 | 903 | 904 | ---@param entity Entity 905 | function set_entity_as_no_longer_needed(entity) 906 | if not ENTITY.DOES_ENTITY_EXIST(entity) then return end 907 | local pHandle = memory.alloc_int() 908 | memory.write_int(pHandle, entity) 909 | ENTITY.SET_ENTITY_AS_NO_LONGER_NEEDED(pHandle) 910 | end 911 | 912 | 913 | ---@param entity Entity 914 | ---@param target Entity 915 | ---@return number 916 | function get_distance_between_entities(entity, target) 917 | if not ENTITY.DOES_ENTITY_EXIST(entity) or not ENTITY.DOES_ENTITY_EXIST(target) then 918 | return 0.0 919 | end 920 | local pos = ENTITY.GET_ENTITY_COORDS(entity, true) 921 | return ENTITY.GET_ENTITY_COORDS(target, true):distance(pos) 922 | end 923 | 924 | -------------------------- 925 | -- PLAYER 926 | -------------------------- 927 | 928 | ---@param player Player 929 | ---@return boolean 930 | function is_player_friend(player) 931 | local pHandle = memory.alloc(104) 932 | NETWORK.NETWORK_HANDLE_FROM_PLAYER(player, pHandle, 13) 933 | local isFriend = NETWORK.NETWORK_IS_HANDLE_VALID(pHandle, 13) and NETWORK.NETWORK_IS_FRIEND(pHandle) 934 | return isFriend 935 | end 936 | 937 | 938 | ---@param player Player 939 | ---@return Vehicle 940 | function get_vehicle_player_is_in(player) 941 | local targetPed = PLAYER.GET_PLAYER_PED_SCRIPT_INDEX(player) 942 | if PED.IS_PED_IN_ANY_VEHICLE(targetPed, false) then 943 | return PED.GET_VEHICLE_PED_IS_IN(targetPed, false) 944 | end 945 | return 0 946 | end 947 | 948 | 949 | ---@param player Player 950 | ---@return Entity 951 | function get_entity_player_is_aiming_at(player) 952 | if not PLAYER.IS_PLAYER_FREE_AIMING(player) then 953 | return NULL 954 | end 955 | local entity, pEntity = NULL, memory.alloc_int() 956 | if PLAYER.GET_ENTITY_PLAYER_IS_FREE_AIMING_AT(player, pEntity) then 957 | entity = memory.read_int(pEntity) 958 | end 959 | if entity ~= NULL and ENTITY.IS_ENTITY_A_PED(entity) and PED.IS_PED_IN_ANY_VEHICLE(entity, false) then 960 | entity = PED.GET_VEHICLE_PED_IS_IN(entity, false) 961 | end 962 | return entity 963 | end 964 | 965 | 966 | ---@param entity Entity 967 | ---@return integer address 968 | function get_net_obj(entity) 969 | local pEntity = entities.handle_to_pointer(entity) 970 | return pEntity ~= NULL and memory.read_long(pEntity + 0xD0) or NULL 971 | end 972 | 973 | 974 | ---@param entity Entity 975 | ---@return Player owner 976 | function get_entity_owner(entity) 977 | local net_obj = get_net_obj(entity) 978 | return net_obj ~= NULL and memory.read_byte(net_obj + 0x49) or -1 979 | end 980 | 981 | 982 | ---@param player Player 983 | ---@return boolean 984 | function is_player_passive(player) 985 | if player ~= players.user() then 986 | local address = memory.script_global(1894573 + (player * 608 + 1) + 8) 987 | if address ~= NULL then return memory.read_byte(address) == 1 end 988 | else 989 | local address = memory.script_global(1574582) 990 | if address ~= NULL then return memory.read_int(address) == 1 end 991 | end 992 | return false 993 | end 994 | 995 | 996 | ---@param player Player 997 | ---@return boolean 998 | function is_player_in_any_interior(player) 999 | local address = memory.script_global(2657589 + (player * 466 + 1) + 245) 1000 | return address ~= NULL and memory.read_int(address) ~= 0 1001 | end 1002 | 1003 | 1004 | ---@param player Player 1005 | ---@return boolean 1006 | function is_player_in_interior(player) 1007 | if player == -1 then 1008 | return false 1009 | end 1010 | local bits = read_global.int(1853910 + (player * 862 + 1) + 267 + 31) 1011 | if (bits & (1 << 0)) ~= 0 then 1012 | return true 1013 | elseif (bits & (1 << 1)) ~= 0 then 1014 | return true 1015 | elseif read_global.int(2657589 + (player * 466 + 1) + 321 + 7) ~= -1 then 1016 | return true 1017 | end 1018 | return false 1019 | end 1020 | 1021 | 1022 | ---@param player Player 1023 | ---@return boolean 1024 | function is_player_in_rc_bandito(player) 1025 | if player ~= -1 then 1026 | local address = memory.script_global(1853910 + (player * 862 + 1) + 267 + 365) 1027 | return BitTest(memory.read_int(address), 29) 1028 | end 1029 | return false 1030 | end 1031 | 1032 | 1033 | ---@param player Player 1034 | ---@return boolean 1035 | function is_player_in_rc_tank(player) 1036 | if player ~= -1 then 1037 | local address = memory.script_global(1853910 + (player * 862 + 1) + 267 + 428 + 2) 1038 | return BitTest(memory.read_int(address), 16) 1039 | end 1040 | return false 1041 | end 1042 | 1043 | 1044 | ---@param player Player 1045 | ---@return boolean 1046 | function is_player_in_rc_personal_vehicle(player) 1047 | if player ~= -1 then 1048 | local address = memory.script_global(1853910 + (player * 862 + 1) + 267 + 428 + 3) 1049 | return BitTest(memory.read_int(address), 6) 1050 | end 1051 | return false 1052 | end 1053 | 1054 | 1055 | ---@param player Player 1056 | ---@return boolean 1057 | function is_player_in_any_rc_vehicle(player) 1058 | if is_player_in_rc_bandito(player) then 1059 | return true 1060 | end 1061 | 1062 | if is_player_in_rc_tank(player) then 1063 | return true 1064 | end 1065 | 1066 | if is_player_in_rc_personal_vehicle(player) then 1067 | return true 1068 | end 1069 | 1070 | return false 1071 | end 1072 | 1073 | 1074 | ---@diagnostic disable: exp-in-action, unknown-symbol, action-after-return, undefined-global 1075 | ---@param colour integer 1076 | ---@return integer 1077 | function get_hud_colour_from_org_colour(colour) 1078 | switch colour do 1079 | case 0: 1080 | return 192 1081 | case 1: 1082 | return 193 1083 | case 2: 1084 | return 194 1085 | case 3: 1086 | return 195 1087 | case 4: 1088 | return 196 1089 | case 5: 1090 | return 197 1091 | case 6: 1092 | return 198 1093 | case 7: 1094 | return 199 1095 | case 8: 1096 | return 200 1097 | case 9: 1098 | return 201 1099 | case 10: 1100 | return 202 1101 | case 11: 1102 | return 203 1103 | case 12: 1104 | return 204 1105 | case 13: 1106 | return 205 1107 | case 14: 1108 | return 206 1109 | end 1110 | return 1 1111 | end 1112 | 1113 | 1114 | ---@diagnostic enable: exp-in-action, unknown-symbol, action-after-return, undefined-global 1115 | ---@param player Player 1116 | ---@return integer 1117 | function get_player_org_blip_colour(player) 1118 | if players.get_boss(player) ~= -1 then 1119 | local hudColour = get_hud_colour_from_org_colour(players.get_org_colour(player)) 1120 | local rgba = get_hud_colour(hudColour) 1121 | return (rgba.r << 24) + (rgba.g << 16) + (rgba.b << 8) + rgba.a 1122 | end 1123 | return 0 1124 | end 1125 | 1126 | 1127 | ---@param player Player 1128 | ---@return string 1129 | function get_condensed_player_name(player) 1130 | local condensed = "" .. PLAYER.GET_PLAYER_NAME(player) .. "" 1131 | 1132 | if players.get_boss(player) ~= -1 then 1133 | local colour = players.get_org_colour(player) 1134 | local hudColour = get_hud_colour_from_org_colour(colour) 1135 | return string.format("~HC_%d~%s~s~", hudColour, condensed) 1136 | end 1137 | 1138 | return condensed 1139 | end 1140 | 1141 | 1142 | ---@param player Player 1143 | ---@param isPlaying boolean 1144 | ---@param inTransition boolean 1145 | ---@return boolean 1146 | function is_player_active(player, isPlaying, inTransition) 1147 | if player == -1 or 1148 | not NETWORK.NETWORK_IS_PLAYER_ACTIVE(player) then 1149 | return false 1150 | end 1151 | if isPlaying and not PLAYER.IS_PLAYER_PLAYING(player) then 1152 | return false 1153 | end 1154 | if inTransition and 1155 | read_global.int(2657589 + (player * 466 + 1)) ~= 4 then 1156 | return false 1157 | end 1158 | return true 1159 | end 1160 | 1161 | 1162 | -------------------------- 1163 | -- CAM 1164 | -------------------------- 1165 | 1166 | ---@param dist number 1167 | ---@return v3 1168 | function get_offset_from_cam(dist) 1169 | local rot = CAM.GET_FINAL_RENDERED_CAM_ROT(2) 1170 | local pos = CAM.GET_FINAL_RENDERED_CAM_COORD() 1171 | local dir = rot:toDir() 1172 | dir:mul(dist) 1173 | local offset = v3.new(pos) 1174 | offset:add(dir) 1175 | return offset 1176 | end 1177 | 1178 | -------------------------- 1179 | -- RAYCAST 1180 | -------------------------- 1181 | 1182 | TraceFlag = 1183 | { 1184 | everything = 4294967295, 1185 | none = 0, 1186 | world = 1, 1187 | vehicles = 2, 1188 | pedsSimpleCollision = 4, 1189 | peds = 8, 1190 | objects = 16, 1191 | water = 32, 1192 | foliage = 256, 1193 | } 1194 | 1195 | ---@class RaycastResult 1196 | ---@field didHit boolean 1197 | ---@field endCoords v3 1198 | ---@field surfaceNormal v3 1199 | ---@field hitEntity Entity 1200 | 1201 | ---@param dist number 1202 | ---@param flag? integer 1203 | ---@return RaycastResult 1204 | function get_raycast_result(dist, flag) 1205 | local result = {} 1206 | flag = flag or TraceFlag.everything 1207 | local didHit = memory.alloc(1) 1208 | local endCoords = v3.new() 1209 | local normal = v3.new() 1210 | local hitEntity = memory.alloc_int() 1211 | local camPos = CAM.GET_FINAL_RENDERED_CAM_COORD() 1212 | local offset = get_offset_from_cam(dist) 1213 | 1214 | local handle = SHAPETEST.START_EXPENSIVE_SYNCHRONOUS_SHAPE_TEST_LOS_PROBE( 1215 | camPos.x, camPos.y, camPos.z, 1216 | offset.x, offset.y, offset.z, 1217 | flag, 1218 | players.user_ped(), 7 1219 | ) 1220 | SHAPETEST.GET_SHAPE_TEST_RESULT(handle, didHit, endCoords, normal, hitEntity) 1221 | 1222 | result.didHit = memory.read_byte(didHit) ~= 0 1223 | result.endCoords = endCoords 1224 | result.surfaceNormal = normal 1225 | result.hitEntity = memory.read_int(hitEntity) 1226 | return result 1227 | end 1228 | 1229 | -------------------------- 1230 | -- STREAMING 1231 | -------------------------- 1232 | 1233 | ---@param model integer 1234 | function request_model(model) 1235 | STREAMING.REQUEST_MODEL(model) 1236 | while not STREAMING.HAS_MODEL_LOADED(model) do util.yield_once() end 1237 | end 1238 | 1239 | 1240 | ---@param asset string 1241 | function request_fx_asset(asset) 1242 | STREAMING.REQUEST_NAMED_PTFX_ASSET(asset) 1243 | while not STREAMING.HAS_NAMED_PTFX_ASSET_LOADED(asset) do util.yield_once() end 1244 | end 1245 | 1246 | 1247 | ---@param hash integer 1248 | function request_weapon_asset(hash) 1249 | WEAPON.REQUEST_WEAPON_ASSET(hash, 31, 0) 1250 | while not WEAPON.HAS_WEAPON_ASSET_LOADED(hash) do util.yield_once() end 1251 | end 1252 | 1253 | 1254 | ---Credits to aaron 1255 | ---@param textureDict string 1256 | function request_streamed_texture_dict(textureDict) 1257 | util.spoof_script("main_persistent", function() 1258 | GRAPHICS.REQUEST_STREAMED_TEXTURE_DICT(textureDict, false) 1259 | end) 1260 | end 1261 | 1262 | 1263 | ---@param textureDict string 1264 | function set_streamed_texture_dict_as_no_longer_needed(textureDict) 1265 | util.spoof_script("main_persistent", function() 1266 | GRAPHICS.SET_STREAMED_TEXTURE_DICT_AS_NO_LONGER_NEEDED(textureDict) 1267 | end) 1268 | end 1269 | 1270 | 1271 | ---@param name string 1272 | ---@return integer 1273 | function request_scaleform_movie(name) 1274 | local handle 1275 | util.spoof_script("main_persistent", function () 1276 | handle = GRAPHICS.REQUEST_SCALEFORM_MOVIE(name) 1277 | end) 1278 | return handle 1279 | end 1280 | 1281 | 1282 | ---@param handle integer 1283 | function set_scaleform_movie_as_no_longer_needed(handle) 1284 | util.spoof_script("main_persistent", function () 1285 | local ptr = memory.alloc_int() 1286 | memory.write_int(ptr, handle) 1287 | GRAPHICS.SET_SCALEFORM_MOVIE_AS_NO_LONGER_NEEDED(ptr) 1288 | end) 1289 | end 1290 | 1291 | -------------------------- 1292 | -- MEMORY 1293 | -------------------------- 1294 | 1295 | ---@param addr integer 1296 | ---@param offsets integer[] 1297 | ---@return integer 1298 | function addr_from_pointer_chain(addr, offsets) 1299 | if addr == 0 then return 0 end 1300 | for k = 1, (#offsets - 1) do 1301 | addr = memory.read_long(addr + offsets[k]) 1302 | if addr == 0 then return 0 end 1303 | end 1304 | addr = addr + offsets[#offsets] 1305 | return addr 1306 | end 1307 | 1308 | 1309 | write_global = { 1310 | byte = function(global, value) 1311 | local address = memory.script_global(global) 1312 | memory.write_byte(address, value) 1313 | end, 1314 | int = function(global, value) 1315 | local address = memory.script_global(global) 1316 | memory.write_int(address, value) 1317 | end, 1318 | float = function(global, value) 1319 | local address = memory.script_global(global) 1320 | memory.write_float(address, value) 1321 | end 1322 | } 1323 | 1324 | 1325 | read_global = { 1326 | byte = function(global) 1327 | local address = memory.script_global(global) 1328 | return memory.read_byte(address) 1329 | end, 1330 | int = function(global) 1331 | local address = memory.script_global(global) 1332 | return memory.read_int(address) 1333 | end, 1334 | float = function(global) 1335 | local address = memory.script_global(global) 1336 | return memory.read_float(address) 1337 | end, 1338 | string = function(global) 1339 | local address = memory.script_global(global) 1340 | return memory.read_string(address) 1341 | end 1342 | } 1343 | 1344 | 1345 | HudTimer = {} 1346 | 1347 | HudTimer.SetHeightMultThisFrame = function (mult) 1348 | write_global.int(1655472 + 1163, mult) 1349 | end 1350 | 1351 | HudTimer.DisableThisFrame = function() 1352 | write_global.int(2696211, 1) 1353 | end 1354 | 1355 | 1356 | function EnableOTR() 1357 | local toggle_addr = 2657589 + ((PLAYER.PLAYER_ID() * 466) + 1) + 210 1358 | if read_global.int(toggle_addr) == 1 then 1359 | return 1360 | end 1361 | write_global.int(toggle_addr, 1) 1362 | write_global.int(2672505 + 56, NETWORK.GET_NETWORK_TIME() + 1) 1363 | end 1364 | 1365 | function DisableOTR() 1366 | write_global.int(2657589 + ((PLAYER.PLAYER_ID() * 466) + 1) + 210, 0) 1367 | end 1368 | 1369 | function DisablePhone() 1370 | write_global.int(20366, 1) 1371 | end 1372 | 1373 | 1374 | function is_phone_open() 1375 | if SCRIPT.GET_NUMBER_OF_THREADS_RUNNING_THE_SCRIPT_WITH_THIS_HASH(util.joaat("cellphone_flashhand")) > 0 then 1376 | return true 1377 | end 1378 | return false 1379 | end 1380 | 1381 | 1382 | ---@param name string 1383 | ---@param pattern string 1384 | ---@param callback fun(address: integer) 1385 | function memory_scan(name, pattern, callback) 1386 | local address = memory.scan(pattern) 1387 | 1388 | if address == NULL then error("Failed to find " .. name) end 1389 | 1390 | callback(address) 1391 | util.log("Found %s", name) 1392 | end 1393 | 1394 | -------------------------- 1395 | -- TABLE 1396 | -------------------------- 1397 | 1398 | ---Returns a random value from the given table. 1399 | ---@param t table 1400 | ---@return any 1401 | function table.random(t) 1402 | if rawget(t, 1) ~= nil then 1403 | return t[ math.random(#t) ] 1404 | end 1405 | local list = {} 1406 | for _, value in pairs(t) do 1407 | table.insert(list, value) 1408 | end 1409 | local result = list[math.random(#list)] 1410 | return type(result) ~= "table" and result or table.random(result) 1411 | end 1412 | 1413 | 1414 | function pairs_by_keys(t, f) 1415 | local a = {} 1416 | for n in pairs(t) do table.insert(a, n) end 1417 | table.sort(a, f) 1418 | local i = 0 1419 | local iter = function() 1420 | i = i + 1 1421 | if a[i] == nil then return nil 1422 | else return a[i], t[a[i]] 1423 | end 1424 | end 1425 | return iter 1426 | end 1427 | 1428 | 1429 | ---Inserts `value` if `t` does not already includes it. 1430 | ---@param t table 1431 | ---@param value any 1432 | function table.insert_once(t, value) 1433 | if not table.find(t, value) then table.insert(t, value) end 1434 | end 1435 | 1436 | 1437 | ---@generic T: table, K, V 1438 | ---@param t T 1439 | ---@param f fun(key: K, value: V): boolean 1440 | ---@return V 1441 | ---@nodiscard 1442 | function table.find_if(t, f) 1443 | for k, v in pairs(t) do 1444 | if f(k, v) then return k end 1445 | end 1446 | return nil 1447 | end 1448 | 1449 | 1450 | ---@generic T: table, K, V 1451 | ---@param t T 1452 | ---@param value any 1453 | ---@return K? 1454 | ---@nodiscard 1455 | function table.find(t, value) 1456 | for k, v in pairs(t) do 1457 | if value == v then return k end 1458 | end 1459 | return nil 1460 | end 1461 | 1462 | 1463 | ---@generic T: table, K, V 1464 | ---@param t T 1465 | ---@param f fun(key: K, value: V):boolean 1466 | ---@return integer 1467 | function table.count_if(t, f) 1468 | local count = 0 1469 | for k, v in pairs(t) do 1470 | if f(k, v) then count = count + 1 end 1471 | end 1472 | return count 1473 | end 1474 | 1475 | -------------------------- 1476 | -- MISC 1477 | -------------------------- 1478 | 1479 | ---Credits to Sainan 1480 | function int_to_uint(int) 1481 | if int >= 0 then return int end 1482 | return (1 << 32) + int 1483 | end 1484 | 1485 | 1486 | function interpolate(y0, y1, perc) 1487 | perc = perc > 1.0 and 1.0 or perc 1488 | return (1 - perc) * y0 + perc * y1 1489 | end 1490 | 1491 | 1492 | ---@param num number 1493 | ---@param places? integer 1494 | ---@return number? 1495 | function round(num, places) 1496 | return tonumber(string.format('%.' .. (places or 0) .. 'f', num)) 1497 | end 1498 | 1499 | 1500 | ---@param blip integer 1501 | ---@return v3? 1502 | function get_blip_coords(blip) 1503 | if blip == 0 then 1504 | return nil 1505 | end 1506 | local pos = HUD.GET_BLIP_COORDS(blip) 1507 | local tick = 0 1508 | local success, groundz = util.get_ground_z(pos.x, pos.y) 1509 | while not success and tick < 10 do 1510 | util.yield_once() 1511 | success, groundz = util.get_ground_z(pos.x, pos.y) 1512 | tick = tick + 1 1513 | end 1514 | if success then pos.z = groundz end 1515 | return pos 1516 | end 1517 | 1518 | 1519 | ---@param pos v3 1520 | ---@return number? 1521 | function get_ground_z(pos) 1522 | local pGroundZ = memory.alloc(4) 1523 | MISC.GET_GROUND_Z_FOR_3D_COORD(pos.x, pos.y, pos.z, pGroundZ, false, true) 1524 | local groundz = memory.read_float(pGroundZ) 1525 | return groundz 1526 | end 1527 | 1528 | 1529 | ---@param windowName string #Must be a label 1530 | ---@param maxInput integer 1531 | ---@param defaultText string 1532 | ---@return string 1533 | function get_input_from_screen_keyboard(windowName, maxInput, defaultText) 1534 | MISC.DISPLAY_ONSCREEN_KEYBOARD(0, windowName, "", defaultText, "", "", "", maxInput); 1535 | while MISC.UPDATE_ONSCREEN_KEYBOARD() == 0 do 1536 | util.yield_once() 1537 | end 1538 | if MISC.UPDATE_ONSCREEN_KEYBOARD() == 1 then 1539 | return MISC.GET_ONSCREEN_KEYBOARD_RESULT() 1540 | end 1541 | return "" 1542 | end 1543 | 1544 | 1545 | ---@param s string 1546 | ---@param x number 1547 | ---@param y number 1548 | ---@param scale number 1549 | ---@param font integer 1550 | function draw_string(s, x, y, scale, font) 1551 | HUD.BEGIN_TEXT_COMMAND_DISPLAY_TEXT("STRING") 1552 | HUD.SET_TEXT_FONT(font or 0) 1553 | HUD.SET_TEXT_SCALE(scale, scale) 1554 | HUD.SET_TEXT_DROP_SHADOW() 1555 | HUD.SET_TEXT_WRAP(0.0, 1.0) 1556 | HUD.SET_TEXT_DROPSHADOW(1, 0, 0, 0, 0) 1557 | HUD.SET_TEXT_OUTLINE() 1558 | HUD.SET_TEXT_EDGE(1, 0, 0, 0, 0) 1559 | HUD.ADD_TEXT_COMPONENT_SUBSTRING_PLAYER_NAME(s) 1560 | HUD.END_TEXT_COMMAND_DISPLAY_TEXT(x, y, 0) 1561 | end 1562 | 1563 | 1564 | function capitalize(txt) 1565 | return tostring(txt):gsub('^%l', string.upper) 1566 | end 1567 | 1568 | 1569 | ---@param min number 1570 | ---@param max number 1571 | ---@return number 1572 | function random_float(min, max) 1573 | return min + math.random() * (max - min) 1574 | end 1575 | 1576 | 1577 | ---@param type integer 1578 | ---@param pos v3 1579 | ---@param scale number 1580 | ---@param colour Colour 1581 | ---@param textureDict string? 1582 | ---@param textureName string? 1583 | function draw_marker(type, pos, scale, colour, textureDict, textureName) 1584 | textureDict = textureDict or 0 1585 | textureName = textureName or 0 1586 | GRAPHICS.DRAW_MARKER( 1587 | type, 1588 | pos.x, pos.y, pos.z, 1589 | 0.0, 0.0, 0.0, 1590 | 0.0, 0.0, 0.0, 1591 | scale, scale, scale, 1592 | colour.r, colour.g, colour.b, colour.a, 1593 | false, false, 0, true, textureDict, textureName, false 1594 | ) 1595 | end 1596 | 1597 | 1598 | local orgLog = util.log 1599 | 1600 | ---@param format string 1601 | ---@param ... any 1602 | util.log = function (format, ...) 1603 | local strg = type(format) ~= "string" and tostring(format) or format:format(...) 1604 | orgLog("[WiriScript] " .. strg) 1605 | end 1606 | 1607 | function draw_debug_text(...) 1608 | local arg = {...} 1609 | local strg = "" 1610 | for _, w in ipairs(arg) do 1611 | strg = strg .. tostring(w) .. '\n' 1612 | end 1613 | local colour = {r = 1.0, g = 0.0, b = 0.0, a = 1.0} 1614 | directx.draw_text(0.05, 0.05, strg, ALIGN_TOP_LEFT, 1.0, colour, false) 1615 | end 1616 | 1617 | return self -------------------------------------------------------------------------------- /lib/wiriscript/guided_missile.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | -------------------------------- 3 | THIS FILE IS PART OF WIRISCRIPT 4 | Nowiry#2663 5 | -------------------------------- 6 | ]] 7 | 8 | require "wiriscript.functions" 9 | 10 | local self = {} 11 | local version = 29 12 | local MissileState = 13 | { 14 | nonExistent = -1, 15 | beingCreated = 0, 16 | onFlight = 1, 17 | exploting = 2, 18 | disconnecting = 3, 19 | beingDestroyed = 4 20 | } 21 | local BoundsState = 22 | { 23 | inBounds = 0, 24 | gettingOut = 1, 25 | outOfBounds = 2, 26 | } 27 | local state = MissileState.nonExistent 28 | local object 29 | local camera 30 | local blip 31 | local startPos 32 | local timer = newTimer() 33 | local fxAsset = "scr_xs_props" 34 | local objHash = util.joaat("xs_prop_arena_airmissile_01a") 35 | local scaleform = 0 36 | local effects = {missile_trail = -1} 37 | local sounds = 38 | { 39 | startUp = Sound.new("HUD_Startup", "DLC_Arena_Piloted_Missile_Sounds"), 40 | outOfBounds = Sound.new("Out_Of_Bounds_Alarm_Loop", "DLC_Arena_Piloted_Missile_Sounds"), 41 | staticLoop = Sound.new("HUD_Static_Loop", "DLC_Arena_Piloted_Missile_Sounds"), 42 | disconnect = Sound.new("HUD_Disconnect", "DLC_Arena_Piloted_Missile_Sounds") 43 | } 44 | 45 | 46 | self.exists = function () 47 | return state ~= MissileState.nonExistent 48 | end 49 | 50 | self.create = function () 51 | if not self.exists() then 52 | state = MissileState.beingCreated 53 | end 54 | end 55 | 56 | self.getVersion = function () 57 | return version 58 | end 59 | 60 | 61 | local function invertHeading(heading) 62 | if heading > 180.0 then 63 | return (heading - 180.0) 64 | end 65 | return (heading + 180.0) 66 | end 67 | 68 | local function currectRotation(value) 69 | if value > 180 then 70 | value = value - 360 71 | end 72 | if value < -180 then 73 | value = value + 360 74 | end 75 | return value 76 | end 77 | 78 | local function drawInstructionalButtons() 79 | if Instructional:begin() then 80 | Instructional.add_control_group(20, "DRONE_SPACE") 81 | Instructional.add_control_group(21, "DRONE_POSITION") 82 | if not PAD.IS_USING_KEYBOARD_AND_MOUSE(0) then 83 | Instructional.add_control(208, "DRONE_SPEEDU") 84 | Instructional.add_control(207, "DRONE_SLOWD") 85 | else 86 | Instructional.add_control(209, "DRONE_SPEEDU") 87 | Instructional.add_control(210, "DRONE_SLOWD") 88 | end 89 | Instructional.add_control(75, "MOVE_DRONE_RE") 90 | Instructional:set_background_colour(0, 0, 0, 80) 91 | Instructional:draw() 92 | end 93 | HUD.HIDE_HUD_COMPONENT_THIS_FRAME(6) 94 | HUD.HIDE_HUD_COMPONENT_THIS_FRAME(7) 95 | HUD.HIDE_HUD_COMPONENT_THIS_FRAME(9) 96 | HUD.HIDE_HUD_COMPONENT_THIS_FRAME(8) 97 | HudTimer.SetHeightMultThisFrame(1) 98 | end 99 | 100 | 101 | local function getScriptAxes() 102 | local leftX = PAD.GET_CONTROL_UNBOUND_NORMAL(0, 218) 103 | local leftY = PAD.GET_CONTROL_UNBOUND_NORMAL(0, 219) 104 | local rightX = PAD.GET_CONTROL_UNBOUND_NORMAL(0, 220) 105 | local rightY = PAD.GET_CONTROL_UNBOUND_NORMAL(0, 221) 106 | return leftX, leftY, rightX, rightY 107 | end 108 | 109 | 110 | local function setMissileRotation() 111 | local max = 40.0 112 | local mult = 1.0 113 | local axisX = 0.0 114 | local axisY = 0.0 115 | local pitch 116 | local roll 117 | local heading 118 | local frameTime = 30 * MISC.GET_FRAME_TIME() 119 | local entityRoll = ENTITY.GET_ENTITY_ROLL(object) 120 | local entityPitch = ENTITY.GET_ENTITY_PITCH(object) 121 | local leftX, leftY, rightX, rightY = getScriptAxes() 122 | 123 | if PAD.IS_USING_KEYBOARD_AND_MOUSE(0) then 124 | mult = 3.0 125 | rightX = rightX * mult 126 | rightY = rightY * mult 127 | end 128 | if PAD.IS_LOOK_INVERTED() then 129 | rightY = - rightY 130 | leftY = - leftY 131 | end 132 | if (rightX ~= 0 or rightY ~= 0) or (leftX ~= 0 or leftY ~= 0) then 133 | if rightX ~= 0 then 134 | axisX = rightX 135 | elseif leftX ~= 0 then 136 | axisX = leftX 137 | else 138 | axisX = 0 139 | end 140 | if rightY ~= 0 then 141 | axisY = rightY 142 | elseif leftY ~= 0 then 143 | axisY = leftY 144 | else 145 | axisY = 0 146 | end 147 | 148 | local entRot = ENTITY.GET_ENTITY_ROTATION(object, 2) 149 | heading = -(axisX * 0.05) * frameTime * 20 150 | pitch = (axisY * 0.05) * frameTime * 20 151 | 152 | if (entityRoll ~= 0 or rightX ~= 0) or leftX ~= 0 then 153 | if rightX ~= 0 then 154 | axisX = rightX 155 | roll = -(axisX * 0.05) * frameTime * (max - 25.0) 156 | elseif leftX ~= 0 then 157 | axisX = leftX 158 | roll = -(axisX * 0.05) * frameTime * (max - 25.0) 159 | else 160 | if entRot.y ~= 0 then 161 | if entRot.y < 0 then 162 | axisX = -1.0 163 | else 164 | axisX = 1.0 165 | end 166 | else 167 | axisX = 0.0 168 | end 169 | if entRot.y ~= 0 then 170 | if entRot.y < 2.0 and entRot.y > 0.0 then 171 | axisX = 0.0001 172 | elseif entRot.y > -2.0 and entRot.y < 0.0 then 173 | axisX = -0.0001 174 | end 175 | else 176 | axisX = 0.0 177 | end 178 | roll = -(axisX * 0.05) * frameTime * (max - 25) 179 | end 180 | else 181 | roll = 0.0 182 | end 183 | local rot = v3.new(pitch, roll, heading) 184 | rot:add(entRot) 185 | if rot.y > max then 186 | rot.y = max 187 | elseif rot.y < -max then 188 | rot.y = -max 189 | end 190 | if rot.x > 80 then 191 | rot.x = 80 192 | elseif rot.x < -max then 193 | rot.x = -max 194 | end 195 | ENTITY.SET_ENTITY_ROTATION(object, rot.x, rot.y, rot.z, 2, true) 196 | else 197 | local entRot = ENTITY.GET_ENTITY_ROTATION(object, 2) 198 | if entityRoll ~= 0 or entityPitch ~= 0 then 199 | if entRot.y ~= 0 then 200 | if entRot.y < 0 then 201 | axisX = -1.0 202 | else 203 | axisX = 1.0 204 | end 205 | else 206 | axisX = 0.0 207 | end 208 | if entRot.y ~= 0 then 209 | if entRot.y < 2.0 and entRot.y > 0.0 then 210 | axisX = 0.0001 211 | elseif entRot.y > -2.0 and entRot.y < 0.0 then 212 | axisX = -0.0001 213 | end 214 | else 215 | axisX = 0.0 216 | end 217 | if entRot.x ~= 0.0 then 218 | if entRot.x < 2.0 and entRot.x > 0.0 then 219 | axisY = 0.0001 220 | elseif entRot.x > -2.0 and entRot.x < 0.0 then 221 | axisY = -0.0001 222 | end 223 | else 224 | axisY = 0.0 225 | end 226 | heading = currectRotation(-(( (axisY * 0.05) * frameTime) * (max - 25))) 227 | roll = currectRotation(-(( (axisX * 0.05) * frameTime) * (max - 25))) 228 | local rot = v3.new(0.0, roll, heading) 229 | rot:add(entRot) 230 | ENTITY.SET_ENTITY_ROTATION(object, rot.x, rot.y, rot.z, 2, true) 231 | end 232 | end 233 | end 234 | 235 | 236 | local lowerLimit = 2500.0 237 | local upperLimit = 3000.0 238 | 239 | local getBoundsState = function () 240 | local pos = ENTITY.GET_ENTITY_COORDS(object, false) 241 | local distance = startPos:distance(pos) 242 | if distance > upperLimit then 243 | return BoundsState.outOfBounds 244 | elseif distance >= lowerLimit and distance < upperLimit then 245 | return BoundsState.gettingOut 246 | end 247 | return BoundsState.inBounds 248 | end 249 | 250 | 251 | ---@param heading number 252 | local function drawMissileScaleformMovie(heading) 253 | if not GRAPHICS.HAS_SCALEFORM_MOVIE_LOADED(scaleform) then 254 | scaleform = request_scaleform_movie("SUBMARINE_MISSILES") 255 | else 256 | GRAPHICS.BEGIN_SCALEFORM_MOVIE_METHOD(scaleform, "SET_ZOOM_VISIBLE") 257 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_BOOL(false) 258 | GRAPHICS.END_SCALEFORM_MOVIE_METHOD() 259 | 260 | GRAPHICS.BEGIN_SCALEFORM_MOVIE_METHOD(scaleform, "SET_ALT_FOV_HEADING") 261 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_FLOAT(0.0) 262 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_FLOAT(0.0) 263 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_FLOAT(heading) 264 | GRAPHICS.END_SCALEFORM_MOVIE_METHOD() 265 | 266 | local boundsState = getBoundsState() 267 | if boundsState == BoundsState.gettingOut then 268 | sounds.outOfBounds:play() 269 | GRAPHICS.BEGIN_SCALEFORM_MOVIE_METHOD(scaleform, "SET_WARNING_IS_VISIBLE") 270 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_BOOL(true) 271 | GRAPHICS.END_SCALEFORM_MOVIE_METHOD() 272 | 273 | GRAPHICS.BEGIN_SCALEFORM_MOVIE_METHOD(scaleform, "SET_WARNING_FLASH_RATE") 274 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_FLOAT(0.5) 275 | GRAPHICS.END_SCALEFORM_MOVIE_METHOD() 276 | elseif boundsState == BoundsState.inBounds then 277 | sounds.outOfBounds:stop() 278 | GRAPHICS.BEGIN_SCALEFORM_MOVIE_METHOD(scaleform, "SET_WARNING_IS_VISIBLE") 279 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_BOOL(false) 280 | GRAPHICS.END_SCALEFORM_MOVIE_METHOD() 281 | end 282 | 283 | GRAPHICS.DRAW_SCALEFORM_MOVIE_FULLSCREEN(scaleform, 255, 255, 255, 255, 1) 284 | end 285 | end 286 | 287 | 288 | local destroy = function () 289 | for _, sound in pairs(sounds) do 290 | sound:stop() 291 | end 292 | if GRAPHICS.DOES_PARTICLE_FX_LOOPED_EXIST(effects.missile_trail) then 293 | GRAPHICS.STOP_PARTICLE_FX_LOOPED(effects.missile_trail, false) 294 | STREAMING.REMOVE_NAMED_PTFX_ASSET(fxAsset) 295 | end 296 | AUDIO.STOP_AUDIO_SCENE("dlc_aw_arena_piloted_missile_scene") 297 | set_scaleform_movie_as_no_longer_needed(scaleform) 298 | 299 | if HUD.DOES_BLIP_EXIST(blip) then 300 | util.remove_blip(blip) 301 | blip = 0 302 | end 303 | 304 | if CAM.DOES_CAM_EXIST(camera) then 305 | CAM.RENDER_SCRIPT_CAMS(false, false, 0, true, false, 0) 306 | CAM.SET_CAM_ACTIVE(camera, false) 307 | CAM.DESTROY_CAM(camera, false) 308 | end 309 | 310 | STREAMING.CLEAR_FOCUS() 311 | GRAPHICS.CLEAR_TIMECYCLE_MODIFIER() 312 | entities.delete_by_handle(object) 313 | HUD.UNLOCK_MINIMAP_ANGLE() 314 | HUD.UNLOCK_MINIMAP_POSITION() 315 | ENTITY.FREEZE_ENTITY_POSITION(players.user_ped(), false) 316 | end 317 | 318 | 319 | local function disableControlActions() 320 | for i = 1, 6 do 321 | PAD.DISABLE_CONTROL_ACTION(0, i, true) 322 | end 323 | PAD.DISABLE_CONTROL_ACTION(0, 71, true) 324 | PAD.DISABLE_CONTROL_ACTION(0, 72, true) 325 | PAD.DISABLE_CONTROL_ACTION(0, 63, true) 326 | PAD.DISABLE_CONTROL_ACTION(0, 64, true) 327 | PAD.DISABLE_CONTROL_ACTION(0, 87, true) 328 | PAD.DISABLE_CONTROL_ACTION(0, 88, true) 329 | PAD.DISABLE_CONTROL_ACTION(0, 89, true) 330 | PAD.DISABLE_CONTROL_ACTION(0, 90, true) 331 | PAD.DISABLE_CONTROL_ACTION(0, 129, true) 332 | PAD.DISABLE_CONTROL_ACTION(0, 130, true) 333 | PAD.DISABLE_CONTROL_ACTION(0, 133, true) 334 | PAD.DISABLE_CONTROL_ACTION(0, 134, true) 335 | PAD.DISABLE_CONTROL_ACTION(0, 75, true) 336 | end 337 | 338 | 339 | self.mainLoop = function () 340 | if state == MissileState.beingCreated then 341 | ENTITY.FREEZE_ENTITY_POSITION(PLAYER.PLAYER_PED_ID(), true) 342 | request_model(objHash) 343 | local coords = ENTITY.GET_ENTITY_COORDS(PLAYER.PLAYER_PED_ID(), false) 344 | object = entities.create_object(objHash, coords) 345 | ENTITY.SET_ENTITY_HEADING(object, invertHeading(CAM.GET_GAMEPLAY_CAM_ROT(0).z)) 346 | ENTITY.SET_ENTITY_AS_MISSION_ENTITY(object, false, true) 347 | ENTITY.SET_ENTITY_INVINCIBLE(object, true) 348 | NETWORK.SET_NETWORK_ID_ALWAYS_EXISTS_FOR_PLAYER(NETWORK.OBJ_TO_NET(object), PLAYER.PLAYER_ID(), true) 349 | ENTITY.SET_ENTITY_LOAD_COLLISION_FLAG(object, true, 1) 350 | NETWORK.SET_NETWORK_ID_EXISTS_ON_ALL_MACHINES(NETWORK.OBJ_TO_NET(object), true); 351 | ENTITY.SET_ENTITY_LOD_DIST(object, 700) 352 | NETWORK.SET_NETWORK_ID_CAN_MIGRATE(NETWORK.OBJ_TO_NET(object), false) 353 | ENTITY.SET_ENTITY_RECORDS_COLLISIONS(object, true) 354 | ENTITY.SET_ENTITY_COORDS_NO_OFFSET(object, coords.x, coords.y, coords.z + 5.0, false, false, true) 355 | ENTITY.SET_ENTITY_HAS_GRAVITY(object, false) 356 | STREAMING.SET_MODEL_AS_NO_LONGER_NEEDED(objHash) 357 | 358 | camera = CAM.CREATE_CAM("DEFAULT_SCRIPTED_CAMERA", true) 359 | CAM.SET_CAM_FOV(camera, 80.0) 360 | CAM.SET_CAM_NEAR_CLIP(camera, 0.01) 361 | CAM.SET_CAM_NEAR_DOF(camera, 0.01) 362 | GRAPHICS.CLEAR_TIMECYCLE_MODIFIER() 363 | GRAPHICS.SET_TIMECYCLE_MODIFIER("eyeinthesky") 364 | CAM.HARD_ATTACH_CAM_TO_ENTITY(camera, object, 0.0, 0.0, 180.0, 0.0, -0.9, 0.0, true) 365 | CAM.RENDER_SCRIPT_CAMS(true, false, 0, true, true, 0) 366 | 367 | if not AUDIO.IS_AUDIO_SCENE_ACTIVE("dlc_aw_arena_piloted_missile_scene") then 368 | AUDIO.START_AUDIO_SCENE("dlc_aw_arena_piloted_missile_scene") 369 | end 370 | 371 | sounds.startUp:play() 372 | request_fx_asset(fxAsset) 373 | GRAPHICS.USE_PARTICLE_FX_ASSET(fxAsset) 374 | effects.missile_trail = GRAPHICS.START_NETWORKED_PARTICLE_FX_LOOPED_ON_ENTITY( 375 | "scr_xs_guided_missile_trail", object, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, false, false, false, 0, 0, 0, 0 376 | ) 377 | 378 | blip = HUD.ADD_BLIP_FOR_COORD(coords.x, coords.y, coords.z) 379 | HUD.SET_BLIP_SCALE(blip, 1.0) 380 | HUD.SET_BLIP_ROUTE(blip, false) 381 | HUD.SET_BLIP_SPRITE(blip, 548) 382 | HUD.SET_BLIP_COLOUR(blip, get_player_org_blip_colour(players.user())) 383 | startPos = coords 384 | 385 | if not GRAPHICS.HAS_SCALEFORM_MOVIE_LOADED(scaleform) then 386 | scaleform = request_scaleform_movie("SUBMARINE_MISSILES") 387 | end 388 | state = MissileState.onFlight 389 | elseif state == MissileState.onFlight then 390 | local forceMag 391 | local accelerating = false 392 | local decelerating = false 393 | local coords = ENTITY.GET_ENTITY_COORDS(object, true) 394 | local velocity = ENTITY.GET_ENTITY_VELOCITY(object) 395 | local rotation = CAM.GET_CAM_ROT(camera, 2) 396 | local heading = invertHeading( ENTITY.GET_ENTITY_HEADING(object) ) 397 | local direction = rotation:toDir() 398 | 399 | DisablePhone() 400 | disableControlActions() 401 | HUD.SET_BLIP_DISPLAY(blip, 2) 402 | HUD.SET_BLIP_COORDS(blip, coords.x, coords.y, coords.z) 403 | HUD.LOCK_MINIMAP_POSITION(coords.x, coords.y) 404 | HUD.SET_BLIP_ROTATION(blip, math.ceil(heading)) 405 | HUD.SET_BLIP_PRIORITY(blip, 9) 406 | HUD.LOCK_MINIMAP_ANGLE(math.ceil(heading)) 407 | 408 | if NETWORK.NETWORK_HAS_CONTROL_OF_NETWORK_ID(NETWORK.OBJ_TO_NET(object)) then 409 | if ENTITY.HAS_ENTITY_COLLIDED_WITH_ANYTHING(object) or ENTITY.GET_LAST_MATERIAL_HIT_BY_ENTITY(object) ~= 0 or 410 | ENTITY.IS_ENTITY_IN_WATER(object) or PAD.IS_DISABLED_CONTROL_JUST_PRESSED(0, 75) or 411 | getBoundsState() == BoundsState.outOfBounds then 412 | state = MissileState.exploting 413 | end 414 | if not PAD.IS_USING_KEYBOARD_AND_MOUSE(0) then 415 | if PAD.GET_CONTROL_UNBOUND_NORMAL(0, 208) ~= 0 then 416 | accelerating = true 417 | end 418 | if PAD.GET_CONTROL_UNBOUND_NORMAL(0, 207) ~= 0 then 419 | decelerating = true 420 | end 421 | else 422 | if PAD.GET_CONTROL_UNBOUND_NORMAL(0, 209) ~= 0 then 423 | accelerating = true 424 | end 425 | if PAD.GET_CONTROL_UNBOUND_NORMAL(0, 210) ~= 0 then 426 | decelerating = true 427 | end 428 | end 429 | 430 | if accelerating then 431 | forceMag = 150.0 432 | elseif decelerating then 433 | forceMag = 50.0 434 | else 435 | forceMag = 100.0 436 | end 437 | 438 | local force = v3.new(direction) 439 | force:mul(forceMag) 440 | ENTITY.APPLY_FORCE_TO_ENTITY_CENTER_OF_MASS(object, 1, force.x, force.y, force.z, false, false, false, false) 441 | setMissileRotation() 442 | STREAMING.SET_FOCUS_POS_AND_VEL(coords.x, coords.y, coords.z, velocity.x, velocity.y, velocity.z) 443 | if MISC.GET_FRAME_COUNT() % 120 == 0 then 444 | PED.SET_SCENARIO_PEDS_SPAWN_IN_SPHERE_AREA(coords.x, coords.y, coords.z, 60.0, 30) 445 | end 446 | 447 | local myPos = ENTITY.GET_ENTITY_COORDS(players.user_ped(), false) 448 | STREAMING.REQUEST_COLLISION_AT_COORD(myPos.x, myPos.y, myPos.z) 449 | 450 | drawMissileScaleformMovie(rotation.z) 451 | drawInstructionalButtons() 452 | else 453 | NETWORK.NETWORK_REQUEST_CONTROL_OF_ENTITY(object) 454 | end 455 | elseif state == MissileState.exploting then 456 | local coord = CAM.GET_CAM_COORD(camera) 457 | FIRE.ADD_EXPLOSION(coord.x, coord.y, coord.z, 81, 5.0, true, false, 1.0, false) 458 | PAD.SET_CONTROL_SHAKE(0, 300, 200) 459 | NETWORK.NETWORK_FADE_OUT_ENTITY(object, false, true) 460 | sounds.startUp:stop() 461 | sounds.outOfBounds:stop() 462 | 463 | if GRAPHICS.DOES_PARTICLE_FX_LOOPED_EXIST(effects.missile_trail) then 464 | GRAPHICS.STOP_PARTICLE_FX_LOOPED(effects.missile_trail, false) 465 | STREAMING.REMOVE_NAMED_PTFX_ASSET(fxAsset) 466 | end 467 | if HUD.DOES_BLIP_EXIST(blip) then 468 | util.remove_blip(blip) 469 | end 470 | 471 | timer.reset() 472 | sounds.staticLoop:play() 473 | GRAPHICS.SET_TIMECYCLE_MODIFIER("MissileOutOfRange") 474 | state = MissileState.disconnecting 475 | elseif state == MissileState.disconnecting then 476 | if timer.elapsed() >= 1000 then 477 | sounds.staticLoop:stop() 478 | CAM.DESTROY_ALL_CAMS(true) 479 | CAM.DESTROY_CAM(camera, false) 480 | CAM.RENDER_SCRIPT_CAMS(false, false, 0, true, false, 0) 481 | STREAMING.CLEAR_FOCUS() 482 | 483 | timer.reset() 484 | sounds.disconnect:play() 485 | state = MissileState.beingDestroyed 486 | end 487 | elseif state == MissileState.beingDestroyed then 488 | if timer.elapsed() >= 500 then 489 | destroy() 490 | state = MissileState.nonExistent 491 | end 492 | end 493 | end 494 | 495 | 496 | self.destroy = function () 497 | if not self.exists() then 498 | return 499 | end 500 | destroy() 501 | state = MissileState.nonExistent 502 | end 503 | 504 | 505 | return self -------------------------------------------------------------------------------- /lib/wiriscript/homing_missiles.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | -------------------------------- 3 | THIS FILE IS PART OF WIRISCRIPT 4 | Nowiry#2663 5 | -------------------------------- 6 | ]] 7 | 8 | ---@diagnostic disable: exp-in-action, unknown-symbol, break-outside, undefined-global 9 | require "wiriscript.functions" 10 | 11 | -------------------------- 12 | -- BITFIELD 13 | -------------------------- 14 | 15 | ---@class Bitwise 16 | ---@field bits integer 17 | local Bitfield = {} 18 | Bitfield.__index = Bitfield 19 | 20 | function Bitfield.new() 21 | return setmetatable({bits = 0}, Bitfield) 22 | end 23 | 24 | ---@param place integer 25 | ---@return boolean 26 | function Bitfield:IsBitSet(place) 27 | return self.bits & (1 << place) ~= 0 28 | end 29 | 30 | ---@param place integer 31 | function Bitfield:SetBit(place) 32 | self.bits = self.bits | (1 << place) 33 | end 34 | 35 | ---@param place integer 36 | function Bitfield:ClearBit(place) 37 | self.bits = self.bits & ~(1 << place) 38 | end 39 | 40 | ---@param place integer 41 | ---@param on boolean 42 | function Bitfield:ToggleBit(place, on) 43 | if on then self:SetBit(place) else self:ClearBit(place) end 44 | end 45 | 46 | function Bitfield:reset() 47 | self.bits = 0 48 | end 49 | 50 | Bitfield.__tostring = function(self) 51 | local tbl = {} 52 | local num = self.bits 53 | for b = 32, 1, -1 do 54 | tbl[b] = math.fmod(num, 2) 55 | num = math.floor((num - tbl[b]) / 2) 56 | end 57 | return table.concat(tbl) 58 | end 59 | 60 | -------------------------- 61 | 62 | local self = {} 63 | local version = 29 64 | local State = 65 | { 66 | GettingNearbyEnts = 0, 67 | SettingTargets = 1, 68 | Reseted = 2 69 | } 70 | local state = State.Reseted 71 | local targetEnts = {-1, -1, -1, -1, -1, -1} 72 | ---Stores nearby targetable entities 73 | ---@type integer[] 74 | local nearbyEntities = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1} 75 | local numTargets = 0 76 | local maxTargets = 6 77 | local lastShot = newTimer() 78 | local rechargeTimer = newTimer() 79 | local entCount = 0 80 | local shotCount = 0 81 | local numShotTargets = 0 -- the number of targets we've shot 82 | local chargeLevel = 100.0 83 | local vehicleWeaponSide = 0 84 | local myVehicle = 0 85 | local weapon = util.joaat("VEHICLE_WEAPON_SPACE_ROCKET") 86 | local lockOnBits = Bitfield.new() 87 | local bits = Bitfield.new() 88 | local whiteList = Bitfield.new() 89 | local trans = { 90 | DisablingPassive = translate("Misc", "Disabling passive mode") 91 | } 92 | local NULL = 0 93 | ---@type Timer[] 94 | local homingTimers = {} 95 | ---@type Sound[] 96 | local amberHomingSounds = {} 97 | ---@type Sound[] 98 | local redHomingSounds = {} 99 | ---@type Timer[] 100 | local lostTargetTimers = {} 101 | 102 | for i = 1, 6 do 103 | homingTimers[i] = newTimer() 104 | lostTargetTimers[i] = newTimer() 105 | amberHomingSounds[i] = Sound.new("VULKAN_LOCK_ON_AMBER", NULL) 106 | redHomingSounds[i] = Sound.new("VULKAN_LOCK_ON_RED", NULL) 107 | end 108 | 109 | local Bit_IsTargetShooting = 0 110 | local Bit_IsRecharging = 1 111 | local Bit_IsCamPointingInFront = 2 112 | 113 | local Bit_IgnoreFriends = 0 114 | local Bit_IgnoreOrgMembers = 1 115 | local Bit_IgnoreCrewMembers = 2 116 | 117 | 118 | ---@param position v3 119 | ---@param scale number 120 | ---@param colour Colour 121 | local DrawLockonSprite = function (position, scale, colour) 122 | if GRAPHICS.HAS_STREAMED_TEXTURE_DICT_LOADED("mpsubmarine_periscope") then 123 | local txdSizeX = scale * 0.042 124 | local txdSizeY = scale * 0.042 * GRAPHICS.GET_ASPECT_RATIO(false) 125 | GRAPHICS.SET_DRAW_ORIGIN(position.x, position.y, position.z, 0) 126 | GRAPHICS.DRAW_SPRITE( 127 | "mpsubmarine_periscope", "target_default", 0.0, 0.0, txdSizeX, txdSizeY, 0.0, colour.r, colour.g, colour.b, colour.a, true, 0) 128 | GRAPHICS.CLEAR_DRAW_ORIGIN() 129 | end 130 | end 131 | 132 | 133 | ---@param vehicle Vehicle 134 | ---@return boolean 135 | local IsAnyPoliceVehicle = function(vehicle) 136 | local modelHash = ENTITY.GET_ENTITY_MODEL(vehicle) 137 | switch int_to_uint(modelHash)do 138 | case 0x79FBB0C5: 139 | case 0x9F05F101: 140 | case 0x71FA16EA: 141 | case 0x8A63C7B9: 142 | case 0x1517D4D9: 143 | case 0xFDEFAEC3: 144 | case 0x1B38E955: 145 | case 0x95F4C618: 146 | case 0xA46462F7: 147 | case 0x9BAA707C: 148 | case 0x72935408: 149 | case 0xB822A1AA: 150 | case 0xE2E7D4AB: 151 | case 0x9DC66994: 152 | return true 153 | end 154 | return false 155 | end 156 | 157 | 158 | ---@param entity Entity 159 | ---@return boolean 160 | local IsEntityInSafeScreenPos = function (entity) 161 | local pScreenX = memory.alloc(4) 162 | local pScreenY = memory.alloc(4) 163 | local pos = ENTITY.GET_ENTITY_COORDS(entity, true) 164 | if not GRAPHICS.GET_SCREEN_COORD_FROM_WORLD_COORD(pos.x, pos.y, pos.z, pScreenX, pScreenY) then 165 | return false 166 | end 167 | local screenX = memory.read_float(pScreenX) 168 | local screenY = memory.read_float(pScreenY) 169 | if screenX < 0.1 or screenX > 0.9 or screenY < 0.1 or screenY > 0.9 then 170 | return false 171 | end 172 | return true 173 | end 174 | 175 | 176 | ---@param player Player 177 | ---@return integer 178 | local GetPlayerOrgBoss = function (player) 179 | if player ~= -1 then 180 | local address = memory.script_global(1894573 + (player * 608 + 1) + 10) 181 | if address ~= 0 then return memory.read_int(address) end 182 | end 183 | return -1 184 | end 185 | 186 | 187 | ---@param player Player 188 | ---@param target Player 189 | ---@return boolean 190 | local ArePlayersInTheSameOrg = function (player, target) 191 | local boss = GetPlayerOrgBoss(player) 192 | return boss ~= -1 and boss == GetPlayerOrgBoss(target) 193 | end 194 | 195 | 196 | ---@param player Player 197 | ---@return integer 198 | local GetHandleFromPlayer = function (player) 199 | local handle = memory.alloc(104) 200 | NETWORK.NETWORK_HANDLE_FROM_PLAYER(player, handle, 13) 201 | return handle 202 | end 203 | 204 | 205 | ---@param player Player 206 | ---@param target Player 207 | ---@return boolean 208 | local ArePlayersInTheSameCrew = function (player, target) 209 | if NETWORK.NETWORK_CLAN_SERVICE_IS_VALID() then 210 | local targetHandle = GetHandleFromPlayer(target) 211 | local handle = GetHandleFromPlayer(player) 212 | 213 | if NETWORK.NETWORK_CLAN_PLAYER_IS_ACTIVE(handle) and NETWORK.NETWORK_CLAN_PLAYER_IS_ACTIVE(targetHandle) then 214 | local targetClanDesc = memory.alloc(280) 215 | local clanDesc = memory.alloc(280) 216 | 217 | NETWORK.NETWORK_CLAN_PLAYER_GET_DESC(clanDesc, 35, handle) 218 | NETWORK.NETWORK_CLAN_PLAYER_GET_DESC(targetClanDesc, 35, targetHandle) 219 | return memory.read_int(clanDesc + 0x0) == memory.read_int(targetClanDesc + 0x0) 220 | end 221 | end 222 | return false 223 | end 224 | 225 | 226 | ---@param ped Ped 227 | local IsPedAnyTargetablePlayer = function (ped) 228 | local player = NETWORK.NETWORK_GET_PLAYER_INDEX_FROM_PED(ped) 229 | if not is_player_active(player, true, true) then 230 | return false 231 | end 232 | 233 | if is_player_in_interior(player) or is_player_passive(player) or 234 | NETWORK.IS_ENTITY_A_GHOST(ped) then 235 | return false 236 | elseif whiteList:IsBitSet(Bit_IgnoreFriends) and is_player_friend(player) then 237 | return false 238 | elseif whiteList:IsBitSet(Bit_IgnoreOrgMembers) and 239 | ArePlayersInTheSameOrg(players.user(), player) then 240 | return false 241 | elseif whiteList:IsBitSet(Bit_IgnoreCrewMembers) and 242 | ArePlayersInTheSameCrew(players.user(), player) then 243 | return false 244 | end 245 | return true 246 | end 247 | 248 | 249 | ---@param vehicle Vehicle 250 | ---@return boolean 251 | local DoesVehicleHavePlayerDriver = function(vehicle) 252 | if VEHICLE.IS_VEHICLE_SEAT_FREE(vehicle, -1, false) then 253 | return false 254 | end 255 | local driver = VEHICLE.GET_PED_IN_VEHICLE_SEAT(vehicle, -1, false) 256 | if not ENTITY.DOES_ENTITY_EXIST(driver) or not PED.IS_PED_A_PLAYER(driver) or 257 | not IsPedAnyTargetablePlayer(driver) then 258 | return false 259 | end 260 | return true 261 | end 262 | 263 | 264 | ---@param player Player 265 | ---@return integer 266 | local GetPlayerWantedLevel = function (player) 267 | local netPlayer = GetNetGamePlayer(player) 268 | if netPlayer == NULL then 269 | return 0 270 | end 271 | local playerInfo = memory.read_long(netPlayer + 0xA0) 272 | if playerInfo == NULL then 273 | return 0 274 | end 275 | return memory.read_uint(playerInfo + 0x0888) 276 | end 277 | 278 | 279 | ---@param entity Entity 280 | ---@return boolean 281 | local IsEntityTargetable = function(entity) 282 | if not ENTITY.DOES_ENTITY_EXIST(entity) or ENTITY.IS_ENTITY_DEAD(entity, false) then 283 | return false 284 | end 285 | local distance = get_distance_between_entities(myVehicle, entity) 286 | if distance > 500.0 or distance < 10.0 then 287 | return false 288 | end 289 | if ENTITY.IS_ENTITY_A_PED(entity) and PED.IS_PED_A_PLAYER(entity) and 290 | players.user_ped() ~= entity and not PED.IS_PED_IN_ANY_VEHICLE(entity, false) and 291 | IsPedAnyTargetablePlayer(entity) then 292 | return true 293 | elseif ENTITY.IS_ENTITY_A_VEHICLE(entity) and entity ~= myVehicle then 294 | if DoesVehicleHavePlayerDriver(entity) then 295 | return true 296 | elseif GetPlayerWantedLevel(players.user()) > 0 and IsAnyPoliceVehicle(entity) then 297 | return true 298 | end 299 | end 300 | return false 301 | end 302 | 303 | 304 | local SetNearbyEntities = function() 305 | local count = 1 306 | local entities = entities.get_all_vehicles_as_handles() 307 | for _, player in ipairs(players.list(false)) do 308 | entities[#entities+1] = PLAYER.GET_PLAYER_PED_SCRIPT_INDEX(player) 309 | end 310 | for _, entity in ipairs(entities) do 311 | if count == 20 then break end 312 | if bits:IsBitSet(Bit_IsCamPointingInFront) and IsEntityTargetable(entity) and 313 | not table.find(targetEnts, entity) and not table.find(nearbyEntities, entity) and 314 | IsEntityInSafeScreenPos(entity) then 315 | nearbyEntities[count] = entity 316 | count = count + 1 317 | end 318 | end 319 | state = State.SettingTargets 320 | end 321 | 322 | 323 | ---@param entity Entity 324 | ---@return boolean 325 | local TargetEntitiesInsert = function (entity) 326 | for i, target in ipairs(targetEnts) do 327 | if target == -1 or not ENTITY.DOES_ENTITY_EXIST(target) then 328 | targetEnts[i] = entity 329 | numTargets = numTargets + 1 330 | return true 331 | end 332 | end 333 | return false 334 | end 335 | 336 | 337 | ---@return integer 338 | local GetFartherTargetIndex = function() 339 | local lastDistance = 0.0 340 | local index = -1 341 | local myPos = ENTITY.GET_ENTITY_COORDS(players.user_ped(), true) 342 | for i = 1, maxTargets do 343 | local pos = ENTITY.GET_ENTITY_COORDS(targetEnts[i], true) 344 | local distance = myPos:distance(pos) 345 | if distance > lastDistance then 346 | index = i 347 | lastDistance = distance 348 | end 349 | end 350 | return index 351 | end 352 | 353 | 354 | ---@param entity Entity 355 | ---@param amplitude number 356 | ---@return boolean 357 | local IsCameraPointingInFrontOfEntity = function(entity, amplitude) 358 | local camDir = CAM.GET_GAMEPLAY_CAM_ROT(0):toDir() 359 | local fwdVector = ENTITY.GET_ENTITY_FORWARD_VECTOR(entity) 360 | camDir.z, fwdVector.z = 0.0, 0.0 361 | local angle = math.acos(fwdVector:dot(camDir) / (#camDir * #fwdVector)) 362 | return math.deg(angle) < amplitude 363 | end 364 | 365 | 366 | local SetTargetEntities = function() 367 | if entCount < 0 or entCount > 19 then 368 | entCount = 0 369 | end 370 | local entity = nearbyEntities[entCount + 1] 371 | 372 | if ENTITY.DOES_ENTITY_EXIST(entity) and not ENTITY.IS_ENTITY_DEAD(entity, false) and 373 | ENTITY.HAS_ENTITY_CLEAR_LOS_TO_ENTITY(myVehicle, entity, 287) then 374 | if numTargets < maxTargets then 375 | if TargetEntitiesInsert(entity) then 376 | nearbyEntities[entCount + 1] = -1 377 | entCount = entCount + 1 378 | end 379 | else 380 | local targetId = GetFartherTargetIndex() 381 | local target = targetEnts[targetId] 382 | 383 | if targetId >= 1 and target then 384 | local entityPos = ENTITY.GET_ENTITY_COORDS(entity, true) 385 | local myPos = ENTITY.GET_ENTITY_COORDS(players.user_ped(), true) 386 | local targetPos = ENTITY.GET_ENTITY_COORDS(target, true) 387 | local targetDist = targetPos:distance(myPos) 388 | local entDist = entityPos:distance(myPos) 389 | if targetDist > entDist then targetEnts[targetId] = entity end 390 | end 391 | 392 | nearbyEntities[entCount + 1] = -1 393 | entCount = entCount + 1 394 | end 395 | else 396 | nearbyEntities[entCount + 1] = -1 397 | entCount = entCount + 1 398 | end 399 | 400 | if entCount > 19 then 401 | state = State.GettingNearbyEnts 402 | entCount = 0 403 | end 404 | end 405 | 406 | 407 | local IsAnyHomingSoundActive = function() 408 | for i = 1, 6 do 409 | local amberSound = amberHomingSounds[i] 410 | local redSound = redHomingSounds[i] 411 | if not amberSound:hasFinished() or not redSound:hasFinished() then 412 | return true 413 | end 414 | end 415 | return false 416 | end 417 | 418 | 419 | local IsWebBrowserOpen = function () 420 | return read_global.int(75693) ~= 0 421 | end 422 | 423 | local IsCameraAppOpen = function () 424 | return SCRIPT.GET_NUMBER_OF_THREADS_RUNNING_THE_SCRIPT_WITH_THIS_HASH(util.joaat("appcamera")) > 0 425 | end 426 | 427 | 428 | ---@param entity Entity 429 | ---@param count integer 430 | local LockonEntity = function (entity, count) 431 | local redSound = redHomingSounds[count] 432 | local bitPlace = count - 1 433 | local lockOnTimer = homingTimers[count] 434 | local amberSound = amberHomingSounds[count] 435 | 436 | if not ENTITY.DOES_ENTITY_EXIST(entity) or ENTITY.IS_ENTITY_DEAD(entity, false) or 437 | not IsEntityInSafeScreenPos(entity) or (IsWebBrowserOpen() or IsCameraAppOpen()) then 438 | amberSound:stop() 439 | lockOnBits:ClearBit(bitPlace) 440 | redSound:stop() 441 | lockOnBits:ClearBit(bitPlace + 6) 442 | return 443 | end 444 | 445 | if ENTITY.IS_ENTITY_A_VEHICLE(entity) and VEHICLE.IS_VEHICLE_DRIVEABLE(entity, false) then 446 | local driver = VEHICLE.GET_PED_IN_VEHICLE_SEAT(entity, -1, false) 447 | if ENTITY.DOES_ENTITY_EXIST(driver) and PED.IS_PED_A_PLAYER(driver) and 448 | is_player_active(NETWORK.NETWORK_GET_PLAYER_INDEX_FROM_PED(driver), true, true) then 449 | VEHICLE.SET_VEHICLE_HOMING_LOCKEDONTO_STATE(entity, 2) 450 | end 451 | end 452 | 453 | if not lockOnBits:IsBitSet(bitPlace) then 454 | if amberSound:hasFinished() and not IsAnyHomingSoundActive() then 455 | lockOnBits:SetBit(bitPlace) 456 | amberSound:play() 457 | lockOnTimer.reset() 458 | end 459 | elseif not lockOnBits:IsBitSet(bitPlace + 6) and 460 | lockOnTimer.elapsed() >= 1000 then 461 | amberSound:stop() 462 | if redSound:hasFinished() then 463 | lockOnBits:SetBit(bitPlace + 6) 464 | redSound:play() 465 | lockOnTimer.reset() 466 | end 467 | elseif lockOnBits:IsBitSet(bitPlace + 6) and 468 | lockOnTimer.elapsed() >= 700 then 469 | if not redSound:hasFinished() then redSound:stop() end 470 | end 471 | 472 | local hudColour = HudColour.orange 473 | if lockOnBits:IsBitSet(bitPlace + 6) then 474 | hudColour = HudColour.red 475 | end 476 | local pos = ENTITY.GET_ENTITY_COORDS(entity, true) 477 | DrawLockonSprite(pos, 1.0, get_hud_colour(hudColour)) 478 | end 479 | 480 | 481 | local GetCrosshairPosition = function () 482 | local vehPos = ENTITY.GET_ENTITY_COORDS(myVehicle, true) 483 | local vehDir = ENTITY.GET_ENTITY_ROTATION(myVehicle, 2):toDir() 484 | local frontPos = v3.new(vehDir) 485 | frontPos:mul(100) 486 | frontPos:add(vehPos) 487 | 488 | local handle = 489 | SHAPETEST.START_EXPENSIVE_SYNCHRONOUS_SHAPE_TEST_LOS_PROBE( 490 | vehPos.x, vehPos.y, vehPos.z, 491 | frontPos.x, frontPos.y, frontPos.z, 511, 492 | myVehicle, 7 493 | ) 494 | 495 | local pHit = memory.alloc(1) 496 | local endCoords = v3.new() 497 | local normal = v3.new() 498 | local pHitEntity = memory.alloc_int() 499 | SHAPETEST.GET_SHAPE_TEST_RESULT(handle, pHit, endCoords, normal, pHitEntity) 500 | return memory.read_int(pHit) == 1 and endCoords or frontPos 501 | end 502 | 503 | 504 | local LockonTargets = function() 505 | if numTargets == 0 and not (IsWebBrowserOpen() or IsCameraAppOpen()) then 506 | local pos = GetCrosshairPosition() 507 | local colour = get_hud_colour(HudColour.white) 508 | colour.a = 160 509 | DrawLockonSprite(pos, 1.0, colour) 510 | end 511 | for i, target in ipairs(targetEnts) do LockonEntity(target, i) end 512 | end 513 | 514 | 515 | local UpdateTargetEntities = function () 516 | local count = 0 517 | for i = 1, 6 do 518 | if ENTITY.DOES_ENTITY_EXIST(targetEnts[i]) then 519 | local timer = lostTargetTimers[i] 520 | local entity = targetEnts[i] 521 | 522 | if i > maxTargets then 523 | targetEnts[i] = -1 524 | numTargets = numTargets - 1 525 | timer.disable() 526 | 527 | elseif not IsEntityInSafeScreenPos(entity) or not IsEntityTargetable(entity) or 528 | not bits:IsBitSet(Bit_IsCamPointingInFront) then 529 | targetEnts[i] = -1 530 | numTargets = numTargets - 1 531 | timer.disable() 532 | 533 | elseif not ENTITY.HAS_ENTITY_CLEAR_LOS_TO_ENTITY(myVehicle, entity, 287) then 534 | if not timer.isEnabled() then 535 | timer.reset() 536 | elseif timer.elapsed() > 1000 then 537 | targetEnts[i] = -1 538 | numTargets = numTargets - 1 539 | timer.disable() 540 | end 541 | 542 | else timer.disable() end 543 | end 544 | 545 | if ENTITY.DOES_ENTITY_EXIST(targetEnts[i]) then 546 | count = count + 1 547 | end 548 | end 549 | 550 | if count ~= numTargets then 551 | numTargets = count 552 | end 553 | end 554 | 555 | 556 | ---@param vehicle Vehicle 557 | ---@param damage integer 558 | ---@param weaponHash Hash 559 | ---@param ownerPed Ped 560 | ---@param isAudible boolean 561 | ---@param isVisible boolean 562 | ---@param speed number 563 | ---@param target Ped 564 | ---@param position integer #right: 0, left: 1 565 | local ShootFromVehicle = function (vehicle, damage, weaponHash, ownerPed, isAudible, isVisible, speed, target, position) 566 | local pos = ENTITY.GET_ENTITY_COORDS(vehicle, true) 567 | local min, max = v3.new(), v3.new() 568 | MISC.GET_MODEL_DIMENSIONS(ENTITY.GET_ENTITY_MODEL(vehicle), min, max) 569 | local direction = ENTITY.GET_ENTITY_ROTATION(vehicle, 2):toDir() 570 | local a 571 | 572 | if position == 0 then 573 | local offset = v3.new(min.x + 0.3, max.y - 0.15, 0.3) 574 | a = ENTITY.GET_OFFSET_FROM_ENTITY_IN_WORLD_COORDS(vehicle, offset.x, offset.y, offset.z) 575 | elseif position == 1 then 576 | local offset = v3.new(max.x - 0.3, max.y - 0.15, 0.3) 577 | a = ENTITY.GET_OFFSET_FROM_ENTITY_IN_WORLD_COORDS(vehicle, offset.x, offset.y, offset.z) 578 | else 579 | error("got unexpected position") 580 | end 581 | 582 | local b = v3.new(direction) 583 | b:mul(5.0); b:add(a) 584 | MISC.SHOOT_SINGLE_BULLET_BETWEEN_COORDS_IGNORE_ENTITY_NEW( 585 | a.x, a.y, a.z, 586 | b.x, b.y, b.z, 587 | damage, 588 | true, 589 | weaponHash, 590 | ownerPed, 591 | isAudible, 592 | not isVisible, 593 | speed, 594 | vehicle, 595 | false, false, target, false, 0, 0, 0 596 | ) 597 | AUDIO.PLAY_SOUND_FROM_COORD(-1, "Fire", pos.x, pos.y, pos.z, "DLC_BTL_Terrobyte_Turret_Sounds", true, 200, true) 598 | end 599 | 600 | 601 | local ShootMissiles = function() 602 | local controlId = 68 603 | if PED.IS_PED_IN_FLYING_VEHICLE(players.user_ped()) then 604 | controlId = 114 605 | end 606 | local target = 0 607 | 608 | if (PAD.IS_DISABLED_CONTROL_PRESSED(2, controlId) or bits:IsBitSet(Bit_IsTargetShooting)) and 609 | not bits:IsBitSet(Bit_IsRecharging) and lastShot.elapsed() > 300 then 610 | if shotCount < 0 or shotCount > 5 then 611 | shotCount = 0 612 | end 613 | 614 | vehicleWeaponSide = vehicleWeaponSide == 0 and 1 or 0 615 | local ownerPed = players.user_ped() 616 | 617 | if numTargets > 0 then 618 | if ENTITY.DOES_ENTITY_EXIST(targetEnts[numShotTargets + 1]) and 619 | not ENTITY.IS_ENTITY_DEAD(targetEnts[numShotTargets + 1], false) then 620 | target = targetEnts[numShotTargets + 1] 621 | bits:SetBit(Bit_IsTargetShooting) 622 | ShootFromVehicle(myVehicle, 200, weapon, ownerPed, true, true, 1000.0, target, vehicleWeaponSide) 623 | shotCount = shotCount + 1 624 | numShotTargets = numShotTargets + 1 625 | lastShot.reset() 626 | end 627 | 628 | if numTargets == numShotTargets then 629 | bits:SetBit(Bit_IsRecharging) 630 | bits:ClearBit(Bit_IsTargetShooting) 631 | numShotTargets = 0 632 | shotCount = 0 633 | chargeLevel = 0 634 | rechargeTimer.reset() 635 | end 636 | 637 | else 638 | ShootFromVehicle(myVehicle, 200, weapon, ownerPed, true, true, 1000.0, 0, vehicleWeaponSide) 639 | shotCount = shotCount + 1 640 | lastShot.reset() 641 | if shotCount == 6 then 642 | bits:SetBit(Bit_IsRecharging) 643 | chargeLevel = 0 644 | shotCount = 0 645 | rechargeTimer.reset() 646 | end 647 | end 648 | end 649 | end 650 | 651 | 652 | local StopHomingSounds = function() 653 | for i = 1, 6 do 654 | if not redHomingSounds[i]:hasFinished() then 655 | redHomingSounds[i]:stop() 656 | end 657 | if not amberHomingSounds[i]:hasFinished() then 658 | amberHomingSounds[i]:stop() 659 | end 660 | end 661 | end 662 | 663 | 664 | local LockonManager = function () 665 | if bits:IsBitSet(Bit_IsRecharging) then 666 | if rechargeTimer.elapsed() < 3000 then 667 | chargeLevel = 100 * rechargeTimer.elapsed() / 3000 668 | StopHomingSounds() 669 | lockOnBits:reset() 670 | return 671 | else 672 | bits:ClearBit(Bit_IsRecharging) 673 | chargeLevel = 100.0 674 | shotCount = 0 675 | numShotTargets = 0 676 | end 677 | end 678 | 679 | if not bits:IsBitSet(Bit_IsTargetShooting) and not bits:IsBitSet(Bit_IsRecharging) and 680 | not (IsWebBrowserOpen() or IsCameraAppOpen()) then 681 | if state == State.GettingNearbyEnts then 682 | SetNearbyEntities() 683 | elseif state == State.SettingTargets then 684 | SetTargetEntities() 685 | end 686 | UpdateTargetEntities() 687 | end 688 | LockonTargets() 689 | end 690 | 691 | 692 | Print = {} 693 | 694 | ---@param font integer 695 | ---@param scale v3 696 | ---@param centred boolean 697 | ---@param rightJustified boolean 698 | ---@param outline boolean 699 | ---@param colour? Colour 700 | ---@param wrap? v3 701 | Print.setupdraw = function(font, scale, centred, rightJustified, outline, colour, wrap) 702 | HUD.SET_TEXT_FONT(font) 703 | HUD.SET_TEXT_SCALE(scale.x, scale.y) 704 | colour = colour or {r = 255, g = 255, b = 255, a = 255} 705 | HUD.SET_TEXT_COLOUR(colour.r, colour.g, colour.b, colour.a) 706 | wrap = wrap or {x = 0.0, y = 1.0} 707 | HUD.SET_TEXT_WRAP(wrap.x, wrap.y) 708 | HUD.SET_TEXT_RIGHT_JUSTIFY(rightJustified) 709 | HUD.SET_TEXT_CENTRE(centred) 710 | HUD.SET_TEXT_DROPSHADOW(0, 0, 0, 0, 0) 711 | HUD.SET_TEXT_EDGE(0, 0, 0, 0, 0) 712 | if outline then HUD.SET_TEXT_OUTLINE() end 713 | end 714 | 715 | 716 | ---@param text string 717 | ---@param x number 718 | ---@param y number 719 | Print.drawstring = function (text, x, y) 720 | HUD.BEGIN_TEXT_COMMAND_DISPLAY_TEXT(text) 721 | GRAPHICS.BEGIN_TEXT_COMMAND_SCALEFORM_STRING(text) 722 | GRAPHICS.END_TEXT_COMMAND_SCALEFORM_STRING() 723 | HUD.END_TEXT_COMMAND_DISPLAY_TEXT(x, y, 0) 724 | end 725 | 726 | 727 | local DrawChargingMeter = function () 728 | if not is_phone_open() then 729 | local maxWidth = 0.119 730 | local posY = 0.63 731 | 732 | local colour = {r = 0, g = 153, b = 51, a = 255} 733 | if chargeLevel < 100 then 734 | colour = {r = 153, g = 0, b = 0, a = 255} 735 | end 736 | local width = interpolate(0.0, maxWidth, chargeLevel / 100) 737 | local height = 0.035 738 | local rectPosX = 0.85 + width/2 739 | GRAPHICS.DRAW_RECT(rectPosX, posY, width, height, colour.r, colour.g, colour.b, colour.a, true) 740 | 741 | local textColour = get_hud_colour(HudColour.white) 742 | Print.setupdraw(4, {x = 0.55, y = 0.55}, true, false, false, textColour) 743 | local textPosX = 0.85 + maxWidth/2 744 | local text = (chargeLevel == 100) and "DRONE_READY" or "DRONE_CHARGING" 745 | Print.drawstring(text, textPosX, posY - 0.019) 746 | 747 | --Caption 748 | local captionHeight = 0.06 749 | GRAPHICS.DRAW_RECT(0.85 + maxWidth/2, posY - captionHeight + 0.005, maxWidth, captionHeight, 156, 156, 156, 80, true) 750 | Print.setupdraw(4, {x = 0.65, y = 0.65}, true, false, false, textColour) 751 | Print.drawstring("DRONE_MISSILE", textPosX + 0.001, posY - captionHeight - 0.015) 752 | end 753 | end 754 | 755 | 756 | local DisableControlActions = function () 757 | PAD.DISABLE_CONTROL_ACTION(2, 25, true) 758 | PAD.DISABLE_CONTROL_ACTION(2, 91, true) 759 | PAD.DISABLE_CONTROL_ACTION(2, 99, true) 760 | PAD.DISABLE_CONTROL_ACTION(2, 115, true) 761 | PAD.DISABLE_CONTROL_ACTION(2, 262, true) 762 | PAD.DISABLE_CONTROL_ACTION(2, 68, true) 763 | PAD.DISABLE_CONTROL_ACTION(2, 69, true) 764 | PAD.DISABLE_CONTROL_ACTION(2, 70, true) 765 | PAD.DISABLE_CONTROL_ACTION(2, 114, true) 766 | PAD.DISABLE_CONTROL_ACTION(2, 331, true) 767 | end 768 | 769 | 770 | self.reset = function() 771 | set_streamed_texture_dict_as_no_longer_needed("mpsubmarine_periscope") 772 | lockOnBits:reset() 773 | bits:reset() 774 | targetEnts = {-1, -1, -1, -1, -1, -1} 775 | entCount = 0 776 | numTargets = 0 777 | shotCount = 0 778 | numShotTargets = 0 779 | chargeLevel = 100.0 780 | myVehicle = 0 781 | StopHomingSounds() 782 | nearbyEntities = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1} 783 | for i = 1, 6 do lostTargetTimers[i].disable() end 784 | state = State.Reseted 785 | end 786 | 787 | 788 | ---@param ignore boolean 789 | self.SetIgnoreFriends = function(ignore) 790 | whiteList:ToggleBit(Bit_IgnoreFriends, ignore) 791 | end 792 | 793 | ---@param ignore boolean 794 | self.SetIgnoreOrgMembers = function (ignore) 795 | whiteList:ToggleBit(Bit_IgnoreOrgMembers, ignore) 796 | end 797 | 798 | ---@param ignore boolean 799 | self.SetIgnoreCrewMembers = function (ignore) 800 | whiteList:ToggleBit(Bit_IgnoreCrewMembers, ignore) 801 | end 802 | 803 | ---@param value integer 804 | self.SetMaxTargets = function (value) 805 | maxTargets = value 806 | end 807 | 808 | self.getVersion = function () 809 | return version 810 | end 811 | 812 | 813 | self.mainLoop = function () 814 | if is_player_active(players.user(), true, true) and PED.IS_PED_IN_ANY_VEHICLE(players.user_ped(), false) then 815 | local vehicle = PED.GET_VEHICLE_PED_IS_IN(players.user_ped(), false) 816 | if ENTITY.DOES_ENTITY_EXIST(vehicle) and not ENTITY.IS_ENTITY_DEAD(vehicle, false) and 817 | VEHICLE.IS_VEHICLE_DRIVEABLE(vehicle, false) and 818 | VEHICLE.GET_PED_IN_VEHICLE_SEAT(vehicle, -1, false) == players.user_ped() then 819 | 820 | if not is_player_passive(players.user()) then 821 | if state == State.Reseted then 822 | request_streamed_texture_dict("mpsubmarine_periscope") 823 | --This prevents not being able to shoot 824 | WEAPON.GIVE_DELAYED_WEAPON_TO_PED(players.user_ped(), weapon, -1, false) 825 | state = State.GettingNearbyEnts 826 | end 827 | myVehicle = vehicle 828 | if IsCameraPointingInFrontOfEntity(vehicle, 40.0) then 829 | bits:SetBit(Bit_IsCamPointingInFront) 830 | else 831 | bits:ClearBit(Bit_IsCamPointingInFront) 832 | end 833 | 834 | LockonManager() 835 | if not (IsWebBrowserOpen() or IsCameraAppOpen()) then 836 | ShootMissiles() 837 | DrawChargingMeter() 838 | end 839 | DisableControlActions() 840 | 841 | elseif not is_player_in_any_rc_vehicle(players.user()) then 842 | if state ~= State.Reseted then 843 | self.reset() 844 | end 845 | local timerStart = memory.script_global(2793044 + 4463) 846 | local timerState = memory.script_global(2793044 + 4463 + 1) 847 | if timerStart ~= NULL and timerState ~= NULL and 848 | memory.read_int(timerState) == 0 then 849 | notification:normal(trans.DisablingPassive) 850 | memory.write_int(timerStart, NETWORK.GET_NETWORK_TIME()) 851 | memory.write_int(timerState, 1) 852 | end 853 | end 854 | elseif state ~= State.Reseted then 855 | self.reset() 856 | end 857 | elseif state ~= State.Reseted then 858 | self.reset() 859 | end 860 | end 861 | 862 | return self -------------------------------------------------------------------------------- /lib/wiriscript/orbital_cannon.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | -------------------------------- 3 | THIS FILE IS PART OF WIRISCRIPT 4 | Nowiry#2663 5 | -------------------------------- 6 | ]] 7 | 8 | require "wiriscript.functions" 9 | 10 | local self = {} 11 | local version = 29 12 | local targetId = -1 13 | local cam = 0 14 | local zoomLevel = 0.0 15 | local scaleform = 0 16 | local maxFov = 110 17 | local minFov = 25 18 | local camFov = maxFov 19 | local zoomTimer = newTimer() 20 | local canZoom = false -- used when not using keyboard 21 | local State = 22 | { 23 | NonExistent = -1, 24 | FadingOut = 0, 25 | CreatingCam = 1, 26 | LoadingScene = 2, 27 | FadingIn = 3, 28 | Charging = 4, 29 | Spectating = 5, 30 | Shooting = 6, 31 | FadingOut2 = 7, 32 | Destroying = 8, 33 | FadingIn2 = 9 34 | } 35 | local sounds = { 36 | zoomOut = Sound.new("zoom_out_loop", "dlc_xm_orbital_cannon_sounds"), 37 | activating = Sound.new("cannon_activating_loop", "dlc_xm_orbital_cannon_sounds"), 38 | backgroundLoop = Sound.new("background_loop", "dlc_xm_orbital_cannon_sounds"), 39 | fireLoop = Sound.new("cannon_charge_fire_loop", "dlc_xm_orbital_cannon_sounds") 40 | } 41 | local countdown = 3 -- `seconds` 42 | local isCounting = false 43 | local lastCountdown = newTimer() 44 | local state = State.NonExistent 45 | local chargeLevel = 0.0 46 | local timer = newTimer() 47 | local didShoot = false 48 | local NULL = 0 49 | local becomeOrbitalCannon = menu.ref_by_path("Online>Become The Orbital Cannon", 38) 50 | local orbitalBlast = Effect.new("scr_xm_orbital", "scr_xm_orbital_blast") 51 | local newSceneStart = newTimer() 52 | local chargeTimer = newTimer() 53 | local isRelocating = false 54 | local noTargetTimer = newTimer() 55 | 56 | 57 | self.exists = function () 58 | return state ~= State.NonExistent 59 | end 60 | 61 | self.getVersion = function () 62 | return version 63 | end 64 | 65 | ---@param playSound boolean 66 | local DispatchZoomLevel = function (playSound) 67 | if playSound then sounds.zoomOut:play() end 68 | GRAPHICS.BEGIN_SCALEFORM_MOVIE_METHOD(scaleform, "SET_ZOOM_LEVEL") 69 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_FLOAT(zoomLevel) 70 | GRAPHICS.END_SCALEFORM_MOVIE_METHOD() 71 | zoomTimer.reset() 72 | end 73 | 74 | 75 | local SetCannonCamZoom = function () 76 | local fovTarget = interpolate(maxFov, minFov, zoomLevel) 77 | local fov = CAM.GET_CAM_FOV(cam) 78 | if fovTarget ~= fov then 79 | camFov = interpolate(fov, fovTarget, zoomTimer.elapsed() / 200) 80 | CAM.SET_CAM_FOV(cam, camFov) 81 | return 82 | else 83 | sounds.zoomOut:stop() 84 | end 85 | 86 | if PAD.IS_USING_KEYBOARD_AND_MOUSE(2) then 87 | if PAD.IS_DISABLED_CONTROL_JUST_PRESSED(0, 241) and zoomLevel < 1.0 then 88 | zoomLevel = zoomLevel + 0.25 89 | DispatchZoomLevel(true) 90 | elseif PAD.IS_DISABLED_CONTROL_JUST_PRESSED(0, 242) and 91 | zoomLevel > 0.0 then 92 | zoomLevel = zoomLevel - 0.25 93 | DispatchZoomLevel(true) 94 | end 95 | elseif canZoom then 96 | local controlNormal = PAD.GET_CONTROL_NORMAL(0, 221) 97 | if controlNormal > 0.3 and canZoom and zoomLevel < 1.0 then 98 | zoomLevel = zoomLevel + 0.25 99 | DispatchZoomLevel(true) 100 | canZoom = false 101 | elseif controlNormal < -0.3 and canZoom and zoomLevel > 0.0 then 102 | zoomLevel = zoomLevel - 0.25 103 | DispatchZoomLevel(true) 104 | canZoom = false 105 | end 106 | elseif PAD.GET_CONTROL_NORMAL(0, 221) == 0 then 107 | canZoom = true 108 | end 109 | end 110 | 111 | 112 | ---@param pos Vector3 113 | ---@param size number 114 | ---@param hudColour integer 115 | local DrawLockonSprite = function (pos, size, hudColour) 116 | local colour = get_hud_colour(hudColour) 117 | local txdSizeX = 0.013 118 | local txdSizeY = 0.013 * GRAPHICS.GET_ASPECT_RATIO(false) 119 | GRAPHICS.SET_DRAW_ORIGIN(pos.x, pos.y, pos.z, 0) 120 | size = (size * 0.03) 121 | GRAPHICS.DRAW_SPRITE("helicopterhud", "hud_corner", -size * 0.5, -size, txdSizeX, txdSizeY, 0.0, colour.r, colour.g, colour.b, colour.a, true, 0) 122 | GRAPHICS.DRAW_SPRITE("helicopterhud", "hud_corner", size * 0.5, -size, txdSizeX, txdSizeY, 90.0, colour.r, colour.g, colour.b, colour.a, true, 0) 123 | GRAPHICS.DRAW_SPRITE("helicopterhud", "hud_corner", -size * 0.5, size, txdSizeX, txdSizeY, 270., colour.r, colour.g, colour.b, colour.a, true, 0) 124 | GRAPHICS.DRAW_SPRITE("helicopterhud", "hud_corner", size * 0.5, size, txdSizeX, txdSizeY, 180., colour.r, colour.g, colour.b, colour.a, true, 0) 125 | GRAPHICS.CLEAR_DRAW_ORIGIN() 126 | end 127 | 128 | 129 | local DisableControlActions = function () 130 | PAD.DISABLE_CONTROL_ACTION(0, 142, true) 131 | PAD.DISABLE_CONTROL_ACTION(0, 141, true) 132 | PAD.DISABLE_CONTROL_ACTION(0, 140, true) 133 | PAD.DISABLE_CONTROL_ACTION(0, 24, true) 134 | PAD.DISABLE_CONTROL_ACTION(0, 84, true) 135 | PAD.DISABLE_CONTROL_ACTION(0, 85, true) 136 | PAD.DISABLE_CONTROL_ACTION(0, 263, true) 137 | PAD.DISABLE_CONTROL_ACTION(0, 264, true) 138 | PAD.DISABLE_CONTROL_ACTION(0, 143, true) 139 | PAD.DISABLE_CONTROL_ACTION(0, 200, true) 140 | PAD.DISABLE_CONTROL_ACTION(0, 257, true) 141 | PAD.DISABLE_CONTROL_ACTION(0, 99, true) 142 | PAD.DISABLE_CONTROL_ACTION(0, 115, true) 143 | HUD.HUD_SUPPRESS_WEAPON_WHEEL_RESULTS_THIS_FRAME() 144 | end 145 | 146 | 147 | ---@param distance number 148 | ---@return integer 149 | local GetArrowAlpha = function (distance) 150 | local alpha = 255 151 | local maxDistance = 2500 152 | local minDistance = 1000 153 | if distance > maxDistance then 154 | alpha = 0 155 | elseif distance < minDistance then 156 | alpha = 255 157 | else 158 | local perc = 1.0 - (distance - minDistance) / (maxDistance - minDistance) 159 | alpha = math.ceil(alpha * perc) 160 | end 161 | return alpha 162 | end 163 | 164 | 165 | ---@param message string 166 | ---@param duration integer 167 | local DisplayHelpMessage = function (message, duration) 168 | HUD.BEGIN_TEXT_COMMAND_DISPLAY_HELP(message) 169 | HUD.END_TEXT_COMMAND_DISPLAY_HELP(0, false, true, duration) 170 | end 171 | 172 | 173 | ---@param entity Entity 174 | ---@param hudColour HudColour 175 | local DrawDirectionalArrowForEntity = function (entity, hudColour) 176 | local entPos = ENTITY.GET_ENTITY_COORDS(entity, false) 177 | local screenX, screenY = memory.alloc(4), memory.alloc(4) 178 | if not GRAPHICS.GET_SCREEN_COORD_FROM_WORLD_COORD(entPos.x, entPos.y, entPos.z, screenX, screenY) then 179 | local colour = get_hud_colour(hudColour) 180 | local camPos = CAM.GET_CAM_COORD(cam) 181 | local camRot = v3.new(-math.pi/2, 0, 0) 182 | local deltaXY = v3.new(entPos.x, entPos.y, 0.0) 183 | deltaXY:sub(v3.new(camPos.x, camPos.y, 0.0)) 184 | local distanceXY = deltaXY:magnitude() 185 | local distanceZ = entPos.z - camPos.z 186 | 187 | local elevation 188 | if distanceZ > 0.0 then 189 | elevation = math.atan(distanceZ / distanceXY) 190 | else 191 | elevation = 0.0 192 | end 193 | 194 | local azimuth 195 | if deltaXY.y ~= 0.0 then 196 | azimuth = math.atan(deltaXY.x, deltaXY.y) 197 | elseif deltaXY.x < 0.0 then 198 | azimuth = -90.0 199 | else 200 | azimuth = 90.0 201 | end 202 | 203 | local angle = math.atan( 204 | math.cos(camRot.x) * math.sin(elevation) - math.sin(camRot.x) * math.cos(elevation) * math.cos(-azimuth - camRot.z), 205 | math.sin(-azimuth - camRot.z) * math.cos(elevation) 206 | ) 207 | local screenX = 0.5 - math.cos(angle) * 0.19 208 | local screenY = 0.5 - math.sin(angle) * 0.19 209 | local colourA = GetArrowAlpha(distanceXY) 210 | GRAPHICS.DRAW_SPRITE("helicopterhud", "hudArrow", screenX, screenY, 0.02, 0.04, math.deg(angle) - 90.0, colour.r, colour.g, colour.b, colourA, false, 0) 211 | end 212 | end 213 | 214 | 215 | ---@param player Player 216 | ---@return boolean 217 | local IsPlayerTargetable = function (player) 218 | local ped = PLAYER.GET_PLAYER_PED_SCRIPT_INDEX(player) 219 | if is_player_active(player, false, true) and not is_player_passive(player) and 220 | not is_player_in_any_interior(player) and (read_global.int(2657589 + (player * 466 + 1) + 427) & (1 << 2)) == 0 and 221 | not NETWORK.IS_ENTITY_A_GHOST(ped) then 222 | return true 223 | end 224 | return false 225 | end 226 | 227 | 228 | local DrawMarkersOnPlayers = function () 229 | for _, player in ipairs(players.list()) do 230 | local ped = PLAYER.GET_PLAYER_PED_SCRIPT_INDEX(player) 231 | if PED.IS_PED_INJURED(ped) or not IsPlayerTargetable(player) or player == targetId then 232 | continue 233 | end 234 | if GRAPHICS.HAS_STREAMED_TEXTURE_DICT_LOADED("helicopterhud") then 235 | local pos = ENTITY.GET_ENTITY_COORDS(ped, false) 236 | local txdScale = interpolate(1.0, 0.5, camFov / maxFov) 237 | DrawLockonSprite(pos, txdScale, HudColour.green) 238 | DrawDirectionalArrowForEntity(ped, HudColour.green) 239 | end 240 | end 241 | end 242 | 243 | 244 | Destroy = function () 245 | sounds.backgroundLoop:stop() 246 | sounds.fireLoop:stop() 247 | sounds.zoomOut:stop() 248 | sounds.activating:stop() 249 | 250 | PLAYER.DISABLE_PLAYER_FIRING(players.user(), false) 251 | menu.set_value(becomeOrbitalCannon, false) 252 | 253 | GRAPHICS.ANIMPOSTFX_STOP("MP_OrbitalCannon") 254 | MISC.CLEAR_OVERRIDE_WEATHER() 255 | GRAPHICS.CASCADE_SHADOWS_SET_AIRCRAFT_MODE(false) 256 | AUDIO.STOP_AUDIO_SCENE("dlc_xm_orbital_cannon_camera_active_scene") 257 | AUDIO.RELEASE_NAMED_SCRIPT_AUDIO_BANK("DLC_CHRISTMAS2017/XM_ION_CANNON") 258 | 259 | CAM.RENDER_SCRIPT_CAMS(false, false, 0, true, false, 0) 260 | if CAM.DOES_CAM_EXIST(cam) then 261 | CAM.SET_CAM_ACTIVE(cam, false) 262 | CAM.DESTROY_CAM(cam, false) 263 | end 264 | STREAMING.CLEAR_FOCUS() 265 | NETWORK.NETWORK_SET_IN_FREE_CAM_MODE(false) 266 | set_scaleform_movie_as_no_longer_needed(scaleform) 267 | 268 | targetId = -1 269 | scaleform = 0 270 | isRelocating = false 271 | zoomLevel = 0.0 272 | chargeLevel = 0.0 273 | didShoot = false 274 | camFov = maxFov 275 | countdown = 3 276 | timer.disable() 277 | chargeTimer.disable() 278 | noTargetTimer.disable() 279 | end 280 | 281 | 282 | local DrawInstructionalButtons = function() 283 | if Instructional:begin() then 284 | Instructional.add_control(202, "HUD_INPUT3") 285 | if not PAD.IS_USING_KEYBOARD_AND_MOUSE(0) then 286 | Instructional.add_control(221, "ORB_CAN_ZOOM") 287 | else 288 | Instructional.add_control(242, "ORB_CAN_ZOOMO") 289 | Instructional.add_control(241, "ORB_CAN_ZOOMI") 290 | end 291 | Instructional.add_control(24, "ORB_CAN_FIRE") 292 | Instructional:set_background_colour(0, 0, 0, 80) 293 | Instructional:draw() 294 | end 295 | end 296 | 297 | 298 | ---@param state integer 299 | local SetCannonState = function (state) 300 | GRAPHICS.BEGIN_SCALEFORM_MOVIE_METHOD(scaleform, "SET_STATE") 301 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_INT(state) 302 | GRAPHICS.END_SCALEFORM_MOVIE_METHOD() 303 | end 304 | 305 | 306 | self.destroy = function () 307 | if not CAM.IS_SCREEN_FADED_IN() then CAM.DO_SCREEN_FADE_IN(0) end 308 | Destroy() 309 | state = State.NonExistent 310 | end 311 | 312 | 313 | ---@param target Player 314 | self.create = function (target) 315 | if target == targetId or self.exists() then 316 | return 317 | end 318 | AUDIO.PLAY_SOUND_FRONTEND(-1, "menu_select", "dlc_xm_orbital_cannon_sounds", true) 319 | targetId = target 320 | state = State.FadingOut 321 | end 322 | 323 | 324 | self.mainLoop = function () 325 | if state == State.NonExistent then 326 | -- Do nothing 327 | elseif not CAM.DOES_CAM_EXIST(cam) then 328 | sounds.activating:play() 329 | 330 | cam = CAM.CREATE_CAM("DEFAULT_SCRIPTED_CAMERA", false) 331 | local pos = players.get_position(targetId) 332 | CAM.SET_CAM_COORD(cam, pos.x, pos.y, pos.z + 300.0) 333 | CAM.SET_CAM_ROT(cam, -90.0, 0.0, 0.0, 2) 334 | CAM.SET_CAM_FOV(cam, maxFov) 335 | CAM.SET_CAM_ACTIVE(cam, true) 336 | 337 | AUDIO.REQUEST_SCRIPT_AUDIO_BANK("DLC_CHRISTMAS2017/XM_ION_CANNON", false, -1) 338 | request_streamed_texture_dict("helicopterhud") 339 | scaleform = request_scaleform_movie("ORBITAL_CANNON_CAM") 340 | AUDIO.START_AUDIO_SCENE("dlc_xm_orbital_cannon_camera_active_scene") 341 | menu.set_value(becomeOrbitalCannon, true) 342 | DispatchZoomLevel(false) 343 | state = State.FadingOut 344 | 345 | elseif not GRAPHICS.HAS_SCALEFORM_MOVIE_LOADED(scaleform) then 346 | scaleform = request_scaleform_movie("ORBITAL_CANNON_CAM") 347 | 348 | else 349 | local myPos = players.get_position(players.user()) 350 | STREAMING.REQUEST_COLLISION_AT_COORD(myPos.x, myPos.y, myPos.z) 351 | SetCannonState(4) 352 | 353 | if util.is_session_transition_active() then 354 | Destroy() 355 | state = State.NonExistent 356 | end 357 | 358 | if PAD.IS_DISABLED_CONTROL_JUST_PRESSED(0, 202) then 359 | timer.reset() 360 | state = State.FadingOut2 361 | end 362 | 363 | if state == State.FadingOut then 364 | if not CAM.IS_SCREEN_FADED_OUT() then 365 | CAM.DO_SCREEN_FADE_OUT(500) 366 | else 367 | isRelocating = false 368 | state = State.LoadingScene 369 | end 370 | 371 | elseif state == State.LoadingScene then 372 | local pos = players.get_position(targetId) 373 | STREAMING.NEW_LOAD_SCENE_START_SPHERE(pos.x, pos.y, pos.z, 300.0, 0) 374 | STREAMING.SET_FOCUS_POS_AND_VEL(pos.x, pos.y, pos.z, 5.0, 0.0, 0.0) 375 | NETWORK.NETWORK_SET_IN_FREE_CAM_MODE(true) 376 | timer.disable() 377 | newSceneStart.reset() 378 | state = State.FadingIn 379 | 380 | elseif state == State.FadingIn then 381 | if not CAM.IS_SCREEN_FADED_OUT() then 382 | -- If for some reason the game does a screen-fade-in 383 | -- before we do 384 | CAM.DO_SCREEN_FADE_OUT(0) 385 | elseif not timer.isEnabled() then 386 | if STREAMING.IS_NEW_LOAD_SCENE_LOADED() or newSceneStart.elapsed() > 10000 then 387 | STREAMING.NEW_LOAD_SCENE_STOP() 388 | timer.reset() 389 | end 390 | elseif timer.elapsed() > 2000 then 391 | CAM.DO_SCREEN_FADE_IN(500) 392 | CAM.RENDER_SCRIPT_CAMS(true, false, 0, true, false, 0) 393 | sounds.activating:stop() 394 | sounds.backgroundLoop:play() 395 | GRAPHICS.ANIMPOSTFX_PLAY("MP_OrbitalCannon", 0, true) 396 | MISC.SET_OVERRIDE_WEATHER("Clear") 397 | GRAPHICS.CASCADE_SHADOWS_SET_AIRCRAFT_MODE(true) 398 | state = State.Charging 399 | end 400 | 401 | local pos = players.get_position(targetId) 402 | CAM.SET_CAM_COORD(cam, pos.x, pos.y, pos.z + 300.0) 403 | 404 | elseif state == State.Charging then 405 | if chargeLevel == 100 then 406 | state = State.Spectating 407 | elseif not chargeTimer.isEnabled() then 408 | chargeTimer.reset() 409 | elseif chargeTimer.elapsed() < 3000 then 410 | chargeLevel = interpolate(0.0, 100.0, chargeTimer.elapsed() / 3000) 411 | else 412 | chargeLevel = 100.0 413 | AUDIO.PLAY_SOUND_FRONTEND(-1, "cannon_active", "dlc_xm_orbital_cannon_sounds", true) 414 | chargeTimer.disable() 415 | state = State.Spectating 416 | end 417 | 418 | local pos = players.get_position(targetId) 419 | CAM.SET_CAM_COORD(cam, pos.x, pos.y, pos.z + 300.0) 420 | SetCannonCamZoom() 421 | 422 | elseif state == State.Spectating then 423 | local camPos = CAM.GET_CAM_COORD(cam) 424 | local ptr = memory.alloc(4) 425 | MISC.GET_GROUND_Z_EXCLUDING_OBJECTS_FOR_3D_COORD(camPos.x, camPos.y, camPos.z, ptr, false, false) 426 | local groundZ = memory.read_float(ptr) 427 | STREAMING.SET_FOCUS_POS_AND_VEL(camPos.x, camPos.y, groundZ, 5.0, 0.0, 0.0) 428 | 429 | if PAD.IS_DISABLED_CONTROL_PRESSED(0, 69) then 430 | if not isCounting then 431 | PAD.SET_CONTROL_SHAKE(0, 1000, 50) 432 | sounds.fireLoop:play() 433 | isCounting = true 434 | lastCountdown.reset() 435 | end 436 | 437 | if lastCountdown.elapsed() > 1000 and countdown > 0 then 438 | countdown = countdown - 1 439 | lastCountdown.reset() 440 | elseif countdown == 0 then 441 | sounds.fireLoop:stop() 442 | timer.reset() 443 | state = State.Shooting 444 | end 445 | 446 | GRAPHICS.BEGIN_SCALEFORM_MOVIE_METHOD(scaleform, "SET_COUNTDOWN") 447 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_INT(countdown) 448 | GRAPHICS.END_SCALEFORM_MOVIE_METHOD() 449 | 450 | elseif isCounting then 451 | AUDIO.SET_VARIABLE_ON_SOUND(sounds.fireLoop.Id, "Firing", 0.0) 452 | sounds.fireLoop:stop() 453 | countdown = 3 454 | isCounting = false 455 | end 456 | 457 | SetCannonCamZoom() 458 | if not IsPlayerTargetable(targetId) then 459 | if not noTargetTimer.isEnabled() then 460 | DisplayHelpMessage("ORB_CAN_NO_TARG", 5000) 461 | noTargetTimer.reset() 462 | elseif noTargetTimer.elapsed() > 3000 then 463 | timer.reset() 464 | noTargetTimer.disable() 465 | state = State.FadingOut2 466 | end 467 | 468 | elseif PED.IS_PED_FATALLY_INJURED(PLAYER.GET_PLAYER_PED_SCRIPT_INDEX(targetId)) then 469 | if not noTargetTimer.isEnabled() then 470 | isRelocating = true 471 | DisplayHelpMessage("ORB_CAN_DEAD", 5000) 472 | noTargetTimer.reset() 473 | elseif noTargetTimer.elapsed() > 30000 then 474 | isRelocating = false 475 | timer.reset() 476 | state = State.FadingOut2 477 | end 478 | 479 | elseif isRelocating then 480 | noTargetTimer.disable() 481 | state = State.FadingOut 482 | 483 | else 484 | local pos = players.get_position(targetId) 485 | CAM.SET_CAM_COORD(cam, pos.x, pos.y, pos.z + 300.0) 486 | end 487 | 488 | elseif state == State.Shooting then 489 | if STREAMING.HAS_NAMED_PTFX_ASSET_LOADED(orbitalBlast.asset) then 490 | chargeLevel = 0.0 491 | local pos = players.get_position(targetId) 492 | FIRE.ADD_OWNED_EXPLOSION(players.user_ped(), pos.x, pos.y, pos.z, 59, 1.0, true, false, 1.0) 493 | GRAPHICS.USE_PARTICLE_FX_ASSET(orbitalBlast.asset) 494 | GRAPHICS.START_NETWORKED_PARTICLE_FX_NON_LOOPED_AT_COORD( 495 | orbitalBlast.name, pos.x, pos.y, pos.z, 0.0, 0.0, 0.0, 1.0, false, false, false, true 496 | ) 497 | AUDIO.PLAY_SOUND_FROM_COORD( 498 | -1, "DLC_XM_Explosions_Orbital_Cannon", pos.x, pos.y, pos.z, NULL, true, 0, false) 499 | CAM.SHAKE_CAM(cam, "GAMEPLAY_EXPLOSION_SHAKE", 1.5) 500 | PAD.SET_CONTROL_SHAKE(0, 500, 256) 501 | timer.reset() 502 | state = State.FadingOut2 503 | didShoot = true 504 | else 505 | STREAMING.REQUEST_NAMED_PTFX_ASSET(orbitalBlast.asset) 506 | end 507 | 508 | elseif state == State.FadingOut2 then 509 | if CAM.IS_SCREEN_FADED_OUT() then 510 | timer.reset() 511 | STREAMING.CLEAR_FOCUS() 512 | state = State.FadingIn2 513 | elseif not didShoot or timer.elapsed() > 2000 then 514 | CAM.DO_SCREEN_FADE_OUT(500) 515 | end 516 | 517 | elseif state == State.FadingIn2 then 518 | if timer.elapsed() > 2000 then 519 | CAM.RENDER_SCRIPT_CAMS(false, false, 0, true, false, 0) 520 | CAM.DO_SCREEN_FADE_IN(500) 521 | state = State.Destroying 522 | end 523 | 524 | elseif state == State.Destroying then 525 | Destroy() 526 | state = State.NonExistent 527 | return 528 | end 529 | 530 | DisableControlActions() 531 | HUD.HIDE_HUD_AND_RADAR_THIS_FRAME() 532 | DrawInstructionalButtons() 533 | HudTimer.DisableThisFrame() 534 | DisablePhone() 535 | 536 | if state ~= State.FadingOut or isRelocating then 537 | GRAPHICS.BEGIN_SCALEFORM_MOVIE_METHOD(scaleform, "SET_CHARGING_LEVEL") 538 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_FLOAT(chargeLevel / 100) 539 | GRAPHICS.END_SCALEFORM_MOVIE_METHOD() 540 | 541 | GRAPHICS.SET_SCRIPT_GFX_DRAW_ORDER(0) 542 | GRAPHICS.DRAW_SCALEFORM_MOVIE_FULLSCREEN(scaleform, 255, 255, 255, 255, 0) 543 | DrawMarkersOnPlayers() 544 | end 545 | end 546 | end 547 | 548 | return self -------------------------------------------------------------------------------- /lib/wiriscript/ped_list.lua: -------------------------------------------------------------------------------- 1 | return { 2 | ["Animal"] = { 3 | ["Hawk"] = "a_c_chickenhawk", 4 | ["Crow"] = "a_c_crow", 5 | ["Coyote"] = "a_c_coyote", 6 | ["Boar"] = "a_c_boar", 7 | ["Pug"] = "a_c_pug", 8 | ["Deer"] = "a_c_deer", 9 | ["Hen"] = "a_c_hen", 10 | ["Mountain Lion"] = "a_c_mtlion", 11 | ["Cow"] = "a_c_cow", 12 | ["Hammerhead Shark"] = "a_c_sharkhammer", 13 | ["Pigeon"] = "a_c_pigeon", 14 | ["Rottweiler"] = "a_c_rottweiler", 15 | ["Seagull"] = "a_c_seagull", 16 | ["Pig"] = "a_c_pig", 17 | ["Humpback"] = "a_c_humpback", 18 | ["Australian Shepherd"] = "a_c_shepherd", 19 | ["Killer Whale"] = "a_c_killerwhale", 20 | ["Dolphin"] = "a_c_dolphin", 21 | ["Fish"] = "a_c_fish", 22 | ["Westie"] = "a_c_westy", 23 | ["Tiger Shark"] = "a_c_sharktiger", 24 | ["Panther"] = "a_c_panther", 25 | ["Cat"] = "a_c_cat_01", 26 | ["Husky"] = "a_c_husky", 27 | ["Cormorant"] = "a_c_cormorant", 28 | ["Retriever"] = "a_c_retriever", 29 | ["Rabbit"] = "a_c_rabbit_01", 30 | ["Rat"] = "a_c_rat", 31 | ["Chimp"] = "a_c_chimp", 32 | ["Stingray"] = "a_c_stingray", 33 | ["Poodle"] = "a_c_poodle", 34 | ["Chop"] = "a_c_chop", 35 | ["Rhesus"] = "a_c_rhesus" 36 | }, 37 | ["Story Scenario Male"] = { 38 | ["Tattoo Artist"] = "u_m_y_tattoo_01", 39 | ["Mani"] = "u_m_y_mani", 40 | ["Jesco White (Tapdancing Hillbilly)"] = "u_m_o_taphillbilly", 41 | ["Jeweller Security"] = "u_m_m_jewelsec_01", 42 | ["Paparazzi Young Male"] = "u_m_y_paparazzi", 43 | ["Prologue Security"] = "u_m_m_prolsec_01", 44 | ["Ushi"] = "u_m_y_ushi", 45 | ["Kifflom Guy"] = "u_m_y_baygor", 46 | ["Jesus"] = "u_m_m_jesus_01", 47 | ["Street Art Male"] = "u_m_m_streetart_01", 48 | ["Vince"] = "u_m_m_vince", 49 | ["Glen-Stank Male"] = "u_m_m_glenstank_01", 50 | ["Ex-Mil Bum"] = "u_m_y_militarybum", 51 | ["Prologue Mourner Male"] = "u_m_m_promourn_01", 52 | ["Republican Space Ranger"] = "u_m_y_rsranger_01", 53 | ["Hippie Male"] = "u_m_y_hippie_01", 54 | ["Ed Toh"] = "u_m_m_edtoh", 55 | ["Stag Party Groom"] = "u_m_y_staggrm_01", 56 | ["Sports Biker"] = "u_m_y_sbike", 57 | ["Spy Actor"] = "u_m_m_spyactor", 58 | ["Rival Paparazzo"] = "u_m_m_rivalpap", 59 | ["Hangar Mechanic"] = "u_m_y_smugmech_01", 60 | ["Male Club Dancer (Leather)"] = "u_m_y_dancelthr_01", 61 | ["Pogo the Monkey"] = "u_m_y_pogo_01", 62 | ["Prologue Driver"] = "u_m_y_proldriver_01", 63 | ["Prisoner"] = "u_m_y_prisoner_01", 64 | ["Anton Beaudelaire"] = "u_m_y_antonb", 65 | ["Love Fist Willy"] = "u_m_m_willyfist", 66 | ["Griff"] = "u_m_m_griff_01", 67 | ["Party Target"] = "u_m_m_partytarget", 68 | ["Chip"] = "u_m_y_chip", 69 | ["Jewel Thief"] = "u_m_m_jewelthief", 70 | ["Movie Director"] = "u_m_m_filmdirector", 71 | ["Mark Fostenburg"] = "u_m_m_markfost", 72 | ["Partygoer"] = "u_m_y_party_01", 73 | ["Blane"] = "u_m_m_blane", 74 | ["Justin"] = "u_m_y_justin", 75 | ["Movie Corpse (Suited)"] = "u_m_o_filmnoir", 76 | ["Cyclist Male"] = "u_m_y_cyclist_01", 77 | ["Male Club Dancer (Burlesque)"] = "u_m_y_danceburl_01", 78 | ["Male Club Dancer (Rave)"] = "u_m_y_dancerave_01", 79 | ["Impotent Rage"] = "u_m_y_imporage", 80 | ["Zombie"] = "u_m_y_zombie_01", 81 | ["Gabriel"] = "u_m_y_gabriel", 82 | ["Bike Hire Guy"] = "u_m_m_bikehire_01", 83 | ["Dead Courier"] = "u_m_y_corpse_01", 84 | ["Gun Vendor"] = "u_m_y_gunvend_01", 85 | ["Guido"] = "u_m_y_guido_01", 86 | ["Tramp Old Male"] = "u_m_o_tramp_01", 87 | ["Bank Manager Male"] = "u_m_m_bankman", 88 | ["Abner"] = "u_m_y_abner", 89 | ["Burger Drug Worker"] = "u_m_y_burgerdrug_01", 90 | ["Baby D"] = "u_m_y_babyd", 91 | ["FIB Mugger"] = "u_m_y_fibmugger_01", 92 | ["DOA Man"] = "u_m_m_doa_01", 93 | ["Al Di Napoli Male"] = "u_m_m_aldinapoli", 94 | ["Financial Guru"] = "u_m_o_finguru_01", 95 | ["Dean"] = "u_m_o_dean", 96 | ["Curtis"] = "u_m_m_curtis", 97 | ["FIB Architect"] = "u_m_m_fibarchitect", 98 | ["Casino Thief"] = "u_m_y_croupthief_01", 99 | ["Caleb"] = "u_m_y_caleb", 100 | ["Avon Juggernaut"] = "u_m_y_juggernaut_01" 101 | }, 102 | ["Cutscene"] = { 103 | ["Brucie Kibbutz"] = "csb_brucie2", 104 | ["Marine"] = "csb_ramp_marine", 105 | ["Natalia"] = "cs_natalia", 106 | ["Cletus"] = "csb_cletus", 107 | ["Jack Howitzer"] = "csb_jackhowitzer", 108 | ["Island Dj 2"] = "csb_isldj_02", 109 | ["Dale"] = "cs_dale", 110 | ["United Paper Man"] = "cs_paper", 111 | ["Burger Drug Worker"] = "csb_burgerdrug", 112 | ["Jimmy De Santa 2"] = "cs_jimmydisanto2", 113 | ["Simeon Yetarian"] = "cs_siemonyetarian", 114 | ["Oscar"] = "csb_oscar", 115 | ["Hugh Welsh"] = "csb_hugh", 116 | ["Tao Cheng"] = "cs_taocheng", 117 | ["Mary-Ann Quinn"] = "cs_maryann", 118 | ["Merryweather Merc"] = "csb_mweather", 119 | ["Mrs. Thornhill"] = "cs_mrs_thornhill", 120 | ["Tennis Coach"] = "cs_tenniscoach", 121 | ["Nervous Ron"] = "cs_nervousron", 122 | ["Vagos Speak"] = "csb_vagspeak", 123 | ["FIB Suit"] = "cs_fbisuit_01", 124 | ["Movie Premiere Female"] = "cs_movpremf_01", 125 | ["Steve Haines"] = "cs_stevehains", 126 | ["DJ Black Madonna"] = "csb_djblamadon", 127 | ["Hipster"] = "csb_ramp_hipster", 128 | ["Vincent (Casino)"] = "csb_vincent", 129 | ["Anita Mendoza"] = "csb_anita", 130 | ["Mjo"] = "csb_mjo", 131 | ["Tony Prince"] = "csb_tonyprince", 132 | ["Avon Hertz"] = "csb_avon", 133 | ["Dave Norton"] = "cs_davenorton", 134 | ["Michelle"] = "cs_michelle", 135 | ["Stretch"] = "cs_stretch", 136 | ["Vincent (Casino) 2"] = "csb_vincent_2", 137 | ["Hick"] = "csb_ramp_hic", 138 | ["Manuel"] = "cs_manuel", 139 | ["Casey"] = "cs_casey", 140 | ["Cris Formage"] = "cs_chrisformage", 141 | ["Agent 14"] = "csb_mp_agent14", 142 | ["Ballas OG"] = "csb_ballasog", 143 | ["Marnie Allen"] = "cs_marnie", 144 | ["Jimmy Boston"] = "cs_jimmyboston", 145 | ["Epsilon Tom"] = "cs_tomepsilon", 146 | ["Thornton Duggan"] = "csb_thornton", 147 | ["Maude"] = "csb_maude", 148 | ["Miguel Madrazo"] = "csb_miguelmadrazo", 149 | ["Island Dj 1"] = "csb_isldj_01", 150 | ["Devin"] = "cs_devin", 151 | ["Minuteman Joe"] = "cs_joeminuteman", 152 | ["Denise"] = "cs_denise", 153 | ["Island Dj 4"] = "csb_isldj_04", 154 | ["Ferdinand Kerimov (Mr. K)"] = "cs_mrk", 155 | ["Dom Beasley"] = "cs_dom", 156 | ["Agent"] = "csb_agent", 157 | ["Grove Street Dealer"] = "csb_grove_str_dlr", 158 | ["Chinese Goon"] = "csb_chin_goon", 159 | ["Peter Dreyfuss"] = "cs_dreyfuss", 160 | ["Dima Popov"] = "csb_popov", 161 | ["Dr. Friedlander"] = "cs_drfriedlander", 162 | ["Wade"] = "cs_wade", 163 | ["Island Dj"] = "csb_isldj_00", 164 | ["sessanta"] = "csb_sessanta", 165 | ["Ashley Butler"] = "cs_ashley", 166 | ["Barry"] = "cs_barry", 167 | ["Reporter"] = "csb_reporter", 168 | ["Drug Dealer"] = "csb_drugdealer", 169 | ["Tale of Us 1"] = "csb_talcc", 170 | ["Fabien"] = "cs_fabien", 171 | ["Stripper"] = "csb_stripper_01", 172 | ["Jeweller Assistant"] = "cs_jewelass", 173 | ["Johnny Klebitz"] = "cs_johnnyklebitz", 174 | ["Tao Cheng (Casino)"] = "cs_taocheng2", 175 | ["Alan Jerome"] = "csb_alan", 176 | ["FOS Rep"] = "csb_fos_rep", 177 | ["Wei Cheng"] = "cs_chengsr", 178 | ["Abigail Mathers"] = "csb_abigail", 179 | ["Imran Shinowa"] = "csb_imran", 180 | ["Car 3 Guy 1"] = "csb_car3guy1", 181 | ["Avi Schwartzman"] = "csb_avischwartzman_02", 182 | ["Tao's Translator (Casino)"] = "cs_taostranslator2", 183 | ["Floyd Hebert"] = "cs_floyd", 184 | ["Lester Crest 3"] = "cs_lestercrest_3", 185 | ["Denise's Friend"] = "csb_denise_friend", 186 | ["Groom"] = "csb_groom", 187 | ["Dixon"] = "csb_dix", 188 | ["Hao 2"] = "csb_hao_02", 189 | ["Huang"] = "csb_huang", 190 | ["Old Man 1"] = "cs_old_man1a", 191 | ["Beverly Felton"] = "cs_beverly", 192 | ["Patricia 2"] = "cs_patricia2", 193 | ["Wendy"] = "csb_wendy", 194 | ["Janet"] = "cs_janet", 195 | ["Georgina Cheng"] = "csb_georginacheng", 196 | ["Money Man"] = "csb_money", 197 | ["Stripper 2"] = "csb_stripper_02", 198 | ["Jimmy De Santa"] = "cs_jimmydisanto", 199 | ["Maxim Rashkovsky"] = "csb_rashcosvki", 200 | ["Amanda De Santa"] = "cs_amandatownley", 201 | ["Lazlow 2"] = "cs_lazlow_2", 202 | ["Celeb 1"] = "csb_celeb_01", 203 | ["Hunter"] = "cs_hunter", 204 | ["Tale of Us 2"] = "csb_talmm", 205 | ["Soloman"] = "csb_sol", 206 | ["English Dave 2"] = "csb_englishdave2", 207 | ["Tonya"] = "csb_tonya", 208 | ["English Dave"] = "csb_englishdave", 209 | ["Life Invader"] = "cs_lifeinvad_01", 210 | ["Karen Daniels"] = "cs_karen_daniels", 211 | ["Island Dj 3"] = "csb_isldj_03", 212 | ["Molly"] = "cs_molly", 213 | ["Ary"] = "csb_ary", 214 | ["Undercover Cop"] = "csb_undercover", 215 | ["Traffic Warden"] = "csb_trafficwarden", 216 | ["Nigel"] = "cs_nigel", 217 | ["Bank Manager"] = "cs_bankman", 218 | ["Chef"] = "csb_chef2", 219 | ["Tracey De Santa"] = "cs_tracydisanto", 220 | ["Priest"] = "cs_priest", 221 | ["Guadalope"] = "cs_guadalope", 222 | ["Bigfoot"] = "cs_orleans", 223 | ["Brad's Cadaver"] = "cs_bradcadaver", 224 | ["Tom Connors"] = "csb_tomcasino", 225 | ["Tom"] = "cs_tom", 226 | ["Omega"] = "cs_omega", 227 | ["Tao's Translator"] = "cs_taostranslator", 228 | ["Tanisha"] = "cs_tanisha", 229 | ["Sss"] = "csb_sss", 230 | ["Solomon Richards"] = "cs_solomon", 231 | ["Prologue Security 2"] = "cs_prolsec_02", 232 | ["Russian Drunk"] = "cs_russiandrunk", 233 | ["Rocco Pelosi"] = "csb_roccopelosi", 234 | ["Screenwriter"] = "csb_screen_writer", 235 | ["Prologue Security"] = "csb_prolsec", 236 | ["Prologue Driver"] = "csb_prologuedriver", 237 | ["Hao"] = "csb_hao", 238 | ["Lazlow"] = "cs_lazlow", 239 | ["Bride"] = "csb_bride", 240 | ["Lester Crest"] = "cs_lestercrest", 241 | ["Clay Simons (The Lost)"] = "cs_clay", 242 | ["Patricia"] = "cs_patricia", 243 | ["GURK"] = "cs_gurk", 244 | ["Customer"] = "csb_customer", 245 | ["Gerald"] = "csb_g", 246 | ["Janitor"] = "csb_janitor", 247 | ["Porn Dude"] = "csb_porndudes", 248 | ["Paige Harris"] = "csb_paige", 249 | ["Mrs. Rackman"] = "csb_mrs_r", 250 | ["Bryony"] = "csb_bryony", 251 | ["Gustavo"] = "csb_gustavo", 252 | ["Terry"] = "cs_terry", 253 | ["Martin Madrazo"] = "cs_martinmadrazo", 254 | ["Old Man 2"] = "cs_old_man2", 255 | ["Andreas Sanchez"] = "cs_andreas", 256 | ["Debra"] = "cs_debra", 257 | ["Movie Premiere Male"] = "cs_movpremmale", 258 | ["Lamar Davis"] = "cs_lamardavis", 259 | ["Josef"] = "cs_josef", 260 | ["Zimbor"] = "cs_zimbor", 261 | ["Avery Duggan"] = "csb_avery", 262 | ["Bogdan"] = "csb_bogdan", 263 | ["Juan Strickler"] = "csb_juanstrickler", 264 | ["Helmsman Pavel"] = "csb_helmsmanpavel ", 265 | ["Magenta"] = "cs_magenta", 266 | ["Brad"] = "cs_brad", 267 | ["Moodyman"] = "csb_moodyman_02", 268 | ["Milton McIlroy"] = "cs_milton", 269 | ["Families Gang Member"] = "csb_ramp_gang", 270 | ["Ortega"] = "csb_ortega", 271 | ["Mimi"] = "csb_mimi", 272 | ["Josh"] = "cs_josh", 273 | ["Jio"] = "csb_jio", 274 | ["Cop"] = "csb_cop", 275 | ["Mrs. Phillips"] = "cs_mrsphillips", 276 | ["Mexican"] = "csb_ramp_mex", 277 | ["Car 3 Guy 2"] = "csb_car3guy2", 278 | ["Agatha Baker"] = "csb_agatha", 279 | ["Anton Beaudelaire"] = "csb_anton", 280 | ["Car Buyer"] = "cs_carbuyer" 281 | }, 282 | ["Others"] = { 283 | ["Jewel Heist Gunman"] = "hc_gunman", 284 | ["Jewel Heist Driver"] = "hc_driver", 285 | ["Jewel Heist Hacker"] = "hc_hacker" 286 | }, 287 | ["Scenario Male"] = { 288 | ["Postal Worker Male 2"] = "s_m_m_postal_02", 289 | ["IAA Security"] = "s_m_m_ciasec_01", 290 | ["Marine"] = "s_m_m_marine_01", 291 | ["Dealer"] = "s_m_y_dealer_01", 292 | ["High Security 2"] = "s_m_m_highsec_02", 293 | ["Life Invader Male"] = "s_m_m_lifeinvad_01", 294 | ["Movie Astronaut"] = "s_m_m_movspace_01", 295 | ["Street Vendor"] = "s_m_m_strvend_01", 296 | ["Street Vendor Young"] = "s_m_y_strvend_01", 297 | ["Snow Cop Male"] = "s_m_m_snowcop_01", 298 | ["Pilot"] = "s_m_y_pilot_01", 299 | ["Clown"] = "s_m_y_clown_01", 300 | ["FIB Office Worker 2"] = "s_m_m_fiboffice_02", 301 | ["Casino Staff"] = "s_m_y_casino_01", 302 | ["Army Mechanic"] = "s_m_y_armymech_01", 303 | ["Doctor"] = "s_m_m_doctor_01", 304 | ["Hairdresser Male"] = "s_m_m_hairdress_01", 305 | ["Ammu-Nation Rural Clerk"] = "s_m_m_ammucountry", 306 | ["FIB Office Worker"] = "s_m_m_fiboffice_01", 307 | ["Baywatch Male"] = "s_m_y_baywatch_01", 308 | ["Bouncer 2"] = "s_m_m_bouncer_02", 309 | ["Pest Control"] = "s_m_y_pestcont_01", 310 | ["Street Preacher"] = "s_m_m_strpreach_01", 311 | ["Busker"] = "s_m_o_busker_01", 312 | ["DW Airport Worker"] = "s_m_y_dwservice_01", 313 | ["Mariachi"] = "s_m_m_mariachi_01", 314 | ["Latino Handyman Male"] = "s_m_m_lathandy_01", 315 | ["Doorman"] = "s_m_y_doorman_01", 316 | ["High Security"] = "s_m_m_highsec_01", 317 | ["Black Ops Soldier"] = "s_m_y_blackops_01", 318 | ["High Security 3"] = "s_m_m_highsec_03", 319 | ["Warehouse Technician"] = "s_m_y_waretech_01", 320 | ["Factory Worker Male"] = "s_m_y_factory_01", 321 | ["Postal Worker Male"] = "s_m_m_postal_01", 322 | ["Crew Member"] = "s_m_m_ccrew_01", 323 | ["Prisoner"] = "s_m_y_prisoner_01", 324 | ["Duggan Secruity"] = "s_m_y_westsec_01", 325 | ["Cop Male"] = "s_m_y_cop_01", 326 | ["Devin's Security"] = "s_m_y_devinsec_01", 327 | ["DW Airport Worker 2"] = "s_m_y_dwservice_02", 328 | ["Prisoner (Muscular)"] = "s_m_y_prismuscl_01", 329 | ["Busboy"] = "s_m_y_busboy_01", 330 | ["Window Cleaner"] = "s_m_y_winclean_01", 331 | ["Chef"] = "s_m_y_chef_01", 332 | ["Waiter"] = "s_m_y_waiter_01", 333 | ["Robber"] = "s_m_y_robber_01", 334 | ["Field Worker"] = "s_m_m_fieldworker_01", 335 | ["FIB Security"] = "s_m_m_fibsec_01", 336 | ["Movie Premiere Male"] = "s_m_m_movprem_01", 337 | ["US Coastguard"] = "s_m_y_uscg_01", 338 | ["Highway Cop"] = "s_m_y_hwaycop_01", 339 | ["Security Guard"] = "s_m_m_security_01", 340 | ["Autoshop Worker 3"] = "s_m_m_Autoshop_03", 341 | ["Bartender (Rural)"] = "s_m_m_cntrybar_01", 342 | ["Line Cook"] = "s_m_m_linecook", 343 | ["UPS Driver 2"] = "s_m_m_ups_02", 344 | ["UPS Driver"] = "s_m_m_ups_01", 345 | ["Sheriff Male"] = "s_m_y_sheriff_01", 346 | ["Trucker Male"] = "s_m_m_trucker_01", 347 | ["Air Worker Male"] = "s_m_y_airworker", 348 | ["Ammu-Nation City Clerk"] = "s_m_y_ammucity_01", 349 | ["Grip"] = "s_m_y_grip_01", 350 | ["Marine Young 3"] = "s_m_y_marine_03", 351 | ["SWAT"] = "s_m_y_swat_01", 352 | ["Marine Young 2"] = "s_m_y_marine_02", 353 | ["Bouncer"] = "s_m_m_bouncer_01", 354 | ["Armoured Van Security"] = "s_m_m_armoured_01", 355 | ["Scientist"] = "s_m_m_scientist_01", 356 | ["Mask Salesman"] = "s_m_y_shop_mask", 357 | ["Mime Artist"] = "s_m_y_mime", 358 | ["Street Performer"] = "s_m_m_strperf_01", 359 | ["Janitor"] = "s_m_m_janitor", 360 | ["Club Bartender Male"] = "s_m_y_clubbar_01", 361 | ["Dock Worker"] = "s_m_y_dockwork_01", 362 | ["Fireman Male"] = "s_m_y_fireman_01", 363 | ["Valet"] = "s_m_y_valet_01", 364 | ["Marine Young"] = "s_m_y_marine_01", 365 | ["Ranger Male"] = "s_m_y_ranger_01", 366 | ["Migrant Male"] = "s_m_m_migrant_01", 367 | ["Autoshop Worker 2"] = "s_m_m_autoshop_02", 368 | ["Black Ops Soldier 3"] = "s_m_y_blackops_03", 369 | ["Duggan Security 2"] = "s_m_y_westsec_02", 370 | ["Barman"] = "s_m_y_barman_01", 371 | ["Mechanic"] = "s_m_y_xmech_01", 372 | ["Paramedic"] = "s_m_m_paramedic_01", 373 | ["Garbage Worker"] = "s_m_y_garbage", 374 | ["Racer Organisator"] = "s_m_m_raceorg_01", 375 | ["Alien"] = "s_m_m_movalien_01", 376 | ["Marine 2"] = "s_m_m_marine_02", 377 | ["construction Worker 2"] = "s_m_y_construct_02", 378 | ["Autopsy Tech"] = "s_m_y_autopsy_01", 379 | ["Autoshop Worker"] = "s_m_m_autoshop_01", 380 | ["Gaffer"] = "s_m_m_gaffer_01", 381 | ["LS Metro Worker Male"] = "s_m_m_lsmetro_01", 382 | ["High Security 4"] = "s_m_m_highsec_04", 383 | ["Black Ops Soldier 2"] = "s_m_y_blackops_02", 384 | ["Transport Worker Male"] = "s_m_m_gentransport", 385 | ["Armoured Van Security 2"] = "s_m_m_armoured_02", 386 | ["Gardener"] = "s_m_m_gardener_01", 387 | ["construction Worker"] = "s_m_y_construct_01", 388 | ["Prison Guard"] = "s_m_m_prisguard_01", 389 | ["Drug Processer"] = "s_m_m_drugprocess_01", 390 | ["Pilot 2"] = "s_m_m_pilot_02", 391 | ["Tattoo Artist 2"] = "s_m_m_tattoo_01", 392 | ["MC Clubhouse Mechanic"] = "s_m_y_xmech_02", 393 | ["Chemical Plant Security"] = "s_m_m_chemsec_01" 394 | }, 395 | ["Story Scenario Female"] = { 396 | ["Miranda"] = "u_f_m_miranda", 397 | ["Corpse Young Female"] = "u_f_y_corpse_01", 398 | ["Casino Cashier"] = "u_f_m_casinocash_01", 399 | ["Female Club Dancer (Rave)"] = "u_f_y_dancerave_01", 400 | ["Poppy Mitchell"] = "u_f_y_poppymich", 401 | ["Jeweller Assistant"] = "u_f_y_jewelass_01", 402 | ["Prologue Mourner Female"] = "u_f_m_promourn_01", 403 | ["Poppy Mitchell 2"] = "u_f_y_poppymich_02", 404 | ["Biker Chic Female"] = "u_f_y_bikerchic", 405 | ["Jane"] = "u_f_y_comjane", 406 | ["Casino shop owner"] = "u_f_m_casinoshop_01", 407 | ["Carol"] = "u_f_o_carol", 408 | ["Spy Actress"] = "u_f_y_spyactress", 409 | ["Eileen"] = "u_f_o_eileen", 410 | ["Female Club Dancer (Burlesque)"] = "u_f_y_danceburl_01", 411 | ["Prologue Host Old Female"] = "u_f_o_prolhost_01", 412 | ["Debbie (Agatha´s Secretary)"] = "u_f_m_debbie_01", 413 | ["Movie Star Female"] = "u_f_o_moviestar", 414 | ["Taylor"] = "u_f_y_taylor", 415 | ["Female Club Dancer (Leather)"] = "u_f_y_dancelthr_01", 416 | ["Mistress"] = "u_f_y_mistress", 417 | ["Corpse Young Female 2"] = "u_f_y_corpse_02", 418 | ["Miranda 2"] = "u_f_m_miranda_02", 419 | ["Lauren"] = "u_f_y_lauren", 420 | ["Princess"] = "u_f_y_princess", 421 | ["Corpse Female"] = "u_f_m_corpse_01", 422 | ["Hot Posh Female"] = "u_f_y_hotposh_01", 423 | ["Beth"] = "u_f_y_beth" 424 | }, 425 | ["Scenario Female"] = { 426 | ["Factory Worker Female"] = "s_f_y_factory_01", 427 | ["Sales Assistant (Low-End)"] = "s_f_y_shop_low", 428 | ["Barber Female"] = "s_f_m_fembarber", 429 | ["Air Hostess"] = "s_f_y_airhostess_01", 430 | ["Hooker 3"] = "s_f_y_hooker_03", 431 | ["Sales Assistant (Mid-Price)"] = "s_f_y_shop_mid", 432 | ["Movie Premiere Female"] = "s_f_y_movprem_01", 433 | ["Autoshop Worker Female"] = "s_f_m_autoshop_01", 434 | ["Club Bartender Female 2"] = "s_f_y_clubbar_02", 435 | ["Stripper 2"] = "s_f_y_stripper_02", 436 | ["Hospital Scrubs Female"] = "s_f_y_scrubs_01", 437 | ["Club Bartender Female"] = "s_f_y_clubbar_01", 438 | ["Sweatshop Worker Young"] = "s_f_y_sweatshop_01", 439 | ["Maid"] = "s_f_m_maid_01", 440 | ["Stripper"] = "s_f_y_stripper_01", 441 | ["Stripper Lite"] = "s_f_y_stripperlite", 442 | ["Sheriff Female"] = "s_f_y_sheriff_01", 443 | ["Migrant Female"] = "s_f_y_migrant_01", 444 | ["Sweatshop Worker"] = "s_f_m_sweatshop_01", 445 | ["Sales Assistant (High-End)"] = "s_f_m_shop_high", 446 | ["Casino Staff"] = "s_f_y_casino_01", 447 | ["Baywatch Female"] = "s_f_y_baywatch_01", 448 | ["Retailstaff"] = "s_f_m_retailstaff_01", 449 | ["Cop Female"] = "s_f_y_cop_01", 450 | ["Beach Bar Staff"] = "s_f_y_beachbarstaff_01", 451 | ["Bartender"] = "s_f_y_bartender_01", 452 | ["Ranger Female"] = "s_f_y_ranger_01", 453 | ["Hooker 2"] = "s_f_y_hooker_02", 454 | ["Hooker"] = "s_f_y_hooker_01" 455 | }, 456 | ["Gang Female"] = { 457 | ["Ballas Female"] = "g_f_y_ballas_01", 458 | ["Gang Female (Import-Export)"] = "g_f_importexport_01", 459 | ["Import Export Female"] = "g_f_importexport_01", 460 | ["Vagos Female"] = "g_f_y_vagos_01", 461 | ["The Lost MC Female"] = "g_f_y_lost_01", 462 | ["Families Female"] = "g_f_y_families_01" 463 | }, 464 | ["Story"] = { 465 | ["Brucie Kibbutz"] = "ig_brucie2", 466 | ["DJ Rupert"] = "ig_djblamrupert", 467 | ["Lester Crest"] = "ig_lestercrest", 468 | ["Cletus"] = "ig_cletus", 469 | ["Benny"] = "ig_benny", 470 | ["Clay Simons (The Lost)"] = "ig_clay", 471 | ["Dale"] = "ig_dale", 472 | ["United Paper Man"] = "ig_paper", 473 | ["Island Dj 4D2"] = "ig_isldj_04_D_02", 474 | ["Sacha Yetarian"] = "ig_sacha", 475 | ["Simeon Yetarian"] = "ig_siemonyetarian", 476 | ["Tao Cheng"] = "ig_taocheng", 477 | ["Mary-Ann Quinn"] = "ig_maryann", 478 | ["Mrs. Thornhill"] = "ig_mrs_thornhill", 479 | ["Tennis Coach"] = "ig_tenniscoach", 480 | ["Nervous Ron"] = "ig_nervousron", 481 | ["FIB Suit"] = "ig_fbisuit_01", 482 | ["Steve Haines"] = "ig_stevehains", 483 | ["DJ Black Madonna"] = "ig_djblamadon", 484 | ["Hipster"] = "ig_ramp_hipster", 485 | ["Vincent (Casino)"] = "ig_vincent", 486 | ["Jackie"] = "ig_jackie", 487 | ["Mjo"] = "ig_mjo", 488 | ["Tony Prince"] = "ig_tonyprince", 489 | ["Milton McIlroy"] = "ig_milton", 490 | ["Dave Norton"] = "ig_davenorton", 491 | ["Michelle"] = "ig_michelle", 492 | ["Stretch"] = "ig_stretch", 493 | ["Vincent (Casino) 2"] = "ig_vincent_2", 494 | ["Hick"] = "ig_ramp_hic", 495 | ["Manuel"] = "ig_manuel", 496 | ["Casey"] = "ig_casey", 497 | ["Cris Formage"] = "ig_chrisformage", 498 | ["Agent 14"] = "ig_mp_agent14", 499 | ["Ballas OG"] = "ig_ballasog", 500 | ["Marnie Allen"] = "ig_marnie", 501 | ["Jimmy Boston"] = "ig_jimmyboston", 502 | ["Avi Schawrtzman"] = "ig_avischwartzman_02", 503 | ["Soloman Manager"] = "ig_djsolmanager", 504 | ["Thornton Duggan"] = "ig_thornton", 505 | ["Maude"] = "ig_maude", 506 | ["Sessanta"] = "ig_sessanta", 507 | ["Lil Dee"] = "ig_lildee", 508 | ["Miguel Madrazo"] = "ig_miguelmadrazo", 509 | ["Island Dj 1"] = "ig_isldj_01", 510 | ["Clay Jackson (The Pain Giver)"] = "ig_claypain", 511 | ["Minuteman Joe"] = "ig_joeminuteman", 512 | ["Denise"] = "ig_denise", 513 | ["Island Dj 4"] = "ig_isldj_04", 514 | ["Ferdinand Kerimov (Mr. K)"] = "ig_mrk", 515 | ["Dom Beasley"] = "ig_dom", 516 | ["Agent"] = "ig_agent", 517 | ["DJ Fotios"] = "ig_djsolfotios", 518 | ["DJ Rob T"] = "ig_djsolrobt", 519 | ["Peter Dreyfuss"] = "ig_dreyfuss", 520 | ["Dima Popov"] = "ig_popov", 521 | ["Dr. Friedlander"] = "ig_drfriedlander", 522 | ["Wade"] = "ig_wade", 523 | ["Island Dj"] = "ig_isldj_00", 524 | ["Agatha Baker"] = "ig_agatha", 525 | ["Ashley Butler"] = "ig_ashley", 526 | ["Barry"] = "ig_barry", 527 | ["Tale of Us 1"] = "ig_talcc", 528 | ["Fabien"] = "ig_fabien", 529 | ["Jeweller Assistant"] = "ig_jewelass", 530 | ["Johnny Klebitz"] = "ig_johnnyklebitz", 531 | ["Tao Cheng (Casino)"] = "ig_taocheng2", 532 | ["Best Man"] = "ig_bestmen", 533 | ["Kerry McIntosh"] = "ig_kerrymcintosh", 534 | ["Pilot"] = "ig_pilot", 535 | ["Wendy"] = "ig_wendy", 536 | ["Lester Crest 3"] = "ig_lestercrest_3", 537 | ["Franklin"] = "player_one", 538 | ["Abigail Mathers"] = "ig_abigail", 539 | ["Jimmy De Santa 2"] = "ig_jimmydisanto2", 540 | ["Ary"] = "ig_ary", 541 | ["Tom Connors"] = "ig_tomcasino", 542 | ["Georgina Cheng"] = "ig_georginacheng", 543 | ["Car 3 Guy 1"] = "ig_car3guy1", 544 | ["Benny (Los Santos Tuners)"] = "ig_benny_02", 545 | ["Tao's Translator (Casino)"] = "ig_taostranslator2", 546 | ["Floyd Hebert"] = "ig_floyd", 547 | ["Malc"] = "ig_malc", 548 | ["Drugdealer"] = "ig_drugdealer", 549 | ["Old Man 2"] = "ig_old_man2", 550 | ["Dixon"] = "ig_dix", 551 | ["Celeb 1"] = "ig_celeb_01", 552 | ["Huang"] = "ig_huang", 553 | ["Old Man 1"] = "ig_old_man1a", 554 | ["Lester Crest (Doomsday Heist)"] = "ig_lestercrest_2", 555 | ["Patricia 2"] = "ig_patricia_02", 556 | ["Tyler Dixon 2"] = "ig_tylerdix_02", 557 | ["Janet"] = "ig_janet", 558 | ["Tale of Us 2"] = "ig_talmm", 559 | ["Money Man"] = "ig_money", 560 | ["Soloman"] = "ig_sol", 561 | ["Jimmy De Santa"] = "ig_jimmydisanto", 562 | ["Maxim Rashkovsky"] = "ig_rashcosvki", 563 | ["Helmsman Pavel"] = "ig_helmsmanpavel", 564 | ["Lazlow 2"] = "ig_lazlow_2", 565 | ["Lacy Jones 2"] = "ig_lacey_jones_02", 566 | ["Kerry McIntosh 2"] = "ig_kerrymcintosh_02", 567 | ["Jimmy Boston 2"] = "ig_jimmyboston_02", 568 | ["DJ Dixon Manager"] = "ig_djdixmanager", 569 | ["Island Dj 4E"] = "ig_isldj_04_E_01", 570 | ["Tonya"] = "ig_tonya", 571 | ["English Dave"] = "ig_englishdave_02", 572 | ["Life Invader"] = "ig_lifeinvad_01", 573 | ["Karen Daniels"] = "ig_karen_daniels", 574 | ["Island Dj 3"] = "ig_isldj_03", 575 | ["Molly"] = "ig_molly", 576 | ["Beverly Felton"] = "ig_beverly", 577 | [" DJ Aurelia"] = "ig_djtalaurelia", 578 | ["Kaylee"] = "ig_kaylee", 579 | ["Nigel"] = "ig_nigel", 580 | ["Bank Manager"] = "ig_bankman", 581 | ["Chef"] = "ig_chef2", 582 | ["DJ Ignazio"] = "ig_djtalignazio", 583 | ["Priest"] = "ig_priest", 584 | ["Mimi"] = "ig_mimi", 585 | ["Bigfoot"] = "ig_orleans", 586 | ["DJ Jakob"] = "ig_djsoljakob", 587 | ["DJ Ryan S"] = "ig_djblamryans", 588 | ["DJ Mike T"] = "ig_djsolmike", 589 | ["Wei Cheng"] = "ig_chengsr", 590 | ["Vagos Funeral Speaker"] = "ig_vagspeak", 591 | ["Jio"] = "ig_jio", 592 | ["Life Invader 2"] = "ig_lifeinvad_02", 593 | ["Traffic Warden"] = "ig_trafficwarden", 594 | ["Tracey De Santa"] = "ig_tracydisanto", 595 | ["Groom"] = "ig_groom", 596 | ["Tao's Translator"] = "ig_taostranslator", 597 | ["Trevor"] = "player_two", 598 | ["Tanisha"] = "ig_tanisha", 599 | ["Talina"] = "ig_talina", 600 | ["Hao"] = "ig_hao", 601 | ["Lazlow"] = "ig_lazlow", 602 | ["Bride"] = "ig_bride", 603 | ["Jay Norris"] = "ig_jay_norris", 604 | ["Sss"] = "ig_sss", 605 | ["Patricia"] = "ig_patricia", 606 | ["Island Dj 2"] = "ig_isldj_02", 607 | ["Russian Drunk"] = "ig_russiandrunk", 608 | ["Gerald"] = "ig_g", 609 | ["Solomon Richards"] = "ig_solomon", 610 | ["Screenwriter"] = "ig_screen_writer", 611 | ["Generic DJ"] = "ig_djgeneric_01", 612 | ["Rocco Pelosi"] = "ig_roccopelosi", 613 | ["Prologue Security 2"] = "ig_prolsec_02", 614 | ["Gustavo"] = "ig_gustavo", 615 | ["Terry"] = "ig_terry", 616 | ["Paige Harris"] = "ig_paige", 617 | ["Avon Hertz"] = "ig_avon", 618 | ["Andreas Sanchez"] = "ig_andreas", 619 | ["Moodyman"] = "ig_moodyman_02", 620 | ["O'Neil Brothers"] = "ig_oneil", 621 | ["Lamar Davis"] = "ig_lamardavis", 622 | ["Josef"] = "ig_josef", 623 | ["Zimbor"] = "ig_zimbor", 624 | ["Avery Duggan"] = "ig_avery", 625 | ["Old Rich Guy"] = "ig_oldrichguy", 626 | ["Island Dj 4D"] = "ig_isldj_04_D_01", 627 | ["Omega"] = "ig_omega", 628 | ["Magenta"] = "ig_magenta", 629 | ["Brad"] = "ig_brad", 630 | ["Amanda De Santa"] = "ig_amandatownley", 631 | ["Natalia"] = "ig_natalia", 632 | ["Hunter"] = "ig_hunter", 633 | ["Ortega"] = "ig_ortega", 634 | ["Josh"] = "ig_josh", 635 | ["Epsilon Tom"] = "ig_tomepsilon", 636 | ["Devin"] = "ig_devin", 637 | ["Juan Strickler"] = "ig_juanstrickler", 638 | ["Mrs. Phillips"] = "ig_mrsphillips", 639 | ["Mexican"] = "ig_ramp_mex", 640 | ["Car 3 Guy 2"] = "ig_car3guy2", 641 | ["Families Gang Member"] = "ig_ramp_gang", 642 | ["Michael"] = "player_zero", 643 | ["Tyler Dixon"] = "ig_tylerdix" 644 | }, 645 | ["Gang Male"] = { 646 | ["Polynesian Goon 2"] = "g_m_y_pologoon_02", 647 | ["Armenian Lieutenant"] = "g_m_m_armlieut_01", 648 | ["The Lost MC Male 2"] = "g_m_y_lost_02", 649 | ["Mexican Boss"] = "g_m_m_mexboss_01", 650 | ["Cartel Guard"] = "g_m_m_cartelguards_01", 651 | ["Azteca"] = "g_m_y_azteca_01", 652 | ["Street Punk"] = "g_m_y_strpunk_01", 653 | ["Mexican Goon"] = "g_m_y_mexgoon_01", 654 | ["Armenian Goon 2"] = "g_m_y_armgoon_02", 655 | ["Chinese Goon Older"] = "g_m_m_chicold_01", 656 | ["Casino Guests"] = "g_m_m_casrn_01", 657 | ["Mexican Goon 2"] = "g_m_y_mexgoon_02", 658 | ["Gang Prisoner Male"] = "g_m_m_prisoners_01", 659 | ["Salvadoran Goon 3"] = "g_m_y_salvagoon_03", 660 | ["Armenian Boss"] = "g_m_m_armboss_01", 661 | ["Cartel Guard 2"] = "g_m_m_cartelguards_02", 662 | ["Korean Young Male 2"] = "g_m_y_korean_02", 663 | ["The Lost MC Male"] = "g_m_y_lost_01", 664 | ["Mexican Boss 2"] = "g_m_m_mexboss_02", 665 | ["Families DNF Male"] = "g_m_y_famdnf_01", 666 | ["Street Punk 2"] = "g_m_y_strpunk_02", 667 | ["Chinese Goon"] = "g_m_m_chigoon_01", 668 | ["Families FOR Male"] = "g_m_y_famfor_01", 669 | ["Gang Male (Import-Export)"] = "g_m_importexport_01", 670 | ["The Lost MC Male 3"] = "g_m_y_lost_03", 671 | ["Korean Boss"] = "g_m_m_korboss_01", 672 | ["Salvadoran Goon 2"] = "g_m_y_salvagoon_02", 673 | ["Mexican Gang Member"] = "g_m_y_mexgang_01", 674 | ["Polynesian Goon"] = "g_m_y_pologoon_01", 675 | ["Salvadoran Boss"] = "g_m_y_salvaboss_01", 676 | ["Salvadoran Goon"] = "g_m_y_salvagoon_01", 677 | ["Mexican Goon 3"] = "g_m_y_mexgoon_03", 678 | ["Korean Young Male"] = "g_m_y_korean_01", 679 | ["Ballas Original Male"] = "g_m_y_ballaorig_01", 680 | ["Families CA Male"] = "g_m_y_famca_01", 681 | ["Korean Lieutenant"] = "g_m_y_korlieut_01", 682 | ["Armenian Goon"] = "g_m_m_armgoon_01", 683 | ["Gang Slasher Male"] = "g_m_m_slasher_01", 684 | ["Chinese Boss"] = "g_m_m_chiboss_01", 685 | ["Ballas South Male"] = "g_m_y_ballasout_01", 686 | ["Chinese Goon 2"] = "g_m_m_chigoon_02", 687 | ["Ballas East Male"] = "g_m_y_ballaeast_01", 688 | ["Chemical Plant Worker"] = "g_m_m_chemwork_01" 689 | }, 690 | ["Multiplayer"] = { 691 | ["Boat-Staff Female"] = "mp_f_boatstaff_01", 692 | ["Securoserve Guard (Male)"] = "mp_m_securoguard_01", 693 | ["Bogdan Goon"] = "mp_m_bogdangoon", 694 | ["Dead Hooker"] = "mp_f_deadhooker", 695 | ["Claude Speed"] = "mp_m_claude_01", 696 | ["Biker Meth Male"] = "mp_m_meth_01", 697 | ["Biker Weed Female"] = "mp_f_weed_01", 698 | ["Executive PA Female 2"] = "mp_f_execpa_02", 699 | ["Freemode Male"] = "mp_m_freemode_01", 700 | ["Families DD Male"] = "mp_m_famdd_01", 701 | ["Misty"] = "mp_f_misty_01", 702 | ["Biker Cocaine Female"] = "mp_f_cocaine_01", 703 | ["FIB Security"] = "mp_m_fibsec_01", 704 | ["Shopkeeper (Male)"] = "mp_m_shopkeep_01", 705 | ["Ex-Army Male"] = "mp_m_exarmy_01", 706 | ["Avon Goon"] = "mp_m_avongoon", 707 | ["Biker Meth Female"] = "mp_f_meth_01", 708 | ["Biker Forgery Male"] = "mp_m_forgery_01", 709 | ["Heli-Staff Female"] = "mp_f_helistaff_01", 710 | ["John Marston"] = "mp_m_marston_01", 711 | ["Vagos Funeral"] = "mp_m_g_vagfun_01", 712 | ["Warehouse Mechanic (Male)"] = "mp_m_waremech_01", 713 | ["Armoured Van Security Male"] = "mp_s_m_armoured_01", 714 | ["Biker Weed Male"] = "mp_m_weed_01", 715 | ["Freemode Female"] = "mp_f_freemode_01", 716 | ["Office Garage Mechanic (Female)"] = "mp_f_cardesign_01", 717 | ["Weapon Work (Male)"] = "mp_m_weapwork_01", 718 | ["Weapon Exp (Male)"] = "mp_m_weapexp_01", 719 | ["Executive PA Female"] = "mp_f_execpa_01", 720 | ["Benny Mechanic (Female)"] = "mp_f_bennymech_01", 721 | ["Biker Forgery Female"] = "mp_f_forgery_01", 722 | ["Stripper Lite (Female)"] = "mp_f_stripperlite", 723 | ["Biker Counterfeit Male"] = "mp_m_counterfeit_01", 724 | ["Clubhouse Bar Female"] = "mp_f_chbar_01", 725 | ["Boat-Staff Male"] = "mp_m_boatstaff_01", 726 | ["Niko Bellic"] = "mp_m_niko_01", 727 | ["Biker Cocaine Male"] = "mp_m_cocaine_01", 728 | ["Executive PA Male"] = "mp_m_execpa_01", 729 | ["Biker Counterfeit Female"] = "mp_f_counterfeit_01", 730 | ["Pros"] = "mp_g_m_pros_01", 731 | }, 732 | ["Player"] = { 733 | ["Freemode Female"] = "mp_f_freemode_01", 734 | ["Trevor"] = "player_two", 735 | ["Freemode Male"] = "mp_m_freemode_01", 736 | ["Franklin"] = "player_one", 737 | ["Michael"] = "player_zero" 738 | }, 739 | ["Ambient Male"] = { 740 | ["Road Cyclist"] = "a_m_y_roadcyc_01", 741 | ["South Central Young Male 4"] = "a_m_y_soucent_04", 742 | ["Tourist Male"] = "a_m_m_tourist_01", 743 | ["Transvestite Male 2"] = "a_m_m_tranvest_02", 744 | ["Hippie Male"] = "a_m_y_hippy_01", 745 | ["Hipster Male 2"] = "a_m_y_hipster_02", 746 | ["Business Young Male"] = "a_m_y_business_01", 747 | ["Vinewood Male 4"] = "a_m_y_vinewood_04", 748 | ["Vinewood Male 3"] = "a_m_y_vinewood_03", 749 | ["Beach Muscle Male"] = "a_m_y_musclbeac_01", 750 | ["Golfer Male"] = "a_m_m_golfer_01", 751 | ["Gay Male 2"] = "a_m_y_gay_02", 752 | ["Farmer"] = "a_m_m_farmer_01", 753 | ["Business Casual"] = "a_m_y_busicas_01", 754 | ["Beach Male 2"] = "a_m_m_beach_02", 755 | ["Beverly Hills Male"] = "a_m_m_bevhills_01", 756 | ["Hiker Male"] = "a_m_y_hiker_01", 757 | ["South Central Young Male 2"] = "a_m_y_soucent_02", 758 | ["Epsilon Male"] = "a_m_y_epsilon_01", 759 | ["Korean Young Male"] = "a_m_y_ktown_01", 760 | ["Skater Young Male"] = "a_m_y_skater_01", 761 | ["Hipster Male 3"] = "a_m_y_hipster_03", 762 | ["Car Club Male"] = "a_m_y_carclub_01", 763 | ["Fat Latino Male"] = "a_m_m_fatlatin_01", 764 | ["Altruist Cult Young Male 2"] = "a_m_y_acult_02", 765 | ["Hillbilly Male 2"] = "a_m_m_hillbilly_02", 766 | ["Hasidic Jew Male"] = "a_m_m_hasjew_01", 767 | ["Beach Male"] = "a_m_m_beach_01", 768 | ["Black Street Male 2"] = "a_m_y_stbla_02", 769 | ["Mexican Thug"] = "a_m_y_mexthug_01", 770 | ["General Street Young Male 2"] = "a_m_y_genstreet_02", 771 | ["Breakdancer Male"] = "a_m_y_breakdance_01", 772 | ["Skater Male"] = "a_m_m_skater_01", 773 | ["Golfer Young Male"] = "a_m_y_golfer_01", 774 | ["Salton Male"] = "a_m_m_salton_01", 775 | ["South Central Young Male 3"] = "a_m_y_soucent_03", 776 | ["Hillbilly Male"] = "a_m_m_hillbilly_01", 777 | ["Beach Young Male"] = "a_m_y_beach_01", 778 | ["Korean Young Male 2"] = "a_m_y_ktown_02", 779 | ["Jogger Male 2"] = "a_m_y_runner_02", 780 | ["Epsilon Male 2"] = "a_m_y_epsilon_02", 781 | ["Korean Old Male"] = "a_m_o_ktown_01", 782 | ["Tramp Old Male"] = "a_m_o_tramp_01", 783 | ["Business Young Male 3"] = "a_m_y_business_03", 784 | ["Beverly Hills Young Male 2"] = "a_m_y_bevhills_02", 785 | ["Polynesian"] = "a_m_m_polynesian_01", 786 | ["Hipster Male"] = "a_m_y_hipster_01", 787 | ["Beach Young Male 4"] = "a_m_y_beach_04", 788 | ["Indian Young Male"] = "a_m_y_indian_01", 789 | ["Salton Young Male"] = "a_m_y_salton_01", 790 | ["Indian Male"] = "a_m_m_indian_01", 791 | ["Salton Male 3"] = "a_m_m_salton_03", 792 | ["East SA Male"] = "a_m_m_eastsa_01", 793 | ["Rural Meth Addict Male"] = "a_m_m_rurmeth_01", 794 | ["Polynesian Young"] = "a_m_y_polynesian_01", 795 | ["Mexican Rural"] = "a_m_m_mexcntry_01", 796 | ["Sunbather Male"] = "a_m_y_sunbathe_01", 797 | ["Korean Male"] = "a_m_m_ktown_01", 798 | ["Surfer"] = "a_m_y_surfer_01", 799 | ["South Central Old Male 2"] = "a_m_o_soucent_02", 800 | ["Club Customer Male 3"] = "a_m_y_clubcust_03", 801 | ["Black Street Male"] = "a_m_y_stbla_01", 802 | ["Beach Tramp Male"] = "a_m_m_trampbeac_01", 803 | ["Altruist Cult Old Male 2"] = "a_m_o_acult_02", 804 | ["Meth Addict"] = "a_m_y_methhead_01", 805 | ["Midlife Crisis Casino Bikers"] = "a_m_m_mlcrisis_01", 806 | ["Club Customer Male 4"] = "a_m_y_clubcust_04", 807 | ["Salton Male 4"] = "a_m_m_salton_04", 808 | ["Formel Casino Guests"] = "a_m_y_smartcaspat_01", 809 | ["Tramp Male"] = "a_m_m_tramp_01", 810 | ["Downhill Cyclist"] = "a_m_y_dhill_01", 811 | ["Altruist Cult Young Male"] = "a_m_y_acult_01", 812 | ["Gay Male"] = "a_m_y_gay_01", 813 | ["Latino Young Male"] = "a_m_y_latino_01", 814 | ["Beach Old Male"] = "a_m_o_beach_01", 815 | ["Tattoo Cust Male"] = "a_m_y_tattoocust_01", 816 | ["Jogger Male"] = "a_m_y_runner_01", 817 | ["East SA Young Male"] = "a_m_y_eastsa_01", 818 | ["African American Male"] = "a_m_m_afriamer_01", 819 | ["General Street Old Male"] = "a_m_o_genstreet_01", 820 | ["General Street Young Male"] = "a_m_y_genstreet_01", 821 | ["Yoga Male"] = "a_m_y_yoga_01", 822 | ["Paparazzi Male"] = "a_m_m_paparazzi_01", 823 | ["Club Customer Male 1"] = "a_m_y_clubcust_01", 824 | ["White Street Male 2"] = "a_m_y_stwhi_02", 825 | ["White Street Male"] = "a_m_y_stwhi_01", 826 | ["Beach Young Male 3"] = "a_m_y_beach_03", 827 | ["Vinewood Male 2"] = "a_m_y_vinewood_02", 828 | ["Hasidic Jew Young Male"] = "a_m_y_hasjew_01", 829 | ["Vinewood Male"] = "a_m_y_vinewood_01", 830 | ["Altruist Cult Mid-Age Male"] = "a_m_m_acult_01", 831 | ["Jetskier"] = "a_m_y_jetski_01", 832 | ["Vinewood Douche"] = "a_m_y_vindouche_01", 833 | ["East SA Male 2"] = "a_m_m_eastsa_02", 834 | ["Malibu Male"] = "a_m_m_malibu_01", 835 | ["Vespucci Beach Male 2"] = "a_m_y_beachvesp_02", 836 | ["Downtown Male"] = "a_m_y_downtown_01", 837 | ["General Fat Male"] = "a_m_m_genfat_01", 838 | ["General Fat Male 2"] = "a_m_m_genfat_02", 839 | ["Vespucci Beach Male"] = "a_m_y_beachvesp_01", 840 | ["Business Young Male 2"] = "a_m_y_business_02", 841 | ["Beverly Hills Male 2"] = "a_m_m_bevhills_02", 842 | ["Transvestite Male"] = "a_m_m_tranvest_01", 843 | ["South Central Young Male"] = "a_m_y_soucent_01", 844 | ["OG Boss"] = "a_m_m_og_boss_01", 845 | ["Tennis Player Male"] = "a_m_m_tennis_01", 846 | ["Altruist Cult Old Male"] = "a_m_o_acult_01", 847 | ["Beach Old Male 2"] = "a_m_o_beach_02", 848 | ["South Central Old Male"] = "a_m_o_soucent_01", 849 | ["Latino Street Young Male"] = "a_m_y_stlat_01", 850 | ["Beach Young Male 2"] = "a_m_y_beach_02", 851 | ["South Central Old Male 3"] = "a_m_o_soucent_03", 852 | ["Juggalo Male"] = "a_m_y_juggalo_01", 853 | ["Skid Row Male"] = "a_m_m_skidrow_01", 854 | ["South Central Male 4"] = "a_m_m_soucent_04", 855 | ["South Central Male 3"] = "a_m_m_soucent_03", 856 | ["South Central Male 2"] = "a_m_m_soucent_02", 857 | ["South Central Male"] = "a_m_m_soucent_01", 858 | ["Beverly Hills Young Male"] = "a_m_y_bevhills_01", 859 | ["Business Male"] = "a_m_m_business_01", 860 | ["Salton Male 2"] = "a_m_m_salton_02", 861 | ["South Central Latino Male"] = "a_m_m_socenlat_01", 862 | ["Salton Old Male"] = "a_m_o_salton_01", 863 | ["Skater Young Male 2"] = "a_m_y_skater_02", 864 | ["East SA Young Male 2"] = "a_m_y_eastsa_02", 865 | ["Mexican Labourer"] = "a_m_m_mexlabor_01", 866 | ["Prologue Host Male"] = "a_m_m_prolhost_01", 867 | ["Club Customer Male 2"] = "a_m_y_clubcust_02", 868 | ["Casual Casino Guests"] = "a_m_y_gencaspat_01", 869 | ["Motocross Biker 2"] = "a_m_y_motox_02", 870 | ["Beach Muscle Male 2"] = "a_m_y_musclbeac_02", 871 | ["Latino Street Male 2"] = "a_m_m_stlat_02", 872 | ["Cyclist Male"] = "a_m_y_cyclist_01", 873 | ["Motocross Biker"] = "a_m_y_motox_01" 874 | }, 875 | ["Ambient Female"] = { 876 | ["Salton Old Female"] = "a_f_o_salton_01", 877 | ["Business Young Female"] = "a_f_y_business_01", 878 | ["Skater Female"] = "a_f_y_skater_01", 879 | ["Juggalo Female"] = "a_f_y_juggalo_01", 880 | ["East SA Young Female 3"] = "a_f_y_eastsa_03", 881 | ["South Central Young Female 2"] = "a_f_y_soucent_02", 882 | ["East SA Female"] = "a_f_m_eastsa_01", 883 | ["Fat White Female"] = "a_f_m_fatwhite_01", 884 | ["Fitness Female"] = "a_f_y_fitness_01", 885 | ["Hipster Female 2"] = "a_f_y_hipster_02", 886 | ["Casual Casino Guest"] = "a_f_y_gencaspat_01", 887 | ["Beverly Hills Young Female 4"] = "a_f_y_bevhills_04", 888 | ["Downtown Female"] = "a_f_m_downtown_01", 889 | ["General Hot Young Female"] = "a_f_y_genhot_01", 890 | ["South Central Young Female"] = "a_f_y_soucent_01", 891 | ["Fat Cult Female"] = "a_f_m_fatcult_01", 892 | ["Hiker Female"] = "a_f_y_hiker_01", 893 | ["Club Customer Female 1"] = "a_f_y_clubcust_01", 894 | ["Formel Casino Guest"] = "a_f_y_smartcaspat_01", 895 | ["Beverly Hills Young Female 3"] = "a_f_y_bevhills_03", 896 | ["East SA Female 2"] = "a_f_m_eastsa_02", 897 | ["Vinewood Female 2"] = "a_f_y_vinewood_02", 898 | ["Indian Young Female"] = "a_f_y_indian_01", 899 | ["Tourist Female"] = "a_f_m_tourist_01", 900 | ["Beverly Hills Young Female 2"] = "a_f_y_bevhills_02", 901 | ["Beverly Hills Female"] = "a_f_m_bevhills_01", 902 | ["Club Customer Female 2"] = "a_f_y_clubcust_02", 903 | ["Business Young Female 3"] = "a_f_y_business_03", 904 | ["Dressy Female"] = "a_f_y_scdressy_01", 905 | ["Beach Young Female 2"] = "a_f_y_beach_02", 906 | ["Yoga Female"] = "a_f_y_yoga_01", 907 | ["East SA Young Female"] = "a_f_y_eastsa_01", 908 | ["Business Female 2"] = "a_f_m_business_02", 909 | ["Skid Row Female"] = "a_f_m_skidrow_01", 910 | ["South Central Female"] = "a_f_m_soucent_01", 911 | ["South Central MC Female"] = "a_f_m_soucentmc_01", 912 | ["Car Club Female"] = "a_f_y_carclub_01", 913 | ["Beach Young Female"] = "a_f_y_beach_01", 914 | ["Hipster Female 3"] = "a_f_y_hipster_03", 915 | ["Beach Female"] = "a_f_m_beach_01", 916 | ["Tramp Female"] = "a_f_m_tramp_01", 917 | ["Korean Female"] = "a_f_m_ktown_01", 918 | ["Beverly Hills Female 2"] = "a_f_m_bevhills_02", 919 | ["Indian Old Female"] = "a_f_o_indian_01", 920 | ["Golfer Young Female"] = "a_f_y_golfer_01", 921 | ["Tourist Young Female 2"] = "a_f_y_tourist_02", 922 | ["Fat Black Female"] = "a_f_m_fatbla_01", 923 | ["Beverly Hills Young Female 5"] = "a_f_y_bevhills_05", 924 | ["Club Customer Female 4"] = "a_f_y_clubcust_04", 925 | ["Club Customer Female 3"] = "a_f_y_clubcust_03", 926 | ["Vinewood Female 4"] = "a_f_y_vinewood_04", 927 | ["Vinewood Female 3"] = "a_f_y_vinewood_03", 928 | ["Vinewood Female"] = "a_f_y_vinewood_01", 929 | ["Tourist Young Female"] = "a_f_y_tourist_01", 930 | ["Hipster Female"] = "a_f_y_hipster_01", 931 | ["Bodybuilder Female"] = "a_f_m_bodybuild_01", 932 | ["Business Young Female 2"] = "a_f_y_business_02", 933 | ["Korean Old Female"] = "a_f_o_ktown_01", 934 | ["Epsilon Female"] = "a_f_y_epsilon_01", 935 | ["South Central Old Female"] = "a_f_o_soucent_01", 936 | ["Jogger Female"] = "a_f_y_runner_01", 937 | ["Female Agent"] = "a_f_y_femaleagent", 938 | ["Beverly Hills Young Female"] = "a_f_y_bevhills_01", 939 | ["South Central Young Female 3"] = "a_f_y_soucent_03", 940 | ["Hipster Female 4"] = "a_f_y_hipster_04", 941 | ["Prologue Host Female"] = "a_f_m_prolhost_01", 942 | ["South Central Old Female 2"] = "a_f_o_soucent_02", 943 | ["Tennis Player Female"] = "a_f_y_tennis_01", 944 | ["South Central Female 2"] = "a_f_m_soucent_02", 945 | ["Business Young Female 4"] = "a_f_y_business_04", 946 | ["Fitness Female 2"] = "a_f_y_fitness_02", 947 | ["East SA Young Female 2"] = "a_f_y_eastsa_02", 948 | ["Salton Female"] = "a_f_m_salton_01", 949 | ["Beach Tramp Female"] = "a_f_m_trampbeac_01", 950 | ["Rural Meth Addict Female"] = "a_f_y_rurmeth_01", 951 | ["General Street Old Female"] = "a_f_o_genstreet_01", 952 | ["Korean Female 2"] = "a_f_m_ktown_02", 953 | ["Hippie Female"] = "a_f_y_hippie_01", 954 | ["Topless"] = "a_f_y_topless_01" 955 | } 956 | } 957 | -------------------------------------------------------------------------------- /lib/wiriscript/ufo.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | -------------------------------- 3 | THIS FILE IS PART OF WIRISCRIPT 4 | Nowiry#2663 5 | -------------------------------- 6 | ]] 7 | 8 | require "wiriscript.functions" 9 | 10 | local self = {} 11 | local version = 29 12 | local UfoState = 13 | { 14 | nonExistent = -1, 15 | beingCreated = 0, 16 | onFlight = 1, 17 | beingDestroyed = 2, 18 | fadingIn = 3, 19 | } 20 | local state = UfoState.nonExistent 21 | local vehicleHash = util.joaat("hydra") 22 | local objHash = util.joaat("imp_prop_ship_01a") 23 | local jet = 0 24 | local object = 0 25 | local cam = 0 26 | local targetVehicles = {} 27 | local isCannonActive = false 28 | local scaleform = 0 29 | local sound = { 30 | zoomOut = Sound.new("zoom_out_loop", "dlc_xm_orbital_cannon_sounds"), 31 | fireLoop = Sound.new("cannon_charge_fire_loop", "dlc_xm_orbital_cannon_sounds"), 32 | backgroundLoop = Sound.new("background_loop", "dlc_xm_orbital_cannon_sounds"), 33 | panLoop = Sound.new("pan_loop", "dlc_xm_orbital_cannon_sounds"), 34 | ufoAmbience = Sound.new("UFO_Ambience", "Sight_Seeing_Sounds"), 35 | abducted = Sound.new("Abducted", "Sight_Seeing_Sounds") 36 | } 37 | local sphereColour = {r = 0, g = 255, b = 255, a = 255} 38 | local backholePos 39 | local NULL = 0 40 | local zoomTimer = newTimer() 41 | local maxFov = 110.0 42 | local minFov = 25.0 43 | local lastZoom 44 | local zoom = 0.0 45 | local camFov = maxFov 46 | local charge = 0.0 47 | local countdown = 3 -- `seconds` 48 | local isCounting = false 49 | local lastShot = newTimer() 50 | local lastCountdown = newTimer() 51 | local rechargeDuration = 2000 -- `ms` 52 | local cameraRot = v3.new(-89.0, 0.0, 0.0) 53 | local fadingTimer = newTimer() 54 | local effect = Effect.new("scr_xm_orbital", "scr_xm_orbital_blast") 55 | 56 | 57 | self.exists = function () 58 | return state ~= UfoState.nonExistent 59 | end 60 | 61 | self.create = function () 62 | state = UfoState.beingCreated 63 | end 64 | 65 | ---@param model string 66 | self.setObjModel = function (model) 67 | objHash = util.joaat(model) 68 | end 69 | 70 | self.getVersion = function () 71 | return version 72 | end 73 | 74 | 75 | ---Returns the positive equivalent of a negative angle. 76 | ---@param value number #angle in `degrees` 77 | ---@return number 78 | local makeAngPositive = function (value) 79 | if value < 0 then value = value + 360 end 80 | return value 81 | end 82 | 83 | 84 | local drawInstructionalButtons = function () 85 | if Instructional:begin() then 86 | Instructional.add_control(75, "BB_LC_EXIT") 87 | if isCannonActive then 88 | Instructional.add_control(69, "ORB_CAN_FIRE") 89 | Instructional.add_control(80, "Disable Cannon") 90 | if PAD.IS_USING_KEYBOARD_AND_MOUSE(0) then 91 | Instructional.add_control_group(29, "ORB_CAN_ZOOM") 92 | end 93 | Instructional.add_control_group(21, "HUD_INPUT101") 94 | else 95 | Instructional.add_control(119, "Vertical flight") 96 | Instructional.add_control(80, "Cannon") 97 | if #targetVehicles > 0 then 98 | Instructional.add_control(22, "Release vehicles") 99 | end 100 | if #targetVehicles < 15 then 101 | Instructional.add_control(73, "Tractor beam") 102 | end 103 | end 104 | Instructional:set_background_colour(0, 0, 0, 80) 105 | Instructional:draw() 106 | end 107 | end 108 | 109 | 110 | ---@param address integer 111 | ---@return integer CPed* 112 | local getVehicleDriver = function (address) 113 | if address == 0 then return 0 end 114 | return memory.read_long(address + 0x0C48) 115 | end 116 | 117 | 118 | ---@param address integer 119 | ---@return boolean 120 | local isPedPlayer = function (address) 121 | return address ~= 0 and memory.read_long(address + 0x10A8) ~= 0 122 | end 123 | 124 | 125 | local setTractorBeamVehicles = function () 126 | if #targetVehicles >= 16 then return end 127 | for _, vehicle in ipairs(entities.get_all_vehicles_as_handles()) do 128 | if #targetVehicles >= 16 then break end 129 | local distance = ENTITY.GET_ENTITY_COORDS(vehicle, false):distance(backholePos) 130 | 131 | if ENTITY.DOES_ENTITY_EXIST(vehicle) and vehicle ~= jet and distance < 80.0 and 132 | ENTITY.HAS_ENTITY_CLEAR_LOS_TO_ENTITY(object, vehicle, 17) then 133 | if Config.ufo.targetplayer then 134 | local pVehicle = entities.handle_to_pointer(vehicle) 135 | local pDriver = getVehicleDriver(pVehicle) 136 | if isPedPlayer(pDriver) and not table.find(targetVehicles, vehicle) then 137 | table.insert(targetVehicles, vehicle) 138 | end 139 | elseif not table.find(targetVehicles, vehicle) then 140 | table.insert(targetVehicles, vehicle) 141 | end 142 | end 143 | end 144 | end 145 | 146 | 147 | ---@param x number 148 | local easeOutExpo = function (x) 149 | return x >= 1.0 and 1.0 or 1 - 2^(-10 * x) 150 | end 151 | 152 | 153 | local tractorBeam = function () 154 | local pos = ENTITY.GET_OFFSET_FROM_ENTITY_IN_WORLD_COORDS(jet, 0.0, 0.0, -8.0) 155 | backholePos = pos 156 | if not isCannonActive then 157 | draw_marker(28, pos, 1.0, sphereColour) 158 | rainbow_colour(sphereColour) 159 | end 160 | 161 | if PAD.IS_DISABLED_CONTROL_JUST_PRESSED(0, 73) then 162 | setTractorBeamVehicles() 163 | elseif PAD.IS_DISABLED_CONTROL_JUST_PRESSED(0, 22) then 164 | targetVehicles = {} 165 | end 166 | 167 | for i = #targetVehicles, 1, -1 do 168 | local vehicle = targetVehicles[i] 169 | local vehiclePos = ENTITY.GET_ENTITY_COORDS(vehicle, false) 170 | local distance = vehiclePos:distance(pos) 171 | 172 | if ENTITY.DOES_ENTITY_EXIST(vehicle) and distance < 200 then 173 | request_control_once(vehicle) 174 | local delta = v3.new(pos) 175 | delta:sub(vehiclePos) 176 | local multiplier = easeOutExpo(distance / 50.0) * 2.5 177 | delta:mul(multiplier) 178 | local vel = ENTITY.GET_ENTITY_VELOCITY(jet) 179 | vel:add(delta) 180 | ENTITY.SET_ENTITY_VELOCITY(vehicle, vel.x, vel.y, vel.z) 181 | ENTITY.SET_ENTITY_NO_COLLISION_ENTITY(vehicle, jet, true) 182 | else table.remove(targetVehicles, i) end 183 | end 184 | end 185 | 186 | 187 | ---@param pos Vector3 188 | ---@param hudColour integer 189 | local drawLockonSprite = function (pos, hudColour, alpha) 190 | local colour = get_hud_colour(hudColour) 191 | local txdSizeX = 0.013 192 | local txdSizeY = 0.013 * GRAPHICS.GET_ASPECT_RATIO(false) 193 | GRAPHICS.SET_DRAW_ORIGIN(pos.x, pos.y, pos.z, 0) 194 | size = 0.015 195 | GRAPHICS.DRAW_SPRITE("helicopterhud", "hud_corner", -size * 0.5, -size, txdSizeX, txdSizeY, 0.0, colour.r, colour.g, colour.b, alpha, true, 0) 196 | GRAPHICS.DRAW_SPRITE("helicopterhud", "hud_corner", size * 0.5, -size, txdSizeX, txdSizeY, 90.0, colour.r, colour.g, colour.b, alpha, true, 0) 197 | GRAPHICS.DRAW_SPRITE("helicopterhud", "hud_corner", -size * 0.5, size, txdSizeX, txdSizeY, 270., colour.r, colour.g, colour.b, alpha, true, 0) 198 | GRAPHICS.DRAW_SPRITE("helicopterhud", "hud_corner", size * 0.5, size, txdSizeX, txdSizeY, 180., colour.r, colour.g, colour.b, alpha, true, 0) 199 | GRAPHICS.CLEAR_DRAW_ORIGIN() 200 | end 201 | 202 | 203 | ---@param distance number 204 | ---@return integer 205 | local getAlphaFromDistance = function (distance, min, max) 206 | local alpha = 255 207 | if distance > max then 208 | alpha = 0 209 | elseif distance < min then 210 | alpha = 255 211 | else 212 | local perc = 1.0 - (distance - min) / (max - min) 213 | alpha = math.ceil(alpha * perc) 214 | end 215 | return alpha 216 | end 217 | 218 | 219 | ---Function from heli_gun script 220 | ---@param entity Entity 221 | ---@param hudColour HudColour 222 | local drawDirectionalArrowForEntity = function (entity, hudColour) 223 | local entPos = ENTITY.GET_ENTITY_COORDS(entity, false) 224 | local ptr = memory.alloc(4) 225 | if not GRAPHICS.GET_SCREEN_COORD_FROM_WORLD_COORD(entPos.x, entPos.y, entPos.z, ptr, ptr) then 226 | local colour = get_hud_colour(hudColour) 227 | local cam = CAM.GET_RENDERING_CAM() 228 | local camPos = CAM.GET_CAM_COORD(cam) 229 | local camRot = CAM.GET_CAM_ROT(cam, 2) 230 | camRot:mul(math.pi / 180) 231 | local deltaXY = v3.new(entPos.x, entPos.y, 0.0) 232 | deltaXY:sub(v3.new(camPos.x, camPos.y, 0.0)) 233 | local distanceXY = deltaXY:magnitude() 234 | local distanceZ = entPos.z - camPos.z 235 | 236 | local elevation 237 | if distanceZ > 0.0 then 238 | elevation = math.atan(distanceZ / distanceXY) 239 | else 240 | elevation = 0.0 241 | end 242 | 243 | local azimuth 244 | if deltaXY.y ~= 0.0 then 245 | azimuth = math.atan(deltaXY.x, deltaXY.y) 246 | elseif deltaXY.x < 0.0 then 247 | azimuth = -math.pi / 2 248 | else 249 | azimuth = math.pi / 2 250 | end 251 | 252 | local angle = math.atan( 253 | math.cos(camRot.x) * math.sin(elevation) - math.sin(camRot.x) * math.cos(elevation) * math.cos(-azimuth - camRot.z), 254 | math.sin(-azimuth - camRot.z) * math.cos(elevation) 255 | ) 256 | local screenX = 0.5 - math.cos(angle) * 0.29 257 | local screenY = 0.5 - math.sin(angle) * 0.29 258 | local alpha = getAlphaFromDistance(distanceXY, 500, 1000) 259 | GRAPHICS.DRAW_SPRITE("helicopterhud", "hudArrow", screenX, screenY, 0.02, 0.04, math.deg(angle) - 90.0, colour.r, colour.g, colour.b, alpha, false, 0) 260 | end 261 | end 262 | 263 | 264 | local drawSpriteOnPlayers = function () 265 | if Config.ufo.disableboxes or 266 | not GRAPHICS.HAS_STREAMED_TEXTURE_DICT_LOADED("helicopterhud") then 267 | return 268 | end 269 | for _, player in pairs(players.list(false)) do 270 | local playerPed = PLAYER.GET_PLAYER_PED_SCRIPT_INDEX(player) 271 | if is_player_active(player, true, true) and not is_player_in_any_interior(player) and 272 | not ENTITY.IS_ENTITY_DEAD(playerPed, false) and get_distance_between_entities(playerPed, players.user_ped()) < 1000 then 273 | local playerPos = players.get_position(player) 274 | local myPos = players.get_position(players.user()) 275 | local distance = myPos:distance(playerPos) 276 | local hudColour = is_player_friend(player) and HudColour.friendly or HudColour.green 277 | drawLockonSprite(playerPos, hudColour, getAlphaFromDistance(distance, 500, 1000)) 278 | if isCannonActive then drawDirectionalArrowForEntity(playerPed, hudColour) end 279 | end 280 | end 281 | end 282 | 283 | 284 | local setCannonCamZoom = function () 285 | if not PAD.IS_USING_KEYBOARD_AND_MOUSE(0) then 286 | return 287 | end 288 | if PAD.IS_CONTROL_JUST_PRESSED(0, 241) and zoom < 1.0 then 289 | zoom = zoom + 0.25 290 | zoomTimer.reset() 291 | end 292 | if PAD.IS_CONTROL_JUST_PRESSED(0, 242) and zoom > 0.0 then 293 | zoom = zoom - 0.25 294 | zoomTimer.reset() 295 | end 296 | if zoom ~= lastZoom then 297 | sound.zoomOut:play() 298 | GRAPHICS.BEGIN_SCALEFORM_MOVIE_METHOD(scaleform, "SET_ZOOM_LEVEL") 299 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_FLOAT(zoom) 300 | GRAPHICS.END_SCALEFORM_MOVIE_METHOD() 301 | lastZoom = zoom 302 | end 303 | local fovTarget = interpolate(maxFov, minFov, zoom) 304 | local fov = CAM.GET_CAM_FOV(CAM.GET_RENDERING_CAM()) 305 | if fovTarget ~= fov and zoomTimer.elapsed() <= 300 then 306 | camFov = interpolate(fov, fovTarget, zoomTimer.elapsed() / 300) 307 | CAM.SET_CAM_FOV(CAM.GET_RENDERING_CAM(), camFov) 308 | else 309 | sound.zoomOut:stop() 310 | end 311 | end 312 | 313 | 314 | local function setCannonCamRot() 315 | local mult = 1.0 316 | local axisX = PAD.GET_CONTROL_UNBOUND_NORMAL(0, 220) 317 | local axisY = PAD.GET_CONTROL_UNBOUND_NORMAL(0, 221) 318 | local pitch 319 | local heading 320 | local frameTime = 30 * MISC.GET_FRAME_TIME() 321 | local maxRotX = -25.0 322 | local minRotX = -89.0 323 | 324 | if PAD.IS_USING_KEYBOARD_AND_MOUSE(0) then 325 | mult = 3.0 326 | axisX = axisX * mult 327 | axisY = axisY * mult 328 | end 329 | 330 | if PAD.IS_LOOK_INVERTED() then 331 | axisY = -axisY 332 | end 333 | 334 | if axisX ~= 0 or axisY ~= 0 then 335 | heading = -(axisX * 0.05) * frameTime * 25 336 | pitch = -(axisY * 0.05) * frameTime * 25 337 | cameraRot:add(v3.new(pitch, 0, heading)) 338 | 339 | if cameraRot.x > maxRotX then 340 | cameraRot.x = maxRotX 341 | elseif cameraRot.x < minRotX then 342 | cameraRot.x = minRotX 343 | end 344 | 345 | sound.panLoop:play() 346 | CAM.HARD_ATTACH_CAM_TO_ENTITY(cam, jet, cameraRot.x, cameraRot.y, cameraRot.z, 0.0, 0.0, -4.0, true) 347 | else 348 | sound.panLoop:stop() 349 | end 350 | 351 | local heading = makeAngPositive(CAM.GET_CAM_ROT(cam, 2).z) 352 | HUD.LOCK_MINIMAP_ANGLE(math.ceil(heading)) 353 | end 354 | 355 | 356 | local doCannon = function () 357 | if not GRAPHICS.HAS_SCALEFORM_MOVIE_LOADED(scaleform) then 358 | scaleform = request_scaleform_movie("ORBITAL_CANNON_CAM") 359 | 360 | elseif not isCannonActive then 361 | if CAM.IS_CAM_RENDERING(cam) then 362 | GRAPHICS.CLEAR_TIMECYCLE_MODIFIER() 363 | CAM.RENDER_SCRIPT_CAMS(false, false, 0, true, false, 0) 364 | end 365 | 366 | if AUDIO.IS_AUDIO_SCENE_ACTIVE("DLC_BTL_Hacker_Drone_HUD_Scene") then 367 | AUDIO.STOP_AUDIO_SCENE("DLC_BTL_Hacker_Drone_HUD_Scene") 368 | end 369 | 370 | sound.panLoop:stop() 371 | sound.zoomOut:stop() 372 | sound.fireLoop:stop() 373 | sound.backgroundLoop:stop() 374 | 375 | else 376 | if not CAM.IS_CAM_RENDERING(cam) then 377 | GRAPHICS.SET_TIMECYCLE_MODIFIER("eyeinthesky") 378 | CAM.RENDER_SCRIPT_CAMS(true, false, 0, true, false, 0) 379 | end 380 | 381 | for n = 1, 6 do PAD.DISABLE_CONTROL_ACTION(0, n, true) end 382 | PAD.DISABLE_CONTROL_ACTION(0, 85, true) 383 | PAD.DISABLE_CONTROL_ACTION(0, 122, true) 384 | HudTimer.DisableThisFrame() 385 | setCannonCamRot() 386 | setCannonCamZoom() 387 | 388 | GRAPHICS.BEGIN_SCALEFORM_MOVIE_METHOD(scaleform, "SET_STATE") 389 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_INT(3) 390 | GRAPHICS.END_SCALEFORM_MOVIE_METHOD() 391 | 392 | GRAPHICS.BEGIN_SCALEFORM_MOVIE_METHOD(scaleform, "SET_CHARGING_LEVEL") 393 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_FLOAT(charge) 394 | GRAPHICS.END_SCALEFORM_MOVIE_METHOD() 395 | 396 | if PAD.IS_CONTROL_PRESSED(0, 69) or countdown == 0 then 397 | local raycastResult = get_raycast_result(1000.0) 398 | local pos = raycastResult.endCoords 399 | 400 | if charge == 1.0 then 401 | if not isCounting then 402 | sound.fireLoop:play() 403 | lastCountdown.reset() 404 | isCounting = true 405 | end 406 | 407 | if countdown ~= 0 then 408 | if lastCountdown.elapsed() >= 1000 then 409 | countdown = countdown - 1 410 | lastCountdown.reset() 411 | end 412 | GRAPHICS.BEGIN_SCALEFORM_MOVIE_METHOD(scaleform, "SET_COUNTDOWN") 413 | GRAPHICS.SCALEFORM_MOVIE_METHOD_ADD_PARAM_INT(countdown) 414 | GRAPHICS.END_SCALEFORM_MOVIE_METHOD() 415 | 416 | elseif STREAMING.HAS_NAMED_PTFX_ASSET_LOADED(effect.asset) then 417 | charge = 0.0 418 | countdown = 3 419 | isCounting = false 420 | local rot = raycastResult.surfaceNormal:toRot() 421 | rot.x = rot.x - 90.0 422 | 423 | FIRE.ADD_EXPLOSION(pos.x, pos.y, pos.z, 59, 1.0, true, false, 1.0, false) 424 | GRAPHICS.USE_PARTICLE_FX_ASSET(effect.asset) 425 | GRAPHICS.START_NETWORKED_PARTICLE_FX_NON_LOOPED_AT_COORD( 426 | effect.name, pos.x, pos.y, pos.z, rot.x, rot.y, rot.z, 1.0, false, false, false, true 427 | ) 428 | AUDIO.PLAY_SOUND_FROM_COORD(-1, "DLC_XM_Explosions_Orbital_Cannon", pos.x, pos.y, pos.z, NULL, true, 0, false) 429 | CAM.SHAKE_CAM(cam, "GAMEPLAY_EXPLOSION_SHAKE", 1.5) 430 | lastShot.reset() 431 | else 432 | STREAMING.REQUEST_NAMED_PTFX_ASSET(effect.asset) 433 | end 434 | 435 | elseif charge ~= 1.0 then 436 | charge = interpolate(0.0, 1.0, lastShot.elapsed() / rechargeDuration) 437 | sound.fireLoop:stop() 438 | countdown = 3 439 | isCounting = false 440 | end 441 | 442 | elseif charge ~= 1.0 or isCounting then 443 | charge = interpolate(0.0, 1.0, lastShot.elapsed() / rechargeDuration) 444 | AUDIO.SET_VARIABLE_ON_SOUND(sound.fireLoop.Id, "Firing", 0.0) 445 | sound.fireLoop:stop() 446 | countdown = 3 447 | isCounting = false 448 | end 449 | 450 | if not AUDIO.IS_AUDIO_SCENE_ACTIVE("DLC_BTL_Hacker_Drone_HUD_Scene") then 451 | AUDIO.START_AUDIO_SCENE("DLC_BTL_Hacker_Drone_HUD_Scene") 452 | end 453 | sound.backgroundLoop:play() 454 | STREAMING.CLEAR_FOCUS() 455 | GRAPHICS.DRAW_SCALEFORM_MOVIE_FULLSCREEN(scaleform, 255, 255, 255, 255, 0) 456 | end 457 | end 458 | 459 | 460 | ---@param vehicle Vehicle 461 | ---@param value number 462 | local setVehicleCamDistance = function (vehicle, value) 463 | if vehicle == 0 then return end 464 | local addr = memory.read_long(entities.handle_to_pointer(vehicle) + 0x20) 465 | if addr ~= NULL then 466 | memory.write_float(addr + 0x38, value) 467 | end 468 | end 469 | 470 | 471 | local destroy = function() 472 | for _, s in pairs(sound) do s:stop() end 473 | STREAMING.CLEAR_FOCUS() 474 | AUDIO.STOP_AUDIO_SCENE("DLC_BTL_Hacker_Drone_HUD_Scene") 475 | AUDIO.RELEASE_NAMED_SCRIPT_AUDIO_BANK("DLC_CHRISTMAS2017/XM_ION_CANNON") 476 | GRAPHICS.CLEAR_TIMECYCLE_MODIFIER() 477 | 478 | set_streamed_texture_dict_as_no_longer_needed("helicopterhud") 479 | set_scaleform_movie_as_no_longer_needed(scaleform) 480 | scaleform = 0 481 | 482 | if ENTITY.DOES_ENTITY_EXIST(jet) then 483 | request_control_once(jet) 484 | setVehicleCamDistance(jet, -1.57) 485 | entities.delete_by_handle(jet) 486 | jet = 0 487 | end 488 | 489 | if ENTITY.DOES_ENTITY_EXIST(object) then 490 | request_control_once(object) 491 | entities.delete_by_handle(object) 492 | object = 0 493 | end 494 | 495 | local pos = players.get_position(players.user()) 496 | local ok, groundZ = util.get_ground_z(pos.x, pos.y) 497 | if ok then pos.z = groundZ end 498 | local outCoords = v3.new() 499 | if PATHFIND.GET_CLOSEST_VEHICLE_NODE(pos.x, pos.y, pos.z, outCoords, 1, 100.0, 2.5) then 500 | pos = outCoords 501 | end 502 | ENTITY.SET_ENTITY_COORDS(players.user_ped(), pos.x, pos.y, pos.z, false, false, false, true) 503 | ENTITY.SET_ENTITY_VISIBLE(players.user_ped(), true, true) 504 | PED.REMOVE_PED_HELMET(players.user_ped(), true) 505 | DisableOTR() 506 | 507 | CAM.SET_CAM_ACTIVE(cam, false) 508 | CAM.RENDER_SCRIPT_CAMS(false, false, -1, true, false, 0) 509 | CAM.DESTROY_CAM(cam, false) 510 | cam = 0 511 | HUD.UNLOCK_MINIMAP_ANGLE() 512 | targetVehicles = {} 513 | isCannonActive = false 514 | end 515 | 516 | 517 | self.mainLoop = function () 518 | if state == UfoState.beingCreated then 519 | if not CAM.IS_SCREEN_FADED_OUT() then 520 | CAM.DO_SCREEN_FADE_OUT(800) 521 | else 522 | if not ENTITY.DOES_ENTITY_EXIST(jet) then 523 | request_model(vehicleHash) 524 | request_model(objHash) 525 | local pos = ENTITY.GET_ENTITY_COORDS(PLAYER.PLAYER_PED_ID(), false) 526 | jet = entities.create_vehicle(vehicleHash, pos, CAM.GET_GAMEPLAY_CAM_ROT(0).z) 527 | ENTITY.SET_ENTITY_VISIBLE(jet, false, false) 528 | ENTITY.SET_ENTITY_VISIBLE(players.user_ped(), false, true) 529 | 530 | VEHICLE.SET_VEHICLE_ENGINE_ON(jet, true, true, true) 531 | ENTITY.SET_ENTITY_INVINCIBLE(jet, true) 532 | VEHICLE.SET_PLANE_TURBULENCE_MULTIPLIER(jet, 0.0) 533 | setVehicleCamDistance(jet, -20.0) 534 | 535 | object = entities.create_object(objHash, pos) 536 | ENTITY.ATTACH_ENTITY_TO_ENTITY(object, jet, 0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false, true, false, false, 0, true, 0) 537 | ENTITY.SET_ENTITY_COORDS_NO_OFFSET(jet, pos.x, pos.y, pos.z + 200, false, false, true) 538 | PED.SET_PED_INTO_VEHICLE(PLAYER.PLAYER_PED_ID(), jet, -1) 539 | 540 | CAM.DESTROY_ALL_CAMS(true) 541 | cam = CAM.CREATE_CAM("DEFAULT_SCRIPTED_CAMERA", false) 542 | cameraRot:set(-89.0, 0, 0) 543 | CAM.HARD_ATTACH_CAM_TO_ENTITY(cam, jet, -89.0, 0.0, 0.0, 0.0, 0.0, -4.0, true) 544 | CAM.SET_CAM_FOV(cam, camFov) 545 | CAM.SET_CAM_ACTIVE(cam, true) 546 | 547 | request_streamed_texture_dict("helicopterhud") 548 | AUDIO.REQUEST_SCRIPT_AUDIO_BANK("DLC_CHRISTMAS2017/XM_ION_CANNON", false, -1) 549 | AUDIO.SET_AUDIO_FLAG("DisableFlightMusic", true) 550 | 551 | fadingTimer.reset() 552 | elseif fadingTimer.elapsed() > 1200 then 553 | state = UfoState.onFlight 554 | CAM.DO_SCREEN_FADE_IN(800) 555 | end 556 | end 557 | 558 | elseif state == UfoState.onFlight then 559 | if ENTITY.IS_ENTITY_VISIBLE(jet) then ENTITY.SET_ENTITY_VISIBLE(jet, false, true) end 560 | PAD.DISABLE_CONTROL_ACTION(0, 75, true) 561 | PAD.DISABLE_CONTROL_ACTION(0, 80, true) 562 | PAD.DISABLE_CONTROL_ACTION(0, 99, true) 563 | 564 | VEHICLE.DISABLE_VEHICLE_WEAPON(true, util.joaat("vehicle_weapon_player_lazer"), jet, players.user_ped()) 565 | VEHICLE.DISABLE_VEHICLE_WEAPON(true, util.joaat("vehicle_weapon_space_rocket"), jet, players.user_ped()) 566 | CAM.DISABLE_CINEMATIC_BONNET_CAMERA_THIS_UPDATE() 567 | 568 | if PAD.IS_DISABLED_CONTROL_JUST_PRESSED(0, 75) or get_vehicle_player_is_in(PLAYER.PLAYER_ID()) ~= jet then 569 | CAM.DO_SCREEN_FADE_OUT(500) 570 | end 571 | 572 | if CAM.IS_SCREEN_FADED_OUT() then 573 | state = UfoState.beingDestroyed 574 | end 575 | 576 | if PAD.IS_CONTROL_JUST_PRESSED(0, 80) or PAD.IS_CONTROL_JUST_PRESSED(0, 45) then 577 | if isCannonActive then 578 | cameraRot:set(-89.0, 0.0, 0.0) 579 | CAM.HARD_ATTACH_CAM_TO_ENTITY(cam, jet, -89.0, 0.0, 0.0, 0.0, 0.0, -4.0, true) 580 | end 581 | AUDIO.PLAY_SOUND_FRONTEND(-1, "cannon_active", "dlc_xm_orbital_cannon_sounds", true); 582 | zoom = 0.0 583 | CAM.SET_CAM_FOV(cam, maxFov) 584 | isCannonActive = not isCannonActive 585 | STREAMING.CLEAR_FOCUS() 586 | HUD.UNLOCK_MINIMAP_ANGLE() 587 | end 588 | 589 | if sound.ufoAmbience:hasFinished() then 590 | sound.ufoAmbience:play() 591 | end 592 | 593 | tractorBeam() 594 | drawSpriteOnPlayers() 595 | doCannon() 596 | drawInstructionalButtons() 597 | HudTimer.SetHeightMultThisFrame(1) 598 | EnableOTR() 599 | DisablePhone() 600 | 601 | elseif state == UfoState.beingDestroyed then 602 | destroy() 603 | if CAM.IS_SCREEN_FADED_OUT() then 604 | fadingTimer.reset() 605 | state = UfoState.fadingIn 606 | end 607 | 608 | elseif state == UfoState.fadingIn then 609 | if fadingTimer.elapsed() > 1200 then 610 | CAM.DO_SCREEN_FADE_IN(500) 611 | state = UfoState.nonExistent 612 | end 613 | end 614 | end 615 | 616 | 617 | self.destroy = function () 618 | if not self.exists() then 619 | return 620 | end 621 | CAM.DO_SCREEN_FADE_IN(0) 622 | destroy() 623 | state = UfoState.nonExistent 624 | end 625 | 626 | return self -------------------------------------------------------------------------------- /resources/WiriTextures.ytd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nowiry/WiriScript/fd3ca74d5be6ea9866ce4f1a2e39a0a083e6f4a6/resources/WiriTextures.ytd --------------------------------------------------------------------------------