Smart Contract
ABRoyaleGame
A.e5b1b335fdaaf4e2.ABRoyaleGame
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}