Guest User

Untitled

a guest
May 22nd, 2019
86
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 123.59 KB | None | 0 0
  1. -- It is very important to consider all cases for arguments, as the client can send anything for arguments via exploit.
  2. -- Pretty much just return false for requests with invalid arguments.
  3.  
  4. -- search "OVH" for overhaul notes
  5. -- search "PDL" and also look for other potential pokemon data leaks
  6. -- todo: search all instances of Pokemon:new on server, ensure PlayerData is included in call
  7. -- destroy Pokemon objects where appropriate
  8.  
  9. -- OVH remove rc4 as much as possible on server side
  10. local _f = require(script.Parent)
  11.  
  12. local PlayerData, PC
  13. local PlayerDataByPlayer = {}--setmetatable({}, {__mode = 'k'})
  14. local function onPlayerEnter(player)
  15. if not player or not player:IsA('Player') or PlayerDataByPlayer[player] then return end
  16. local pd = PlayerData:new(player)
  17. PlayerDataByPlayer[player] = pd
  18. end
  19.  
  20. local network = _f.Network
  21. local context = _f.Context
  22.  
  23. local publicFns = {
  24. getContinueScreenInfo = true,
  25. continueGame = true,
  26. startNewGame = true,
  27. saveGame = true,
  28. completeEvent = true,
  29.  
  30. getStarterData = true,
  31. buyStarter = true,
  32. buyAshGreninja = true,
  33. roStatus = true,
  34.  
  35. getParty = true,
  36. getPartyPokeBalls = true,
  37. getPokemonSummary = true,
  38. getCutter = true,
  39. getDigger = true,
  40. getHeadbutter = true,
  41. getSmasher = true,
  42. getClimber = true,
  43. getHappiness = true,
  44.  
  45. getDex = true,
  46. getCardInfo = true,
  47.  
  48. getBagPouch = true,
  49. getTMs = true,
  50. getBattleBag = true,
  51. useItem = true,
  52. giveItem = true,
  53. takeItem = true,
  54. tossItem = true,
  55. teachTM = true,
  56. obtainItem = true,
  57.  
  58. deleteMove = true,
  59. remindMove = true,
  60. getShop = true,
  61. maxBuy = true,
  62. buyItem = true,
  63. bMaxBuy = true,
  64. buyWithBP = true,
  65. sellItem = true,
  66.  
  67. makeDecision = true,
  68. openPC = true,
  69. cPC = true,
  70. closePC = true,
  71.  
  72. getDCPhrase = true,
  73. takeEgg = true,
  74. getDCInfo = true,
  75. leaveDCPokemon = true,
  76. takeDCPokemon = true,
  77.  
  78. countBatteries = true,
  79. hasFossil = true,
  80. reviveFossil = true,
  81. dive = true,
  82. nextDig = true,
  83. finishDig = true,
  84.  
  85. nSpins = true,
  86. spinForStamp = true,
  87. stampInventory = true,
  88. setStamps = true,
  89.  
  90. hasOKS = true,
  91. hasSTP = true,
  92. hasFlute = true,
  93. hasRTM = true,
  94. hasJKey = true,
  95. getHoneyData = true,
  96. getHoney = true,
  97. isDinWM = true,
  98. isTinD = true,
  99. buySushi = true,
  100. getGreenhouseState = true,
  101. giveEkans = true,
  102. motorize = true,
  103. buyTicket = true,
  104. giveTicketItems = true,
  105.  
  106. hover = true,
  107. setHoverboard = true,
  108. ownsHoverboard = true,
  109. purchaseHoverboard = true,
  110.  
  111. getWtrOp = true, -- get water options (Surf/Old Rod/etc.)
  112.  
  113. -- debug
  114. pdc = true
  115. }
  116. local publicEvents = {
  117. chooseName = true,
  118. completedEggCycle = true,
  119. rearrangeParty = true,
  120. keepEgg = true,
  121. resetFishStreak = true,
  122. slatherHoney = true,
  123. purchaseRoPower = true,
  124. unhover = true,
  125. }
  126. network:bindFunction('PDS', function(player, fnName, ...)
  127. if not publicFns[fnName] then network.GenerateReport(player, 'attempted to call PDS function "'..tostring(fnName)..'"') return end
  128. local pd = PlayerDataByPlayer[player]
  129. if not pd then
  130. -- uh, we should have created PlayerData for this player... what happened?
  131. error(player.Name .. ' has no Player Data')
  132. end
  133. return pd[fnName](pd, ...)
  134. end)
  135. network:bindEvent('PDS', function(player, fnName, ...)
  136. if not publicEvents[fnName] then network.GenerateReport(player, 'attempted to call PDS event "'..tostring(fnName)..'"') return end
  137. local pd = PlayerDataByPlayer[player]
  138. if not pd then
  139. -- uh, we should have created PlayerData for this player... what happened?
  140. error(player.Name .. ' has no Player Data')
  141. end
  142. pd[fnName](pd, ...)
  143. end)
  144.  
  145.  
  146. local storage = game:GetService('ServerStorage')
  147. local Utilities = _f.Utilities
  148. local BitBuffer = _f.BitBuffer--require(storage.Plugins.BitBuffer)
  149. local Region = require(storage.Plugins.Region)
  150. local Assets = require(storage.src.Assets) -- for game passes
  151. local UsableItemsClient = require(storage.src.UsableItemsClient)() -- note: nothing passed for _p
  152. local RoamingPokemon = require(storage.Data.Chunks).roamingEncounter
  153.  
  154. local MAX_MONEY = 9999999
  155. local MAX_BP = 9999
  156. local RO_POWER_EFFECT_DURATION = 60 * 60
  157.  
  158. local RUN_FULL_CHECK = false
  159.  
  160. PlayerData = Utilities.class({
  161. className = 'ServerPlayerData',
  162. gameBegan = false,
  163. trainerName = '',
  164. pokedex = '',
  165. money = 0,
  166. bp = 0,
  167. obtainedItems = '',
  168. tms = '',
  169. hms = '',
  170. defeatedTrainers = '',
  171. expShareOn = false,
  172. lcht = 0,
  173. lastDrifloonEncounterWeek = 0,
  174. lastTrubbishEncounterWeek = 0,
  175. lastHoneyGivenDay = 0,
  176. fishingStreak = 0,
  177. starterType = '',
  178. stampSpins = 0,
  179. currentHoverboard = '',
  180.  
  181. }, function(player)
  182. local self = {
  183. player = player,
  184. userId = player.UserId,
  185. trainerName = player.Name, -- temporary/backup
  186. pc = PC:new(),
  187. party = {},
  188. bag = {{},{},{},{},{}}, -- Items, Medicine, Poke Balls, Berries, Key Items
  189. badges = {},
  190. completedEvents = {},
  191. daycare = {
  192. depositedPokemon = {},
  193. manHasEgg = false
  194. },
  195. ownedGamePassCache = {},
  196. rtick = tick()%1,
  197. roPowers = {
  198. powerLevel = {0, 0, 0, 0, 0, 0, 0},
  199. lastPurchasedAt = {0, 0, 0, 0, 0, 0, 0}
  200. },
  201. flags = {}, -- for indicating that the player is allowed to do certain tasks
  202. lastCompletedEggCycle = tick(),
  203. -- eggCycleAbuseReports = 0, -- limit these per session
  204. decision_data = {},
  205. decision_count = 0,
  206. starterProductStack = {},
  207. ashGreninjaProductStack = {}, -- todo: unify this with the starter purchase system
  208. hoverboardProductStack = {},
  209. lottoticketProductStack = {},
  210. pbStamps = {},
  211. ownedHoverboards = {},
  212. }
  213. setmetatable(self, PlayerData)
  214. -- cache player save data as soon as possible
  215. Utilities.fastSpawn(PlayerData.getSaveData, self)
  216. -- cache owned game passes for quicker lookup
  217. if self.userId > 0 then
  218. Utilities.fastSpawn(function()
  219. for _, passId in pairs(Assets.passId) do
  220. self:ownsGamePass(passId)
  221. end
  222. end)
  223. end
  224. return self
  225. end)
  226.  
  227. function PlayerData:random(x, y)
  228. local r = (math.random()+self.rtick)%1
  229. if x and y then
  230. return math.floor(x + (y+1-x)*r)
  231. elseif x then
  232. return math.floor(1 + x*r)
  233. end
  234. return r
  235. end
  236. function PlayerData:random2(x, y)
  237. local r = (math.random()-self.rtick+1)%1
  238. if x and y then
  239. return math.floor(x + (y+1-x)*r)
  240. elseif x then
  241. return math.floor(1 + x*r)
  242. end
  243. return r
  244. end
  245.  
  246.  
  247. function PlayerData:check() end -- OVH todo
  248.  
  249.  
  250. function PlayerData:isInBattle()
  251. return _f.BattleEngine:getBattleSideForPlayer(self.player) ~= nil
  252. end
  253.  
  254. function PlayerData:isInTrade()
  255. return _f.TradeManager:playerIsInTrade(self.player)
  256. end
  257.  
  258. function PlayerData:getParty(context)
  259. -- check for open battles involving this player
  260. -- party order may change, hp, etc.
  261. local battleSide = _f.BattleEngine:getBattleSideForPlayer(self.player)
  262. local battleParty
  263. if battleSide then
  264. battleParty = battleSide.pokemon
  265. -- 2v2
  266. if battleSide.isTwoPlayerSide and battleSide.battle.is2v2 then
  267. local lp = battleSide.battle.listeningPlayers
  268. local teamn = (lp[battleSide.id]==self.player) and 1 or 2
  269. -- local indexOffset = (teamn==2) and battleSide.nPokemonFromTeam1 or 0
  270. local party = {}
  271. for _, battlePokemon in pairs(battleSide.pokemon) do
  272. if battlePokemon.teamn == teamn then
  273. table.insert(party, self.party[battlePokemon.originalPartyIndex]:getPartyData(battlePokemon, context))
  274. end
  275. end
  276. return party
  277. end
  278. --
  279. end
  280.  
  281. local party = {0, 0, 0, 0, 0, 0} -- placeholders
  282. for i, pokemon in ipairs(self.party) do
  283. if battleParty then
  284. local battlePokemon
  285. for _, p in pairs(battleParty) do
  286. if p.index == i then
  287. battlePokemon = p
  288. break
  289. end
  290. end
  291. if battlePokemon then
  292. party[battlePokemon.position] = pokemon:getPartyData(battlePokemon, context)
  293. end
  294. else
  295. party[i] = pokemon:getPartyData({}, context)
  296. end
  297. end
  298. for i = 6, 1, -1 do
  299. if party[i] == 0 then
  300. table.remove(party, i)
  301. end
  302. end
  303. return party
  304. end
  305.  
  306. function PlayerData:getPartyPokeBalls()
  307. -- we also (discretely) heal here
  308. self:heal()
  309. local balls = {}
  310. for _, p in pairs(self.party) do
  311. if not p.egg then
  312. table.insert(balls, p.pokeball or 1)
  313. end
  314. end
  315. return balls
  316. end
  317.  
  318. function PlayerData:getPokemonSummary(index)
  319. local battleSide = _f.BattleEngine:getBattleSideForPlayer(self.player)
  320. local pokemon, battlePokemon
  321. if battleSide then
  322. -- 2v2
  323. if battleSide.isTwoPlayerSide and battleSide.battle.is2v2 then
  324. local lp = battleSide.battle.listeningPlayers
  325. local teamn = (lp[battleSide.id]==self.player) and 1 or 2
  326. -- local indexOffset = (teamn==2) and battleSide.nPokemonFromTeam1 or 0
  327. for _, battlePokemon in pairs(battleSide.pokemon) do
  328. if battlePokemon.teamn == teamn then
  329. index = index - 1
  330. if index == 0 then
  331. -- if battlePokemon.index == index then
  332. return self.party[battlePokemon.originalPartyIndex]:getSummary(battlePokemon)
  333. end
  334. end
  335. end
  336. return nil
  337. end
  338. --
  339. battlePokemon = battleSide.pokemon[index]
  340. pokemon = self.party[battlePokemon.index]
  341. else
  342. pokemon = self.party[index]
  343. end
  344. if not pokemon then return end
  345. return pokemon:getSummary(battlePokemon or {})
  346. end
  347.  
  348. function PlayerData:getMoveUser(moveId)
  349. for _, p in pairs(self.party) do
  350. if not p.egg then
  351. for _, m in pairs(p.moves) do
  352. if m.id == moveId then
  353. return p:getName()
  354. end
  355. end
  356. end
  357. end
  358. end
  359. function PlayerData:getCutter()
  360. if not self.badges[1] then return end
  361. return self:getMoveUser('cut')
  362. end
  363. function PlayerData:getDigger()
  364. return self:getMoveUser('dig')
  365. end
  366. function PlayerData:getHeadbutter()
  367. return self:getMoveUser('headbutt')
  368. end
  369. local rockSmashEncounter
  370. function PlayerData:getSmasher()
  371. if not self.badges[5] then return end
  372. local pName = self:getMoveUser('rocksmash')
  373. if pName then
  374. local model = storage.Models.BrokenRock:Clone()
  375. model.Parent = self.player:WaitForChild('PlayerGui')
  376. local enc
  377. if self:random2(3) == 2 then
  378. if not rockSmashEncounter then
  379. rockSmashEncounter = require(storage.Data.Chunks).rockSmashEncounter
  380. end
  381. enc = rockSmashEncounter
  382. end
  383. return pName, model, enc
  384. end
  385. end
  386. function PlayerData:getClimber()
  387. if not self.badges[6] then return end
  388. return self:getMoveUser('rockclimb')
  389. end
  390.  
  391. function PlayerData:getHappiness()
  392. local p = self:getFirstNonEgg()
  393. if not p then return end
  394. local h = p.happiness
  395. local n = 'Your '..p.name..'...'
  396. if h >= 255 then
  397. return {n, 'It\'s extremely friendly toward you.', 'It couldn\'t possibly love you more.', 'It\'s a pleasure to see!'}
  398. elseif h >= 200 then
  399. return {n, 'It seems to be very happy.', 'It\'s obviously friendly toward you.'}
  400. elseif h >= 150 then
  401. return {n, 'It\'s quite friendly toward you.', 'It seems to want to be babied a little.'}
  402. elseif h >= 100 then
  403. return {n, 'It\'s getting used to you.', 'It seems to believe in you.'}
  404. elseif h >= 50 then
  405. return {n, 'It\'s not very used to you yet.', 'It neither loves nor hates you.'}
  406. elseif h > 0 then
  407. return {n, 'It\'s very wary.', 'It has a scary look in its eyes.', 'It doesn\'t like you much at all.'}
  408. end
  409. return {n, 'This is a little hard for me to say...', 'Your pokemon simply detests you.', 'Doesn\'t that make you uncomfortable?'}
  410. end
  411.  
  412. function PlayerData:getDex()
  413. return self.pokedex
  414. end
  415.  
  416. function PlayerData:getCardInfo()
  417. return {
  418. name = self.trainerName,
  419. dex = select(2, self:countSeenAndOwnedPokemon()),
  420. badges = Utilities.map({1,2,3,4,5,6,7,8}, function(i) return self.badges[i] and 1 or 0 end),
  421. money = self.money,
  422. bp = self.bp
  423. }
  424. end
  425.  
  426. function PlayerData:chooseName(tName)
  427. self.trainerName = tName
  428. end
  429.  
  430. function PlayerData:getBattleTeam(ignoreHPState, teamPreviewOrder) -- todo: connect team preview
  431. if ignoreHPState and teamPreviewOrder then
  432. local team = {}
  433. for teamIndex, partyIndex in pairs(teamPreviewOrder) do
  434. team[teamIndex] = self.party[partyIndex]:getBattleData(true)
  435. end
  436. return team
  437. end
  438.  
  439. local team = {}
  440. local fainted = {}
  441. for _, p in pairs(self.party) do
  442. local d = p:getBattleData(ignoreHPState)
  443. if (ignoreHPState or p.hp > 0) and not p.egg then
  444. table.insert(team, d)
  445. else
  446. table.insert(fainted, d)
  447. end
  448. end
  449. assert(#team > 0, 'No healthy Pokemon')
  450. for _, d in pairs(fainted) do
  451. table.insert(team, d)
  452. end
  453. return team
  454. end
  455.  
  456. function PlayerData:newPokemon(data)
  457. return _f.ServerPokemon:new(data, self)
  458. end
  459.  
  460. function PlayerData:startNewGame()
  461. if self.gameBegan then --[[ERROR]] return false end
  462. self.gameBegan = true
  463.  
  464. self:onGameBegin()
  465. end
  466.  
  467. function PlayerData:continueGame()
  468. if self.gameBegan then --[[ERROR]] return false end
  469. local data, pcData = self:getSaveData()
  470. if not data then --[[ERROR]] return false end
  471. self.gameBegan = true
  472. self.loadedData = nil -- remove cached data
  473.  
  474. local etc = self:deserialize(data)
  475. if pcData then
  476. self:PC_deserialize(pcData)
  477. end
  478. self:onGameBegin()
  479. return true, etc
  480. end
  481.  
  482. function PlayerData:onGameBegin()
  483. if self.gameBeganExtras then return end -- dispatch once
  484. self.gameBeganExtras = true
  485. -- cache game passes that may have been deleted (but the player has the key item for them still)
  486. -- or, if they own the pass but not the key item, give them the key item
  487. for _, passName in pairs({'ShinyCharm', 'AbilityCharm', 'OvalCharm'}) do
  488. local itemId = passName:lower()
  489. if self:getBagDataById(itemId, 5) then
  490. self.ownedGamePassCache[Assets.passId[passName] ] = true
  491. elseif self.ownedGamePassCache[Assets.passId[passName] ] then
  492. self:addBagItems({id = itemId, quantity = 1})
  493. end
  494. end
  495. -- the following passes have a special function to run when purchased, activate them
  496. for _, passName in pairs({'ExpShare', 'MoreBoxes'}) do
  497. local passId = Assets.passId[passName]
  498. if self.ownedGamePassCache[passId] then
  499. self:onAssetPurchased(passId)
  500. end
  501. end
  502. -- let the player know what these initial values are
  503. local firstNonEgg = self:getFirstNonEgg()
  504. if firstNonEgg then
  505. _f.Network:post('PDChanged', self.player,
  506. 'firstNonEggLevel', firstNonEgg.level,
  507. 'firstNonEggAbility', firstNonEgg:getAbilityName(),
  508. 'money', self.money,
  509. 'bp', self.bp)
  510. end
  511. -- etc.
  512. self:checkForHatchables(true)
  513. self:updatePlayerListEntry()
  514. end
  515.  
  516. local shopProducts = {
  517. [Assets.productId.MasterBall] = {id = 'masterball', icon = 87619102}
  518. }
  519. function PlayerData:onDevProductPurchased(id) -- todo: make processreceipt return a response based on this function's response
  520. if not id then return end
  521. local attemptAutosave = false
  522. -- Starter Product
  523. if id == Assets.productId.Starter then
  524. local s = self.starterProductStack
  525. if #s > 0 then
  526. table.remove(s, #s)()
  527. end
  528. -- Ash-Greninja
  529. elseif id == Assets.productId.AshGreninja then
  530. local s = self.ashGreninjaProductStack
  531. if #s > 0 then
  532. table.remove(s, #s)()
  533. end
  534. -- Hoverboard
  535. elseif id == Assets.productId.Hoverboard then
  536. local s = self.hoverboardProductStack
  537. if #s > 0 then
  538. table.remove(s, #s)()
  539. end
  540. -- Lotto Ticket
  541. elseif id == Assets.productId.LottoTicket then
  542. local s = self.lottoticketProductStack
  543. if #s > 0 then
  544. table.remove(s, #s)()
  545. end
  546. -- BP Products
  547. elseif id == Assets.productId.TenBP then
  548. self:addBP(10, true, true)
  549. elseif id == Assets.productId.FiftyBP then
  550. self:addBP(50, true, true)
  551. -- UMV Batter Products
  552. elseif id == Assets.productId.UMV1 then
  553. self:addBagItems({id = 'umvbattery', quantity = 6})
  554. elseif id == Assets.productId.UMV3 then
  555. self:addBagItems({id = 'umvbattery', quantity = 6})
  556. elseif id == Assets.productId.UMV6 then
  557. self:addBagItems({id = 'umvbattery', quantity = 6})
  558. -- Money Products
  559. elseif id == Assets.productId._10kP then
  560. self:addMoney(10000, true)
  561. elseif id == Assets.productId._50kP then
  562. self:addMoney(50000, true)
  563. elseif id == Assets.productId._100kP then
  564. self:addMoney(100000, true)
  565. elseif id == Assets.productId._200kP then
  566. self:addMoney(200000, true)
  567. -- Stamp Spinner Products
  568. elseif id == Assets.productId.PBSpins1 then
  569. self.stampSpins = math.min(999, self.stampSpins + 5)
  570. _f.Network:post('uPBSpins', self.player, self.stampSpins)
  571. attemptAutosave = true
  572. elseif id == Assets.productId.PBSpins5 then
  573. self.stampSpins = math.min(999, self.stampSpins + 10)
  574. _f.Network:post('uPBSpins', self.player, self.stampSpins)
  575. attemptAutosave = true
  576. elseif id == Assets.productId.PBSpins10 then
  577. self.stampSpins = math.min(999, self.stampSpins + 50)
  578. _f.Network:post('uPBSpins', self.player, self.stampSpins)
  579. attemptAutosave = true
  580. else
  581. -- Shop Products
  582. local shopItem = shopProducts[id]
  583. if shopItem then
  584. local item = _f.Database.ItemById[shopItem.id]
  585. self:addBagItems({num = item.num, quantity = shopItem.qty or 1})
  586. _f.Network:post('ItemProductPurchased', self.player, item.name, shopItem.icon)
  587. else
  588. -- RO-Power Products
  589. for g, list in pairs(Assets.productId.RoPowers) do
  590. for l, pId in pairs(list) do
  591. if pId == id then
  592. -- print('RO POWER PURCHASED')
  593. _f.Network:post('rpActivate', self.player, g, l, RO_POWER_EFFECT_DURATION)
  594. self:ROPowers_setTimePurchasedAndLevelForPower(g, os.time(), l)
  595. -- -- auto-save just the ro-power data
  596. self:ROPowers_save()
  597. -- local s = pcall(function()
  598. -- local buffer = BitBuffer.Create()
  599. -- for i = 1, 7 do
  600. -- buffer:WriteBool(self.roPowers.powerLevel[i] == 2)
  601. -- buffer:WriteFloat64(self.roPowers.lastPurchasedAt[i])
  602. -- end
  603. -- _f.DataPersistence.ROPowerSave(self.player, 'save', buffer:ToBase64())
  604. -- end)
  605. -- if not s then warn('RO-Power autosave failed') end
  606. --
  607. break
  608. end
  609. end
  610. end
  611. end
  612. end
  613. if attemptAutosave then
  614. -- attempt an autosave of the received stamp & used spin
  615. spawn(function()
  616. if self.lastSaveEtc then
  617. self:saveGame(self.lastSaveEtc)
  618. end
  619. end)
  620. end
  621. end
  622.  
  623. function PlayerData:onAssetPurchased(id) -- keep in mind this will be called at least once every session after the pass is purchased (protect it from multi-awarding)
  624. if id == Assets.passId.ExpShare then
  625. if not self:getBagDataById('expshare', 5) then
  626. self:addBagItems({id = 'expshare', quantity = 1})
  627. _f.Network:post('PDChanged', self.player, 'expShareOn', true) -- when initially given, automatically turn it on
  628. end
  629. elseif id == Assets.passId.MoreBoxes then
  630. if self.pc.maxBoxes == 8 then
  631. self.pc.maxBoxes = 50
  632. _f.Network:post('PCPassPurchased', self.player)
  633. end
  634. end
  635. end
  636.  
  637. function PlayerData:completeEvent(eventName, ...)
  638. if self.completedEvents[eventName] then return false end
  639. local event = _f.PlayerEvents[eventName]
  640. if not event then return false end
  641. local r = event
  642. local pseudo = false -- pseudo-events do not store to PlayerData
  643. if type(event) == 'function' then
  644. r = event(self, ...)
  645. elseif type(event) == 'table' then
  646. if event.manual then return false end
  647. if event.pseudo then pseudo = true end
  648. if event.callback then
  649. r = event.callback(self, ...)
  650. end
  651. -- todo: continue to fill cases
  652. end
  653. if r ~= false and not pseudo then
  654. self.completedEvents[eventName] = true
  655. end
  656. return r
  657. end
  658.  
  659. function PlayerData:completeEventServer(eventName, ...)
  660. if self.completedEvents[eventName] then return false end
  661. local event = _f.PlayerEvents[eventName]
  662. if event == nil then return false end
  663. local r = event
  664. if type(event) == 'function' then
  665. r = event(self, ...)
  666. elseif type(event) == 'table' then
  667. -- todo: other cases where server is concerned with the data in the table
  668. if type(event.pseudo) == 'function' and event.pseudo(self) then return false end
  669. if event.callback then
  670. r = event.callback(self, ...)
  671. end
  672. elseif r == false then
  673. r = nil
  674. end
  675. if r ~= false then
  676. self.completedEvents[eventName] = true
  677. _f.Network:post('eventCompleted', self.player, eventName) -- notify client
  678. end
  679. return r
  680. end
  681.  
  682. function PlayerData:giveStoryAbsol(slot)
  683. local hadSeenAbsol = self:hasSeenPokemon( 359)
  684. local hadOwnedAbsol = self:hasOwnedPokemon(359)
  685. local absol = self:newPokemon {
  686. name = 'Absol',
  687. level = 60,
  688. shinyChance = 40,
  689. item = 534,-- Absolite
  690. moves = {{id = 'nightslash'},{id = 'psychocut'},{id = 'megahorn'},{id = 'detect'}}
  691. }
  692. local box, position
  693. if slot then
  694. box, position = self:PC_sendToStore(table.remove(self.party, slot), true)
  695. end
  696. table.insert(self.party, 1, absol)
  697. self:onOwnPokemon(359)
  698. self.absolMeta = {
  699. slot = slot, box = box, position = position,
  700. seen = hadSeenAbsol,
  701. owned = hadOwnedAbsol
  702. }
  703. end
  704.  
  705. function PlayerData:undoGiveStoryAbsol()
  706. self:incrementBagItem('megakeystone', -1)
  707. self.flags.gotAbsol = nil
  708. if self.party[1].name == 'Absol' then
  709. table.remove(self.party, 1)
  710. end
  711. local meta = self.absolMeta
  712. if not meta then return end
  713. self.absolMeta = nil
  714. local slot, box, position = meta.slot, meta.box, meta.position
  715. if slot and box and position then
  716. table.insert(self.party, slot, _f.ServerPokemon:deserialize(self.pc.boxes[box][position][3], self))
  717. self.pc.boxes[box][position] = nil
  718. end
  719. if not meta.seen then self:unseePokemon(359) end
  720. if not meta.owned then self:unownPokemon(359) end
  721. end
  722.  
  723. function PlayerData:getStarterData()
  724. local starters = {} do
  725. for i, v in pairs({ -- starters are listed in 3 places: here, PlayerData:buyStarter (just below), and PlayerEvents.ChooseFirstPokemon
  726. 'Bulbasaur', 'Charmander', 'Squirtle',
  727. 'Chikorita', 'Cyndaquil', 'Totodile',
  728. 'Treecko', 'Torchic', 'Mudkip',
  729. 'Turtwig', 'Chimchar', 'Piplup',
  730. 'Snivy', 'Tepig', 'Oshawott',
  731. 'Chespin', 'Fennekin', 'Froakie',
  732. 'Rowlet', 'Litten', 'Popplio',
  733. }) do
  734. starters[i] = {v, _f.Database.GifData._FRONT[v]}
  735. end
  736. end
  737. return starters
  738. end
  739.  
  740. function PlayerData:buyStarter(species)
  741. local valid = {
  742. Bulbasaur = true, Charmander = true, Squirtle = true,
  743. Chikorita = true, Cyndaquil = true, Totodile = true,
  744. Treecko = true, Torchic = true, Mudkip = true,
  745. Turtwig = true, Chimchar = true, Piplup = true,
  746. Snivy = true, Tepig = true, Oshawott = true,
  747. Chespin = true, Fennekin = true, Froakie = true,
  748. Rowlet = true, Litten = true, Popplio = true,
  749. }
  750. if not species or not valid[species] then return false end
  751. local sendToPC = false
  752. local processed = false
  753. local pokemon
  754. table.insert(self.starterProductStack, function()
  755. if processed then return end
  756. processed = true
  757. pokemon = self:newPokemon {
  758. name = species,
  759. level = 5,
  760. shinyChance = 2,
  761. }
  762. if sendToPC then
  763. self:PC_sendToStore(pokemon)
  764. return
  765. end
  766. -- defer storage until after nickname
  767. end)
  768. game:GetService('MarketplaceService'):PromptProductPurchase(self.player, Assets.productId.Starter)
  769. for i = 1, 40 do
  770. wait(.5)
  771. if processed then break end
  772. end
  773. if not processed then
  774. -- timed out
  775. sendToPC = true
  776. return 'to'
  777. end
  778. if pokemon then
  779. return {
  780. d = self:createDecision {
  781. callback = function(_, nickname)
  782. if type(nickname) == 'string' then
  783. pokemon:giveNickname(nickname)
  784. end
  785. local box = self:caughtPokemon(pokemon)
  786. if box then
  787. return pokemon:getName() .. ' has been transferred to Box ' .. box .. '!'
  788. end
  789. end
  790. },
  791. i = pokemon:getIcon(),
  792. s = pokemon.shiny
  793. }
  794. end
  795. -- is there a condition that reaches here?
  796. end
  797.  
  798. function PlayerData:buyAshGreninja()
  799. if #self.party > 5 then return 'fp' end
  800. local sendToPC = false
  801. local processed = false
  802. local pokemon
  803. table.insert(self.ashGreninjaProductStack, function()
  804. if processed then return end
  805. processed = true
  806. pokemon = self:newPokemon {
  807. name = 'Greninja',
  808. forme = 'bb',
  809. level = 36,
  810. shinyChance = 1024,
  811. ot = 12301,
  812. moves = {
  813. {id = 'watershuriken'},{id = 'aerialace'},
  814. {id = 'doubleteam'}, {id = 'nightslash'}
  815. }
  816. }
  817. if sendToPC then
  818. -- processed after timeout, store without nicknaming
  819. self:PC_sendToStore(pokemon)
  820. return
  821. end
  822. -- defer storage until after nickname
  823. end)
  824. game:GetService('MarketplaceService'):PromptProductPurchase(self.player, Assets.productId.AshGreninja)
  825. for i = 1, 40 do
  826. wait(.5)
  827. if processed then break end
  828. end
  829. if not processed then
  830. -- timed out
  831. sendToPC = true
  832. return 'to'
  833. end
  834. if pokemon then
  835. return {
  836. d = self:createDecision {
  837. callback = function(_, nickname)
  838. if type(nickname) == 'string' then
  839. pokemon:giveNickname(nickname)
  840. end
  841. local box = self:caughtPokemon(pokemon)
  842. if box then
  843. return pokemon:getName() .. ' has been transferred to Box ' .. box .. '!'
  844. end
  845. end
  846. },
  847. i = pokemon:getIcon(),
  848. s = pokemon.shiny
  849. }
  850. end
  851. end
  852.  
  853. function PlayerData:completedEggCycle()
  854. -- my fastest egg step completion was approx. 39.4 sec
  855. -- THIS MAY NO LONGER BE THE CASE WHEN WE RELEASE HOVERBOARDS
  856. -- reject & report anything faster than 30 seconds
  857. local now = tick()
  858. local duration = tick()-self.lastCompletedEggCycle
  859. local maxStepTime = (self.currentHoverboard~='' and self.hoverboardModel) and (self.currentHoverboard:sub(1,6)=='Basic ' and 20 or 15) or 30
  860. if duration < maxStepTime then--30 then
  861. -- TODO
  862. return
  863. end
  864. self.lastCompletedEggCycle = now
  865.  
  866. local party = self.party
  867. self:Daycare_tryBreed()
  868. local reduceBy = 1
  869. for _, p in pairs(party) do
  870. local a = p:getAbilityName()
  871. if not p.egg and (a == 'Flame Body' or a == 'Magma Armor') then
  872. reduceBy = 2
  873. break
  874. end
  875. end
  876. reduceBy = reduceBy * (1 + self:ROPowers_getPowerLevel(2))
  877. for _, p in pairs(party) do
  878. if p.egg then
  879. if not p.fossilEgg then
  880. p.eggCycles = p.eggCycles - reduceBy
  881. end
  882. else
  883. p:addHappiness(2, 2, 1)
  884. end
  885. end
  886. self:checkForHatchables()
  887. -- add 256 Exp. to Pokemon in the Day Care
  888. for _, p in pairs(self.daycare.depositedPokemon) do
  889. p.experience = p.experience + 256
  890. end
  891. end
  892.  
  893. function PlayerData:rearrangeParty(indices)
  894. if self:isInBattle() then return end
  895. local nParty = #self.party
  896. if #indices ~= nParty then return end
  897. local ii = {}
  898. local vv = {}
  899. for i, v in pairs(indices) do
  900. if type(i) ~= 'number' or i > nParty or type(v) ~= 'number' or v > nParty then return end
  901. if ii[i] or vv[v] then return end -- clone attempt
  902. ii[i] = true
  903. vv[v] = true
  904. end
  905. for i = 1, nParty do if not ii[i] or not vv[i] then return end end
  906. local party = {}
  907. for i = 1, nParty do
  908. party[i] = self.party[indices[i]]
  909. end
  910. self.party = party
  911. local firstNonEgg = self:getFirstNonEgg()
  912. _f.Network:post('PDChanged', self.player, 'firstNonEggLevel', firstNonEgg.level,
  913. 'firstNonEggAbility', firstNonEgg:getAbilityName())
  914. end
  915.  
  916. function PlayerData:getBattleBag()
  917. if not self:isInBattle() then return end
  918. local bags = {{},{},{}}
  919. for n = 1, 4 do
  920. for _, bd in pairs(self.bag[n]) do
  921. local item = _f.Database.ItemByNumber[bd.num]
  922. if item and item.battleCategory then
  923. table.insert(bags[item.battleCategory], {
  924. id = item.id,
  925. name = item.name,
  926. icon = item.icon or item.num,
  927. qty = bd.quantity,
  928. desc = item.desc,
  929. bUse = item.isPokeball or type(item.onUse) == 'function',
  930. bCat = item.battleCategory
  931. })
  932. end
  933. end
  934. end
  935. return bags
  936. end
  937.  
  938. function PlayerData:getBagDataForTransfer(item, bd, context) -- helper function
  939. local itemId = item.id
  940. local canUse
  941. local usableItemClient = UsableItemsClient[itemId]
  942. if not usableItemClient or not usableItemClient.canUse then
  943. local usableItemServer = _f.UsableItems[itemId]
  944. if usableItemServer then
  945. local s_canUse = usableItemServer.canUse
  946. if s_canUse then
  947. if type(s_canUse) == 'function' then
  948. canUse = {}
  949. for i, p in pairs(self.party) do
  950. canUse[tostring(i)] = s_canUse(p) -- stupid table limitations...
  951. end
  952. else
  953. canUse = s_canUse
  954. end
  955. end
  956. end
  957. end
  958. return {
  959. id = itemId,
  960. name = item.name,
  961. icon = item.icon or item.num,
  962. qty = (item.bagCategory~=5 or item.showsQuantity) and bd.quantity or nil,
  963. desc = item.desc,
  964. canUse = canUse, -- true or false or a table of true/false (1 for each pokemon in party)
  965. -- ^ exists when UsableItemsServer has a canUse function but UsableItemsClient doesn't
  966.  
  967. sell = (context=='sell' and item.sellPrice or nil),
  968. }
  969. end
  970.  
  971. function PlayerData:getBagPouch(n, context)
  972. local pouch = {}
  973. local count = 0
  974. for _, bd in pairs(self.bag[n]) do
  975. local item = _f.Database.ItemByNumber[bd.num]
  976. count = count + 1
  977. pouch[count] = self:getBagDataForTransfer(item, bd, context)
  978. end
  979. return pouch
  980. end
  981.  
  982. function PlayerData:getTMs()
  983. local list = {}
  984.  
  985. local partyKnownMoves = {}
  986. local partyLearnedMachines = {}
  987. for i, p in pairs(self.party) do
  988. local k = {}
  989. local l = {}
  990. if not p.egg then
  991. for _, move in pairs(p:getMoves()) do
  992. k[move.num] = true
  993. end
  994. pcall(function()
  995. for _, num in pairs(p:getLearnedMoves().machine) do
  996. l[num] = true
  997. end
  998. end)
  999. end
  1000. partyKnownMoves[i] = k
  1001. partyLearnedMachines[i] = l
  1002. end
  1003.  
  1004. local buffer = BitBuffer.Create()
  1005. local function add(str, isHMs)
  1006. buffer:FromBase64(str)
  1007. local data = _f.Database.Machines[isHMs and 'hms' or 'tms']
  1008. for m = 1, str:len()*6 do
  1009. if buffer:ReadBool() then
  1010. local moveId = data[m]
  1011. local move = _f.Database.MoveById[moveId]
  1012. local moveNum = move.num
  1013. local canLearn = {}
  1014. for i, p in pairs(self.party) do
  1015. canLearn[i] = (partyKnownMoves[i][moveNum] and 2) or (partyLearnedMachines[i][moveNum] and 1) or 0
  1016. end
  1017. list[#list+1] = {
  1018. mName = move.name,
  1019. num = m,
  1020. hm = isHMs,
  1021. type = move.type,
  1022. desc = move.category..', '..move.type..'-type, '..(move.basePower or 0)..' Power,\n'..(move.accuracy==true and '--' or ((move.accuracy or 0)..'%'))..' Accuracy'..((move.desc and move.desc~='') and ('. Effect: '..move.desc) or ''),
  1023. learn = canLearn
  1024. }
  1025. end
  1026. end
  1027. end
  1028. add(self.tms)
  1029. add(self.hms, true)
  1030.  
  1031. return list
  1032. end
  1033.  
  1034. function PlayerData:teachTM(pokemonIndex, tmNum, isHM)
  1035. -- verify arguments
  1036. local moveId; pcall(function() moveId = _f.Database.Machines[isHM and 'hms' or 'tms'][tmNum] end)
  1037. local pokemon; pcall(function() pokemon = self.party[pokemonIndex] end)
  1038. if not moveId or not pokemon or pokemon.egg then return false end
  1039. -- verify player owns TM/HM
  1040. if not BitBuffer.GetBit(isHM and self.hms or self.tms, tmNum) then return false end
  1041. -- verify pokemon can learn TM/HM
  1042. local canLearn = false
  1043. pcall(function()
  1044. local moveNum = _f.Database.MoveById[moveId].num
  1045. for _, num in pairs(pokemon:getLearnedMoves().machine) do
  1046. if num == moveNum then
  1047. canLearn = true
  1048. break
  1049. end
  1050. end
  1051. end)
  1052. if not canLearn then return false end
  1053. -- verify pokemon doesn't already know the move
  1054. for _, move in pairs(pokemon.moves) do
  1055. if move.id == moveId then
  1056. return false
  1057. end
  1058. end
  1059. -- learn immediately if there is space
  1060. if #pokemon.moves < 4 then
  1061. pokemon.moves[#pokemon.moves+1] = {id = moveId}
  1062. return true
  1063. end
  1064. -- gather data about known moves and the move to learn
  1065. local moves = {}
  1066. local function add(move)
  1067. moves[#moves+1] = {
  1068. name = move.name,
  1069. category = move.category,
  1070. type = move.type,
  1071. power = move.basePower,
  1072. accuracy = move.accuracy,
  1073. pp = move.pp,
  1074. desc = move.desc
  1075. }
  1076. end
  1077. for _, move in pairs(pokemon.moves) do
  1078. if move.id == moveId then return false end -- make sure move is not already known
  1079. add(_f.Database.MoveById[move.id])
  1080. end
  1081. add(_f.Database.MoveById[moveId])
  1082. -- send data & new decision id to player
  1083. return moves, self:createDecision {
  1084. callback = function(_, moveSlot)
  1085. if type(moveSlot) ~= 'number' or moveSlot < 1 or moveSlot > 4 then return end
  1086. pokemon.moves[math.floor(moveSlot)] = {id = moveId}
  1087. end
  1088. }
  1089. end
  1090.  
  1091. function PlayerData:useItem(itemId, targetIndex)
  1092. if not itemId or type(itemId) ~= 'string' then return false end
  1093. local usableItemServer = _f.UsableItems[itemId]
  1094. local usableItemClient = UsableItemsClient[itemId]
  1095. -- .noTarget and .nonConsumable are preferred to be placed on the client's usableItem (or else the client will be confused
  1096. local hasTarget = not ((usableItemServer and usableItemServer.noTarget) or (usableItemClient and usableItemClient.noTarget))
  1097. local consume = not ((usableItemServer and usableItemServer.nonConsumable) or (usableItemClient and usableItemClient.nonConsumable))
  1098. if (targetIndex ~= nil) ~= (hasTarget and true or false) then return false end
  1099. local target
  1100. if hasTarget then
  1101. target = self.party[targetIndex]
  1102. if not target then return false end
  1103. end
  1104. local item = _f.Database.ItemById[itemId]
  1105. if not item then return false end
  1106. local bd = self:getBagDataByNum(item.num)
  1107. if not bd or not bd.quantity or bd.quantity < 1 then return false end
  1108. local used
  1109. if usableItemServer and usableItemServer.onUse then
  1110. used = usableItemServer.onUse(target)
  1111. if used == false then return false end
  1112. end
  1113. if consume then
  1114.  
  1115. local _, bd = self:incrementBagItem(item.num, -1) -- qty verified above
  1116. if itemId:match('repel$') then -- repels report whether there are any remaining
  1117. return (bd and bd.quantity and bd.quantity > 0) and 1 or 0
  1118. end
  1119. end
  1120. return used, (target and target:getPartyData({}))
  1121. end
  1122.  
  1123. function PlayerData:giveItem(itemId, pokemonIndex)
  1124. if not itemId or type(itemId) ~= 'string' or not pokemonIndex or type(pokemonIndex) ~= 'number' then return false end
  1125. local item = _f.Database.ItemById[itemId]
  1126. local pokemon = self.party[pokemonIndex]
  1127. if not item or not pokemon or pokemon.egg then return false end
  1128. if not item.bagCategory or item.bagCategory > 4 then return false end -- check whether it can even be held
  1129. if not self:incrementBagItem(item.num, -1) then return false end
  1130. local taking = pokemon:getHeldItem()
  1131. local takenBD
  1132. if taking.num then
  1133. local s, r = self:incrementBagItem(taking.num, 1)
  1134. if s then takenBD = r end
  1135. end
  1136. pokemon.item = item.num
  1137. return true, (takenBD and self:getBagDataForTransfer(taking, takenBD)), (takenBD and taking.bagCategory)
  1138. end
  1139.  
  1140. function PlayerData:takeItem(pokemonIndex)
  1141. if not pokemonIndex or type(pokemonIndex) ~= 'number' then return false end
  1142. local pokemon = self.party[pokemonIndex]
  1143. if not pokemon or pokemon.egg then return false end
  1144. local item = pokemon:getHeldItem()
  1145. if not item.num then return false end
  1146. local s, bd = self:incrementBagItem(item.num, 1)
  1147. if not s then return false end
  1148. pokemon.item = nil
  1149. return true, self:getBagDataForTransfer(item, bd), item.bagCategory
  1150. end
  1151.  
  1152. function PlayerData:tossItem(itemId, amount)
  1153. if not itemId or type(itemId) ~= 'string' or not amount or type(amount) ~= 'number' or amount < 1 then return false end
  1154. local item = _f.Database.ItemById[itemId]
  1155. if not item or not item.bagCategory or item.bagCategory > 4 or itemId == 'masterball' then return false end -- check whether it can be tossed
  1156. if not self:incrementBagItem(item.num, -amount) then return false end
  1157. return true
  1158. end
  1159.  
  1160. function PlayerData:deleteMove(pokemonIndex)
  1161. if not pokemonIndex or not self.party[pokemonIndex] then return end
  1162. local pokemon = self.party[pokemonIndex]
  1163. if pokemon.egg then return 0, 'eg' end
  1164. if #pokemon.moves == 0 then return 0, '0m' end
  1165. if #pokemon.moves == 1 then return pokemon.name, '1m' end
  1166. return pokemon.name, {
  1167. moves = pokemon:getCurrentMovesData(),
  1168. d = self:createDecision {
  1169. callback = function(_, moveslot)
  1170. if not moveslot or not pokemon.moves[moveslot] then return end
  1171. table.remove(pokemon.moves, moveslot)
  1172. end
  1173. }
  1174. }
  1175. end
  1176.  
  1177. function PlayerData:remindMove()
  1178. local heartscale = _f.Database.ItemById.heartscale
  1179. local nHeartScales = 0
  1180. pcall(function() nHeartScales = self:getBagDataByNum(heartscale.num, 1).quantity end)
  1181. return {
  1182. hsi = heartscale.icon or heartscale.num,
  1183. nhs = nHeartScales,
  1184. money = self.money,
  1185. d = self:createDecision {
  1186. callback = function(_, pokemonIndex)
  1187. if not pokemonIndex or not self.party[pokemonIndex] then return end
  1188. local pokemon = self.party[pokemonIndex]
  1189. if pokemon.egg then return 0, 'eg' end
  1190.  
  1191. local learnedMoves
  1192. pcall(function() learnedMoves = pokemon:getLearnedMoves().levelUp end)
  1193. local moves = {}
  1194. if learnedMoves then
  1195. -- get moves by level (earliest learned to latest learned)
  1196. local level = pokemon.level
  1197. for _, d in pairs(learnedMoves) do
  1198. if level < d[1] then break end
  1199. for i = 2, #d do
  1200. table.insert(moves, d[i])
  1201. end
  1202. end
  1203. -- remove duplicate moves
  1204. for i, move in pairs(moves) do
  1205. for j = #moves, i+1, -1 do
  1206. if move == moves[j] then
  1207. table.remove(moves, j)
  1208. end
  1209. end
  1210. end
  1211. -- remove currently known moves
  1212. for _, move in pairs(pokemon:getMoves()) do
  1213. for j = #moves, 1, -1 do
  1214. if move.num == moves[j] then
  1215. table.remove(moves, j)
  1216. break
  1217. end
  1218. end
  1219. end
  1220. end
  1221. if #moves == 0 then return pokemon.name, 'nm' end
  1222. local validMovesNumToId = {}
  1223. for i, moveNum in pairs(moves) do
  1224. local move = _f.Database.MoveByNumber[moveNum]
  1225. moves[i] = {
  1226. num = move.num,
  1227. name = move.name,
  1228. category = move.category,
  1229. type = move.type,
  1230. power = move.basePower,
  1231. accuracy = move.accuracy,
  1232. pp = move.pp,
  1233. desc = move.desc
  1234. }
  1235. validMovesNumToId[moveNum] = move.id
  1236. end
  1237.  
  1238. return pokemon.name, {
  1239. nn = pokemon:getName(),
  1240. known = pokemon:getCurrentMovesData(),
  1241. moves = moves,
  1242. d = self:createDecision {
  1243. callback = function(_, paymentMethod, moveNum, moveSlot)
  1244. if (paymentMethod ~= 1 and paymentMethod ~= 2)
  1245. or (moveSlot ~= 1 and moveSlot ~= 2 and moveSlot ~= 3 and moveSlot ~= 4) then
  1246. return
  1247. end
  1248. local moveId = validMovesNumToId[moveNum]
  1249. if not moveId then return end
  1250. if paymentMethod == 1 then
  1251. if not (self:incrementBagItem(heartscale.num, -1)) then return end
  1252. else
  1253. if not (self:addMoney(-30000)) then return end
  1254. end
  1255. pokemon.moves[moveSlot] = {id = moveId}
  1256. end
  1257. }
  1258. }
  1259. end
  1260. }
  1261. }
  1262. end
  1263.  
  1264. local getShop = require(script.GetShop)
  1265. function PlayerData:getShop(shopId)
  1266. local items, other = getShop(self, shopId)
  1267. if not items then return false end
  1268. self.currentShop = items
  1269. return items, other
  1270. end
  1271.  
  1272. function PlayerData:maxBuyInternal(itemId)
  1273. if not self.currentShop then return false end
  1274. pcall(function() itemId = Utilities.rc4(itemId) end)
  1275. if type(itemId) ~= 'string' then return false end
  1276. local item = _f.Database.ItemById[itemId]
  1277. if not item then return false end
  1278. local price
  1279. for _, l in pairs(self.currentShop) do
  1280. if Utilities.rc4(l[1]) == itemId then
  1281. price = l[2]
  1282. break
  1283. end
  1284. end
  1285. if not price then return false end
  1286. local currentQty = 0
  1287. local bd = self:getBagDataByNum(item.num)
  1288. if bd then
  1289. currentQty = bd.quantity or 0
  1290. end
  1291. if currentQty >= 99 then return 'fb' end -- full bag
  1292. if self.money < price then return 'nm' end -- not enough money
  1293. return math.min(99-currentQty, math.floor(self.money/price)), item, price
  1294. end
  1295. function PlayerData:maxBuy(itemId) -- rc4'd (from client)
  1296. return (self:maxBuyInternal(itemId)) -- return single value to client
  1297. end
  1298.  
  1299. function PlayerData:buyItem(itemId, qty) -- rc4'd
  1300. local max, item, price = self:maxBuyInternal(itemId)
  1301. if type(max) ~= 'number' or not item or not price or qty > max or qty < 1 then return false end
  1302. qty = math.floor(qty)
  1303. if not self:addMoney(-price*qty) then return false end
  1304. self:addBagItems{num = item.num, quantity = qty}
  1305. local givePremierBall = false
  1306. if item.isPokeball and qty > 9 then
  1307. self:addBagItems{id = 'premierball', quantity = 1}
  1308. givePremierBall = true
  1309. end
  1310. return true, givePremierBall
  1311. end
  1312.  
  1313. function PlayerData:bMaxBuyInternal(shopIndex)
  1314. if not self.currentShop then return false end
  1315. local itemIdPricePair = self.currentShop[shopIndex]
  1316. if type(itemIdPricePair) ~= 'table' then return false end
  1317. local itemId = itemIdPricePair[1]
  1318. if type(itemId) ~= 'string' then return false end
  1319. local price = itemIdPricePair[2]
  1320. if type(price) ~= 'number' then return false end
  1321. if itemId:sub(1, 2) == 'BP' then return false end -- assumption: no items sold here later will start with "BP"
  1322. local tmNum = itemId:match('^TM(%d+)')
  1323. if tmNum then
  1324. tmNum = tonumber(tmNum)
  1325. if BitBuffer.GetBit(self.tms, tmNum) then return 'ao' end -- already own
  1326. if self.bp < price then return 'nm' end
  1327. return 'tm', tonumber(tmNum), price
  1328. end
  1329. local item = _f.Database.ItemById[itemId]
  1330. if not item then return false end
  1331. local currentQty = 0
  1332. local bd = self:getBagDataByNum(item.num)
  1333. if bd then
  1334. currentQty = bd.quantity or 0
  1335. end
  1336. if currentQty >= 99 then return 'fb' end -- full bag
  1337. if self.bp < price then return 'nm' end -- not enough money
  1338. return math.min(99-currentQty, math.floor(self.bp/price)), item, price
  1339. end
  1340. function PlayerData:bMaxBuy(shopIndex)
  1341. return (self:bMaxBuyInternal(shopIndex))
  1342. end
  1343.  
  1344. function PlayerData:buyWithBP(shopIndex, qty)
  1345. local max, item, price = self:bMaxBuyInternal(shopIndex)
  1346. if max == 'tm' then
  1347. self:obtainTM(item)
  1348. self.bp = self.bp - price
  1349. return true, self.bp
  1350. end
  1351. if not item or type(max) ~= 'number' or type(qty) ~= 'number' or max < qty or qty < 1 then return false end
  1352. qty = math.floor(qty)
  1353. self.bp = self.bp - price*qty
  1354. self:addBagItems{num = item.num, quantity = qty}
  1355. return true, self.bp
  1356. end
  1357.  
  1358. function PlayerData:sellItem(itemId, qty) -- NOT rc4'd
  1359. if type(itemId) ~= 'string' or type(qty) ~= 'number' or qty < 1 then return false end
  1360. local item = _f.Database.ItemById[itemId]
  1361. if not item or not item.sellPrice then return false end
  1362. local bd = self:getBagDataByNum(item.num)
  1363. qty = math.floor(qty)
  1364. if not bd or not bd.quantity or bd.quantity < 1 or qty > bd.quantity then return false end
  1365. if not self:addMoney(qty*item.sellPrice) then return 'fw' end
  1366. self:incrementBagItem(item.num, -qty)
  1367. return self.money
  1368. end
  1369.  
  1370. function PlayerData:obtainItem(id)
  1371. if not self.currentObtainableItems then return end
  1372. local item = self.currentObtainableItems[id]
  1373. if not item then return end
  1374. self.currentObtainableItems[id] = nil -- no repeat obtains
  1375. if type(item) == 'number' then
  1376. -- TM
  1377. self:obtainTM(item)
  1378. return 'TM'..(item<10 and '0' or '')..item
  1379. elseif type(item) == 'table' then
  1380. -- item
  1381. local oin = item[2]
  1382. item = _f.Database.ItemById[item[1] ]
  1383. if not item then return end
  1384. self.obtainedItems = BitBuffer.SetBit(self.obtainedItems, oin, true)
  1385. self:addBagItems({num = item.num, quantity = 1})
  1386. return item.name
  1387. end
  1388. end
  1389.  
  1390. function PlayerData:makeDecision(id, ...)
  1391. if not id or type(id) ~= 'number' then return false end
  1392. local data = self.decision_data[id]
  1393. if not data then return false end
  1394. local ret = {data.callback(data, ...)}
  1395. if ret[1] == false then return false end
  1396. self.decision_data[id] = false
  1397. return unpack(ret)
  1398. end
  1399.  
  1400. function PlayerData:openPC()
  1401. if self.pcSession then
  1402. self.pcSession:close()
  1403. end
  1404. if self:isInBattle() then return end
  1405. local newSession = _f.PCService:new(self)
  1406. self.pcSession = newSession
  1407. return newSession:getStartPacket()
  1408. end
  1409.  
  1410. function PlayerData:cPC(fn, ...)
  1411. if type(fn) ~= 'string' then return end
  1412. local pc = self.pcSession
  1413. if not pc or not pc.public[fn] then return end
  1414. return pc[fn](pc, ...)
  1415. end
  1416.  
  1417. function PlayerData:closePC(id, ch)
  1418. local pc = self.pcSession
  1419. if not pc then return end
  1420. if id and pc.id ~= id then return end
  1421. local ret = pc:close(ch)
  1422. self.pcSession = nil
  1423. return ret
  1424. end
  1425.  
  1426.  
  1427.  
  1428.  
  1429.  
  1430.  
  1431.  
  1432.  
  1433.  
  1434.  
  1435.  
  1436.  
  1437. function PlayerData:createDecision(data)
  1438. assert(data.callback ~= nil, 'decision must include a callback')
  1439. local id = self.decision_count + 1
  1440. self.decision_count = id
  1441. self.decision_data[id] = data
  1442. return id
  1443. end
  1444.  
  1445. function PlayerData:checkForHatchables(forceClear)
  1446. -- make sure that there isn't a queued hatch waiting
  1447. for i, d in pairs(self.decision_data) do -- note that d can be `false`
  1448. if d and d.hatch then
  1449. if forceClear then
  1450. self.decision_data[i] = false
  1451. else
  1452. return
  1453. end
  1454. end
  1455. end
  1456. -- check for hatchable egg in party
  1457. for _, p in pairs(self.party) do
  1458. if p.egg and not p.fossilEgg and p.eggCycles <= 0 then
  1459. local id = self:createDecision {
  1460. hatch = true,
  1461. callback = function(data, nickname)
  1462. -- hatch pokemon
  1463. self:onOwnPokemon(p.num)
  1464. p.egg = nil
  1465. p.ot = self.userId
  1466. if nickname and type(nickname) == 'string' then
  1467. p:giveNickname(nickname)
  1468. end
  1469. -- check for another hatchable
  1470. self:checkForHatchables(true)
  1471. end
  1472. }
  1473. -- send event to player
  1474. _f.Network:post('hatch', self.player, {
  1475. d_id = id,
  1476. eggIcon = p:getIcon(),
  1477. pSprite = p:getSprite(true),
  1478. pName = p.data.baseSpecies or p.data.species,
  1479. pIcon = p:getIcon(true),
  1480. pShiny = p.shiny and true or false
  1481. })
  1482. -- only allow one at a time
  1483. return
  1484. end
  1485. end
  1486. end
  1487.  
  1488. function PlayerData:resetFishStreak()
  1489. self.fishingStreak = 0
  1490. end
  1491.  
  1492. function PlayerData:getRegion()
  1493. -- not perfect, just gives a best guess (can only be depended on when player is assumed to be outdoors)
  1494. if not self.currentChunk then return end
  1495. local chunkData = _f.Database.ChunkData[self.currentChunk]
  1496. if chunkData then
  1497. local onlyRegion
  1498. for name in pairs(chunkData.regions) do
  1499. if not onlyRegion then
  1500. onlyRegion = name
  1501. else
  1502. onlyRegion = nil
  1503. break
  1504. end
  1505. end
  1506. if onlyRegion then return onlyRegion end
  1507. end
  1508. local map = storage.MapChunks:FindFirstChild(self.currentChunk)
  1509. if not map then return end
  1510. local regions = map:FindFirstChild('Regions')
  1511. if not regions then return end
  1512. local pos; pcall(function() pos = self.player.Character.HumanoidRootPart.Position end)
  1513. if not pos then return end
  1514. for _, part in pairs(regions:GetChildren()) do
  1515. if part:IsA('BasePart') then
  1516. if Region.FromPart(part):CastPoint(pos) then
  1517. return part.Name
  1518. end
  1519. end
  1520. end
  1521. end
  1522.  
  1523.  
  1524. function PlayerData:addMoney(amount)
  1525. if amount < 0 and self.money+amount < 0 then return false end
  1526. if amount > 0 and self.money > MAX_MONEY then return false end
  1527. self.money = math.min(self.money + amount, MAX_MONEY)
  1528. _f.Network:post('PDChanged', self.player, 'money', self.money)
  1529. return true
  1530. end
  1531.  
  1532. function PlayerData:addBP(amount, showGui)
  1533. self.bp = math.min(self.bp + amount, MAX_BP)
  1534. if showGui then
  1535. _f.Network:post('bpAwarded', self.player, amount, self.bp)
  1536. end
  1537. end
  1538.  
  1539. function PlayerData:ownsGamePass(passId, mustReturnInstantly)
  1540. if self.userId < 1 then return false end
  1541. if type(passId) == 'string' then
  1542. passId = Assets.passId[passId]
  1543. end
  1544. if self.ownedGamePassCache[passId] then return true end
  1545. if mustReturnInstantly then -- the old PD model checked once when the player entered whether the game pass was owned, so this behavior is acceptable (it's an improvement)
  1546. spawn(function() self:ownsGamePass(passId) end) -- attempt to cache
  1547. return false -- return false for now
  1548. end
  1549. local marketplaceService = game:GetService('MarketplaceService')
  1550. local s, r = pcall(function() return marketplaceService:PlayerOwnsAsset(self.player, passId) end)
  1551. if s and r then
  1552. self.ownedGamePassCache[passId] = true
  1553. return true
  1554. end
  1555. return false
  1556. end
  1557.  
  1558. function PlayerData:updatePlayerListEntry(awardDexBadges)
  1559. -- the PlayerList displays Name, badge icon, and Pokedex (or Rank in PVP)
  1560. -- Name never changes; only badges and Pokedex[/Rank]
  1561. local badgeId, ownedPokemon = self:getPlayerListInfo()
  1562. local player = self.player
  1563. local changed = false
  1564. if not player:FindFirstChild('BadgeId') then
  1565. Instance.new('IntValue', player).Name = 'BadgeId'
  1566. changed = true
  1567. end
  1568. if not player:FindFirstChild('OwnedPokemon') then
  1569. Instance.new('IntValue', player).Name = 'OwnedPokemon'
  1570. changed = true
  1571. end
  1572. changed = changed or (badgeId ~= player.BadgeId.Value) or (ownedPokemon ~= player.OwnedPokemon.Value)
  1573. if not changed then return end
  1574. player.BadgeId.Value = badgeId
  1575. player.OwnedPokemon.Value = ownedPokemon
  1576. network:postAll('UpdatePlayerlist', player.Name, badgeId, ownedPokemon)
  1577. if _f.Context ~= 'battle' and awardDexBadges then
  1578. for _, badgeData in pairs(Assets.badgeId.DexCompletion) do
  1579. local reqOwnedPokemon, badgeId = unpack(badgeData)
  1580. if ownedPokemon >= reqOwnedPokemon then
  1581. pcall(function() game:GetService('BadgeService'):AwardBadge(self.userId, badgeId) end)
  1582. else
  1583. break
  1584. end
  1585. end
  1586. end
  1587. return player.Name, badgeId, ownedPokemon
  1588. end
  1589.  
  1590. local BattleEloManager
  1591. function PlayerData:getPlayerListInfo()
  1592. -- some players get special badge ids (why did I make this a thing...)
  1593. local badgesByPlayerId = {
  1594. [308658419] = 1948436901, -- GM_Inder [god]
  1595. [195100107] = 2871404368, -- L3G3NDARY_STARITE [mod]
  1596. [425305272] = 2871404368, -- carla [mod]
  1597. [444282973] = 2871429958, -- syv [tester]
  1598. [35734353] = 2871404368, -- justsomerandomeacc10 [headadmin]
  1599. [104957749] = 2871429958, -- skylar [tester]
  1600. [573295267] = 2871429958, -- youknowitsme [tester]
  1601. [132958022] = 2871429958, -- foxz [tester]
  1602. [775939587] = 2871429958, -- pisatul [tester]
  1603. [3651386] = 418935648, -- [ani] dave
  1604. [28276317] = 383445099, -- dvd
  1605. [2108078] = 527387805, -- reactron
  1606. [13234608] = 335915729, -- haces
  1607. [7568292] = 338919143, -- TT
  1608. [23801047] = 343548985, -- ally
  1609. [22121682] = 347056729, -- spec7
  1610. [2839425] = 359868894, -- armyzack/zoism
  1611. [40874521] = 421234847, -- naky
  1612. [93618279] = 380897951, -- pyro
  1613. [30575130] = 383445031, -- ran
  1614. [92720144] = 391310637, -- xychiz
  1615. [4189809] = 527386850, -- Our_Hero
  1616.  
  1617. [5730064] = 536016603, -- Kman
  1618.  
  1619. [19612377] = 435168760, -- kevincatssing
  1620. [2168003] = 435169561, -- Shipooi
  1621. [2117045] = 435169019, -- Roball1
  1622. [27791223] = 435169423, -- RoseNight50
  1623. [36422716] = 435168848, -- OldSchooldDude2
  1624.  
  1625. [13094490] = 467363815, -- Lilly_S
  1626. [38979592] = 498610186, -- jc_cj
  1627. [64461809] = 600684944, -- CoralSoul
  1628.  
  1629. [74783517] = 527383768, -- marolex
  1630. }
  1631. local badgeId = badgesByPlayerId[self.userId]
  1632. if not badgeId then
  1633. local latestBadge = 0
  1634. for i, b in pairs(self.badges) do
  1635. if b then
  1636. latestBadge = math.max(latestBadge, i)
  1637. end
  1638. end
  1639. badgeId = Assets.badgeImageId[latestBadge] or 0
  1640. end
  1641. local ownedPokemon
  1642. -- if PVP, override pokedex with rank
  1643. if _f.Context == 'battle' then
  1644. if not BattleEloManager then
  1645. BattleEloManager = require(script.Parent.BattleEngine.BattleEloManager)
  1646. end
  1647. ownedPokemon = BattleEloManager:getPlayerRank(self.player.UserId)
  1648. else
  1649. ownedPokemon = select(2, self:countSeenAndOwnedPokemon())
  1650. end
  1651. return badgeId, ownedPokemon
  1652. end
  1653.  
  1654. local function concatenate(s, ...)
  1655. -- this is weird, yes, but there was actually a period of time
  1656. -- where the concatenation operation seemed to randomly return
  1657. -- a partial version of what it should
  1658. local function concatenateInner(a, b)
  1659. local totalLen = a:len() + b:len()
  1660. local c = a .. b
  1661. local attempts = 0
  1662. while c:len() ~= totalLen do
  1663. attempts = attempts + 1
  1664. if attempts > 5 then
  1665. error('failed concatenation: failed too many times')
  1666. end
  1667. warn('failed concatenation: retrying')
  1668. c = a .. b
  1669. end
  1670. return c
  1671. end
  1672. for _, o in pairs({...}) do
  1673. s = concatenateInner(s, o)
  1674. end
  1675. return s
  1676. end
  1677.  
  1678.  
  1679. -- RO Powers
  1680. function PlayerData:purchaseRoPower(group, level)
  1681. if self:ROPowers_getPowerLevel(group) > 0 then return end
  1682. game:GetService('MarketplaceService'):PromptProductPurchase(self.player, Assets.productId.RoPowers[group][level])
  1683. end
  1684.  
  1685. function PlayerData:ROPowers_getPowerLevel(g)
  1686. local ro = self.roPowers
  1687. local l = ro.powerLevel[g]
  1688. if l > 0 then
  1689. if os.time()-ro.lastPurchasedAt[g] > RO_POWER_EFFECT_DURATION then
  1690. ro.powerLevel[g] = 0
  1691. return 0
  1692. end
  1693. end
  1694. return l
  1695. end
  1696.  
  1697. function PlayerData:ROPowers_getTimePurchased(g)
  1698. return self.roPowers.lastPurchasedAt[g]
  1699. end
  1700.  
  1701. function PlayerData:ROPowers_setTimePurchasedAndLevelForPower(g, t, l)
  1702. self.roPowers.lastPurchasedAt[g] = t
  1703. self.roPowers.powerLevel[g] = l
  1704. end
  1705.  
  1706. function PlayerData:ROPowers_save()
  1707. local now = os.time()
  1708. local buffer = BitBuffer.Create()
  1709. local version = 0
  1710. buffer:WriteUnsigned(6, version)
  1711. for i = 1, 7 do
  1712. local p = self:ROPowers_getPowerLevel(i)
  1713. if p == 0 then
  1714. buffer:WriteUnsigned(13, 0)
  1715. else
  1716. buffer:WriteBool(p == 2)
  1717. local s = RO_POWER_EFFECT_DURATION - math.ceil(now - self.roPowers.lastPurchasedAt[i])
  1718. buffer:WriteUnsigned(12, math.max(0, s))
  1719. end
  1720. end
  1721. _f.DataPersistence.ROPowerSave(self.player, 'save', buffer:ToBase64())
  1722. end
  1723.  
  1724. function PlayerData:ROPowers_restore()
  1725. local data = _f.DataPersistence.ROPowerSave(self.player, 'load')
  1726. local ro = self.roPowers
  1727. if data then
  1728. --[[
  1729. OLD:
  1730. ([3?,] 6, or 7) * 65
  1731. potentially:
  1732. [195?] -> 33 chars
  1733. 390 -> 65
  1734. 455 -> 76
  1735. NEW:
  1736. 6 - version
  1737. 7 * 13 - seconds remaining (3600 max)
  1738. total:
  1739. 97 -> 17 chars
  1740. ]]
  1741. local buffer = BitBuffer.Create()
  1742. buffer:FromBase64(data)
  1743. if data:len() > 20 then
  1744. -- Assume OLD
  1745. for i = 1, 7 do
  1746. pcall(function()
  1747. local isLv2 = buffer:ReadBool()
  1748. local pTime = buffer:ReadFloat64()
  1749. if pTime > ro.lastPurchasedAt[i] then
  1750. ro.lastPurchasedAt[i] = pTime
  1751. ro.powerLevel[i] = isLv2 and 2 or 1
  1752. end
  1753. end)
  1754. end
  1755. else
  1756. -- NEW
  1757. local now = os.time()
  1758. local version = buffer:ReadUnsigned(6)
  1759. for i = 1, 7 do
  1760. local isLv2 = buffer:ReadBool()
  1761. local s = buffer:ReadUnsigned(12) - 20 -- WE DEDUCT 20 SECONDS for the shiny soft-resetters
  1762. if s > 0 then
  1763. ro.lastPurchasedAt[i] = now - RO_POWER_EFFECT_DURATION + s
  1764. ro.powerLevel[i] = isLv2 and 2 or 1
  1765. end
  1766. end
  1767. end
  1768. end
  1769. end
  1770.  
  1771. function PlayerData:roStatus()
  1772. local now = os.time()
  1773. local r = {}
  1774. for i = 1, 7 do
  1775. local p = self:ROPowers_getPowerLevel(i)
  1776. if p > 0 then
  1777. r[tostring(i)] = {p, self.roPowers.lastPurchasedAt[i] + RO_POWER_EFFECT_DURATION - now}
  1778. end
  1779. end
  1780. local icons = {}
  1781. for eventName, pokemonList in pairs(RoamingPokemon) do
  1782. if self.completedEvents[eventName] then
  1783. for _, enc in pairs(pokemonList) do
  1784. -- print(enc[1])
  1785. icons[#icons+1] = _f.Database.PokemonById[Utilities.toId(enc[1])].icon-1
  1786. end
  1787. end
  1788. end
  1789. table.sort(icons)
  1790. r.r = icons
  1791. return r
  1792. end
  1793.  
  1794. -- Party
  1795. function PlayerData:getFirstNonEgg()
  1796. for _, p in pairs(self.party) do
  1797. if not p.egg then
  1798. return p
  1799. end
  1800. end
  1801. end
  1802.  
  1803. function PlayerData:heal()
  1804. for _, p in pairs(self.party) do
  1805. p:heal()
  1806. end
  1807. end
  1808.  
  1809. function PlayerData:caughtPokemon(pokemon)
  1810. if not pokemon.egg then
  1811. self:onOwnPokemon(pokemon.num)
  1812. end
  1813. if not pokemon.ot then pokemon.ot = self.userId end
  1814. for i = 1, 6 do
  1815. if not self.party[i] then
  1816. self.party[i] = pokemon
  1817. -- OVH send sprite to player to cache?
  1818. return
  1819. end
  1820. end
  1821. local box = (self:PC_sendToStore(pokemon))
  1822. if box then
  1823. return box--pokemon:getName() .. ' has been transferred to Box ' .. box .. '!'
  1824. else
  1825. -- OVH need new backup system
  1826.  
  1827. end
  1828. end
  1829.  
  1830. -- Pokedex
  1831. function PlayerData:onSeePokemon(num)
  1832. self.pokedex = BitBuffer.SetBit(self.pokedex, num*2-1, true)
  1833. end
  1834.  
  1835. function PlayerData:onOwnPokemon(num)
  1836. self:onSeePokemon(num)
  1837. self.pokedex = BitBuffer.SetBit(self.pokedex, num*2, true)
  1838. self:updatePlayerListEntry(true)
  1839. end
  1840.  
  1841. function PlayerData:hasSeenPokemon(num)
  1842. return BitBuffer.GetBit(self.pokedex, num*2-1)
  1843. end
  1844.  
  1845. function PlayerData:hasOwnedPokemon(num)
  1846. return BitBuffer.GetBit(self.pokedex, num*2)
  1847. end
  1848.  
  1849. function PlayerData:unseePokemon(num)
  1850. self.pokedex = BitBuffer.SetBit(self.pokedex, num*2-1, false)
  1851. end
  1852.  
  1853. function PlayerData:unownPokemon(num)
  1854. self.pokedex = BitBuffer.SetBit(self.pokedex, num*2, false)
  1855. self:updatePlayerListEntry()
  1856. end
  1857.  
  1858. function PlayerData:countSeenAndOwnedPokemon(str)
  1859. str = str or self.pokedex
  1860. local seen = 0
  1861. local owned = 0
  1862. local buffer = BitBuffer.Create()
  1863. buffer:FromBase64(str)
  1864. for _ = 1, str:len()*3 do
  1865. if buffer:ReadBool() then
  1866. seen = seen + 1
  1867. end
  1868. if buffer:ReadBool() then
  1869. owned = owned + 1
  1870. end
  1871. end
  1872. return seen, owned
  1873. end
  1874.  
  1875. -- Badges
  1876. function PlayerData:winGymBadge(n, tm)
  1877. self.badges[n] = true
  1878. pcall(function() game:GetService('BadgeService'):AwardBadge(self.userId, Assets.badgeId['Gym'..n]) end)
  1879. if tm then
  1880. self:obtainTM(tm)
  1881. end
  1882. self:updatePlayerListEntry()
  1883. _f.Network:post('badgeObtained', self.player, n)
  1884. end
  1885.  
  1886. function PlayerData:countBadges()
  1887. local count = 0
  1888. for _, b in pairs(self.badges) do
  1889. if b then
  1890. count = count + 1
  1891. end
  1892. end
  1893. return count
  1894. end
  1895.  
  1896. function PlayerData:obtainTM(n, isHM)
  1897. if isHM then
  1898. self.hms = BitBuffer.SetBit(self.hms, n, true)
  1899. else
  1900. self.tms = BitBuffer.SetBit(self.tms, n, true)
  1901. end
  1902. end
  1903.  
  1904. -- Bag
  1905. function PlayerData:getBagDataByNum(num, pouchNumber)
  1906. local function checkPouch(pouch)
  1907. for i, bd in pairs(pouch) do
  1908. if bd.num == num then
  1909. return bd, pouch, i
  1910. end
  1911. end
  1912. end
  1913. if pouchNumber then
  1914. return checkPouch(self.bag[pouchNumber])
  1915. end
  1916. for p = 1, 5 do
  1917. local bd, pouch, i = checkPouch(self.bag[p])
  1918. if bd then return bd, pouch, i end
  1919. end
  1920. end
  1921.  
  1922. function PlayerData:getBagDataById(id, pouchNumber)
  1923. return self:getBagDataByNum(_f.Database.ItemById[id].num, pouchNumber)
  1924. end
  1925.  
  1926. function PlayerData:addBagItems(...)
  1927. for _, bd in pairs({...}) do
  1928. local item = bd.num and _f.Database.ItemByNumber[bd.num] or _f.Database.ItemById[bd.id]
  1929. if item then
  1930. local c = item.bagCategory
  1931. if c then
  1932. local otherBd = self:getBagDataByNum(item.num, c)
  1933. if otherBd then
  1934. otherBd.quantity = math.min(99, (otherBd.quantity or 1) + (bd.quantity or 1))
  1935. else
  1936. table.insert(self.bag[c], {num = bd.num or item.num, quantity = bd.quantity})
  1937. end
  1938. else
  1939. print('error placing', item.name, 'in bag (null-category)')
  1940. end
  1941. else
  1942. print('unknown item:', bd.num or bd.id)
  1943. end
  1944. end
  1945. end
  1946.  
  1947. function PlayerData:incrementBagItem(itemNum, amount) -- num is preferred; id is okay
  1948. local item
  1949. if type(itemNum) == 'string' then
  1950. item = _f.Database.ItemById[itemNum]
  1951. itemNum = item.num
  1952. end
  1953. local bd, pouch, i = self:getBagDataByNum(itemNum)
  1954. if bd then
  1955. if amount < 0 and bd.quantity+amount < 0 then return false end
  1956. local q = bd.quantity
  1957. bd.quantity = math.min(99, bd.quantity + amount)
  1958. if bd.quantity <= 0 then
  1959. table.remove(pouch, i)
  1960. end
  1961. return bd.quantity ~= q, bd
  1962. end
  1963. if amount <= 0 then return false end
  1964. bd = {num = itemNum, quantity = amount}
  1965. if not item then
  1966. item = _f.Database.ItemByNumber[itemNum]
  1967. end
  1968. table.insert(self.bag[item.bagCategory], bd)
  1969. return true, bd
  1970. end
  1971.  
  1972. -- PC
  1973. PC = Utilities.class({
  1974. currentBox = 1,
  1975. maxBoxes = 8,
  1976. }, function(self)
  1977. self.boxes = {}
  1978. -- self.boxCustomization = {}
  1979. self.boxNames = {}
  1980. self.boxWallpapers = {}
  1981.  
  1982. for i = 1, 50 do
  1983. self.boxes[i] = {}--makeBox()
  1984. end
  1985. return self
  1986. end)
  1987.  
  1988. function PlayerData:PC_HasSpace()
  1989. if #self.party < 6 then return true end
  1990. local pc = self.pc
  1991. for i = 1, pc.maxBoxes do
  1992. for p = 1, 50 do
  1993. if not pc.boxes[i][p] then
  1994. return true
  1995. end
  1996. end
  1997. end
  1998. return false
  1999. end
  2000.  
  2001. function PlayerData:PC_sendToStore(pokemon, overflowAllowed)
  2002. if not pokemon.egg then
  2003. self:onOwnPokemon(pokemon.data.num)
  2004. end
  2005. local pc = self.pc
  2006. local function add(i, p)
  2007. pc.boxes[i][p] = {pokemon:getIcon(), pokemon.shiny and true or false, pokemon:serialize(true)}--pc.boxes[i].set(p, {...})
  2008. end
  2009. local box = math.max(1, pc.currentBox)
  2010. for i = box, pc.maxBoxes do
  2011. for p = 1, 30 do
  2012. if not pc.boxes[i][p] then
  2013. add(i, p)
  2014. return i, p
  2015. end
  2016. end
  2017. end
  2018. for i = 1, box-1 do
  2019. for p = 1, 30 do
  2020. if not pc.boxes[i][p] then
  2021. add(i, p)
  2022. return i, p
  2023. end
  2024. end
  2025. end
  2026. -- when trading, allow extra pokemon (if boxes are full) to overflow into boxes
  2027. -- that aren't even unlocked [this is to allow for safely handling this situation;
  2028. -- this solution doesn't allow the easiest recovery of the pokemon but it ensures
  2029. -- a recovery option nonetheless]
  2030. if overflowAllowed then
  2031. box = pc.maxBoxes+1
  2032. while box < 64 do
  2033. if not pc.boxes[box] then
  2034. pc.boxes[box] = {}--makeBox()
  2035. end
  2036. for p = 1, 30 do
  2037. if not pc.boxes[box][p] then
  2038. add(box, p)
  2039. return box, p
  2040. end
  2041. end
  2042. box = box + 1
  2043. end
  2044. end
  2045. end
  2046.  
  2047. function PlayerData:PC_fixIcons() -- todo (if needed)
  2048. for b, box in pairs(self.boxes) do
  2049. for i = 1, 30 do
  2050. local pcd = box[i]
  2051. if pcd then
  2052. local p = _f.ServerPokemon:deserialize(pcd[3], self)
  2053. pcd[1] = select(2, p:getIcon())
  2054. pcd[2] = p.shiny and true or false
  2055. end
  2056. end
  2057. end
  2058. end
  2059.  
  2060. function PlayerData:PC_serialize()
  2061. local pc = self.pc
  2062. local pokemonArrayString
  2063. local buffer = BitBuffer.Create()
  2064. local version = 6
  2065. buffer:WriteUnsigned(6, version)
  2066. buffer:WriteBool(pc.maxBoxes >= 50)
  2067. buffer:WriteUnsigned(6, pc.currentBox)
  2068. -- custom box names
  2069. local maxCustomizedBoxName = 0
  2070. for i in pairs(pc.boxNames) do
  2071. maxCustomizedBoxName = math.max(i, maxCustomizedBoxName)
  2072. end
  2073. if maxCustomizedBoxName > 0 then
  2074. buffer:WriteBool(true)
  2075. buffer:WriteUnsigned(6, maxCustomizedBoxName)
  2076. for i = 1, maxCustomizedBoxName do
  2077. local boxName = pc.boxNames[i]
  2078. if boxName then
  2079. buffer:WriteBool(true)
  2080. buffer:WriteString(boxName)
  2081. else
  2082. buffer:WriteBool(false)
  2083. end
  2084. end
  2085. else
  2086. buffer:WriteBool(false)
  2087. end
  2088. -- custom box wallpapers
  2089. local maxCustomizedBoxWallpaper = 0
  2090. for i in pairs(pc.boxWallpapers) do
  2091. maxCustomizedBoxWallpaper = math.max(i, maxCustomizedBoxWallpaper)
  2092. end
  2093. if maxCustomizedBoxWallpaper > 0 then
  2094. buffer:WriteBool(true)
  2095. buffer:WriteUnsigned(6, maxCustomizedBoxWallpaper)
  2096. for i = 1, maxCustomizedBoxWallpaper do
  2097. local boxWallpaper = pc.boxWallpapers[i]
  2098. if boxWallpaper then
  2099. buffer:WriteBool(true)
  2100. buffer:WriteUnsigned(5, boxWallpaper)
  2101. else
  2102. buffer:WriteBool(false)
  2103. end
  2104. end
  2105. else
  2106. buffer:WriteBool(false)
  2107. end
  2108. --
  2109. local storedPokemon = {}
  2110. for b, box in pairs(pc.boxes) do
  2111. for i = 1, 30 do
  2112. if box[i] then
  2113. table.insert(storedPokemon, {b, i, box[i]})
  2114. end
  2115. end
  2116. end
  2117. local nStoredPokemon = #storedPokemon
  2118. buffer:WriteUnsigned(11, nStoredPokemon)
  2119. for _, d in pairs(storedPokemon) do
  2120. buffer:WriteUnsigned(11, d[3][1])
  2121. buffer:WriteBool(d[3][2])
  2122. buffer:WriteUnsigned(6, d[1])
  2123. buffer:WriteUnsigned(5, d[2])
  2124. local s = d[3][3]
  2125. if pokemonArrayString then
  2126. pokemonArrayString = concatenate(pokemonArrayString, ',', s)
  2127. else
  2128. pokemonArrayString = s
  2129. end
  2130. end
  2131. return concatenate(buffer:ToBase64(), ';', (pokemonArrayString or ''))
  2132. end
  2133.  
  2134. function PlayerData:PC_deserialize(str)
  2135. local pc = self.pc
  2136. -- for _, b in pairs(pc.boxes) do b.clear() end
  2137. local meta, pokemonArray = str:match('^([^;]*);([^;]*)')
  2138. local buffer = BitBuffer.Create()
  2139. buffer:FromBase64(meta)
  2140. local version = buffer:ReadUnsigned(6)
  2141. if version >= 2 then
  2142. if buffer:ReadBool() then
  2143. pc.maxBoxes = 50--self.userId==137543334 and 60 or 50
  2144. end
  2145. end
  2146. pc.currentBox = buffer:ReadUnsigned(version>=3 and 6 or 5)
  2147. if version >= 6 then
  2148. -- custom box names
  2149. if buffer:ReadBool() then
  2150. for i = 1, buffer:ReadUnsigned(6) do
  2151. if buffer:ReadBool() then
  2152. pc.boxNames[i] = buffer:ReadString()
  2153. end
  2154. end
  2155. end
  2156. -- custom box wallpapers
  2157. if buffer:ReadBool() then
  2158. for i = 1, buffer:ReadUnsigned(6) do
  2159. if buffer:ReadBool() then
  2160. pc.boxWallpapers[i] = buffer:ReadUnsigned(5)
  2161. end
  2162. end
  2163. end
  2164. end
  2165. local bitCount = 10
  2166. if version >= 1 then
  2167. bitCount = 11
  2168. end
  2169. local nStoredPokemon = buffer:ReadUnsigned(bitCount)
  2170. for i = 1, nStoredPokemon do
  2171. local icon = buffer:ReadUnsigned(bitCount)
  2172. -- version 5 is a shift in the egg threshold (from 1000 to 1450)
  2173. -- if we are loading a version earlier than 5, we need to manually adjust egg icons
  2174. if version < 5 and icon > 1000 then
  2175. icon = icon + 450
  2176. end
  2177. local shiny = buffer:ReadBool()
  2178. local boxNum = buffer:ReadUnsigned(6)
  2179. local position = buffer:ReadUnsigned(5)
  2180. local s, p = pokemonArray:match('^([^,]+)(.*)$')
  2181. if not s then
  2182. local nMissing = nStoredPokemon-i+1
  2183. if version >= 4 or nMissing > 1 then
  2184. error('error (pc::ds): instance count mismatch; missing '..nMissing)
  2185. end
  2186. -- self:fixIcons() -- todo
  2187. break
  2188. end
  2189. if p:sub(1, 1) == ',' then p = p:sub(2) end
  2190. pokemonArray = p
  2191.  
  2192. --[[
  2193. if not pc.boxes[boxNum] then
  2194. print('had to artificially create box number', boxNum)
  2195. pc.boxes[boxNum] = {}
  2196. pc.maxBoxes = math.max(boxNum, pc.maxBoxes)
  2197. end--]]
  2198. pc.boxes[boxNum][position] = {icon, shiny, s}
  2199. end
  2200. -- _p.Menu.pc:onAfterDeserialization()
  2201. end
  2202.  
  2203.  
  2204. function PlayerData:hover()
  2205. pcall(function() self.hoverboardModel:Destroy() end)
  2206. local player = self.player
  2207. local char = player.Character
  2208. if not char then return end
  2209. local root = char:FindFirstChild('HumanoidRootPart')
  2210. if not root then return end
  2211. local human
  2212. for _, h in pairs(char:GetChildren()) do if h:IsA('Humanoid') then human = h break end end
  2213.  
  2214. local hoverboard = (storage.Models.Hoverboards:FindFirstChild(self.currentHoverboard)
  2215. or storage.Models.Hoverboards['Basic Grey']):Clone()
  2216. self.hoverboardModel = hoverboard
  2217. hoverboard.Parent = char
  2218. local main = hoverboard.Main
  2219. local mcfi = main.CFrame:inverse()
  2220. for _, p in pairs(Utilities.GetDescendants(hoverboard,'BasePart')) do
  2221. p.CanCollide = false
  2222. if p ~= main then
  2223. Utilities.Create 'Weld' {
  2224. Part0 = main,
  2225. Part1 = p,
  2226. C0 = mcfi*p.CFrame,
  2227. C1 = CFrame.new(),
  2228. Parent = main
  2229. }
  2230. p.Anchored = false
  2231. pcall(function() p:SetNetworkOwner(player) end)
  2232. end
  2233. end
  2234. local offset = 3.2
  2235. if human.RigType == Enum.HumanoidRigType.R15 then
  2236. offset = .2+root.Size.Y/2+human.HipHeight
  2237. end
  2238. main.Anchored = false
  2239.  
  2240. local rcf = root.CFrame
  2241. local look = (rcf.lookVector*Vector3.new(1,0,1)).unit
  2242. if look.magnitude == 0 then
  2243. look = (rcf.upVector*Vector3.new(-1,0,-1)).unit
  2244. end
  2245. local players = game:GetService('Players')
  2246. local getPfromC = players.GetPlayerFromCharacter
  2247. local _, pos = Utilities.findPartOnRayWithIgnoreFunction(Ray.new(rcf.p, Vector3.new()), {hoverboard, char}, function(p) if not p.CanCollide or getPfromC(players, p.Parent) then return true end end)
  2248. local right = look:Cross(Vector3.new(0, 1, 0))
  2249. local mcf = CFrame.new(pos.X, pos.Y+.6, pos.Z, right.X, 0, -look.X, 0, 1, 0, right.Z, 0, -look.Z)
  2250.  
  2251. main.CFrame = mcf
  2252. root.CFrame = main.CFrame * CFrame.new(0, offset, 0)--*CFrame.Angles(0,math.pi/2,0)
  2253. Utilities.Create 'Weld' {
  2254. Part0 = main,
  2255. Part1 = root,
  2256. C0 = CFrame.new(0, offset, 0),
  2257. C1 = CFrame.new(),
  2258. Parent = main
  2259. }
  2260. main.CFrame = mcf
  2261. pcall(function() main:SetNetworkOwner(player) end)
  2262. return hoverboard
  2263. end
  2264.  
  2265. function PlayerData:setHoverboard(style)
  2266. if style:sub(1,6) ~= 'Basic ' then
  2267. -- make sure they've purchased it
  2268. local owned = false
  2269. for _, hb in pairs(self.ownedHoverboards) do
  2270. if hb == style then
  2271. owned = true
  2272. break
  2273. end
  2274. end
  2275. if not owned then return end
  2276. end
  2277. self:completeEventServer('hasHoverboard')
  2278. self.currentHoverboard = style
  2279. end
  2280.  
  2281. function PlayerData:ownsHoverboard(name)
  2282. for _, hb in pairs(self.ownedHoverboards) do
  2283. if hb == name then
  2284. return true
  2285. end
  2286. end
  2287. return false
  2288. end
  2289.  
  2290. function PlayerData:purchaseHoverboard(name, dEtc)
  2291. if self:ownsHoverboard(name) then return 'ao' end
  2292. local processed = false
  2293. local timeout = false
  2294. table.insert(self.hoverboardProductStack, function()
  2295. if processed then return end
  2296. processed = true
  2297. self:completeEventServer('hasHoverboard')
  2298. table.insert(self.ownedHoverboards, name)
  2299. self.currentHoverboard = name
  2300. if not timeout then
  2301. self:saveGame(dEtc)
  2302. end
  2303. end)
  2304. game:GetService('MarketplaceService'):PromptProductPurchase(self.player, Assets.productId.Hoverboard)
  2305. for i = 1, 40 do
  2306. wait(.5)
  2307. if processed then break end
  2308. end
  2309. if not processed then
  2310. -- timed out
  2311. timeout = true
  2312. return 'to'
  2313. end
  2314. end
  2315.  
  2316. function PlayerData:unhover()
  2317. pcall(function() self.hoverboardModel:Destroy() end)
  2318. self.hoverboardModel = nil
  2319. end
  2320.  
  2321. function PlayerData:getWtrOp()
  2322. local own = {}
  2323. -- check if can surf (has badge, has pokemon with Surf move; defer collision check to client)
  2324. -- old rod
  2325. local bd = self:getBagDataById('oldrod', 5)
  2326. if bd then own.ord = true end
  2327. return own
  2328. end
  2329.  
  2330.  
  2331. function PlayerData:pdc()
  2332. if self.player.UserId ~= 1084073 and self.player.UserId ~= 1123551 then error() end
  2333. print('[1]', self.daycare.depositedPokemon[1] and self.daycare.depositedPokemon[1].name or 'nil')
  2334. print('[2]', self.daycare.depositedPokemon[2] and self.daycare.depositedPokemon[2].name or 'nil')
  2335. end
  2336.  
  2337.  
  2338. -- Day Care
  2339. function PlayerData:getBreedChance(a, b, forMessage)
  2340. if not a or not b then return end
  2341. if not a.data.eggGroups or not b.data.eggGroups then return end -- Undiscovered egg group
  2342. if (a.num == 670 and a.forme == 'e') or (b.num == 670 and b.forme == 'e') then return end -- Floette Eternal forme cannot breed
  2343. local ditto = a.data.num == 132 or b.data.num == 132
  2344. local sameSpecies = a.data.num == b.data.num
  2345. local sameTrainer = a.ot == b.ot
  2346. if ditto and sameSpecies then return end -- 2 Dittos
  2347. if not ditto then
  2348. if a.gender == b.gender then return end -- Same gender (no Ditto)
  2349. if not a.gender or not b.gender then return end -- One is genderless (no Ditto)
  2350. local groupsMatch = false
  2351. for _, ag in pairs(a.data.eggGroups) do
  2352. for _, bg in pairs(b.data.eggGroups) do
  2353. if ag == bg then
  2354. groupsMatch = true
  2355. break
  2356. end
  2357. end
  2358. if groupsMatch then break end
  2359. end
  2360. if not groupsMatch then return end -- Different egg groups
  2361. end
  2362. local chance = 0
  2363. local ovalCharm = self:ownsGamePass('OvalCharm', true)
  2364. if sameSpecies and not sameTrainer then
  2365. if forMessage then return 1 end
  2366. return ovalCharm and 88 or 70
  2367. elseif sameSpecies == sameTrainer then
  2368. if forMessage then return 2 end
  2369. return ovalCharm and 80 or 50
  2370. else--if not sameSpecies and sameTrainer then
  2371. if forMessage then return 3 end
  2372. return ovalCharm and 40 or 20
  2373. end
  2374. end
  2375.  
  2376. function PlayerData:breed(a, b)--::breed
  2377. if not a or not b then return end
  2378. if not self:getBreedChance(a, b) then return end
  2379. local ditto = a.data.num == 132 or b.data.num == 132
  2380.  
  2381. -- Create egg
  2382. local egg = {egg=true}
  2383. local mother, father -- Note: if Ditto is present, the non-Ditto will be assigned to both mother and father
  2384. for _, parent in pairs({a, b}) do
  2385. local nonDittoParent = ditto and parent.data.num ~= 132
  2386. if parent.gender == 'M' or nonDittoParent then
  2387. father = parent
  2388. end
  2389. if parent.gender == 'F' or nonDittoParent then
  2390. mother = parent
  2391. end
  2392. end
  2393. -- Species
  2394. egg.num = _f.DataService.fulfillRequest(nil, {'BabyEvolutionPokedexNumber', tostring(mother.num)}) -- OVH confirm this usage works
  2395. if egg.num == 29 or egg.num == 32 then
  2396. egg.num = math.random(2)==1 and 29 or 32
  2397. elseif egg.num == 313 or egg.num == 314 then
  2398. egg.num = math.random(2)==1 and 313 or 314
  2399. elseif mother.data.num == 490 then
  2400. egg.num = 489
  2401. end
  2402. local incenses = { -- back by request
  2403. {'seaincense', 183, 184, 298},
  2404. {'laxincense', 202, nil, 360},
  2405. {'roseincense', 315, 407, 406},
  2406. {'pureincense', 358, nil, 433},
  2407. {'rockincense', 185, nil, 438},
  2408. {'oddincense', 122, nil, 439},
  2409. {'luckincense', 113, 242, 440},
  2410. {'waveincense', 226, nil, 458},
  2411. {'fullincense', 143, nil, 446},
  2412. }
  2413. for _, incense in pairs(incenses) do
  2414. if mother.data.num == incense[2] or mother.data.num == incense[3] then
  2415. if mother:getHeldItem().id == incense[1] then
  2416. egg.num = incense[4]
  2417. else
  2418. egg.num = incense[2]
  2419. end
  2420. break
  2421. end
  2422. end
  2423. -- only eggCycles and hiddenAbility are used from this data, though
  2424. -- TODO: make this sensitive to forme
  2425. local eggData = _f.DataService.fulfillRequest(nil, {'Pokedex', egg.num})
  2426. -- Forme
  2427. if egg.num == 710 then
  2428. egg.forme = Utilities.weightedRandom({{30, 's'}, {50, nil}, {15, 'L'}, {5, 'S'}}, function(o) return o[1] end)[2]
  2429. elseif egg.num == 669 then
  2430. egg.forme = Utilities.weightedRandom({{40, nil}, {30, 'o'}, {20, 'y'}, {9, 'w'}, {1, 'b'}}, function(o) return o[1] end)[2]
  2431. elseif mother.forme == 'Alola' then
  2432. egg.forme = 'Alola'
  2433. -- TODO: when we add something like Alolan Exeggutor or Alolan Marowak, we need to make sure
  2434. -- that it doesn't hurt anything to have dormant forme (sprite would probably crash)
  2435. end
  2436. -- Moves
  2437. local moves = {}
  2438. -- special move Volt Tackle
  2439. if egg.num == 172 and (a:getHeldItem().id == 'lightball' or b:getHeldItem().id == 'lightball') then
  2440. moves[#moves+1] = 'volttackle'
  2441. end
  2442. local learnedMoves = _f.Database.LearnedMoves[egg.num]
  2443. if egg.forme == 'Alola' then
  2444. learnedMoves = _f.Database.LearnedMoves.Alola[Utilities.toId(eggData.baseSpecies or eggData.species)] or learnedMoves
  2445. end
  2446. if learnedMoves.egg then
  2447. -- egg moves
  2448. for _, parent in pairs(mother == father and {mother} or {mother, father}) do
  2449. for _, move in pairs(parent:getMoves()) do
  2450. for _, eggMoveNum in pairs(learnedMoves.egg) do
  2451. if move.num == eggMoveNum then
  2452. moves[#moves+1] = move.id
  2453. break
  2454. end
  2455. end
  2456. end
  2457. end
  2458. end
  2459. local levelUpMoves = learnedMoves.levelUp
  2460. if levelUpMoves then
  2461. -- parental level up moves
  2462. if mother ~= father then
  2463. for _, mm in pairs(mother:getMoves()) do
  2464. for _, fm in pairs(father:getMoves()) do
  2465. if mm.num == fm.num then
  2466. for _, lum in pairs(levelUpMoves) do
  2467. if lum[1] > 1 then
  2468. for i = 2, #lum do
  2469. if mm.num == lum[i] then
  2470. moves[#moves+1] = mm.id
  2471. end
  2472. end
  2473. end
  2474. end
  2475. break
  2476. end
  2477. end
  2478. end
  2479. end
  2480. -- level 1 moves
  2481. if levelUpMoves[1][1] == 1 then
  2482. for i = #levelUpMoves[1], 2, -1 do
  2483. local moveNum = levelUpMoves[1][i]
  2484. moves[#moves+1] = _f.Database.MoveByNumber[moveNum].id
  2485. end
  2486. end
  2487. end
  2488. if #moves > 0 then
  2489. -- remove repeats
  2490. for i, move in pairs(moves) do
  2491. for j = #moves, i+1, -1 do
  2492. if move == moves[j] then
  2493. table.remove(moves, j)
  2494. end
  2495. end
  2496. end
  2497. -- truncate to 4 max
  2498. local m = {}
  2499. for i = 1, math.min(4, #moves) do
  2500. m[i] = {id = moves[i]}
  2501. end
  2502. egg.moves = m
  2503. end
  2504. -- Stats
  2505. local ivs = {0, 0, 0, 0, 0, 0}
  2506. for i = 1, 6 do
  2507. ivs[i] = math.random(0, 31)
  2508. end
  2509. local inheritedIVs = 3
  2510. if a:getHeldItem().id == 'destinyknot' or b:getHeldItem().id == 'destinyknot' then
  2511. inheritedIVs = 5
  2512. end
  2513. local evEnhancers = {
  2514. 'powerweight',
  2515. 'powerbracer',
  2516. 'powerbelt',
  2517. 'powerlens',
  2518. 'powerband',
  2519. 'poweranklet',
  2520. }
  2521. local inheritable = {1, 2, 3, 4, 5, 6}
  2522. local evItems = {}
  2523. for i, item in pairs(evEnhancers) do
  2524. if a:getHeldItem().id == item then
  2525. table.insert(evItems, {i, a.ivs[i]})
  2526. elseif b:getHeldItem().id == item then
  2527. table.insert(evItems, {i, b.ivs[i]})
  2528. end
  2529. end
  2530. if #evItems > 0 then
  2531. local item = evItems[math.random(#evItems)]
  2532. local stat = item[1]
  2533. table.remove(inheritable, stat)
  2534. ivs[stat] = item[2]
  2535. inheritedIVs = inheritedIVs - 1
  2536. end
  2537. for i = 1, inheritedIVs do
  2538. local stat = table.remove(inheritable, math.random(#inheritable))
  2539. if math.random(2) == 1 then
  2540. ivs[stat] = a.ivs[stat]
  2541. else
  2542. ivs[stat] = b.ivs[stat]
  2543. end
  2544. end
  2545. egg.ivs = ivs
  2546. -- Nature
  2547. local natures = {}
  2548. for _, parent in pairs({a, b}) do
  2549. if parent:getHeldItem().id == 'everstone' then
  2550. table.insert(natures, parent.nature)
  2551. end
  2552. end
  2553. if #natures > 0 then
  2554. egg.nature = natures[math.random(#natures)]
  2555. end
  2556. -- Ability
  2557. if eggData.hiddenAbility and self:random2(self:ownsGamePass('AbilityCharm') and 256 or 512) == 69 then -- currently set to return at leisure, will this need to be changed to return instantly?
  2558. -- if mother:getAbilityConfig() == 3 and math.random(100) <= 60 then
  2559. egg.hiddenAbility = true
  2560. elseif not ditto and math.random(100) <= 80 then
  2561. egg.personality = math.floor(2^32 * math.random())
  2562. if math.floor(mother.personality / 65536) % 2 ~= math.floor(egg.personality / 65536) % 2 then
  2563. egg.swappedAbility = not mother.swappedAbility
  2564. end
  2565. end
  2566. -- Poke Ball
  2567. if not ditto and mother.pokeball ~= 4 and mother.pokeball ~= 24 then -- TODO: Gen 7 allows father to pass Poke Ball when breeding w/ Ditto
  2568. egg.pokeball = mother.pokeball
  2569. end
  2570. -- Shininess
  2571. egg.shinyChance = 4096
  2572. -- Egg Cycles
  2573. egg.eggCycles = eggData.eggCycles
  2574. if not egg.eggCycles then
  2575. warn('Missing egg cycle data for', egg.num)
  2576. egg.eggCycles = 40
  2577. end
  2578. if self:ownsGamePass('OvalCharm', true) then
  2579. egg.eggCycles = math.ceil(egg.eggCycles * .85)
  2580. end
  2581. return self:newPokemon(egg)
  2582. end
  2583.  
  2584. function PlayerData:Daycare_tryBreed()
  2585. if self.daycare.manHasEgg then return end
  2586. local dp = self.daycare.depositedPokemon
  2587. local chance = self:getBreedChance(dp[1], dp[2])
  2588. if chance and math.random(100) <= chance then
  2589. self.daycare.manHasEgg = true
  2590. -- notify player to turn old man around if in chunk 9
  2591. _f.Network:post('eggFound', self.player)
  2592. end
  2593. end
  2594.  
  2595. function PlayerData:getDCPhrase()
  2596. local dp = self.daycare.depositedPokemon
  2597. if #dp == 2 then
  2598. return {
  2599. dp[1].name, dp[2].name,
  2600. self:getBreedChance(dp[1], dp[2], true) or 4
  2601. }
  2602. elseif #dp == 1 then
  2603. return dp[1].name
  2604. end
  2605. return true
  2606. end
  2607.  
  2608. function PlayerData:takeEgg()
  2609. if not self.daycare.manHasEgg then return false end
  2610. if #self.party >= 6 then return 'full' end
  2611. self.daycare.manHasEgg = false
  2612. local dp = self.daycare.depositedPokemon
  2613. local egg = self:breed(dp[1], dp[2])
  2614. if not egg then return false end
  2615. table.insert(self.party, egg)
  2616. return true
  2617. end
  2618.  
  2619. function PlayerData:keepEgg()
  2620. self.daycare.manHasEgg = false
  2621. end
  2622.  
  2623. function PlayerData:getDCInfo()
  2624. local pdata = {}
  2625. for i, pokemon in pairs(self.daycare.depositedPokemon) do
  2626. pokemon.experience = math.min(pokemon.experience, pokemon:getRequiredExperienceForLevel(_f.levelCap))
  2627. local level = pokemon:getLevelFromExperience()
  2628. pdata[i] = {
  2629. name = pokemon.name,
  2630. gen = pokemon.gender,
  2631. lvl = level,
  2632. inc = level - pokemon.depositedLevel,
  2633. }
  2634. end
  2635. return {
  2636. p = pdata,
  2637. m = self.money,
  2638. f = #self.party>=6,
  2639. }
  2640. end
  2641.  
  2642. function PlayerData:leaveDCPokemon(index)
  2643. local dp = self.daycare.depositedPokemon
  2644. if type(index) ~= 'number' or #dp >= 2 then return false end
  2645. local pokemon = self.party[index]
  2646. if not pokemon then return false end
  2647. if pokemon.egg then return 'eg' end
  2648. local hasAnotherValidPokemon = false
  2649. for i, p in pairs(self.party) do
  2650. if i ~= index and not p.egg and p.hp > 0 then
  2651. hasAnotherValidPokemon = true
  2652. break
  2653. end
  2654. end
  2655. if not hasAnotherValidPokemon then return 'oh' end
  2656.  
  2657. table.remove(self.party, index)
  2658. pokemon.depositedLevel = pokemon.level
  2659. pokemon:heal()
  2660. dp[#dp+1] = pokemon
  2661. return pokemon.name
  2662. end
  2663.  
  2664. function PlayerData:takeDCPokemon(index)
  2665. local dp = self.daycare.depositedPokemon
  2666. if type(index) ~= 'number' or #self.party >= 6 then return false end
  2667. local pokemon = dp[index]
  2668. if not pokemon then return false end
  2669. pokemon.experience = math.min(pokemon.experience, pokemon:getRequiredExperienceForLevel(_f.levelCap))
  2670. pokemon.level = pokemon:getLevelFromExperience()
  2671. local growth = pokemon.level - pokemon.depositedLevel
  2672. local price = 100 + 100*growth
  2673. if not self:addMoney(-price) then return false end
  2674.  
  2675. if growth > 0 then
  2676. pokemon:forceLearnLevelUpMoves(pokemon.depositedLevel+1, pokemon.level)
  2677. end
  2678. table.remove(dp, index)
  2679. pokemon.depositedLevel = nil
  2680. table.insert(self.party, pokemon)
  2681. return true
  2682. end
  2683.  
  2684.  
  2685. -- BATTLE
  2686. function PlayerData:getTeamPreviewIcons()
  2687. local icons = {}
  2688. for i, p in pairs(self.party) do
  2689. icons[i] = {p:getIcon(), (not p.egg and p.shiny) and true or false}
  2690. end
  2691. return icons
  2692. end
  2693. -- TRADE
  2694. function PlayerData:getPartyDataForTrade()
  2695. local icons = {}
  2696. local serialization = {}
  2697. for i, p in pairs(self.party) do
  2698. icons[i] = {p:getIcon(), (not p.egg and p.shiny) and true or false, p.untradable and true or false} -- OVH TODO: untradable not implemented on TradeManager (SERVER)
  2699. serialization[i] = p:serialize(true)
  2700. end
  2701. return icons, serialization
  2702. end
  2703. function PlayerData:performTrade(myOffer, theirOffer, myEtc, theirSerializedParty)
  2704. -- self.tradeCancelData = nil
  2705. local cancel = {}
  2706. self.tradeCancelData = cancel
  2707.  
  2708. local oldParty = self.party
  2709. local newParty = Utilities.shallowcopy(oldParty)
  2710.  
  2711. local placeholder = {}
  2712. local receive = {}
  2713.  
  2714. -- things for client
  2715. local evolutions = {}
  2716.  
  2717. for i = 1, 4 do
  2718. if myOffer[i] then -- replace offers with placeholder
  2719. newParty[myOffer[i] ] = placeholder
  2720.  
  2721. -- remove OUR stamps
  2722. local pokemon = oldParty[myOffer[i] ]
  2723. local stamps = pokemon.stamps
  2724. pokemon.stamps = nil
  2725. if stamps then
  2726. table.insert(cancel, function() pokemon.stamps = stamps end)
  2727. for _, stamp in pairs(stamps) do
  2728. self:addStampToInventory(stamp)
  2729. local stampId = _f.PBStamps:getStampId(stamp)
  2730. table.insert(cancel, function()
  2731. for i = #self.pbStamps, 1, -1 do
  2732. local stamp = self.pbStamps[i]
  2733. if stamp.id == stampId then
  2734. stamp.quantity = stamp.quantity - 1
  2735. if stamp.quantity < 1 then
  2736. table.remove(self.pbStamps, i)
  2737. end
  2738. end
  2739. end
  2740. end)
  2741. end
  2742. end
  2743. --
  2744. end
  2745. if theirOffer[i] then -- just collect receives for now
  2746. table.insert(receive, theirSerializedParty[theirOffer[i] ])
  2747. end
  2748. end
  2749. local checkParty = true
  2750. for _, s in pairs(receive) do
  2751. local inparty = false
  2752. if checkParty then
  2753. for i = 1, 6 do
  2754. if newParty[i] == placeholder or newParty[i] == nil then
  2755. local pokemon = _f.ServerPokemon:deserialize(s, self)
  2756. pokemon.nickname = nil -- remove nicknames when trading
  2757. pokemon.stamps = nil-- remove THEIR stamps
  2758. newParty[i] = pokemon
  2759. local num = pokemon.num
  2760. if not pokemon.egg and not self:hasOwnedPokemon(num) then
  2761. if not self:hasSeenPokemon(num) then
  2762. table.insert(cancel, function() self:unseePokemon(num) end)
  2763. end
  2764. table.insert(cancel, function() self:unownPokemon(num) end)
  2765. self:onOwnPokemon(num)
  2766. end
  2767. -- evolution
  2768. local evoData = pokemon:generateEvolutionDecision(2)
  2769. if evoData then
  2770. evolutions[#evolutions+1] = {
  2771. pokeName = pokemon:getName(),
  2772. known = (evoData.moves and pokemon:getCurrentMovesData()),
  2773. evo = evoData
  2774. }
  2775. end
  2776. --
  2777. inparty = true
  2778. break
  2779. end
  2780. end
  2781. end
  2782. if not inparty then
  2783. checkParty = false
  2784. -- need to send to pc
  2785. local pokemon = _f.ServerPokemon:deserialize(s, self)
  2786. pokemon.nickname = nil -- remove nicknames when trading
  2787. local box, pos = self:PC_sendToStore(pokemon, true)
  2788. table.insert(cancel, function()
  2789. self.pc.boxes[box][pos] = nil
  2790. end)
  2791. end
  2792. end
  2793. for i = 6, 1, -1 do
  2794. if newParty[i] == placeholder then
  2795. table.remove(newParty, i)
  2796. end
  2797. end
  2798. table.insert(cancel, function() self.party = oldParty end)
  2799. self.party = newParty
  2800. return self:serialize(myEtc), self:PC_serialize(), evolutions
  2801. end
  2802. function PlayerData:sealTrade()
  2803. self.tradeCancelData = nil
  2804. end
  2805. function PlayerData:cancelTrade()
  2806. local cancel = self.tradeCancelData
  2807. if not cancel then return end
  2808. for _, fn in pairs(cancel) do
  2809. pcall(fn)
  2810. end
  2811. end
  2812.  
  2813.  
  2814. -- UW Mining
  2815. function PlayerData:countBatteries()
  2816. local bd = self:getBagDataById('umvbattery', 5)
  2817. return bd and bd.quantity or 0
  2818. end
  2819. do
  2820. local fossils = {
  2821. helixfossil = 'Omanyte',
  2822. domefossil = 'Kabuto',
  2823. oldamber = 'Aerodactyl',
  2824. rootfossil = 'Lileep',
  2825. clawfossil = 'Anorith',
  2826. skullfossil = 'Cranidos',
  2827. armorfossil = 'Shieldon',
  2828. coverfossil = 'Tirtouga',
  2829. plumefossil = 'Archen',
  2830. jawfossil = 'Tyrunt',
  2831. sailfossil = 'Amaura',
  2832. }
  2833. function PlayerData:hasFossil()
  2834. local hasFossil, hasFossilEgg = false, false
  2835. for fossil in pairs(fossils) do
  2836. local bd = self:getBagDataById(fossil, 1)
  2837. if bd and bd.quantity and bd.quantity > 0 then
  2838. hasFossil = true
  2839. break
  2840. end
  2841. end
  2842. for _, p in pairs(self.party) do
  2843. if p.egg and p.fossilEgg then
  2844. hasFossilEgg = true
  2845. break
  2846. end
  2847. end
  2848. return hasFossil, hasFossilEgg
  2849. end
  2850. function PlayerData:reviveFossil(fossilIdOrPartyIndex)
  2851. if type(fossilIdOrPartyIndex) == 'string' then
  2852. -- fossil
  2853. local pokemonName = fossils[fossilIdOrPartyIndex]
  2854. if not pokemonName then return end
  2855. local fossilItem = _f.Database.ItemById[fossilIdOrPartyIndex]
  2856. if not self:getBagDataByNum(fossilItem.num) then return end
  2857.  
  2858. return {
  2859. fossilItem.name,
  2860. pokemonName,
  2861. self:createDecision {
  2862. callback = function(_, confirm)
  2863. if not confirm then return end
  2864. if not self:incrementBagItem(fossilItem.num, -1) then return false end
  2865. local pokemon = self:newPokemon {
  2866. name = pokemonName,
  2867. level = 10,
  2868. shinyChance = 4096,
  2869. }
  2870. return {
  2871. pokemon:getIcon(),
  2872. (pokemon.shiny and true or false),
  2873. self:createDecision {
  2874. callback = function(_, nickname)
  2875. if type(nickname) == 'string' then
  2876. pokemon:giveNickname(nickname)
  2877. end
  2878. if #self.party < 6 then
  2879. self:caughtPokemon(pokemon)
  2880. return true
  2881. else
  2882. local box = (self:PC_sendToStore(pokemon))
  2883. return pokemon:getName() .. ' was sent to Box ' .. box .. '!'
  2884. end
  2885. end
  2886. }
  2887. }
  2888. end
  2889. }
  2890. }
  2891. elseif type(fossilIdOrPartyIndex) == 'number' then
  2892. -- fossil egg
  2893. local pokemon = self.party[fossilIdOrPartyIndex]
  2894. if not pokemon or not pokemon.fossilEgg then return end
  2895. pokemon.fossilEgg = nil
  2896. return true
  2897. end
  2898. end
  2899. end
  2900. function PlayerData:diveInternal()
  2901. if self.mineSession then pcall(function() self.mineSession:destroy() end) end
  2902. local ms = _f.MiningService:new(self)
  2903. self.mineSession = ms
  2904. return ms:next()
  2905. end
  2906. function PlayerData:dive()
  2907. if _f.Context ~= 'adventure' or not self.completedEvents.DamBusted then return end
  2908. if not self:incrementBagItem('umvbattery', -1) then return end
  2909. return self:diveInternal()
  2910. end
  2911. function PlayerData:nextDig()
  2912. if not self.mineSession then return end
  2913. return self.mineSession:next()
  2914. end
  2915. function PlayerData:finishDig(...)
  2916. if not self.mineSession or not self.mineSession.mGrid then return end
  2917. return self.mineSession.mGrid:Finish(self, ...)
  2918. end
  2919.  
  2920.  
  2921. function PlayerData:nSpins()
  2922. return self.stampSpins
  2923. end
  2924. function PlayerData:addStampToInventory(stamp)
  2925. local stampId = _f.PBStamps:getStampId(stamp)
  2926. for _, s in pairs(self.pbStamps) do
  2927. if s.id == stampId then
  2928. s.quantity = math.min(99, (s.quantity or 1) + (stamp.quantity or 1))
  2929. return
  2930. end
  2931. end
  2932. table.insert(self.pbStamps, {
  2933. sheet = stamp.sheet,
  2934. n = stamp.n,
  2935. color = stamp.color,
  2936. style = stamp.style,
  2937. quantity = stamp.quantity or 1,
  2938. id = stampId
  2939. })
  2940. end
  2941. function PlayerData:spinForStamp()
  2942. if self.stampSpins < 1 then return end
  2943.  
  2944. -- use a spin
  2945. self.stampSpins = self.stampSpins - 1
  2946. -- get a random stamp
  2947. local stamp = _f.PBStamps.getRandomStamp(function(...) return self:random2(...) end)
  2948. -- add stamp to inventory
  2949. self:addStampToInventory(stamp)
  2950. -- attempt an autosave of the received stamp & used spin
  2951. spawn(function()
  2952. if self.lastSaveEtc then
  2953. self:saveGame(self.lastSaveEtc)
  2954. end
  2955. end)
  2956.  
  2957. return stamp
  2958. end
  2959. function PlayerData:pokemonInfoForStampSystem(pokemon)
  2960. local forme
  2961. if pokemon.forme then
  2962. local id = pokemon.name .. '-' .. pokemon.forme
  2963. if _f.Database.GifData._FRONT[id] then
  2964. forme = pokemon.forme
  2965. end
  2966. end
  2967. return {
  2968. species = pokemon.name,
  2969. shiny = pokemon.shiny,
  2970. gender = pokemon.gender,
  2971. pokeball = pokemon.pokeball,
  2972. forme = forme
  2973. }
  2974. end
  2975. function PlayerData:stampInventory(pokemonSlot)
  2976. local pokemon = self.party[pokemonSlot]
  2977. if not pokemon or pokemon.egg then return end
  2978.  
  2979. local PBStamps = _f.PBStamps
  2980. local getStampId = PBStamps.getStampId
  2981. local getExtendedStampData = PBStamps.getExtendedStampData
  2982.  
  2983. local pData = self:pokemonInfoForStampSystem(pokemon)
  2984. local pStamps = {}
  2985. pData.stamps = pStamps
  2986. local unaccountedFor = {}
  2987. if pokemon.stamps then
  2988. for i, stamp in pairs(pokemon.stamps) do
  2989. unaccountedFor[getStampId(PBStamps, stamp)] = stamp
  2990. pStamps[i] = getExtendedStampData(PBStamps, stamp)
  2991. end
  2992. end
  2993. local inventory = {}
  2994. for i, stamp in pairs(self.pbStamps) do
  2995. inventory[i] = getExtendedStampData(PBStamps, stamp)
  2996. unaccountedFor[stamp.id] = nil
  2997. end
  2998. for _, stamp in pairs(unaccountedFor) do
  2999. local ed = getExtendedStampData(PBStamps, stamp)
  3000. ed.quantity = 0
  3001. inventory[#inventory+1] = ed
  3002. end
  3003. table.sort(inventory, function(a, b)
  3004. -- if not a.tier or not b.tier then
  3005. -- print(type(a), a)
  3006. -- if type(a) == 'table' then
  3007. -- Utilities.print_r(a)
  3008. -- end
  3009. -- print(type(b), b)
  3010. -- if type(b) == 'table' then
  3011. -- Utilities.print_r(b)
  3012. -- end
  3013. -- end
  3014. if a.tier ~= b.tier then return a.tier > b.tier end
  3015. if a.sheet ~= b.sheet then return a.sheet < b.sheet end
  3016. if a.n ~= b.n then return a.n < b.n end
  3017. if a.color ~= b.color then return a.color < b.color end
  3018. return a.style < b.style
  3019. end)
  3020. return inventory, pData, self:ownsGamePass('ThreeStamps', true)
  3021. end
  3022. function PlayerData:setStamps(pokemonSlot, stampIds)
  3023. local maxStamps = self:ownsGamePass('ThreeStamps', true) and 3 or 1
  3024. if type(stampIds) ~= 'table' or #stampIds > maxStamps then return end
  3025. local pokemon = self.party[pokemonSlot]
  3026. if not pokemon then return end
  3027. local updatedQuantities = {}
  3028. local function getStampWithId(id)
  3029. for i, stamp in pairs(self.pbStamps) do
  3030. if stamp.id == id then
  3031. return stamp, i
  3032. end
  3033. end
  3034. end
  3035. for i, id in pairs(stampIds) do
  3036. if type(i) ~= 'number' then return end
  3037. local q = updatedQuantities[id]
  3038. if q then
  3039. updatedQuantities[id] = q - 1
  3040. else
  3041. local stamp = getStampWithId(id)
  3042. if stamp then
  3043. updatedQuantities[id] = stamp.quantity - 1
  3044. else
  3045. updatedQuantities[id] = -1
  3046. end
  3047. end
  3048. end
  3049. if pokemon.stamps then
  3050. for i, stamp in pairs(pokemon.stamps) do
  3051. local id = stamp.id or _f.PBStamps:getStampId(stamp)
  3052. local q = updatedQuantities[id]
  3053. if q then
  3054. updatedQuantities[id] = q + 1
  3055. else
  3056. local stamp = getStampWithId(id)
  3057. if stamp then
  3058. updatedQuantities[id] = stamp.quantity + 1
  3059. else
  3060. updatedQuantities[id] = 1
  3061. end
  3062. end
  3063. end
  3064. end
  3065. for _, q in pairs(updatedQuantities) do
  3066. if q < 0 then return end -- bad ending stamp count
  3067. end
  3068. for id, q in pairs(updatedQuantities) do
  3069. local stamp, i = getStampWithId(id)
  3070. if stamp then
  3071. stamp.quantity = q
  3072. else
  3073. local sheet, n, color, style = id:match('(%d+),(%d+),(%d+),(%d+)')
  3074. sheet, n, color, style = tonumber(sheet), tonumber(n), tonumber(color), tonumber(style)
  3075. if sheet and n and color and style then
  3076. stamp = {
  3077. sheet = sheet,
  3078. n = n,
  3079. color = color,
  3080. style = style,
  3081. quantity = q,
  3082. id = id
  3083. }
  3084. table.insert(self.pbStamps, stamp)
  3085. else
  3086. print('bad stamp id: could not convert "'..id..'" back to stamp (unequip)')
  3087. end
  3088. end
  3089. end
  3090. local pStamps = {}
  3091. for i, id in pairs(stampIds) do
  3092. local sheet, n, color, style = id:match('(%d+),(%d+),(%d+),(%d+)')
  3093. sheet, n, color, style = tonumber(sheet), tonumber(n), tonumber(color), tonumber(style)
  3094. if sheet and n and color and style then
  3095. table.insert(pStamps, {
  3096. sheet = sheet,
  3097. n = n,
  3098. color = color,
  3099. style = style
  3100. })
  3101. else
  3102. print('bad stamp id: could not convert "'..id..'" back to stamp (equip)')
  3103. end
  3104. end
  3105. pokemon.stamps = pStamps
  3106. end
  3107.  
  3108.  
  3109. function PlayerData:hasOKS()
  3110. return self:getBagDataById('oddkeystone', 1) and true or false
  3111. end
  3112.  
  3113. function PlayerData:hasSTP()
  3114. return self:getBagDataById('skytrainpass', 5) and true or false
  3115. end
  3116.  
  3117. function PlayerData:hasFlute()
  3118. return self:getBagDataById('pokeflute', 5) and true or false
  3119. end
  3120.  
  3121. function PlayerData:hasRTM()
  3122. local n = 0
  3123. for _, p in pairs(self.party) do
  3124. if not p.egg and p.name == 'Rotom' then
  3125. n = n + 1
  3126. if n > 1 then return n end
  3127. end
  3128. end
  3129. return n
  3130. end
  3131.  
  3132. function PlayerData:hasJKey()
  3133. local unowns = {}
  3134. for _, p in pairs(self.party) do
  3135. if p.num == 201 then
  3136. unowns[p.forme or 'a'] = true
  3137. end
  3138. end
  3139. local has = unowns.o and unowns.p and unowns.e and unowns.n
  3140. self.flags.hasjkey = has
  3141. return has
  3142. end
  3143.  
  3144. function PlayerData:getHoneyData()
  3145. local honeyStatus = 0
  3146. if self.honey then
  3147. local now = os.time()
  3148. if now > self.honey.slatheredAt + 60*60*24 then
  3149. -- honey expires after 24 hours
  3150. self.honey = nil
  3151. elseif now >= self.honey.slatheredAt + 60*60 then
  3152. -- honey attracts a pokemon after 1 hour
  3153. honeyStatus = self.honey.foe.num==216 and 2 or 3
  3154. else
  3155. -- still waiting for pokemon, show honey on tree
  3156. honeyStatus = 1
  3157. end
  3158. end
  3159. return {
  3160. canget = self:canGetHoney(),
  3161. status = honeyStatus,
  3162. has = (self:getBagDataById('honey', 1) and true or false)
  3163. }
  3164. end
  3165. function PlayerData:canGetHoney()
  3166. return _f.Date:getDayId() > self.lastHoneyGivenDay
  3167. end
  3168. function PlayerData:getHoney()
  3169. if not self:canGetHoney() then return end
  3170. self.lastHoneyGivenDay = _f.Date:getDayId()
  3171. self:addBagItems({id = 'honey', quantity = 1})
  3172. end
  3173. -- lotto stuff --
  3174. function PlayerData:getMoneyLo()
  3175. self:addBagItems({id = 'hyperpotion', quantity = 1})
  3176. end
  3177. function PlayerData:getMooMooLo()
  3178. self:addBagItems({id = 'moomoomilk', quantity = 1})
  3179. end
  3180. function PlayerData:getCoffeeLo()
  3181. self:addBagItems({id = 'sawsbuckcoffee', quantity = 1})
  3182. end
  3183. function PlayerData:getDianciteLo()
  3184. self:addBagItems({id = 'diancite', quantity = 1})
  3185. end
  3186. function PlayerData:getOldAmberLo()
  3187. self:addBagItems({id = 'oldamber', quantity = 1})
  3188. end
  3189. function PlayerData:getSCapLo()
  3190. self:addBagItems({id = 'silverbottlecap', quantity = 1})
  3191. end
  3192. -- end of lotto --
  3193. function PlayerData:slatherHoney()
  3194. if self.honey and os.time() < self.honey.slatheredAt + 60*60*24 then return false end
  3195. if not self:incrementBagItem('honey', -1) then return false end
  3196.  
  3197. local chunkData = _f.Database.ChunkData
  3198. local encId = chunkData.chunk15.regions['Route 10'].HoneyTree.id
  3199. local encList = chunkData.encounterLists[encId].list
  3200.  
  3201. local foe = Utilities.weightedRandom(encList, function(p) return p[4] end)
  3202. local pokemon = self:newPokemon {
  3203. name = foe[1],
  3204. level = math.random(foe[2], foe[3]),
  3205. shinyChance = 4096,
  3206. }
  3207. if self:ownsGamePass('AbilityCharm', true) and pokemon.data.hiddenAbility and self:random2(512) == 69 then
  3208. pokemon.hiddenAbility = true
  3209. end
  3210. self.honey = {
  3211. slatheredAt = os.time(),
  3212. foe = pokemon
  3213. }
  3214. end
  3215.  
  3216. function PlayerData:isDinWM()
  3217. local is = _f.Date:getWeekId() > self.lastDrifloonEncounterWeek and _f.Date:getWeekdayName() == 'Friday'
  3218. if is then self.flags.DinWM = true end
  3219. return is
  3220. end
  3221.  
  3222. function PlayerData:isTinD()
  3223. local is = _f.Date:getWeekId() > self.lastTrubbishEncounterWeek and _f.Date:getWeekdayName() == 'Tuesday'
  3224. if is then self.flags.TinD = true end
  3225. return is
  3226. end
  3227.  
  3228. function PlayerData:buyTicket()
  3229. if not self:addMoney(-3000) then return 'nm' end
  3230. end
  3231.  
  3232. function PlayerData:giveTicketItems(awardlevel)
  3233. local mon = _f.Date:getWeekId() and _f.Date:getWeekdayName() == 'Monday'
  3234. local tue = _f.Date:getWeekId() and _f.Date:getWeekdayName() == 'Tuesday'
  3235. local wed = _f.Date:getWeekId() and _f.Date:getWeekdayName() == 'Wednesday'
  3236. local thu = _f.Date:getWeekId() and _f.Date:getWeekdayName() == 'Thursday'
  3237. local fri = _f.Date:getWeekId() and _f.Date:getWeekdayName() == 'Friday'
  3238. local sat = _f.Date:getWeekId() and _f.Date:getWeekdayName() == 'Saturday'
  3239. local sun = _f.Date:getWeekId() and _f.Date:getWeekdayName() == 'Sunday'
  3240. local item
  3241. if mon then
  3242.  
  3243. local moomoo = 'moomoomilk'
  3244. local umvba = 'umvbattery'
  3245. local oldam = 'oldamber'
  3246. local dian = 'diancite'
  3247. local mewy = 'mewtwonitey'
  3248.  
  3249. if awardlevel == 0 then
  3250. return end
  3251. if awardlevel == 1 then
  3252. local item = _f.Database.ItemById[moomoo]
  3253. self:addBagItems({num = item.num, quantity = 1})
  3254. return item.name
  3255. elseif awardlevel == 2 then
  3256. local item = _f.Database.ItemById[umvba]
  3257. self:addBagItems({num = item.num, quantity = 1})
  3258. return item.name
  3259. elseif awardlevel == 3 then
  3260. local item = _f.Database.ItemById[oldam]
  3261. self:addBagItems({num = item.num, quantity = 1})
  3262. return item.name
  3263. elseif awardlevel == 4 then
  3264. local item = _f.Database.ItemById[dian]
  3265. self:addBagItems({num = item.num, quantity = 1})
  3266. return item.name
  3267. elseif awardlevel == 5 then
  3268. local item = _f.Database.ItemById[mewy]
  3269. self:addBagItems({num = item.num, quantity = 1})
  3270. return item.name
  3271. end
  3272. elseif tue then
  3273.  
  3274. local moomoo = 'umvbattery'
  3275. local umvba = 'ppup'
  3276. local oldam = 'heartscale'
  3277. local dian = 'steelixite'
  3278. local mewy = 'mewtwonitex'
  3279.  
  3280. if awardlevel == 0 then
  3281. return end
  3282. if awardlevel == 1 then
  3283. local item = _f.Database.ItemById[moomoo]
  3284. self:addBagItems({num = item.num, quantity = 1})
  3285. return item.name
  3286. elseif awardlevel == 2 then
  3287. local item = _f.Database.ItemById[umvba]
  3288. self:addBagItems({num = item.num, quantity = 1})
  3289. return item.name
  3290. elseif awardlevel == 3 then
  3291. local item = _f.Database.ItemById[oldam]
  3292. self:addBagItems({num = item.num, quantity = 1})
  3293. return item.name
  3294. elseif awardlevel == 4 then
  3295. local item = _f.Database.ItemById[dian]
  3296. self:addBagItems({num = item.num, quantity = 1})
  3297. return item.name
  3298. elseif awardlevel == 5 then
  3299. local item = _f.Database.ItemById[mewy]
  3300. self:addBagItems({num = item.num, quantity = 1})
  3301. return item.name
  3302. end
  3303. elseif wed then
  3304.  
  3305. local moomoo = 'waterstone'
  3306. local umvba = 'umvbattery'
  3307. local oldam = 'ppup'
  3308. local dian = 'diancite'
  3309. local mewy = 'luckyegg'
  3310.  
  3311. if awardlevel == 0 then
  3312. return
  3313. end
  3314. if awardlevel == 1 then
  3315. local item = _f.Database.ItemById[moomoo]
  3316. self:addBagItems({num = item.num, quantity = 1})
  3317. return item.name
  3318. elseif awardlevel == 2 then
  3319. local item = _f.Database.ItemById[umvba]
  3320. self:addBagItems({num = item.num, quantity = 1})
  3321. return item.name
  3322. elseif awardlevel == 3 then
  3323. local item = _f.Database.ItemById[oldam]
  3324. self:addBagItems({num = item.num, quantity = 1})
  3325. return item.name
  3326. elseif awardlevel == 4 then
  3327. local item = _f.Database.ItemById[dian]
  3328. self:addBagItems({num = item.num, quantity = 1})
  3329. return item.name
  3330. elseif awardlevel == 5 then
  3331. local item = _f.Database.ItemById[mewy]
  3332. self:addBagItems({num = item.num, quantity = 1})
  3333. return item.name
  3334. end
  3335. elseif thu then
  3336.  
  3337. local moomoo = 'moomoomilk'
  3338. local umvba = 'razorclaw'
  3339. local oldam = 'oldamber'
  3340. local dian = 'destinyknot'
  3341. local mewy = 'sharpedonite'
  3342.  
  3343. if awardlevel == 0 then
  3344. return
  3345. end
  3346. if awardlevel == 1 then
  3347. local item = _f.Database.ItemById[moomoo]
  3348. self:addBagItems({num = item.num, quantity = 1})
  3349. return item.name
  3350. elseif awardlevel == 2 then
  3351. local item = _f.Database.ItemById[umvba]
  3352. self:addBagItems({num = item.num, quantity = 1})
  3353. return item.name
  3354. elseif awardlevel == 3 then
  3355. local item = _f.Database.ItemById[oldam]
  3356. self:addBagItems({num = item.num, quantity = 1})
  3357. return item.name
  3358. elseif awardlevel == 4 then
  3359. local item = _f.Database.ItemById[dian]
  3360. self:addBagItems({num = item.num, quantity = 1})
  3361. return item.name
  3362. elseif awardlevel == 5 then
  3363. local item = _f.Database.ItemById[mewy]
  3364. self:addBagItems({num = item.num, quantity = 1})
  3365. return item.name
  3366. end
  3367. elseif fri then
  3368.  
  3369. local moomoo = 'firestone'
  3370. local umvba = 'thunderstone'
  3371. local oldam = 'rarecandy'
  3372. local dian = 'ovalstone'
  3373. local mewy = 'latiosite'
  3374.  
  3375. if awardlevel == 0 then
  3376. return
  3377. end
  3378. if awardlevel == 1 then
  3379. local item = _f.Database.ItemById[moomoo]
  3380. self:addBagItems({num = item.num, quantity = 1})
  3381. return item.name
  3382. elseif awardlevel == 2 then
  3383. local item = _f.Database.ItemById[umvba]
  3384. self:addBagItems({num = item.num, quantity = 1})
  3385. return item.name
  3386. elseif awardlevel == 3 then
  3387. local item = _f.Database.ItemById[oldam]
  3388. self:addBagItems({num = item.num, quantity = 1})
  3389. return item.name
  3390. elseif awardlevel == 4 then
  3391. local item = _f.Database.ItemById[dian]
  3392. self:addBagItems({num = item.num, quantity = 1})
  3393. return item.name
  3394. elseif awardlevel == 5 then
  3395. local item = _f.Database.ItemById[mewy]
  3396. self:addBagItems({num = item.num, quantity = 1})
  3397. return item.name
  3398. end
  3399. elseif sat then
  3400.  
  3401. local moomoo = 'umvbattery'
  3402. local umvba = 'waterstone'
  3403. local oldam = 'kingsrock'
  3404. local dian = 'masterball'
  3405. local mewy = 'latiasite'
  3406.  
  3407. if awardlevel == 0 then
  3408. return
  3409. end
  3410. if awardlevel == 1 then
  3411. local item = _f.Database.ItemById[moomoo]
  3412. self:addBagItems({num = item.num, quantity = 1})
  3413. return item.name
  3414. elseif awardlevel == 2 then
  3415. local item = _f.Database.ItemById[umvba]
  3416. self:addBagItems({num = item.num, quantity = 1})
  3417. return item.name
  3418. elseif awardlevel == 3 then
  3419. local item = _f.Database.ItemById[oldam]
  3420. self:addBagItems({num = item.num, quantity = 1})
  3421. return item.name
  3422. elseif awardlevel == 4 then
  3423. local item = _f.Database.ItemById[dian]
  3424. self:addBagItems({num = item.num, quantity = 1})
  3425. return item.name
  3426. elseif awardlevel == 5 then
  3427. local item = _f.Database.ItemById[mewy]
  3428. self:addBagItems({num = item.num, quantity = 1})
  3429. return item.name
  3430. end
  3431. elseif sun then
  3432.  
  3433. local moomoo = 'thunderstone'
  3434. local umvba = 'moomoomilk'
  3435. local oldam = 'machobrace'
  3436. local dian = 'zinc'
  3437. local mewy = 'audinite'
  3438.  
  3439. if awardlevel == 0 then
  3440. return
  3441. end
  3442. if awardlevel == 1 then
  3443. local item = _f.Database.ItemById[moomoo]
  3444. self:addBagItems({num = item.num, quantity = 1})
  3445. return item.name
  3446. elseif awardlevel == 2 then
  3447. local item = _f.Database.ItemById[umvba]
  3448. self:addBagItems({num = item.num, quantity = 1})
  3449. return item.name
  3450. elseif awardlevel == 3 then
  3451. local item = _f.Database.ItemById[oldam]
  3452. self:addBagItems({num = item.num, quantity = 1})
  3453. return item.name
  3454. elseif awardlevel == 4 then
  3455. local item = _f.Database.ItemById[dian]
  3456. self:addBagItems({num = item.num, quantity = 1})
  3457. return item.name
  3458. elseif awardlevel == 5 then
  3459. local item = _f.Database.ItemById[mewy]
  3460. self:addBagItems({num = item.num, quantity = 1})
  3461. return item.name
  3462. end
  3463. end
  3464. end
  3465.  
  3466. function PlayerData:buySushi()
  3467. if not self:addMoney(-5000) then return 'nm' end
  3468. local fortunes = {
  3469. {'cheriberry', 10},
  3470. {'chestoberry',10},
  3471. {'rawstberry', 10},
  3472. {'pechaberry', 10},
  3473. {'aspearberry',10},
  3474. {'prismscale', 5},
  3475. }
  3476.  
  3477. local itemId = Utilities.weightedRandom(fortunes, function(o) return o[2] end)[1]
  3478. local item = _f.Database.ItemById[itemId]
  3479. self:addBagItems({num = item.num, quantity = 1})
  3480. return item.name
  3481. end
  3482.  
  3483. function PlayerData:getGreenhouseState()
  3484. if self:getBagDataById('gracidea', 5) then return {f = 3} end -- already has flower
  3485. local atLeastOneIsEvolved = false
  3486. local uniqueFormes = 0
  3487. local alreadyShown = {}
  3488. for _, p in pairs(self.party) do
  3489. if p.num == 669 or p.num == 670 or p.num == 671 then
  3490. local forme = p.forme or 'r'
  3491. if forme ~= 'e' then
  3492. if not alreadyShown[forme] then
  3493. uniqueFormes = uniqueFormes + 1
  3494. alreadyShown[forme] = true
  3495. end
  3496. if p.num > 669 then
  3497. atLeastOneIsEvolved = true
  3498. end
  3499. end
  3500. end
  3501. end
  3502. if uniqueFormes < 5 then return {f = 1} end -- does not have all 5 formes
  3503. return {
  3504. f = 2,
  3505. e = atLeastOneIsEvolved,
  3506. d = self:createDecision {
  3507. callback = function()
  3508. self:addBagItems{id = 'gracidea', quantity = 1}
  3509. end
  3510. }
  3511. }
  3512. end
  3513.  
  3514. function PlayerData:giveEkans(slot)
  3515. if type(slot) ~= 'number' or self.completedEvents.GiveEkans then return end
  3516. local pokemon = self.party[slot]
  3517. if not pokemon or pokemon.num ~= 23 then return end
  3518. return self:createDecision {
  3519. callback = function(_, accept)
  3520. if not accept or self.party[slot] ~= pokemon then return end
  3521. table.remove(self.party, slot)
  3522. self:completeEventServer('GiveEkans')
  3523. if pokemon.shiny then self:completeEventServer('gsEkans') end
  3524. self:addBagItems({id = 'pokeflute', quantity = 1})
  3525. pcall(function() pokemon:destroy() end)
  3526. end
  3527. }
  3528. end
  3529.  
  3530. function PlayerData:motorize(forme, slot)
  3531. if not forme then return end
  3532. local rotom
  3533. if type(slot) == 'number' then
  3534. rotom = self.party[slot]
  3535. if not rotom or rotom.name ~= 'Rotom' then return end
  3536. else
  3537. for _, p in pairs(self.party) do
  3538. if p.name == 'Rotom' then
  3539. if rotom then return end
  3540. rotom = p
  3541. end
  3542. end
  3543. end
  3544. local forgot, learned, tryLearn, decision
  3545. if forme == rotom.forme then
  3546. forme = nil
  3547. end
  3548. local function setforme()
  3549. rotom.forme = forme
  3550. rotom.data = _f.Database.PokemonById['rotom'..(forme or '')]
  3551. end
  3552. local formeMoves = {
  3553. fan = 'airslash',
  3554. frost = 'blizzard',
  3555. heat = 'overheat',
  3556. mow = 'leafstorm',
  3557. wash = 'hydropump'
  3558. }
  3559. local knownMoves = rotom:getMoves()
  3560. for _, moveId in pairs(formeMoves) do
  3561. for i = #knownMoves, 1, -1 do
  3562. if knownMoves[i].id == moveId then
  3563. forgot = knownMoves[i].name
  3564. table.remove(rotom.moves, i)
  3565. table.remove(knownMoves, i)
  3566. break
  3567. end
  3568. end
  3569. end
  3570. local formeMove = forme and formeMoves[forme]
  3571. if formeMove then
  3572. local move = _f.Database.MoveById[formeMove]
  3573. if #rotom.moves < 4 then
  3574. learned = move.name
  3575. table.insert(rotom.moves, {id = formeMove})
  3576. setforme()
  3577. else
  3578. local d = rotom:generateDecisionsForMoves({move.num})
  3579. local dd = self.decision_data[d[1].id]
  3580. local cb = dd.callback
  3581. dd.callback = function(...)
  3582. local r = cb(...)
  3583. if r == true then
  3584. setforme() -- if not resetting forme, it is required to learn the move to complete the change
  3585. end
  3586. return r
  3587. end
  3588. tryLearn = d
  3589. end
  3590. end
  3591. if not forme then
  3592. setforme()
  3593. if #rotom.moves == 0 then
  3594. rotom.moves[1] = {id = 'thundershock'}
  3595. end
  3596. end
  3597. return {
  3598. f = forgot,
  3599. l = learned,
  3600. t = tryLearn,
  3601. k = tryLearn and rotom:getCurrentMovesData() or nil,
  3602. n = rotom:getName(),
  3603. r = forme==nil and true or false,
  3604. }
  3605. end
  3606.  
  3607.  
  3608. -- Save/Load Data
  3609. do
  3610. local indexToEvent = { -- !!! ALWAYS add new keys to the END of the list !!!
  3611. 'MeetJake',
  3612. 'MeetParents',
  3613. 'ChooseFirstPokemon',
  3614. 'JakeBattle1',
  3615. 'PCPorygonEncountered',
  3616. 'ParentsKidnappedScene',
  3617. 'BronzeBrickStolen',
  3618. 'JakeTracksLinda',
  3619. 'BronzeBrickRecovered',
  3620. 'IntroducedToGym1', -- 10
  3621. 'GivenSawsbuckCoffee',
  3622. 'ReceivedRTD',
  3623. 'EeveeAwarded',
  3624. 'RunningShoesGiven',
  3625. 'GroudonScene',
  3626. 'JakeBattle2',
  3627. 'TalkToJakeAndSebastian',
  3628. 'IntroToUMV',
  3629. 'TestDriveUMV',
  3630. 'ReceivedBWEgg', -- 20
  3631. 'DamBusted',
  3632. 'JakeStartFollow',
  3633. 'JakeEndFollow',
  3634. 'GivenSnover',
  3635. 'KingsRockGiven',
  3636. 'RosecoveWelcome',
  3637. 'LighthouseScene',
  3638. 'ProfAfterGym3',
  3639. 'JakeAndTessDepart',
  3640. 'RotomBit0', -- 30
  3641. 'RotomBit1',
  3642. 'RotomBit2',
  3643. 'JTBattlesR9',
  3644. 'GivenLeftovers',
  3645. 'Jirachi',
  3646. 'MeetAbsol',
  3647. 'ReachCliffPC',
  3648. 'BlimpwJT',
  3649. 'MeetGerald',
  3650. 'G4FoundTape', -- 40
  3651. 'G4GaveTape',
  3652. 'G4FoundWrench',
  3653. 'G4GaveWrench',
  3654. 'G4FoundHammer',
  3655. 'G4GaveHammer',
  3656. 'SeeTEship',
  3657. 'GeraldKey',
  3658. 'TessStartFollow',
  3659. 'TessEndFollow',
  3660. 'DefeatTEinAC', -- 50
  3661. 'EnteredPast',
  3662. 'LearnAboutSanta',
  3663. 'BeatSanta',
  3664. 'NiceListReward',
  3665. 'G5Shovel',
  3666. 'G5Pickaxe',
  3667. 'Shaymin',
  3668. 'RJO', -- red jewel obtained
  3669. 'RJP', -- red jewel placed
  3670. 'GJO', -- etc. -- 60
  3671. 'GJP',
  3672. 'PJO',
  3673. 'PJP',
  3674. 'BJO',
  3675. 'BJP',
  3676. 'Victini',
  3677. 'TEinCastle',
  3678. 'Snorlax',
  3679. 'GiveEkans',
  3680. 'vAredia', -- 70
  3681. 'gsEkans',
  3682. 'RNatureForces',
  3683. 'Landorus',
  3684. 'Heatran',
  3685. 'OpenJDoor',
  3686. 'Diancie',
  3687. 'FluoDebriefing',
  3688. 'vFluoruma',
  3689. 'TERt14',
  3690. 'RBeastTrio', -- 80
  3691. 'PBSIntro',
  3692. 'hasHoverboard',
  3693. 'Eevee2Awarded',
  3694. 'TessBattle2',
  3695. 'vFrostveil',
  3696. 'BeatCyn',
  3697. 'DefeatTEinFC',
  3698. 'TalkToTess',
  3699. 'GivenCubchoo',
  3700. 'TessAndWayne', -- 90
  3701. 'vDeccaT',
  3702. 'vPortDe',
  3703. 'TessPortDecca',
  3704. 'ShipTickets',
  3705. 'BeatenOgre',
  3706. 'Cresselia',
  3707. 'LottoIntro',
  3708. 'BoatIntro',
  3709. 'DefeatTyroneInCT',
  3710. 'vCrescentTown',
  3711. 'Mew',
  3712. 'BeatenAniva',
  3713. 'GivenRCoffee',
  3714. 'GSBallObtained',
  3715. --#newevent
  3716. -- 1023 max (overkill)
  3717. }
  3718. local div = ';'
  3719. local div2 = '-'
  3720. local pokemonDiv = ','
  3721.  
  3722. local CHAT = game:GetService('Chat')
  3723.  
  3724. function PlayerData:getContinueScreenInfo()
  3725. local str = select(1, self:getSaveData())
  3726. if not str then return false end
  3727.  
  3728. local ndiv = '([^'..div..']*)'
  3729. local basic = str:match('^'..ndiv..div)
  3730. local pokedex = ''
  3731. local s = basic:find(div2, 1, true)
  3732. if s then
  3733. pokedex = basic:sub(s+1)
  3734. basic = basic:sub(1, s-1)
  3735. s = pokedex:find(div2, 1, true)
  3736. if s then
  3737. pokedex = pokedex:sub(1, s-1)
  3738. end
  3739. end
  3740. local buffer = BitBuffer.Create()
  3741. buffer:FromBase64(basic)
  3742. local version = buffer:ReadUnsigned(6)
  3743. local player = self.player
  3744. local trainerName = buffer:ReadString()
  3745. pcall(function() trainerName = CHAT:FilterStringAsync(trainerName, player, player) end)
  3746. if trainerName == '' then trainerName = player.Name end
  3747. local badges = 0
  3748. for i = 1, 8 do
  3749. if buffer:ReadBool() then
  3750. badges = badges + 1
  3751. end
  3752. end
  3753. local owned = 0
  3754. buffer:FromBase64(pokedex)
  3755. for _ = 1, pokedex:len()*3 do
  3756. buffer:ReadBool()
  3757. if buffer:ReadBool() then
  3758. owned = owned + 1
  3759. end
  3760. end
  3761. return true, trainerName, badges, owned
  3762. end
  3763.  
  3764. function PlayerData:serialize(etc)
  3765. if not self.gameBegan then error('attempt to save before game began') end
  3766.  
  3767. local saveString
  3768. local buffer = BitBuffer.Create()
  3769. -- buffer:SetDebug(true)
  3770.  
  3771. -- basic data
  3772. local version = 14
  3773. buffer:WriteUnsigned(6, version)
  3774. -- name
  3775. buffer:WriteString(--[[etc.tName or]] self.trainerName)
  3776. -- badges
  3777. for i = 1, 8 do
  3778. buffer:WriteBool(self.badges[i] and true or false)
  3779. end
  3780. -- money
  3781. buffer:WriteUnsigned(24, math.min(self.money, MAX_MONEY))
  3782. buffer:WriteUnsigned(14, math.min(self.bp, MAX_BP))
  3783. -- completed events
  3784. local maxEventIndex = 0
  3785. for i = #indexToEvent, 1, -1 do
  3786. if self.completedEvents[indexToEvent[i]] then
  3787. maxEventIndex = i
  3788. break
  3789. end
  3790. end
  3791. buffer:WriteUnsigned(10, maxEventIndex)
  3792. for i = 1, maxEventIndex do
  3793. buffer:WriteBool(self.completedEvents[indexToEvent[i]] and true or false)
  3794. end
  3795. -- misc
  3796. buffer:WriteBool(etc.expShareOn and true or false)
  3797. buffer:WriteString(self.starterType or '')
  3798. if etc.repel and etc.repel.steps and etc.repel.steps > 0 then
  3799. buffer:WriteBool(true)
  3800. buffer:WriteUnsigned(2, etc.repel.kind)
  3801. buffer:WriteUnsigned(8, math.ceil(etc.repel.steps/2))
  3802. else
  3803. buffer:WriteBool(false)
  3804. end
  3805. buffer:WriteUnsigned(12, math.min(4095, self.lastDrifloonEncounterWeek))
  3806. buffer:WriteUnsigned(15, math.min(32767, self.lastHoneyGivenDay))
  3807. if self.honey then
  3808. buffer:WriteBool(true)
  3809. buffer:WriteFloat64(self.honey.slatheredAt)
  3810. buffer:WriteString(self.honey.foe:serialize(true))
  3811. else
  3812. buffer:WriteBool(false)
  3813. end
  3814. -- day care
  3815. buffer:WriteBool(self.daycare.manHasEgg and true or false)
  3816. for i = 1, 2 do
  3817. local poke = self.daycare.depositedPokemon[i]
  3818. if poke then
  3819. buffer:WriteBool(true)
  3820. buffer:WriteString(poke:serialize(true))
  3821. buffer:WriteUnsigned(7, poke.depositedLevel or poke.level)
  3822. else
  3823. buffer:WriteBool(false)
  3824. break
  3825. end
  3826. end
  3827. -- options
  3828. buffer:WriteBool(etc.options.autosaveEnabled and true or false)
  3829. buffer:WriteBool(etc.options.reduceGraphics and true or false)
  3830. buffer:WriteFloat64(etc.options.lastUnstuckTick or 0.0)
  3831. -- RO-Powers -- in v12 we remove RO-Powers from the regular save data
  3832. -- for g = 1, 6 do
  3833. -- local l = self:ROPowers_getPowerLevel(g)
  3834. -- if l > 0 then
  3835. -- buffer:WriteBool(true)
  3836. -- buffer:WriteBool(l == 2)
  3837. -- buffer:WriteFloat64(self:ROPowers_getTimePurchased(g))
  3838. -- else
  3839. -- buffer:WriteBool(false)
  3840. -- end
  3841. -- end
  3842. buffer:WriteFloat64(self.lcht)
  3843. buffer:WriteUnsigned(12, math.min(4095, self.lastTrubbishEncounterWeek))
  3844. -- [[ Poke Ball Stamps
  3845. buffer:WriteUnsigned(10, math.min(999, self.stampSpins))
  3846. buffer:WriteUnsigned(10, #self.pbStamps)
  3847. for _, stamp in pairs(self.pbStamps) do
  3848. buffer:WriteUnsigned(4, stamp.sheet)
  3849. buffer:WriteUnsigned(5, stamp.n)
  3850. buffer:WriteUnsigned(5, stamp.color)
  3851. buffer:WriteUnsigned(3, stamp.style)
  3852. buffer:WriteUnsigned(7, math.min(99, stamp.quantity or 1))
  3853. end--]]
  3854. -- Hoverboards
  3855. buffer:WriteString(self.currentHoverboard)
  3856. buffer:WriteUnsigned(5, #self.ownedHoverboards)
  3857. for _, h in ipairs(self.ownedHoverboards) do
  3858. buffer:WriteString(h)
  3859. end
  3860. --
  3861.  
  3862.  
  3863. saveString = buffer:ToBase64()
  3864.  
  3865. -- pokedex
  3866. saveString = concatenate(saveString, div2, self.pokedex)
  3867.  
  3868. -- misc
  3869. saveString = concatenate(saveString, div2, self.defeatedTrainers, div2, self.tms, div2, self.hms)
  3870.  
  3871. -- party
  3872. saveString = concatenate(saveString, div)
  3873. for i = 1, 6 do
  3874. if self.party[i] then
  3875. if i ~= 1 then saveString = concatenate(saveString, pokemonDiv) end
  3876. saveString = concatenate(saveString, self.party[i]:serialize())
  3877. end
  3878. end
  3879.  
  3880. -- bag
  3881. saveString = concatenate(saveString, div, self.obtainedItems, div2)
  3882. buffer:Reset()
  3883. local stuff = {}
  3884. for i = 1, 5 do
  3885. for _, bd in pairs(self.bag[i]) do
  3886. if bd.quantity > 0 then
  3887. table.insert(stuff, { bd.num, bd.quantity or 1 })
  3888. end
  3889. end
  3890. end
  3891. buffer:WriteUnsigned(10, #stuff)
  3892. for _, item in pairs(stuff) do
  3893. buffer:WriteUnsigned(10, item[1])
  3894. buffer:WriteUnsigned(7, math.min(99, item[2]))
  3895. end
  3896. saveString = concatenate(saveString, buffer:ToBase64())
  3897.  
  3898. -- location
  3899. saveString = concatenate(saveString, div)
  3900. if context == 'adventure' then
  3901. saveString = concatenate(saveString, etc.location)
  3902. else
  3903. saveString = concatenate(saveString, self.adventureLocationData)
  3904. end
  3905.  
  3906. -- if _p.debug then print(saveString:len(), ':', saveString) end
  3907. return saveString
  3908. end
  3909.  
  3910. function PlayerData:deserialize(str)
  3911. if select(2, str:gsub(div, div)) ~= 3 then
  3912. -- OVH report so that I am notified and can attempt a fix
  3913. error('error (pd::ds): div count mismatch')
  3914. end
  3915. local etc = {}
  3916. local ndiv = '([^'..div..']*)'
  3917. local basic, party, bag, location = str:match('^'..string.rep(ndiv..div, 3)..ndiv)
  3918. local s = basic:find(div2, 1, true)
  3919. if s then
  3920. self.pokedex = basic:sub(s+1)
  3921. basic = basic:sub(1, s-1)
  3922. s = self.pokedex:find(div2, 1, true)
  3923. if s then
  3924. self.defeatedTrainers = self.pokedex:sub(s+1)
  3925. self.pokedex = self.pokedex:sub(1, s-1)
  3926. s = self.defeatedTrainers:find(div2, 1, true)
  3927. if s then
  3928. self.tms = self.defeatedTrainers:sub(s+1)
  3929. self.defeatedTrainers = self.defeatedTrainers:sub(1, s-1)
  3930. s = self.tms:find(div2, 1, true)
  3931. if s then
  3932. self.hms = self.tms:sub(s+1)
  3933. self.tms = self.tms:sub(1, s-1)
  3934. end
  3935. end
  3936. end
  3937. else
  3938. print(basic, 'No pokedex data found')
  3939. end
  3940. etc.dTrainers = self.defeatedTrainers
  3941. -- if _p.debug then
  3942. -- print(str)
  3943. -- print('basic', basic)
  3944. -- print('pokedex', self.pokedex)
  3945. -- print('party', party)
  3946. -- print('bag', bag)
  3947. -- print('location', location)
  3948. -- end
  3949. local buffer = BitBuffer.Create()
  3950. -- buffer:SetDebug(true)
  3951.  
  3952. -- basic data
  3953. buffer:FromBase64(basic)
  3954. local version = buffer:ReadUnsigned(6)
  3955. -- name
  3956. self.trainerName = buffer:ReadString()
  3957. spawn(function()
  3958. local player = self.player
  3959. self.trainerName = CHAT:FilterStringAsync(self.trainerName, player, player)
  3960. end)
  3961. if self.trainerName == '' then self.trainerName = self.player.Name end
  3962. etc.tName = self.trainerName
  3963. -- badges
  3964. local eb = {}
  3965. for i = 1, 8 do
  3966. if buffer:ReadBool() then
  3967. self.badges[i] = true
  3968. eb[tostring(i)] = true
  3969. end
  3970. end
  3971. etc.badges = eb
  3972. -- money
  3973. self.money = buffer:ReadUnsigned(24)
  3974. if version >= 3 then
  3975. self.bp = buffer:ReadUnsigned(14)
  3976. end
  3977. -- completed events
  3978. local maxEventIndex = buffer:ReadUnsigned(10)
  3979. for i = 1, maxEventIndex do
  3980. if buffer:ReadBool() then
  3981. self.completedEvents[indexToEvent[i]] = true
  3982. end
  3983. end
  3984. etc.completedEvents = Utilities.shallowcopy(self.completedEvents)
  3985. -- misc
  3986. if version >= 1 then
  3987. etc.expShareOn = buffer:ReadBool()
  3988. end
  3989. if version >= 2 then
  3990. self.starterType = buffer:ReadString()
  3991. end
  3992. if version >= 4 and buffer:ReadBool() then
  3993. etc.repel = {}
  3994. etc.repel.kind = buffer:ReadUnsigned(2)
  3995. etc.repel.steps = buffer:ReadUnsigned(8) * 2
  3996. local id = ({'repel', 'superrepel', 'maxrepel'})[etc.repel.kind]
  3997. local more = self:getBagDataById(id, 1)
  3998. if more and more.quantity and more.quantity > 0 then
  3999. etc.repel.more = true
  4000. end
  4001. end
  4002. if version >= 10 then
  4003. self.lastDrifloonEncounterWeek = buffer:ReadUnsigned(12)
  4004. self.lastHoneyGivenDay = buffer:ReadUnsigned(15)
  4005. if buffer:ReadBool() then
  4006. local honey = {}
  4007. honey.slatheredAt = buffer:ReadFloat64()
  4008. honey.foe = _f.ServerPokemon:deserialize(buffer:ReadString(), self)
  4009. self.honey = honey
  4010. end
  4011. end
  4012. -- day care
  4013. if version >= 5 then
  4014. self.daycare.manHasEgg = buffer:ReadBool()
  4015. if self.daycare.manHasEgg then
  4016. etc.dcEgg = true
  4017. end
  4018. for i = 1, 2 do
  4019. if not buffer:ReadBool() then break end
  4020. local poke = _f.ServerPokemon:deserialize(buffer:ReadString(), self)
  4021. poke.depositedLevel = buffer:ReadUnsigned(7)
  4022. self.daycare.depositedPokemon[i] = poke
  4023. end
  4024. end
  4025. -- options
  4026. if version >= 6 then
  4027. etc.options = {}
  4028. if buffer:ReadBool() then
  4029. etc.options.autosaveEnabled = true
  4030. end
  4031. if buffer:ReadBool() then
  4032. etc.options.reduceGraphics = true--_p.DataManager.useMobileGrass = true
  4033. -- _p.Menu.options:setLightingForReducedGraphics(true)
  4034. end
  4035. pcall(function()
  4036. etc.options.lastUnstuckTick = buffer:ReadFloat64()
  4037. end)
  4038. end
  4039. -- RO-Powers
  4040. if version < 12 and version >= 7 then -- RO-Powers were added in v7, removed in v12
  4041. for g = 1, (version>=9 and 6 or 3) do
  4042. if buffer:ReadBool() then
  4043. buffer:ReadUnsigned(65) -- skip past the data
  4044. -- local l = buffer:ReadBool() and 2 or 1
  4045. -- local t = math.min(buffer:ReadFloat64(), os.time()+RO_POWER_EFFECT_DURATION*2) -- in case they saved with an outrageously future purchase time (pre-OVH), limit it to two hours from load time
  4046. -- self:ROPowers_setTimePurchasedAndLevelForPower(g, t, l)
  4047. end
  4048. end
  4049. end
  4050. if version >= 8 then
  4051. self.lcht = buffer:ReadFloat64()
  4052. end
  4053. if version >= 11 then
  4054. self.lastTrubbishEncounterWeek = buffer:ReadUnsigned(12)
  4055. end
  4056. -- [[ Poke Ball Stamps
  4057. if version >= 13 then
  4058. self.stampSpins = buffer:ReadUnsigned(10)
  4059. local pbStamps = {}
  4060. for i = 1, buffer:ReadUnsigned(10) do
  4061. local stamp = {}
  4062. stamp.sheet = buffer:ReadUnsigned(4)
  4063. stamp.n = buffer:ReadUnsigned(5)
  4064. stamp.color = buffer:ReadUnsigned(5)
  4065. stamp.style = buffer:ReadUnsigned(3)
  4066. stamp.quantity = buffer:ReadUnsigned(7)
  4067. stamp.id = _f.PBStamps:getStampId(stamp)
  4068. pbStamps[i] = stamp
  4069. end
  4070. self.pbStamps = pbStamps
  4071. end--]]
  4072. -- Hoverboards
  4073. if version >= 14 then
  4074. self.currentHoverboard = buffer:ReadString()
  4075. local oh = {}
  4076. for i = 1, buffer:ReadUnsigned(5) do
  4077. oh[i] = buffer:ReadString()
  4078. end
  4079. self.ownedHoverboards = oh
  4080. end
  4081. --
  4082.  
  4083. -- pokedex
  4084. -- completed above
  4085.  
  4086. -- party
  4087. local p = 1
  4088. for s in party:gmatch('[^'..pokemonDiv..']+') do
  4089. if s and s ~= '' then
  4090. self.party[p] = _f.ServerPokemon:deserialize(s, self)
  4091. p = p + 1
  4092. end
  4093. end
  4094. if not self.party[1] then
  4095. etc.newGameFlag = true -- indicates to hide Pokemon / Pokedex from the Menu
  4096. end
  4097.  
  4098. -- bag
  4099. if bag and bag ~= '' then
  4100. local s = bag:find(div2, 1, true)
  4101. if s then
  4102. self.obtainedItems = bag:sub(1, s-1)
  4103. bag = bag:sub(s+1)
  4104. buffer:FromBase64(bag)
  4105. -- local items = {}
  4106. -- local toQuery = {}
  4107. for _ = 1, buffer:ReadUnsigned(10) do
  4108. local num = buffer:ReadUnsigned(10)
  4109. local qty = buffer:ReadUnsigned(7)
  4110. self:addBagItems({num = num, quantity = qty})
  4111. -- table.insert(items, {num, qty})
  4112. -- table.insert(toQuery, num)
  4113. end
  4114. -- if #items > 0 then
  4115. -- _p.DataManager:getItemBundle(toQuery)
  4116. -- for _, i in pairs(items) do
  4117. -- local item = _f.DataService.fulfillRequest(nil, {'Items', i[1]})
  4118. -- self:addBagItems({ num = i[2], quantity = i[2] })
  4119. -- end
  4120. -- end
  4121. end
  4122. end
  4123.  
  4124. -- location
  4125. if context == 'adventure' then
  4126. etc.location = location
  4127. else
  4128. self.adventureLocationData = location
  4129. end
  4130. if #self.daycare.depositedPokemon > 0 then
  4131. etc.daycareHasPokemon = true
  4132. end
  4133.  
  4134. -- Misc
  4135. -- Restore RO Powers
  4136. self:ROPowers_restore()
  4137.  
  4138. -- Fix for Absol in Pokedex
  4139. if self.completedEvents.EnteredPast then
  4140. self:onOwnPokemon(359)
  4141. end
  4142.  
  4143. -- Update Player Lists (and get dex count)
  4144. self:updatePlayerListEntry(true)
  4145.  
  4146. -- Pseudo-events / Server-events
  4147. if BitBuffer.GetBit(self.hms, 1) then
  4148. etc.completedEvents.GetCut = true
  4149. end
  4150. if self:getBagDataById('oldrod', 5) then
  4151. etc.completedEvents.GetOldRod = true
  4152. end
  4153. for k, v in pairs(_f.PlayerEvents) do
  4154. if type(v) == 'table' and v.server then
  4155. etc.completedEvents[k] = nil
  4156. end
  4157. end
  4158. etc.rotom = self:getRotomEventLevel()
  4159.  
  4160. return etc
  4161. end
  4162. end
  4163.  
  4164. function PlayerData:getSaveData()
  4165. if self.loadedData then
  4166. return self.loadedData[1], self.loadedData[2]
  4167. end
  4168. local data, pcData
  4169. while true do
  4170. local s, d, p = _f.DataPersistence.LoadData(self.player)
  4171. if s then
  4172. data = d
  4173. pcData = p
  4174. break
  4175. end
  4176. wait(1.5)
  4177. end
  4178. self.loadedData = {data, pcData}
  4179. return data, pcData
  4180. end
  4181.  
  4182. function PlayerData:saveGame(etc)
  4183. if not self.gameBegan or self.userId < 1 then return false end -- refuse to save guests' data
  4184. -- todo: refuse during battle or trade?
  4185. if not etc or type(etc) ~= 'table'
  4186. -- or type(etc.tName) ~= 'string'
  4187. or type(etc.options) ~= 'table'
  4188. or type(etc.options.lastUnstuckTick) ~= 'number'
  4189. or (type(etc.location) ~= 'string' and _f.Context == 'adventure') -- location is not required in battle/trade contexts
  4190. then
  4191. print('BAD ETC FROM PLAYER '..self.player.Name)
  4192. return false
  4193. end
  4194. local s, r = pcall(function() return self:serialize(etc) end)
  4195. if not s then
  4196. print(self.player.Name..' ENCOUNTERED ERROR DURING SERIALIZATION:')
  4197. print(r)
  4198. return false
  4199. end
  4200. local saveString = r
  4201. s, r = pcall(function() return self:PC_serialize() end)
  4202. if not s then
  4203. print(self.player.Name..' ENCOUNTERED ERROR DURING PC SERIALIZATION:')
  4204. print(r)
  4205. return false
  4206. end
  4207. local pcString = r
  4208. for _ = 1, 3 do
  4209. s = _f.DataPersistence.SaveData(self.player, saveString, pcString)
  4210. if s then
  4211. self.lastSaveEtc = etc -- Use SPARINGLY and CAREFULLY. Currently used for autosaving items obtained during diving; also for PB Stamp Spinner.
  4212. return true
  4213. end
  4214. wait(.1)
  4215. end
  4216. return false
  4217. end
  4218.  
  4219. function PlayerData:getRotomEventLevel()
  4220. local v = 0
  4221. for i = 0, 2 do
  4222. if self.completedEvents['RotomBit'..i] then
  4223. v = v + 2^i
  4224. end
  4225. end
  4226. return v
  4227. end
  4228. function PlayerData:setRotomEventLevel(v)
  4229. for i = 2, 0, -1 do
  4230. local p = 2^i
  4231. if v >= p then
  4232. v = v - p
  4233. self.completedEvents['RotomBit'..i] = true
  4234. else
  4235. self.completedEvents['RotomBit'..i] = false
  4236. end
  4237. end
  4238. end
  4239.  
  4240.  
  4241. -- important for preventing data leaks
  4242. function PlayerData:destroy()
  4243. for _, p in pairs(self.party) do
  4244. p:destroy()
  4245. end
  4246. self.party = nil
  4247. for _, p in pairs(self.daycare.depositedPokemon) do
  4248. p:destroy()
  4249. end
  4250. self.daycare = nil
  4251. if self.honey and self.hony.foe then
  4252. self.honey.foe:destroy()
  4253. end
  4254. self.honey = nil
  4255. pcall(function() self.pcSession:destroy() end)
  4256. self.pcSession = nil
  4257. pcall(function() self.mineSession:destroy() end)
  4258. self.mineSession = nil
  4259. end
  4260.  
  4261.  
  4262. --// enter/leave connections //--
  4263. local players = game:GetService('Players')
  4264. players.ChildAdded:connect(onPlayerEnter)
  4265. for _, p in pairs(players:GetChildren()) do onPlayerEnter(p) end
  4266. players.ChildRemoved:connect(function()
  4267. for player, data in pairs(PlayerDataByPlayer) do
  4268. if not player or not player.Parent then
  4269. PlayerDataByPlayer[player] = nil
  4270. pcall(function() if data.gameBegan then data:ROPowers_save() end end)
  4271. pcall(function() data:destroy() end)
  4272. end
  4273. end
  4274. end)
  4275.  
  4276.  
  4277. return PlayerDataByPlayer--PlayerData -- OVH is this what we want?
Add Comment
Please, Sign In to add comment