· 6 years ago · May 23, 2019, 03:44 AM
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
10local _f = require(script.Parent)
11
12local PlayerData, PC
13local PlayerDataByPlayer = {}--setmetatable({}, {__mode = 'k'})
14local 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
18end
19
20local network = _f.Network
21local context = _f.Context
22
23local 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}
116local 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}
126network: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, ...)
134end)
135network: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, ...)
143end)
144
145
146local storage = game:GetService('ServerStorage')
147local Utilities = _f.Utilities
148local BitBuffer = _f.BitBuffer--require(storage.Plugins.BitBuffer)
149local Region = require(storage.Plugins.Region)
150local Assets = require(storage.src.Assets) -- for game passes
151local UsableItemsClient = require(storage.src.UsableItemsClient)() -- note: nothing passed for _p
152local RoamingPokemon = require(storage.Data.Chunks).roamingEncounter
153
154local MAX_MONEY = 9999999
155local MAX_BP = 9999
156local RO_POWER_EFFECT_DURATION = 60 * 60
157
158local RUN_FULL_CHECK = false
159
160PlayerData = 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
225end)
226
227function 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
235end
236function 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
244end
245
246
247function PlayerData:check() end -- OVH todo
248
249
250function PlayerData:isInBattle()
251 return _f.BattleEngine:getBattleSideForPlayer(self.player) ~= nil
252end
253
254function PlayerData:isInTrade()
255 return _f.TradeManager:playerIsInTrade(self.player)
256end
257
258function 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
304end
305
306function 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
316end
317
318function 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 {})
346end
347
348function 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
358end
359function PlayerData:getCutter()
360 if not self.badges[1] then return end
361 return self:getMoveUser('cut')
362end
363function PlayerData:getDigger()
364 return self:getMoveUser('dig')
365end
366function PlayerData:getHeadbutter()
367 return self:getMoveUser('headbutt')
368end
369local rockSmashEncounter
370function 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
385end
386function PlayerData:getClimber()
387 if not self.badges[6] then return end
388 return self:getMoveUser('rockclimb')
389end
390
391function 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?'}
410end
411
412function PlayerData:getDex()
413 return self.pokedex
414end
415
416function 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 }
424end
425
426function PlayerData:chooseName(tName)
427 self.trainerName = tName
428end
429
430function 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
454end
455
456function PlayerData:newPokemon(data)
457 return _f.ServerPokemon:new(data, self)
458end
459
460function PlayerData:startNewGame()
461 if self.gameBegan then --[[ERROR]] return false end
462 self.gameBegan = true
463
464 self:onGameBegin()
465end
466
467function 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
480end
481
482function 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()
514end
515
516local shopProducts = {
517 [Assets.productId.MasterBall] = {id = 'masterball', icon = 87619102}
518}
519function 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
621end
622
623function 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
635end
636
637function 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
657end
658
659function 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
680end
681
682function 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 }
703end
704
705function 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
721end
722
723function 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
738end
739
740function 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?
796end
797
798function 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
851end
852
853function 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
891end
892
893function 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())
914end
915
916function 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
936end
937
938function 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 }
969end
970
971function 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
980end
981
982function 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
1032end
1033
1034function 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 }
1089end
1090
1091function 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({}))
1121end
1122
1123function 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)
1138end
1139
1140function 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
1150end
1151
1152function 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
1158end
1159
1160function 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 }
1175end
1176
1177function 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 }
1262end
1263
1264local getShop = require(script.GetShop)
1265function 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
1270end
1271
1272function 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
1294end
1295function PlayerData:maxBuy(itemId) -- rc4'd (from client)
1296 return (self:maxBuyInternal(itemId)) -- return single value to client
1297end
1298
1299function 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
1311end
1312
1313function 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
1339end
1340function PlayerData:bMaxBuy(shopIndex)
1341 return (self:bMaxBuyInternal(shopIndex))
1342end
1343
1344function 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
1356end
1357
1358function 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
1368end
1369
1370function 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
1388end
1389
1390function 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)
1398end
1399
1400function 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()
1408end
1409
1410function 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, ...)
1415end
1416
1417function 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
1424end
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437function 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
1443end
1444
1445function 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
1486end
1487
1488function PlayerData:resetFishStreak()
1489 self.fishingStreak = 0
1490end
1491
1492function 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
1521end
1522
1523
1524function 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
1530end
1531
1532function 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
1537end
1538
1539function 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
1556end
1557
1558function 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
1588end
1589
1590local BattleEloManager
1591function 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
1652end
1653
1654local 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
1676end
1677
1678
1679-- RO Powers
1680function 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])
1683end
1684
1685function 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
1695end
1696
1697function PlayerData:ROPowers_getTimePurchased(g)
1698 return self.roPowers.lastPurchasedAt[g]
1699end
1700
1701function PlayerData:ROPowers_setTimePurchasedAndLevelForPower(g, t, l)
1702 self.roPowers.lastPurchasedAt[g] = t
1703 self.roPowers.powerLevel[g] = l
1704end
1705
1706function 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())
1722end
1723
1724function 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
1769end
1770
1771function 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
1792end
1793
1794-- Party
1795function PlayerData:getFirstNonEgg()
1796 for _, p in pairs(self.party) do
1797 if not p.egg then
1798 return p
1799 end
1800 end
1801end
1802
1803function PlayerData:heal()
1804 for _, p in pairs(self.party) do
1805 p:heal()
1806 end
1807end
1808
1809function 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
1828end
1829
1830-- Pokedex
1831function PlayerData:onSeePokemon(num)
1832 self.pokedex = BitBuffer.SetBit(self.pokedex, num*2-1, true)
1833end
1834
1835function PlayerData:onOwnPokemon(num)
1836 self:onSeePokemon(num)
1837 self.pokedex = BitBuffer.SetBit(self.pokedex, num*2, true)
1838 self:updatePlayerListEntry(true)
1839end
1840
1841function PlayerData:hasSeenPokemon(num)
1842 return BitBuffer.GetBit(self.pokedex, num*2-1)
1843end
1844
1845function PlayerData:hasOwnedPokemon(num)
1846 return BitBuffer.GetBit(self.pokedex, num*2)
1847end
1848
1849function PlayerData:unseePokemon(num)
1850 self.pokedex = BitBuffer.SetBit(self.pokedex, num*2-1, false)
1851end
1852
1853function PlayerData:unownPokemon(num)
1854 self.pokedex = BitBuffer.SetBit(self.pokedex, num*2, false)
1855 self:updatePlayerListEntry()
1856end
1857
1858function 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
1873end
1874
1875-- Badges
1876function 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)
1884end
1885
1886function 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
1894end
1895
1896function 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
1902end
1903
1904-- Bag
1905function 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
1920end
1921
1922function PlayerData:getBagDataById(id, pouchNumber)
1923 return self:getBagDataByNum(_f.Database.ItemById[id].num, pouchNumber)
1924end
1925
1926function 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
1945end
1946
1947function 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
1970end
1971
1972-- PC
1973PC = 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
1986end)
1987
1988function 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
1999end
2000
2001function 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
2045end
2046
2047function 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
2058end
2059
2060function 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 ''))
2132end
2133
2134function 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()
2201end
2202
2203
2204function 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
2263end
2264
2265function 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
2279end
2280
2281function 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
2288end
2289
2290function 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
2314end
2315
2316function PlayerData:unhover()
2317 pcall(function() self.hoverboardModel:Destroy() end)
2318 self.hoverboardModel = nil
2319end
2320
2321function 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
2328end
2329
2330
2331function 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')
2335end
2336
2337
2338-- Day Care
2339function 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
2374end
2375
2376function 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)
2582end
2583
2584function 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
2593end
2594
2595function 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
2606end
2607
2608function 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
2617end
2618
2619function PlayerData:keepEgg()
2620 self.daycare.manHasEgg = false
2621end
2622
2623function 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 }
2640end
2641
2642function 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
2662end
2663
2664function 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
2682end
2683
2684
2685-- BATTLE
2686function 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
2692end
2693-- TRADE
2694function 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
2702end
2703function 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
2801end
2802function PlayerData:sealTrade()
2803 self.tradeCancelData = nil
2804end
2805function 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
2811end
2812
2813
2814-- UW Mining
2815function PlayerData:countBatteries()
2816 local bd = self:getBagDataById('umvbattery', 5)
2817 return bd and bd.quantity or 0
2818end
2819do
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
2899end
2900function 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()
2905end
2906function 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()
2910end
2911function PlayerData:nextDig()
2912 if not self.mineSession then return end
2913 return self.mineSession:next()
2914end
2915function PlayerData:finishDig(...)
2916 if not self.mineSession or not self.mineSession.mGrid then return end
2917 return self.mineSession.mGrid:Finish(self, ...)
2918end
2919
2920
2921function PlayerData:nSpins()
2922 return self.stampSpins
2923end
2924function 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 })
2940end
2941function 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
2958end
2959function 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 }
2974end
2975function 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)
3021end
3022function 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
3106end
3107
3108
3109function PlayerData:hasOKS()
3110 return self:getBagDataById('oddkeystone', 1) and true or false
3111end
3112
3113function PlayerData:hasSTP()
3114 return self:getBagDataById('skytrainpass', 5) and true or false
3115end
3116
3117function PlayerData:hasFlute()
3118 return self:getBagDataById('pokeflute', 5) and true or false
3119end
3120
3121function 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
3130end
3131
3132function 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
3142end
3143
3144function 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 }
3164end
3165function PlayerData:canGetHoney()
3166 return _f.Date:getDayId() > self.lastHoneyGivenDay
3167end
3168function PlayerData:getHoney()
3169 if not self:canGetHoney() then return end
3170 self.lastHoneyGivenDay = _f.Date:getDayId()
3171 self:addBagItems({id = 'honey', quantity = 1})
3172end
3173-- lotto stuff --
3174function PlayerData:getMoneyLo()
3175 self:addBagItems({id = 'hyperpotion', quantity = 1})
3176end
3177function PlayerData:getMooMooLo()
3178 self:addBagItems({id = 'moomoomilk', quantity = 1})
3179end
3180function PlayerData:getCoffeeLo()
3181 self:addBagItems({id = 'sawsbuckcoffee', quantity = 1})
3182end
3183function PlayerData:getDianciteLo()
3184 self:addBagItems({id = 'diancite', quantity = 1})
3185end
3186function PlayerData:getOldAmberLo()
3187 self:addBagItems({id = 'oldamber', quantity = 1})
3188end
3189function PlayerData:getSCapLo()
3190 self:addBagItems({id = 'silverbottlecap', quantity = 1})
3191end
3192-- end of lotto --
3193function 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 }
3214end
3215
3216function 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
3220end
3221
3222function 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
3226end
3227
3228function PlayerData:buyTicket()
3229 if not self:addMoney(-3000) then return 'nm' end
3230end
3231
3232function 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
3464end
3465
3466function 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
3481end
3482
3483function 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 }
3512end
3513
3514function 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 }
3528end
3529
3530function 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 }
3605end
3606
3607
3608-- Save/Load Data
3609do
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
4162end
4163
4164function 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
4180end
4181
4182function 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
4217end
4218
4219function 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
4227end
4228function 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
4238end
4239
4240
4241-- important for preventing data leaks
4242function 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
4259end
4260
4261
4262--// enter/leave connections //--
4263local players = game:GetService('Players')
4264players.ChildAdded:connect(onPlayerEnter)
4265for _, p in pairs(players:GetChildren()) do onPlayerEnter(p) end
4266players.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
4274end)
4275
4276
4277return PlayerDataByPlayer--PlayerData -- OVH is this what we want?