Smart Contract

ABRoyaleGame

A.e5b1b335fdaaf4e2.ABRoyaleGame

Valid From

83,587,856

Deployed

2h ago
Feb 28, 2026, 05:06:06 PM UTC

Dependents

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