| @@ -0,0 +1,381 @@ | |||
| 1 | + | -- @docfuncs @{ | |
| 2 | + | function print(...) | |
| 3 | + | local msg = "" | |
| 4 | + | local args = { ... } | |
| 5 | + | local appendSpace = #args > 1 | |
| 6 | + | for i,v in ipairs(args) do | |
| 7 | + | msg = msg .. tostring(v) | |
| 8 | + | if appendSpace and i < #args then | |
| 9 | + | msg = msg .. ' ' | |
| 10 | + | end | |
| 11 | + | end | |
| 12 | + | g_logger.log(LogInfo, msg) | |
| 13 | + | end | |
| 14 | + | ||
| 15 | + | function pinfo(msg) | |
| 16 | + | g_logger.log(LogInfo, msg) | |
| 17 | + | end | |
| 18 | + | ||
| 19 | + | function perror(msg) | |
| 20 | + | g_logger.log(LogError, msg) | |
| 21 | + | end | |
| 22 | + | ||
| 23 | + | function pwarning(msg) | |
| 24 | + | g_logger.log(LogWarning, msg) | |
| 25 | + | end | |
| 26 | + | ||
| 27 | + | function pdebug(msg) | |
| 28 | + | g_logger.log(LogDebug, msg) | |
| 29 | + | end | |
| 30 | + | ||
| 31 | + | function fatal(msg) | |
| 32 | + | g_logger.log(LogFatal, msg) | |
| 33 | + | end | |
| 34 | + | ||
| 35 | + | function exit() | |
| 36 | + | g_app.exit() | |
| 37 | + | end | |
| 38 | + | ||
| 39 | + | function quit() | |
| 40 | + | g_app.exit() | |
| 41 | + | end | |
| 42 | + | ||
| 43 | + | function connect(object, arg1, arg2, arg3) | |
| 44 | + | if not object then | |
| 45 | + | perror("connect: object is nil") | |
| 46 | + | return | |
| 47 | + | end | |
| 48 | + | local signalsAndSlots | |
| 49 | + | local pushFront | |
| 50 | + | if type(arg1) == 'string' then | |
| 51 | + | signalsAndSlots = { [arg1] = arg2 } | |
| 52 | + | pushFront = arg3 | |
| 53 | + | else | |
| 54 | + | signalsAndSlots = arg1 | |
| 55 | + | pushFront = arg2 | |
| 56 | + | end | |
| 57 | + | ||
| 58 | + | for signal,slot in pairs(signalsAndSlots) do | |
| 59 | + | if not object[signal] then | |
| 60 | + | local mt = getmetatable(object) | |
| 61 | + | if mt and type(object) == 'userdata' then | |
| 62 | + | object[signal] = function(...) | |
| 63 | + | return signalcall(mt[signal], ...) | |
| 64 | + | end | |
| 65 | + | end | |
| 66 | + | end | |
| 67 | + | ||
| 68 | + | if not object[signal] then | |
| 69 | + | object[signal] = slot | |
| 70 | + | elseif type(object[signal]) == 'function' then | |
| 71 | + | object[signal] = { object[signal] } | |
| 72 | + | end | |
| 73 | + | ||
| 74 | + | if type(slot) ~= 'function' then | |
| 75 | + | perror(debug.traceback('unable to connect a non function value')) | |
| 76 | + | end | |
| 77 | + | ||
| 78 | + | if type(object[signal]) == 'table' then | |
| 79 | + | if pushFront then | |
| 80 | + | table.insert(object[signal], 1, slot) | |
| 81 | + | else | |
| 82 | + | table.insert(object[signal], #object[signal]+1, slot) | |
| 83 | + | end | |
| 84 | + | end | |
| 85 | + | end | |
| 86 | + | end | |
| 87 | + | ||
| 88 | + | function disconnect(object, arg1, arg2) | |
| 89 | + | local signalsAndSlots | |
| 90 | + | if type(arg1) == 'string' then | |
| 91 | + | if arg2 == nil then | |
| 92 | + | object[arg1] = nil | |
| 93 | + | return | |
| 94 | + | end | |
| 95 | + | signalsAndSlots = { [arg1] = arg2 } | |
| 96 | + | elseif type(arg1) == 'table' then | |
| 97 | + | signalsAndSlots = arg1 | |
| 98 | + | else | |
| 99 | + | return -- Evitamos error: unable to disconnect | |
| 100 | + | end | |
| 101 | + | ||
| 102 | + | for signal,slot in pairs(signalsAndSlots) do | |
| 103 | + | if not object[signal] then | |
| 104 | + | elseif type(object[signal]) == 'function' then | |
| 105 | + | if object[signal] == slot then | |
| 106 | + | object[signal] = nil | |
| 107 | + | end | |
| 108 | + | elseif type(object[signal]) == 'table' then | |
| 109 | + | for k,func in pairs(object[signal]) do | |
| 110 | + | if func == slot then | |
| 111 | + | table.remove(object[signal], k) | |
| 112 | + | ||
| 113 | + | if #object[signal] == 1 then | |
| 114 | + | object[signal] = object[signal][1] | |
| 115 | + | end | |
| 116 | + | break | |
| 117 | + | end | |
| 118 | + | end | |
| 119 | + | end | |
| 120 | + | end | |
| 121 | + | end | |
| 122 | + | ||
| 123 | + | function newclass(name) | |
| 124 | + | if not name then | |
| 125 | + | perror(debug.traceback('new class has no name.')) | |
| 126 | + | end | |
| 127 | + | ||
| 128 | + | local class = {} | |
| 129 | + | function class.internalCreate() | |
| 130 | + | local instance = {} | |
| 131 | + | for k,v in pairs(class) do | |
| 132 | + | instance[k] = v | |
| 133 | + | end | |
| 134 | + | return instance | |
| 135 | + | end | |
| 136 | + | ||
| 137 | + | class.create = class.internalCreate | |
| 138 | + | class.__class = name | |
| 139 | + | class.getClassName = function() return name end | |
| 140 | + | return class | |
| 141 | + | end | |
| 142 | + | ||
| 143 | + | function extends(base, name) | |
| 144 | + | if not name then | |
| 145 | + | perror(debug.traceback('extended class has no name.')) | |
| 146 | + | end | |
| 147 | + | ||
| 148 | + | local derived = {} | |
| 149 | + | function derived.internalCreate() | |
| 150 | + | local instance = base.create() | |
| 151 | + | for k,v in pairs(derived) do | |
| 152 | + | instance[k] = v | |
| 153 | + | end | |
| 154 | + | return instance | |
| 155 | + | end | |
| 156 | + | ||
| 157 | + | derived.create = derived.internalCreate | |
| 158 | + | derived.__class = name | |
| 159 | + | derived.getClassName = function() return name end | |
| 160 | + | return derived | |
| 161 | + | end | |
| 162 | + | ||
| 163 | + | function runinsandbox(func, ...) | |
| 164 | + | if type(func) == 'string' then | |
| 165 | + | func, err = loadfile(resolvepath(func, 2)) | |
| 166 | + | if not func then | |
| 167 | + | error(err) | |
| 168 | + | end | |
| 169 | + | end | |
| 170 | + | local env = { } | |
| 171 | + | local oldenv = getfenv(0) | |
| 172 | + | setmetatable(env, { __index = oldenv } ) | |
| 173 | + | setfenv(0, env) | |
| 174 | + | func(...) | |
| 175 | + | setfenv(0, oldenv) | |
| 176 | + | return env | |
| 177 | + | end | |
| 178 | + | ||
| 179 | + | function loadasmodule(name, file) | |
| 180 | + | file = file or resolvepath(name, 2) | |
| 181 | + | if package.loaded[name] then | |
| 182 | + | return package.loaded[name] | |
| 183 | + | end | |
| 184 | + | local env = runinsandbox(file) | |
| 185 | + | package.loaded[name] = env | |
| 186 | + | return env | |
| 187 | + | end | |
| 188 | + | ||
| 189 | + | local function module_loader(modname) | |
| 190 | + | local module = g_modules.getModule(modname) | |
| 191 | + | if not module then | |
| 192 | + | return '\n\tno module \'' .. modname .. '\'' | |
| 193 | + | end | |
| 194 | + | return function() | |
| 195 | + | if not module:load() then | |
| 196 | + | error('unable to load required module ' .. modname) | |
| 197 | + | end | |
| 198 | + | return module:getSandbox() | |
| 199 | + | end | |
| 200 | + | end | |
| 201 | + | table.insert(package.loaders, 1, module_loader) | |
| 202 | + | ||
| 203 | + | function import(table) | |
| 204 | + | assert(type(table) == 'table') | |
| 205 | + | local env = getfenv(2) | |
| 206 | + | for k,v in pairs(table) do | |
| 207 | + | env[k] = v | |
| 208 | + | end | |
| 209 | + | end | |
| 210 | + | ||
| 211 | + | function export(what, key) | |
| 212 | + | if key ~= nil then | |
| 213 | + | _G[key] = what | |
| 214 | + | else | |
| 215 | + | for k,v in pairs(what) do | |
| 216 | + | _G[k] = v | |
| 217 | + | end | |
| 218 | + | end | |
| 219 | + | end | |
| 220 | + | ||
| 221 | + | function unexport(key) | |
| 222 | + | if type(key) == 'table' then | |
| 223 | + | for _k,v in pairs(key) do | |
| 224 | + | _G[v] = nil | |
| 225 | + | end | |
| 226 | + | else | |
| 227 | + | _G[key] = nil | |
| 228 | + | end | |
| 229 | + | end | |
| 230 | + | ||
| 231 | + | function getfsrcpath(depth) | |
| 232 | + | depth = depth or 2 | |
| 233 | + | local info = debug.getinfo(1+depth, "Sn") | |
| 234 | + | local path | |
| 235 | + | if info.short_src then | |
| 236 | + | path = info.short_src:match("(.*)/.*") | |
| 237 | + | end | |
| 238 | + | if not path then | |
| 239 | + | path = '/' | |
| 240 | + | elseif path:sub(0, 1) ~= '/' then | |
| 241 | + | path = '/' .. path | |
| 242 | + | end | |
| 243 | + | return path | |
| 244 | + | end | |
| 245 | + | ||
| 246 | + | function resolvepath(filePath, depth) | |
| 247 | + | if not filePath then return nil end | |
| 248 | + | depth = depth or 1 | |
| 249 | + | if filePath then | |
| 250 | + | if filePath:sub(0, 1) ~= '/' then | |
| 251 | + | local basepath = getfsrcpath(depth+1) | |
| 252 | + | if basepath:sub(#basepath) ~= '/' then basepath = basepath .. '/' end | |
| 253 | + | return basepath .. filePath | |
| 254 | + | else | |
| 255 | + | return filePath | |
| 256 | + | end | |
| 257 | + | else | |
| 258 | + | local basepath = getfsrcpath(depth+1) | |
| 259 | + | if basepath:sub(#basepath) ~= '/' then basepath = basepath .. '/' end | |
| 260 | + | return basepath | |
| 261 | + | end | |
| 262 | + | end | |
| 263 | + | ||
| 264 | + | function toboolean(v) | |
| 265 | + | if type(v) == 'string' then | |
| 266 | + | v = v:trim():lower() | |
| 267 | + | if v == '1' or v == 'true' then | |
| 268 | + | return true | |
| 269 | + | end | |
| 270 | + | elseif type(v) == 'number' then | |
| 271 | + | if v == 1 then | |
| 272 | + | return true | |
| 273 | + | end | |
| 274 | + | elseif type(v) == 'boolean' then | |
| 275 | + | return v | |
| 276 | + | end | |
| 277 | + | return false | |
| 278 | + | end | |
| 279 | + | ||
| 280 | + | function fromboolean(boolean) | |
| 281 | + | if boolean then | |
| 282 | + | return 'true' | |
| 283 | + | else | |
| 284 | + | return 'false' | |
| 285 | + | end | |
| 286 | + | end | |
| 287 | + | ||
| 288 | + | function booleantonumber(boolean) | |
| 289 | + | if boolean then | |
| 290 | + | return 1 | |
| 291 | + | else | |
| 292 | + | return 0 | |
| 293 | + | end | |
| 294 | + | end | |
| 295 | + | ||
| 296 | + | function numbertoboolean(number) | |
| 297 | + | if number ~= 0 then | |
| 298 | + | return true | |
| 299 | + | else | |
| 300 | + | return false | |
| 301 | + | end | |
| 302 | + | end | |
| 303 | + | ||
| 304 | + | function protectedcall(func, ...) | |
| 305 | + | local status, ret = pcall(func, ...) | |
| 306 | + | if status then | |
| 307 | + | return ret | |
| 308 | + | end | |
| 309 | + | ||
| 310 | + | perror(ret) | |
| 311 | + | return false | |
| 312 | + | end | |
| 313 | + | ||
| 314 | + | function signalcall(param, ...) | |
| 315 | + | if type(param) == 'function' then | |
| 316 | + | local status, ret = pcall(param, ...) | |
| 317 | + | if status then | |
| 318 | + | return ret | |
| 319 | + | else | |
| 320 | + | perror(ret) | |
| 321 | + | end | |
| 322 | + | elseif type(param) == 'table' then | |
| 323 | + | for k,v in pairs(param) do | |
| 324 | + | local status, ret = pcall(v, ...) | |
| 325 | + | if status then | |
| 326 | + | if ret then return true end | |
| 327 | + | else | |
| 328 | + | perror(ret) | |
| 329 | + | end | |
| 330 | + | end | |
| 331 | + | elseif param ~= nil then | |
| 332 | + | error('attempt to call a non function value') | |
| 333 | + | end | |
| 334 | + | return false | |
| 335 | + | end | |
| 336 | + | ||
| 337 | + | function tr(s, ...) | |
| 338 | + | return string.format(s, ...) | |
| 339 | + | end | |
| 340 | + | ||
| 341 | + | function getOppositeAnchor(anchor) | |
| 342 | + | if anchor == AnchorLeft then | |
| 343 | + | return AnchorRight | |
| 344 | + | elseif anchor == AnchorRight then | |
| 345 | + | return AnchorLeft | |
| 346 | + | elseif anchor == AnchorTop then | |
| 347 | + | return AnchorBottom | |
| 348 | + | elseif anchor == AnchorBottom then | |
| 349 | + | return AnchorTop | |
| 350 | + | elseif anchor == AnchorVerticalCenter then | |
| 351 | + | return AnchorHorizontalCenter | |
| 352 | + | elseif anchor == AnchorHorizontalCenter then | |
| 353 | + | return AnchorVerticalCenter | |
| 354 | + | end | |
| 355 | + | return anchor | |
| 356 | + | end | |
| 357 | + | ||
| 358 | + | function makesingleton(obj) | |
| 359 | + | local singleton = {} | |
| 360 | + | if obj.getClassName then | |
| 361 | + | for key,value in pairs(_G[obj:getClassName()]) do | |
| 362 | + | if type(value) == 'function' then | |
| 363 | + | singleton[key] = function(...) return value(obj, ...) end | |
| 364 | + | end | |
| 365 | + | end | |
| 366 | + | end | |
| 367 | + | return singleton | |
| 368 | + | end | |
| 369 | + | ||
| 370 | + | function comma_value(amount) | |
| 371 | + | local formatted = amount | |
| 372 | + | while true do | |
| 373 | + | formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2') | |
| 374 | + | if (k==0) then | |
| 375 | + | break | |
| 376 | + | end | |
| 377 | + | end | |
| 378 | + | return formatted | |
| 379 | + | end | |
| 380 | + | ||
| 381 | + | -- @} |
| @@ -0,0 +1,22 @@ | |||
| 1 | + | local CRASH_FILE = "exception.dmp" | |
| 2 | + | ||
| 3 | + | function init() | |
| 4 | + | if g_resources.fileExists(CRASH_FILE) then | |
| 5 | + | local crashLog = g_resources.readFileContents(CRASH_FILE) | |
| 6 | + | local clientLog = g_logger.getLastLog() | |
| 7 | + | HTTP.post(Services.crash, { | |
| 8 | + | version = APP_VERSION, | |
| 9 | + | build = g_app.getVersion(), | |
| 10 | + | os = g_app.getOs(), | |
| 11 | + | platform = g_window.getPlatformType(), | |
| 12 | + | crash = base64.encode(crashLog), | |
| 13 | + | log = base64.encode(clientLog) | |
| 14 | + | }, function(data, err) | |
| 15 | + | if err then | |
| 16 | + | return g_logger.error("Error while reporting crash report: " .. err) | |
| 17 | + | end | |
| 18 | + | g_resources.deleteFile(CRASH_FILE) | |
| 19 | + | end) | |
| 20 | + | end | |
| 21 | + | end | |
| 22 | + |
| @@ -0,0 +1,8 @@ | |||
| 1 | + | Module | |
| 2 | + | name: crash_reporter | |
| 3 | + | description: Sends crash log to remote server | |
| 4 | + | author: otclient@otclient.ovh | |
| 5 | + | website: otclient.ovh | |
| 6 | + | reloadable: false | |
| 7 | + | scripts: [ crash_reporter ] | |
| 8 | + | @onLoad: init() |
| @@ -0,0 +1,412 @@ | |||
| 1 | + | -- @docconsts @{ | |
| 2 | + | ||
| 3 | + | FloorHigher = 0 | |
| 4 | + | FloorLower = 15 | |
| 5 | + | ||
| 6 | + | SkullNone = 0 | |
| 7 | + | SkullYellow = 1 | |
| 8 | + | SkullGreen = 2 | |
| 9 | + | SkullWhite = 3 | |
| 10 | + | SkullRed = 4 | |
| 11 | + | SkullBlack = 5 | |
| 12 | + | SkullOrange = 6 | |
| 13 | + | ||
| 14 | + | ShieldNone = 0 | |
| 15 | + | ShieldWhiteYellow = 1 | |
| 16 | + | ShieldWhiteBlue = 2 | |
| 17 | + | ShieldBlue = 3 | |
| 18 | + | ShieldYellow = 4 | |
| 19 | + | ShieldBlueSharedExp = 5 | |
| 20 | + | ShieldYellowSharedExp = 6 | |
| 21 | + | ShieldBlueNoSharedExpBlink = 7 | |
| 22 | + | ShieldYellowNoSharedExpBlink = 8 | |
| 23 | + | ShieldBlueNoSharedExp = 9 | |
| 24 | + | ShieldYellowNoSharedExp = 10 | |
| 25 | + | ShieldGray = 11 | |
| 26 | + | ||
| 27 | + | EmblemNone = 0 | |
| 28 | + | EmblemGreen = 1 | |
| 29 | + | EmblemRed = 2 | |
| 30 | + | EmblemBlue = 3 | |
| 31 | + | EmblemMember = 4 | |
| 32 | + | EmblemOther = 5 | |
| 33 | + | ||
| 34 | + | VipIconFirst = 0 | |
| 35 | + | VipIconLast = 10 | |
| 36 | + | ||
| 37 | + | Directions = { | |
| 38 | + | North = 0, | |
| 39 | + | East = 1, | |
| 40 | + | South = 2, | |
| 41 | + | West = 3, | |
| 42 | + | NorthEast = 4, | |
| 43 | + | SouthEast = 5, | |
| 44 | + | SouthWest = 6, | |
| 45 | + | NorthWest = 7 | |
| 46 | + | } | |
| 47 | + | ||
| 48 | + | Skill = { | |
| 49 | + | Fist = 0, | |
| 50 | + | Club = 1, | |
| 51 | + | Sword = 2, | |
| 52 | + | Axe = 3, | |
| 53 | + | Distance = 4, | |
| 54 | + | Shielding = 5, | |
| 55 | + | Fishing = 6, | |
| 56 | + | CriticalChance = 7, | |
| 57 | + | CriticalDamage = 8, | |
| 58 | + | LifeLeechChance = 9, | |
| 59 | + | LifeLeechAmount = 10, | |
| 60 | + | ManaLeechChance = 11, | |
| 61 | + | ManaLeechAmount = 12 | |
| 62 | + | } | |
| 63 | + | ||
| 64 | + | North = Directions.North | |
| 65 | + | East = Directions.East | |
| 66 | + | South = Directions.South | |
| 67 | + | West = Directions.West | |
| 68 | + | NorthEast = Directions.NorthEast | |
| 69 | + | SouthEast = Directions.SouthEast | |
| 70 | + | SouthWest = Directions.SouthWest | |
| 71 | + | NorthWest = Directions.NorthWest | |
| 72 | + | ||
| 73 | + | FightOffensive = 1 | |
| 74 | + | FightBalanced = 2 | |
| 75 | + | FightDefensive = 3 | |
| 76 | + | ||
| 77 | + | DontChase = 0 | |
| 78 | + | ChaseOpponent = 1 | |
| 79 | + | ||
| 80 | + | PVPWhiteDove = 0 | |
| 81 | + | PVPWhiteHand = 1 | |
| 82 | + | PVPYellowHand = 2 | |
| 83 | + | PVPRedFist = 3 | |
| 84 | + | ||
| 85 | + | GameProtocolChecksum = 1 | |
| 86 | + | GameAccountNames = 2 | |
| 87 | + | GameChallengeOnLogin = 3 | |
| 88 | + | GamePenalityOnDeath = 4 | |
| 89 | + | GameNameOnNpcTrade = 5 | |
| 90 | + | GameDoubleFreeCapacity = 6 | |
| 91 | + | GameDoubleExperience = 7 | |
| 92 | + | GameTotalCapacity = 8 | |
| 93 | + | GameSkillsBase = 9 | |
| 94 | + | GamePlayerRegenerationTime = 10 | |
| 95 | + | GameChannelPlayerList = 11 | |
| 96 | + | GamePlayerMounts = 12 | |
| 97 | + | GameEnvironmentEffect = 13 | |
| 98 | + | GameCreatureEmblems = 14 | |
| 99 | + | GameItemAnimationPhase = 15 | |
| 100 | + | GameMagicEffectU16 = 16 | |
| 101 | + | GamePlayerMarket = 17 | |
| 102 | + | GameSpritesU32 = 18 | |
| 103 | + | GameTileAddThingWithStackpos = 19 | |
| 104 | + | GameOfflineTrainingTime = 20 | |
| 105 | + | GamePurseSlot = 21 | |
| 106 | + | GameFormatCreatureName = 22 | |
| 107 | + | GameSpellList = 23 | |
| 108 | + | GameClientPing = 24 | |
| 109 | + | GameExtendedClientPing = 25 | |
| 110 | + | GameDoubleHealth = 28 | |
| 111 | + | GameDoubleSkills = 29 | |
| 112 | + | GameChangeMapAwareRange = 30 | |
| 113 | + | GameMapMovePosition = 31 | |
| 114 | + | GameAttackSeq = 32 | |
| 115 | + | GameBlueNpcNameColor = 33 | |
| 116 | + | GameDiagonalAnimatedText = 34 | |
| 117 | + | GameLoginPending = 35 | |
| 118 | + | GameNewSpeedLaw = 36 | |
| 119 | + | GameForceFirstAutoWalkStep = 37 | |
| 120 | + | GameMinimapRemove = 38 | |
| 121 | + | GameDoubleShopSellAmount = 39 | |
| 122 | + | GameContainerPagination = 40 | |
| 123 | + | GameThingMarks = 41 | |
| 124 | + | GameLooktypeU16 = 42 | |
| 125 | + | GamePlayerStamina = 43 | |
| 126 | + | GamePlayerAddons = 44 | |
| 127 | + | GameMessageStatements = 45 | |
| 128 | + | GameMessageLevel = 46 | |
| 129 | + | GameNewFluids = 47 | |
| 130 | + | GamePlayerStateU16 = 48 | |
| 131 | + | GameNewOutfitProtocol = 49 | |
| 132 | + | GamePVPMode = 50 | |
| 133 | + | GameWritableDate = 51 | |
| 134 | + | GameAdditionalVipInfo = 52 | |
| 135 | + | GameBaseSkillU16 = 53 | |
| 136 | + | GameCreatureIcons = 54 | |
| 137 | + | GameHideNpcNames = 55 | |
| 138 | + | GameSpritesAlphaChannel = 56 | |
| 139 | + | GamePremiumExpiration = 57 | |
| 140 | + | GameBrowseField = 58 | |
| 141 | + | GameEnhancedAnimations = 59 | |
| 142 | + | GameOGLInformation = 60 | |
| 143 | + | GameMessageSizeCheck = 61 | |
| 144 | + | GamePreviewState = 62 | |
| 145 | + | GameLoginPacketEncryption = 63 | |
| 146 | + | GameClientVersion = 64 | |
| 147 | + | GameContentRevision = 65 | |
| 148 | + | GameExperienceBonus = 66 | |
| 149 | + | GameAuthenticator = 67 | |
| 150 | + | GameUnjustifiedPoints = 68 | |
| 151 | + | GameSessionKey = 69 | |
| 152 | + | GameDeathType = 70 | |
| 153 | + | GameIdleAnimations = 71 | |
| 154 | + | GameKeepUnawareTiles = 72 | |
| 155 | + | GameIngameStore = 73 | |
| 156 | + | GameIngameStoreHighlights = 74 | |
| 157 | + | GameIngameStoreServiceType = 75 | |
| 158 | + | GameAdditionalSkills = 76 | |
| 159 | + | GameDistanceEffectU16 = 77 | |
| 160 | + | GamePrey = 78 | |
| 161 | + | GameDoubleMagicLevel = 79 | |
| 162 | + | ||
| 163 | + | GameExtendedOpcode = 80 | |
| 164 | + | GameMinimapLimitedToSingleFloor = 81 | |
| 165 | + | GameSendWorldName = 82 | |
| 166 | + | ||
| 167 | + | GameDoubleLevel = 83 | |
| 168 | + | GameDoubleSoul = 84 | |
| 169 | + | GameDoublePlayerGoodsMoney = 85 | |
| 170 | + | GameCreatureWalkthrough = 86 -- add Walkthrough for versions less than 854, unpass = msg->getU8(); in protocolgameparse.cpp | |
| 171 | + | GameDoubleTradeMoney = 87 | |
| 172 | + | GameSequencedPackets = 88 | |
| 173 | + | GameTibia12Protocol = 89 | |
| 174 | + | ||
| 175 | + | GameNewWalking = 90 | |
| 176 | + | GameSlowerManualWalking = 91 | |
| 177 | + | GameItemTooltip = 93 | |
| 178 | + | ||
| 179 | + | GameBot = 95 | |
| 180 | + | GameBiggerMapCache = 96 | |
| 181 | + | GameForceLight = 97 | |
| 182 | + | GameNoDebug = 98 | |
| 183 | + | GameBotProtection = 99 | |
| 184 | + | ||
| 185 | + | GameCreatureDirectionPassable = 100 | |
| 186 | + | GameFasterAnimations = 101 | |
| 187 | + | GameCenteredOutfits = 102 | |
| 188 | + | GameSendIdentifiers = 103 | |
| 189 | + | GameWingsAndAura = 104 | |
| 190 | + | GamePlayerStateU32 = 105 | |
| 191 | + | GameOutfitShaders = 106 | |
| 192 | + | GameForceAllowItemHotkeys = 107 | |
| 193 | + | GameCountU16 = 108 | |
| 194 | + | GameDrawAuraOnTop = 109 | |
| 195 | + | ||
| 196 | + | GamePacketSizeU32 = 110 | |
| 197 | + | GamePacketCompression = 111 | |
| 198 | + | ||
| 199 | + | GameItemShader = 112 | |
| 200 | + | GameCreatureAttachedEffect = 113 | |
| 201 | + | GameCreatureShader = 114 | |
| 202 | + | GameEffectsShader = 115 | |
| 203 | + | GameWingOffset = 116 | |
| 204 | + | GameAuraFrontAndBack = 115 -- To use that: First layer is bottom/back, second (blend layer) is top/front | |
| 205 | + | GameDontCacheFiles = 118 -- doesn't work with encryption and compression | |
| 206 | + | GameBigAurasCenter = 119 -- Automatic negative offset for aura bigger than 32x32 | |
| 207 | + | GameNewUpdateWalk = 120 -- Walk update rate dependant on FPS | |
| 208 | + | GameNewCreatureStacking = 121 -- Ignore MAX_THINGS limit while adding to tile | |
| 209 | + | GameCreaturesMana = 122 -- get mana from server for creatures other than Player | |
| 210 | + | GameDontMergeAnimatedText = 123 | |
| 211 | + | ||
| 212 | + | GameMissionId = 125 | |
| 213 | + | GameItemCustomAttributes = 126 | |
| 214 | + | GameAnimatedTextCustomFont = 127 | |
| 215 | + | GameOutfitAttackData = 128 | |
| 216 | + | ||
| 217 | + | LastGameFeature = 130 | |
| 218 | + | ||
| 219 | + | TextColors = { | |
| 220 | + | red = '#f55e5e', --'#c83200' | |
| 221 | + | orange = '#f36500', --'#c87832' | |
| 222 | + | yellow = '#ffff00', --'#e6c832' | |
| 223 | + | green = '#00EB00', --'#3fbe32' | |
| 224 | + | lightblue = '#5ff7f7', | |
| 225 | + | blue = '#9f9dfd', | |
| 226 | + | --blue1 = '#6e50dc', | |
| 227 | + | --blue2 = '#3264c8', | |
| 228 | + | --blue3 = '#0096c8', | |
| 229 | + | white = '#ffffff', --'#bebebe' | |
| 230 | + | } | |
| 231 | + | ||
| 232 | + | MessageModes = { | |
| 233 | + | None = 0, | |
| 234 | + | Say = 1, | |
| 235 | + | Whisper = 2, | |
| 236 | + | Yell = 3, | |
| 237 | + | PrivateFrom = 4, | |
| 238 | + | PrivateTo = 5, | |
| 239 | + | ChannelManagement = 6, | |
| 240 | + | Channel = 7, | |
| 241 | + | ChannelHighlight = 8, | |
| 242 | + | Spell = 9, | |
| 243 | + | NpcFrom = 10, | |
| 244 | + | NpcTo = 11, | |
| 245 | + | GamemasterBroadcast = 12, | |
| 246 | + | GamemasterChannel = 13, | |
| 247 | + | GamemasterPrivateFrom = 14, | |
| 248 | + | GamemasterPrivateTo = 15, | |
| 249 | + | Login = 16, | |
| 250 | + | Warning = 17, | |
| 251 | + | Game = 18, | |
| 252 | + | Failure = 19, | |
| 253 | + | Look = 20, | |
| 254 | + | DamageDealed = 21, | |
| 255 | + | DamageReceived = 22, | |
| 256 | + | Heal = 23, | |
| 257 | + | Exp = 24, | |
| 258 | + | DamageOthers = 25, | |
| 259 | + | HealOthers = 26, | |
| 260 | + | ExpOthers = 27, | |
| 261 | + | Status = 28, | |
| 262 | + | Loot = 29, | |
| 263 | + | TradeNpc = 30, | |
| 264 | + | Guild = 31, | |
| 265 | + | PartyManagement = 32, | |
| 266 | + | Party = 33, | |
| 267 | + | BarkLow = 34, | |
| 268 | + | BarkLoud = 35, | |
| 269 | + | Report = 36, | |
| 270 | + | HotkeyUse = 37, | |
| 271 | + | TutorialHint = 38, | |
| 272 | + | Thankyou = 39, | |
| 273 | + | Market = 40, | |
| 274 | + | Mana = 41, | |
| 275 | + | BeyondLast = 42, | |
| 276 | + | MonsterYell = 43, | |
| 277 | + | MonsterSay = 44, | |
| 278 | + | Red = 45, | |
| 279 | + | Blue = 46, | |
| 280 | + | RVRChannel = 47, | |
| 281 | + | RVRAnswer = 48, | |
| 282 | + | RVRContinue = 49, | |
| 283 | + | GameHighlight = 50, | |
| 284 | + | NpcFromStartBlock = 51, | |
| 285 | + | Last = 52, | |
| 286 | + | Invalid = 255, | |
| 287 | + | } | |
| 288 | + | ||
| 289 | + | OTSERV_RSA = "1091201329673994292788609605089955415282375029027981291234687579" .. | |
| 290 | + | "3726629149257644633073969600111060390723088861007265581882535850" .. | |
| 291 | + | "3429057592827629436413108566029093628212635953836686562675849720" .. | |
| 292 | + | "6207862794310902180176810615217550567108238764764442605581471797" .. | |
| 293 | + | "07119674283982419152118103759076030616683978566631413" | |
| 294 | + | ||
| 295 | + | CIPSOFT_RSA = "1321277432058722840622950990822933849527763264961655079678763618" .. | |
| 296 | + | "4334395343554449668205332383339435179772895415509701210392836078" .. | |
| 297 | + | "6959821132214473291575712138800495033169914814069637740318278150" .. | |
| 298 | + | "2907336840325241747827401343576296990629870233111328210165697754" .. | |
| 299 | + | "88792221429527047321331896351555606801473202394175817" | |
| 300 | + | ||
| 301 | + | -- set to the latest Tibia.pic signature to make otclient compatible with official tibia | |
| 302 | + | PIC_SIGNATURE = 0x56C5DDE7 | |
| 303 | + | ||
| 304 | + | OsTypes = { | |
| 305 | + | Linux = 1, | |
| 306 | + | Windows = 2, | |
| 307 | + | Flash = 3, | |
| 308 | + | OtclientLinux = 10, | |
| 309 | + | OtclientWindows = 11, | |
| 310 | + | OtclientMac = 12, | |
| 311 | + | } | |
| 312 | + | ||
| 313 | + | PathFindResults = { | |
| 314 | + | Ok = 0, | |
| 315 | + | Position = 1, | |
| 316 | + | Impossible = 2, | |
| 317 | + | TooFar = 3, | |
| 318 | + | NoWay = 4, | |
| 319 | + | } | |
| 320 | + | ||
| 321 | + | PathFindFlags = { | |
| 322 | + | AllowNullTiles = 1, | |
| 323 | + | AllowCreatures = 2, | |
| 324 | + | AllowNonPathable = 4, | |
| 325 | + | AllowNonWalkable = 8, | |
| 326 | + | } | |
| 327 | + | ||
| 328 | + | VipState = { | |
| 329 | + | Offline = 0, | |
| 330 | + | Online = 1, | |
| 331 | + | Pending = 2, | |
| 332 | + | } | |
| 333 | + | ||
| 334 | + | ExtendedIds = { | |
| 335 | + | Activate = 0, | |
| 336 | + | Locale = 1, | |
| 337 | + | Ping = 2, | |
| 338 | + | Sound = 3, | |
| 339 | + | Game = 4, | |
| 340 | + | Particles = 5, | |
| 341 | + | MapShader = 6, | |
| 342 | + | NeedsUpdate = 7, | |
| 343 | + | Telegraph = 8 | |
| 344 | + | } | |
| 345 | + | ||
| 346 | + | PreviewState = { | |
| 347 | + | Default = 0, | |
| 348 | + | Inactive = 1, | |
| 349 | + | Active = 2 | |
| 350 | + | } | |
| 351 | + | ||
| 352 | + | Blessings = { | |
| 353 | + | None = 0, | |
| 354 | + | Adventurer = 1, | |
| 355 | + | SpiritualShielding = 2, | |
| 356 | + | EmbraceOfTibia = 4, | |
| 357 | + | FireOfSuns = 8, | |
| 358 | + | WisdomOfSolitude = 16, | |
| 359 | + | SparkOfPhoenix = 32 | |
| 360 | + | } | |
| 361 | + | ||
| 362 | + | DeathType = { | |
| 363 | + | Regular = 0, | |
| 364 | + | Blessed = 1 | |
| 365 | + | } | |
| 366 | + | ||
| 367 | + | ProductType = { | |
| 368 | + | Other = 0, | |
| 369 | + | NameChange = 1 | |
| 370 | + | } | |
| 371 | + | ||
| 372 | + | StoreErrorType = { | |
| 373 | + | NoError = -1, | |
| 374 | + | PurchaseError = 0, | |
| 375 | + | NetworkError = 1, | |
| 376 | + | HistoryError = 2, | |
| 377 | + | TransferError = 3, | |
| 378 | + | Information = 4 | |
| 379 | + | } | |
| 380 | + | ||
| 381 | + | StoreState = { | |
| 382 | + | None = 0, | |
| 383 | + | New = 1, | |
| 384 | + | Sale = 2, | |
| 385 | + | Timed = 3 | |
| 386 | + | } | |
| 387 | + | ||
| 388 | + | AccountStatus = { | |
| 389 | + | Ok = 0, | |
| 390 | + | Frozen = 1, | |
| 391 | + | Suspended = 2, | |
| 392 | + | } | |
| 393 | + | ||
| 394 | + | SubscriptionStatus = { | |
| 395 | + | Free = 0, | |
| 396 | + | Premium = 1, | |
| 397 | + | } | |
| 398 | + | ||
| 399 | + | ChannelEvent = { | |
| 400 | + | Join = 0, | |
| 401 | + | Leave = 1, | |
| 402 | + | Invite = 2, | |
| 403 | + | Exclude = 3, | |
| 404 | + | } | |
| 405 | + | ||
| 406 | + | -- Helper for item id validation (add at the end of the file) | |
| 407 | + | function g_things:isValidItemId(itemId) | |
| 408 | + | -- This assumes g_things:getItemType(itemId) returns nil for invalid ids | |
| 409 | + | return self:getItemType(itemId) ~= nil | |
| 410 | + | end | |
| 411 | + | ||
| 412 | + | -- @} |
| @@ -0,0 +1,176 @@ | |||
| 1 | + | -- @docclass Creature | |
| 2 | + | ||
| 3 | + | -- @docconsts @{ | |
| 4 | + | ||
| 5 | + | SkullNone = 0 | |
| 6 | + | SkullYellow = 1 | |
| 7 | + | SkullGreen = 2 | |
| 8 | + | SkullWhite = 3 | |
| 9 | + | SkullRed = 4 | |
| 10 | + | SkullBlack = 5 | |
| 11 | + | SkullOrange = 6 | |
| 12 | + | ||
| 13 | + | ShieldNone = 0 | |
| 14 | + | ShieldWhiteYellow = 1 | |
| 15 | + | ShieldWhiteBlue = 2 | |
| 16 | + | ShieldBlue = 3 | |
| 17 | + | ShieldYellow = 4 | |
| 18 | + | ShieldBlueSharedExp = 5 | |
| 19 | + | ShieldYellowSharedExp = 6 | |
| 20 | + | ShieldBlueNoSharedExpBlink = 7 | |
| 21 | + | ShieldYellowNoSharedExpBlink = 8 | |
| 22 | + | ShieldBlueNoSharedExp = 9 | |
| 23 | + | ShieldYellowNoSharedExp = 10 | |
| 24 | + | ||
| 25 | + | EmblemNone = 0 | |
| 26 | + | EmblemGreen = 1 | |
| 27 | + | EmblemRed = 2 | |
| 28 | + | EmblemBlue = 3 | |
| 29 | + | ||
| 30 | + | NpcIconNone = 0 | |
| 31 | + | NpcIconChat = 1 | |
| 32 | + | NpcIconTrade = 2 | |
| 33 | + | NpcIconQuest = 3 | |
| 34 | + | NpcIconTradeQuest = 4 | |
| 35 | + | ||
| 36 | + | CreatureTypePlayer = 0 | |
| 37 | + | CreatureTypeMonster = 1 | |
| 38 | + | CreatureTypeNpc = 2 | |
| 39 | + | CreatureTypeSummonOwn = 3 | |
| 40 | + | CreatureTypeSummonOther = 4 | |
| 41 | + | ||
| 42 | + | -- @} | |
| 43 | + | ||
| 44 | + | function getNextSkullId(skullId) | |
| 45 | + | if skullId == SkullRed or skullId == SkullBlack then | |
| 46 | + | return SkullBlack | |
| 47 | + | end | |
| 48 | + | return SkullRed | |
| 49 | + | end | |
| 50 | + | ||
| 51 | + | function getSkullImagePath(skullId) | |
| 52 | + | local path | |
| 53 | + | if skullId == SkullYellow then | |
| 54 | + | path = '/images/game/skulls/skull_yellow' | |
| 55 | + | elseif skullId == SkullGreen then | |
| 56 | + | path = '/images/game/skulls/skull_green' | |
| 57 | + | elseif skullId == SkullWhite then | |
| 58 | + | path = '/images/game/skulls/skull_white' | |
| 59 | + | elseif skullId == SkullRed then | |
| 60 | + | path = '/images/game/skulls/skull_red' | |
| 61 | + | elseif skullId == SkullBlack then | |
| 62 | + | path = '/images/game/skulls/skull_black' | |
| 63 | + | elseif skullId == SkullOrange then | |
| 64 | + | path = '/images/game/skulls/skull_orange' | |
| 65 | + | end | |
| 66 | + | return path | |
| 67 | + | end | |
| 68 | + | ||
| 69 | + | function getShieldImagePathAndBlink(shieldId) | |
| 70 | + | local path, blink | |
| 71 | + | if shieldId == ShieldWhiteYellow then | |
| 72 | + | path, blink = '/images/game/shields/shield_yellow_white', false | |
| 73 | + | elseif shieldId == ShieldWhiteBlue then | |
| 74 | + | path, blink = '/images/game/shields/shield_blue_white', false | |
| 75 | + | elseif shieldId == ShieldBlue then | |
| 76 | + | path, blink = '/images/game/shields/shield_blue', false | |
| 77 | + | elseif shieldId == ShieldYellow then | |
| 78 | + | path, blink = '/images/game/shields/shield_yellow', false | |
| 79 | + | elseif shieldId == ShieldBlueSharedExp then | |
| 80 | + | path, blink = '/images/game/shields/shield_blue_shared', false | |
| 81 | + | elseif shieldId == ShieldYellowSharedExp then | |
| 82 | + | path, blink = '/images/game/shields/shield_yellow_shared', false | |
| 83 | + | elseif shieldId == ShieldBlueNoSharedExpBlink then | |
| 84 | + | path, blink = '/images/game/shields/shield_blue_not_shared', true | |
| 85 | + | elseif shieldId == ShieldYellowNoSharedExpBlink then | |
| 86 | + | path, blink = '/images/game/shields/shield_yellow_not_shared', true | |
| 87 | + | elseif shieldId == ShieldBlueNoSharedExp then | |
| 88 | + | path, blink = '/images/game/shields/shield_blue_not_shared', false | |
| 89 | + | elseif shieldId == ShieldYellowNoSharedExp then | |
| 90 | + | path, blink = '/images/game/shields/shield_yellow_not_shared', false | |
| 91 | + | elseif shieldId == ShieldGray then | |
| 92 | + | path, blink = '/images/game/shields/shield_gray', false | |
| 93 | + | end | |
| 94 | + | return path, blink | |
| 95 | + | end | |
| 96 | + | ||
| 97 | + | function getEmblemImagePath(emblemId) | |
| 98 | + | local path | |
| 99 | + | if emblemId == EmblemGreen then | |
| 100 | + | path = '/images/game/emblems/emblem_green' | |
| 101 | + | elseif emblemId == EmblemRed then | |
| 102 | + | path = '/images/game/emblems/emblem_red' | |
| 103 | + | elseif emblemId == EmblemBlue then | |
| 104 | + | path = '/images/game/emblems/emblem_blue' | |
| 105 | + | elseif emblemId == EmblemMember then | |
| 106 | + | path = '/images/game/emblems/emblem_member' | |
| 107 | + | elseif emblemId == EmblemOther then | |
| 108 | + | path = '/images/game/emblems/emblem_other' | |
| 109 | + | end | |
| 110 | + | return path | |
| 111 | + | end | |
| 112 | + | ||
| 113 | + | function getTypeImagePath(creatureType) | |
| 114 | + | local path | |
| 115 | + | if creatureType == CreatureTypeSummonOwn then | |
| 116 | + | path = '/images/game/creaturetype/summon_own' | |
| 117 | + | elseif creatureType == CreatureTypeSummonOther then | |
| 118 | + | path = '/images/game/creaturetype/summon_other' | |
| 119 | + | end | |
| 120 | + | return path | |
| 121 | + | end | |
| 122 | + | ||
| 123 | + | function getIconImagePath(iconId) | |
| 124 | + | local path | |
| 125 | + | if iconId == NpcIconChat then | |
| 126 | + | path = '/images/game/npcicons/icon_chat' | |
| 127 | + | elseif iconId == NpcIconTrade then | |
| 128 | + | path = '/images/game/npcicons/icon_trade' | |
| 129 | + | elseif iconId == NpcIconQuest then | |
| 130 | + | path = '/images/game/npcicons/icon_quest' | |
| 131 | + | elseif iconId == NpcIconTradeQuest then | |
| 132 | + | path = '/images/game/npcicons/icon_tradequest' | |
| 133 | + | end | |
| 134 | + | return path | |
| 135 | + | end | |
| 136 | + | ||
| 137 | + | function Creature:onSkullChange(skullId) | |
| 138 | + | local imagePath = getSkullImagePath(skullId) | |
| 139 | + | if imagePath then | |
| 140 | + | self:setSkullTexture(imagePath) | |
| 141 | + | end | |
| 142 | + | end | |
| 143 | + | ||
| 144 | + | function Creature:onShieldChange(shieldId) | |
| 145 | + | local imagePath, blink = getShieldImagePathAndBlink(shieldId) | |
| 146 | + | if imagePath then | |
| 147 | + | self:setShieldTexture(imagePath, blink) | |
| 148 | + | end | |
| 149 | + | end | |
| 150 | + | ||
| 151 | + | function Creature:onEmblemChange(emblemId) | |
| 152 | + | local imagePath = getEmblemImagePath(emblemId) | |
| 153 | + | if imagePath then | |
| 154 | + | self:setEmblemTexture(imagePath) | |
| 155 | + | end | |
| 156 | + | end | |
| 157 | + | ||
| 158 | + | function Creature:onTypeChange(typeId) | |
| 159 | + | local imagePath = getTypeImagePath(typeId) | |
| 160 | + | if imagePath then | |
| 161 | + | self:setTypeTexture(imagePath) | |
| 162 | + | end | |
| 163 | + | end | |
| 164 | + | ||
| 165 | + | function Creature:onIconChange(iconId) | |
| 166 | + | local imagePath = getIconImagePath(iconId) | |
| 167 | + | if imagePath then | |
| 168 | + | self:setIconTexture(imagePath) | |
| 169 | + | end | |
| 170 | + | end | |
| 171 | + | ||
| 172 | + | function Creature:setOutfitShader(shader) | |
| 173 | + | local outfit = self:getOutfit() | |
| 174 | + | outfit.shader = shader | |
| 175 | + | self:setOutfit(outfit) | |
| 176 | + | end |
| @@ -0,0 +1,118 @@ | |||
| 1 | + | function g_game.getRsa() | |
| 2 | + | return G.currentRsa | |
| 3 | + | end | |
| 4 | + | ||
| 5 | + | function g_game.findPlayerItem(itemId, subType) | |
| 6 | + | local localPlayer = g_game.getLocalPlayer() | |
| 7 | + | if localPlayer then | |
| 8 | + | for slot = InventorySlotFirst, InventorySlotLast do | |
| 9 | + | local item = localPlayer:getInventoryItem(slot) | |
| 10 | + | if item and item:getId() == itemId and (subType == -1 or item:getSubType() == subType) then | |
| 11 | + | return item | |
| 12 | + | end | |
| 13 | + | end | |
| 14 | + | end | |
| 15 | + | ||
| 16 | + | return g_game.findItemInContainers(itemId, subType) | |
| 17 | + | end | |
| 18 | + | ||
| 19 | + | function g_game.chooseRsa(host) | |
| 20 | + | if G.currentRsa ~= CIPSOFT_RSA and G.currentRsa ~= OTSERV_RSA then return end | |
| 21 | + | if host:ends('.tibia.com') or host:ends('.cipsoft.com') then | |
| 22 | + | g_game.setRsa(CIPSOFT_RSA) | |
| 23 | + | ||
| 24 | + | if g_app.getOs() == 'windows' then | |
| 25 | + | g_game.setCustomOs(OsTypes.Windows) | |
| 26 | + | else | |
| 27 | + | g_game.setCustomOs(OsTypes.Linux) | |
| 28 | + | end | |
| 29 | + | else | |
| 30 | + | if G.currentRsa == CIPSOFT_RSA then | |
| 31 | + | g_game.setCustomOs(-1) | |
| 32 | + | end | |
| 33 | + | g_game.setRsa(OTSERV_RSA) | |
| 34 | + | end | |
| 35 | + | ||
| 36 | + | -- Hack fix to resolve some 760 login issues | |
| 37 | + | if g_game.getClientVersion() <= 760 then | |
| 38 | + | g_game.setCustomOs(2) | |
| 39 | + | end | |
| 40 | + | end | |
| 41 | + | ||
| 42 | + | function g_game.setRsa(rsa, e) | |
| 43 | + | e = e or '65537' | |
| 44 | + | g_crypt.rsaSetPublicKey(rsa, e) | |
| 45 | + | G.currentRsa = rsa | |
| 46 | + | end | |
| 47 | + | ||
| 48 | + | function g_game.isOfficialTibia() | |
| 49 | + | return G.currentRsa == CIPSOFT_RSA | |
| 50 | + | end | |
| 51 | + | ||
| 52 | + | function g_game.getSupportedClients() | |
| 53 | + | return { | |
| 54 | + | 740, 741, 750, 760, 770, 772, | |
| 55 | + | 780, 781, 782, 790, 792, | |
| 56 | + | ||
| 57 | + | 800, 810, 811, 820, 821, 822, | |
| 58 | + | 830, 831, 840, 842, 850, 853, | |
| 59 | + | 854, 855, 857, 860, 861, 862, | |
| 60 | + | 870, 871, | |
| 61 | + | ||
| 62 | + | 900, 910, 920, 931, 940, 943, | |
| 63 | + | 944, 951, 952, 953, 954, 960, | |
| 64 | + | 961, 963, 970, 971, 972, 973, | |
| 65 | + | 980, 981, 982, 983, 984, 985, | |
| 66 | + | 986, | |
| 67 | + | ||
| 68 | + | 1000, 1001, 1002, 1010, 1011, | |
| 69 | + | 1012, 1013, 1020, 1021, 1022, | |
| 70 | + | 1030, 1031, 1032, 1033, 1034, | |
| 71 | + | 1035, 1036, 1037, 1038, 1039, | |
| 72 | + | 1040, 1041, 1050, 1051, 1052, | |
| 73 | + | 1053, 1054, 1055, 1056, 1057, | |
| 74 | + | 1058, 1059, 1060, 1061, 1062, | |
| 75 | + | 1063, 1064, 1070, 1071, 1072, | |
| 76 | + | 1073, 1074, 1075, 1076, 1080, | |
| 77 | + | 1081, 1082, 1090, 1091, 1092, | |
| 78 | + | 1093, 1094, 1095, 1096, 1097, | |
| 79 | + | 1098, 1099 | |
| 80 | + | } | |
| 81 | + | end | |
| 82 | + | ||
| 83 | + | -- The client version and protocol version where | |
| 84 | + | -- unsynchronized for some releases, not sure if this | |
| 85 | + | -- will be the normal standard. | |
| 86 | + | ||
| 87 | + | -- Client Version: Publicly given version when | |
| 88 | + | -- downloading Cipsoft client. | |
| 89 | + | ||
| 90 | + | -- Protocol Version: Previously was the same as | |
| 91 | + | -- the client version, but was unsychronized in some | |
| 92 | + | -- releases, now it needs to be verified and added here | |
| 93 | + | -- if it does not match the client version. | |
| 94 | + | ||
| 95 | + | -- Reason for defining both: The server now requires a | |
| 96 | + | -- Client version and Protocol version from the client. | |
| 97 | + | ||
| 98 | + | -- Important: Use getClientVersion for specific protocol | |
| 99 | + | -- features to ensure we are using the proper version. | |
| 100 | + | ||
| 101 | + | function g_game.getClientProtocolVersion(client) | |
| 102 | + | local clients = { | |
| 103 | + | [980] = 971, | |
| 104 | + | [981] = 973, | |
| 105 | + | [982] = 974, | |
| 106 | + | [983] = 975, | |
| 107 | + | [984] = 976, | |
| 108 | + | [985] = 977, | |
| 109 | + | [986] = 978, | |
| 110 | + | [1001] = 979, | |
| 111 | + | [1002] = 980 | |
| 112 | + | } | |
| 113 | + | return clients[client] or client | |
| 114 | + | end | |
| 115 | + | ||
| 116 | + | if not G.currentRsa then | |
| 117 | + | g_game.setRsa(OTSERV_RSA) | |
| 118 | + | end |
| @@ -0,0 +1,23 @@ | |||
| 1 | + | Module | |
| 2 | + | name: gamelib | |
| 3 | + | description: Contains game related classes | |
| 4 | + | author: OTClient team | |
| 5 | + | website: https://github.com/edubart/otclient | |
| 6 | + | ||
| 7 | + | @onLoad: | | |
| 8 | + | dofile 'const' | |
| 9 | + | dofile 'util' | |
| 10 | + | dofile 'protocol' | |
| 11 | + | dofile 'protocollogin' | |
| 12 | + | dofile 'protocolgame' | |
| 13 | + | dofile 'position' | |
| 14 | + | dofile 'game' | |
| 15 | + | ||
| 16 | + | dofile 'creature' | |
| 17 | + | dofile 'player' | |
| 18 | + | dofile 'market' | |
| 19 | + | dofile 'textmessages' | |
| 20 | + | dofile 'thing' | |
| 21 | + | dofile 'spells' | |
| 22 | + | ||
| 23 | + | dofiles 'ui' |
| @@ -0,0 +1,202 @@ | |||
| 1 | + | MarketMaxAmount = 2000 | |
| 2 | + | MarketMaxAmountStackable = 64000 | |
| 3 | + | MarketMaxPrice = 999999999 | |
| 4 | + | MarketMaxOffers = 100 | |
| 5 | + | ||
| 6 | + | MarketAction = { | |
| 7 | + | Buy = 0, | |
| 8 | + | Sell = 1 | |
| 9 | + | } | |
| 10 | + | ||
| 11 | + | MarketRequest = { | |
| 12 | + | MyOffers = 0xFFFE, | |
| 13 | + | MyHistory = 0xFFFF | |
| 14 | + | } | |
| 15 | + | ||
| 16 | + | MarketOfferState = { | |
| 17 | + | Active = 0, | |
| 18 | + | Cancelled = 1, | |
| 19 | + | Expired = 2, | |
| 20 | + | Accepted = 3, | |
| 21 | + | AcceptedEx = 255 | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | MarketCategory = { | |
| 25 | + | All = 0, | |
| 26 | + | Armors = 1, | |
| 27 | + | Amulets = 2, | |
| 28 | + | Boots = 3, | |
| 29 | + | Containers = 4, | |
| 30 | + | Decoration = 5, | |
| 31 | + | Food = 6, | |
| 32 | + | HelmetsHats = 7, | |
| 33 | + | Legs = 8, | |
| 34 | + | Others = 9, | |
| 35 | + | Potions = 10, | |
| 36 | + | Rings = 11, | |
| 37 | + | Runes = 12, | |
| 38 | + | Shields = 13, | |
| 39 | + | Tools = 14, | |
| 40 | + | Valuables = 15, | |
| 41 | + | Ammunition = 16, | |
| 42 | + | Axes = 17, | |
| 43 | + | Clubs = 18, | |
| 44 | + | DistanceWeapons = 19, | |
| 45 | + | Swords = 20, | |
| 46 | + | WandsRods = 21, | |
| 47 | + | PremiumScrolls = 22, | |
| 48 | + | TibiaCoins = 23, | |
| 49 | + | CreatureProducs = 24, | |
| 50 | + | Unknown1 = 25, | |
| 51 | + | Unknown2 = 26, | |
| 52 | + | StashRetrieve = 27, | |
| 53 | + | Unknown3 = 28, | |
| 54 | + | Unknown4 = 29, | |
| 55 | + | Gold = 30, | |
| 56 | + | Unassigned = 31, | |
| 57 | + | MetaWeapons = 255 | |
| 58 | + | } | |
| 59 | + | ||
| 60 | + | MarketCategory.First = MarketCategory.Armors | |
| 61 | + | MarketCategory.Last = MarketCategory.Unassigned | |
| 62 | + | ||
| 63 | + | MarketCategoryWeapons = { | |
| 64 | + | [MarketCategory.Ammunition] = { slots = {255} }, | |
| 65 | + | [MarketCategory.Axes] = { slots = {255, InventorySlotOther, InventorySlotLeft} }, | |
| 66 | + | [MarketCategory.Clubs] = { slots = {255, InventorySlotOther, InventorySlotLeft} }, | |
| 67 | + | [MarketCategory.DistanceWeapons] = { slots = {255, InventorySlotOther, InventorySlotLeft} }, | |
| 68 | + | [MarketCategory.Swords] = { slots = {255, InventorySlotOther, InventorySlotLeft} }, | |
| 69 | + | [MarketCategory.WandsRods] = { slots = {255, InventorySlotOther, InventorySlotLeft} } | |
| 70 | + | } | |
| 71 | + | ||
| 72 | + | MarketCategoryStrings = { | |
| 73 | + | [0] = 'All', | |
| 74 | + | [1] = 'Armors', | |
| 75 | + | [2] = 'Amulets', | |
| 76 | + | [3] = 'Boots', | |
| 77 | + | [4] = 'Containers', | |
| 78 | + | [5] = 'Decoration', | |
| 79 | + | [6] = 'Food', | |
| 80 | + | [7] = 'Helmets and Hats', | |
| 81 | + | [8] = 'Legs', | |
| 82 | + | [9] = 'Others', | |
| 83 | + | [10] = 'Potions', | |
| 84 | + | [11] = 'Rings', | |
| 85 | + | [12] = 'Runes', | |
| 86 | + | [13] = 'Shields', | |
| 87 | + | [14] = 'Tools', | |
| 88 | + | [15] = 'Valuables', | |
| 89 | + | [16] = 'Ammunition', | |
| 90 | + | [17] = 'Axes', | |
| 91 | + | [18] = 'Clubs', | |
| 92 | + | [19] = 'Distance Weapons', | |
| 93 | + | [20] = 'Swords', | |
| 94 | + | [21] = 'Wands and Rods', | |
| 95 | + | [22] = 'Premium Scrolls', | |
| 96 | + | [23] = 'Tibia Coins', | |
| 97 | + | [24] = 'Creature Products', | |
| 98 | + | [25] = 'Unknown 1', | |
| 99 | + | [26] = 'Unknown 2', | |
| 100 | + | [27] = 'Stash Retrieve', | |
| 101 | + | [28] = 'Unknown 3', | |
| 102 | + | [29] = 'Unknown 4', | |
| 103 | + | [30] = 'Gold', | |
| 104 | + | [31] = 'Unassigned', | |
| 105 | + | [255] = 'Weapons' | |
| 106 | + | } | |
| 107 | + | ||
| 108 | + | function getMarketCategoryName(id) | |
| 109 | + | if table.haskey(MarketCategoryStrings, id) then | |
| 110 | + | return MarketCategoryStrings[id] | |
| 111 | + | end | |
| 112 | + | end | |
| 113 | + | ||
| 114 | + | function getMarketCategoryId(name) | |
| 115 | + | local id = table.find(MarketCategoryStrings, name) | |
| 116 | + | if id then | |
| 117 | + | return id | |
| 118 | + | end | |
| 119 | + | end | |
| 120 | + | ||
| 121 | + | MarketItemDescription = { | |
| 122 | + | Armor = 1, | |
| 123 | + | Attack = 2, | |
| 124 | + | Container = 3, | |
| 125 | + | Defense = 4, | |
| 126 | + | General = 5, | |
| 127 | + | DecayTime = 6, | |
| 128 | + | Combat = 7, | |
| 129 | + | MinLevel = 8, | |
| 130 | + | MinMagicLevel = 9, | |
| 131 | + | Vocation = 10, | |
| 132 | + | Rune = 11, | |
| 133 | + | Ability = 12, | |
| 134 | + | Charges = 13, | |
| 135 | + | WeaponName = 14, | |
| 136 | + | Weight = 15, | |
| 137 | + | Imbuements = 16 | |
| 138 | + | } | |
| 139 | + | ||
| 140 | + | MarketItemDescription.First = MarketItemDescription.Armor | |
| 141 | + | MarketItemDescription.Last = MarketItemDescription.Weight | |
| 142 | + | ||
| 143 | + | MarketItemDescriptionStrings = { | |
| 144 | + | [1] = 'Armor', | |
| 145 | + | [2] = 'Attack', | |
| 146 | + | [3] = 'Container', | |
| 147 | + | [4] = 'Defense', | |
| 148 | + | [5] = 'Description', | |
| 149 | + | [6] = 'Use Time', | |
| 150 | + | [7] = 'Combat', | |
| 151 | + | [8] = 'Min Level', | |
| 152 | + | [9] = 'Min Magic Level', | |
| 153 | + | [10] = 'Vocation', | |
| 154 | + | [11] = 'Rune', | |
| 155 | + | [12] = 'Ability', | |
| 156 | + | [13] = 'Charges', | |
| 157 | + | [14] = 'Weapon Type', | |
| 158 | + | [15] = 'Weight', | |
| 159 | + | [16] = 'Imbuements' | |
| 160 | + | } | |
| 161 | + | ||
| 162 | + | function getMarketDescriptionName(id) | |
| 163 | + | if table.haskey(MarketItemDescriptionStrings, id) then | |
| 164 | + | return MarketItemDescriptionStrings[id] | |
| 165 | + | end | |
| 166 | + | end | |
| 167 | + | ||
| 168 | + | function getMarketDescriptionId(name) | |
| 169 | + | local id = table.find(MarketItemDescriptionStrings, name) | |
| 170 | + | if id then | |
| 171 | + | return id | |
| 172 | + | end | |
| 173 | + | end | |
| 174 | + | ||
| 175 | + | MarketSlotFilters = { | |
| 176 | + | [InventorySlotOther] = "Two-Handed", | |
| 177 | + | [InventorySlotLeft] = "One-Handed", | |
| 178 | + | [255] = "Any" | |
| 179 | + | } | |
| 180 | + | ||
| 181 | + | MarketFilters = { | |
| 182 | + | Vocation = 1, | |
| 183 | + | Level = 2, | |
| 184 | + | Depot = 3, | |
| 185 | + | SearchAll = 4 | |
| 186 | + | } | |
| 187 | + | ||
| 188 | + | MarketFilters.First = MarketFilters.Vocation | |
| 189 | + | MarketFilters.Last = MarketFilters.Depot | |
| 190 | + | ||
| 191 | + | function getMarketSlotFilterId(name) | |
| 192 | + | local id = table.find(MarketSlotFilters, name) | |
| 193 | + | if id then | |
| 194 | + | return id | |
| 195 | + | end | |
| 196 | + | end | |
| 197 | + | ||
| 198 | + | function getMarketSlotFilterName(id) | |
| 199 | + | if table.haskey(MarketSlotFilters, id) then | |
| 200 | + | return MarketSlotFilters[id] | |
| 201 | + | end | |
| 202 | + | end |
| @@ -0,0 +1,151 @@ | |||
| 1 | + | -- @docclass Player | |
| 2 | + | ||
| 3 | + | PlayerStates = { | |
| 4 | + | None = 0, | |
| 5 | + | Poison = 1, | |
| 6 | + | Burn = 2, | |
| 7 | + | Energy = 4, | |
| 8 | + | Drunk = 8, | |
| 9 | + | ManaShield = 16, | |
| 10 | + | Paralyze = 32, | |
| 11 | + | Haste = 64, | |
| 12 | + | Swords = 128, | |
| 13 | + | Drowning = 256, | |
| 14 | + | Freezing = 512, | |
| 15 | + | Dazzled = 1024, | |
| 16 | + | Cursed = 2048, | |
| 17 | + | PartyBuff = 4096, | |
| 18 | + | PzBlock = 8192, | |
| 19 | + | Pz = 16384, | |
| 20 | + | Bleeding = 32768, | |
| 21 | + | Hungry = 65536 | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | InventorySlotOther = 0 | |
| 25 | + | InventorySlotHead = 1 | |
| 26 | + | InventorySlotNeck = 2 | |
| 27 | + | InventorySlotBack = 3 | |
| 28 | + | InventorySlotBody = 4 | |
| 29 | + | InventorySlotRight = 5 | |
| 30 | + | InventorySlotLeft = 6 | |
| 31 | + | InventorySlotLeg = 7 | |
| 32 | + | InventorySlotFeet = 8 | |
| 33 | + | InventorySlotFinger = 9 | |
| 34 | + | InventorySlotAmmo = 10 | |
| 35 | + | InventorySlotPurse = 11 | |
| 36 | + | ||
| 37 | + | InventorySlotFirst = 1 | |
| 38 | + | InventorySlotLast = 10 | |
| 39 | + | ||
| 40 | + | function Player:isPartyLeader() | |
| 41 | + | local shield = self:getShield() | |
| 42 | + | return (shield == ShieldWhiteYellow or | |
| 43 | + | shield == ShieldYellow or | |
| 44 | + | shield == ShieldYellowSharedExp or | |
| 45 | + | shield == ShieldYellowNoSharedExpBlink or | |
| 46 | + | shield == ShieldYellowNoSharedExp) | |
| 47 | + | end | |
| 48 | + | ||
| 49 | + | function Player:isPartyMember() | |
| 50 | + | local shield = self:getShield() | |
| 51 | + | return (shield == ShieldWhiteYellow or | |
| 52 | + | shield == ShieldYellow or | |
| 53 | + | shield == ShieldYellowSharedExp or | |
| 54 | + | shield == ShieldYellowNoSharedExpBlink or | |
| 55 | + | shield == ShieldYellowNoSharedExp or | |
| 56 | + | shield == ShieldBlueSharedExp or | |
| 57 | + | shield == ShieldBlueNoSharedExpBlink or | |
| 58 | + | shield == ShieldBlueNoSharedExp or | |
| 59 | + | shield == ShieldBlue) | |
| 60 | + | end | |
| 61 | + | ||
| 62 | + | function Player:isPartySharedExperienceActive() | |
| 63 | + | local shield = self:getShield() | |
| 64 | + | return (shield == ShieldYellowSharedExp or | |
| 65 | + | shield == ShieldYellowNoSharedExpBlink or | |
| 66 | + | shield == ShieldYellowNoSharedExp or | |
| 67 | + | shield == ShieldBlueSharedExp or | |
| 68 | + | shield == ShieldBlueNoSharedExpBlink or | |
| 69 | + | shield == ShieldBlueNoSharedExp) | |
| 70 | + | end | |
| 71 | + | ||
| 72 | + | function Player:hasVip(creatureName) | |
| 73 | + | for id, vip in pairs(g_game.getVips()) do | |
| 74 | + | if (vip[1] == creatureName) then return true end | |
| 75 | + | end | |
| 76 | + | return false | |
| 77 | + | end | |
| 78 | + | ||
| 79 | + | function Player:isMounted() | |
| 80 | + | local outfit = self:getOutfit() | |
| 81 | + | return outfit.mount ~= nil and outfit.mount > 0 | |
| 82 | + | end | |
| 83 | + | ||
| 84 | + | function Player:toggleMount() | |
| 85 | + | if g_game.getFeature(GamePlayerMounts) then | |
| 86 | + | g_game.mount(not self:isMounted()) | |
| 87 | + | end | |
| 88 | + | end | |
| 89 | + | ||
| 90 | + | function Player:mount() | |
| 91 | + | if g_game.getFeature(GamePlayerMounts) then | |
| 92 | + | g_game.mount(true) | |
| 93 | + | end | |
| 94 | + | end | |
| 95 | + | ||
| 96 | + | function Player:dismount() | |
| 97 | + | if g_game.getFeature(GamePlayerMounts) then | |
| 98 | + | g_game.mount(false) | |
| 99 | + | end | |
| 100 | + | end | |
| 101 | + | ||
| 102 | + | function Player:getItem(itemId, subType) | |
| 103 | + | return g_game.findPlayerItem(itemId, subType or -1) | |
| 104 | + | end | |
| 105 | + | ||
| 106 | + | function Player:getItems(itemId, subType) | |
| 107 | + | local subType = subType or -1 | |
| 108 | + | ||
| 109 | + | local items = {} | |
| 110 | + | for i=InventorySlotFirst,InventorySlotLast do | |
| 111 | + | local item = self:getInventoryItem(i) | |
| 112 | + | if item and item:getId() == itemId and (subType == -1 or item:getSubType() == subType) then | |
| 113 | + | table.insert(items, item) | |
| 114 | + | end | |
| 115 | + | end | |
| 116 | + | ||
| 117 | + | for i, container in pairs(g_game.getContainers()) do | |
| 118 | + | for j, item in pairs(container:getItems()) do | |
| 119 | + | if item:getId() == itemId and (subType == -1 or item:getSubType() == subType) then | |
| 120 | + | item.container = container | |
| 121 | + | table.insert(items, item) | |
| 122 | + | end | |
| 123 | + | end | |
| 124 | + | end | |
| 125 | + | return items | |
| 126 | + | end | |
| 127 | + | ||
| 128 | + | function Player:getItemsCount(itemId) | |
| 129 | + | local items, count = self:getItems(itemId), 0 | |
| 130 | + | for i=1,#items do | |
| 131 | + | count = count + items[i]:getCount() | |
| 132 | + | end | |
| 133 | + | return count | |
| 134 | + | end | |
| 135 | + | ||
| 136 | + | function Player:hasState(state, states) | |
| 137 | + | if not states then | |
| 138 | + | states = self:getStates() | |
| 139 | + | end | |
| 140 | + | ||
| 141 | + | for i = 1, 32 do | |
| 142 | + | local pow = math.pow(2, i-1) | |
| 143 | + | if pow > states then break end | |
| 144 | + | ||
| 145 | + | local states = bit32.band(states, pow) | |
| 146 | + | if states == state then | |
| 147 | + | return true | |
| 148 | + | end | |
| 149 | + | end | |
| 150 | + | return false | |
| 151 | + | end |
| @@ -0,0 +1,37 @@ | |||
| 1 | + | Position = {} | |
| 2 | + | ||
| 3 | + | function Position.equals(pos1, pos2) | |
| 4 | + | return pos1.x == pos2.x and pos1.y == pos2.y and pos1.z == pos2.z | |
| 5 | + | end | |
| 6 | + | ||
| 7 | + | function Position.greaterThan(pos1, pos2, orEqualTo) | |
| 8 | + | if orEqualTo then | |
| 9 | + | return pos1.x >= pos2.x or pos1.y >= pos2.y or pos1.z >= pos2.z | |
| 10 | + | else | |
| 11 | + | return pos1.x > pos2.x or pos1.y > pos2.y or pos1.z > pos2.z | |
| 12 | + | end | |
| 13 | + | end | |
| 14 | + | ||
| 15 | + | function Position.lessThan(pos1, pos2, orEqualTo) | |
| 16 | + | if orEqualTo then | |
| 17 | + | return pos1.x <= pos2.x or pos1.y <= pos2.y or pos1.z <= pos2.z | |
| 18 | + | else | |
| 19 | + | return pos1.x < pos2.x or pos1.y < pos2.y or pos1.z < pos2.z | |
| 20 | + | end | |
| 21 | + | end | |
| 22 | + | ||
| 23 | + | function Position.isInRange(pos1, pos2, xRange, yRange) | |
| 24 | + | return math.abs(pos1.x-pos2.x) <= xRange and math.abs(pos1.y-pos2.y) <= yRange and pos1.z == pos2.z; | |
| 25 | + | end | |
| 26 | + | ||
| 27 | + | function Position.isValid(pos) | |
| 28 | + | return not (pos.x == 65535 and pos.y == 65535 and pos.z == 255) | |
| 29 | + | end | |
| 30 | + | ||
| 31 | + | function Position.distance(pos1, pos2) | |
| 32 | + | return math.sqrt(math.pow((pos2.x - pos1.x), 2) + math.pow((pos2.y - pos1.y), 2)) | |
| 33 | + | end | |
| 34 | + | ||
| 35 | + | function Position.manhattanDistance(pos1, pos2) | |
| 36 | + | return math.abs(pos2.x - pos1.x) + math.abs(pos2.y - pos1.y) | |
| 37 | + | end |
| @@ -0,0 +1,206 @@ | |||
| 1 | + | GameServerOpcodes = { | |
| 2 | + | GameServerInitGame = 10, | |
| 3 | + | GameServerGMActions = 11, | |
| 4 | + | GameServerEnterGame = 15, | |
| 5 | + | GameServerLoginError = 20, | |
| 6 | + | GameServerLoginAdvice = 21, | |
| 7 | + | GameServerLoginWait = 22, | |
| 8 | + | GameServerAddCreature = 23, | |
| 9 | + | GameServerPingBack = 29, | |
| 10 | + | GameServerPing = 30, | |
| 11 | + | GameServerChallenge = 31, | |
| 12 | + | GameServerDeath = 40, | |
| 13 | + | ||
| 14 | + | -- all in game opcodes must be greater than 50 | |
| 15 | + | GameServerFirstGameOpcode = 50, | |
| 16 | + | ||
| 17 | + | -- otclient ONLY | |
| 18 | + | GameServerExtendedOpcode = 50, | |
| 19 | + | ||
| 20 | + | -- NOTE: add any custom opcodes in this range | |
| 21 | + | -- 51 - 99 | |
| 22 | + | ||
| 23 | + | -- original tibia ONLY | |
| 24 | + | GameServerFullMap = 100, | |
| 25 | + | GameServerMapTopRow = 101, | |
| 26 | + | GameServerMapRightRow = 102, | |
| 27 | + | GameServerMapBottomRow = 103, | |
| 28 | + | GameServerMapLeftRow = 104, | |
| 29 | + | GameServerUpdateTile = 105, | |
| 30 | + | GameServerCreateOnMap = 106, | |
| 31 | + | GameServerChangeOnMap = 107, | |
| 32 | + | GameServerDeleteOnMap = 108, | |
| 33 | + | GameServerMoveCreature = 109, | |
| 34 | + | GameServerOpenContainer = 110, | |
| 35 | + | GameServerCloseContainer = 111, | |
| 36 | + | GameServerCreateContainer = 112, | |
| 37 | + | GameServerChangeInContainer = 113, | |
| 38 | + | GameServerDeleteInContainer = 114, | |
| 39 | + | GameServerSetInventory = 120, | |
| 40 | + | GameServerDeleteInventory = 121, | |
| 41 | + | GameServerOpenNpcTrade = 122, | |
| 42 | + | GameServerPlayerGoods = 123, | |
| 43 | + | GameServerCloseNpcTrade = 124, | |
| 44 | + | GameServerOwnTrade = 125, | |
| 45 | + | GameServerCounterTrade = 126, | |
| 46 | + | GameServerCloseTrade = 127, | |
| 47 | + | GameServerAmbient = 130, | |
| 48 | + | GameServerGraphicalEffect = 131, | |
| 49 | + | GameServerTextEffect = 132, | |
| 50 | + | GameServerMissleEffect = 133, | |
| 51 | + | GameServerMarkCreature = 134, | |
| 52 | + | GameServerTrappers = 135, | |
| 53 | + | GameServerCreatureHealth = 140, | |
| 54 | + | GameServerCreatureLight = 141, | |
| 55 | + | GameServerCreatureOutfit = 142, | |
| 56 | + | GameServerCreatureSpeed = 143, | |
| 57 | + | GameServerCreatureSkull = 144, | |
| 58 | + | GameServerCreatureParty = 145, | |
| 59 | + | GameServerCreatureUnpass = 146, | |
| 60 | + | GameServerEditText = 150, | |
| 61 | + | GameServerEditList = 151, | |
| 62 | + | GameServerPlayerDataBasic = 159, -- 910 | |
| 63 | + | GameServerPlayerData = 160, | |
| 64 | + | GameServerPlayerSkills = 161, | |
| 65 | + | GameServerPlayerState = 162, | |
| 66 | + | GameServerClearTarget = 163, | |
| 67 | + | GameServerSpellDelay = 164, -- 870 | |
| 68 | + | GameServerSpellGroupDelay = 165, -- 870 | |
| 69 | + | GameServerMultiUseDelay = 166, -- 870 | |
| 70 | + | GameServerTalk = 170, | |
| 71 | + | GameServerChannels = 171, | |
| 72 | + | GameServerOpenChannel = 172, | |
| 73 | + | GameServerOpenPrivateChannel = 173, | |
| 74 | + | GameServerRuleViolationChannel = 174, | |
| 75 | + | GameServerRuleViolationRemove = 175, | |
| 76 | + | GameServerRuleViolationCancel = 176, | |
| 77 | + | GameServerRuleViolationLock = 177, | |
| 78 | + | GameServerOpenOwnChannel = 178, | |
| 79 | + | GameServerCloseChannel = 179, | |
| 80 | + | GameServerTextMessage = 180, | |
| 81 | + | GameServerCancelWalk = 181, | |
| 82 | + | GameServerWalkWait = 182, | |
| 83 | + | GameServerFloorChangeUp = 190, | |
| 84 | + | GameServerFloorChangeDown = 191, | |
| 85 | + | GameServerChooseOutfit = 200, | |
| 86 | + | GameServerVipAdd = 210, | |
| 87 | + | GameServerVipLogin = 211, | |
| 88 | + | GameServerVipLogout = 212, | |
| 89 | + | GameServerTutorialHint = 220, | |
| 90 | + | GameServerAutomapFlag = 221, | |
| 91 | + | GameServerCoinBalance = 223, -- 1080 | |
| 92 | + | GameServerStoreError = 224, -- 1080 | |
| 93 | + | GameServerRequestPurchaseData = 225, -- 1080 | |
| 94 | + | GameServerQuestLog = 240, | |
| 95 | + | GameServerQuestLine = 241, | |
| 96 | + | GameServerCoinBalanceUpdating = 242, -- 1080 | |
| 97 | + | GameServerChannelEvent = 243, -- 910 | |
| 98 | + | GameServerItemInfo = 244, -- 910 | |
| 99 | + | GameServerPlayerInventory = 245, -- 910 | |
| 100 | + | GameServerMarketEnter = 246, -- 944 | |
| 101 | + | GameServerMarketLeave = 247, -- 944 | |
| 102 | + | GameServerMarketDetail = 248, -- 944 | |
| 103 | + | GameServerMarketBrowse = 249, -- 944 | |
| 104 | + | GameServerShowModalDialog = 250, -- 960 | |
| 105 | + | GameServerStore = 251, -- 1080 | |
| 106 | + | GameServerStoreOffers = 252, -- 1080 | |
| 107 | + | GameServerStoreTransactionHistory = 253, -- 1080 | |
| 108 | + | GameServerStoreCompletePurchase = 254 -- 1080 | |
| 109 | + | } | |
| 110 | + | ||
| 111 | + | ClientOpcodes = { | |
| 112 | + | ClientEnterAccount = 1, | |
| 113 | + | ClientEnterGame = 10, | |
| 114 | + | ClientLeaveGame = 20, | |
| 115 | + | ClientPing = 29, | |
| 116 | + | ClientPingBack = 30, | |
| 117 | + | ||
| 118 | + | -- all in game opcodes must be equal or greater than 50 | |
| 119 | + | ClientFirstGameOpcode = 50, | |
| 120 | + | ||
| 121 | + | -- otclient ONLY | |
| 122 | + | ClientExtendedOpcode = 50, | |
| 123 | + | ||
| 124 | + | -- NOTE: add any custom opcodes in this range | |
| 125 | + | -- 51 - 99 | |
| 126 | + | ||
| 127 | + | -- original tibia ONLY | |
| 128 | + | ClientAutoWalk = 100, | |
| 129 | + | ClientWalkNorth = 101, | |
| 130 | + | ClientWalkEast = 102, | |
| 131 | + | ClientWalkSouth = 103, | |
| 132 | + | ClientWalkWest = 104, | |
| 133 | + | ClientStop = 105, | |
| 134 | + | ClientWalkNorthEast = 106, | |
| 135 | + | ClientWalkSouthEast = 107, | |
| 136 | + | ClientWalkSouthWest = 108, | |
| 137 | + | ClientWalkNorthWest = 109, | |
| 138 | + | ClientTurnNorth = 111, | |
| 139 | + | ClientTurnEast = 112, | |
| 140 | + | ClientTurnSouth = 113, | |
| 141 | + | ClientTurnWest = 114, | |
| 142 | + | ClientEquipItem = 119, -- 910 | |
| 143 | + | ClientMove = 120, | |
| 144 | + | ClientInspectNpcTrade = 121, | |
| 145 | + | ClientBuyItem = 122, | |
| 146 | + | ClientSellItem = 123, | |
| 147 | + | ClientCloseNpcTrade = 124, | |
| 148 | + | ClientRequestTrade = 125, | |
| 149 | + | ClientInspectTrade = 126, | |
| 150 | + | ClientAcceptTrade = 127, | |
| 151 | + | ClientRejectTrade = 128, | |
| 152 | + | ClientUseItem = 130, | |
| 153 | + | ClientUseItemWith = 131, | |
| 154 | + | ClientUseOnCreature = 132, | |
| 155 | + | ClientRotateItem = 133, | |
| 156 | + | ClientCloseContainer = 135, | |
| 157 | + | ClientUpContainer = 136, | |
| 158 | + | ClientEditText = 137, | |
| 159 | + | ClientEditList = 138, | |
| 160 | + | ClientLook = 140, | |
| 161 | + | ClientTalk = 150, | |
| 162 | + | ClientRequestChannels = 151, | |
| 163 | + | ClientJoinChannel = 152, | |
| 164 | + | ClientLeaveChannel = 153, | |
| 165 | + | ClientOpenPrivateChannel = 154, | |
| 166 | + | ClientCloseNpcChannel = 158, | |
| 167 | + | ClientChangeFightModes = 160, | |
| 168 | + | ClientAttack = 161, | |
| 169 | + | ClientFollow = 162, | |
| 170 | + | ClientInviteToParty = 163, | |
| 171 | + | ClientJoinParty = 164, | |
| 172 | + | ClientRevokeInvitation = 165, | |
| 173 | + | ClientPassLeadership = 166, | |
| 174 | + | ClientLeaveParty = 167, | |
| 175 | + | ClientShareExperience = 168, | |
| 176 | + | ClientDisbandParty = 169, | |
| 177 | + | ClientOpenOwnChannel = 170, | |
| 178 | + | ClientInviteToOwnChannel = 171, | |
| 179 | + | ClientExcludeFromOwnChannel = 172, | |
| 180 | + | ClientCancelAttackAndFollow = 190, | |
| 181 | + | ClientRefreshContainer = 202, | |
| 182 | + | ClientRequestOutfit = 210, | |
| 183 | + | ClientChangeOutfit = 211, | |
| 184 | + | ClientMount = 212, -- 870 | |
| 185 | + | ClientAddVip = 220, | |
| 186 | + | ClientRemoveVip = 221, | |
| 187 | + | ClientBugReport = 230, | |
| 188 | + | ClientRuleViolation = 231, | |
| 189 | + | ClientDebugReport = 232, | |
| 190 | + | ClientTransferCoins = 239, -- 1080 | |
| 191 | + | ClientRequestQuestLog = 240, | |
| 192 | + | ClientRequestQuestLine = 241, | |
| 193 | + | ClientNewRuleViolation = 242, -- 910 | |
| 194 | + | ClientRequestItemInfo = 243, -- 910 | |
| 195 | + | ClientMarketLeave = 244, -- 944 | |
| 196 | + | ClientMarketBrowse = 245, -- 944 | |
| 197 | + | ClientMarketCreate = 246, -- 944 | |
| 198 | + | ClientMarketCancel = 247, -- 944 | |
| 199 | + | ClientMarketAccept = 248, -- 944 | |
| 200 | + | ClientAnswerModalDialog = 249, -- 960 | |
| 201 | + | ClientOpenStore = 250, -- 1080 | |
| 202 | + | ClientRequestStoreOffers = 251, -- 1080 | |
| 203 | + | ClientBuyStoreOffer = 252, -- 1080 | |
| 204 | + | ClientOpenTransactionHistory = 253, -- 1080 | |
| 205 | + | ClientRequestTransactionHistory = 254 -- 1080 | |
| 206 | + | } |
| @@ -0,0 +1,144 @@ | |||
| 1 | + | local opcodeCallbacks = {} | |
| 2 | + | local extendedCallbacks = {} | |
| 3 | + | local extendedJSONCallbacks = {} | |
| 4 | + | local extendedJSONData = {} | |
| 5 | + | local maxPacketSize = 65000 | |
| 6 | + | ||
| 7 | + | -- Utility function to check if an item id is valid | |
| 8 | + | local function isValidItemId(itemId) | |
| 9 | + | -- g_things is usually the global accessor for items | |
| 10 | + | return g_things and g_things:isValidItemId(itemId) | |
| 11 | + | end | |
| 12 | + | ||
| 13 | + | function ProtocolGame:onOpcode(opcode, msg) | |
| 14 | + | for i, callback in pairs(opcodeCallbacks) do | |
| 15 | + | if i == opcode then | |
| 16 | + | callback(self, msg) | |
| 17 | + | return true | |
| 18 | + | end | |
| 19 | + | end | |
| 20 | + | return false | |
| 21 | + | end | |
| 22 | + | ||
| 23 | + | function ProtocolGame:onExtendedOpcode(opcode, buffer) | |
| 24 | + | local callback = extendedCallbacks[opcode] | |
| 25 | + | if callback then | |
| 26 | + | callback(self, opcode, buffer) | |
| 27 | + | end | |
| 28 | + | ||
| 29 | + | callback = extendedJSONCallbacks[opcode] | |
| 30 | + | if callback then | |
| 31 | + | local status = buffer:sub(1,1) -- O - just one message, S - start, P - part, E - end | |
| 32 | + | local data = buffer:sub(2) | |
| 33 | + | if status ~= "E" and status ~= "P" then | |
| 34 | + | extendedJSONData[opcode] = "" | |
| 35 | + | end | |
| 36 | + | if status ~= "S" and status ~= "P" and status ~= "E" then | |
| 37 | + | extendedJSONData[opcode] = buffer | |
| 38 | + | else | |
| 39 | + | extendedJSONData[opcode] = extendedJSONData[opcode] .. data | |
| 40 | + | end | |
| 41 | + | if status ~= "S" and status ~= "P" then | |
| 42 | + | local json_status, json_data = pcall(function() return json.decode(extendedJSONData[opcode]) end) | |
| 43 | + | extendedJSONData[opcode] = nil | |
| 44 | + | if not json_status then | |
| 45 | + | error("Invalid data in extended JSON opcode (" .. json_status .. "): " .. json_data) | |
| 46 | + | return | |
| 47 | + | end | |
| 48 | + | callback(self, opcode, json_data) | |
| 49 | + | end | |
| 50 | + | end | |
| 51 | + | end | |
| 52 | + | ||
| 53 | + | function ProtocolGame.registerOpcode(opcode, callback) | |
| 54 | + | if opcodeCallbacks[opcode] then | |
| 55 | + | error('opcode ' .. opcode .. ' already registered will be overriden') | |
| 56 | + | end | |
| 57 | + | ||
| 58 | + | opcodeCallbacks[opcode] = callback | |
| 59 | + | end | |
| 60 | + | ||
| 61 | + | function ProtocolGame.unregisterOpcode(opcode) | |
| 62 | + | opcodeCallbacks[opcode] = nil | |
| 63 | + | end | |
| 64 | + | ||
| 65 | + | function ProtocolGame.registerExtendedOpcode(opcode, callback) | |
| 66 | + | if not callback or type(callback) ~= 'function' then | |
| 67 | + | error('Invalid callback.') | |
| 68 | + | end | |
| 69 | + | ||
| 70 | + | if opcode < 0 or opcode > 255 then | |
| 71 | + | error('Invalid opcode. Range: 0-255') | |
| 72 | + | end | |
| 73 | + | ||
| 74 | + | if extendedCallbacks[opcode] then | |
| 75 | + | error('Opcode is already taken.') | |
| 76 | + | end | |
| 77 | + | ||
| 78 | + | extendedCallbacks[opcode] = callback | |
| 79 | + | end | |
| 80 | + | ||
| 81 | + | function ProtocolGame.unregisterExtendedOpcode(opcode) | |
| 82 | + | if opcode < 0 or opcode > 255 then | |
| 83 | + | error('Invalid opcode. Range: 0-255') | |
| 84 | + | end | |
| 85 | + | ||
| 86 | + | if not extendedCallbacks[opcode] then | |
| 87 | + | error('Opcode is not registered.') | |
| 88 | + | end | |
| 89 | + | ||
| 90 | + | extendedCallbacks[opcode] = nil | |
| 91 | + | end | |
| 92 | + | ||
| 93 | + | function ProtocolGame.registerExtendedJSONOpcode(opcode, callback) | |
| 94 | + | if not callback or type(callback) ~= 'function' then | |
| 95 | + | error('Invalid callback.') | |
| 96 | + | end | |
| 97 | + | ||
| 98 | + | if opcode < 0 or opcode > 255 then | |
| 99 | + | error('Invalid opcode. Range: 0-255') | |
| 100 | + | end | |
| 101 | + | ||
| 102 | + | if extendedJSONCallbacks[opcode] then | |
| 103 | + | error('Opcode is already taken.') | |
| 104 | + | end | |
| 105 | + | ||
| 106 | + | extendedJSONCallbacks[opcode] = callback | |
| 107 | + | end | |
| 108 | + | ||
| 109 | + | function ProtocolGame.unregisterExtendedJSONOpcode(opcode) | |
| 110 | + | if opcode < 0 or opcode > 255 then | |
| 111 | + | error('Invalid opcode. Range: 0-255') | |
| 112 | + | end | |
| 113 | + | ||
| 114 | + | if not extendedJSONCallbacks[opcode] then | |
| 115 | + | error('Opcode is not registered.') | |
| 116 | + | end | |
| 117 | + | ||
| 118 | + | extendedJSONCallbacks[opcode] = nil | |
| 119 | + | end | |
| 120 | + | ||
| 121 | + | function ProtocolGame:sendExtendedJSONOpcode(opcode, data) | |
| 122 | + | if opcode < 0 or opcode > 255 then | |
| 123 | + | error('Invalid opcode. Range: 0-255') | |
| 124 | + | end | |
| 125 | + | ||
| 126 | + | if type(data) ~= "table" then | |
| 127 | + | error('Invalid data type, should be table') | |
| 128 | + | end | |
| 129 | + | ||
| 130 | + | local buffer = json.encode(data) | |
| 131 | + | local s = {} | |
| 132 | + | for i=1, #buffer, maxPacketSize do | |
| 133 | + | s[#s+1] = buffer:sub(i,i+maxPacketSize - 1) | |
| 134 | + | end | |
| 135 | + | if #s == 1 then | |
| 136 | + | self:sendExtendedOpcode(opcode, s[1]) | |
| 137 | + | return | |
| 138 | + | end | |
| 139 | + | self:sendExtendedOpcode(opcode, "S" .. s[1]) | |
| 140 | + | for i=2,#s - 1 do | |
| 141 | + | self:sendExtendedOpcode(opcode, "P" .. s[i]) | |
| 142 | + | end | |
| 143 | + | self:sendExtendedOpcode(opcode, "E" .. s[#s]) | |
| 144 | + | end |
| @@ -0,0 +1,321 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | ProtocolLogin = extends(Protocol, "ProtocolLogin") | |
| 3 | + | ||
| 4 | + | LoginServerError = 10 | |
| 5 | + | LoginServerTokenSuccess = 12 | |
| 6 | + | LoginServerTokenError = 13 | |
| 7 | + | LoginServerUpdate = 17 | |
| 8 | + | LoginServerMotd = 20 | |
| 9 | + | LoginServerUpdateNeeded = 30 | |
| 10 | + | LoginServerSessionKey = 40 | |
| 11 | + | LoginServerCharacterList = 100 | |
| 12 | + | LoginServerExtendedCharacterList = 101 | |
| 13 | + | LoginServerProxyList = 110 | |
| 14 | + | ||
| 15 | + | -- Since 10.76 | |
| 16 | + | LoginServerRetry = 10 | |
| 17 | + | LoginServerErrorNew = 11 | |
| 18 | + | ||
| 19 | + | function ProtocolLogin:login(host, port, accountName, accountPassword, authenticatorToken, stayLogged) | |
| 20 | + | if string.len(host) == 0 or port == nil or port == 0 then | |
| 21 | + | signalcall(self.onLoginError, self, tr("You must enter a valid server address and port.")) | |
| 22 | + | return | |
| 23 | + | end | |
| 24 | + | ||
| 25 | + | self.accountName = accountName | |
| 26 | + | self.accountPassword = accountPassword | |
| 27 | + | self.authenticatorToken = authenticatorToken | |
| 28 | + | self.stayLogged = stayLogged | |
| 29 | + | self.connectCallback = self.sendLoginPacket | |
| 30 | + | ||
| 31 | + | self:connect(host, port) | |
| 32 | + | end | |
| 33 | + | ||
| 34 | + | function ProtocolLogin:cancelLogin() | |
| 35 | + | self:disconnect() | |
| 36 | + | end | |
| 37 | + | ||
| 38 | + | function ProtocolLogin:sendLoginPacket() | |
| 39 | + | local msg = OutputMessage.create() | |
| 40 | + | msg:addU8(ClientOpcodes.ClientEnterAccount) | |
| 41 | + | msg:addU16(g_game.getOs()) | |
| 42 | + | if g_game.getCustomProtocolVersion() > 0 then | |
| 43 | + | msg:addU16(g_game.getCustomProtocolVersion()) | |
| 44 | + | else | |
| 45 | + | msg:addU16(g_game.getProtocolVersion()) | |
| 46 | + | end | |
| 47 | + | ||
| 48 | + | if g_game.getFeature(GameClientVersion) then | |
| 49 | + | msg:addU32(g_game.getClientVersion()) | |
| 50 | + | end | |
| 51 | + | ||
| 52 | + | if g_game.getFeature(GameContentRevision) then | |
| 53 | + | msg:addU16(g_things.getContentRevision()) | |
| 54 | + | msg:addU16(0) | |
| 55 | + | else | |
| 56 | + | msg:addU32(g_things.getDatSignature()) | |
| 57 | + | end | |
| 58 | + | msg:addU32(g_sprites.getSprSignature()) | |
| 59 | + | msg:addU32(PIC_SIGNATURE) | |
| 60 | + | ||
| 61 | + | if g_game.getFeature(GamePreviewState) then | |
| 62 | + | msg:addU8(0) | |
| 63 | + | end | |
| 64 | + | ||
| 65 | + | local offset = msg:getMessageSize() | |
| 66 | + | if g_game.getFeature(GameLoginPacketEncryption) then | |
| 67 | + | -- first RSA byte must be 0 | |
| 68 | + | msg:addU8(0) | |
| 69 | + | ||
| 70 | + | -- xtea key | |
| 71 | + | self:generateXteaKey() | |
| 72 | + | local xteaKey = self:getXteaKey() | |
| 73 | + | msg:addU32(xteaKey[1]) | |
| 74 | + | msg:addU32(xteaKey[2]) | |
| 75 | + | msg:addU32(xteaKey[3]) | |
| 76 | + | msg:addU32(xteaKey[4]) | |
| 77 | + | end | |
| 78 | + | ||
| 79 | + | if g_game.getFeature(GameAccountNames) then | |
| 80 | + | msg:addString(self.accountName) | |
| 81 | + | else | |
| 82 | + | msg:addU32(tonumber(self.accountName)) | |
| 83 | + | end | |
| 84 | + | ||
| 85 | + | msg:addString(self.accountPassword) | |
| 86 | + | ||
| 87 | + | if self.getLoginExtendedData then | |
| 88 | + | local data = self:getLoginExtendedData() | |
| 89 | + | msg:addString(data) | |
| 90 | + | else | |
| 91 | + | msg:addString("OTCv8") | |
| 92 | + | local version = g_app.getVersion():split(" ")[1]:gsub("%.", "") | |
| 93 | + | if version:len() == 2 then | |
| 94 | + | version = version .. "0" | |
| 95 | + | end | |
| 96 | + | msg:addU16(tonumber(version)) | |
| 97 | + | end | |
| 98 | + | ||
| 99 | + | local paddingBytes = g_crypt.rsaGetSize() - (msg:getMessageSize() - offset) | |
| 100 | + | assert(paddingBytes >= 0) | |
| 101 | + | for i = 1, paddingBytes do | |
| 102 | + | msg:addU8(math.random(0, 0xff)) | |
| 103 | + | end | |
| 104 | + | ||
| 105 | + | if g_game.getFeature(GameLoginPacketEncryption) then | |
| 106 | + | msg:encryptRsa() | |
| 107 | + | end | |
| 108 | + | ||
| 109 | + | if g_game.getFeature(GameOGLInformation) then | |
| 110 | + | msg:addU8(1) --unknown | |
| 111 | + | msg:addU8(1) --unknown | |
| 112 | + | ||
| 113 | + | if g_game.getClientVersion() >= 1072 then | |
| 114 | + | msg:addString(string.format('%s %s', g_graphics.getVendor(), g_graphics.getRenderer())) | |
| 115 | + | else | |
| 116 | + | msg:addString(g_graphics.getRenderer()) | |
| 117 | + | end | |
| 118 | + | msg:addString(g_graphics.getVersion()) | |
| 119 | + | end | |
| 120 | + | ||
| 121 | + | -- add RSA encrypted auth token | |
| 122 | + | if g_game.getFeature(GameAuthenticator) then | |
| 123 | + | offset = msg:getMessageSize() | |
| 124 | + | ||
| 125 | + | -- first RSA byte must be 0 | |
| 126 | + | msg:addU8(0) | |
| 127 | + | msg:addString(self.authenticatorToken) | |
| 128 | + | ||
| 129 | + | if g_game.getFeature(GameSessionKey) then | |
| 130 | + | msg:addU8(booleantonumber(self.stayLogged)) | |
| 131 | + | end | |
| 132 | + | ||
| 133 | + | paddingBytes = g_crypt.rsaGetSize() - (msg:getMessageSize() - offset) | |
| 134 | + | assert(paddingBytes >= 0) | |
| 135 | + | for i = 1, paddingBytes do | |
| 136 | + | msg:addU8(math.random(0, 0xff)) | |
| 137 | + | end | |
| 138 | + | ||
| 139 | + | msg:encryptRsa() | |
| 140 | + | end | |
| 141 | + | ||
| 142 | + | if g_game.getFeature(GamePacketSizeU32) then | |
| 143 | + | self:enableBigPackets() | |
| 144 | + | end | |
| 145 | + | ||
| 146 | + | if g_game.getFeature(GameProtocolChecksum) then | |
| 147 | + | self:enableChecksum() | |
| 148 | + | end | |
| 149 | + | ||
| 150 | + | self:send(msg) | |
| 151 | + | if g_game.getFeature(GameLoginPacketEncryption) then | |
| 152 | + | self:enableXteaEncryption() | |
| 153 | + | end | |
| 154 | + | self:recv() | |
| 155 | + | end | |
| 156 | + | ||
| 157 | + | function ProtocolLogin:onConnect() | |
| 158 | + | self.gotConnection = true | |
| 159 | + | self:connectCallback() | |
| 160 | + | self.connectCallback = nil | |
| 161 | + | end | |
| 162 | + | ||
| 163 | + | function ProtocolLogin:onRecv(msg) | |
| 164 | + | while not msg:eof() do | |
| 165 | + | local opcode = msg:getU8() | |
| 166 | + | if opcode == LoginServerErrorNew then | |
| 167 | + | self:parseError(msg) | |
| 168 | + | elseif opcode == LoginServerError then | |
| 169 | + | self:parseError(msg) | |
| 170 | + | elseif opcode == LoginServerMotd then | |
| 171 | + | self:parseMotd(msg) | |
| 172 | + | elseif opcode == LoginServerUpdateNeeded then | |
| 173 | + | signalcall(self.onLoginError, self, tr("Client needs update.")) | |
| 174 | + | elseif opcode == LoginServerTokenSuccess then | |
| 175 | + | local unknown = msg:getU8() | |
| 176 | + | elseif opcode == LoginServerTokenError then | |
| 177 | + | -- TODO: prompt for token here | |
| 178 | + | local unknown = msg:getU8() | |
| 179 | + | signalcall(self.onLoginError, self, tr("Invalid authentification token.")) | |
| 180 | + | elseif opcode == LoginServerCharacterList then | |
| 181 | + | self:parseCharacterList(msg) | |
| 182 | + | elseif opcode == LoginServerExtendedCharacterList then | |
| 183 | + | self:parseExtendedCharacterList(msg) | |
| 184 | + | elseif opcode == LoginServerUpdate then | |
| 185 | + | local signature = msg:getString() | |
| 186 | + | signalcall(self.onUpdateNeeded, self, signature) | |
| 187 | + | elseif opcode == LoginServerSessionKey then | |
| 188 | + | self:parseSessionKey(msg) | |
| 189 | + | elseif opcode == LoginServerProxyList then | |
| 190 | + | local proxies = {} | |
| 191 | + | local proxiesCount = msg:getU8() | |
| 192 | + | for i=1, proxiesCount do | |
| 193 | + | local host = msg:getString() | |
| 194 | + | local port = msg:getU16() | |
| 195 | + | local priority = msg:getU16() | |
| 196 | + | table.insert(proxies, {host=host, port=port, priority=priority}) | |
| 197 | + | end | |
| 198 | + | signalcall(self.onProxyList, self, proxies) | |
| 199 | + | else | |
| 200 | + | self:parseOpcode(opcode, msg) | |
| 201 | + | end | |
| 202 | + | end | |
| 203 | + | self:disconnect() | |
| 204 | + | end | |
| 205 | + | ||
| 206 | + | function ProtocolLogin:parseError(msg) | |
| 207 | + | local errorMessage = msg:getString() | |
| 208 | + | signalcall(self.onLoginError, self, errorMessage) | |
| 209 | + | end | |
| 210 | + | ||
| 211 | + | function ProtocolLogin:parseMotd(msg) | |
| 212 | + | local motd = msg:getString() | |
| 213 | + | signalcall(self.onMotd, self, motd) | |
| 214 | + | end | |
| 215 | + | ||
| 216 | + | function ProtocolLogin:parseSessionKey(msg) | |
| 217 | + | local sessionKey = msg:getString() | |
| 218 | + | signalcall(self.onSessionKey, self, sessionKey) | |
| 219 | + | end | |
| 220 | + | ||
| 221 | + | function ProtocolLogin:parseCharacterList(msg) | |
| 222 | + | local characters = {} | |
| 223 | + | ||
| 224 | + | if g_game.getClientVersion() > 1010 then | |
| 225 | + | local worlds = {} | |
| 226 | + | ||
| 227 | + | local worldsCount = msg:getU8() | |
| 228 | + | for i=1, worldsCount do | |
| 229 | + | local world = {} | |
| 230 | + | local worldId = msg:getU8() | |
| 231 | + | world.worldName = msg:getString() | |
| 232 | + | world.worldIp = msg:getString() | |
| 233 | + | world.worldPort = msg:getU16() | |
| 234 | + | world.previewState = msg:getU8() | |
| 235 | + | worlds[worldId] = world | |
| 236 | + | end | |
| 237 | + | ||
| 238 | + | local charactersCount = msg:getU8() | |
| 239 | + | for i = 1, charactersCount do | |
| 240 | + | local character = {} | |
| 241 | + | local worldId = msg:getU8() | |
| 242 | + | character.name = msg:getString() | |
| 243 | + | character.level = msg:getU32() | |
| 244 | + | character.vocation = msg:getU16() | |
| 245 | + | character.looktype = msg:getU16() | |
| 246 | + | character.lookhead = msg:getU16() | |
| 247 | + | character.lookbody = msg:getU16() | |
| 248 | + | character.looklegs = msg:getU16() | |
| 249 | + | character.lookfeet = msg:getU16() | |
| 250 | + | character.lookaddons = msg:getU16() | |
| 251 | + | character.lookwings = msg:getU16() | |
| 252 | + | character.lookaura = msg:getU16() | |
| 253 | + | character.lookshader = msg:getString() | |
| 254 | + | character.worldName = worlds[worldId].worldName | |
| 255 | + | character.worldIp = worlds[worldId].worldIp | |
| 256 | + | character.worldPort = worlds[worldId].worldPort | |
| 257 | + | character.previewState = worlds[worldId].previewState | |
| 258 | + | characters[i] = character | |
| 259 | + | end | |
| 260 | + | ||
| 261 | + | else | |
| 262 | + | local charactersCount = msg:getU8() | |
| 263 | + | for i=1,charactersCount do | |
| 264 | + | local character = {} | |
| 265 | + | character.name = msg:getString() | |
| 266 | + | character.level = msg:getU32() | |
| 267 | + | character.vocation = msg:getU16() | |
| 268 | + | character.looktype = msg:getU16() | |
| 269 | + | character.lookhead = msg:getU16() | |
| 270 | + | character.lookbody = msg:getU16() | |
| 271 | + | character.looklegs = msg:getU16() | |
| 272 | + | character.lookfeet = msg:getU16() | |
| 273 | + | character.lookaddons = msg:getU16() | |
| 274 | + | character.lookwings = msg:getU16() | |
| 275 | + | character.lookaura = msg:getU16() | |
| 276 | + | character.lookshader = msg:getString() | |
| 277 | + | character.worldName = msg:getString() | |
| 278 | + | character.worldIp = iptostring(msg:getU32()) | |
| 279 | + | character.worldPort = msg:getU16() | |
| 280 | + | ||
| 281 | + | if g_game.getFeature(GamePreviewState) then | |
| 282 | + | character.previewState = msg:getU8() | |
| 283 | + | end | |
| 284 | + | ||
| 285 | + | characters[i] = character | |
| 286 | + | end | |
| 287 | + | end | |
| 288 | + | ||
| 289 | + | local account = {} | |
| 290 | + | if g_game.getProtocolVersion() > 1077 then | |
| 291 | + | account.status = msg:getU8() | |
| 292 | + | account.subStatus = msg:getU8() | |
| 293 | + | ||
| 294 | + | account.premDays = msg:getU32() | |
| 295 | + | if account.premDays ~= 0 and account.premDays ~= 65535 then | |
| 296 | + | account.premDays = math.floor((account.premDays - os.time()) / 86400) | |
| 297 | + | end | |
| 298 | + | else | |
| 299 | + | account.status = AccountStatus.Ok | |
| 300 | + | account.premDays = msg:getU16() | |
| 301 | + | account.subStatus = account.premDays > 0 and SubscriptionStatus.Premium or SubscriptionStatus.Free | |
| 302 | + | end | |
| 303 | + | ||
| 304 | + | signalcall(self.onCharacterList, self, characters, account) | |
| 305 | + | end | |
| 306 | + | ||
| 307 | + | function ProtocolLogin:parseExtendedCharacterList(msg) | |
| 308 | + | local characters = msg:getTable() | |
| 309 | + | local account = msg:getTable() | |
| 310 | + | local otui = msg:getString() | |
| 311 | + | signalcall(self.onCharacterList, self, characters, account, otui) | |
| 312 | + | end | |
| 313 | + | ||
| 314 | + | function ProtocolLogin:parseOpcode(opcode, msg) | |
| 315 | + | signalcall(self.onOpcode, self, opcode, msg) | |
| 316 | + | end | |
| 317 | + | ||
| 318 | + | function ProtocolLogin:onError(msg, code) | |
| 319 | + | local text = translateNetworkError(code, self:isConnecting(), msg) | |
| 320 | + | signalcall(self.onLoginError, self, text) | |
| 321 | + | end |
| @@ -0,0 +1,441 @@ | |||
| 1 | + | SpelllistSettings = { | |
| 2 | + | ['Default'] = { | |
| 3 | + | iconFile = '/images/game/spells/defaultspells', | |
| 4 | + | iconSize = {width = 32, height = 32}, | |
| 5 | + | spellListWidth = 210, | |
| 6 | + | spellWindowWidth = 550, | |
| 7 | + | spellOrder = {'Animate Dead', 'Annihilation', 'Avalanche', 'Berserk', 'Blood Rage', 'Brutal Strike', 'Cancel Invisibility', 'Challenge', 'Chameleon', 'Charge', 'Conjure Arrow', 'Conjure Bolt', 'Conjure Explosive Arrow', 'Conjure Piercing Bolt', 'Conjure Poisoned Arrow', 'Conjure Power Bolt', 'Conjure Sniper Arrow', 'Convince Creature', 'Creature Illusion', 'Cure Bleeding', 'Cure Burning', 'Cure Curse', 'Cure Electrification', 'Cure Poison', 'Cure Poison Rune', 'Curse', 'Death Strike', 'Desintegrate', 'Destroy Field', 'Divine Caldera', 'Divine Healing', 'Divine Missile', 'Electrify', 'Enchant Party', 'Enchant Spear', 'Enchant Staff', 'Energy Beam', 'Energy Field', 'Energy Strike', 'Energy Wall', 'Energy Wave', 'Energybomb', 'Envenom', 'Eternal Winter', 'Ethereal Spear', 'Explosion', 'Fierce Berserk', 'Find Person', 'Fire Field', 'Fire Wall', 'Fire Wave', 'Fireball', 'Firebomb', 'Flame Strike', 'Food', 'Front Sweep', 'Great Energy Beam', 'Great Fireball', 'Great Light', 'Groundshaker', 'Haste', 'Heal Friend', 'Heal Party', 'Heavy Magic Missile', 'Hells Core', 'Holy Flash', 'Holy Missile', 'Ice Strike', 'Ice Wave', 'Icicle', 'Ignite', 'Inflict Wound', 'Intense Healing', 'Intense Healing Rune', 'Intense Recovery', 'Intense Wound Cleansing', 'Invisibility', 'Levitate', 'Light', 'Light Healing', 'Light Magic Missile', 'Lightning', 'Magic Rope', 'Magic Shield', 'Magic Wall', 'Mass Healing', 'Paralyze', 'Physical Strike', 'Poison Bomb', 'Poison Field', 'Poison Wall', 'Protect Party', 'Protector', 'Rage of the Skies', 'Recovery', 'Salvation', 'Sharpshooter', 'Soulfire', 'Stalagmite', 'Stone Shower', 'Strong Energy Strike', 'Strong Ethereal Spear', 'Strong Flame Strike', 'Strong Haste', 'Strong Ice Strike', 'Strong Ice Wave', 'Strong Terra Strike', 'Sudden Death', 'Summon Creature', 'Swift Foot', 'Terra Strike', 'Terra Wave', 'Thunderstorm', 'Train Party', 'Ultimate Energy Strike', 'Ultimate Flame Strike', 'Ultimate Healing', 'Ultimate Healing Rune', 'Ultimate Ice Strike', 'Ultimate Light', 'Ultimate Terra Strike', 'Whirlwind Throw', 'Wild Growth', 'Wound Cleansing', 'Wrath of Nature'} | |
| 8 | + | }--[[, | |
| 9 | + | ||
| 10 | + | ['Sample'] = { | |
| 11 | + | iconFile = '/images/game/spells/sample', | |
| 12 | + | iconSize = {width = 64, height = 64}, | |
| 13 | + | spellOrder = {'Critical Strike', 'Firefly', 'Fire Breath', 'Moonglaives', 'Wind Walk'} | |
| 14 | + | }]] | |
| 15 | + | } | |
| 16 | + | ||
| 17 | + | SpellInfo = { | |
| 18 | + | ['Default'] = { | |
| 19 | + | ['Death Strike'] = {id = 87, words = 'exori mort', exhaustion = 2000, premium = true, type = 'Instant', icon = 'deathstrike', mana = 20, level = 16, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, | |
| 20 | + | ['Flame Strike'] = {id = 89, words = 'exori flam', exhaustion = 2000, premium = true, type = 'Instant', icon = 'flamestrike', mana = 20, level = 14, soul = 0, group = {[1] = 2000}, vocations = {1, 2, 5, 6}}, | |
| 21 | + | ['Strong Flame Strike'] = {id = 150, words = 'exori gran flam', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongflamestrike', mana = 60, level = 70, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {1, 5}}, | |
| 22 | + | ['Ultimate Flame Strike'] = {id = 154, words = 'exori max flam', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ultimateflamestrike', mana = 100, level = 90, soul = 0, group = {[1] = 4000}, vocations = {1, 5}}, | |
| 23 | + | ['Energy Strike'] = {id = 88, words = 'exori vis', exhaustion = 2000, premium = true, type = 'Instant', icon = 'energystrike', mana = 20, level = 12, soul = 0, group = {[1] = 2000}, vocations = {1, 2, 5, 6}}, | |
| 24 | + | ['Strong Energy Strike'] = {id = 151, words = 'exori gran vis', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongenergystrike', mana = 60, level = 80, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {1, 5}}, | |
| 25 | + | ['Ultimate Energy Strike'] = {id = 155, words = 'exori max vis', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ultimateenergystrike', mana = 100, level = 100,soul = 0, group = {[1] = 4000}, vocations = {1, 5}}, | |
| 26 | + | ['Whirlwind Throw'] = {id = 107, words = 'exori hur', exhaustion = 6000, premium = true, type = 'Instant', icon = 'whirlwindthrow', mana = 40, level = 28, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, | |
| 27 | + | ['Fire Wave'] = {id = 19, words = 'exevo flam hur', exhaustion = 4000, premium = false, type = 'Instant', icon = 'firewave', mana = 25, level = 18, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, | |
| 28 | + | ['Ethereal Spear'] = {id = 111, words = 'exori con', exhaustion = 2000, premium = true, type = 'Instant', icon = 'etherealspear', mana = 25, level = 23, soul = 0, group = {[1] = 2000}, vocations = {3, 7}}, | |
| 29 | + | ['Strong Ethereal Spear'] = {id = 57, words = 'exori gran con', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongetherealspear', mana = 55, level = 90, soul = 0, group = {[1] = 2000}, vocations = {3, 7}}, | |
| 30 | + | ['Energy Beam'] = {id = 22, words = 'exevo vis lux', exhaustion = 4000, premium = false, type = 'Instant', icon = 'energybeam', mana = 40, level = 23, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, | |
| 31 | + | ['Great Energy Beam'] = {id = 23, words = 'exevo gran vis lux', exhaustion = 6000, premium = false, type = 'Instant', icon = 'greatenergybeam', mana = 110, level = 29, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, | |
| 32 | + | ['Groundshaker'] = {id = 106, words = 'exori mas', exhaustion = 8000, premium = true, type = 'Instant', icon = 'groundshaker', mana = 160, level = 33, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, | |
| 33 | + | ['Berserk'] = {id = 80, words = 'exori', exhaustion = 4000, premium = true, type = 'Instant', icon = 'berserk', mana = 115, level = 35, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, | |
| 34 | + | ['Annihilation'] = {id = 62, words = 'exori gran ico', exhaustion = 30000, premium = true, type = 'Instant', icon = 'annihilation', mana = 300, level = 110,soul = 0, group = {[1] = 4000}, vocations = {4, 8}}, | |
| 35 | + | ['Brutal Strike'] = {id = 61, words = 'exori ico', exhaustion = 6000, premium = true, type = 'Instant', icon = 'brutalstrike', mana = 30, level = 16, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, | |
| 36 | + | ['Front Sweep'] = {id = 59, words = 'exori min', exhaustion = 6000, premium = true, type = 'Instant', icon = 'frontsweep', mana = 200, level = 70, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, | |
| 37 | + | ['Inflict Wound'] = {id = 141, words = 'utori kor', exhaustion = 30000, premium = true, type = 'Instant', icon = 'inflictwound', mana = 30, level = 40, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, | |
| 38 | + | ['Ignite'] = {id = 138, words = 'utori flam', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ignite', mana = 30, level = 26, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, | |
| 39 | + | ['Lightning'] = {id = 149, words = 'exori amp vis', exhaustion = 8000, premium = true, type = 'Instant', icon = 'lightning', mana = 60, level = 55, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {1, 5}}, | |
| 40 | + | ['Curse'] = {id = 139, words = 'utori mort', exhaustion = 50000, premium = true, type = 'Instant', icon = 'curse', mana = 30, level = 75, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, | |
| 41 | + | ['Electrify'] = {id = 140, words = 'utori vis', exhaustion = 30000, premium = true, type = 'Instant', icon = 'electrify', mana = 30, level = 34, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, | |
| 42 | + | ['Energy Wave'] = {id = 13, words = 'exevo vis hur', exhaustion = 8000, premium = false, type = 'Instant', icon = 'energywave', mana = 170, level = 38, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, | |
| 43 | + | ['Rage of the Skies'] = {id = 119, words = 'exevo gran mas vis', exhaustion = 40000, premium = true, type = 'Instant', icon = 'rageoftheskies', mana = 600, level = 55, soul = 0, group = {[1] = 4000}, vocations = {1, 5}}, | |
| 44 | + | ['Fierce Berserk'] = {id = 105, words = 'exori gran', exhaustion = 6000, premium = true, type = 'Instant', icon = 'fierceberserk', mana = 340, level = 90, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, | |
| 45 | + | ['Hells Core'] = {id = 24, words = 'exevo gran mas flam', exhaustion = 40000, premium = true, type = 'Instant', icon = 'hellscore', mana = 1100, level = 60, soul = 0, group = {[1] = 4000}, vocations = {1, 5}}, | |
| 46 | + | ['Holy Flash'] = {id = 143, words = 'utori san', exhaustion = 40000, premium = true, type = 'Instant', icon = 'holyflash', mana = 30, level = 70, soul = 0, group = {[1] = 2000}, vocations = {3, 7}}, | |
| 47 | + | ['Divine Missile'] = {id = 122, words = 'exori san', exhaustion = 2000, premium = true, type = 'Instant', icon = 'divinemissile', mana = 20, level = 40, soul = 0, group = {[1] = 2000}, vocations = {3, 7}}, | |
| 48 | + | ['Divine Caldera'] = {id = 124, words = 'exevo mas san', exhaustion = 4000, premium = true, type = 'Instant', icon = 'divinecaldera', mana = 160, level = 50, soul = 0, group = {[1] = 2000}, vocations = {3, 7}}, | |
| 49 | + | ['Physical Strike'] = {id = 148, words = 'exori moe ico', exhaustion = 2000, premium = true, type = 'Instant', icon = 'physicalstrike', mana = 20, level = 16, soul = 0, group = {[1] = 2000}, vocations = {2, 6}}, | |
| 50 | + | ['Eternal Winter'] = {id = 118, words = 'exevo gran mas frigo', exhaustion = 40000, premium = true, type = 'Instant', icon = 'eternalwinter', mana = 1050, level = 60, soul = 0, group = {[1] = 4000}, vocations = {2, 6}}, | |
| 51 | + | ['Ice Strike'] = {id = 112, words = 'exori frigo', exhaustion = 2000, premium = true, type = 'Instant', icon = 'icestrike', mana = 20, level = 15, soul = 0, group = {[1] = 2000}, vocations = {1, 5, 2, 6}}, | |
| 52 | + | ['Strong Ice Strike'] = {id = 152, words = 'exori gran frigo', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongicestrike', mana = 60, level = 80, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {2, 6}}, | |
| 53 | + | ['Ultimate Ice Strike'] = {id = 156, words = 'exori max frigo', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ultimateicestrike', mana = 100, level = 100,soul = 0, group = {[1] = 4000}, vocations = {2, 6}}, | |
| 54 | + | ['Ice Wave'] = {id = 121, words = 'exevo frigo hur', exhaustion = 4000, premium = false, type = 'Instant', icon = 'icewave', mana = 25, level = 18, soul = 0, group = {[1] = 2000}, vocations = {2, 6}}, | |
| 55 | + | ['Strong Ice Wave'] = {id = 43, words = 'exevo gran frigo hur', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongicewave', mana = 170, level = 40, soul = 0, group = {[1] = 2000}, vocations = {2, 6}}, | |
| 56 | + | ['Envenom'] = {id = 142, words = 'utori pox', exhaustion = 40000, premium = true, type = 'Instant', icon = 'envenom', mana = 30, level = 50, soul = 0, group = {[1] = 2000}, vocations = {2, 6}}, | |
| 57 | + | ['Terra Strike'] = {id = 113, words = 'exori tera', exhaustion = 2000, premium = true, type = 'Instant', icon = 'terrastrike', mana = 20, level = 13, soul = 0, group = {[1] = 2000}, vocations = {1, 5, 2, 6}}, | |
| 58 | + | ['Strong Terra Strike'] = {id = 153, words = 'exori gran tera', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongterrastrike', mana = 60, level = 70, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {2, 6}}, | |
| 59 | + | ['Ultimate Terra Strike'] = {id = 157, words = 'exori max tera', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ultimateterrastrike', mana = 100, level = 90, soul = 0, group = {[1] = 4000}, vocations = {2, 6}}, | |
| 60 | + | ['Terra Wave'] = {id = 120, words = 'exevo tera hur', exhaustion = 4000, premium = false, type = 'Instant', icon = 'terrawave', mana = 210, level = 38, soul = 0, group = {[1] = 2000}, vocations = {2, 6}}, | |
| 61 | + | ['Wrath of Nature'] = {id = 56, words = 'exevo gran mas tera', exhaustion = 40000, premium = true, type = 'Instant', icon = 'wrathofnature', mana = 700, level = 55, soul = 0, group = {[1] = 4000}, vocations = {2, 6}}, | |
| 62 | + | ['Light Healing'] = {id = 1, words = 'exura', exhaustion = 1000, premium = false, type = 'Instant', icon = 'lighthealing', mana = 20, level = 9, soul = 0, group = {[2] = 1000}, vocations = {1, 2, 3, 5, 6, 7}}, | |
| 63 | + | ['Wound Cleansing'] = {id = 123, words = 'exura ico', exhaustion = 1000, premium = false, type = 'Instant', icon = 'woundcleansing', mana = 40, level = 10, soul = 0, group = {[2] = 1000}, vocations = {4, 8}}, | |
| 64 | + | ['Intense Wound Cleansing'] = {id = 158, words = 'exura gran ico', exhaustion = 600000,premium = true, type = 'Instant', icon = 'intensewoundcleansing', mana = 200, level = 80, soul = 0, group = {[2] = 1000}, vocations = {4, 8}}, | |
| 65 | + | ['Cure Bleeding'] = {id = 144, words = 'exana kor', exhaustion = 6000, premium = true, type = 'Instant', icon = 'curebleeding', mana = 30, level = 30, soul = 0, group = {[2] = 1000}, vocations = {4, 8}}, | |
| 66 | + | ['Cure Electrification'] = {id = 146, words = 'exana vis', exhaustion = 6000, premium = true, type = 'Instant', icon = 'curseelectrification', mana = 30, level = 22, soul = 0, group = {[2] = 1000}, vocations = {2, 6}}, | |
| 67 | + | ['Cure Poison'] = {id = 29, words = 'exana pox', exhaustion = 6000, premium = false, type = 'Instant', icon = 'curepoison', mana = 30, level = 10, soul = 0, group = {[2] = 1000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}}, | |
| 68 | + | ['Cure Burning'] = {id = 145, words = 'exana flam', exhaustion = 6000, premium = true, type = 'Instant', icon = 'cureburning', mana = 30, level = 30, soul = 0, group = {[2] = 1000}, vocations = {2, 6}}, | |
| 69 | + | ['Cure Curse'] = {id = 147, words = 'exana mort', exhaustion = 6000, premium = true, type = 'Instant', icon = 'curecurse', mana = 40, level = 80, soul = 0, group = {[2] = 1000}, vocations = {3, 7}}, | |
| 70 | + | ['Recovery'] = {id = 159, words = 'utura', exhaustion = 60000, premium = true, type = 'Instant', icon = 'recovery', mana = 75, level = 50, soul = 0, group = {[2] = 1000}, vocations = {4, 8, 3, 7}}, | |
| 71 | + | ['Intense Recovery'] = {id = 160, words = 'utura gran', exhaustion = 60000, premium = true, type = 'Instant', icon = 'intenserecovery', mana = 165, level = 100,soul = 0, group = {[2] = 1000}, vocations = {4, 8, 3, 7}}, | |
| 72 | + | ['Salvation'] = {id = 36, words = 'exura gran san', exhaustion = 1000, premium = true, type = 'Instant', icon = 'salvation', mana = 210, level = 60, soul = 0, group = {[2] = 1000}, vocations = {3, 7}}, | |
| 73 | + | ['Intense Healing'] = {id = 2, words = 'exura gran', exhaustion = 1000, premium = false, type = 'Instant', icon = 'intensehealing', mana = 70, level = 20, soul = 0, group = {[2] = 1000}, vocations = {1, 2, 3, 5, 6, 7}}, | |
| 74 | + | ['Heal Friend'] = {id = 84, words = 'exura sio', exhaustion = 1000, premium = true, type = 'Instant', icon = 'healfriend', mana = 140, level = 18, soul = 0, group = {[2] = 1000}, vocations = {2, 6}}, | |
| 75 | + | ['Ultimate Healing'] = {id = 3, words = 'exura vita', exhaustion = 1000, premium = false, type = 'Instant', icon = 'ultimatehealing', mana = 160, level = 30, soul = 0, group = {[2] = 1000}, vocations = {1, 2, 5, 6}}, | |
| 76 | + | ['Mass Healing'] = {id = 82, words = 'exura gran mas res', exhaustion = 2000, premium = true, type = 'Instant', icon = 'masshealing', mana = 150, level = 36, soul = 0, group = {[2] = 1000}, vocations = {2, 6}}, | |
| 77 | + | ['Divine Healing'] = {id = 125, words = 'exura san', exhaustion = 1000, premium = false, type = 'Instant', icon = 'divinehealing', mana = 160, level = 35, soul = 0, group = {[2] = 1000}, vocations = {3, 7}}, | |
| 78 | + | ['Light'] = {id = 10, words = 'utevo lux', exhaustion = 2000, premium = false, type = 'Instant', icon = 'light', mana = 20, level = 8, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}}, | |
| 79 | + | ['Find Person'] = {id = 20, words = 'exiva', exhaustion = 2000, premium = false, type = 'Instant', icon = 'findperson', mana = 20, level = 8, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}}, | |
| 80 | + | ['Magic Rope'] = {id = 76, words = 'exani tera', exhaustion = 2000, premium = true, type = 'Instant', icon = 'magicrope', mana = 20, level = 9, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}}, | |
| 81 | + | ['Levitate'] = {id = 81, words = 'exani hur', exhaustion = 2000, premium = true, type = 'Instant', icon = 'levitate', mana = 50, level = 12, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}}, | |
| 82 | + | ['Great Light'] = {id = 11, words = 'utevo gran lux', exhaustion = 2000, premium = false, type = 'Instant', icon = 'greatlight', mana = 60, level = 13, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}}, | |
| 83 | + | ['Magic Shield'] = {id = 44, words = 'utamo vita', exhaustion = 2000, premium = false, type = 'Instant', icon = 'magicshield', mana = 50, level = 14, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, | |
| 84 | + | ['Haste'] = {id = 6, words = 'utani hur', exhaustion = 2000, premium = true, type = 'Instant', icon = 'haste', mana = 60, level = 14, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}}, | |
| 85 | + | ['Charge'] = {id = 131, words = 'utani tempo hur', exhaustion = 2000, premium = true, type = 'Instant', icon = 'charge', mana = 100, level = 25, soul = 0, group = {[3] = 2000}, vocations = {4, 8}}, | |
| 86 | + | ['Swift Foot'] = {id = 134, words = 'utamo tempo san', exhaustion = 2000, premium = true, type = 'Instant', icon = 'swiftfoot', mana = 400, level = 55, soul = 0, group = {[1] = 10000, [3] = 2000}, vocations = {3, 7}}, | |
| 87 | + | ['Challenge'] = {id = 93, words = 'exeta res', exhaustion = 2000, premium = true, type = 'Instant', icon = 'challenge', mana = 30, level = 20, soul = 0, group = {[3] = 2000}, vocations = {8}}, | |
| 88 | + | ['Strong Haste'] = {id = 39, words = 'utani gran hur', exhaustion = 2000, premium = true, type = 'Instant', icon = 'stronghaste', mana = 100, level = 20, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, | |
| 89 | + | ['Creature Illusion'] = {id = 38, words = 'utevo res ina', exhaustion = 2000, premium = false, type = 'Instant', icon = 'creatureillusion', mana = 100, level = 23, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, | |
| 90 | + | ['Ultimate Light'] = {id = 75, words = 'utevo vis lux', exhaustion = 2000, premium = true, type = 'Instant', icon = 'ultimatelight', mana = 140, level = 26, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, | |
| 91 | + | ['Cancel Invisibility'] = {id = 90, words = 'exana ina', exhaustion = 2000, premium = true, type = 'Instant', icon = 'cancelinvisibility', mana = 200, level = 26, soul = 0, group = {[3] = 2000}, vocations = {3, 7}}, | |
| 92 | + | ['Invisibility'] = {id = 45, words = 'utana vid', exhaustion = 2000, premium = false, type = 'Instant', icon = 'invisible', mana = 440, level = 35, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, | |
| 93 | + | ['Sharpshooter'] = {id = 135, words = 'utito tempo san', exhaustion = 2000, premium = true, type = 'Instant', icon = 'sharpshooter', mana = 450, level = 60, soul = 0, group = {[2] = 10000, [3] = 10000}, vocations = {3, 7}}, | |
| 94 | + | ['Protector'] = {id = 132, words = 'utamo tempo', exhaustion = 2000, premium = true, type = 'Instant', icon = 'protector', mana = 200, level = 55, soul = 0, group = {[1] = 10000, [3] = 2000}, vocations = {4, 8}}, | |
| 95 | + | ['Blood Rage'] = {id = 133, words = 'utito tempo', exhaustion = 2000, premium = true, type = 'Instant', icon = 'bloodrage', mana = 290, level = 60, soul = 0, group = {[3] = 2000}, vocations = {4, 8}}, | |
| 96 | + | ['Train Party'] = {id = 126, words = 'utito mas sio', exhaustion = 2000, premium = true, type = 'Instant', icon = 'trainparty', mana = 'Var.', level = 32, soul = 0, group = {[3] = 2000}, vocations = {8}}, | |
| 97 | + | ['Protect Party'] = {id = 127, words = 'utamo mas sio', exhaustion = 2000, premium = true, type = 'Instant', icon = 'protectparty', mana = 'Var.', level = 32, soul = 0, group = {[3] = 2000}, vocations = {7}}, | |
| 98 | + | ['Heal Party'] = {id = 128, words = 'utura mas sio', exhaustion = 2000, premium = true, type = 'Instant', icon = 'healparty', mana = 'Var.', level = 32, soul = 0, group = {[3] = 2000}, vocations = {6}}, | |
| 99 | + | ['Enchant Party'] = {id = 129, words = 'utori mas sio', exhaustion = 2000, premium = true, type = 'Instant', icon = 'enchantparty', mana = 'Var.', level = 32, soul = 0, group = {[3] = 2000}, vocations = {5}}, | |
| 100 | + | ['Summon Creature'] = {id = 9, words = 'utevo res', exhaustion = 2000, premium = false, type = 'Instant', icon = 'summoncreature', mana = 'Var.', level = 25, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, | |
| 101 | + | ['Conjure Arrow'] = {id = 51, words = 'exevo con', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'conjurearrow', mana = 100, level = 13, soul = 1, group = {[3] = 2000}, vocations = {3, 7}}, | |
| 102 | + | ['Food'] = {id = 42, words = 'exevo pan', exhaustion = 2000, premium = false, type = 'Instant', icon = 'food', mana = 120, level = 14, soul = 1, group = {[3] = 2000}, vocations = {2, 6}}, | |
| 103 | + | ['Conjure Poisoned Arrow'] = {id = 48, words = 'exevo con pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'poisonedarrow', mana = 130, level = 16, soul = 2, group = {[3] = 2000}, vocations = {3, 7}}, | |
| 104 | + | ['Conjure Bolt'] = {id = 79, words = 'exevo con mort', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'conjurebolt', mana = 140, level = 17, soul = 2, group = {[3] = 2000}, vocations = {3, 7}}, | |
| 105 | + | ['Conjure Sniper Arrow'] = {id = 108, words = 'exevo con hur', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'sniperarrow', mana = 160, level = 24, soul = 3, group = {[3] = 2000}, vocations = {3, 7}}, | |
| 106 | + | ['Conjure Explosive Arrow'] = {id = 49, words = 'exevo con flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'explosivearrow', mana = 290, level = 25, soul = 3, group = {[3] = 2000}, vocations = {3, 7}}, | |
| 107 | + | ['Conjure Piercing Bolt'] = {id = 109, words = 'exevo con grav', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'piercingbolt', mana = 180, level = 33, soul = 3, group = {[3] = 2000}, vocations = {3, 7}}, | |
| 108 | + | ['Enchant Staff'] = {id = 92, words = 'exeta vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'enchantstaff', mana = 80, level = 41, soul = 0, group = {[3] = 2000}, vocations = {5}}, | |
| 109 | + | ['Enchant Spear'] = {id = 110, words = 'exeta con', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'enchantspear', mana = 350, level = 45, soul = 3, group = {[3] = 2000}, vocations = {3, 7}}, | |
| 110 | + | ['Conjure Power Bolt'] = {id = 95, words = 'exevo con vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'powerbolt', mana = 800, level = 59, soul = 3, group = {[3] = 2000}, vocations = {7}}, | |
| 111 | + | ['Poison Field'] = {id = 26, words = 'adevo grav pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'poisonfield', mana = 200, level = 14, soul = 1, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, | |
| 112 | + | ['Light Magic Missile'] = {id = 7, words = 'adori min vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'lightmagicmissile', mana = 120, level = 15, soul = 1, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, | |
| 113 | + | ['Fire Field'] = {id = 25, words = 'adevo grav flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'firefield', mana = 240, level = 15, soul = 1, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, | |
| 114 | + | ['Fireball'] = {id = 15, words = 'adori flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'fireball', mana = 460, level = 27, soul = 3, group = {[3] = 2000}, vocations = {1, 5}}, | |
| 115 | + | ['Energy Field'] = {id = 27, words = 'adevo grav vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'energyfield', mana = 320, level = 18, soul = 2, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, | |
| 116 | + | ['Stalagmite'] = {id = 77, words = 'adori tera', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'stalagmite', mana = 400, level = 24, soul = 2, group = {[3] = 2000}, vocations = {1, 5, 2, 6}}, | |
| 117 | + | ['Great Fireball'] = {id = 16, words = 'adori mas flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'greatfireball', mana = 530, level = 30, soul = 3, group = {[3] = 2000}, vocations = {1, 5}}, | |
| 118 | + | ['Heavy Magic Missile'] = {id = 8, words = 'adori vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'heavymagicmissile', mana = 350, level = 25, soul = 2, group = {[3] = 2000}, vocations = {1, 5, 2, 6}}, | |
| 119 | + | ['Poison Bomb'] = {id = 91, words = 'adevo mas pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'poisonbomb', mana = 520, level = 25, soul = 2, group = {[3] = 2000}, vocations = {2, 6}}, | |
| 120 | + | ['Firebomb'] = {id = 17, words = 'adevo mas flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'firebomb', mana = 600, level = 27, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, | |
| 121 | + | ['Soulfire'] = {id = 50, words = 'adevo res flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'soulfire', mana = 600, level = 27, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, | |
| 122 | + | ['Poison Wall'] = {id = 32, words = 'adevo mas grav pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'poisonwall', mana = 640, level = 29, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, | |
| 123 | + | ['Explosion'] = {id = 18, words = 'adevo mas hur', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'explosion', mana = 570, level = 31, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, | |
| 124 | + | ['Fire Wall'] = {id = 28, words = 'adevo mas grav flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'firewall', mana = 780, level = 33, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, | |
| 125 | + | ['Energybomb'] = {id = 55, words = 'adevo mas vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'energybomb', mana = 880, level = 37, soul = 5, group = {[3] = 2000}, vocations = {1, 5}}, | |
| 126 | + | ['Energy Wall'] = {id = 33, words = 'adevo mas grav vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'energywall', mana = 1000, level = 41, soul = 5, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, | |
| 127 | + | ['Sudden Death'] = {id = 21, words = 'adori gran mort', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'suddendeath', mana = 985, level = 45, soul = 5, group = {[3] = 2000}, vocations = {1, 5}}, | |
| 128 | + | ['Cure Poison Rune'] = {id = 31, words = 'adana pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'antidote', mana = 200, level = 15, soul = 1, group = {[3] = 2000}, vocations = {2, 6}}, | |
| 129 | + | ['Intense Healing Rune'] = {id = 4, words = 'adura gran', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'intensehealingrune', mana = 240, level = 15, soul = 2, group = {[3] = 2000}, vocations = {2, 6}}, | |
| 130 | + | ['Ultimate Healing Rune'] = {id = 5, words = 'adura vita', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'ultimatehealingrune', mana = 400, level = 24, soul = 3, group = {[3] = 2000}, vocations = {2, 6}}, | |
| 131 | + | ['Convince Creature'] = {id = 12, words = 'adeta sio', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'convincecreature', mana = 200, level = 16, soul = 3, group = {[3] = 2000}, vocations = {2, 6}}, | |
| 132 | + | ['Animate Dead'] = {id = 83, words = 'adana mort', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'animatedead', mana = 600, level = 27, soul = 5, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, | |
| 133 | + | ['Chameleon'] = {id = 14, words = 'adevo ina', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'chameleon', mana = 600, level = 27, soul = 2, group = {[3] = 2000}, vocations = {2, 6}}, | |
| 134 | + | ['Destroy Field'] = {id = 30, words = 'adito grav', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'destroyfield', mana = 120, level = 17, soul = 2, group = {[3] = 2000}, vocations = {1, 2, 3, 5, 6, 7}}, | |
| 135 | + | ['Desintegrate'] = {id = 78, words = 'adito tera', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'desintegrate', mana = 200, level = 21, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 3, 5, 6, 7}}, | |
| 136 | + | ['Magic Wall'] = {id = 86, words = 'adevo grav tera', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'magicwall', mana = 750, level = 32, soul = 5, group = {[3] = 2000}, vocations = {1, 5}}, | |
| 137 | + | ['Wild Growth'] = {id = 94, words = 'adevo grav vita', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'wildgrowth', mana = 600, level = 27, soul = 5, group = {[3] = 2000}, vocations = {2, 6}}, | |
| 138 | + | ['Paralyze'] = {id = 54, words = 'adana ani', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'paralyze', mana = 1400, level = 54, soul = 3, group = {[3] = 2000}, vocations = {2, 6}}, | |
| 139 | + | ['Icicle'] = {id = 114, words = 'adori frigo', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'icicle', mana = 460, level = 28, soul = 3, group = {[3] = 2000}, vocations = {2, 6}}, | |
| 140 | + | ['Avalanche'] = {id = 115, words = 'adori mas frigo', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'avalanche', mana = 530, level = 30, soul = 3, group = {[3] = 2000}, vocations = {2, 6}}, | |
| 141 | + | ['Stone Shower'] = {id = 116, words = 'adori mas tera', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'stoneshower', mana = 430, level = 28, soul = 3, group = {[3] = 2000}, vocations = {2, 6}}, | |
| 142 | + | ['Thunderstorm'] = {id = 117, words = 'adori mas vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'thunderstorm', mana = 430, level = 28, soul = 3, group = {[3] = 2000}, vocations = {1, 5}}, | |
| 143 | + | ['Holy Missile'] = {id = 130, words = 'adori san', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'holymissile', mana = 350, level = 27, soul = 3, group = {[3] = 2000}, vocations = {3, 7}} | |
| 144 | + | }--[[, | |
| 145 | + | ||
| 146 | + | ['Sample'] = { | |
| 147 | + | ['Wind Walk'] = {id = 1, words = 'windwalk', description = 'Run at enormous speed.', exhaustion = 2000, premium = false, type = 'Instant', icon = 1, mana = 50, level = 10, soul = 0, group = {[3] = 2000}, vocations = {1, 2}}, | |
| 148 | + | ['Fire Breath'] = {id = 2, words = 'firebreath', description = 'A strong firewave.', exhaustion = 2000, premium = false, type = 'Instant', icon = 2, mana = 350, level = 27, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, | |
| 149 | + | ['Moonglaives'] = {id = 3, words = 'moonglaives', description = 'Throw moonglaives around you.', exhaustion = 2000, premium = false, type = 'Instant', icon = 3, mana = 90, level = 55, soul = 0, group = {[1] = 2000}, vocations = {3, 7}}, | |
| 150 | + | ['Critical Strike'] = {id = 4, words = 'criticalstrike', description = 'Land a critical strike.', exhaustion = 2000, premium = false, type = 'Instant', icon = 4, mana = 350, level = 27, soul = 0, group = {[1] = 2000}, vocations = {3, 4, 7, 8}}, | |
| 151 | + | ['Firefly'] = {id = 5, words = 'firefly', description = 'Summon a angry firefly', exhaustion = 2000, premium = false, type = 'Instant', icon = 5, mana = 350, level = 27, soul = 0, group = {[1] = 2000}, vocations = {1, 2, 5, 6}} | |
| 152 | + | }]] | |
| 153 | + | } | |
| 154 | + | ||
| 155 | + | -- ['const_name'] = {client_id, TFS_id} | |
| 156 | + | -- Conversion from TFS icon id to the id used by client (icons.png order) | |
| 157 | + | SpellIcons = { | |
| 158 | + | ['intenserecovery'] = {16, 160}, | |
| 159 | + | ['recovery'] = {15, 159}, | |
| 160 | + | ['intensewoundcleansing'] = {4, 158}, | |
| 161 | + | ['ultimateterrastrike'] = {37, 157}, | |
| 162 | + | ['ultimateicestrike'] = {34, 156}, | |
| 163 | + | ['ultimateenergystrike'] = {31, 155}, | |
| 164 | + | ['ultimateflamestrike'] = {28, 154}, | |
| 165 | + | ['strongterrastrike'] = {36, 153}, | |
| 166 | + | ['strongicestrike'] = {33, 152}, | |
| 167 | + | ['strongenergystrike'] = {30, 151}, | |
| 168 | + | ['strongflamestrike'] = {27, 150}, | |
| 169 | + | ['lightning'] = {51, 149}, | |
| 170 | + | ['physicalstrike'] = {17, 148}, | |
| 171 | + | ['curecurse'] = {11, 147}, | |
| 172 | + | ['curseelectrification'] = {14, 146}, | |
| 173 | + | ['cureburning'] = {13, 145}, | |
| 174 | + | ['curebleeding'] = {12, 144}, | |
| 175 | + | ['holyflash'] = {53, 143}, | |
| 176 | + | ['envenom'] = {58, 142}, | |
| 177 | + | ['inflictwound'] = {57, 141}, | |
| 178 | + | ['electrify'] = {56, 140}, | |
| 179 | + | ['curse'] = {54, 139}, | |
| 180 | + | ['ignite'] = {55, 138}, | |
| 181 | + | -- [[ 136 / 137 Unknown ]] | |
| 182 | + | ['sharpshooter'] = {121, 135}, | |
| 183 | + | ['swiftfoot'] = {119, 134}, | |
| 184 | + | ['bloodrage'] = {96, 133}, | |
| 185 | + | ['protector'] = {122, 132}, | |
| 186 | + | ['charge'] = {98, 131}, | |
| 187 | + | ['holymissile'] = {76, 130}, | |
| 188 | + | ['enchantparty'] = {113, 129}, | |
| 189 | + | ['healparty'] = {126, 128}, | |
| 190 | + | ['protectparty'] = {123, 127}, | |
| 191 | + | ['trainparty'] = {120, 126}, | |
| 192 | + | ['divinehealing'] = {2, 125}, | |
| 193 | + | ['divinecaldera'] = {40, 124}, | |
| 194 | + | ['woundcleansing'] = {3, 123}, | |
| 195 | + | ['divinemissile'] = {39, 122}, | |
| 196 | + | ['icewave'] = {45, 121}, | |
| 197 | + | ['terrawave'] = {47, 120}, | |
| 198 | + | ['rageoftheskies'] = {52, 119}, | |
| 199 | + | ['eternalwinter'] = {50, 118}, | |
| 200 | + | ['thunderstorm'] = {63, 117}, | |
| 201 | + | ['stoneshower'] = {65, 116}, | |
| 202 | + | ['avalanche'] = {92, 115}, | |
| 203 | + | ['icicle'] = {75, 114}, | |
| 204 | + | ['terrastrike'] = {35, 113}, | |
| 205 | + | ['icestrike'] = {32, 112}, | |
| 206 | + | ['etherealspear'] = {18, 111}, | |
| 207 | + | ['enchantspear'] = {104, 110}, | |
| 208 | + | ['piercingbolt'] = {110, 109}, | |
| 209 | + | ['sniperarrow'] = {112, 108}, | |
| 210 | + | ['whirlwindthrow'] = {19, 107}, | |
| 211 | + | ['groundshaker'] = {25, 106}, | |
| 212 | + | ['fierceberserk'] = {22, 105}, | |
| 213 | + | -- [[ 96 - 104 Unknown ]] | |
| 214 | + | ['powerbolt'] = {108, 95}, | |
| 215 | + | ['wildgrowth'] = {61, 94}, | |
| 216 | + | ['challenge'] = {97, 93}, | |
| 217 | + | ['enchantstaff'] = {103, 92}, | |
| 218 | + | ['poisonbomb'] = {70, 91}, | |
| 219 | + | ['cancelinvisibility'] = {95, 90}, | |
| 220 | + | ['flamestrike'] = {26, 89}, | |
| 221 | + | ['energystrike'] = {29, 88}, | |
| 222 | + | ['deathstrike'] = {38, 87}, | |
| 223 | + | ['magicwall'] = {72, 86}, | |
| 224 | + | ['healfriend'] = {8, 84}, | |
| 225 | + | ['animatedead'] = {93, 83}, | |
| 226 | + | ['masshealing'] = {9, 82}, | |
| 227 | + | ['levitate'] = {125, 81}, | |
| 228 | + | ['berserk'] = {21, 80}, | |
| 229 | + | ['conjurebolt'] = {107, 79}, | |
| 230 | + | ['desintegrate'] = {88, 78}, | |
| 231 | + | ['stalagmite'] = {66, 77}, | |
| 232 | + | ['magicrope'] = {105, 76}, | |
| 233 | + | ['ultimatelight'] = {115, 75}, | |
| 234 | + | -- [[ 71 - 64 TFS House Commands ]] | |
| 235 | + | -- [[ 63 - 70 Unknown ]] | |
| 236 | + | ['annihilation'] = {24, 62}, | |
| 237 | + | ['brutalstrike'] = {23, 61}, | |
| 238 | + | -- [[ 60 Unknown ]] | |
| 239 | + | ['frontsweep'] = {20, 59}, | |
| 240 | + | -- [[ 58 Unknown ]] | |
| 241 | + | ['strongetherealspear'] = {59, 57}, | |
| 242 | + | ['wrathofnature'] = {48, 56}, | |
| 243 | + | ['energybomb'] = {86, 55}, | |
| 244 | + | ['paralyze'] = {71, 54}, | |
| 245 | + | -- [[ 53 Unknown ]] | |
| 246 | + | -- [[ 52 TFS Retrieve Friend ]] | |
| 247 | + | ['conjurearrow'] = {106, 51}, | |
| 248 | + | ['soulfire'] = {67, 50}, | |
| 249 | + | ['explosivearrow'] = {109, 49}, | |
| 250 | + | ['poisonedarrow'] = {111, 48}, | |
| 251 | + | -- [[ 46 / 47 Unknown ]] | |
| 252 | + | ['invisible'] = {94, 45}, | |
| 253 | + | ['magicshield'] = {124, 44}, | |
| 254 | + | ['strongicewave'] = {46, 43}, | |
| 255 | + | ['food'] = {99, 42}, | |
| 256 | + | -- [[ 40 / 41 Unknown ]] | |
| 257 | + | ['stronghaste'] = {102, 39}, | |
| 258 | + | ['creatureillusion'] = {100, 38}, | |
| 259 | + | -- [[ 37 TFS Move ]] | |
| 260 | + | ['salvation'] = {60, 36}, | |
| 261 | + | -- [[ 34 / 35 Unknown ]] | |
| 262 | + | ['energywall'] = {84, 33}, | |
| 263 | + | ['poisonwall'] = {68, 32}, | |
| 264 | + | ['antidote'] = {10, 31}, | |
| 265 | + | ['destroyfield'] = {87, 30}, | |
| 266 | + | ['curepoison'] = {10, 29}, | |
| 267 | + | ['firewall'] = {80, 28}, | |
| 268 | + | ['energyfield'] = {85, 27}, | |
| 269 | + | ['poisonfield'] = {69, 26}, | |
| 270 | + | ['firefield'] = {81, 25}, | |
| 271 | + | ['hellscore'] = {49, 24}, | |
| 272 | + | ['greatenergybeam'] = {42, 23}, | |
| 273 | + | ['energybeam'] = {41, 22}, | |
| 274 | + | ['suddendeath'] = {64, 21}, | |
| 275 | + | ['findperson'] = {114, 20}, | |
| 276 | + | ['firewave'] = {44, 19}, | |
| 277 | + | ['explosion'] = {83, 18}, | |
| 278 | + | ['firebomb'] = {82, 17}, | |
| 279 | + | ['greatfireball'] = {78, 16}, | |
| 280 | + | ['fireball'] = {79, 15}, | |
| 281 | + | ['chameleon'] = {91, 14}, | |
| 282 | + | ['energywave'] = {43, 13}, | |
| 283 | + | ['convincecreature'] = {90, 12}, | |
| 284 | + | ['greatlight'] = {116, 11}, | |
| 285 | + | ['light'] = {117, 10}, | |
| 286 | + | ['summoncreature'] = {118, 9}, | |
| 287 | + | ['heavymagicmissile'] = {77, 8}, | |
| 288 | + | ['lightmagicmissile'] = {73, 7}, | |
| 289 | + | ['haste'] = {101, 6}, | |
| 290 | + | ['ultimatehealingrune'] = {62, 5}, | |
| 291 | + | ['intensehealingrune'] = {74, 4}, | |
| 292 | + | ['ultimatehealing'] = {1, 3}, | |
| 293 | + | ['intensehealing'] = {7, 2}, | |
| 294 | + | ['lighthealing'] = {6, 1} | |
| 295 | + | } | |
| 296 | + | ||
| 297 | + | VocationNames = { | |
| 298 | + | [1] = 'Sorcerer', | |
| 299 | + | [2] = 'Druid', | |
| 300 | + | [3] = 'Paladin', | |
| 301 | + | [4] = 'Knight', | |
| 302 | + | [5] = 'Master Sorcerer', | |
| 303 | + | [6] = 'Elder Druid', | |
| 304 | + | [7] = 'Royal Paladin', | |
| 305 | + | [8] = 'Elite Knight' | |
| 306 | + | } | |
| 307 | + | ||
| 308 | + | SpellGroups = { | |
| 309 | + | [1] = 'Attack', | |
| 310 | + | [2] = 'Healing', | |
| 311 | + | [3] = 'Support', | |
| 312 | + | [4] = 'Special' | |
| 313 | + | } | |
| 314 | + | ||
| 315 | + | Spells = {} | |
| 316 | + | ||
| 317 | + | function Spells.getClientId(spellName) | |
| 318 | + | local profile = Spells.getSpellProfileByName(spellName) | |
| 319 | + | ||
| 320 | + | local id = SpellInfo[profile][spellName].icon | |
| 321 | + | if not tonumber(id) and SpellIcons[id] then | |
| 322 | + | return SpellIcons[id][1] | |
| 323 | + | end | |
| 324 | + | return tonumber(id) | |
| 325 | + | end | |
| 326 | + | ||
| 327 | + | function Spells.getServerId(spellName) | |
| 328 | + | local profile = Spells.getSpellProfileByName(spellName) | |
| 329 | + | ||
| 330 | + | local id = SpellInfo[profile][spellName].icon | |
| 331 | + | if not tonumber(id) and SpellIcons[id] then | |
| 332 | + | return SpellIcons[id][2] | |
| 333 | + | end | |
| 334 | + | return tonumber(id) | |
| 335 | + | end | |
| 336 | + | ||
| 337 | + | function Spells.getSpellByName(name) | |
| 338 | + | return SpellInfo[Spells.getSpellProfileByName(name)][name] | |
| 339 | + | end | |
| 340 | + | ||
| 341 | + | function Spells.getSpellByWords(words) | |
| 342 | + | local words = words:lower():trim() | |
| 343 | + | for profile,data in pairs(SpellInfo) do | |
| 344 | + | for k,spell in pairs(data) do | |
| 345 | + | if spell.words == words then | |
| 346 | + | return spell, profile, k | |
| 347 | + | end | |
| 348 | + | end | |
| 349 | + | end | |
| 350 | + | return nil | |
| 351 | + | end | |
| 352 | + | ||
| 353 | + | function Spells.getSpellByIcon(iconId) | |
| 354 | + | for profile,data in pairs(SpellInfo) do | |
| 355 | + | for k,spell in pairs(data) do | |
| 356 | + | if spell.id == iconId then | |
| 357 | + | return spell, profile, k | |
| 358 | + | end | |
| 359 | + | end | |
| 360 | + | end | |
| 361 | + | return nil | |
| 362 | + | end | |
| 363 | + | ||
| 364 | + | function Spells.getSpellIconIds() | |
| 365 | + | local ids = {} | |
| 366 | + | for profile,data in pairs(SpellInfo) do | |
| 367 | + | for k,spell in pairs(data) do | |
| 368 | + | table.insert(ids, spell.id) | |
| 369 | + | end | |
| 370 | + | end | |
| 371 | + | return ids | |
| 372 | + | end | |
| 373 | + | ||
| 374 | + | function Spells.getSpellProfileById(id) | |
| 375 | + | for profile,data in pairs(SpellInfo) do | |
| 376 | + | for k,spell in pairs(data) do | |
| 377 | + | if spell.id == id then | |
| 378 | + | return profile | |
| 379 | + | end | |
| 380 | + | end | |
| 381 | + | end | |
| 382 | + | return nil | |
| 383 | + | end | |
| 384 | + | ||
| 385 | + | function Spells.getSpellProfileByWords(words) | |
| 386 | + | for profile,data in pairs(SpellInfo) do | |
| 387 | + | for k,spell in pairs(data) do | |
| 388 | + | if spell.words == words then | |
| 389 | + | return profile | |
| 390 | + | end | |
| 391 | + | end | |
| 392 | + | end | |
| 393 | + | return nil | |
| 394 | + | end | |
| 395 | + | ||
| 396 | + | function Spells.getSpellProfileByName(spellName) | |
| 397 | + | for profile,data in pairs(SpellInfo) do | |
| 398 | + | if table.findbykey(data, spellName:trim(), true) then | |
| 399 | + | return profile | |
| 400 | + | end | |
| 401 | + | end | |
| 402 | + | return nil | |
| 403 | + | end | |
| 404 | + | ||
| 405 | + | function Spells.getSpellsByVocationId(vocId) | |
| 406 | + | local spells = {} | |
| 407 | + | for profile,data in pairs(SpellInfo) do | |
| 408 | + | for k,spell in pairs(data) do | |
| 409 | + | if table.contains(spell.vocations, vocId) then | |
| 410 | + | table.insert(spells, spell) | |
| 411 | + | end | |
| 412 | + | end | |
| 413 | + | end | |
| 414 | + | return spells | |
| 415 | + | end | |
| 416 | + | ||
| 417 | + | function Spells.filterSpellsByGroups(spells, groups) | |
| 418 | + | local filtered = {} | |
| 419 | + | for v,spell in pairs(spells) do | |
| 420 | + | local spellGroups = Spells.getGroupIds(spell) | |
| 421 | + | if table.equals(spellGroups, groups) then | |
| 422 | + | table.insert(filtered, spell) | |
| 423 | + | end | |
| 424 | + | end | |
| 425 | + | return filtered | |
| 426 | + | end | |
| 427 | + | ||
| 428 | + | function Spells.getGroupIds(spell) | |
| 429 | + | local groups = {} | |
| 430 | + | for k,_ in pairs(spell.group) do | |
| 431 | + | table.insert(groups, k) | |
| 432 | + | end | |
| 433 | + | return groups | |
| 434 | + | end | |
| 435 | + | ||
| 436 | + | function Spells.getImageClip(id, profile) | |
| 437 | + | return (((id-1)%12)*SpelllistSettings[profile].iconSize.width) .. ' ' | |
| 438 | + | .. ((math.ceil(id/12)-1)*SpelllistSettings[profile].iconSize.height) .. ' ' | |
| 439 | + | .. SpelllistSettings[profile].iconSize.width .. ' ' | |
| 440 | + | .. SpelllistSettings[profile].iconSize.height | |
| 441 | + | end |
| @@ -0,0 +1,34 @@ | |||
| 1 | + | local messageModeCallbacks = {} | |
| 2 | + | ||
| 3 | + | function g_game.onTextMessage(messageMode, message) | |
| 4 | + | local callbacks = messageModeCallbacks[messageMode] | |
| 5 | + | if not callbacks or #callbacks == 0 then | |
| 6 | + | perror(string.format('Unhandled onTextMessage message mode %i: %s', messageMode, message)) | |
| 7 | + | return | |
| 8 | + | end | |
| 9 | + | ||
| 10 | + | for _, callback in pairs(callbacks) do | |
| 11 | + | callback(messageMode, message) | |
| 12 | + | end | |
| 13 | + | end | |
| 14 | + | ||
| 15 | + | function registerMessageMode(messageMode, callback) | |
| 16 | + | if not messageModeCallbacks[messageMode] then | |
| 17 | + | messageModeCallbacks[messageMode] = {} | |
| 18 | + | end | |
| 19 | + | ||
| 20 | + | table.insert(messageModeCallbacks[messageMode], callback) | |
| 21 | + | return true | |
| 22 | + | end | |
| 23 | + | ||
| 24 | + | function unregisterMessageMode(messageMode, callback) | |
| 25 | + | if not messageModeCallbacks[messageMode] then | |
| 26 | + | return false | |
| 27 | + | end | |
| 28 | + | ||
| 29 | + | return table.removevalue(messageModeCallbacks[messageMode], callback) | |
| 30 | + | end | |
| 31 | + | ||
| 32 | + | function onViewChat() | |
| 33 | + | -- Implement the logic for viewing chat here | |
| 34 | + | end |
| @@ -0,0 +1,49 @@ | |||
| 1 | + | ThingCategoryItem = 0 | |
| 2 | + | ThingCategoryCreature = 1 | |
| 3 | + | ThingCategoryEffect = 2 | |
| 4 | + | ThingCategoryMissile = 3 | |
| 5 | + | ThingInvalidCategory = 4 | |
| 6 | + | ThingLastCategory = ThingInvalidCategory | |
| 7 | + | ||
| 8 | + | ThingAttrGround = 0 | |
| 9 | + | ThingAttrGroundBorder = 1 | |
| 10 | + | ThingAttrOnBottom = 2 | |
| 11 | + | ThingAttrOnTop = 3 | |
| 12 | + | ThingAttrContainer = 4 | |
| 13 | + | ThingAttrStackable = 5 | |
| 14 | + | ThingAttrForceUse = 6 | |
| 15 | + | ThingAttrMultiUse = 7 | |
| 16 | + | ThingAttrWritable = 8 | |
| 17 | + | ThingAttrWritableOnce = 9 | |
| 18 | + | ThingAttrFluidContainer = 10 | |
| 19 | + | ThingAttrSplash = 11 | |
| 20 | + | ThingAttrNotWalkable = 12 | |
| 21 | + | ThingAttrNotMoveable = 13 | |
| 22 | + | ThingAttrBlockProjectile = 14 | |
| 23 | + | ThingAttrNotPathable = 15 | |
| 24 | + | ThingAttrPickupable = 16 | |
| 25 | + | ThingAttrHangable = 17 | |
| 26 | + | ThingAttrHookSouth = 18 | |
| 27 | + | ThingAttrHookEast = 19 | |
| 28 | + | ThingAttrRotateable = 20 | |
| 29 | + | ThingAttrLight = 21 | |
| 30 | + | ThingAttrDontHide = 22 | |
| 31 | + | ThingAttrTranslucent = 23 | |
| 32 | + | ThingAttrDisplacement = 24 | |
| 33 | + | ThingAttrElevation = 25 | |
| 34 | + | ThingAttrLyingCorpse = 26 | |
| 35 | + | ThingAttrAnimateAlways = 27 | |
| 36 | + | ThingAttrMinimapColor = 28 | |
| 37 | + | ThingAttrLensHelp = 29 | |
| 38 | + | ThingAttrFullGround = 30 | |
| 39 | + | ThingAttrLook = 31 | |
| 40 | + | ThingAttrCloth = 32 | |
| 41 | + | ThingAttrMarket = 33 | |
| 42 | + | ThingAttrNoMoveAnimation = 253 -- >= 1010, value = 16 | |
| 43 | + | ThingAttrChargeable = 254 -- deprecated | |
| 44 | + | ThingLastAttr = 255 | |
| 45 | + | ||
| 46 | + | SpriteMaskRed = 1 | |
| 47 | + | SpriteMaskGreen = 2 | |
| 48 | + | SpriteMaskBlue = 3 | |
| 49 | + | SpriteMaskYellow = 4 |
| @@ -0,0 +1,25 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UIAccordeonButton = extends(UIButton, "UIAccordeonButton") | |
| 3 | + | ||
| 4 | + | function UIAccordeonButton.create() | |
| 5 | + | local btn = UIAccordeonButton.internalCreate() | |
| 6 | + | btn:setOn(false) | |
| 7 | + | return btn | |
| 8 | + | end | |
| 9 | + | ||
| 10 | + | function UIAccordeonButton.onClick(self) | |
| 11 | + | local contentPanel = self:getParent():getChildAfter(self) | |
| 12 | + | ||
| 13 | + | if not contentPanel or #contentPanel:getChildren() == 0 then -- disable if there is no content to show | |
| 14 | + | return | |
| 15 | + | end | |
| 16 | + | ||
| 17 | + | if not self:isOn() then | |
| 18 | + | contentPanel:getLayout():setFitChildren(true) | |
| 19 | + | else | |
| 20 | + | contentPanel:getLayout():setFitChildren(false) | |
| 21 | + | contentPanel:setHeight(0) | |
| 22 | + | end | |
| 23 | + | ||
| 24 | + | self:setOn(not self:isOn()) | |
| 25 | + | end |
| @@ -0,0 +1,39 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UIAccordeonListItem = extends(UIWidget, "UIAccordeonListItem") | |
| 3 | + | ||
| 4 | + | function UIAccordeonListItem.create() | |
| 5 | + | local panel = UIAccordeonListItem.internalCreate() | |
| 6 | + | return panel | |
| 7 | + | end | |
| 8 | + | ||
| 9 | + | function UIAccordeonListItem:onStyleApply(styleName, styleNode) | |
| 10 | + | for name,value in pairs(styleNode) do | |
| 11 | + | if name == 'title' then | |
| 12 | + | self.title = value -- delayed execution | |
| 13 | + | end | |
| 14 | + | end | |
| 15 | + | end | |
| 16 | + | ||
| 17 | + | function UIAccordeonListItem:onLayoutUpdate() | |
| 18 | + | local title = self:recursiveGetChildById('title') | |
| 19 | + | if title and self.title ~= nil then | |
| 20 | + | title:setText(self.title) | |
| 21 | + | self.title = nil | |
| 22 | + | end | |
| 23 | + | ||
| 24 | + | -- couldn't find a better way to do this, but basically move all the added children to content panel automatically | |
| 25 | + | for i=#self:getChildren(), 3, -1 do | |
| 26 | + | self:recursiveGetChildById('content'):addChild(self:getChildren()[i]) | |
| 27 | + | end | |
| 28 | + | end | |
| 29 | + | ||
| 30 | + | function UIAccordeonListItem:setTitle(text) | |
| 31 | + | local title = self:recursiveGetChildById('title') | |
| 32 | + | if title then | |
| 33 | + | title:setText(text) | |
| 34 | + | end | |
| 35 | + | end | |
| 36 | + | ||
| 37 | + | function UIAccordeonListItem:clearChildren() | |
| 38 | + | self:getChildById("content"):clearChildren() | |
| 39 | + | end |
| @@ -0,0 +1,180 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UIAnim = extends(UIImageView, "UIAnim") | |
| 3 | + | ||
| 4 | + | function UIAnim.create() | |
| 5 | + | local widget = UIAnim.internalCreate() | |
| 6 | + | widget.anim = { | |
| 7 | + | opacity = { | |
| 8 | + | value = 1, | |
| 9 | + | dir = 1, | |
| 10 | + | delta = 0.1, | |
| 11 | + | speed = 0 | |
| 12 | + | }, | |
| 13 | + | sprite = { | |
| 14 | + | playing = true, | |
| 15 | + | path = '', | |
| 16 | + | count = 0, | |
| 17 | + | speed = 0, | |
| 18 | + | index = 0, | |
| 19 | + | startIndex = 1, | |
| 20 | + | defaultIndex = 1, | |
| 21 | + | prefix = '', | |
| 22 | + | } | |
| 23 | + | } | |
| 24 | + | widget.events = { | |
| 25 | + | sprite = nil, | |
| 26 | + | opacity = nil | |
| 27 | + | } | |
| 28 | + | return widget | |
| 29 | + | end | |
| 30 | + | ||
| 31 | + | function UIAnim:onStyleApply(styleName, styleNode) | |
| 32 | + | local updateAnim = false | |
| 33 | + | ||
| 34 | + | for name,value in pairs(styleNode) do | |
| 35 | + | if name == 'sprite-path' then | |
| 36 | + | if not value:ends('/') then | |
| 37 | + | value = value .. '/' | |
| 38 | + | end | |
| 39 | + | self.anim.sprite.path = value | |
| 40 | + | updateAnim = true | |
| 41 | + | elseif name == 'sprite-prefix' then | |
| 42 | + | self.anim.sprite.prefix = value | |
| 43 | + | updateAnim = true | |
| 44 | + | elseif name == 'sprite-count' then | |
| 45 | + | self.anim.sprite.count = tonumber(value) | |
| 46 | + | updateAnim = true | |
| 47 | + | elseif name == 'sprite-speed' then | |
| 48 | + | self.anim.sprite.speed = tonumber(value) | |
| 49 | + | updateAnim = true | |
| 50 | + | elseif name == 'sprite-start-index' then | |
| 51 | + | self.anim.sprite.startIndex = tonumber(value) | |
| 52 | + | updateAnim = true | |
| 53 | + | elseif name == 'sprite-default-index' then | |
| 54 | + | self.anim.sprite.defaultIndex = tonumber(value) | |
| 55 | + | updateAnim = true | |
| 56 | + | elseif name == 'opacity-speed' then | |
| 57 | + | self.anim.opacity.speed = tonumber(value) | |
| 58 | + | updateAnim = true | |
| 59 | + | elseif name == 'opacity-delta' then | |
| 60 | + | self.anim.opacity.delta = tonumber(value) | |
| 61 | + | updateAnim = true | |
| 62 | + | end | |
| 63 | + | end | |
| 64 | + | ||
| 65 | + | if updateAnim then | |
| 66 | + | self:updateAnim() | |
| 67 | + | end | |
| 68 | + | end | |
| 69 | + | ||
| 70 | + | function UIAnim:onSpriteReset() | |
| 71 | + | local sprite = self.anim.sprite | |
| 72 | + | sprite.index = sprite.defaultIndex | |
| 73 | + | ||
| 74 | + | local filename = sprite.path .. sprite.prefix .. sprite.index | |
| 75 | + | self:setImageSource(filename) | |
| 76 | + | ||
| 77 | + | -- sprite.event = scheduleEvent(self.onAnim, sprite.speed) | |
| 78 | + | end | |
| 79 | + | ||
| 80 | + | function UIAnim:onSprite() | |
| 81 | + | local sprite = self.anim.sprite | |
| 82 | + | sprite.index = sprite.index + 1 | |
| 83 | + | if sprite.index > sprite.count then | |
| 84 | + | sprite.index = sprite.startIndex | |
| 85 | + | end | |
| 86 | + | ||
| 87 | + | local filename = sprite.path .. sprite.prefix .. sprite.index | |
| 88 | + | self:setImageSource(filename) | |
| 89 | + | ||
| 90 | + | -- sprite.event = scheduleEvent(self.onAnim, sprite.speed) | |
| 91 | + | end | |
| 92 | + | ||
| 93 | + | function UIAnim:onOpacity() | |
| 94 | + | local settings = self.anim.opacity | |
| 95 | + | local opacity = self:getOpacity() | |
| 96 | + | local delta = settings.delta | |
| 97 | + | local dir = settings.dir | |
| 98 | + | opacity = opacity + dir * delta | |
| 99 | + | ||
| 100 | + | if opacity < 0 or opacity > 1 then | |
| 101 | + | settings.dir = dir * -1 | |
| 102 | + | end | |
| 103 | + | ||
| 104 | + | self:setOpacity(opacity) | |
| 105 | + | end | |
| 106 | + | ||
| 107 | + | function UIAnim:updateAnim(name) | |
| 108 | + | -- if not self.sprite then return end | |
| 109 | + | ||
| 110 | + | if not name or name == 'sprite' then | |
| 111 | + | if self.events.sprite then | |
| 112 | + | removeEvent(self.events.sprite) | |
| 113 | + | self.events.sprite = nil | |
| 114 | + | end | |
| 115 | + | ||
| 116 | + | if self.anim.sprite then | |
| 117 | + | local speed = self.anim.sprite.speed | |
| 118 | + | if speed > 0 then | |
| 119 | + | self.events.sprite = cycleEvent(function() self:onSprite() end, speed) | |
| 120 | + | end | |
| 121 | + | end | |
| 122 | + | end | |
| 123 | + | ||
| 124 | + | if not name or name == 'opacity' then | |
| 125 | + | if self.events.opacity then | |
| 126 | + | removeEvent(self.events.opacity) | |
| 127 | + | self.events.opacity = nil | |
| 128 | + | end | |
| 129 | + | ||
| 130 | + | if self.anim.opacity then | |
| 131 | + | local settings = self.anim.opacity | |
| 132 | + | if settings.speed > 0 then | |
| 133 | + | local delta = settings.delta | |
| 134 | + | self.events.opacity = cycleEvent(function() self:onOpacity() end, settings.speed) | |
| 135 | + | end | |
| 136 | + | end | |
| 137 | + | end | |
| 138 | + | end | |
| 139 | + | ||
| 140 | + | function UIAnim:stopAnim(name) | |
| 141 | + | if name == 'sprite' then | |
| 142 | + | removeEvent(self.events.sprite) | |
| 143 | + | self.events.sprite = nil | |
| 144 | + | self:onSpriteReset() | |
| 145 | + | ||
| 146 | + | elseif name == 'opacity' then | |
| 147 | + | removeEvent(self.events.opacity) | |
| 148 | + | self.events.opacity = nil | |
| 149 | + | ||
| 150 | + | elseif not name then | |
| 151 | + | for name, event in pairs(self.events) do | |
| 152 | + | removeEvent(event) | |
| 153 | + | if name == 'sprite' then | |
| 154 | + | self:onSpriteReset() | |
| 155 | + | end | |
| 156 | + | end | |
| 157 | + | ||
| 158 | + | self.events = {} | |
| 159 | + | end | |
| 160 | + | end | |
| 161 | + | ||
| 162 | + | function UIAnim:startAnim(name) | |
| 163 | + | self:updateAnim(name) | |
| 164 | + | end | |
| 165 | + | ||
| 166 | + | function UIAnim:onDestroy() | |
| 167 | + | for _, event in pairs(self.events) do | |
| 168 | + | removeEvent(event) | |
| 169 | + | end | |
| 170 | + | self.events = {} | |
| 171 | + | end | |
| 172 | + | ||
| 173 | + | function UIAnim:isPlaying(name) | |
| 174 | + | name = name or 'sprite' | |
| 175 | + | if name == 'sprite' then | |
| 176 | + | return self.events.sprite ~= nil | |
| 177 | + | else | |
| 178 | + | return self.events.opacity ~= nil | |
| 179 | + | end | |
| 180 | + | end |