local lastSkin -- used to record the player's skin when active -- skin statmods start here. All changes additive, except PPR which is override. PSO.info.skinstats = { ["fallback"] = { [TYPE_HUNTER] = { hp = 0, pp = 0, satk = 0, ratk = 0, tatk = 0, def = 0, ppr = 1000, -- overrides base ppr! incremenets every tic until it reaches class' case regen target (18000 for hunter) hpscal = 0, satkscal = 0, ratkscal = 0, tatkscal = 0, defscal = 0 }, [TYPE_RANGER] = { hp = 0, pp = 0, satk = 0, ratk = 0, tatk = 0, def = 0, ppr = 1000, -- target: 6000 hpscal = 0, satkscal = 0, ratkscal = 0, tatkscal = 0, defscal = 0 }, [TYPE_FORCE] = { hp = 0, pp = 0, satk = 0, ratk = 0, tatk = 0, def = 0, ppr = 1000, -- target: 4000 hpscal = 0, satkscal = 0, ratkscal = 0, tatkscal = 0, defscal = 0 } }, ["sonic"] = { [TYPE_HUNTER] = { hp = -25, pp = 16, satk = 3, ratk = 3, tatk = 3, def = -1, ppr = 1200, hpscal = 10, satkscal = 0, ratkscal = 0, tatkscal = 0, defscal = 1 }, [TYPE_RANGER] = { hp = -20, pp = 16, satk = 3, ratk = 3, tatk = 3, def = -1, ppr = 600, hpscal = 10, satkscal = 0, ratkscal = 0, tatkscal = 0, defscal = 1 }, [TYPE_FORCE] = { hp = -30, pp = 16, satk = 3, ratk = 3, tatk = 3, def = -1, ppr = 667, hpscal = 10, satkscal = 0, ratkscal = 0, tatkscal = 0, defscal = 1 } }, ["tails"] = { [TYPE_HUNTER] = { hp = 5, pp = 1, satk = -1, ratk = 0, tatk = 1, def = 0, ppr = 1285, hpscal = 2, satkscal = 0, ratkscal = 0, tatkscal = 0, defscal = -1 }, [TYPE_RANGER] = { hp = 5, pp = 1, satk = 1, ratk = -1, tatk = 0, def = 0, ppr = 667, hpscal = 2, satkscal = 0, ratkscal = 0, tatkscal = 0, defscal = -1 }, [TYPE_FORCE] = { hp = 5, pp = 1, satk = 2, ratk = -1, tatk = -1, def = 0, ppr = 571, hpscal = 2, satkscal = 0, ratkscal = 0, tatkscal = 0, defscal = -1 } }, ["knuckles"] = { [TYPE_HUNTER] = { hp = 25, pp = -14, satk = 3, ratk = -5, tatk = 2, def = 1, ppr = 900, hpscal = -5, satkscal = -1, ratkscal = 2, tatkscal = 1, defscal = 0 }, [TYPE_RANGER] = { hp = 25, pp = -14, satk = 3, ratk = -5, tatk = 1, def = 1, ppr = 400, hpscal = -5, satkscal = 0, ratkscal = 1, tatkscal = 1, defscal = 0 }, [TYPE_FORCE] = { hp = 25, pp = -14, satk = 3, ratk = -5, tatk = 1, def = 1, ppr = 400, hpscal = -5, satkscal = -1, ratkscal = 2, tatkscal = 0, defscal = 0 } }, ["amy"] = { [TYPE_HUNTER] = { hp = -15, pp = 26, satk = 1, ratk = -2, tatk = 3, def = 0, ppr = 2500, hpscal = 5, satkscal = 0, ratkscal = 1, tatkscal = -1, defscal = 0 }, [TYPE_RANGER] = { hp = -15, pp = 26, satk = 1, ratk = -2, tatk = 3, def = 0, ppr = 2000, hpscal = 5, satkscal = 0, ratkscal = 1, tatkscal = -1, defscal = 0 }, [TYPE_FORCE] = { hp = -15, pp = 26, satk = 1, ratk = -2, tatk = 3, def = 0, ppr = 1600, hpscal = 5, satkscal = 0, ratkscal = 1, tatkscal = -1, defscal = 0 } }, ["fang"] = { [TYPE_HUNTER] = { hp = 15, pp = 6, satk = -3, ratk = 4, tatk = 2, def = 1, ppr = 1000, hpscal = -2, satkscal = 1, ratkscal = 0, tatkscal = 0, defscal = 1 }, [TYPE_RANGER] = { hp = 15, pp = 6, satk = -3, ratk = 4, tatk = 2, def = 1, ppr = 1000, hpscal = -2, satkscal = 1, ratkscal = 0, tatkscal = 0, defscal = 1 }, [TYPE_FORCE] = { hp = 15, pp = 6, satk = -3, ratk = 4, tatk = 2, def = 1, ppr = 1000, hpscal = -2, satkscal = 1, ratkscal = 0, tatkscal = 0, defscal = 1 } }, ["metalsonic"] = { [TYPE_HUNTER] = { hp = 10, pp = -24, satk = 8, ratk = 5, tatk = -15, def = 2, ppr = 1800, hpscal = 0, satkscal = 0, ratkscal = 0, tatkscal = 2, defscal = 0 }, [TYPE_RANGER] = { hp = 10, pp = -24, satk = 7, ratk = 10, tatk = -17, def = 2, ppr = 1500, hpscal = 0, satkscal = 0, ratkscal = 0, tatkscal = 2, defscal = 0 }, [TYPE_FORCE] = { hp = 10, pp = -24, satk = 4, ratk = 7, tatk = -5, def = 2, ppr = 1333, hpscal = 5, satkscal = 0, ratkscal = 0, tatkscal = 0, defscal = 1 } } } PSO.lib.playerPreviewStats = function(p, type, level, units, ignoresynch) local stats = {} if not p.pso.ais local synch = p.pso.levelsynch if synch level = synch end local typinfo = PSO.info.typestats[type] -- start checking for Skinstats here local ssinfo if not PSO.info.skinstats[lastSkin] then -- if none exists for a given skin, use the "fallback" table (no stat changes) ssinfo = PSO.info.skinstats["fallback"][p.pso.type] else ssinfo = PSO.info.skinstats[lastSkin][p.pso.type] end stats.maxhp = (typinfo.hp + ssinfo.hp) +((typinfo.hp + ssinfo.hp)/(50 + ssinfo.hpscal))*(level-1) stats.maxpp = (typinfo.pp + ssinfo.pp) + (level-1) stats.satk = (typinfo.satk + ssinfo.satk) + (typinfo.satk + ssinfo.satk)*(level-1) /(4 + ssinfo.satkscal) stats.ratk = (typinfo.ratk + ssinfo.ratk) + (typinfo.ratk + ssinfo.ratk)*(level-1) /(4 + ssinfo.ratkscal) stats.tatk = (typinfo.tatk + ssinfo.tatk) + (typinfo.tatk + ssinfo.tatk)*(level-1) /(4 + ssinfo.tatkscal) stats.def = (typinfo.def + ssinfo.def) + (typinfo.def + ssinfo.def)*(level-1) /(4 + ssinfo.defscal) stats.ppregen = ssinfo.ppr -- 1000 normally stats.actionspd = 100 -- resolution if units local armour = PSO.lib.getPlayerActiveArmour(p) if armour local def = PSO.info.items[armour.ref] local ehp = (def.hp or 0) local epp = (def.pp or 0) if synch local b, ahp, app = PSO.lib.lvSynchArmour(p, synch, armour) ehp = ahp or 0 epp = app or 0 end stats.maxhp = $ + ehp stats.maxpp = $ + epp if def.slots -- necessary to count these for lv synch local numslots = 0 for i = 1, def.slots if armour.slots[i] numslots = $+1 end end for i = 1, def.slots if armour.slots[i] local unit = armour.slots[i] local unitsynch if synch unitsynch = PSO.lib.lvSynchUnit(p, synch, unit, numslots) end stats.maxhp = $ + (unitsynch and unitsynch.hp or unit.hp or 0) stats.maxpp = $ + (unitsynch and unitsynch.pp or unit.pp or 0) stats.satk = $ + (unitsynch and unitsynch.satk or unit.satk or 0) stats.ratk = $ + (unitsynch and unitsynch.ratk or unit.ratk or 0) stats.tatk = $ + (unitsynch and unitsynch.tatk or unit.tatk or 0) stats.ppregen = max(1, $ + 1000*(unit.ppregen or 0)/100) stats.actionspd = max(1, $+(unit.actionspd or 0)) end end end end end stats.maxhp = max($, 1) stats.maxpp = max($, 1) stats.satk = max($, 1) stats.ratk = max($, 1) stats.tatk = max($, 1) stats.def = max($, 1) else -- ais level is the quest's enemy level if server.questdata local q = PSO.info.quests[server.questdata.section].quests[server.questdata.index] level = q.levels[server.questdata.difficulty] or server.pso.level end if server.psoenemylv -- debug cheats level = min(255, server.psoenemylv) end local info = PSO.info.typestats.ais stats.maxhp = info.hp *PSO.info.enemyStatScaling[level]/PSO.info.enemyStatScalingRes stats.maxpp = info.pp stats.satk = info.satk *PSO.info.enemyStatScaling[level]/PSO.info.enemyStatScalingRes stats.ratk = info.ratk *PSO.info.enemyStatScaling[level]/PSO.info.enemyStatScalingRes stats.tatk = info.tatk *PSO.info.enemyStatScaling[level]/PSO.info.enemyStatScalingRes stats.def = info.def *PSO.info.enemyStatScaling[level]/PSO.info.enemyStatScalingRes stats.ppregen = 1000 end return stats end -- stat screen copied to adjust reading of base PPR PSO.info.menus["status_stats"] = { hudfunc = function(v, p, c, choices, choice, timer) drawMainMenu(v, p, c, 2, 999) local xpos = 2 local w = 160 local y = 20 local h = 122 v.drawFill(xpos, y, w, h, 253|V_SNAPTOLEFT) v.drawString(xpos+2, y+2, PSO.info.typenames[p.pso.type].." LV"..p.pso.level, (p.pso.level < PSO.info.LEVELCAP and V_YELLOWMAP or V_ORANGEMAP)|V_SNAPTOLEFT) if p.pso.level < PSO.info.LEVELCAP v.drawString(xpos+2, y+10, "EXP: "..p.pso.exp.."/"..(PSO.info.exptable[p.pso.level] or "UNDEFINED"), V_YELLOWMAP|V_SNAPTOLEFT) else v.drawString(xpos+2, y+10, "CUBE: "..p.pso.exp.."/"..(PSO.info.exptable[p.pso.level] or "UNDEFINED"), V_ORANGEMAP|V_SNAPTOLEFT) end local playtime_h = G_TicsToHours(p.pso.playtime) local playtime_m = string.format("%02d", G_TicsToMinutes(p.pso.playtime)) local playtime_s = string.format("%02d", G_TicsToSeconds(p.pso.playtime)) v.drawString(xpos+2, y+18, "\x82PLAYTIME: \x80"..playtime_h..":"..playtime_m..":"..playtime_s, V_SNAPTOLEFT) local snamex = xpos+2 local svalx = xpos+60 local svalx2 = xpos+100 v.drawString(snamex, y+30, "STAT", V_YELLOWMAP|V_SNAPTOLEFT) v.drawString(svalx, y+30, "VAL", V_YELLOWMAP|V_SNAPTOLEFT) v.drawString(svalx2, y+30, "(GEAR)", V_YELLOWMAP|V_SNAPTOLEFT) v.drawFill(xpos+2, y+38, w-4, 1, V_SNAPTOLEFT) local stats = PSO.lib.playerPreviewStats(p, p.pso.type, p.pso.level) local statrefs = {"maxhp", "maxpp", "satk", "ratk", "tatk", "def", "critrate", "critdamage", "actionspd", "ppregen"} local statnames = {"HP", "PP", "S. ATK", "R. ATK", "T. ATK", "DEF", "CRIT. RATE", "CRIT. DMG", "ATK. SPEED", "PP REGEN"} stats["critrate"] = PSO.info.BASECRITRATE stats["critdamage"] = PSO.info.BASECRITDMG stats["actionspd"] = 100 -- by default stats["ppregen"] = PSO.info.typestats[p.pso.type].ppregen for i = 1, #statrefs local ypos = y+40 + 8*(i-1) v.drawString(snamex, ypos, statnames[i], V_YELLOWMAP|V_SNAPTOLEFT, "thin") local addstr = "" local val = tostring(stats[statrefs[i]]) if i > 6 and i < 10 addstr = "%" elseif i == 10 -- get pp regen per second... local threshold = stats["ppregen"]*FRACUNIT local tic = TICRATE*FRACUNIT local pp_s = FixedDiv(tic, threshold) pp_s = $ * PSO.info.skinstats[lastSkin][p.pso.type].ppr / 1000 -- attempt using base PPR override here local pp_whole = pp_s/FRACUNIT local pp_sub = (pp_s % FRACUNIT)*100 / FRACUNIT val = pp_whole.."."..string.sub(string.format("%02d", pp_sub), 1, 2) addstr = "/s" end v.drawString(svalx, ypos, val..addstr, V_SNAPTOLEFT|V_ALLOWLOWERCASE, "thin") local basestat = (p.pso[statrefs[i]] or 0) local addstat = 0 if i >= 3 and i <= 5 local weapon = PSO.lib.getPlayerActiveWeapon(p) local dmts = {DMT_STRIKE, DMT_RANGED, DMT_TECHNIQUE} if weapon local wdmt = PSO.info.weaponDamageType[weapon.weapontype] if wdmt == dmts[i-2] local satk if p.pso.levelsynch satk = PSO.lib.lvSynchWeapon(p, p.pso.levelsynch, weapon) end addstat = $ + (satk or weapon.atk) end end elseif i == 6 local armor = PSO.lib.getPlayerArmor(p) if armor local sdef if p.pso.levelsynch sdef = PSO.lib.lvSynchArmour(p, p.pso.levelsynch, armor) end addstat = $ + (sdef or armor.def) end elseif i > 6 and i < 10 -- get unit stats basestat = stats[statrefs[i]] local armor = PSO.lib.getPlayerArmor(p) if armor -- we don't need to level synch units local slots = PSO.lib.itemInfo(armor).slots for j = 1, slots or 0 local unit = armor.slots[j] if unit local uinfo = PSO.lib.itemInfo(unit) addstat = $ + (uinfo[statrefs[i]] or 0) end end end elseif i == 10 addstat = "" -- get pp regen per second... local threshold = stats["ppregen"]*FRACUNIT local tic = TICRATE*FRACUNIT local pp_s = FixedDiv(tic, threshold) pp_s = $ * (p.pso.ppregen) / 1000 local pp_whole = pp_s/FRACUNIT local pp_sub = (pp_s % FRACUNIT)*100 / FRACUNIT basestat = pp_whole.."."..string.sub(string.format("%02d", pp_sub), 1, 2) end v.drawString(svalx2, ypos, "("..tostring(basestat + addstat)..addstr..")", V_SNAPTOLEFT|V_ALLOWLOWERCASE, "thin") end end, runfunc = function(p) local input = PSO.lib.playerInputDown if input(p, IPT_BACK) == 1 PSO.lib.changeMenu(p, "status", true, true) p.pso.menu.timer = TICRATE*10 -- cheat. S_StartSound(nil, sfx_mback, p) end return true end, choices = {}, } -- force stat recalc when player changes skins! PSO.lib.ssSkinChange = function(p) for p in players.iterate do if p.mo.skin ~= lastSkin then -- check if player changes skin, used to force stat recalculation lastSkin = p.mo.skin if levelsynch then PSO.lib.playerCalcStats(p, p.pso.levelsynch) else PSO.lib.playerCalcStats(p, p.pso.level) end end end end addHook("ThinkFrame", PSO.lib.ssSkinChange)