Smart Contract

ABRoyaleGame

A.c59f4df08f49f89d.ABRoyaleGame

Valid From

84,896,173

Deployed

2d ago
Feb 26, 2026, 12:07:57 PM UTC

Dependents

0 imports
1import FungibleToken from 0xf233dcee88fe0abe
2import FlowToken from 0x1654653399040a61
3import ArenaBoyzGlobals from 0xc59f4df08f49f89d
4import ArenaBoyzHistory from 0xc59f4df08f49f89d
5
6
7pub contract ABRoyaleGame {
8    
9    pub var pods: [Pod]
10    pub var heroes: [Hero]
11    pub var seeded_hash: [UInt8]
12    pub var attacks : [Attack]
13    pub var last_results : [AttackResult] //TODO: emit events instead of a struct saying what happened during attacks
14    pub var seconds_per_turn : Int //this is now how many seconds each player is given to act
15    pub var turn : UInt32
16    pub var circle : UInt32
17    pub var enrolled_players : Int32
18    pub var alive_heroes : Int32
19    pub var current_player_id : Int32
20    pub var pod_size: UInt32
21    pub var turn_end_timestamp: UFix64
22    pub var game_started : Bool
23    pub var game_id : UInt32
24    pub var entry_fee : UFix64
25    pub var payment_address : Address
26    pub var runners_up : [Int32]
27    pub var runner_up_id : Int32
28    pub var second_runner_up_id : Int32
29    pub var current_key_id : Int
30    pub var signup_keys: {UInt32: Int}
31
32    pub let KeyStoragePath: StoragePath
33    pub let KeyPublicPath: PublicPath
34    pub let AdminStoragePath: StoragePath
35    pub event AttackResultEvent(pod: UInt32, attacker: UInt32, attacker_key: Int, target_key: Int, action_key: Int, target: UInt32, damage: UInt16, critical: Bool, counter_critical: Bool, counter_damage: UInt16, action_target: UInt32, action_amount: UFix64, action_item: UInt8, game_id: UInt32, attacker_hp: UInt16, target_hp: UInt16, action_target_hp: UInt16, attacker_hp_max: UInt16, target_hp_max: UInt16, action_target_hp_max: UInt16 )
36    pub event GameResultEvent(game_id: UInt32, victor_id: Int32, placement: Int, ending_turn: UInt32, enrolled_players: Int32)
37    pub event GameResultEventV2(game_id: UInt32, victor_id: Int32, runner_up_id: Int32, second_runner_up_id: Int32, placement: Int, ending_turn: UInt32, enrolled_players: Int32)
38    pub struct ScavengeConflict {
39        pub set var hero_ids: [UInt32]
40        pub set var attack_indicies: [Int]
41
42        init(hero_ids: [UInt32], attack_indicies: [Int]) {
43            self.hero_ids = hero_ids
44            self.attack_indicies = attack_indicies
45        }
46
47        access(contract) fun addConflict(hero_id: UInt32, attack_index: Int) {
48            self.hero_ids.append(hero_id)
49            self.attack_indicies.append(attack_index)
50        }
51    }
52    
53    pub struct Pod {
54        pub(set) var hero_ids: [UInt32] //open question: is it better to iterate through pods (there will be total players / heroes per pod of them, approx one order of magnitude less, to find which pod a hero is in, or store the pod in each hero. trading computation vs. storage)
55        pub(set) var scavenge_pool: [UInt8; 32]
56
57        init() {
58            self.hero_ids = []
59            self.scavenge_pool = ArenaBoyzGlobals.scavenge_pool_0
60        }
61        pub fun ResetScavengePool(Circle : UInt32) {
62            if(Circle == 1) {
63                self.scavenge_pool = ArenaBoyzGlobals.scavenge_pool_1
64            }
65            if(Circle == 2) {
66                self.scavenge_pool = ArenaBoyzGlobals.scavenge_pool_2
67            }
68            if(Circle == 3) {
69                self.scavenge_pool = ArenaBoyzGlobals.scavenge_pool_3
70            }
71            if(Circle == 4) {
72                self.scavenge_pool = ArenaBoyzGlobals.scavenge_pool_4
73            }
74            if(Circle == 5) {
75                self.scavenge_pool = ArenaBoyzGlobals.scavenge_pool_5
76            }
77            if(Circle == 6) {
78                self.scavenge_pool = ArenaBoyzGlobals.scavenge_pool_6
79            }
80            if(Circle >= 7) {
81                self.scavenge_pool = ArenaBoyzGlobals.getScavengePool7()
82            }
83        }
84        pub fun SortPlayers(Players: [Hero]) {
85            var i: Int = 0
86            while i < self.hero_ids.length - 1 {
87                var maxIndex: Int = i
88                var j: Int = i + 1
89                while j < self.hero_ids.length {
90                    let heroIdJ: UInt32 = self.hero_ids[j]
91                    let heroIdMax: UInt32 = self.hero_ids[maxIndex]
92
93                    var speedJ: UInt16 = 0
94                    var speedMax: UInt16 = 0
95                    var hpJ: UInt16 = 0
96                    var hpMax: UInt16 = 0
97
98                    // Find hero by ID for heroIdJ
99                    for player in Players {
100                        if player.hero_id == heroIdJ {
101                            speedJ = player.GetSpeed()
102                            hpJ = player.hitpoints
103                            break
104                        }
105                    }
106
107                    // Find hero by ID for heroIdMax
108                    for player in Players {
109                        if player.hero_id == heroIdMax {
110                            speedMax = player.GetSpeed()
111                            hpMax = player.hitpoints
112                            break
113                        }
114                    }
115
116                    if hpJ == 0 && hpMax > 0 {
117                        // Skip, as heroes with 0 HP should be sorted to the back
118                    } else if hpMax == 0 && hpJ > 0 {
119                        maxIndex = j
120                    } else if speedJ > speedMax || (speedJ == speedMax && revertibleRandom() > revertibleRandom()) {
121                        maxIndex = j
122                    }
123                    j = j + 1
124                }
125                if maxIndex != i {
126                    let temp: UInt32 = self.hero_ids[i]
127                    self.hero_ids[i] = self.hero_ids[maxIndex]
128                    self.hero_ids[maxIndex] = temp
129                }
130                i = i + 1
131            }
132        }
133
134        pub fun DropItemIntoPool(Item : UInt8) {
135            var i: UInt8 = 0
136            for item in self.scavenge_pool {
137                if(item == 0) {
138                    self.scavenge_pool[i] = Item
139                    break
140                }
141                i = i + 1
142            }
143        }
144        
145    }
146
147    pub struct Hero {
148        pub(set) var hero_id: UInt32
149        pub(set) var key_id: Int
150        pub(set) var hitpoints: UInt16
151        pub(set) var max_hitpoints: UInt16
152        pub(set) var shields: UInt16
153        pub(set) var damage: UInt16
154        pub(set) var defense: UInt16
155        pub(set) var accuracy: UInt16
156        pub(set) var speed: UInt16
157        pub(set) var counter: UInt16
158        pub(set) var attack_award: UInt8
159        pub(set) var stim: UFix64
160        pub(set) var has_moved: Bool //0: cannot go yet 1: can take action 2: has already taken action
161        pub(set) var move_timestamp: UFix64
162        pub(set) var current_pod_id: UInt32 //its almost certainly going to be preferable to store the pod the hero is in, rather than iterate through all pods
163        pub(set) var items: [UInt8; 24]
164
165        init() {
166            self.hero_id = 0
167            self.key_id = 0
168            self.hitpoints = 0
169            self.shields = 0
170            self.max_hitpoints = 0
171            self.damage = 0
172            self.defense = 0
173            self.accuracy = 0
174            self.speed = 0
175            self.counter = 0
176            self.attack_award = 0
177            self.stim = 0.0
178            self.current_pod_id = 0
179            self.has_moved = false
180            self.move_timestamp = 0.0
181            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]           
182           
183        }
184        pub fun GetDamage() : UInt16 {
185            var temp_stat: UInt16 = self.damage
186            var effective_item_max: Int = ArenaBoyzGlobals.effective_item_max
187            var i = 0
188            for item in self.items {
189                if(effective_item_max > 0) {
190                    if(i == 0) { // only count primary weapons in slot 0
191                        if(item >= ArenaBoyzGlobals.ar_start && item < ArenaBoyzGlobals.sniper_start) { //AR, defense-based
192                            temp_stat = temp_stat + (ArenaBoyzGlobals.primary_bonus_per_thresh*(UInt16(item)))*(self.GetDefense()/ArenaBoyzGlobals.threshhold_amount)
193                        }
194                        if(item >= ArenaBoyzGlobals.sniper_start && item < ArenaBoyzGlobals.smg_start) { //Sniper, accuracy-based
195                            temp_stat = temp_stat + (ArenaBoyzGlobals.primary_bonus_per_thresh*(UInt16(item-(ArenaBoyzGlobals.sniper_start-1))))*(self.GetAccuracy()/ArenaBoyzGlobals.threshhold_amount)
196                        }
197                        if(item >= ArenaBoyzGlobals.smg_start && item < ArenaBoyzGlobals.shotgun_start) { //SMG, speed-based
198                            temp_stat = temp_stat + (ArenaBoyzGlobals.primary_bonus_per_thresh*(UInt16(item-(ArenaBoyzGlobals.smg_start-1))))*(self.GetSpeed()/ArenaBoyzGlobals.threshhold_amount)
199                        }
200                        if(item >= ArenaBoyzGlobals.shotgun_start && item < ArenaBoyzGlobals.throwable_start) { //Shotgun, counter-based
201                            temp_stat = temp_stat + (ArenaBoyzGlobals.primary_bonus_per_thresh*(UInt16(item-(ArenaBoyzGlobals.shotgun_start-1))))*(self.GetCounter()/ArenaBoyzGlobals.threshhold_amount)
202                        }
203                    }
204                    if(item >= ArenaBoyzGlobals.damage_start && item < ArenaBoyzGlobals.defense_start) {
205                        temp_stat = temp_stat + ArenaBoyzGlobals.equipment_bonus_per_level*UInt16(item - (ArenaBoyzGlobals.damage_start -1))
206                    }
207                }
208                effective_item_max = effective_item_max - 1
209                i = i + 1
210            }
211            return temp_stat
212        }
213        pub fun GetAccuracy() : UInt16 {
214            var temp_stat: UInt16 = self.accuracy
215            var effective_item_max: Int = ArenaBoyzGlobals.effective_item_max
216            for item in self.items {
217                if(effective_item_max > 0) {
218                    if(item >= ArenaBoyzGlobals.accuracy_start && item < ArenaBoyzGlobals.speed_start) {
219                        temp_stat = temp_stat + ArenaBoyzGlobals.equipment_bonus_per_level*(UInt16(item - (ArenaBoyzGlobals.accuracy_start - 1)))
220                    }
221                }
222                effective_item_max = effective_item_max - 1
223            }
224            return temp_stat;
225        }
226        pub fun GetCounter() : UInt16 {
227            var temp_stat: UInt16 = self.counter
228            var effective_item_max: Int = ArenaBoyzGlobals.effective_item_max
229            for item in self.items {
230                if(effective_item_max > 0) {
231                    if(item >= ArenaBoyzGlobals.counter_start && item < ArenaBoyzGlobals.items_end) {
232                        temp_stat = temp_stat + ArenaBoyzGlobals.equipment_bonus_per_level*(UInt16(item - (ArenaBoyzGlobals.counter_start-1)))
233                    }
234                }
235                effective_item_max = effective_item_max - 1
236            }
237            return temp_stat;
238        }
239        pub fun GetDefense() : UInt16 {
240            var temp_stat: UInt16 = self.defense
241            var effective_item_max: Int = ArenaBoyzGlobals.effective_item_max
242            for item in self.items {
243                if(effective_item_max > 0) {
244                    if(item >= ArenaBoyzGlobals.defense_start && item < ArenaBoyzGlobals.accuracy_start) {
245                        temp_stat = temp_stat + ArenaBoyzGlobals.equipment_bonus_per_level*(UInt16(item - (ArenaBoyzGlobals.defense_start-1)))
246                    }
247                }
248                effective_item_max = effective_item_max - 1
249            }
250            return temp_stat;
251        }
252        pub fun GetSpeed() : UInt16 {
253            var temp_stat: UInt16 = self.speed
254            var effective_item_max: Int = ArenaBoyzGlobals.effective_item_max
255            for item in self.items {
256                if(effective_item_max > 0) {
257                    if(item >= ArenaBoyzGlobals.speed_start && item < ArenaBoyzGlobals.counter_start) {
258                        temp_stat = temp_stat + ArenaBoyzGlobals.equipment_bonus_per_level*(UInt16(item - (ArenaBoyzGlobals.speed_start - 1)))
259                    }
260                }
261                effective_item_max = effective_item_max - 1
262            }
263            return temp_stat;
264        }
265        pub fun GetAttackAwardAmount() : UFix64 {
266            var health_pct:UFix64 = UFix64(self.hitpoints)/UFix64(self.max_hitpoints)
267            var base_value:UFix64 = 5.0
268            if(health_pct >= 1.0) {
269                return base_value*5.0
270            }
271            if(health_pct < 1.0 && health_pct >= 0.75) {
272                return base_value*4.0
273            }
274            if(health_pct < 0.75 && health_pct >= 0.5) {
275                return base_value*3.0
276            }
277            if(health_pct < 0.5 && health_pct >= 0.25) {
278                return base_value*2.0
279            }
280            return base_value
281        }
282         access(contract) fun GenerateHero(hero_id: UInt32, starting_pod_id : UInt32) {
283            self.hero_id = hero_id
284            self.max_hitpoints = ArenaBoyzGlobals.hp_base
285            self.hitpoints = ArenaBoyzGlobals.hp_base
286            self.damage = ArenaBoyzGlobals.damage_base
287            self.defense = ArenaBoyzGlobals.defense_base
288            self.accuracy = ArenaBoyzGlobals.accuracy_base
289            self.speed = ArenaBoyzGlobals.speed_base
290            self.counter = ArenaBoyzGlobals.counter_base
291            self.current_pod_id = starting_pod_id
292            self.attack_award = UInt8(revertibleRandom() & 0xFF)%5
293            self.items[0] = ArenaBoyzGlobals.starting_primary_pool[UInt8(revertibleRandom() & 0xFF)%4] 
294
295            //10% bonus jitter applied below, for some variance
296            self.max_hitpoints = self.max_hitpoints + UInt16(revertibleRandom() & 0xFFFF)%100
297            self.hitpoints = self.max_hitpoints
298            self.damage = self.damage + UInt16(revertibleRandom() & 0xFFFF)%30
299            self.defense = self.defense  + UInt16(revertibleRandom() & 0xFFFF)%10
300            self.accuracy = self.accuracy + UInt16(revertibleRandom() & 0xFFFF)%20
301            self.speed = self.speed + UInt16(revertibleRandom() & 0xFFFF)%20
302            self.counter = self.counter + UInt16(revertibleRandom() & 0xFFFF)%20
303        }
304        access(contract) fun ProcessConsumableItems() {
305            var i: Int = 0
306            for item in self.items {
307                if(item >= ArenaBoyzGlobals.throwable_start && item < ArenaBoyzGlobals.healing_start) {
308                    //self.counter = self.counter + ArenaBoyzGlobals.targetable_bonus_per_level * (1 + UInt16(item) - UInt16(ArenaBoyzGlobals.throwable_start))
309                    self.items[i] = 0
310                }
311                else if(item >= ArenaBoyzGlobals.healing_start && item < ArenaBoyzGlobals.shield_start) {
312                    self.defense = self.defense + ArenaBoyzGlobals.targetable_bonus_per_level * (1 + UInt16(item) - UInt16(ArenaBoyzGlobals.healing_start))
313                    self.items[i] = 0
314                }
315                else if(item >= ArenaBoyzGlobals.shield_start && item < ArenaBoyzGlobals.stim_start) {
316                    self.speed = self.speed + ArenaBoyzGlobals.targetable_bonus_per_level * (1 + UInt16(item) - UInt16(ArenaBoyzGlobals.shield_start))
317                    self.items[i] = 0
318                }
319                 else if(item >= ArenaBoyzGlobals.stim_start && item < ArenaBoyzGlobals.medal_start) {
320                    //self.speed = self.speed + ArenaBoyzGlobals.targetable_bonus_per_level * (1 + UInt16(item) - UInt16(ArenaBoyzGlobals.stim_start))
321                    self.items[i] = 0
322                }
323                else if(item >= ArenaBoyzGlobals.medal_start && item < ArenaBoyzGlobals.damage_start) {
324                    self.counter = self.counter + ArenaBoyzGlobals.targetable_bonus_per_level * (1 + UInt16(item) - UInt16(ArenaBoyzGlobals.medal_start))
325                    self.items[i] = 0
326                }
327
328                i = i + 1
329            }
330            self.RemoveInventoryGaps()
331        }
332        access(contract) fun ProcessEndPod() {
333              var i: Int = 0
334            for item in self.items {
335                if(item >= ArenaBoyzGlobals.medal_start && item < ArenaBoyzGlobals.damage_start) {
336                    self.max_hitpoints = self.max_hitpoints + ArenaBoyzGlobals.medal_max_hp_per_level * (1 + UInt16(item) - UInt16(ArenaBoyzGlobals.medal_start))
337                }
338                i = i + 1
339            }
340            if(self.hitpoints > 0) {
341                self.max_hitpoints = self.max_hitpoints + ArenaBoyzGlobals.free_max_hp_per_level;
342                self.hitpoints = self.max_hitpoints
343            }
344        }
345        access(contract) fun RemoveInventoryGaps() {
346            var j:Int = 0 // Pointer for the next position of a non-zero element
347            var i:Int = 0
348            for item in self.items
349            {
350                // When a non-zero element is found, swap it with the element at j
351                if (item != 0)
352                {
353                    var temp:UInt8 = item
354                    self.items[i] = self.items[j]
355                    self.items[j] = temp
356                    j = j + 1
357                }
358                i = i + 1
359            }
360        } 
361        access(contract) fun DiscardExcessInventory() {
362            var i:Int = 0
363            for item in self.items
364            {
365                if (i >= ArenaBoyzGlobals.effective_item_max)
366                {
367                    self.items[i] = 0
368                }
369                i = i + 1
370            }
371        }
372        access(contract) fun ProcessEndTurn() {
373            
374            self.DiscardExcessInventory()
375            self.has_moved = false
376            self.stim = 0.0
377        }
378        access(contract) fun SetTimestamp(timestamp : UFix64) {
379            self.move_timestamp = timestamp
380        }
381    }
382
383    pub struct Attack {
384        pub(set) var attacker: UInt32
385        pub(set) var attack_target: UInt32
386        pub(set) var action_target: UInt32
387        pub(set) var scavenge_choice: UInt8
388        pub(set) var selected_loot: UInt8
389        pub(set) var equip_primary : Bool
390        pub(set) var turn : UInt32
391        
392        init(attacker : UInt32, attack_target : UInt32, action_target : UInt32, scavenge_choice : UInt8, equip_primary : Bool, selected_loot : UInt8, turn : UInt32 ) {
393            self.attacker = attacker
394            self.attack_target = attack_target
395            self.action_target = action_target
396            self.scavenge_choice = scavenge_choice
397            self.equip_primary = equip_primary
398            self.selected_loot = selected_loot
399            self.turn = turn
400        }
401    }
402
403    pub struct AttackResult {
404        pub(set) var attacker: UInt32
405        pub(set) var target: UInt32
406        pub(set) var damage: UInt32
407        
408        init() {
409            self.attacker = 0
410            self.target = 0
411            self.damage = 0
412        }
413
414    }
415
416    init() {
417        self.heroes = []
418        self.attacks = []
419        self.last_results = []
420        self.seeded_hash = []
421        self.pods = []
422        self.signup_keys = {}
423        self.runners_up = [-1,- 1, -1]
424        self.enrolled_players = 0
425        self.alive_heroes = 0
426        self.current_player_id = -1
427        self.current_key_id = -1
428        self.seconds_per_turn = ArenaBoyzGlobals.seconds_per_turn
429        self.turn = 0
430        self.turn_end_timestamp = 0.0
431        self.circle = 0
432        self.pod_size = ArenaBoyzGlobals.heroes_per_pod
433        self.game_id = 0
434        self.entry_fee = 10.0
435        self.payment_address = self.account.address
436        self.runner_up_id = 0
437        self.second_runner_up_id = 0
438        self.game_started = false
439        self.AdminStoragePath = /storage/arenaboyzPaths4Admin
440        self.KeyStoragePath = /storage/arenaboyzPaths4Key
441        self.KeyPublicPath = /public/arenaboyzPaths4Key
442        let admin: @ABRoyaleGame.Admin <- create Admin()
443        self.account.save(<-admin, to: self.AdminStoragePath)
444    }
445 
446    //a player signs up to play the game here, pays the game fee
447    pub fun playerSignup(payment: @FungibleToken.Vault) : @PlayerKey {
448        pre {
449            self.game_started == false : "game must not be running yet"
450        }
451        assert(payment.balance == self.entry_fee, message: "insufficient funds")
452        
453        let receiverRef =  getAccount(self.payment_address)
454            .getCapability(/public/flowTokenReceiver)
455            .borrow<&{FungibleToken.Receiver}>()
456			?? panic("Could not borrow receiver reference to the recipient's Vault")
457        
458        receiverRef.deposit(from: <- payment)
459        self.enrolled_players = self.enrolled_players + 1
460        self.current_player_id = self.current_player_id + 1
461        self.current_key_id = self.current_key_id + 1
462        self.signup_keys[UInt32(self.current_player_id)] = self.current_key_id
463        log(self.signup_keys[UInt32(self.current_player_id)])
464        return <- create PlayerKey(player_id: UInt32(self.current_player_id), game_id: self.game_id, key_id : self.current_key_id)
465    }
466    pub fun updatePlayerKey(signup_key: @PlayerKey, payment: @FungibleToken.Vault) : @PlayerKey {
467        pre {
468            self.game_started == false : "game must not be running yet" 
469            
470
471        }
472        assert(payment.balance == self.entry_fee, message: "insufficient funds")
473      
474        var i: Int = 0
475        while i < Int(self.current_player_id+1) {
476            if(self.signup_keys[UInt32(i)] == signup_key.key_id) {
477                panic("player has already signed up")
478            }
479            i = i + 1
480        }
481        let receiverRef =  getAccount(self.payment_address)
482            .getCapability(/public/flowTokenReceiver)
483            .borrow<&{FungibleToken.Receiver}>()
484            ?? panic("Could not borrow receiver reference to the recipient's Vault")
485        
486        receiverRef.deposit(from: <- payment)
487        signup_key.setGameID(game_id: self.game_id)
488        self.current_player_id = self.current_player_id + 1
489        signup_key.setPlayerID(player_id: UInt32(self.current_player_id))
490        self.signup_keys[UInt32(self.current_player_id)] = signup_key.key_id
491        log(self.signup_keys[UInt32(self.current_player_id)])
492        self.enrolled_players = self.enrolled_players + 1
493
494        return <-signup_key
495    }
496    
497    pub fun shuffle(array: [UInt32]) : [UInt32] {
498        let n = array.length
499        var shuffledArray = array
500
501        var i: Int = 0
502        while i < n {
503            let j = i + (Int(revertibleRandom()) % (n - i))
504            let temp = shuffledArray[i]
505            shuffledArray[i] = shuffledArray[j]
506            shuffledArray[j] = temp
507            i = i + 1
508        }
509        return shuffledArray
510    }
511
512    pub fun getPodSizes(total_players: Int32): [Int] { //pod balancing aims to create specific balacing to ensure fairness
513        var podSizes: [Int] = []
514        var totalPlayers: Int = Int(total_players)
515        if totalPlayers <= 8 {
516            podSizes = [totalPlayers]
517        } 
518        else if totalPlayers <= 16 {
519            let playersInFirstPod = (totalPlayers + 1) / 2
520            let playersInSecondPod = totalPlayers - playersInFirstPod
521            podSizes = [playersInFirstPod, playersInSecondPod]
522        } 
523        else if totalPlayers <= 24 {
524            let playersInFirstPod = (totalPlayers + 2) / 3
525            let playersInSecondPod = (totalPlayers + 1) / 3
526            let playersInThirdPod = totalPlayers - playersInFirstPod - playersInSecondPod
527            podSizes = [playersInFirstPod, playersInSecondPod, playersInThirdPod]
528        } 
529        else {
530            while totalPlayers > 24 {
531                podSizes.append(8)
532                totalPlayers = totalPlayers - 8
533            }
534            if totalPlayers <= 8 {
535                podSizes.append(totalPlayers)
536            } 
537            else if totalPlayers <= 16 {
538                let playersInFirstPod = (totalPlayers + 1) / 2
539                let playersInSecondPod = totalPlayers - playersInFirstPod
540                podSizes.append(playersInFirstPod)
541                podSizes.append(playersInSecondPod)
542            } 
543            else {
544                let playersInFirstPod = (totalPlayers + 2) / 3
545                let playersInSecondPod = (totalPlayers + 1) / 3
546                let playersInThirdPod = totalPlayers - playersInFirstPod - playersInSecondPod
547                podSizes.append(playersInFirstPod)
548                podSizes.append(playersInSecondPod)
549                podSizes.append(playersInThirdPod)
550            }
551        }
552        var maxPodSize = 0
553        var i = 0
554        while i < podSizes.length {
555            if podSizes[i] > maxPodSize {
556                maxPodSize = podSizes[i]
557            }
558            i = i + 1
559        }
560        self.pod_size = UInt32(maxPodSize)
561        return podSizes
562    }
563
564    priv fun initializeHeroesAndPods(podSizes : [Int]) {
565        if(self.enrolled_players < Int32(self.pod_size)) { //When we go back to pods, this will come back into play
566            self.pod_size = UInt32(self.enrolled_players)
567        }
568        var hero_ids: [UInt32] = []
569        var id : UInt32 = 0
570        while id < UInt32(self.enrolled_players) {
571            hero_ids.append(id)
572            self.heroes.append(Hero())
573            self.heroes[id].GenerateHero(hero_id: id, starting_pod_id: 0)
574             if let signupKey = self.signup_keys[id] {
575                self.heroes[id].key_id = signupKey
576                log(signupKey)
577            } else {
578                panic("Signup key for hero ID is nil")
579            }
580            id = id + 1
581        }
582        var podIndex = 0
583        while podIndex < podSizes.length {
584            self.pods.append(Pod())
585            log("appended pod")
586            podIndex = podIndex + 1
587        }
588        hero_ids = self.shuffle(array: hero_ids)
589        podIndex = 0
590        var currentHeroIndex: Int = 0
591            podIndex = 0
592            while podIndex < podSizes.length {
593                let podSize = podSizes[podIndex]
594                var podHeroCount = 0
595                while podHeroCount < podSize && currentHeroIndex < hero_ids.length {
596                    let heroID = hero_ids[currentHeroIndex]
597                    self.pods[podIndex].hero_ids.append(heroID)
598                    self.heroes[heroID].current_pod_id = UInt32(podIndex)
599                    log("appending hero")
600                    log(heroID)
601                    log(UInt32(podIndex))
602                    currentHeroIndex = currentHeroIndex + 1
603                    podHeroCount = podHeroCount + 1
604                }
605
606                podIndex = podIndex + 1
607            }
608
609      
610
611        var largest_pod: Int = 0
612         for pod in self.pods {
613  
614            var localHeroes: [Hero] = []
615            for hero_id in pod.hero_ids {
616                localHeroes.append(self.heroes[hero_id])
617            }
618
619            pod.SortPlayers(Players: localHeroes)
620
621            var i: Int = 0
622            while i < Int(pod.hero_ids.length) {
623                let podHeroId: UInt32 = pod.hero_ids[i]
624                self.heroes[podHeroId].move_timestamp = getCurrentBlock().timestamp + UFix64(i * self.seconds_per_turn)
625                i = i + 1
626            }
627             if(i > largest_pod) {
628                largest_pod = i
629            }
630            
631        }
632        log("largest pod")
633        log(largest_pod)
634        self.turn_end_timestamp = getCurrentBlock().timestamp + UFix64(self.seconds_per_turn * largest_pod)
635        self.game_started = true
636        self.alive_heroes = self.enrolled_players
637        log(self.turn)
638    }
639
640    priv fun resetTurn(podSizes : [Int]) {
641        pre {
642            self.game_started == true : "game must be running"
643        }
644        self.turn = self.turn + 1
645        if (self.turn % ArenaBoyzGlobals.turns_per_circle == 0) {
646            self.circle = self.circle + 1
647            var aliveHeroIDs: [UInt32] = []
648            var heroIndex = 0
649
650            // Collect all alive heroes
651            while heroIndex < self.heroes.length {
652                let hero = self.heroes[heroIndex]
653                if (hero.hitpoints > 0) {
654                    aliveHeroIDs.append(hero.hero_id)
655                }
656                heroIndex = heroIndex + 1
657            }
658
659            while (self.pods.length > 0) {
660                self.pods.removeLast()
661            }
662
663            var podIndex = 0
664            while podIndex < podSizes.length {
665                self.pods.append(Pod())
666                podIndex = podIndex + 1
667            }
668
669            var shuffledHeroIDs: [UInt32] = self.shuffle(array: aliveHeroIDs)
670
671            var currentHeroIndex: Int = 0
672            podIndex = 0
673            while podIndex < podSizes.length {
674                let podSize = podSizes[podIndex]
675
676                var podHeroCount = 0
677                while podHeroCount < podSize && currentHeroIndex < shuffledHeroIDs.length {
678                    let heroID = shuffledHeroIDs[currentHeroIndex]
679                    self.pods[podIndex].hero_ids.append(heroID)
680                    self.heroes[heroID].current_pod_id = UInt32(podIndex)
681                    currentHeroIndex = currentHeroIndex + 1
682                    podHeroCount = podHeroCount + 1
683                }
684
685                podIndex = podIndex + 1
686            }
687        }
688        var pod_i : Int = 0
689      
690 
691        for pod in self.pods {
692            if(self.turn % ArenaBoyzGlobals.turns_per_circle == 0) {
693                
694                self.pods[pod_i].ResetScavengePool(Circle: self.circle)
695            }
696            if(self.turn/ArenaBoyzGlobals.turns_per_circle < 7 ) { // end of game, final circle!
697                
698                var i: Int = 0
699                for heroId in pod.hero_ids {
700                    self.heroes[heroId].ProcessEndTurn()
701                    if(self.turn % ArenaBoyzGlobals.turns_per_circle == 0) {
702                        self.heroes[heroId].ProcessEndPod()
703                    }
704                }
705            }
706            else {
707                var i: Int = 0
708                for heroId in pod.hero_ids {
709                    self.heroes[heroId].ProcessEndTurn()
710                    if(self.turn % ArenaBoyzGlobals.turns_per_circle == 0) {
711                        self.heroes[heroId].ProcessEndPod()
712                    }
713                }
714            }
715            //shorten turn length when a player is killed, but we need to do this only one time per player
716             var pod_survivors : Int = 0
717             for live_i in pod.hero_ids {
718                if self.heroes[live_i].hitpoints > 0  {
719                    pod_survivors = pod_survivors + 1
720                }
721            }
722          //  if(self.seconds_per_turn < pod_survivors*60 + 60) {
723        //        self.seconds_per_turn = pod_survivors*60 + 60
724         //   }
725            pod_i = pod_i  + 1
726        }
727        // Sort players within each pod and set move timestamps
728        var largest_pod: Int = 0
729        for pod in self.pods {
730            // Create a local array of heroes for this pod
731            var localHeroes: [Hero] = []
732            for hero_id in pod.hero_ids {
733                localHeroes.append(self.heroes[hero_id])
734            }
735
736            // Sort the local array of heroes
737            pod.SortPlayers(Players: localHeroes)
738
739            var i: Int = 0
740            while i < Int(pod.hero_ids.length) {
741                let podHeroId: UInt32 = pod.hero_ids[i]
742                self.heroes[podHeroId].move_timestamp = getCurrentBlock().timestamp + UFix64(i * self.seconds_per_turn)
743                i = i + 1
744            }
745            if(i > largest_pod) {
746                largest_pod = i
747            }
748        }
749
750        self.turn_end_timestamp = getCurrentBlock().timestamp + UFix64(self.seconds_per_turn * largest_pod)
751        log(self.turn)
752    }
753    //TODO: reshuffle pods on new circle. circle should be incremented, and players should be randomly shuffled into new pods.
754
755    //Player commits to an attack here
756    pub fun submitAttack(attacker_key: @PlayerKey, attack_target : UInt32, action_target : UInt32, scavenge_choice : UInt8, equip_primary : Bool): @PlayerKey {
757        pre {
758           
759            //TODO: speed management goes here. players should be prohibited from entering attacks before it is their turn to do so
760            //see self.tick and self.turn vs. their speed. it should also be recorded if they have moved already this turn.
761                
762                attacker_key.game_id == self.game_id : "attacker must be from this game"
763                self.heroes[attacker_key.player_id].hitpoints > 0 : "attacker must be alive!"
764                self.heroes[attack_target].hitpoints > 0 : "attack target must be alive!"
765                self.heroes[action_target].hitpoints > 0 : "action target must be alive!"
766                self.heroes[attacker_key.player_id].has_moved == false : "hero has already attacked!"
767                self.heroes[attacker_key.player_id].move_timestamp <= getCurrentBlock().timestamp : "attacker is not allowed to enter an attack yet!"
768                self.heroes[attacker_key.player_id].current_pod_id == self.heroes[attack_target].current_pod_id : "attack target is not in the same pod!"
769                self.heroes[attacker_key.player_id].current_pod_id == self.heroes[action_target].current_pod_id : "action target is not in the same pod!"
770                //TODO: Can we put in some constants or globals for design-related items in code
771            }
772        
773        self.heroes[attacker_key.player_id].key_id = attacker_key.key_id //this is an okay place to do this, but a player who never attacks will never set their key_id
774        var a_remove: Int = 0
775        for attack in self.attacks {
776           if(attack.attacker == attacker_key.player_id) { //if the player has submitted an attack already, remove it
777                self.attacks.remove(at: a_remove)
778                break
779            }
780            a_remove = a_remove + 1
781        }
782        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 ))
783        //TODO: we can change contract state here, such as setting a block height
784        return <-attacker_key
785    }
786    pub fun dropItem(dropper_key: @PlayerKey, item_index : Int): @PlayerKey {
787        pre {
788            dropper_key.game_id == self.game_id : "dropper must be from this game"
789        }
790        let dropped_item: Int = Int(self.heroes[dropper_key.player_id].items[item_index])
791        self.heroes[dropper_key.player_id].items[item_index] = 0
792        self.pods[self.heroes[dropper_key.player_id].current_pod_id].DropItemIntoPool(Item: UInt8(dropped_item))
793        return <-dropper_key
794    }
795    pub fun equipItem(equipper_key: @PlayerKey, item_index : Int): @PlayerKey {
796        pre{
797           equipper_key.game_id == self.game_id : "dropper must be from this game"
798           self.heroes[equipper_key.player_id].items[item_index] < ArenaBoyzGlobals.targetables_start : "has to pick a primary weapon to equip primary!"
799        }
800        let old_primary: Int = Int(self.heroes[equipper_key.player_id].items[0])
801        self.heroes[equipper_key.player_id].items[0] = self.heroes[equipper_key.player_id].items[item_index]
802        self.heroes[equipper_key.player_id].items[item_index] = UInt8(old_primary)
803
804        return <-equipper_key
805    }
806    //at the time handleDeath is called, all damage has already been applied to both attack target and attacker due to counter. so either 
807    //both players are already dead, or just the attacker is.
808    priv fun handleDeath(): Bool {
809        log("handling death")
810        self.alive_heroes = 0
811        var winner_id: Int32 = -1
812        var runner_up_id: Int32 = -1
813        var second_runner_up_id: Int32 = -1
814        var game_over: Bool = false
815        var runners_up_ids: [Int32] = [] // Dynamic array
816
817        for hero in self.heroes {
818            if(hero.hitpoints > 0) {
819                self.alive_heroes = self.alive_heroes + 1
820                winner_id = Int32(hero.key_id)
821                runners_up_ids.append(Int32(hero.key_id)) // Add survivor IDs dynamically
822                log(Int32(hero.key_id))
823            }
824        }
825
826        if(self.alive_heroes == 1) {
827            var eliminatedPlayer: Int32 = -1
828
829            for h in self.runners_up {
830                log(h)
831                if (!runners_up_ids.contains(h)) {
832                    eliminatedPlayer = h
833                    break
834                }
835            }
836            self.runner_up_id = eliminatedPlayer
837
838            emit GameResultEvent(game_id: self.game_id, victor_id: winner_id, placement: 1, ending_turn: self.turn, enrolled_players: self.enrolled_players)
839            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,  placement: 1, ending_turn: self.turn, enrolled_players: self.enrolled_players)
840            game_over = true
841        }
842        else if(self.alive_heroes == 2) {
843            var eliminatedPlayer: Int32 = -1
844
845            for h in self.runners_up {
846                if (!runners_up_ids.contains(h)) {
847                    eliminatedPlayer = h
848                    break
849                }
850            }
851            self.runners_up = runners_up_ids // Update runners_up directly from dynamic array
852            self.second_runner_up_id = eliminatedPlayer
853        }
854        else if(self.alive_heroes == 3) {
855            self.runners_up = runners_up_ids // Update runners_up directly from dynamic array
856        }
857
858        if(game_over) {
859            self.resetGame()
860            return true
861        }
862        return false
863    }
864    priv fun resolveAttacks() {
865        var validAttacks: [Attack] = [] //new code to remove attacks that have not been flagged
866    
867        for attack in self.attacks {
868            if attack.turn == self.turn {
869                validAttacks.append(attack)
870            }
871        }
872        self.attacks = validAttacks
873
874        var conflicts: {UInt8: ScavengeConflict} = {}
875        var conflict_i = 0
876        for attack in self.attacks {
877            let item_id = attack.scavenge_choice
878            let hero_id = attack.attacker
879
880            if conflicts[item_id] != nil {
881                conflicts[item_id]?.addConflict(hero_id: hero_id, attack_index: conflict_i)
882            } else {
883                conflicts[item_id] = ScavengeConflict( hero_ids: [hero_id], attack_indicies: [conflict_i])
884            }
885            conflict_i = conflict_i + 1
886        }
887
888        for conflict in conflicts.values {
889            if conflict.hero_ids.length > 1 {
890                // More than one hero is trying to scavenge the same item, resolve conflict
891                let winnerIndex = Int(revertibleRandom())%conflict.hero_ids.length
892                var attack_index_i = 0
893                for attack_index in conflict.attack_indicies {
894                    if attack_index_i != winnerIndex {
895                        // Losers get consolation item
896                        self.attacks[attack_index].scavenge_choice = 0
897                    }
898                    // The winner keeps their original choice, no action needed
899                    attack_index_i = attack_index_i+1
900                }
901            }
902            // If only one hero is involved, no conflict to resolve
903        }
904        
905        for attack in self.attacks {
906            self.heroes[attack.attacker].has_moved = true
907            self.heroes[attack.attacker].shields = 0
908            var attack_pod: UInt32 = self.heroes[attack.attacker].current_pod_id
909            var attacker_hp: UInt16 = self.heroes[attack.attacker].hitpoints
910            var target_hp: UInt16 = self.heroes[attack.attack_target].hitpoints
911            var action_target_hp: UInt16 = 0
912            var attacker_hp_max: UInt16 = self.heroes[attack.attacker].max_hitpoints
913            var target_hp_max: UInt16 = self.heroes[attack.attack_target].max_hitpoints
914            var action_target_hp_max: UInt16 = 0
915            var stimBoost: UFix64 = 1.0
916            var scavenged_item_id: Int = 0
917            var actionAmount: UFix64 = 0.0
918
919            //item and looting logic
920            if(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] >= ArenaBoyzGlobals.throwable_start && 
921            self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] < ArenaBoyzGlobals.healing_start) { //throwing weapon
922                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)))
923                var originalThrownDamage: UInt16 = thrownDamage
924                action_target_hp =  self.heroes[attack.action_target].hitpoints
925                action_target_hp_max =  self.heroes[attack.action_target].max_hitpoints
926                if(self.heroes[attack.action_target].shields > thrownDamage) {
927                    thrownDamage = 0
928                }
929                else {
930                    thrownDamage = thrownDamage - self.heroes[attack.action_target].shields
931                }
932                if(originalThrownDamage > self.heroes[attack.action_target].shields ) {
933                    self.heroes[attack.action_target].shields = 0
934                }
935                else {
936                    self.heroes[attack.action_target].shields = self.heroes[attack.action_target].shields - originalThrownDamage
937                }
938                thrownDamage = self.CalculateDamage(baseDamage : thrownDamage, defense:  self.heroes[attack.action_target].GetDefense());
939                actionAmount = UFix64(thrownDamage)
940                if(thrownDamage > self.heroes[attack.action_target].hitpoints) {
941                    self.heroes[attack.action_target].hitpoints = 0;
942                }
943                else {
944                    self.heroes[attack.action_target].hitpoints = self.heroes[attack.action_target].hitpoints - thrownDamage;
945                }
946            }
947            if(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] >= ArenaBoyzGlobals.healing_start && 
948            self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] < ArenaBoyzGlobals.shield_start) {
949                
950                action_target_hp =  self.heroes[attack.action_target].hitpoints
951                action_target_hp_max =  self.heroes[attack.action_target].max_hitpoints
952                    var hitpointsBefore : UInt16 = self.heroes[attack.action_target].hitpoints
953                    self.heroes[attack.action_target].hitpoints = 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)))
954                    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))))
955                    if(self.heroes[attack.action_target].hitpoints > self.heroes[attack.action_target].max_hitpoints) {
956                        actionAmount = UFix64(self.heroes[attack.action_target].max_hitpoints - hitpointsBefore)
957                        self.heroes[attack.action_target].hitpoints = self.heroes[attack.action_target].max_hitpoints
958                        
959                    }
960
961            }
962            if(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] >= ArenaBoyzGlobals.shield_start && 
963            self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] < ArenaBoyzGlobals.stim_start) {
964                action_target_hp =  self.heroes[attack.action_target].hitpoints
965                action_target_hp_max =  self.heroes[attack.action_target].max_hitpoints
966                self.heroes[attack.action_target].shields = 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)))
967                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))))
968            }
969            if(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] >= ArenaBoyzGlobals.stim_start && 
970            self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] < ArenaBoyzGlobals.medal_start) {
971                action_target_hp =  self.heroes[attack.action_target].hitpoints
972                action_target_hp_max =  self.heroes[attack.action_target].max_hitpoints
973                self.heroes[attack.action_target].stim = 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))
974                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)))
975            }
976            if(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] >= ArenaBoyzGlobals.medal_start && 
977            self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] < ArenaBoyzGlobals.damage_start) {
978                action_target_hp =  self.heroes[attack.action_target].hitpoints
979                action_target_hp_max =  self.heroes[attack.action_target].max_hitpoints
980                self.heroes[attack.action_target].max_hitpoints = 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))
981                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)))
982            }
983            stimBoost = stimBoost + self.heroes[attack.attacker].stim
984            self.heroes[attack.attacker].stim = 0.0
985            var scavenging_iterator: Int = 0
986            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) {
987                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
988                    if(item == 0) { //TODO - stop if they go over max inventory of 12, dont let them loot in this case.
989                        
990                        self.heroes[attack.attacker].items[scavenging_iterator] = self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice]
991                        scavenged_item_id = scavenging_iterator
992                        break;
993                    }
994                    scavenging_iterator = scavenging_iterator + 1
995                }
996            }
997
998            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
999              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
1000                    if(item == 0) { //TODO - stop if they go over max inventory of 12, dont let them loot in this case.
1001                        scavenged_item_id = scavenging_iterator
1002                        break;
1003                    }
1004                    scavenging_iterator = scavenging_iterator + 1
1005                }
1006                var oldPrimary: UInt8 = self.heroes[attack.attacker].items[0]
1007                self.heroes[attack.attacker].items[0] = self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice]
1008                
1009                if(oldPrimary != 0) { //fixes a bug if player is picking up a primary when they have no items
1010                    self.heroes[attack.attacker].items[scavenged_item_id] = oldPrimary;
1011                }
1012            }
1013            //"tighten up" scavenge pool, closing in on the taken item
1014         /*    var currentIndex: Int = Int(attack.scavenge_choice);
1015            while ( currentIndex < self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool.length - 1) {
1016                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]
1017                currentIndex = currentIndex + 1
1018            }*/
1019            self.heroes[attack.attacker].ProcessConsumableItems();
1020            self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] = 0
1021
1022            //Attack damage is calculated below
1023            var isCritical: Bool = (UInt16(revertibleRandom() & 0xFFFF)%1000 < self.heroes[attack.attacker].GetAccuracy())
1024            var isCounterCritical: Bool = (UInt16(revertibleRandom() & 0xFFFF)%1000 < self.heroes[attack.attack_target].GetAccuracy())
1025            var counterAttack: Bool = (UInt16(revertibleRandom() & 0xFFFF)%1000 < self.heroes[attack.attack_target].GetCounter())// - shieldMod
1026            var damageDealt: UInt16 = self.heroes[attack.attacker].GetDamage()
1027           
1028            var originalDamageDealt: UInt16 = damageDealt
1029            if(self.heroes[attack.attack_target].shields > damageDealt) {
1030                damageDealt = 0
1031            }
1032            else {
1033                damageDealt = damageDealt - self.heroes[attack.attack_target].shields
1034            }
1035            if(originalDamageDealt > self.heroes[attack.attack_target].shields ) {
1036                self.heroes[attack.attack_target].shields = 0
1037            }
1038            else {
1039                self.heroes[attack.attack_target].shields = self.heroes[attack.attack_target].shields - originalDamageDealt
1040            }
1041            damageDealt = self.CalculateDamage(baseDamage: damageDealt, defense: self.heroes[attack.attack_target].GetDefense())
1042            
1043            if (isCritical)
1044            {
1045                damageDealt = damageDealt * 2
1046            } 
1047           
1048            // Process the counterattack
1049            var counterDamage: UInt16 = 0
1050            if (counterAttack)
1051            {
1052                counterDamage = self.CalculateDamage(baseDamage : self.heroes[attack.attack_target].GetDamage(), defense:  self.heroes[attack.attacker].GetDefense());
1053                
1054                var originalCounterDamageDealt: UInt16 = counterDamage
1055                
1056                if(self.heroes[attack.attacker].shields > counterDamage) {
1057                    counterDamage = 0
1058                }
1059                else {
1060                    counterDamage = counterDamage - self.heroes[attack.attacker].shields
1061                }
1062               
1063                if(originalCounterDamageDealt > self.heroes[attack.attacker].shields ) {
1064                    self.heroes[attack.attacker].shields = 0
1065                }
1066                else {
1067                    self.heroes[attack.attacker].shields = self.heroes[attack.attacker].shields - originalCounterDamageDealt
1068                }
1069                  if (isCounterCritical)
1070                {
1071                    counterDamage = counterDamage * 2
1072                }
1073            }
1074            //    pub event AttackResult(attacker: UInt32, target: UInt32, damage: UInt32, critical: Bool, counter_damage: UInt32, action_target: UInt32, action_damage: UInt32, action_item: UInt8 )
1075
1076            //get attack awards before damage is dealt, so we get damage scaling
1077            if(self.heroes[attack.attack_target].attack_award == 0) {
1078                self.heroes[attack.attacker].damage = self.heroes[attack.attacker].damage + UInt16(self.heroes[attack.attack_target].GetAttackAwardAmount() * stimBoost)
1079            }
1080            if(self.heroes[attack.attack_target].attack_award == 1) {
1081                self.heroes[attack.attacker].defense = self.heroes[attack.attacker].defense + UInt16(self.heroes[attack.attack_target].GetAttackAwardAmount() * stimBoost)
1082            }
1083            if(self.heroes[attack.attack_target].attack_award == 2) {
1084                self.heroes[attack.attacker].accuracy = self.heroes[attack.attacker].accuracy + UInt16(self.heroes[attack.attack_target].GetAttackAwardAmount() * stimBoost)
1085            }
1086            if(self.heroes[attack.attack_target].attack_award == 3) {
1087                self.heroes[attack.attacker].speed = self.heroes[attack.attacker].speed + UInt16(self.heroes[attack.attack_target].GetAttackAwardAmount() * stimBoost)
1088            }
1089            if(self.heroes[attack.attack_target].attack_award == 4) {
1090                self.heroes[attack.attacker].counter = self.heroes[attack.attacker].counter + UInt16(self.heroes[attack.attack_target].GetAttackAwardAmount() * stimBoost)
1091            }
1092            if(self.heroes[attack.attack_target].hitpoints < damageDealt) {
1093                self.heroes[attack.attack_target].hitpoints = 0
1094            }
1095            else {
1096                self.heroes[attack.attack_target].hitpoints = self.heroes[attack.attack_target].hitpoints - damageDealt
1097            }
1098            var game_over : Bool = false
1099            var event_emitted : Bool = false;
1100            if(self.heroes[attack.attack_target].hitpoints <= 0) {
1101                counterDamage = 0;
1102                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 )
1103                event_emitted = true
1104                self.heroes[attack.attack_target].current_pod_id = 4294967295;
1105                var target_item_iterator: Int = 0;
1106                for target_item in self.heroes[attack.attack_target].items {
1107                    var attacker_item_iterator: Int = 0;
1108                    for attacker_item in self.heroes[attack.attacker].items  { //process looting
1109                        if(attacker_item == 0) {
1110                            self.heroes[attack.attacker].items[attacker_item_iterator] = target_item
1111                            self.heroes[attack.attack_target].items[target_item_iterator] = 0;
1112                            continue;
1113                        }
1114                        attacker_item_iterator = attacker_item_iterator + 1 ;
1115                    }
1116                    target_item_iterator = target_item_iterator + 1;
1117                }
1118                if(self.handleDeath()) {
1119                    game_over = true
1120                  
1121                    return
1122                }
1123            }
1124            if(!game_over && self.heroes[attack.action_target].hitpoints <= 0) {
1125                counterDamage = 0;
1126                if(!event_emitted) {
1127                    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 )
1128                    event_emitted = true
1129                }
1130                self.heroes[attack.action_target].current_pod_id = 4294967295;
1131                var action_target_item_iterator: Int = 0;
1132                for action_target_item in self.heroes[attack.action_target].items {
1133                    var attacker_item_iterator: Int = 0;
1134                    for attacker_item in self.heroes[attack.attacker].items  { //process looting
1135                        if(attacker_item == 0) {
1136                            self.heroes[attack.attacker].items[attacker_item_iterator] = action_target_item
1137                            self.heroes[attack.action_target].items[action_target_item_iterator] = 0;
1138                            continue;
1139                        }
1140                        attacker_item_iterator = attacker_item_iterator + 1 ;
1141                    }
1142                    action_target_item_iterator = action_target_item_iterator + 1;
1143                }
1144                if(self.handleDeath()) {
1145                    game_over = true
1146                    return
1147                }
1148            }
1149            if(self.heroes[attack.attacker].hitpoints < counterDamage) {
1150                self.heroes[attack.attacker].hitpoints = 0
1151            }
1152            else {
1153                self.heroes[attack.attacker].hitpoints = self.heroes[attack.attacker].hitpoints - counterDamage
1154            }
1155           
1156            
1157            if(!event_emitted) {
1158                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 )
1159                event_emitted = true
1160            }
1161            if(!game_over && self.heroes[attack.attacker].hitpoints <= 0) {
1162                self.heroes[attack.attacker].current_pod_id = 4294967295;
1163                var counter_item_iterator: Int = 0;
1164                for counter_target_item in self.heroes[attack.attacker].items {
1165                    var attacker_item_iterator: Int = 0;
1166                    for attacker_item in self.heroes[attack.attack_target].items  { //process looting
1167                        if(attacker_item == 0) {
1168                            self.heroes[attack.attack_target].items[attacker_item_iterator] = counter_target_item
1169                            self.heroes[attack.attacker].items[counter_item_iterator] = 0;
1170                            continue;
1171                        }
1172                        attacker_item_iterator = attacker_item_iterator + 1 ;
1173                    }
1174                    counter_item_iterator = counter_item_iterator + 1;
1175                }
1176                self.handleDeath()
1177                    
1178            }
1179
1180        }
1181        while(self.attacks.length > 0){
1182            self.attacks.removeLast()
1183        }
1184    }
1185    priv fun resetGame() {
1186        while(self.heroes.length > 0) {
1187            self.heroes.removeLast()
1188        }
1189        while(self.attacks.length > 0){
1190            self.attacks.removeLast()
1191        }
1192        while(self.pods.length > 0){
1193            self.pods.removeLast()
1194        }
1195        self.signup_keys = {}
1196        self.game_id = self.game_id + 1
1197        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
1198        self.current_player_id = -1
1199        self.seconds_per_turn = ArenaBoyzGlobals.seconds_per_turn
1200        self.turn = 0
1201        self.turn_end_timestamp = 0.0
1202        self.circle = 0
1203        self.pod_size = ArenaBoyzGlobals.heroes_per_pod
1204        self.game_started = false
1205    }
1206    
1207    priv fun setEntryFee(entry_fee : UFix64) {
1208        self.entry_fee = entry_fee
1209    }
1210    pub fun CalculateDamage(baseDamage : UInt16, defense : UInt16) : UInt16
1211    {
1212        var damage : UInt16 = 0
1213        if(defense > baseDamage) {
1214            damage = 0
1215        }
1216        else {
1217            damage = baseDamage - defense
1218        }
1219        if(damage < baseDamage/ArenaBoyzGlobals.minimum_damage_divisor) {
1220            damage = baseDamage/ArenaBoyzGlobals.minimum_damage_divisor //min damage is 20% of your base
1221        }
1222        return damage
1223    }
1224    pub resource interface PlayerKeyInterface {
1225        pub fun getPlayerID(): UInt32
1226        pub fun getKeyID(): Int
1227    }
1228    pub resource PlayerKey: PlayerKeyInterface {
1229        access(contract) var player_id: UInt32
1230        access(contract) var game_id: UInt32
1231        access(contract) var key_id: Int
1232
1233         init(
1234            player_id: UInt32,
1235            game_id: UInt32,
1236            key_id: Int
1237        ) {
1238            self.player_id = player_id
1239            self.game_id = game_id
1240            self.key_id = key_id
1241        }
1242        pub fun getPlayerID() : UInt32 {
1243            return self.player_id
1244        }
1245        pub fun getKeyID() : Int {
1246            return self.key_id
1247        }
1248        access(contract) fun setGameID(game_id : UInt32) {
1249            self.game_id = game_id
1250            log(self.game_id)
1251        }
1252        access(contract) fun setPlayerID(player_id : UInt32) {
1253            self.player_id = player_id
1254        }
1255
1256    }
1257    
1258    pub resource Admin {
1259        
1260        init() {}
1261        pub fun initializeHeroesAndPods() {
1262            ABRoyaleGame.initializeHeroesAndPods(podSizes: ABRoyaleGame.getPodSizes(total_players: ABRoyaleGame.enrolled_players))
1263        }
1264        pub fun resolveAttacks() {
1265            ABRoyaleGame.resolveAttacks()
1266        }
1267        pub fun resetTurn() {
1268            
1269            ABRoyaleGame.resetTurn(podSizes: ABRoyaleGame.getPodSizes(total_players: ABRoyaleGame.alive_heroes))
1270        }
1271
1272        pub fun resetGame() {
1273            ABRoyaleGame.resetGame()
1274        }
1275        pub fun setEntryFee(entry_fee : UFix64) {
1276            ABRoyaleGame.setEntryFee(entry_fee: entry_fee)
1277        }
1278        
1279    }
1280    
1281}