Smart Contract

ABRoyaleGame

A.6e7cad7066b272e9.ABRoyaleGame

Valid From

86,783,835

Deployed

3d ago
Feb 24, 2026, 11:42:49 PM UTC

Dependents

12 imports
1import FungibleToken from 0xf233dcee88fe0abe
2import FlowToken from 0x1654653399040a61
3import ArenaBoyzGlobals from 0x6e7cad7066b272e9
4import ArenaBoyzHistory from 0x6e7cad7066b272e9
5
6
7access(all) contract ABRoyaleGame {
8    
9    access(all) var pods: [Pod]
10    access(all) var heroes: [Hero]
11    access(all) var seeded_hash: [UInt8]
12    access(all) var attacks: [Attack]
13    access(all) var last_results: [AttackResult] // TODO: emit events instead of a struct for attack results
14    access(all) var seconds_per_turn: Int // Number of seconds each player is given to act
15    access(all) var turn: UInt32
16    access(all) var circle: UInt32
17    access(all) var enrolled_players: Int32
18    access(all) var current_player_id: Int32
19    access(all) var pod_size: UInt32
20    access(all) var turn_end_timestamp: UFix64
21    access(all) var game_started: Bool
22    access(all) var game_id: UInt32
23    access(all) var entry_fee: UFix64
24    access(all) var payment_vault: @{FungibleToken.Vault}
25    access(all) var runners_up: [Int32]
26    access(all) var runner_up_id: Int32
27    access(all) var second_runner_up_id: Int32
28    access(all) var current_key_id: Int
29    access(all) var signup_keys: {UInt32: Int}
30
31    access(all) let KeyStoragePath: StoragePath
32    access(all) let KeyPublicPath: PublicPath
33    access(all) let AdminStoragePath: StoragePath
34
35    init() {
36            self.heroes = []
37            self.attacks = []
38            self.last_results = []
39            self.seeded_hash = []
40            self.pods = []
41            self.signup_keys = {}
42            self.runners_up = [-1, -1, -1]
43            self.enrolled_players = 0
44            self.current_player_id = -1
45            self.current_key_id = -1
46            self.seconds_per_turn = ArenaBoyzGlobals.seconds_per_turn
47            self.turn = 0
48            self.turn_end_timestamp = 0.0
49            self.circle = 0
50            self.pod_size = ArenaBoyzGlobals.heroes_per_pod
51            self.game_id = 0
52            self.entry_fee = 10.0
53            self.payment_vault <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>())
54            self.runner_up_id = 0
55            self.second_runner_up_id = 0
56            self.game_started = false
57            self.AdminStoragePath = /storage/arenaboyzPathsNewAdmin
58            self.KeyStoragePath = /storage/arenaboyzPathsNewKey
59            self.KeyPublicPath = /public/arenaboyzPathsNewKey
60            let admin: @ABRoyaleGame.Admin <- create Admin()
61            self.account.storage.save(<-admin, to: self.AdminStoragePath)
62    }
63
64    //1.0 setters
65    access(account) fun setPods(newPods: [Pod]) {
66        self.pods = newPods
67    }
68
69    access(account) fun setHeroes(newHeroes: [Hero]) {
70        self.heroes = newHeroes
71    }
72
73    access(account) fun setSeededHash(newSeededHash: [UInt8]) {
74        self.seeded_hash = newSeededHash
75    }
76
77    access(account) fun setAttacks(newAttacks: [Attack]) {
78        self.attacks = newAttacks
79    }
80
81    access(account) fun setLastResults(newLastResults: [AttackResult]) {
82        self.last_results = newLastResults
83    }
84
85    access(account) fun setSecondsPerTurn(newSecondsPerTurn: Int) {
86        self.seconds_per_turn = newSecondsPerTurn
87    }
88
89    access(account) fun setTurn(newTurn: UInt32) {
90        self.turn = newTurn
91    }
92
93    access(account) fun setCircle(newCircle: UInt32) {
94        self.circle = newCircle
95    }
96
97    access(account) fun setEnrolledPlayers(newEnrolledPlayers: Int32) {
98        self.enrolled_players = newEnrolledPlayers
99    }
100
101    access(account) fun setCurrentPlayerId(newCurrentPlayerId: Int32) {
102        self.current_player_id = newCurrentPlayerId
103    }
104
105    access(account) fun setPodSize(newPodSize: UInt32) {
106        self.pod_size = newPodSize
107    }
108
109    access(account) fun setTurnEndTimestamp(newTurnEndTimestamp: UFix64) {
110        self.turn_end_timestamp = newTurnEndTimestamp
111    }
112
113    access(account) fun setGameStarted(newGameStarted: Bool) {
114        self.game_started = newGameStarted
115    }
116
117    access(account) fun setGameId(newGameId: UInt32) {
118        self.game_id = newGameId
119    }
120
121    access(account) fun setRunnersUp(newRunnersUp: [Int32]) {
122        self.runners_up = newRunnersUp
123    }
124
125    access(account) fun setRunnerUpId(newRunnerUpId: Int32) {
126        self.runner_up_id = newRunnerUpId
127    }
128
129    access(account) fun setSecondRunnerUpId(newSecondRunnerUpId: Int32) {
130        self.second_runner_up_id = newSecondRunnerUpId
131    }
132
133    access(account) fun setCurrentKeyId(newCurrentKeyId: Int) {
134        self.current_key_id = newCurrentKeyId
135    }
136
137    access(account) fun setSignupKeys(newSignupKeys: {UInt32: Int}) {
138        self.signup_keys = newSignupKeys
139    }
140    access(all) event AttackResultEvent(
141        pod: UInt32, 
142        attacker: UInt32, 
143        attacker_key: Int, 
144        target_key: Int, 
145        action_key: Int, 
146        target: UInt32, 
147        damage: UInt16, 
148        critical: Bool, 
149        counter_critical: Bool, 
150        counter_damage: UInt16, 
151        action_target: UInt32, 
152        action_amount: UFix64, 
153        action_item: UInt8, 
154        game_id: UInt32, 
155        attacker_hp: UInt16, 
156        target_hp: UInt16, 
157        action_target_hp: UInt16, 
158        attacker_hp_max: UInt16, 
159        target_hp_max: UInt16, 
160        action_target_hp_max: UInt16
161    )
162
163    access(all) event GameResultEvent(
164        game_id: UInt32, 
165        victor_id: Int32, 
166        ending_turn: UInt32, 
167        enrolled_players: Int32
168    )
169
170    access(all) event GameResultEventV2(
171        game_id: UInt32, 
172        victor_id: Int32, 
173        runner_up_id: Int32, 
174        second_runner_up_id: Int32, 
175        ending_turn: UInt32, 
176        enrolled_players: Int32
177    )
178    
179    access(all) struct ScavengeConflict {
180
181        access(all) var hero_ids: [UInt32]
182        access(all) var attack_indicies: [Int]
183
184        init(hero_ids: [UInt32], attack_indicies: [Int]) {
185            self.hero_ids = hero_ids
186            self.attack_indicies = attack_indicies
187        }
188
189        access(contract) fun addConflict(hero_id: UInt32, attack_index: Int) {
190            self.hero_ids.append(hero_id)
191            self.attack_indicies.append(attack_index)
192        }
193    }
194    
195    access(all) struct Pod {
196        access(all) var hero_ids: [UInt32] // IDs of heroes in this pod
197        access(all) var scavenge_pool: [UInt8; 32] // Scavenge pool for this pod
198
199        init() {
200            self.hero_ids = []
201            self.scavenge_pool = ArenaBoyzGlobals.getScavengePool0()
202        }
203        access(account) fun setHeroIds(newHeroIds: [UInt32]) {
204            self.hero_ids = newHeroIds
205        }
206        access(account) fun setScavengePool(newScavengePool: [UInt8; 32]) {
207            self.scavenge_pool = newScavengePool
208        }
209
210        access(account) fun ResetScavengePool(Circle: UInt32) {
211            if Circle == 1 {
212                self.scavenge_pool = ArenaBoyzGlobals.getScavengePool1()
213            } else if Circle == 2 {
214                self.scavenge_pool = ArenaBoyzGlobals.getScavengePool2()
215            } else if Circle == 3 {
216                self.scavenge_pool = ArenaBoyzGlobals.getScavengePool3()
217            } else if Circle == 4 {
218                self.scavenge_pool = ArenaBoyzGlobals.getScavengePool4()
219            } else if Circle == 5 {
220                self.scavenge_pool = ArenaBoyzGlobals.getScavengePool5()
221            } else if Circle == 6 {
222                self.scavenge_pool = ArenaBoyzGlobals.getScavengePool6()
223            } else {
224                self.scavenge_pool = ArenaBoyzGlobals.getScavengePool7()
225            }
226        }
227
228        access(account) fun SortPlayers(Players: [Hero]) {
229            var i: Int = 0
230            while i < self.hero_ids.length - 1 {
231                var maxIndex: Int = i
232                var j: Int = i + 1
233                while j < self.hero_ids.length {
234                    let heroIdJ: UInt32 = self.hero_ids[j]
235                    let heroIdMax: UInt32 = self.hero_ids[maxIndex]
236
237                    var speedJ: UInt16 = 0
238                    var speedMax: UInt16 = 0
239                    var hpJ: UInt16 = 0
240                    var hpMax: UInt16 = 0
241
242                    // Find hero by ID for heroIdJ
243                    var playerIndex = 0
244                    while playerIndex < Players.length {
245                        let player = Players[playerIndex]
246                        
247                        if player.hero_id == heroIdJ {
248                            speedJ = player.GetSpeed()
249                            hpJ = player.hitpoints
250                            break
251                        }
252                        
253                        playerIndex = playerIndex + 1
254                    }
255
256                    // Reset index for the next loop
257                    playerIndex = 0
258                    while playerIndex < Players.length {
259                        let player = Players[playerIndex]
260                        
261                        if player.hero_id == heroIdMax {
262                            speedMax = player.GetSpeed()
263                            hpMax = player.hitpoints
264                            break
265                        }
266                        
267                        playerIndex = playerIndex + 1
268                    }
269
270                    if hpJ == 0 && hpMax > 0 {
271                        // Skip, as heroes with 0 HP should be sorted to the back
272                    } else if hpMax == 0 && hpJ > 0 {
273                        maxIndex = j
274                    } else if speedJ > speedMax || (speedJ == speedMax && revertibleRandom<UInt32>() > revertibleRandom<UInt32>()) {
275                        maxIndex = j
276                    }
277                    j = j + 1
278                }
279                if maxIndex != i {
280                    let temp: UInt32 = self.hero_ids[i]
281                    self.hero_ids[i] = self.hero_ids[maxIndex]
282                    self.hero_ids[maxIndex] = temp
283                }
284                i = i + 1
285            }
286        }
287
288        access(account) fun DropItemIntoPool(Item: UInt8) {
289            var i: Int = 0
290            while i < self.scavenge_pool.length {
291                if self.scavenge_pool[i] == 0 {
292                    self.scavenge_pool[i] = Item
293                    break
294                }
295                i = i + 1
296            }
297        }
298    }
299
300    access(all) struct Hero {
301    access(all)  var hero_id: UInt32
302    access(all)  var key_id: Int
303    access(all) var hitpoints: UInt16
304    access(all) var max_hitpoints: UInt16
305    access(all) var shields: UInt16
306    access(all) var damage: UInt16
307    access(all) var defense: UInt16
308    access(all) var accuracy: UInt16
309    access(all) var speed: UInt16
310    access(all) var counter: UInt16
311    access(all) var attack_award: UInt8
312    access(all) var stim: UFix64
313    access(all) var has_moved: Bool // 0: cannot go yet, 1: can take action, 2: has already taken action
314    access(all) var move_timestamp: UFix64
315    access(all) var current_pod_id: UInt32 // Storing the pod the hero is in, rather than iterating through all pods
316    access(all) var items: [UInt8; 24]
317
318    init() {
319        self.hero_id = 0
320        self.key_id = 0
321        self.hitpoints = 0
322        self.shields = 0
323        self.max_hitpoints = 0
324        self.damage = 0
325        self.defense = 0
326        self.accuracy = 0
327        self.speed = 0
328        self.counter = 0
329        self.attack_award = 0
330        self.stim = 0.0
331        self.current_pod_id = 0
332        self.has_moved = false
333        self.move_timestamp = 0.0
334        self.items = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
335    }
336    // 1.0 setters
337    access(account) fun setHeroId(newHeroId: UInt32) {
338        self.hero_id = newHeroId
339    }
340
341    access(account) fun setKeyId(newKeyId: Int) {
342        self.key_id = newKeyId
343    }
344
345    access(account) fun setHitpoints(newHitpoints: UInt16) {
346        self.hitpoints = newHitpoints
347    }
348
349    access(account) fun setMaxHitpoints(newMaxHitpoints: UInt16) {
350        self.max_hitpoints = newMaxHitpoints
351    }
352
353    access(account) fun setShields(newShields: UInt16) {
354        self.shields = newShields
355    }
356
357    access(account) fun setDamage(newDamage: UInt16) {
358        self.damage = newDamage
359    }
360
361    access(account) fun setDefense(newDefense: UInt16) {
362        self.defense = newDefense
363    }
364
365    access(account) fun setAccuracy(newAccuracy: UInt16) {
366        self.accuracy = newAccuracy
367    }
368
369    access(account) fun setSpeed(newSpeed: UInt16) {
370        self.speed = newSpeed
371    }
372
373    access(account) fun setCounter(newCounter: UInt16) {
374        self.counter = newCounter
375    }
376
377    access(account) fun setAttackAward(newAttackAward: UInt8) {
378        self.attack_award = newAttackAward
379    }
380
381    access(account) fun setStim(newStim: UFix64) {
382        self.stim = newStim
383    }
384
385    access(account) fun setHasMoved(newHasMoved: Bool) {
386        self.has_moved = newHasMoved
387    }
388
389    access(account) fun setMoveTimestamp(newMoveTimestamp: UFix64) {
390        self.move_timestamp = newMoveTimestamp
391    }
392
393    access(account) fun setCurrentPodId(newCurrentPodId: UInt32) {
394        self.current_pod_id = newCurrentPodId
395    }
396
397    access(account) fun setItems(newItems: [UInt8; 24]) {
398        self.items = newItems
399    }
400
401    access(account) fun setItem(newItemIndex: Int, newItemID: UInt8) {
402        self.items[newItemIndex] = newItemID
403    }
404
405    access(all) fun GetDamage(): UInt16 {
406        var temp_stat: UInt16 = self.damage
407        var effective_item_max: Int = ArenaBoyzGlobals.effective_item_max
408        var i = 0
409        while i < self.items.length {
410            let item = self.items[i]
411            if effective_item_max > 0 {
412                if i == 0 { // Only count primary weapons in slot 0
413                    if item >= ArenaBoyzGlobals.ar_start && item < ArenaBoyzGlobals.sniper_start { // AR, defense-based
414                        temp_stat = temp_stat + (ArenaBoyzGlobals.primary_bonus_per_thresh * UInt16(item)) * (self.GetDefense() / ArenaBoyzGlobals.threshhold_amount)
415                    } else if item >= ArenaBoyzGlobals.sniper_start && item < ArenaBoyzGlobals.smg_start { // Sniper, accuracy-based
416                        temp_stat = temp_stat + (ArenaBoyzGlobals.primary_bonus_per_thresh * UInt16(item - (ArenaBoyzGlobals.sniper_start - 1))) * (self.GetAccuracy() / ArenaBoyzGlobals.threshhold_amount)
417                    } else if item >= ArenaBoyzGlobals.smg_start && item < ArenaBoyzGlobals.shotgun_start { // SMG, speed-based
418                        temp_stat = temp_stat + (ArenaBoyzGlobals.primary_bonus_per_thresh * UInt16(item - (ArenaBoyzGlobals.smg_start - 1))) * (self.GetSpeed() / ArenaBoyzGlobals.threshhold_amount)
419                    } else if item >= ArenaBoyzGlobals.shotgun_start && item < ArenaBoyzGlobals.throwable_start { // Shotgun, counter-based
420                        temp_stat = temp_stat + (ArenaBoyzGlobals.primary_bonus_per_thresh * UInt16(item - (ArenaBoyzGlobals.shotgun_start - 1))) * (self.GetCounter() / ArenaBoyzGlobals.threshhold_amount)
421                    }
422                }
423                if item >= ArenaBoyzGlobals.damage_start && item < ArenaBoyzGlobals.defense_start {
424                    temp_stat = temp_stat + ArenaBoyzGlobals.equipment_bonus_per_level * UInt16(item - (ArenaBoyzGlobals.damage_start - 1))
425                }
426            }
427            effective_item_max = effective_item_max - 1
428            i = i + 1
429        }
430        return temp_stat
431    }
432
433    access(all) fun GetAccuracy(): UInt16 {
434        var temp_stat: UInt16 = self.accuracy
435        var effective_item_max: Int = ArenaBoyzGlobals.effective_item_max
436        var i = 0
437        while i < self.items.length {
438            let item = self.items[i]
439            if effective_item_max > 0 {
440                if item >= ArenaBoyzGlobals.accuracy_start && item < ArenaBoyzGlobals.speed_start {
441                    temp_stat = temp_stat + ArenaBoyzGlobals.equipment_bonus_per_level * UInt16(item - (ArenaBoyzGlobals.accuracy_start - 1))
442                }
443            }
444            effective_item_max = effective_item_max - 1
445            i = i + 1
446        }
447        return temp_stat
448    }
449
450    access(all) fun GetCounter(): UInt16 {
451        var temp_stat: UInt16 = self.counter
452        var effective_item_max: Int = ArenaBoyzGlobals.effective_item_max
453        var i = 0
454        while i < self.items.length {
455            let item = self.items[i]
456            if effective_item_max > 0 {
457                if item >= ArenaBoyzGlobals.counter_start && item < ArenaBoyzGlobals.items_end {
458                    temp_stat = temp_stat + ArenaBoyzGlobals.equipment_bonus_per_level * UInt16(item - (ArenaBoyzGlobals.counter_start - 1))
459                }
460            }
461            effective_item_max = effective_item_max - 1
462            i = i + 1
463        }
464        return temp_stat
465    }
466
467    access(all) fun GetDefense(): UInt16 {
468        var temp_stat: UInt16 = self.defense
469        var effective_item_max: Int = ArenaBoyzGlobals.effective_item_max
470        var i = 0
471        while i < self.items.length {
472            let item = self.items[i]
473            if effective_item_max > 0 {
474                if item >= ArenaBoyzGlobals.defense_start && item < ArenaBoyzGlobals.accuracy_start {
475                    temp_stat = temp_stat + ArenaBoyzGlobals.equipment_bonus_per_level * UInt16(item - (ArenaBoyzGlobals.defense_start - 1))
476                }
477            }
478            effective_item_max = effective_item_max - 1
479            i = i + 1
480        }
481        return temp_stat
482    }
483
484    access(all) fun GetSpeed(): UInt16 {
485        var temp_stat: UInt16 = self.speed
486        var effective_item_max: Int = ArenaBoyzGlobals.effective_item_max
487        var i = 0
488        while i < self.items.length {
489            let item = self.items[i]
490            if effective_item_max > 0 {
491                if item >= ArenaBoyzGlobals.speed_start && item < ArenaBoyzGlobals.counter_start {
492                    temp_stat = temp_stat + ArenaBoyzGlobals.equipment_bonus_per_level * UInt16(item - (ArenaBoyzGlobals.speed_start - 1))
493                }
494            }
495            effective_item_max = effective_item_max - 1
496            i = i + 1
497        }
498        return temp_stat
499    }
500
501    access(all) fun GetAttackAwardAmount(): UFix64 {
502        let health_pct: UFix64 = UFix64(self.hitpoints) / UFix64(self.max_hitpoints)
503        let base_value: UFix64 = 5.0
504        if health_pct >= 1.0 {
505            return base_value * 5.0
506        }
507        if health_pct < 1.0 && health_pct >= 0.75 {
508            return base_value * 4.0
509        }
510        if health_pct < 0.75 && health_pct >= 0.5 {
511            return base_value * 3.0
512        }
513        if health_pct < 0.5 && health_pct >= 0.25 {
514            return base_value * 2.0
515        }
516        return base_value
517    }
518
519    access(account) fun GenerateHero(hero_id: UInt32, starting_pod_id: UInt32) {
520        self.hero_id = hero_id
521        self.max_hitpoints = ArenaBoyzGlobals.hp_base
522        self.hitpoints = ArenaBoyzGlobals.hp_base
523        self.damage = ArenaBoyzGlobals.damage_base
524        self.defense = ArenaBoyzGlobals.defense_base
525        self.accuracy = ArenaBoyzGlobals.accuracy_base
526        self.speed = ArenaBoyzGlobals.speed_base
527        self.counter = ArenaBoyzGlobals.counter_base
528        self.current_pod_id = starting_pod_id
529        self.attack_award = UInt8(revertibleRandom<UInt32>() & 0xFF) % 5
530        self.items[0] = ArenaBoyzGlobals.starting_primary_pool[UInt8(revertibleRandom<UInt32>() & 0xFF) % 4]
531
532        // 10% bonus jitter applied below, for some variance
533        self.max_hitpoints = self.max_hitpoints + UInt16(revertibleRandom<UInt32>() & 0xFFFF) % 100
534        self.hitpoints = self.max_hitpoints
535        self.damage = self.damage + UInt16(revertibleRandom<UInt32>() & 0xFFFF) % 30
536        self.defense = self.defense + UInt16(revertibleRandom<UInt32>() & 0xFFFF) % 10
537        self.accuracy = self.accuracy + UInt16(revertibleRandom<UInt32>() & 0xFFFF) % 20
538        self.speed = self.speed + UInt16(revertibleRandom<UInt32>() & 0xFFFF) % 20
539        self.counter = self.counter + UInt16(revertibleRandom<UInt32>() & 0xFFFF) % 20
540    }
541
542    access(account) fun ProcessConsumableItems() {
543        var i: Int = 0
544        while i < self.items.length {
545            let item = self.items[i]
546            if item >= ArenaBoyzGlobals.throwable_start && item < ArenaBoyzGlobals.healing_start {
547                // self.counter = self.counter + ArenaBoyzGlobals.targetable_bonus_per_level * (1 + UInt16(item) - UInt16(ArenaBoyzGlobals.throwable_start))
548                self.items[i] = 0
549            } else if item >= ArenaBoyzGlobals.healing_start && item < ArenaBoyzGlobals.shield_start {
550                self.defense = self.defense + ArenaBoyzGlobals.targetable_bonus_per_level * (1 + UInt16(item) - UInt16(ArenaBoyzGlobals.healing_start))
551                self.items[i] = 0
552            } else if item >= ArenaBoyzGlobals.shield_start && item < ArenaBoyzGlobals.stim_start {
553                self.speed = self.speed + ArenaBoyzGlobals.targetable_bonus_per_level * (1 + UInt16(item) - UInt16(ArenaBoyzGlobals.shield_start))
554                self.items[i] = 0
555            } else if item >= ArenaBoyzGlobals.stim_start && item < ArenaBoyzGlobals.medal_start {
556                // self.speed = self.speed + ArenaBoyzGlobals.targetable_bonus_per_level * (1 + UInt16(item) - UInt16(ArenaBoyzGlobals.stim_start))
557                self.items[i] = 0
558            } else if item >= ArenaBoyzGlobals.medal_start && item < ArenaBoyzGlobals.damage_start {
559                self.counter = self.counter + ArenaBoyzGlobals.targetable_bonus_per_level * (1 + UInt16(item) - UInt16(ArenaBoyzGlobals.medal_start))
560                self.items[i] = 0
561            }
562            i = i + 1
563        }
564        self.RemoveInventoryGaps()
565    }
566
567    access(account) fun ProcessEndPod() {
568        var i: Int = 0
569        while i < self.items.length {
570            let item = self.items[i]
571            if item >= ArenaBoyzGlobals.medal_start && item < ArenaBoyzGlobals.damage_start {
572                self.max_hitpoints = self.max_hitpoints + ArenaBoyzGlobals.medal_max_hp_per_level * (1 + UInt16(item) - UInt16(ArenaBoyzGlobals.medal_start))
573            }
574            i = i + 1
575        }
576        if self.hitpoints > 0 {
577            self.max_hitpoints = self.max_hitpoints + ArenaBoyzGlobals.free_max_hp_per_level
578            self.hitpoints = self.max_hitpoints
579        }
580    }
581
582    access(account) fun RemoveInventoryGaps() {
583        var j: Int = 0 // Pointer for the next position of a non-zero element
584        var i: Int = 0
585        while i < self.items.length {
586            let item = self.items[i]
587            // When a non-zero element is found, swap it with the element at j
588            if item != 0 {
589                let temp: UInt8 = item
590                self.items[i] = self.items[j]
591                self.items[j] = temp
592                j = j + 1
593            }
594            i = i + 1
595        }
596    }
597
598    access(account) fun DiscardExcessInventory() {
599        var i: Int = 0
600        while i < self.items.length {
601            if i >= ArenaBoyzGlobals.effective_item_max {
602                self.items[i] = 0
603            }
604            i = i + 1
605        }
606    }
607
608    access(account) fun ProcessEndTurn() {
609        self.DiscardExcessInventory()
610        self.has_moved = false
611        self.stim = 0.0
612    }
613
614    access(self) fun SetTimestamp(timestamp: UFix64) {
615        self.move_timestamp = timestamp
616    }
617}
618
619
620    access(all) struct Attack {
621        access(all) var attacker: UInt32
622        access(all) var attack_target: UInt32
623        access(all) var action_target: UInt32
624        access(all) var scavenge_choice: UInt8
625        access(all) var selected_loot: UInt8
626        access(all) var equip_primary: Bool
627        access(all) var turn: UInt32
628
629        init(attacker: UInt32, attack_target: UInt32, action_target: UInt32, scavenge_choice: UInt8, equip_primary: Bool, selected_loot: UInt8, turn: UInt32) {
630            self.attacker = attacker
631            self.attack_target = attack_target
632            self.action_target = action_target
633            self.scavenge_choice = scavenge_choice
634            self.equip_primary = equip_primary
635            self.selected_loot = selected_loot
636            self.turn = turn
637        }
638
639        access(contract) fun setScavengeChoice(newScavengeChoice: UInt8) {
640            self.scavenge_choice = newScavengeChoice
641        }
642    }
643
644    access(all) struct AttackResult {
645        access(all) var attacker: UInt32
646        access(all) var target: UInt32
647        access(all) var damage: UInt32
648
649        init(attacker: UInt32, target: UInt32, damage: UInt32) {
650            self.attacker = attacker
651            self.target = target
652            self.damage = damage
653        }
654    }
655
656    
657 
658    //a player signs up to play the game here, pays the game fee
659    access(all) fun playerSignup(payment: @{FungibleToken.Vault}): @PlayerKey {
660        pre {
661            self.game_started == false: "game must not be running yet"
662            self.enrolled_players < 16: "game must have less than 16 players" // enforcing hard cap for the time being
663            payment.isInstance(Type<@FlowToken.Vault>()): "Payment must be a FlowToken Vault"
664 
665        }
666        post {
667            self.enrolled_players == before(self.enrolled_players) + 1: "Player count should increase by 1"
668        }
669        
670        assert(payment.balance == self.entry_fee, message: "insufficient funds")
671
672        // Deposit the payment into the recipient's Vault using the new approach
673        let flowPayment <- payment as! @FlowToken.Vault
674        self.payment_vault.deposit(from: <-flowPayment)
675
676        // Update internal game state
677        self.enrolled_players = self.enrolled_players + 1
678        self.current_player_id = self.current_player_id + 1
679        self.current_key_id = self.current_key_id + 1
680        
681        self.signup_keys[UInt32(self.current_player_id)] = self.current_key_id
682        
683        log(self.signup_keys[UInt32(self.current_player_id)])
684        
685        return <-create PlayerKey(player_id: UInt32(self.current_player_id), game_id: self.game_id, key_id: self.current_key_id)
686    }
687
688   access(all) fun updatePlayerKey(signup_key: @PlayerKey, payment: @{FungibleToken.Vault}): @PlayerKey {
689        pre {
690            self.game_started == false: "game must not be running yet" 
691            self.enrolled_players < 16: "game must have less than 16 players" // enforcing hard cap for the time being
692            payment.isInstance(Type<@FlowToken.Vault>()): "Payment must be a FlowToken Vault"
693        }
694        post {
695            self.enrolled_players == before(self.enrolled_players) + 1: "Player count should increase by 1"
696        }
697
698        assert(payment.balance == self.entry_fee, message: "insufficient funds")
699        
700        var i: Int = 0
701        while i < Int(self.current_player_id + 1) {
702            if self.signup_keys[UInt32(i)] == signup_key.key_id {
703                panic("player has already signed up")
704            }
705            i = i + 1
706        }
707        let flowPayment <- payment as! @FlowToken.Vault
708        self.payment_vault.deposit(from: <-flowPayment)
709        
710        signup_key.setGameID(game_id: self.game_id)
711        self.current_player_id = self.current_player_id + 1
712        signup_key.setPlayerID(player_id: UInt32(self.current_player_id))
713        
714        self.signup_keys[UInt32(self.current_player_id)] = signup_key.key_id
715        log(self.signup_keys[UInt32(self.current_player_id)])
716        
717        self.enrolled_players = self.enrolled_players + 1
718
719        return <-signup_key
720    }
721    
722    access(self) fun shuffle(array: [UInt32]) : [UInt32] {
723        let n = array.length
724        var shuffledArray = array
725
726        var i: Int = 0
727        while i < n {
728            let j = i + (Int(revertibleRandom<UInt32>()) % (n - i))
729            let temp = shuffledArray[i]
730            shuffledArray[i] = shuffledArray[j]
731            shuffledArray[j] = temp
732            i = i + 1
733        }
734        return shuffledArray
735    }
736
737    access(self) fun getPodSizes(total_players: Int32): [Int] { //pod balancing aims to create specific balacing to ensure fairness
738        var podSizes: [Int] = []
739        var totalPlayers: Int = Int(total_players)
740        if totalPlayers <= 8 {
741            podSizes = [totalPlayers]
742        } 
743        else if totalPlayers <= 16 {
744            let playersInFirstPod = (totalPlayers + 1) / 2
745            let playersInSecondPod = totalPlayers - playersInFirstPod
746            podSizes = [playersInFirstPod, playersInSecondPod]
747        } 
748        else if totalPlayers <= 24 {
749            let playersInFirstPod = (totalPlayers + 2) / 3
750            let playersInSecondPod = (totalPlayers + 1) / 3
751            let playersInThirdPod = totalPlayers - playersInFirstPod - playersInSecondPod
752            podSizes = [playersInFirstPod, playersInSecondPod, playersInThirdPod]
753        } 
754        else {
755            while totalPlayers > 24 {
756                podSizes.append(8)
757                totalPlayers = totalPlayers - 8
758            }
759            if totalPlayers <= 8 {
760                podSizes.append(totalPlayers)
761            } 
762            else if totalPlayers <= 16 {
763                let playersInFirstPod = (totalPlayers + 1) / 2
764                let playersInSecondPod = totalPlayers - playersInFirstPod
765                podSizes.append(playersInFirstPod)
766                podSizes.append(playersInSecondPod)
767            } 
768            else {
769                let playersInFirstPod = (totalPlayers + 2) / 3
770                let playersInSecondPod = (totalPlayers + 1) / 3
771                let playersInThirdPod = totalPlayers - playersInFirstPod - playersInSecondPod
772                podSizes.append(playersInFirstPod)
773                podSizes.append(playersInSecondPod)
774                podSizes.append(playersInThirdPod)
775            }
776        }
777        var maxPodSize = 0
778        var i = 0
779        while i < podSizes.length {
780            if podSizes[i] > maxPodSize {
781                maxPodSize = podSizes[i]
782            }
783            i = i + 1
784        }
785        self.pod_size = UInt32(maxPodSize)
786        return podSizes
787    }
788
789    access(self) fun initializeHeroesAndPods(podSizes : [Int]) {
790        pre {
791            self.game_started == false : "game must not be started already"
792        }
793        if(self.enrolled_players < Int32(self.pod_size)) { //When we go back to pods, this will come back into play
794            self.pod_size = UInt32(self.enrolled_players)
795        }
796        var hero_ids: [UInt32] = []
797        var id : UInt32 = 0
798        while id < UInt32(self.enrolled_players) {
799            hero_ids.append(id)
800            self.heroes.append(Hero())
801            self.heroes[id].GenerateHero(hero_id: id, starting_pod_id: 0)
802             if let signupKey = self.signup_keys[id] {
803                self.heroes[id].setKeyId(newKeyId: signupKey)
804                log(signupKey)
805            } else {
806                panic("Signup key for hero ID is nil")
807            }
808            id = id + 1
809        }
810        var podIndex = 0
811        while podIndex < podSizes.length {
812            self.pods.append(Pod())
813            log("appended pod")
814            podIndex = podIndex + 1
815        }
816        hero_ids = self.shuffle(array: hero_ids)
817        podIndex = 0
818        var currentHeroIndex: Int = 0
819            podIndex = 0
820            while podIndex < podSizes.length {
821                let podSize = podSizes[podIndex]
822                var podHeroCount = 0
823                while podHeroCount < podSize && currentHeroIndex < hero_ids.length {
824                    let heroID = hero_ids[currentHeroIndex]
825                    self.pods[podIndex].hero_ids.append(heroID)
826                    self.heroes[heroID].setCurrentPodId(newCurrentPodId: UInt32(podIndex))
827                    log("appending hero")
828                    log(heroID)
829                    log(UInt32(podIndex))
830                    currentHeroIndex = currentHeroIndex + 1
831                    podHeroCount = podHeroCount + 1
832                }
833
834                podIndex = podIndex + 1
835            }
836
837      
838
839        var largest_pod: Int = 0
840        var podIndex2: Int = 0
841
842        while podIndex2 < self.pods.length {
843            let pod = self.pods[podIndex2]
844            
845            var localHeroes: [Hero] = []
846            var heroIdIndex: Int = 0
847
848            while heroIdIndex < pod.hero_ids.length {
849                let hero_id = pod.hero_ids[heroIdIndex]
850                localHeroes.append(self.heroes[hero_id])
851                heroIdIndex = heroIdIndex + 1
852            }
853
854            pod.SortPlayers(Players: localHeroes)
855
856            var i: Int = 0
857            while i < Int(pod.hero_ids.length) {
858                let podHeroId: UInt32 = pod.hero_ids[i]
859                self.heroes[podHeroId].setMoveTimestamp(newMoveTimestamp : getCurrentBlock().timestamp + UFix64(i * self.seconds_per_turn))
860                i = i + 1
861            }
862
863            if(i > largest_pod) {
864                largest_pod = i
865            }
866
867            podIndex2 = podIndex2 + 1
868        }
869        log("largest pod")
870        log(largest_pod)
871        self.turn_end_timestamp = getCurrentBlock().timestamp + UFix64(self.seconds_per_turn * largest_pod)
872        self.game_started = true
873        log(self.turn)
874    }
875
876    access(self) fun resetTurn(podSizes : [Int]) {
877        pre {
878            self.game_started == true : "game must be running"
879        }
880        self.turn = self.turn + 1
881        if (self.turn % ArenaBoyzGlobals.turns_per_circle == 0) {
882            self.circle = self.circle + 1
883            var aliveHeroIDs: [UInt32] = []
884            var heroIndex = 0
885
886            // Collect all alive heroes
887            while heroIndex < self.heroes.length {
888                let hero = self.heroes[heroIndex]
889                if (hero.hitpoints > 0) {
890                    aliveHeroIDs.append(hero.hero_id)
891                }
892                heroIndex = heroIndex + 1
893            }
894
895            while (self.pods.length > 0) {
896                self.pods.removeLast()
897            }
898
899            var podIndex = 0
900            while podIndex < podSizes.length {
901                self.pods.append(Pod())
902                podIndex = podIndex + 1
903            }
904
905            var shuffledHeroIDs: [UInt32] = self.shuffle(array: aliveHeroIDs)
906
907            var currentHeroIndex: Int = 0
908            podIndex = 0
909            while podIndex < podSizes.length {
910                let podSize = podSizes[podIndex]
911
912                var podHeroCount = 0
913                while podHeroCount < podSize && currentHeroIndex < shuffledHeroIDs.length {
914                    let heroID = shuffledHeroIDs[currentHeroIndex]
915                    self.pods[podIndex].hero_ids.append(heroID)
916                    self.heroes[heroID].setCurrentPodId(newCurrentPodId : UInt32(podIndex))
917                    currentHeroIndex = currentHeroIndex + 1
918                    podHeroCount = podHeroCount + 1
919                }
920
921                podIndex = podIndex + 1
922            }
923        }
924        var pod_i: Int = 0
925
926        while pod_i < self.pods.length {
927            let pod = self.pods[pod_i]
928            
929            if(self.turn % ArenaBoyzGlobals.turns_per_circle == 0) {
930                self.pods[pod_i].ResetScavengePool(Circle: self.circle)
931            }
932            
933            var heroIdIndex: Int = 0
934            
935            if(self.turn / ArenaBoyzGlobals.turns_per_circle < 7) { // end of game, final circle!
936                while heroIdIndex < pod.hero_ids.length {
937                    let heroId = pod.hero_ids[heroIdIndex]
938                    self.heroes[heroId].ProcessEndTurn()
939                    if(self.turn % ArenaBoyzGlobals.turns_per_circle == 0) {
940                        self.heroes[heroId].ProcessEndPod()
941                    }
942                    heroIdIndex = heroIdIndex + 1
943                }
944            } else {
945                while heroIdIndex < pod.hero_ids.length {
946                    let heroId = pod.hero_ids[heroIdIndex]
947                    self.heroes[heroId].ProcessEndTurn()
948                    if(self.turn % ArenaBoyzGlobals.turns_per_circle == 0) {
949                        self.heroes[heroId].ProcessEndPod()
950                    }
951                    heroIdIndex = heroIdIndex + 1
952                }
953            }
954            
955            // Shorten turn length when a player is killed, but do this only one time per player
956            var pod_survivors: Int = 0
957            var live_i: Int = 0
958            while live_i < pod.hero_ids.length {
959                let heroId = pod.hero_ids[live_i]
960                if self.heroes[heroId].hitpoints > 0 {
961                    pod_survivors = pod_survivors + 1
962                }
963                live_i = live_i + 1
964            }
965            
966            pod_i = pod_i + 1
967        }
968        // Sort players within each pod and set move timestamps
969        var largest_pod: Int = 0
970        var podIndex3: Int = 0
971
972        while podIndex3 < self.pods.length {
973            let pod = self.pods[podIndex3]
974
975            // Create a local array of heroes for this pod
976            var localHeroes: [Hero] = []
977            var heroIdIndex: Int = 0
978
979            while heroIdIndex < pod.hero_ids.length {
980                let hero_id = pod.hero_ids[heroIdIndex]
981                localHeroes.append(self.heroes[hero_id])
982                heroIdIndex = heroIdIndex + 1
983            }
984
985            // Sort the local array of heroes
986            pod.SortPlayers(Players: localHeroes)
987
988            var i: Int = 0
989            while i < Int(pod.hero_ids.length) {
990                let podHeroId: UInt32 = pod.hero_ids[i]
991                self.heroes[podHeroId].setMoveTimestamp(newMoveTimestamp: getCurrentBlock().timestamp + UFix64(i * self.seconds_per_turn))
992                i = i + 1
993            }
994
995            if(i > largest_pod) {
996                largest_pod = i
997            }
998
999            podIndex3 = podIndex3 + 1
1000        }
1001
1002        self.setTurnEndTimestamp(newTurnEndTimestamp: getCurrentBlock().timestamp + UFix64(self.seconds_per_turn * largest_pod))
1003        log(self.turn)
1004    }
1005    //TODO: reshuffle pods on new circle. circle should be incremented, and players should be randomly shuffled into new pods.
1006
1007    //Player commits to an attack here
1008    access(all) fun submitAttack(attacker_key: @PlayerKey, attack_target : UInt32, action_target : UInt32, scavenge_choice : UInt8, equip_primary : Bool): @PlayerKey {
1009        pre {
1010           
1011            //TODO: speed management goes here. players should be prohibited from entering attacks before it is their turn to do so
1012            //see self.tick and self.turn vs. their speed. it should also be recorded if they have moved already this turn.
1013                
1014                attacker_key.game_id == self.game_id : "attacker must be from this game"
1015                self.heroes[attacker_key.player_id].hitpoints > 0 : "attacker must be alive!"
1016                self.heroes[attack_target].hitpoints > 0 : "attack target must be alive!"
1017                self.heroes[action_target].hitpoints > 0 : "action target must be alive!"
1018                self.heroes[attacker_key.player_id].has_moved == false : "hero has already attacked!"
1019                self.heroes[attacker_key.player_id].move_timestamp <= getCurrentBlock().timestamp : "attacker is not allowed to enter an attack yet!"
1020                self.heroes[attacker_key.player_id].current_pod_id == self.heroes[attack_target].current_pod_id : "attack target is not in the same pod!"
1021                self.heroes[attacker_key.player_id].current_pod_id == self.heroes[action_target].current_pod_id : "action target is not in the same pod!"
1022                //TODO: Can we put in some constants or globals for design-related items in code
1023            }
1024        
1025        self.heroes[attacker_key.player_id].setKeyId(newKeyId : attacker_key.key_id) //this is an okay place to do this, but a player who never attacks will never set their key_id
1026        var a_remove: Int = 0
1027        while a_remove < self.attacks.length {
1028            let attack = self.attacks[a_remove]
1029
1030            if(attack.attacker == attacker_key.player_id) { // if the player has submitted an attack already, remove it
1031                self.attacks.remove(at: a_remove)
1032                break
1033            }
1034
1035            a_remove = a_remove + 1
1036        }
1037
1038        self.attacks.append(Attack(attacker : attacker_key.player_id, attack_target : attack_target, action_target : action_target, scavenge_choice : scavenge_choice, equip_primary: equip_primary, selected_loot:self.pods[self.heroes[attacker_key.player_id].current_pod_id].scavenge_pool[scavenge_choice], turn: self.turn ))
1039        //TODO: we can change contract state here, such as setting a block height
1040        return <-attacker_key
1041    }
1042    access(all) fun dropItem(dropper_key: @PlayerKey, item_index : Int): @PlayerKey {
1043        pre {
1044            dropper_key.game_id == self.game_id : "dropper must be from this game"
1045        }
1046        let dropped_item: Int = Int(self.heroes[dropper_key.player_id].items[item_index])
1047        self.heroes[dropper_key.player_id].items[item_index] = 0
1048        self.pods[self.heroes[dropper_key.player_id].current_pod_id].DropItemIntoPool(Item: UInt8(dropped_item))
1049        return <-dropper_key
1050    }
1051    access(all) fun equipItem(equipper_key: @PlayerKey, item_index : Int): @PlayerKey {
1052        pre{
1053           equipper_key.game_id == self.game_id : "dropper must be from this game"
1054           self.heroes[equipper_key.player_id].items[item_index] < ArenaBoyzGlobals.targetables_start : "has to pick a primary weapon to equip primary!"
1055        }
1056        let old_primary: Int = Int(self.heroes[equipper_key.player_id].items[0])
1057        self.heroes[equipper_key.player_id].items[0] = self.heroes[equipper_key.player_id].items[item_index]
1058        self.heroes[equipper_key.player_id].items[item_index] = UInt8(old_primary)
1059
1060        return <-equipper_key
1061    }
1062    //at the time handleDeath is called, all damage has already been applied to both attack target and attacker due to counter. so either 
1063    //both players are already dead, or just the attacker is.
1064    access(self) fun handleDeath(dead_player: UInt32): Bool {
1065        log("handling death")
1066        var alive_heroes: Int = 0
1067        var winner_id: Int32 = -1
1068        var runner_up_id: Int32 = -1
1069        var second_runner_up_id: Int32 = -1
1070        var game_over: Bool = false
1071
1072        var heroIndex: Int = 0
1073        var dead_player_key: Int32 = -1
1074        for hero in self.heroes {
1075            if(hero.hero_id == dead_player) {
1076                dead_player_key = Int32(hero.key_id)
1077            }
1078        }
1079
1080        while heroIndex < self.heroes.length {
1081            let hero = self.heroes[heroIndex]
1082
1083            if(hero.hitpoints > 0) {
1084                alive_heroes = alive_heroes + 1
1085                winner_id = Int32(hero.key_id)
1086                log(Int32(hero.key_id))
1087            }
1088
1089            heroIndex = heroIndex + 1
1090        }
1091
1092        if(alive_heroes == 1) {
1093            self.runner_up_id = dead_player_key
1094
1095            emit GameResultEvent(game_id: self.game_id, victor_id: winner_id, ending_turn: self.turn, enrolled_players: self.enrolled_players)
1096            ArenaBoyzHistory.addGame(game_id: self.game_id, victor_id: winner_id, runner_up_id: self.runner_up_id, second_runner_up_id: self.second_runner_up_id, ending_turn: self.turn, enrolled_players: self.enrolled_players)
1097            game_over = true
1098        }
1099        else if(alive_heroes == 2) {
1100           
1101            self.second_runner_up_id = dead_player_key
1102        }
1103        if(game_over) {
1104            //self.resetGame()
1105            return true
1106        }
1107        return false
1108    }
1109    access(self) fun resolveAttacks() {
1110        var validAttacks: [Attack] = [] //new code to remove attacks that have not been flagged
1111    
1112        for attack in self.attacks {
1113            if attack.turn == self.turn {
1114                validAttacks.append(attack)
1115            }
1116        }
1117        self.attacks = validAttacks
1118
1119        var conflicts: {UInt8: ScavengeConflict} = {}
1120        var conflict_i = 0
1121        for attack in self.attacks {
1122            let item_id = attack.scavenge_choice
1123            let hero_id = attack.attacker
1124
1125            if conflicts[item_id] != nil {
1126                conflicts[item_id]?.addConflict(hero_id: hero_id, attack_index: conflict_i)
1127            } else {
1128                conflicts[item_id] = ScavengeConflict( hero_ids: [hero_id], attack_indicies: [conflict_i])
1129            }
1130            conflict_i = conflict_i + 1
1131        }
1132
1133        for conflict in conflicts.values {
1134            if conflict.hero_ids.length > 1 {
1135                // More than one hero is trying to scavenge the same item, resolve conflict
1136                let winnerIndex = Int(revertibleRandom<UInt32>())%conflict.hero_ids.length
1137                var attack_index_i = 0
1138                while attack_index_i < conflict.attack_indicies.length {
1139                    let attack_index = conflict.attack_indicies[attack_index_i]
1140                    
1141                    if attack_index_i != winnerIndex {
1142                        // Losers get consolation item
1143                        self.attacks[attack_index].setScavengeChoice(newScavengeChoice: 0)
1144                    }
1145                    // The winner keeps their original choice, no action needed
1146                    
1147                    attack_index_i = attack_index_i + 1
1148                }
1149            }
1150            // If only one hero is involved, no conflict to resolve
1151        }
1152        var resetGame = false
1153        for attack in self.attacks {
1154            self.heroes[attack.attacker].setHasMoved(newHasMoved: true)
1155            self.heroes[attack.attacker].setShields(newShields: 0)
1156            var attack_pod: UInt32 = self.heroes[attack.attacker].current_pod_id
1157            var attacker_hp: UInt16 = self.heroes[attack.attacker].hitpoints
1158            var target_hp: UInt16 = self.heroes[attack.attack_target].hitpoints
1159            var action_target_hp: UInt16 = 0
1160            var attacker_hp_max: UInt16 = self.heroes[attack.attacker].max_hitpoints
1161            var target_hp_max: UInt16 = self.heroes[attack.attack_target].max_hitpoints
1162            var action_target_hp_max: UInt16 = 0
1163            var stimBoost: UFix64 = 1.0
1164            var scavenged_item_id: Int = 0
1165            var actionAmount: UFix64 = 0.0
1166
1167            //item and looting logic
1168            if(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] >= ArenaBoyzGlobals.throwable_start && 
1169            self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] < ArenaBoyzGlobals.healing_start) { //throwing weapon
1170                var thrownDamage: UInt16 = UInt16(ArenaBoyzGlobals.throwable_damage_per_level * (1 + UInt16(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice]) - UInt16(ArenaBoyzGlobals.throwable_start)))
1171                var originalThrownDamage: UInt16 = thrownDamage
1172                action_target_hp =  self.heroes[attack.action_target].hitpoints
1173                action_target_hp_max =  self.heroes[attack.action_target].max_hitpoints
1174                if(self.heroes[attack.action_target].shields > thrownDamage) {
1175                    thrownDamage = 0
1176                }
1177                else {
1178                    thrownDamage = thrownDamage - self.heroes[attack.action_target].shields
1179                }
1180                if(originalThrownDamage > self.heroes[attack.action_target].shields ) {
1181                    self.heroes[attack.action_target].setShields(newShields: 0)
1182                }
1183                else {
1184                    self.heroes[attack.action_target].setShields(newShields: self.heroes[attack.action_target].shields - originalThrownDamage)
1185                }
1186                thrownDamage = self.CalculateDamage(baseDamage : thrownDamage, defense:  self.heroes[attack.action_target].GetDefense());
1187                actionAmount = UFix64(thrownDamage)
1188                if(thrownDamage > self.heroes[attack.action_target].hitpoints) {
1189                    self.heroes[attack.action_target].setHitpoints(newHitpoints: 0)
1190                }
1191                else {
1192                    self.heroes[attack.action_target].setHitpoints(newHitpoints: self.heroes[attack.action_target].hitpoints - thrownDamage)
1193                }
1194            }
1195            if(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] >= ArenaBoyzGlobals.healing_start && 
1196            self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] < ArenaBoyzGlobals.shield_start) {
1197                
1198                action_target_hp =  self.heroes[attack.action_target].hitpoints
1199                action_target_hp_max =  self.heroes[attack.action_target].max_hitpoints
1200                    var hitpointsBefore : UInt16 = self.heroes[attack.action_target].hitpoints
1201                    self.heroes[attack.action_target].setHitpoints(newHitpoints: self.heroes[attack.action_target].hitpoints + 100 + UInt16(ArenaBoyzGlobals.heal_per_level * (1 + UInt16(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice]) - UInt16(ArenaBoyzGlobals.healing_start))))
1202                    actionAmount = UFix64(UInt16(100) + UInt16(ArenaBoyzGlobals.heal_per_level * (1 + UInt16(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice]) - UInt16(ArenaBoyzGlobals.healing_start))))
1203                    if(self.heroes[attack.action_target].hitpoints > self.heroes[attack.action_target].max_hitpoints) {
1204                        actionAmount = UFix64(self.heroes[attack.action_target].max_hitpoints - hitpointsBefore)
1205                        self.heroes[attack.action_target].setHitpoints(newHitpoints: self.heroes[attack.action_target].max_hitpoints)
1206                        
1207                    }
1208
1209            }
1210            if(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] >= ArenaBoyzGlobals.shield_start && 
1211            self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] < ArenaBoyzGlobals.stim_start) {
1212                action_target_hp =  self.heroes[attack.action_target].hitpoints
1213                action_target_hp_max =  self.heroes[attack.action_target].max_hitpoints
1214                self.heroes[attack.action_target].setShields(newShields: self.heroes[attack.action_target].shields + 100 + UInt16(ArenaBoyzGlobals.shield_per_level * (1 + UInt16(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice]) - UInt16(ArenaBoyzGlobals.shield_start))))
1215                actionAmount = UFix64(UInt16(100) + UInt16(ArenaBoyzGlobals.shield_per_level * (1 + UInt16(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice]) - UInt16(ArenaBoyzGlobals.shield_start))))
1216            }
1217            if(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] >= ArenaBoyzGlobals.stim_start && 
1218            self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] < ArenaBoyzGlobals.medal_start) {
1219                action_target_hp =  self.heroes[attack.action_target].hitpoints
1220                action_target_hp_max =  self.heroes[attack.action_target].max_hitpoints
1221                self.heroes[attack.action_target].setStim(newStim : self.heroes[attack.action_target].stim + ArenaBoyzGlobals.stim_per_level * UFix64(1 + Int(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice]) - Int(ArenaBoyzGlobals.stim_start)))
1222                actionAmount = UFix64(ArenaBoyzGlobals.stim_per_level * UFix64(1 + Int(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice]) - Int(ArenaBoyzGlobals.stim_start)))
1223            }
1224            if(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] >= ArenaBoyzGlobals.medal_start && 
1225            self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] < ArenaBoyzGlobals.damage_start) {
1226                action_target_hp =  self.heroes[attack.action_target].hitpoints
1227                action_target_hp_max =  self.heroes[attack.action_target].max_hitpoints
1228                self.heroes[attack.action_target].setMaxHitpoints(newMaxHitpoints : self.heroes[attack.action_target].max_hitpoints+ ArenaBoyzGlobals.medal_max_hp_per_level * (1 + UInt16(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice]) - UInt16(ArenaBoyzGlobals.medal_start)))
1229                actionAmount = UFix64(ArenaBoyzGlobals.medal_max_hp_per_level * (1 + UInt16(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice]) - UInt16(ArenaBoyzGlobals.medal_start)))
1230            }
1231            stimBoost = stimBoost + self.heroes[attack.attacker].stim
1232            self.heroes[attack.attacker].setStim(newStim : 0.0)
1233            var scavenging_iterator: Int = 0
1234            if(!attack.equip_primary || self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] == 0 || self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] >= ArenaBoyzGlobals.targetables_start) {
1235                var index = 0
1236                while index < self.heroes[attack.attacker].items.length {
1237                    let item = self.heroes[attack.attacker].items[index]
1238                    if(item == 0) { // Stop if they go over max inventory of 12, don't let them loot in this case.
1239                        self.heroes[attack.attacker].items[index] = self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice]
1240                        scavenged_item_id = index
1241                        break
1242                    }
1243                    index = index + 1
1244                    scavenging_iterator = scavenging_iterator + 1
1245                }
1246            }
1247
1248            else { //Or they are equipping a primary, in which case, they will swap out their existing weapon and move it to their first available spot
1249              for item in self.heroes[attack.attacker].items { //get our scavenge item if it isn't being equipped- it goes into the first empty inventory slot
1250                    if(item == 0) { //TODO - stop if they go over max inventory of 12, dont let them loot in this case.
1251                        scavenged_item_id = scavenging_iterator
1252                        break;
1253                    }
1254                    scavenging_iterator = scavenging_iterator + 1
1255                }
1256                var oldPrimary: UInt8 = self.heroes[attack.attacker].items[0]
1257                self.heroes[attack.attacker].items[0] = self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice]
1258                
1259                if(oldPrimary != 0) { //fixes a bug if player is picking up a primary when they have no items
1260                    self.heroes[attack.attacker].items[scavenged_item_id] = oldPrimary;
1261                }
1262            }
1263            //"tighten up" scavenge pool, closing in on the taken item
1264         /*    var currentIndex: Int = Int(attack.scavenge_choice);
1265            while ( currentIndex < self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool.length - 1) {
1266                self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[currentIndex] = self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[currentIndex + 1]
1267                currentIndex = currentIndex + 1
1268            }*/
1269            self.heroes[attack.attacker].ProcessConsumableItems();
1270            self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] = 0
1271
1272            //Attack damage is calculated below
1273            var isCritical: Bool = (UInt16(revertibleRandom<UInt32>() & 0xFFFF)%1000 < self.heroes[attack.attacker].GetAccuracy())
1274            var isCounterCritical: Bool = (UInt16(revertibleRandom<UInt32>() & 0xFFFF)%1000 < self.heroes[attack.attack_target].GetAccuracy())
1275            var counterAttack: Bool = (UInt16(revertibleRandom<UInt32>() & 0xFFFF)%1000 < self.heroes[attack.attack_target].GetCounter())// - shieldMod
1276            var damageDealt: UInt16 = self.heroes[attack.attacker].GetDamage()
1277           
1278            var originalDamageDealt: UInt16 = damageDealt
1279            if(self.heroes[attack.attack_target].shields > damageDealt) {
1280                damageDealt = 0
1281            }
1282            else {
1283                damageDealt = damageDealt - self.heroes[attack.attack_target].shields
1284            }
1285            if(originalDamageDealt > self.heroes[attack.attack_target].shields ) {
1286                self.heroes[attack.attack_target].setShields(newShields : 0)
1287            }
1288            else {
1289                self.heroes[attack.attack_target].setShields(newShields :self.heroes[attack.attack_target].shields - originalDamageDealt)
1290            }
1291            damageDealt = self.CalculateDamage(baseDamage: damageDealt, defense: self.heroes[attack.attack_target].GetDefense())
1292            
1293            if (isCritical)
1294            {
1295                damageDealt = damageDealt * 2
1296            } 
1297           
1298            // Process the counterattack
1299            var counterDamage: UInt16 = 0
1300            if (counterAttack)
1301            {
1302                counterDamage = self.CalculateDamage(baseDamage : self.heroes[attack.attack_target].GetDamage(), defense:  self.heroes[attack.attacker].GetDefense());
1303                
1304                var originalCounterDamageDealt: UInt16 = counterDamage
1305                
1306                if(self.heroes[attack.attacker].shields > counterDamage) {
1307                    counterDamage = 0
1308                }
1309                else {
1310                    counterDamage = counterDamage - self.heroes[attack.attacker].shields
1311                }
1312               
1313                if(originalCounterDamageDealt > self.heroes[attack.attacker].shields ) {
1314                    self.heroes[attack.attacker].setShields(newShields : 0)
1315                }
1316                else {
1317                    self.heroes[attack.attacker].setShields(newShields : self.heroes[attack.attacker].shields - originalCounterDamageDealt)
1318                }
1319                  if (isCounterCritical)
1320                {
1321                    counterDamage = counterDamage * 2
1322                }
1323            }
1324            //    access(all) event AttackResult(attacker: UInt32, target: UInt32, damage: UInt32, critical: Bool, counter_damage: UInt32, action_target: UInt32, action_damage: UInt32, action_item: UInt8 )
1325
1326            //get attack awards before damage is dealt, so we get damage scaling
1327            if(self.heroes[attack.attack_target].attack_award == 0) {
1328                self.heroes[attack.attacker].setDamage(newDamage: self.heroes[attack.attacker].damage + UInt16(self.heroes[attack.attack_target].GetAttackAwardAmount() * stimBoost))
1329            }
1330            if(self.heroes[attack.attack_target].attack_award == 1) {
1331                self.heroes[attack.attacker].setDefense(newDefense: self.heroes[attack.attacker].defense + UInt16(self.heroes[attack.attack_target].GetAttackAwardAmount() * stimBoost))
1332            }
1333            if(self.heroes[attack.attack_target].attack_award == 2) {
1334                self.heroes[attack.attacker].setAccuracy(newAccuracy: self.heroes[attack.attacker].accuracy + UInt16(self.heroes[attack.attack_target].GetAttackAwardAmount() * stimBoost))
1335            }
1336            if(self.heroes[attack.attack_target].attack_award == 3) {
1337                self.heroes[attack.attacker].setSpeed(newSpeed: self.heroes[attack.attacker].speed + UInt16(self.heroes[attack.attack_target].GetAttackAwardAmount() * stimBoost))
1338            }
1339            if(self.heroes[attack.attack_target].attack_award == 4) {
1340                self.heroes[attack.attacker].setCounter(newCounter: self.heroes[attack.attacker].counter + UInt16(self.heroes[attack.attack_target].GetAttackAwardAmount() * stimBoost))
1341            }
1342            if(self.heroes[attack.attack_target].hitpoints < damageDealt) {
1343                self.heroes[attack.attack_target].setHitpoints(newHitpoints: 0)
1344            }
1345            else {
1346                self.heroes[attack.attack_target].setHitpoints(newHitpoints: self.heroes[attack.attack_target].hitpoints - damageDealt)
1347            }
1348            var game_over : Bool = false
1349            var event_emitted : Bool = false
1350            if(self.heroes[attack.attack_target].hitpoints <= 0) {
1351                counterDamage = 0
1352                emit AttackResultEvent(pod: attack_pod, attacker: attack.attacker, attacker_key: self.heroes[attack.attacker].key_id, target_key: self.heroes[attack.attack_target].key_id, action_key: self.heroes[attack.action_target].key_id, target: attack.attack_target,  damage: damageDealt, critical: isCritical, counter_critical: isCounterCritical, counter_damage: counterDamage, action_target: attack.action_target, action_amount: actionAmount, action_item: attack.selected_loot, game_id: self.game_id, attacker_hp: attacker_hp, target_hp: target_hp, action_target_hp: action_target_hp, attacker_hp_max: attacker_hp_max, target_hp_max: target_hp_max, action_target_hp_max: action_target_hp_max )
1353                event_emitted = true
1354                self.heroes[attack.attack_target].setCurrentPodId(newCurrentPodId: 4294967295);
1355                var target_item_iterator: Int = 0
1356                while target_item_iterator < self.heroes[attack.attack_target].items.length {
1357                    let target_item = self.heroes[attack.attack_target].items[target_item_iterator]
1358
1359                    var attacker_item_iterator: Int = 0
1360                    while attacker_item_iterator < self.heroes[attack.attacker].items.length { // process looting
1361                        let attacker_item = self.heroes[attack.attacker].items[attacker_item_iterator]
1362
1363                        if(attacker_item == 0) {
1364                            self.heroes[attack.attacker].setItem(newItemIndex: attacker_item_iterator, newItemID: target_item)
1365                            self.heroes[attack.attack_target].setItem(newItemIndex: target_item_iterator, newItemID: 0)
1366                            break // exit the inner loop after setting the item
1367                        }
1368
1369                        attacker_item_iterator = attacker_item_iterator + 1
1370                    }
1371
1372                    target_item_iterator = target_item_iterator + 1
1373                }
1374                if(self.handleDeath(dead_player: attack.attack_target)) {
1375                    game_over = true
1376                    resetGame = true
1377                    break
1378                }
1379            }
1380            if(!game_over && self.heroes[attack.action_target].hitpoints <= 0) {
1381                counterDamage = 0;
1382                if(!event_emitted) {
1383                    emit AttackResultEvent(pod: attack_pod, attacker: attack.attacker, attacker_key: self.heroes[attack.attacker].key_id, target_key: self.heroes[attack.attack_target].key_id, action_key: self.heroes[attack.action_target].key_id, target: attack.attack_target,  damage: damageDealt, critical: isCritical, counter_critical: isCounterCritical, counter_damage: counterDamage, action_target: attack.action_target, action_amount: actionAmount, action_item: attack.selected_loot, game_id: self.game_id, attacker_hp: attacker_hp, target_hp: target_hp, action_target_hp: action_target_hp, attacker_hp_max: attacker_hp_max, target_hp_max: target_hp_max, action_target_hp_max: action_target_hp_max )
1384                    event_emitted = true
1385                }
1386                self.heroes[attack.action_target].setCurrentPodId(newCurrentPodId: 4294967295)
1387                var action_target_item_iterator: Int = 0
1388                while action_target_item_iterator < self.heroes[attack.action_target].items.length {
1389                    let action_target_item = self.heroes[attack.action_target].items[action_target_item_iterator]
1390
1391                    var attacker_item_iterator: Int = 0
1392                    while attacker_item_iterator < self.heroes[attack.attacker].items.length { // process looting
1393                        let attacker_item = self.heroes[attack.attacker].items[attacker_item_iterator]
1394
1395                        if(attacker_item == 0) {
1396                            self.heroes[attack.attacker].setItem(newItemIndex: attacker_item_iterator, newItemID: action_target_item)
1397                            self.heroes[attack.action_target].setItem(newItemIndex: action_target_item_iterator, newItemID: 0)
1398                            break // Exit the inner loop after successfully setting the item
1399                        }
1400
1401                        attacker_item_iterator = attacker_item_iterator + 1
1402                    }
1403
1404                    action_target_item_iterator = action_target_item_iterator + 1
1405                }
1406                if(self.handleDeath(dead_player : attack.action_target)) {
1407                    resetGame = true
1408                    game_over = true
1409                    break
1410                }
1411            }
1412            if(self.heroes[attack.attacker].hitpoints < counterDamage) {
1413                self.heroes[attack.attacker].setHitpoints(newHitpoints: 0)
1414            }
1415            else {
1416                self.heroes[attack.attacker].setHitpoints(newHitpoints: self.heroes[attack.attacker].hitpoints - counterDamage)
1417            }
1418           
1419            
1420            if(!event_emitted) {
1421                emit AttackResultEvent(pod: attack_pod, attacker: attack.attacker, attacker_key: self.heroes[attack.attacker].key_id, target_key: self.heroes[attack.attack_target].key_id, action_key: self.heroes[attack.action_target].key_id, target: attack.attack_target,  damage: damageDealt, critical: isCritical, counter_critical: isCounterCritical, counter_damage: counterDamage, action_target: attack.action_target, action_amount: actionAmount, action_item: attack.selected_loot, game_id: self.game_id, attacker_hp: attacker_hp, target_hp: target_hp, action_target_hp: action_target_hp, attacker_hp_max: attacker_hp_max, target_hp_max: target_hp_max, action_target_hp_max: action_target_hp_max )
1422                event_emitted = true
1423            }
1424            if(!game_over && self.heroes[attack.attacker].hitpoints <= 0) {
1425                self.heroes[attack.attacker].setCurrentPodId(newCurrentPodId: 4294967295)
1426                var counter_item_iterator: Int = 0
1427                while counter_item_iterator < self.heroes[attack.attacker].items.length {
1428                    let counter_target_item = self.heroes[attack.attacker].items[counter_item_iterator]
1429
1430                    var attacker_item_iterator: Int = 0
1431                    while attacker_item_iterator < self.heroes[attack.attack_target].items.length { // process looting
1432                        let attacker_item = self.heroes[attack.attack_target].items[attacker_item_iterator]
1433
1434                        if(attacker_item == 0) {
1435                            self.heroes[attack.attack_target].setItem(newItemIndex: attacker_item_iterator, newItemID: counter_target_item)
1436                            self.heroes[attack.attacker].setItem(newItemIndex: counter_item_iterator, newItemID: 0)
1437                            break // Exit the inner loop after successfully setting the item
1438                        }
1439
1440                        attacker_item_iterator = attacker_item_iterator + 1
1441                    }
1442
1443                    counter_item_iterator = counter_item_iterator + 1
1444                }
1445                if(self.handleDeath(dead_player : attack.attacker)) {
1446                    resetGame = true
1447                }
1448                    
1449            }
1450
1451        }
1452        if(resetGame) {
1453            self.resetGame()
1454        }
1455        while(self.attacks.length > 0){
1456            self.attacks.removeLast()
1457        }
1458    }
1459    access(self) fun resetGame() {
1460        while(self.heroes.length > 0) {
1461            self.heroes.removeLast()
1462        }
1463        while(self.attacks.length > 0){
1464            self.attacks.removeLast()
1465        }
1466        while(self.pods.length > 0){
1467            self.pods.removeLast()
1468        }
1469        self.signup_keys = {}
1470        self.game_id = self.game_id + 1
1471        self.enrolled_players = 0 //enrolled players starting here fixes a problem but it also does not reflect now the number of players in the game0
1472        self.current_player_id = -1
1473        self.seconds_per_turn = ArenaBoyzGlobals.seconds_per_turn
1474        self.turn = 0
1475        self.turn_end_timestamp = 0.0
1476        self.circle = 0
1477        self.pod_size = ArenaBoyzGlobals.heroes_per_pod
1478        self.game_started = false
1479    }
1480    
1481    access(self) fun setEntryFee(entry_fee : UFix64) {
1482        self.entry_fee = entry_fee
1483    }
1484    access(all) fun CalculateDamage(baseDamage : UInt16, defense : UInt16) : UInt16
1485    {
1486        var damage : UInt16 = 0
1487        if(defense > baseDamage) {
1488            damage = 0
1489        }
1490        else {
1491            damage = baseDamage - defense
1492        }
1493        if(damage < baseDamage/ArenaBoyzGlobals.minimum_damage_divisor) {
1494            damage = baseDamage/ArenaBoyzGlobals.minimum_damage_divisor //min damage is 20% of your base
1495        }
1496        return damage
1497    }
1498    access(all) resource interface PlayerKeyInterface {
1499        access(all) fun getPlayerID(): UInt32
1500        access(all) fun getKeyID(): Int
1501    }
1502    access(all) resource PlayerKey: PlayerKeyInterface {
1503        access(contract) var player_id: UInt32
1504        access(contract) var game_id: UInt32
1505        access(contract) var key_id: Int
1506
1507         init(
1508            player_id: UInt32,
1509            game_id: UInt32,
1510            key_id: Int
1511        ) {
1512            self.player_id = player_id
1513            self.game_id = game_id
1514            self.key_id = key_id
1515        }
1516        access(all) fun getPlayerID() : UInt32 {
1517            return self.player_id
1518        }
1519        access(all) fun getKeyID() : Int {
1520            return self.key_id
1521        }
1522        access(contract) fun setGameID(game_id : UInt32) {
1523            self.game_id = game_id
1524            log(self.game_id)
1525        }
1526        access(contract) fun setPlayerID(player_id : UInt32) {
1527            self.player_id = player_id
1528        }
1529
1530    }
1531    
1532    access(all) resource Admin {
1533        
1534        init() {}
1535        access(all) fun initializeHeroesAndPods() {
1536            ABRoyaleGame.initializeHeroesAndPods(podSizes: ABRoyaleGame.getPodSizes(total_players: ABRoyaleGame.enrolled_players))
1537        }
1538        access(all) fun resolveAttacks() {
1539            ABRoyaleGame.resolveAttacks()
1540        }
1541        access(all) fun resetTurn() {
1542            
1543            ABRoyaleGame.resetTurn(podSizes: ABRoyaleGame.getPodSizes(total_players: ABRoyaleGame.enrolled_players))
1544        }
1545
1546        access(all) fun resetGame() {
1547            ABRoyaleGame.resetGame()
1548        }
1549        access(all) fun setEntryFee(entry_fee : UFix64) {
1550            ABRoyaleGame.setEntryFee(entry_fee: entry_fee)
1551        }
1552        access(all) fun setSecondsPerTurn(newSecondsPerTurn : Int) {
1553            ABRoyaleGame.setSecondsPerTurn(newSecondsPerTurn: newSecondsPerTurn)
1554        }
1555        
1556    }
1557    
1558}