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