Smart Contract

ABRoyaleGame

A.3a3a4f7d9eec9073.ABRoyaleGame

Valid From

83,180,886

Deployed

1y ago
Jul 25, 2024, 09:34:00 PM UTC

Dependents

0 imports
1import FungibleToken from 0xf233dcee88fe0abe
2import NonFungibleToken from 0x1d7e57aa55817448
3import MetadataViews from 0x1d7e57aa55817448
4import ArenaBoyzGlobals from 0x3a3a4f7d9eec9073
5import ArenaBoyzHistory from 0x3a3a4f7d9eec9073
6import ArenaBoyz from 0x3a3a4f7d9eec9073
7
8pub contract ABRoyaleGame {
9    
10    pub var pods: [Pod]
11    pub var heroes: [Hero]
12    pub var seeded_hash: [UInt8]
13    pub var attacks : [Attack]
14    pub var last_results : [AttackResult] //TODO: emit events instead of a struct saying what happened during attacks
15    pub var seconds_per_turn : Int //this is now how many seconds each player is given to act
16    pub var turn : UInt32
17    pub var circle : UInt32
18    pub var enrolled_players : 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 current_key_id : Int
25    pub var signup_keys: {UInt32: Int}
26
27    pub let KeyStoragePath: StoragePath
28    pub let KeyPublicPath: PublicPath
29    pub let AdminStoragePath: StoragePath
30    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 )
31    pub event GameResultEvent(game_id: UInt32, victor_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.enrolled_players = 0 //enrolled players starting here fixes a problem but it also does not reflect now the number of players in the game0
418        self.current_player_id = -1
419        self.current_key_id = -1
420        self.seconds_per_turn = ArenaBoyzGlobals.seconds_per_turn
421        self.turn = 0
422        self.turn_end_timestamp = 0.0
423        self.circle = 0
424        self.pod_size = ArenaBoyzGlobals.heroes_per_pod
425        self.game_id = 0
426        self.game_started = false
427        self.AdminStoragePath = /storage/arenaboyzPaths2Admin
428        self.KeyStoragePath = /storage/arenaboyzPaths2Key
429        self.KeyPublicPath = /public/arenaboyzPaths2Key
430        let admin: @ABRoyaleGame.Admin <- create Admin()
431        self.account.save(<-admin, to: self.AdminStoragePath)
432    }
433 
434    //a player signs up to play the game here, pays the game fee
435    pub fun playerSignup() : @PlayerKey {
436        pre {
437            self.game_started == false : "game must not be running yet"
438            self.enrolled_players < 16 : "game must have less than 16 players" //enforcing hard cap for the time being
439        }
440        self.enrolled_players = self.enrolled_players + 1
441        self.current_player_id = self.current_player_id + 1
442        self.current_key_id = self.current_key_id + 1
443        self.signup_keys[UInt32(self.current_player_id)] = self.current_key_id
444        log(self.signup_keys[UInt32(self.current_player_id)])
445        return <- create PlayerKey(player_id: UInt32(self.current_player_id), game_id: self.game_id, key_id : self.current_key_id)
446    }
447    pub fun updatePlayerKey(signup_key: @PlayerKey) : @PlayerKey {
448        pre {
449            self.game_started == false : "game must not be running yet" 
450            
451            self.enrolled_players < 16 : "game must have less than 16 players" //enforcing hard cap for the time being
452
453        }
454        var i: Int = 0
455        while i < Int(self.current_player_id) {
456            if(self.signup_keys[UInt32(i)] == signup_key.key_id) {
457                panic("player has already signed up")
458            }
459            i = i + 1
460        }
461        signup_key.setGameID(game_id: self.game_id)
462        self.current_player_id = self.current_player_id + 1
463        signup_key.setPlayerID(player_id: UInt32(self.current_player_id))
464        self.signup_keys[UInt32(self.current_player_id)] = signup_key.key_id
465        log(self.signup_keys[UInt32(self.current_player_id)])
466        self.enrolled_players = self.enrolled_players + 1
467
468        return <-signup_key
469    }
470    
471    pub fun shuffle(array: [UInt32]) : [UInt32] {
472        let n = array.length
473        var shuffledArray = array
474
475        var i: Int = 0
476        while i < n {
477            let j = i + (Int(revertibleRandom()) % (n - i))
478            let temp = shuffledArray[i]
479            shuffledArray[i] = shuffledArray[j]
480            shuffledArray[j] = temp
481            i = i + 1
482        }
483        return shuffledArray
484    }
485
486    //for each player that signed up, init their hero with a random roll
487    priv fun initializeHeroesAndPods() {
488
489        var pod_count: UInt32 = (UInt32(self.enrolled_players) + UInt32(self.pod_size) - 1) / UInt32(self.pod_size)
490        while(pod_count > 0) {
491            self.pods.append(Pod()) 
492            pod_count = pod_count - 1
493        }
494   
495        if(self.enrolled_players < Int32(self.pod_size)) { //When we go back to pods, this will come back into play
496            self.pod_size = UInt32(self.enrolled_players)
497        }
498        var hero_ids: [UInt32] = []
499        var id : UInt32 = 0
500        while id < UInt32(self.enrolled_players) {
501            hero_ids.append(id)
502            self.heroes.append(Hero())
503            id = id + 1
504        }
505        hero_ids = self.shuffle(array: hero_ids)
506          // Assign heroes to pods
507        /*var pod_index: UInt32 = 0
508        for hid in hero_ids {
509            let pod_id = pod_index / self.pod_size
510            self.heroes[hid].GenerateHero(hero_id: hid, starting_pod_id: pod_id)
511            self.pods[Int(pod_id)].hero_ids.append(hid)
512            pod_index = pod_index + 1
513        }*/
514
515        var pod_index: UInt32 = 0
516            for hid in hero_ids {
517                var pod_id: UInt32 = pod_index / self.pod_size
518                self.heroes[hid].GenerateHero(hero_id: hid, starting_pod_id: pod_id)
519                if let signupKey = self.signup_keys[hid] {
520                    self.heroes[hid].key_id = signupKey
521                    log(signupKey)
522                } else {
523                    panic("Signup key for hero ID is nil")
524                }
525                // Check if the last hero would be alone in a new pod
526                if (pod_index == UInt32(hero_ids.length) - 1 && self.pods[Int(pod_id)].hero_ids.length == 0) {
527                    // Add the last hero to the last non-empty pod
528                    pod_id = UInt32(self.pods.length - 2)
529                }
530
531                self.pods[Int(pod_id)].hero_ids.append(hid)
532                self.heroes[hid].current_pod_id = pod_id
533                pod_index = pod_index + 1
534            }
535
536        // Sort players within each pod and set move timestamps
537        var largest_pod: Int = 0
538         for pod in self.pods {
539            // Create a local array of heroes for this pod
540            var localHeroes: [Hero] = []
541            for hero_id in pod.hero_ids {
542                localHeroes.append(self.heroes[hero_id])
543            }
544
545            // Sort the local array of heroes
546            pod.SortPlayers(Players: localHeroes)
547
548            var i: Int = 0
549            while i < Int(pod.hero_ids.length) {
550                let podHeroId: UInt32 = pod.hero_ids[i]
551                self.heroes[podHeroId].move_timestamp = getCurrentBlock().timestamp + UFix64(i * self.seconds_per_turn)
552                i = i + 1
553            }
554             if(i > largest_pod) {
555                largest_pod = i
556            }
557            
558        }
559
560        self.turn_end_timestamp = getCurrentBlock().timestamp + UFix64(self.seconds_per_turn * largest_pod)
561        self.game_started = true
562        /*
563        while(hero_id < UInt32(self.enrolled_players)) {
564            self.heroes.append(Hero())
565            self.heroes[hero_id].GenerateHero(hero_id: hero_id, starting_pod_id : pod_index/self.pod_size)
566            hero_id = hero_id + 1
567
568            self.pods[0].hero_ids.append(hero_id)
569            if(pod_index%self.pod_size == (self.pod_size-1)) { //adding final hero of pod, sort pod by speed now
570                self.pods[0].SortPlayers(Players: self.heroes)
571                var i: Int = 0
572                while i < Int(self.enrolled_players) {
573                    let podHeroId:UInt32 = self.pods[0].hero_ids[i]
574                    self.heroes[podHeroId].move_timestamp = getCurrentBlock().timestamp+UFix64(i*(self.seconds_per_turn/Int(self.pod_size)))
575                    i = i + 1
576                } 
577            }
578            
579            pod_index = pod_index + 1
580        }
581        self.turn_end_timestamp = getCurrentBlock().timestamp + UFix64(self.seconds_per_turn)
582        */
583        log(self.turn)
584    }
585
586    priv fun resetTurn() {
587        pre {
588            self.game_started == true : "game must be running"
589        }
590        self.turn = self.turn + 1
591        if(self.turn % ArenaBoyzGlobals.turns_per_circle == 0 ) {
592            self.circle = self.circle + 1
593            var aliveHeroIDs: [UInt32] = []
594            for hero in self.heroes {
595                if (hero.hitpoints > 0) {
596                    aliveHeroIDs.append(hero.hero_id)
597                }
598            }
599            var alive_heroes: UInt32 = UInt32(aliveHeroIDs.length)
600
601            // Destroy all pods
602            while (self.pods.length > 0) {
603                self.pods.removeLast()
604            }
605
606            // Calculate the number of pods needed
607            var pod_count: UInt32 = (UInt32(self.enrolled_players) + UInt32(self.pod_size) - 1) / UInt32(self.pod_size)
608            if (pod_count < 2) {
609                pod_count = 1
610            }
611
612            while (pod_count > 0) {
613                self.pods.append(Pod())
614                pod_count = pod_count - 1
615                log(pod_count)
616            }
617
618            var hero_ids: [UInt32] = aliveHeroIDs
619            hero_ids = self.shuffle(array: hero_ids)
620
621            // Assign heroes to pods
622            var pod_index: UInt32 = 0
623            for hid in hero_ids {
624                var pod_id: UInt32 = pod_index / self.pod_size
625
626                // Check if the last hero would be alone in a new pod
627                if (pod_index == UInt32(hero_ids.length) - 1 && self.pods[Int(pod_id)].hero_ids.length == 0) {
628                    // Add the last hero to the last non-empty pod
629                    pod_id = UInt32(self.pods.length - 2)
630                }
631
632                self.pods[Int(pod_id)].hero_ids.append(hid)
633                self.heroes[hid].current_pod_id = pod_id
634                pod_index = pod_index + 1
635            }
636
637        }
638        var pod_i : Int = 0
639      //  self.seconds_per_turn = 0 //resetting this to zero, we will go with the longest pod
640 
641        for pod in self.pods {
642            if(self.turn % ArenaBoyzGlobals.turns_per_circle == 0) { //reset pods here, shuffling players into new pods.
643                log("circle")
644                log(self.circle)
645                self.pods[pod_i].ResetScavengePool(Circle: self.circle)
646            }
647            if(self.turn/ArenaBoyzGlobals.turns_per_circle < 7 ) { // end of game, final circle!
648                log("doing circle!")
649                var i: Int = 0
650                for heroId in pod.hero_ids {
651                    self.heroes[heroId].ProcessEndTurn()
652                    if(self.turn % ArenaBoyzGlobals.turns_per_circle == 0) {
653                        self.heroes[heroId].ProcessEndPod()
654                    }
655                }
656            }
657            else {
658                var i: Int = 0
659                for heroId in pod.hero_ids {
660                    self.heroes[heroId].ProcessEndTurn()
661                    if(self.turn % ArenaBoyzGlobals.turns_per_circle == 0) {
662                        self.heroes[heroId].ProcessEndPod()
663                    }
664                }
665            }
666            //shorten turn length when a player is killed, but we need to do this only one time per player
667             var pod_survivors : Int = 0
668             for live_i in pod.hero_ids {
669                if self.heroes[live_i].hitpoints > 0  {
670                    pod_survivors = pod_survivors + 1
671                }
672            }
673          //  if(self.seconds_per_turn < pod_survivors*60 + 60) {
674        //        self.seconds_per_turn = pod_survivors*60 + 60
675         //   }
676            pod_i = pod_i  + 1
677        }
678        // Sort players within each pod and set move timestamps
679        var largest_pod: Int = 0
680        for pod in self.pods {
681            // Create a local array of heroes for this pod
682            var localHeroes: [Hero] = []
683            for hero_id in pod.hero_ids {
684                localHeroes.append(self.heroes[hero_id])
685            }
686
687            // Sort the local array of heroes
688            pod.SortPlayers(Players: localHeroes)
689
690            var i: Int = 0
691            while i < Int(pod.hero_ids.length) {
692                let podHeroId: UInt32 = pod.hero_ids[i]
693                self.heroes[podHeroId].move_timestamp = getCurrentBlock().timestamp + UFix64(i * self.seconds_per_turn)
694                i = i + 1
695            }
696            if(i > largest_pod) {
697                largest_pod = i
698            }
699        }
700
701        self.turn_end_timestamp = getCurrentBlock().timestamp + UFix64(self.seconds_per_turn * largest_pod)
702        log(self.turn)
703    }
704    //TODO: reshuffle pods on new circle. circle should be incremented, and players should be randomly shuffled into new pods.
705
706    //Player commits to an attack here
707    pub fun submitAttack(attacker_key: @PlayerKey, attack_target : UInt32, action_target : UInt32, scavenge_choice : UInt8, equip_primary : Bool): @PlayerKey {
708        pre {
709           
710            //TODO: speed management goes here. players should be prohibited from entering attacks before it is their turn to do so
711            //see self.tick and self.turn vs. their speed. it should also be recorded if they have moved already this turn.
712                
713                attacker_key.game_id == self.game_id : "attacker must be from this game"
714                self.heroes[attacker_key.player_id].hitpoints > 0 : "attacker must be alive!"
715                self.heroes[attack_target].hitpoints > 0 : "attack target must be alive!"
716                self.heroes[action_target].hitpoints > 0 : "action target must be alive!"
717                self.heroes[attacker_key.player_id].has_moved == false : "hero has already attacked!"
718                self.heroes[attacker_key.player_id].move_timestamp <= getCurrentBlock().timestamp : "attacker is not allowed to enter an attack yet!"
719                self.heroes[attacker_key.player_id].current_pod_id == self.heroes[attack_target].current_pod_id : "attack target is not in the same pod!"
720                self.heroes[attacker_key.player_id].current_pod_id == self.heroes[action_target].current_pod_id : "action target is not in the same pod!"
721                //TODO: Can we put in some constants or globals for design-related items in code
722            }
723        
724        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
725        var a_remove: Int = 0
726        for attack in self.attacks {
727           if(attack.attacker == attacker_key.player_id) { //if the player has submitted an attack already, remove it
728                self.attacks.remove(at: a_remove)
729                break
730            }
731            a_remove = a_remove + 1
732        }
733        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 ))
734        //TODO: we can change contract state here, such as setting a block height
735        return <-attacker_key
736    }
737    pub fun dropItem(dropper_key: @PlayerKey, item_index : Int): @PlayerKey {
738        pre {
739            dropper_key.game_id == self.game_id : "dropper must be from this game"
740        }
741        let dropped_item: Int = Int(self.heroes[dropper_key.player_id].items[item_index])
742        self.heroes[dropper_key.player_id].items[item_index] = 0
743        self.pods[self.heroes[dropper_key.player_id].current_pod_id].DropItemIntoPool(Item: UInt8(dropped_item))
744        return <-dropper_key
745    }
746    pub fun equipItem(equipper_key: @PlayerKey, item_index : Int): @PlayerKey {
747        pre{
748           equipper_key.game_id == self.game_id : "dropper must be from this game"
749           self.heroes[equipper_key.player_id].items[item_index] < ArenaBoyzGlobals.targetables_start : "has to pick a primary weapon to equip primary!"
750        }
751        let old_primary: Int = Int(self.heroes[equipper_key.player_id].items[0])
752        self.heroes[equipper_key.player_id].items[0] = self.heroes[equipper_key.player_id].items[item_index]
753        self.heroes[equipper_key.player_id].items[item_index] = UInt8(old_primary)
754
755        return <-equipper_key
756    }
757    priv fun handleDeath(): Bool {
758        var alive_heroes: Int = 0
759        var winner_id: Int32 = -1
760        var game_over: Bool = false
761        for hero in self.heroes {
762            if(hero.hitpoints > 0) {
763                alive_heroes = alive_heroes + 1
764                winner_id = Int32(hero.key_id)
765            }
766        }
767        if(alive_heroes == 0) {
768            emit GameResultEvent(game_id: self.game_id, victor_id: -1, placement: 1, ending_turn: self.turn, enrolled_players: self.enrolled_players)
769            ArenaBoyzHistory.addGame(game_id: self.game_id, victor_id: -1, placement: 1, ending_turn: self.turn, enrolled_players: self.enrolled_players)
770            game_over = true
771        }
772        else if(alive_heroes == 1) {
773            emit GameResultEvent(game_id: self.game_id, victor_id: winner_id, placement: 1, ending_turn: self.turn, enrolled_players: self.enrolled_players)
774            ArenaBoyzHistory.addGame(game_id: self.game_id, victor_id: winner_id, placement: 1, ending_turn: self.turn, enrolled_players: self.enrolled_players)
775            game_over = true
776        }
777        if(game_over) {
778            self.resetGame()
779            return true
780        }
781        return false
782    }
783
784    priv fun resolveAttacks() {
785        var validAttacks: [Attack] = [] //new code to remove attacks that have not been flagged
786    
787        for attack in self.attacks {
788            if attack.turn == self.turn {
789                validAttacks.append(attack)
790            }
791        }
792        self.attacks = validAttacks
793
794        var conflicts: {UInt8: ScavengeConflict} = {}
795        var conflict_i = 0
796        for attack in self.attacks {
797            let item_id = attack.scavenge_choice
798            let hero_id = attack.attacker
799
800            if conflicts[item_id] != nil {
801                conflicts[item_id]?.addConflict(hero_id: hero_id, attack_index: conflict_i)
802            } else {
803                conflicts[item_id] = ScavengeConflict( hero_ids: [hero_id], attack_indicies: [conflict_i])
804            }
805            conflict_i = conflict_i + 1
806        }
807
808        for conflict in conflicts.values {
809            if conflict.hero_ids.length > 1 {
810                // More than one hero is trying to scavenge the same item, resolve conflict
811                let winnerIndex = Int(revertibleRandom())%conflict.hero_ids.length
812                var attack_index_i = 0
813                for attack_index in conflict.attack_indicies {
814                    if attack_index_i != winnerIndex {
815                        // Losers get consolation item
816                        self.attacks[attack_index].scavenge_choice = 0
817                    }
818                    // The winner keeps their original choice, no action needed
819                    attack_index_i = attack_index_i+1
820                }
821            }
822            // If only one hero is involved, no conflict to resolve
823        }
824        
825        for attack in self.attacks {
826            log(self.heroes[attack.attacker].GetDamage())
827            self.heroes[attack.attacker].has_moved = true
828            self.heroes[attack.attacker].shields = 0
829            var attack_pod: UInt32 = self.heroes[attack.attacker].current_pod_id
830            var attacker_hp: UInt16 = self.heroes[attack.attacker].hitpoints
831            var target_hp: UInt16 = self.heroes[attack.attack_target].hitpoints
832            var action_target_hp: UInt16 = 0
833            var attacker_hp_max: UInt16 = self.heroes[attack.attacker].max_hitpoints
834            var target_hp_max: UInt16 = self.heroes[attack.attack_target].max_hitpoints
835            var action_target_hp_max: UInt16 = 0
836            var stimBoost: UFix64 = 1.0
837            var scavenged_item_id: Int = 0
838            var actionAmount: UFix64 = 0.0
839
840            //item and looting logic
841            if(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] >= ArenaBoyzGlobals.throwable_start && 
842            self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] < ArenaBoyzGlobals.healing_start) { //throwing weapon
843                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)))
844                var originalThrownDamage: UInt16 = thrownDamage
845                action_target_hp =  self.heroes[attack.action_target].hitpoints
846                action_target_hp_max =  self.heroes[attack.action_target].max_hitpoints
847                if(self.heroes[attack.action_target].shields > thrownDamage) {
848                    thrownDamage = 0
849                }
850                else {
851                    thrownDamage = thrownDamage - self.heroes[attack.action_target].shields
852                }
853                if(originalThrownDamage > self.heroes[attack.action_target].shields ) {
854                    self.heroes[attack.action_target].shields = 0
855                }
856                else {
857                    self.heroes[attack.action_target].shields = self.heroes[attack.action_target].shields - originalThrownDamage
858                }
859                thrownDamage = self.CalculateDamage(baseDamage : thrownDamage, defense:  self.heroes[attack.action_target].GetDefense());
860                actionAmount = UFix64(thrownDamage)
861                if(thrownDamage > self.heroes[attack.action_target].hitpoints) {
862                    self.heroes[attack.action_target].hitpoints = 0;
863                }
864                else {
865                    self.heroes[attack.action_target].hitpoints = self.heroes[attack.action_target].hitpoints - thrownDamage;
866                }
867            }
868            if(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] >= ArenaBoyzGlobals.healing_start && 
869            self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] < ArenaBoyzGlobals.shield_start) {
870                
871                action_target_hp =  self.heroes[attack.action_target].hitpoints
872                action_target_hp_max =  self.heroes[attack.action_target].max_hitpoints
873                    var hitpointsBefore : UInt16 = self.heroes[attack.action_target].hitpoints
874                    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)))
875                    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))))
876                    if(self.heroes[attack.action_target].hitpoints > self.heroes[attack.action_target].max_hitpoints) {
877                        actionAmount = UFix64(self.heroes[attack.action_target].max_hitpoints - hitpointsBefore)
878                        self.heroes[attack.action_target].hitpoints = self.heroes[attack.action_target].max_hitpoints
879                        
880                    }
881
882            }
883            if(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] >= ArenaBoyzGlobals.shield_start && 
884            self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] < ArenaBoyzGlobals.stim_start) {
885                action_target_hp =  self.heroes[attack.action_target].hitpoints
886                action_target_hp_max =  self.heroes[attack.action_target].max_hitpoints
887                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)))
888                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))))
889            }
890            if(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] >= ArenaBoyzGlobals.stim_start && 
891            self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] < ArenaBoyzGlobals.medal_start) {
892                action_target_hp =  self.heroes[attack.action_target].hitpoints
893                action_target_hp_max =  self.heroes[attack.action_target].max_hitpoints
894                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))
895                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)))
896            }
897            if(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] >= ArenaBoyzGlobals.medal_start && 
898            self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] < ArenaBoyzGlobals.damage_start) {
899                action_target_hp =  self.heroes[attack.action_target].hitpoints
900                action_target_hp_max =  self.heroes[attack.action_target].max_hitpoints
901                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))
902                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)))
903            }
904            stimBoost = stimBoost + self.heroes[attack.attacker].stim
905            self.heroes[attack.attacker].stim = 0.0
906            var scavenging_iterator: Int = 0
907            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) {
908                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
909                    if(item == 0) { //TODO - stop if they go over max inventory of 12, dont let them loot in this case.
910                        log(item.toString())
911                        log(scavenging_iterator.toString())
912                        self.heroes[attack.attacker].items[scavenging_iterator] = self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice]
913                        scavenged_item_id = scavenging_iterator
914                        break;
915                    }
916                    scavenging_iterator = scavenging_iterator + 1
917                }
918            }
919
920            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
921              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
922                    if(item == 0) { //TODO - stop if they go over max inventory of 12, dont let them loot in this case.
923                        scavenged_item_id = scavenging_iterator
924                        break;
925                    }
926                    scavenging_iterator = scavenging_iterator + 1
927                }
928                var oldPrimary: UInt8 = self.heroes[attack.attacker].items[0]
929                self.heroes[attack.attacker].items[0] = self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice]
930                
931                if(oldPrimary != 0) { //fixes a bug if player is picking up a primary when they have no items
932                    self.heroes[attack.attacker].items[scavenged_item_id] = oldPrimary;
933                }
934            }
935            //"tighten up" scavenge pool, closing in on the taken item
936         /*    var currentIndex: Int = Int(attack.scavenge_choice);
937            while ( currentIndex < self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool.length - 1) {
938                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]
939                currentIndex = currentIndex + 1
940            }*/
941            self.heroes[attack.attacker].ProcessConsumableItems();
942            self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] = 0
943
944            //Attack damage is calculated below
945            var isCritical: Bool = (UInt16(revertibleRandom() & 0xFFFF)%1000 < self.heroes[attack.attacker].GetAccuracy())
946            var isCounterCritical: Bool = (UInt16(revertibleRandom() & 0xFFFF)%1000 < self.heroes[attack.attack_target].GetAccuracy())
947            var counterAttack: Bool = (UInt16(revertibleRandom() & 0xFFFF)%1000 < self.heroes[attack.attack_target].GetCounter())// - shieldMod
948            var damageDealt: UInt16 = self.heroes[attack.attacker].GetDamage()
949            log(damageDealt)
950           
951            var originalDamageDealt: UInt16 = damageDealt
952            if(self.heroes[attack.attack_target].shields > damageDealt) {
953                damageDealt = 0
954            }
955            else {
956                damageDealt = damageDealt - self.heroes[attack.attack_target].shields
957            }
958            if(originalDamageDealt > self.heroes[attack.attack_target].shields ) {
959                self.heroes[attack.attack_target].shields = 0
960            }
961            else {
962                self.heroes[attack.attack_target].shields = self.heroes[attack.attack_target].shields - originalDamageDealt
963            }
964            damageDealt = self.CalculateDamage(baseDamage: damageDealt, defense: self.heroes[attack.attack_target].GetDefense())
965            
966            log(damageDealt)
967            if (isCritical)
968            {
969                damageDealt = damageDealt * 2
970            }
971            // Process the counterattack
972            var counterDamage: UInt16 = 0
973            if (counterAttack)
974            {
975                counterDamage = self.CalculateDamage(baseDamage : self.heroes[attack.attack_target].GetDamage(), defense:  self.heroes[attack.attacker].GetDefense());
976                
977                var originalCounterDamageDealt: UInt16 = counterDamage
978                
979                if(self.heroes[attack.attacker].shields > counterDamage) {
980                    counterDamage = 0
981                }
982                else {
983                    counterDamage = counterDamage - self.heroes[attack.attacker].shields
984                }
985               
986                if(originalCounterDamageDealt > self.heroes[attack.attacker].shields ) {
987                    self.heroes[attack.attacker].shields = 0
988                }
989                else {
990                    self.heroes[attack.attacker].shields = self.heroes[attack.attacker].shields - originalCounterDamageDealt
991                }
992                  if (isCounterCritical)
993                {
994                    counterDamage = counterDamage * 2
995                }
996            }
997            //    pub event AttackResult(attacker: UInt32, target: UInt32, damage: UInt32, critical: Bool, counter_damage: UInt32, action_target: UInt32, action_damage: UInt32, action_item: UInt8 )
998
999            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 )
1000            //get attack awards before damage is dealt, so we get damage scaling
1001            if(self.heroes[attack.attack_target].attack_award == 0) {
1002                self.heroes[attack.attacker].damage = self.heroes[attack.attacker].damage + UInt16(self.heroes[attack.attack_target].GetAttackAwardAmount() * stimBoost)
1003            }
1004            if(self.heroes[attack.attack_target].attack_award == 1) {
1005                self.heroes[attack.attacker].defense = self.heroes[attack.attacker].defense + UInt16(self.heroes[attack.attack_target].GetAttackAwardAmount() * stimBoost)
1006            }
1007            if(self.heroes[attack.attack_target].attack_award == 2) {
1008                self.heroes[attack.attacker].accuracy = self.heroes[attack.attacker].accuracy + UInt16(self.heroes[attack.attack_target].GetAttackAwardAmount() * stimBoost)
1009            }
1010            if(self.heroes[attack.attack_target].attack_award == 3) {
1011                self.heroes[attack.attacker].speed = self.heroes[attack.attacker].speed + UInt16(self.heroes[attack.attack_target].GetAttackAwardAmount() * stimBoost)
1012            }
1013            if(self.heroes[attack.attack_target].attack_award == 4) {
1014                self.heroes[attack.attacker].counter = self.heroes[attack.attacker].counter + UInt16(self.heroes[attack.attack_target].GetAttackAwardAmount() * stimBoost)
1015            }
1016            if(self.heroes[attack.attack_target].hitpoints < damageDealt) {
1017                self.heroes[attack.attack_target].hitpoints = 0
1018            }
1019            else {
1020                self.heroes[attack.attack_target].hitpoints = self.heroes[attack.attack_target].hitpoints - damageDealt
1021            }
1022            log(damageDealt)
1023            if(self.heroes[attack.attacker].hitpoints < counterDamage) {
1024                self.heroes[attack.attacker].hitpoints = 0
1025            }
1026            else {
1027                self.heroes[attack.attacker].hitpoints = self.heroes[attack.attacker].hitpoints - counterDamage
1028            }
1029            var game_over : Bool = false
1030            if(self.heroes[attack.attack_target].hitpoints <= 0) {
1031                self.heroes[attack.attack_target].current_pod_id = 4294967295;
1032                var target_item_iterator: Int = 0;
1033                for target_item in self.heroes[attack.attack_target].items {
1034                    var attacker_item_iterator: Int = 0;
1035                    for attacker_item in self.heroes[attack.attacker].items  { //process looting
1036                        if(attacker_item == 0) {
1037                            self.heroes[attack.attacker].items[attacker_item_iterator] = target_item
1038                            self.heroes[attack.attack_target].items[target_item_iterator] = 0;
1039                            continue;
1040                        }
1041                        attacker_item_iterator = attacker_item_iterator + 1 ;
1042                    }
1043                    target_item_iterator = target_item_iterator + 1;
1044                }
1045                if(self.handleDeath()) {
1046                    game_over = true
1047                }
1048            }
1049            if(!game_over && self.heroes[attack.action_target].hitpoints <= 0) {
1050                self.heroes[attack.action_target].current_pod_id = 4294967295;
1051                var action_target_item_iterator: Int = 0;
1052                for action_target_item in self.heroes[attack.action_target].items {
1053                    var attacker_item_iterator: Int = 0;
1054                    for attacker_item in self.heroes[attack.attacker].items  { //process looting
1055                        if(attacker_item == 0) {
1056                            self.heroes[attack.attacker].items[attacker_item_iterator] = action_target_item
1057                            self.heroes[attack.action_target].items[action_target_item_iterator] = 0;
1058                            continue;
1059                        }
1060                        attacker_item_iterator = attacker_item_iterator + 1 ;
1061                    }
1062                    action_target_item_iterator = action_target_item_iterator + 1;
1063                }
1064                if(self.handleDeath()) {
1065                    game_over = true
1066                }
1067            }
1068            if(!game_over && self.heroes[attack.attacker].hitpoints <= 0) {
1069                self.heroes[attack.attacker].current_pod_id = 4294967295;
1070                var counter_item_iterator: Int = 0;
1071                for counter_target_item in self.heroes[attack.attacker].items {
1072                    var attacker_item_iterator: Int = 0;
1073                    for attacker_item in self.heroes[attack.attack_target].items  { //process looting
1074                        if(attacker_item == 0) {
1075                            self.heroes[attack.attack_target].items[attacker_item_iterator] = counter_target_item
1076                            self.heroes[attack.attacker].items[counter_item_iterator] = 0;
1077                            continue;
1078                        }
1079                        attacker_item_iterator = attacker_item_iterator + 1 ;
1080                    }
1081                    counter_item_iterator = counter_item_iterator + 1;
1082                }
1083                self.handleDeath()
1084                    
1085            }
1086
1087        }
1088        while(self.attacks.length > 0){
1089            self.attacks.removeLast()
1090        }
1091    }
1092    priv fun resetGame() {
1093        while(self.heroes.length > 0) {
1094            self.heroes.removeLast()
1095        }
1096        while(self.attacks.length > 0){
1097            self.attacks.removeLast()
1098        }
1099        while(self.pods.length > 0){
1100            self.pods.removeLast()
1101        }
1102        self.signup_keys = {}
1103        self.game_id = self.game_id + 1
1104        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
1105        self.current_player_id = -1
1106        self.seconds_per_turn = ArenaBoyzGlobals.seconds_per_turn
1107        self.turn = 0
1108        self.turn_end_timestamp = 0.0
1109        self.circle = 0
1110        self.pod_size = ArenaBoyzGlobals.heroes_per_pod
1111        self.game_started = false
1112    }
1113    pub fun CalculateDamage(baseDamage : UInt16, defense : UInt16) : UInt16
1114    {
1115        var damage : UInt16 = 0
1116        if(defense > baseDamage) {
1117            damage = 0
1118        }
1119        else {
1120            damage = baseDamage - defense
1121        }
1122        if(damage < baseDamage/ArenaBoyzGlobals.minimum_damage_divisor) {
1123            damage = baseDamage/ArenaBoyzGlobals.minimum_damage_divisor //min damage is 20% of your base
1124        }
1125        return damage
1126    }
1127    pub resource interface PlayerKeyInterface {
1128        pub fun getPlayerID(): UInt32
1129        pub fun getKeyID(): Int
1130    }
1131    pub resource PlayerKey: PlayerKeyInterface {
1132        access(contract) var player_id: UInt32
1133        access(contract) var game_id: UInt32
1134        access(contract) var key_id: Int
1135
1136         init(
1137            player_id: UInt32,
1138            game_id: UInt32,
1139            key_id: Int
1140        ) {
1141            self.player_id = player_id
1142            self.game_id = game_id
1143            self.key_id = key_id
1144        }
1145        pub fun getPlayerID() : UInt32 {
1146            return self.player_id
1147        }
1148        pub fun getKeyID() : Int {
1149            return self.key_id
1150        }
1151        access(contract) fun setGameID(game_id : UInt32) {
1152            self.game_id = game_id
1153            log(self.game_id)
1154        }
1155        access(contract) fun setPlayerID(player_id : UInt32) {
1156            self.player_id = player_id
1157        }
1158
1159    }
1160    
1161    pub resource Admin {
1162        
1163        init() {}
1164        pub fun initializeHeroesAndPods() {
1165            ABRoyaleGame.initializeHeroesAndPods()
1166        }
1167        pub fun resolveAttacks() {
1168            ABRoyaleGame.resolveAttacks()
1169        }
1170        pub fun resetTurn() {
1171            ABRoyaleGame.resetTurn()
1172        }
1173
1174        pub fun resetGame() {
1175            ABRoyaleGame.resetGame()
1176        }
1177        
1178    }
1179    
1180}