Smart Contract
ABRoyaleGame
A.6e7cad7066b272e9.ABRoyaleGame
1import FungibleToken from 0xf233dcee88fe0abe
2import FlowToken from 0x1654653399040a61
3import ArenaBoyzGlobals from 0x6e7cad7066b272e9
4import ArenaBoyzHistory from 0x6e7cad7066b272e9
5
6
7access(all) contract ABRoyaleGame {
8
9 access(all) var pods: [Pod]
10 access(all) var heroes: [Hero]
11 access(all) var seeded_hash: [UInt8]
12 access(all) var attacks: [Attack]
13 access(all) var last_results: [AttackResult] // TODO: emit events instead of a struct for attack results
14 access(all) var seconds_per_turn: Int // Number of seconds each player is given to act
15 access(all) var turn: UInt32
16 access(all) var circle: UInt32
17 access(all) var enrolled_players: Int32
18 access(all) var current_player_id: Int32
19 access(all) var pod_size: UInt32
20 access(all) var turn_end_timestamp: UFix64
21 access(all) var game_started: Bool
22 access(all) var game_id: UInt32
23 access(all) var entry_fee: UFix64
24 access(all) var payment_vault: @{FungibleToken.Vault}
25 access(all) var runners_up: [Int32]
26 access(all) var runner_up_id: Int32
27 access(all) var second_runner_up_id: Int32
28 access(all) var current_key_id: Int
29 access(all) var signup_keys: {UInt32: Int}
30
31 access(all) let KeyStoragePath: StoragePath
32 access(all) let KeyPublicPath: PublicPath
33 access(all) let AdminStoragePath: StoragePath
34
35 init() {
36 self.heroes = []
37 self.attacks = []
38 self.last_results = []
39 self.seeded_hash = []
40 self.pods = []
41 self.signup_keys = {}
42 self.runners_up = [-1, -1, -1]
43 self.enrolled_players = 0
44 self.current_player_id = -1
45 self.current_key_id = -1
46 self.seconds_per_turn = ArenaBoyzGlobals.seconds_per_turn
47 self.turn = 0
48 self.turn_end_timestamp = 0.0
49 self.circle = 0
50 self.pod_size = ArenaBoyzGlobals.heroes_per_pod
51 self.game_id = 0
52 self.entry_fee = 10.0
53 self.payment_vault <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>())
54 self.runner_up_id = 0
55 self.second_runner_up_id = 0
56 self.game_started = false
57 self.AdminStoragePath = /storage/arenaboyzPathsNewAdmin
58 self.KeyStoragePath = /storage/arenaboyzPathsNewKey
59 self.KeyPublicPath = /public/arenaboyzPathsNewKey
60 let admin: @ABRoyaleGame.Admin <- create Admin()
61 self.account.storage.save(<-admin, to: self.AdminStoragePath)
62 }
63
64 //1.0 setters
65 access(account) fun setPods(newPods: [Pod]) {
66 self.pods = newPods
67 }
68
69 access(account) fun setHeroes(newHeroes: [Hero]) {
70 self.heroes = newHeroes
71 }
72
73 access(account) fun setSeededHash(newSeededHash: [UInt8]) {
74 self.seeded_hash = newSeededHash
75 }
76
77 access(account) fun setAttacks(newAttacks: [Attack]) {
78 self.attacks = newAttacks
79 }
80
81 access(account) fun setLastResults(newLastResults: [AttackResult]) {
82 self.last_results = newLastResults
83 }
84
85 access(account) fun setSecondsPerTurn(newSecondsPerTurn: Int) {
86 self.seconds_per_turn = newSecondsPerTurn
87 }
88
89 access(account) fun setTurn(newTurn: UInt32) {
90 self.turn = newTurn
91 }
92
93 access(account) fun setCircle(newCircle: UInt32) {
94 self.circle = newCircle
95 }
96
97 access(account) fun setEnrolledPlayers(newEnrolledPlayers: Int32) {
98 self.enrolled_players = newEnrolledPlayers
99 }
100
101 access(account) fun setCurrentPlayerId(newCurrentPlayerId: Int32) {
102 self.current_player_id = newCurrentPlayerId
103 }
104
105 access(account) fun setPodSize(newPodSize: UInt32) {
106 self.pod_size = newPodSize
107 }
108
109 access(account) fun setTurnEndTimestamp(newTurnEndTimestamp: UFix64) {
110 self.turn_end_timestamp = newTurnEndTimestamp
111 }
112
113 access(account) fun setGameStarted(newGameStarted: Bool) {
114 self.game_started = newGameStarted
115 }
116
117 access(account) fun setGameId(newGameId: UInt32) {
118 self.game_id = newGameId
119 }
120
121 access(account) fun setRunnersUp(newRunnersUp: [Int32]) {
122 self.runners_up = newRunnersUp
123 }
124
125 access(account) fun setRunnerUpId(newRunnerUpId: Int32) {
126 self.runner_up_id = newRunnerUpId
127 }
128
129 access(account) fun setSecondRunnerUpId(newSecondRunnerUpId: Int32) {
130 self.second_runner_up_id = newSecondRunnerUpId
131 }
132
133 access(account) fun setCurrentKeyId(newCurrentKeyId: Int) {
134 self.current_key_id = newCurrentKeyId
135 }
136
137 access(account) fun setSignupKeys(newSignupKeys: {UInt32: Int}) {
138 self.signup_keys = newSignupKeys
139 }
140 access(all) event AttackResultEvent(
141 pod: UInt32,
142 attacker: UInt32,
143 attacker_key: Int,
144 target_key: Int,
145 action_key: Int,
146 target: UInt32,
147 damage: UInt16,
148 critical: Bool,
149 counter_critical: Bool,
150 counter_damage: UInt16,
151 action_target: UInt32,
152 action_amount: UFix64,
153 action_item: UInt8,
154 game_id: UInt32,
155 attacker_hp: UInt16,
156 target_hp: UInt16,
157 action_target_hp: UInt16,
158 attacker_hp_max: UInt16,
159 target_hp_max: UInt16,
160 action_target_hp_max: UInt16
161 )
162
163 access(all) event GameResultEvent(
164 game_id: UInt32,
165 victor_id: Int32,
166 ending_turn: UInt32,
167 enrolled_players: Int32
168 )
169
170 access(all) event GameResultEventV2(
171 game_id: UInt32,
172 victor_id: Int32,
173 runner_up_id: Int32,
174 second_runner_up_id: Int32,
175 ending_turn: UInt32,
176 enrolled_players: Int32
177 )
178
179 access(all) struct ScavengeConflict {
180
181 access(all) var hero_ids: [UInt32]
182 access(all) var attack_indicies: [Int]
183
184 init(hero_ids: [UInt32], attack_indicies: [Int]) {
185 self.hero_ids = hero_ids
186 self.attack_indicies = attack_indicies
187 }
188
189 access(contract) fun addConflict(hero_id: UInt32, attack_index: Int) {
190 self.hero_ids.append(hero_id)
191 self.attack_indicies.append(attack_index)
192 }
193 }
194
195 access(all) struct Pod {
196 access(all) var hero_ids: [UInt32] // IDs of heroes in this pod
197 access(all) var scavenge_pool: [UInt8; 32] // Scavenge pool for this pod
198
199 init() {
200 self.hero_ids = []
201 self.scavenge_pool = ArenaBoyzGlobals.getScavengePool0()
202 }
203 access(account) fun setHeroIds(newHeroIds: [UInt32]) {
204 self.hero_ids = newHeroIds
205 }
206 access(account) fun setScavengePool(newScavengePool: [UInt8; 32]) {
207 self.scavenge_pool = newScavengePool
208 }
209
210 access(account) fun ResetScavengePool(Circle: UInt32) {
211 if Circle == 1 {
212 self.scavenge_pool = ArenaBoyzGlobals.getScavengePool1()
213 } else if Circle == 2 {
214 self.scavenge_pool = ArenaBoyzGlobals.getScavengePool2()
215 } else if Circle == 3 {
216 self.scavenge_pool = ArenaBoyzGlobals.getScavengePool3()
217 } else if Circle == 4 {
218 self.scavenge_pool = ArenaBoyzGlobals.getScavengePool4()
219 } else if Circle == 5 {
220 self.scavenge_pool = ArenaBoyzGlobals.getScavengePool5()
221 } else if Circle == 6 {
222 self.scavenge_pool = ArenaBoyzGlobals.getScavengePool6()
223 } else {
224 self.scavenge_pool = ArenaBoyzGlobals.getScavengePool7()
225 }
226 }
227
228 access(account) fun SortPlayers(Players: [Hero]) {
229 var i: Int = 0
230 while i < self.hero_ids.length - 1 {
231 var maxIndex: Int = i
232 var j: Int = i + 1
233 while j < self.hero_ids.length {
234 let heroIdJ: UInt32 = self.hero_ids[j]
235 let heroIdMax: UInt32 = self.hero_ids[maxIndex]
236
237 var speedJ: UInt16 = 0
238 var speedMax: UInt16 = 0
239 var hpJ: UInt16 = 0
240 var hpMax: UInt16 = 0
241
242 // Find hero by ID for heroIdJ
243 var playerIndex = 0
244 while playerIndex < Players.length {
245 let player = Players[playerIndex]
246
247 if player.hero_id == heroIdJ {
248 speedJ = player.GetSpeed()
249 hpJ = player.hitpoints
250 break
251 }
252
253 playerIndex = playerIndex + 1
254 }
255
256 // Reset index for the next loop
257 playerIndex = 0
258 while playerIndex < Players.length {
259 let player = Players[playerIndex]
260
261 if player.hero_id == heroIdMax {
262 speedMax = player.GetSpeed()
263 hpMax = player.hitpoints
264 break
265 }
266
267 playerIndex = playerIndex + 1
268 }
269
270 if hpJ == 0 && hpMax > 0 {
271 // Skip, as heroes with 0 HP should be sorted to the back
272 } else if hpMax == 0 && hpJ > 0 {
273 maxIndex = j
274 } else if speedJ > speedMax || (speedJ == speedMax && revertibleRandom<UInt32>() > revertibleRandom<UInt32>()) {
275 maxIndex = j
276 }
277 j = j + 1
278 }
279 if maxIndex != i {
280 let temp: UInt32 = self.hero_ids[i]
281 self.hero_ids[i] = self.hero_ids[maxIndex]
282 self.hero_ids[maxIndex] = temp
283 }
284 i = i + 1
285 }
286 }
287
288 access(account) fun DropItemIntoPool(Item: UInt8) {
289 var i: Int = 0
290 while i < self.scavenge_pool.length {
291 if self.scavenge_pool[i] == 0 {
292 self.scavenge_pool[i] = Item
293 break
294 }
295 i = i + 1
296 }
297 }
298 }
299
300 access(all) struct Hero {
301 access(all) var hero_id: UInt32
302 access(all) var key_id: Int
303 access(all) var hitpoints: UInt16
304 access(all) var max_hitpoints: UInt16
305 access(all) var shields: UInt16
306 access(all) var damage: UInt16
307 access(all) var defense: UInt16
308 access(all) var accuracy: UInt16
309 access(all) var speed: UInt16
310 access(all) var counter: UInt16
311 access(all) var attack_award: UInt8
312 access(all) var stim: UFix64
313 access(all) var has_moved: Bool // 0: cannot go yet, 1: can take action, 2: has already taken action
314 access(all) var move_timestamp: UFix64
315 access(all) var current_pod_id: UInt32 // Storing the pod the hero is in, rather than iterating through all pods
316 access(all) var items: [UInt8; 24]
317
318 init() {
319 self.hero_id = 0
320 self.key_id = 0
321 self.hitpoints = 0
322 self.shields = 0
323 self.max_hitpoints = 0
324 self.damage = 0
325 self.defense = 0
326 self.accuracy = 0
327 self.speed = 0
328 self.counter = 0
329 self.attack_award = 0
330 self.stim = 0.0
331 self.current_pod_id = 0
332 self.has_moved = false
333 self.move_timestamp = 0.0
334 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]
335 }
336 // 1.0 setters
337 access(account) fun setHeroId(newHeroId: UInt32) {
338 self.hero_id = newHeroId
339 }
340
341 access(account) fun setKeyId(newKeyId: Int) {
342 self.key_id = newKeyId
343 }
344
345 access(account) fun setHitpoints(newHitpoints: UInt16) {
346 self.hitpoints = newHitpoints
347 }
348
349 access(account) fun setMaxHitpoints(newMaxHitpoints: UInt16) {
350 self.max_hitpoints = newMaxHitpoints
351 }
352
353 access(account) fun setShields(newShields: UInt16) {
354 self.shields = newShields
355 }
356
357 access(account) fun setDamage(newDamage: UInt16) {
358 self.damage = newDamage
359 }
360
361 access(account) fun setDefense(newDefense: UInt16) {
362 self.defense = newDefense
363 }
364
365 access(account) fun setAccuracy(newAccuracy: UInt16) {
366 self.accuracy = newAccuracy
367 }
368
369 access(account) fun setSpeed(newSpeed: UInt16) {
370 self.speed = newSpeed
371 }
372
373 access(account) fun setCounter(newCounter: UInt16) {
374 self.counter = newCounter
375 }
376
377 access(account) fun setAttackAward(newAttackAward: UInt8) {
378 self.attack_award = newAttackAward
379 }
380
381 access(account) fun setStim(newStim: UFix64) {
382 self.stim = newStim
383 }
384
385 access(account) fun setHasMoved(newHasMoved: Bool) {
386 self.has_moved = newHasMoved
387 }
388
389 access(account) fun setMoveTimestamp(newMoveTimestamp: UFix64) {
390 self.move_timestamp = newMoveTimestamp
391 }
392
393 access(account) fun setCurrentPodId(newCurrentPodId: UInt32) {
394 self.current_pod_id = newCurrentPodId
395 }
396
397 access(account) fun setItems(newItems: [UInt8; 24]) {
398 self.items = newItems
399 }
400
401 access(account) fun setItem(newItemIndex: Int, newItemID: UInt8) {
402 self.items[newItemIndex] = newItemID
403 }
404
405 access(all) fun GetDamage(): UInt16 {
406 var temp_stat: UInt16 = self.damage
407 var effective_item_max: Int = ArenaBoyzGlobals.effective_item_max
408 var i = 0
409 while i < self.items.length {
410 let item = self.items[i]
411 if effective_item_max > 0 {
412 if i == 0 { // Only count primary weapons in slot 0
413 if item >= ArenaBoyzGlobals.ar_start && item < ArenaBoyzGlobals.sniper_start { // AR, defense-based
414 temp_stat = temp_stat + (ArenaBoyzGlobals.primary_bonus_per_thresh * UInt16(item)) * (self.GetDefense() / ArenaBoyzGlobals.threshhold_amount)
415 } else if item >= ArenaBoyzGlobals.sniper_start && item < ArenaBoyzGlobals.smg_start { // Sniper, accuracy-based
416 temp_stat = temp_stat + (ArenaBoyzGlobals.primary_bonus_per_thresh * UInt16(item - (ArenaBoyzGlobals.sniper_start - 1))) * (self.GetAccuracy() / ArenaBoyzGlobals.threshhold_amount)
417 } else if item >= ArenaBoyzGlobals.smg_start && item < ArenaBoyzGlobals.shotgun_start { // SMG, speed-based
418 temp_stat = temp_stat + (ArenaBoyzGlobals.primary_bonus_per_thresh * UInt16(item - (ArenaBoyzGlobals.smg_start - 1))) * (self.GetSpeed() / ArenaBoyzGlobals.threshhold_amount)
419 } else if item >= ArenaBoyzGlobals.shotgun_start && item < ArenaBoyzGlobals.throwable_start { // Shotgun, counter-based
420 temp_stat = temp_stat + (ArenaBoyzGlobals.primary_bonus_per_thresh * UInt16(item - (ArenaBoyzGlobals.shotgun_start - 1))) * (self.GetCounter() / ArenaBoyzGlobals.threshhold_amount)
421 }
422 }
423 if item >= ArenaBoyzGlobals.damage_start && item < ArenaBoyzGlobals.defense_start {
424 temp_stat = temp_stat + ArenaBoyzGlobals.equipment_bonus_per_level * UInt16(item - (ArenaBoyzGlobals.damage_start - 1))
425 }
426 }
427 effective_item_max = effective_item_max - 1
428 i = i + 1
429 }
430 return temp_stat
431 }
432
433 access(all) fun GetAccuracy(): UInt16 {
434 var temp_stat: UInt16 = self.accuracy
435 var effective_item_max: Int = ArenaBoyzGlobals.effective_item_max
436 var i = 0
437 while i < self.items.length {
438 let item = self.items[i]
439 if effective_item_max > 0 {
440 if item >= ArenaBoyzGlobals.accuracy_start && item < ArenaBoyzGlobals.speed_start {
441 temp_stat = temp_stat + ArenaBoyzGlobals.equipment_bonus_per_level * UInt16(item - (ArenaBoyzGlobals.accuracy_start - 1))
442 }
443 }
444 effective_item_max = effective_item_max - 1
445 i = i + 1
446 }
447 return temp_stat
448 }
449
450 access(all) fun GetCounter(): UInt16 {
451 var temp_stat: UInt16 = self.counter
452 var effective_item_max: Int = ArenaBoyzGlobals.effective_item_max
453 var i = 0
454 while i < self.items.length {
455 let item = self.items[i]
456 if effective_item_max > 0 {
457 if item >= ArenaBoyzGlobals.counter_start && item < ArenaBoyzGlobals.items_end {
458 temp_stat = temp_stat + ArenaBoyzGlobals.equipment_bonus_per_level * UInt16(item - (ArenaBoyzGlobals.counter_start - 1))
459 }
460 }
461 effective_item_max = effective_item_max - 1
462 i = i + 1
463 }
464 return temp_stat
465 }
466
467 access(all) fun GetDefense(): UInt16 {
468 var temp_stat: UInt16 = self.defense
469 var effective_item_max: Int = ArenaBoyzGlobals.effective_item_max
470 var i = 0
471 while i < self.items.length {
472 let item = self.items[i]
473 if effective_item_max > 0 {
474 if item >= ArenaBoyzGlobals.defense_start && item < ArenaBoyzGlobals.accuracy_start {
475 temp_stat = temp_stat + ArenaBoyzGlobals.equipment_bonus_per_level * UInt16(item - (ArenaBoyzGlobals.defense_start - 1))
476 }
477 }
478 effective_item_max = effective_item_max - 1
479 i = i + 1
480 }
481 return temp_stat
482 }
483
484 access(all) fun GetSpeed(): UInt16 {
485 var temp_stat: UInt16 = self.speed
486 var effective_item_max: Int = ArenaBoyzGlobals.effective_item_max
487 var i = 0
488 while i < self.items.length {
489 let item = self.items[i]
490 if effective_item_max > 0 {
491 if item >= ArenaBoyzGlobals.speed_start && item < ArenaBoyzGlobals.counter_start {
492 temp_stat = temp_stat + ArenaBoyzGlobals.equipment_bonus_per_level * UInt16(item - (ArenaBoyzGlobals.speed_start - 1))
493 }
494 }
495 effective_item_max = effective_item_max - 1
496 i = i + 1
497 }
498 return temp_stat
499 }
500
501 access(all) fun GetAttackAwardAmount(): UFix64 {
502 let health_pct: UFix64 = UFix64(self.hitpoints) / UFix64(self.max_hitpoints)
503 let base_value: UFix64 = 5.0
504 if health_pct >= 1.0 {
505 return base_value * 5.0
506 }
507 if health_pct < 1.0 && health_pct >= 0.75 {
508 return base_value * 4.0
509 }
510 if health_pct < 0.75 && health_pct >= 0.5 {
511 return base_value * 3.0
512 }
513 if health_pct < 0.5 && health_pct >= 0.25 {
514 return base_value * 2.0
515 }
516 return base_value
517 }
518
519 access(account) fun GenerateHero(hero_id: UInt32, starting_pod_id: UInt32) {
520 self.hero_id = hero_id
521 self.max_hitpoints = ArenaBoyzGlobals.hp_base
522 self.hitpoints = ArenaBoyzGlobals.hp_base
523 self.damage = ArenaBoyzGlobals.damage_base
524 self.defense = ArenaBoyzGlobals.defense_base
525 self.accuracy = ArenaBoyzGlobals.accuracy_base
526 self.speed = ArenaBoyzGlobals.speed_base
527 self.counter = ArenaBoyzGlobals.counter_base
528 self.current_pod_id = starting_pod_id
529 self.attack_award = UInt8(revertibleRandom<UInt32>() & 0xFF) % 5
530 self.items[0] = ArenaBoyzGlobals.starting_primary_pool[UInt8(revertibleRandom<UInt32>() & 0xFF) % 4]
531
532 // 10% bonus jitter applied below, for some variance
533 self.max_hitpoints = self.max_hitpoints + UInt16(revertibleRandom<UInt32>() & 0xFFFF) % 100
534 self.hitpoints = self.max_hitpoints
535 self.damage = self.damage + UInt16(revertibleRandom<UInt32>() & 0xFFFF) % 30
536 self.defense = self.defense + UInt16(revertibleRandom<UInt32>() & 0xFFFF) % 10
537 self.accuracy = self.accuracy + UInt16(revertibleRandom<UInt32>() & 0xFFFF) % 20
538 self.speed = self.speed + UInt16(revertibleRandom<UInt32>() & 0xFFFF) % 20
539 self.counter = self.counter + UInt16(revertibleRandom<UInt32>() & 0xFFFF) % 20
540 }
541
542 access(account) fun ProcessConsumableItems() {
543 var i: Int = 0
544 while i < self.items.length {
545 let item = self.items[i]
546 if item >= ArenaBoyzGlobals.throwable_start && item < ArenaBoyzGlobals.healing_start {
547 // self.counter = self.counter + ArenaBoyzGlobals.targetable_bonus_per_level * (1 + UInt16(item) - UInt16(ArenaBoyzGlobals.throwable_start))
548 self.items[i] = 0
549 } else if item >= ArenaBoyzGlobals.healing_start && item < ArenaBoyzGlobals.shield_start {
550 self.defense = self.defense + ArenaBoyzGlobals.targetable_bonus_per_level * (1 + UInt16(item) - UInt16(ArenaBoyzGlobals.healing_start))
551 self.items[i] = 0
552 } else if item >= ArenaBoyzGlobals.shield_start && item < ArenaBoyzGlobals.stim_start {
553 self.speed = self.speed + ArenaBoyzGlobals.targetable_bonus_per_level * (1 + UInt16(item) - UInt16(ArenaBoyzGlobals.shield_start))
554 self.items[i] = 0
555 } else if item >= ArenaBoyzGlobals.stim_start && item < ArenaBoyzGlobals.medal_start {
556 // self.speed = self.speed + ArenaBoyzGlobals.targetable_bonus_per_level * (1 + UInt16(item) - UInt16(ArenaBoyzGlobals.stim_start))
557 self.items[i] = 0
558 } else if item >= ArenaBoyzGlobals.medal_start && item < ArenaBoyzGlobals.damage_start {
559 self.counter = self.counter + ArenaBoyzGlobals.targetable_bonus_per_level * (1 + UInt16(item) - UInt16(ArenaBoyzGlobals.medal_start))
560 self.items[i] = 0
561 }
562 i = i + 1
563 }
564 self.RemoveInventoryGaps()
565 }
566
567 access(account) fun ProcessEndPod() {
568 var i: Int = 0
569 while i < self.items.length {
570 let item = self.items[i]
571 if item >= ArenaBoyzGlobals.medal_start && item < ArenaBoyzGlobals.damage_start {
572 self.max_hitpoints = self.max_hitpoints + ArenaBoyzGlobals.medal_max_hp_per_level * (1 + UInt16(item) - UInt16(ArenaBoyzGlobals.medal_start))
573 }
574 i = i + 1
575 }
576 if self.hitpoints > 0 {
577 self.max_hitpoints = self.max_hitpoints + ArenaBoyzGlobals.free_max_hp_per_level
578 self.hitpoints = self.max_hitpoints
579 }
580 }
581
582 access(account) fun RemoveInventoryGaps() {
583 var j: Int = 0 // Pointer for the next position of a non-zero element
584 var i: Int = 0
585 while i < self.items.length {
586 let item = self.items[i]
587 // When a non-zero element is found, swap it with the element at j
588 if item != 0 {
589 let temp: UInt8 = item
590 self.items[i] = self.items[j]
591 self.items[j] = temp
592 j = j + 1
593 }
594 i = i + 1
595 }
596 }
597
598 access(account) fun DiscardExcessInventory() {
599 var i: Int = 0
600 while i < self.items.length {
601 if i >= ArenaBoyzGlobals.effective_item_max {
602 self.items[i] = 0
603 }
604 i = i + 1
605 }
606 }
607
608 access(account) fun ProcessEndTurn() {
609 self.DiscardExcessInventory()
610 self.has_moved = false
611 self.stim = 0.0
612 }
613
614 access(self) fun SetTimestamp(timestamp: UFix64) {
615 self.move_timestamp = timestamp
616 }
617}
618
619
620 access(all) struct Attack {
621 access(all) var attacker: UInt32
622 access(all) var attack_target: UInt32
623 access(all) var action_target: UInt32
624 access(all) var scavenge_choice: UInt8
625 access(all) var selected_loot: UInt8
626 access(all) var equip_primary: Bool
627 access(all) var turn: UInt32
628
629 init(attacker: UInt32, attack_target: UInt32, action_target: UInt32, scavenge_choice: UInt8, equip_primary: Bool, selected_loot: UInt8, turn: UInt32) {
630 self.attacker = attacker
631 self.attack_target = attack_target
632 self.action_target = action_target
633 self.scavenge_choice = scavenge_choice
634 self.equip_primary = equip_primary
635 self.selected_loot = selected_loot
636 self.turn = turn
637 }
638
639 access(contract) fun setScavengeChoice(newScavengeChoice: UInt8) {
640 self.scavenge_choice = newScavengeChoice
641 }
642 }
643
644 access(all) struct AttackResult {
645 access(all) var attacker: UInt32
646 access(all) var target: UInt32
647 access(all) var damage: UInt32
648
649 init(attacker: UInt32, target: UInt32, damage: UInt32) {
650 self.attacker = attacker
651 self.target = target
652 self.damage = damage
653 }
654 }
655
656
657
658 //a player signs up to play the game here, pays the game fee
659 access(all) fun playerSignup(payment: @{FungibleToken.Vault}): @PlayerKey {
660 pre {
661 self.game_started == false: "game must not be running yet"
662 self.enrolled_players < 16: "game must have less than 16 players" // enforcing hard cap for the time being
663 payment.isInstance(Type<@FlowToken.Vault>()): "Payment must be a FlowToken Vault"
664
665 }
666 post {
667 self.enrolled_players == before(self.enrolled_players) + 1: "Player count should increase by 1"
668 }
669
670 assert(payment.balance == self.entry_fee, message: "insufficient funds")
671
672 // Deposit the payment into the recipient's Vault using the new approach
673 let flowPayment <- payment as! @FlowToken.Vault
674 self.payment_vault.deposit(from: <-flowPayment)
675
676 // Update internal game state
677 self.enrolled_players = self.enrolled_players + 1
678 self.current_player_id = self.current_player_id + 1
679 self.current_key_id = self.current_key_id + 1
680
681 self.signup_keys[UInt32(self.current_player_id)] = self.current_key_id
682
683 log(self.signup_keys[UInt32(self.current_player_id)])
684
685 return <-create PlayerKey(player_id: UInt32(self.current_player_id), game_id: self.game_id, key_id: self.current_key_id)
686 }
687
688 access(all) fun updatePlayerKey(signup_key: @PlayerKey, payment: @{FungibleToken.Vault}): @PlayerKey {
689 pre {
690 self.game_started == false: "game must not be running yet"
691 self.enrolled_players < 16: "game must have less than 16 players" // enforcing hard cap for the time being
692 payment.isInstance(Type<@FlowToken.Vault>()): "Payment must be a FlowToken Vault"
693 }
694 post {
695 self.enrolled_players == before(self.enrolled_players) + 1: "Player count should increase by 1"
696 }
697
698 assert(payment.balance == self.entry_fee, message: "insufficient funds")
699
700 var i: Int = 0
701 while i < Int(self.current_player_id + 1) {
702 if self.signup_keys[UInt32(i)] == signup_key.key_id {
703 panic("player has already signed up")
704 }
705 i = i + 1
706 }
707 let flowPayment <- payment as! @FlowToken.Vault
708 self.payment_vault.deposit(from: <-flowPayment)
709
710 signup_key.setGameID(game_id: self.game_id)
711 self.current_player_id = self.current_player_id + 1
712 signup_key.setPlayerID(player_id: UInt32(self.current_player_id))
713
714 self.signup_keys[UInt32(self.current_player_id)] = signup_key.key_id
715 log(self.signup_keys[UInt32(self.current_player_id)])
716
717 self.enrolled_players = self.enrolled_players + 1
718
719 return <-signup_key
720 }
721
722 access(self) fun shuffle(array: [UInt32]) : [UInt32] {
723 let n = array.length
724 var shuffledArray = array
725
726 var i: Int = 0
727 while i < n {
728 let j = i + (Int(revertibleRandom<UInt32>()) % (n - i))
729 let temp = shuffledArray[i]
730 shuffledArray[i] = shuffledArray[j]
731 shuffledArray[j] = temp
732 i = i + 1
733 }
734 return shuffledArray
735 }
736
737 access(self) fun getPodSizes(total_players: Int32): [Int] { //pod balancing aims to create specific balacing to ensure fairness
738 var podSizes: [Int] = []
739 var totalPlayers: Int = Int(total_players)
740 if totalPlayers <= 8 {
741 podSizes = [totalPlayers]
742 }
743 else if totalPlayers <= 16 {
744 let playersInFirstPod = (totalPlayers + 1) / 2
745 let playersInSecondPod = totalPlayers - playersInFirstPod
746 podSizes = [playersInFirstPod, playersInSecondPod]
747 }
748 else if totalPlayers <= 24 {
749 let playersInFirstPod = (totalPlayers + 2) / 3
750 let playersInSecondPod = (totalPlayers + 1) / 3
751 let playersInThirdPod = totalPlayers - playersInFirstPod - playersInSecondPod
752 podSizes = [playersInFirstPod, playersInSecondPod, playersInThirdPod]
753 }
754 else {
755 while totalPlayers > 24 {
756 podSizes.append(8)
757 totalPlayers = totalPlayers - 8
758 }
759 if totalPlayers <= 8 {
760 podSizes.append(totalPlayers)
761 }
762 else if totalPlayers <= 16 {
763 let playersInFirstPod = (totalPlayers + 1) / 2
764 let playersInSecondPod = totalPlayers - playersInFirstPod
765 podSizes.append(playersInFirstPod)
766 podSizes.append(playersInSecondPod)
767 }
768 else {
769 let playersInFirstPod = (totalPlayers + 2) / 3
770 let playersInSecondPod = (totalPlayers + 1) / 3
771 let playersInThirdPod = totalPlayers - playersInFirstPod - playersInSecondPod
772 podSizes.append(playersInFirstPod)
773 podSizes.append(playersInSecondPod)
774 podSizes.append(playersInThirdPod)
775 }
776 }
777 var maxPodSize = 0
778 var i = 0
779 while i < podSizes.length {
780 if podSizes[i] > maxPodSize {
781 maxPodSize = podSizes[i]
782 }
783 i = i + 1
784 }
785 self.pod_size = UInt32(maxPodSize)
786 return podSizes
787 }
788
789 access(self) fun initializeHeroesAndPods(podSizes : [Int]) {
790 pre {
791 self.game_started == false : "game must not be started already"
792 }
793 if(self.enrolled_players < Int32(self.pod_size)) { //When we go back to pods, this will come back into play
794 self.pod_size = UInt32(self.enrolled_players)
795 }
796 var hero_ids: [UInt32] = []
797 var id : UInt32 = 0
798 while id < UInt32(self.enrolled_players) {
799 hero_ids.append(id)
800 self.heroes.append(Hero())
801 self.heroes[id].GenerateHero(hero_id: id, starting_pod_id: 0)
802 if let signupKey = self.signup_keys[id] {
803 self.heroes[id].setKeyId(newKeyId: signupKey)
804 log(signupKey)
805 } else {
806 panic("Signup key for hero ID is nil")
807 }
808 id = id + 1
809 }
810 var podIndex = 0
811 while podIndex < podSizes.length {
812 self.pods.append(Pod())
813 log("appended pod")
814 podIndex = podIndex + 1
815 }
816 hero_ids = self.shuffle(array: hero_ids)
817 podIndex = 0
818 var currentHeroIndex: Int = 0
819 podIndex = 0
820 while podIndex < podSizes.length {
821 let podSize = podSizes[podIndex]
822 var podHeroCount = 0
823 while podHeroCount < podSize && currentHeroIndex < hero_ids.length {
824 let heroID = hero_ids[currentHeroIndex]
825 self.pods[podIndex].hero_ids.append(heroID)
826 self.heroes[heroID].setCurrentPodId(newCurrentPodId: UInt32(podIndex))
827 log("appending hero")
828 log(heroID)
829 log(UInt32(podIndex))
830 currentHeroIndex = currentHeroIndex + 1
831 podHeroCount = podHeroCount + 1
832 }
833
834 podIndex = podIndex + 1
835 }
836
837
838
839 var largest_pod: Int = 0
840 var podIndex2: Int = 0
841
842 while podIndex2 < self.pods.length {
843 let pod = self.pods[podIndex2]
844
845 var localHeroes: [Hero] = []
846 var heroIdIndex: Int = 0
847
848 while heroIdIndex < pod.hero_ids.length {
849 let hero_id = pod.hero_ids[heroIdIndex]
850 localHeroes.append(self.heroes[hero_id])
851 heroIdIndex = heroIdIndex + 1
852 }
853
854 pod.SortPlayers(Players: localHeroes)
855
856 var i: Int = 0
857 while i < Int(pod.hero_ids.length) {
858 let podHeroId: UInt32 = pod.hero_ids[i]
859 self.heroes[podHeroId].setMoveTimestamp(newMoveTimestamp : getCurrentBlock().timestamp + UFix64(i * self.seconds_per_turn))
860 i = i + 1
861 }
862
863 if(i > largest_pod) {
864 largest_pod = i
865 }
866
867 podIndex2 = podIndex2 + 1
868 }
869 log("largest pod")
870 log(largest_pod)
871 self.turn_end_timestamp = getCurrentBlock().timestamp + UFix64(self.seconds_per_turn * largest_pod)
872 self.game_started = true
873 log(self.turn)
874 }
875
876 access(self) fun resetTurn(podSizes : [Int]) {
877 pre {
878 self.game_started == true : "game must be running"
879 }
880 self.turn = self.turn + 1
881 if (self.turn % ArenaBoyzGlobals.turns_per_circle == 0) {
882 self.circle = self.circle + 1
883 var aliveHeroIDs: [UInt32] = []
884 var heroIndex = 0
885
886 // Collect all alive heroes
887 while heroIndex < self.heroes.length {
888 let hero = self.heroes[heroIndex]
889 if (hero.hitpoints > 0) {
890 aliveHeroIDs.append(hero.hero_id)
891 }
892 heroIndex = heroIndex + 1
893 }
894
895 while (self.pods.length > 0) {
896 self.pods.removeLast()
897 }
898
899 var podIndex = 0
900 while podIndex < podSizes.length {
901 self.pods.append(Pod())
902 podIndex = podIndex + 1
903 }
904
905 var shuffledHeroIDs: [UInt32] = self.shuffle(array: aliveHeroIDs)
906
907 var currentHeroIndex: Int = 0
908 podIndex = 0
909 while podIndex < podSizes.length {
910 let podSize = podSizes[podIndex]
911
912 var podHeroCount = 0
913 while podHeroCount < podSize && currentHeroIndex < shuffledHeroIDs.length {
914 let heroID = shuffledHeroIDs[currentHeroIndex]
915 self.pods[podIndex].hero_ids.append(heroID)
916 self.heroes[heroID].setCurrentPodId(newCurrentPodId : UInt32(podIndex))
917 currentHeroIndex = currentHeroIndex + 1
918 podHeroCount = podHeroCount + 1
919 }
920
921 podIndex = podIndex + 1
922 }
923 }
924 var pod_i: Int = 0
925
926 while pod_i < self.pods.length {
927 let pod = self.pods[pod_i]
928
929 if(self.turn % ArenaBoyzGlobals.turns_per_circle == 0) {
930 self.pods[pod_i].ResetScavengePool(Circle: self.circle)
931 }
932
933 var heroIdIndex: Int = 0
934
935 if(self.turn / ArenaBoyzGlobals.turns_per_circle < 7) { // end of game, final circle!
936 while heroIdIndex < pod.hero_ids.length {
937 let heroId = pod.hero_ids[heroIdIndex]
938 self.heroes[heroId].ProcessEndTurn()
939 if(self.turn % ArenaBoyzGlobals.turns_per_circle == 0) {
940 self.heroes[heroId].ProcessEndPod()
941 }
942 heroIdIndex = heroIdIndex + 1
943 }
944 } else {
945 while heroIdIndex < pod.hero_ids.length {
946 let heroId = pod.hero_ids[heroIdIndex]
947 self.heroes[heroId].ProcessEndTurn()
948 if(self.turn % ArenaBoyzGlobals.turns_per_circle == 0) {
949 self.heroes[heroId].ProcessEndPod()
950 }
951 heroIdIndex = heroIdIndex + 1
952 }
953 }
954
955 // Shorten turn length when a player is killed, but do this only one time per player
956 var pod_survivors: Int = 0
957 var live_i: Int = 0
958 while live_i < pod.hero_ids.length {
959 let heroId = pod.hero_ids[live_i]
960 if self.heroes[heroId].hitpoints > 0 {
961 pod_survivors = pod_survivors + 1
962 }
963 live_i = live_i + 1
964 }
965
966 pod_i = pod_i + 1
967 }
968 // Sort players within each pod and set move timestamps
969 var largest_pod: Int = 0
970 var podIndex3: Int = 0
971
972 while podIndex3 < self.pods.length {
973 let pod = self.pods[podIndex3]
974
975 // Create a local array of heroes for this pod
976 var localHeroes: [Hero] = []
977 var heroIdIndex: Int = 0
978
979 while heroIdIndex < pod.hero_ids.length {
980 let hero_id = pod.hero_ids[heroIdIndex]
981 localHeroes.append(self.heroes[hero_id])
982 heroIdIndex = heroIdIndex + 1
983 }
984
985 // Sort the local array of heroes
986 pod.SortPlayers(Players: localHeroes)
987
988 var i: Int = 0
989 while i < Int(pod.hero_ids.length) {
990 let podHeroId: UInt32 = pod.hero_ids[i]
991 self.heroes[podHeroId].setMoveTimestamp(newMoveTimestamp: getCurrentBlock().timestamp + UFix64(i * self.seconds_per_turn))
992 i = i + 1
993 }
994
995 if(i > largest_pod) {
996 largest_pod = i
997 }
998
999 podIndex3 = podIndex3 + 1
1000 }
1001
1002 self.setTurnEndTimestamp(newTurnEndTimestamp: getCurrentBlock().timestamp + UFix64(self.seconds_per_turn * largest_pod))
1003 log(self.turn)
1004 }
1005 //TODO: reshuffle pods on new circle. circle should be incremented, and players should be randomly shuffled into new pods.
1006
1007 //Player commits to an attack here
1008 access(all) fun submitAttack(attacker_key: @PlayerKey, attack_target : UInt32, action_target : UInt32, scavenge_choice : UInt8, equip_primary : Bool): @PlayerKey {
1009 pre {
1010
1011 //TODO: speed management goes here. players should be prohibited from entering attacks before it is their turn to do so
1012 //see self.tick and self.turn vs. their speed. it should also be recorded if they have moved already this turn.
1013
1014 attacker_key.game_id == self.game_id : "attacker must be from this game"
1015 self.heroes[attacker_key.player_id].hitpoints > 0 : "attacker must be alive!"
1016 self.heroes[attack_target].hitpoints > 0 : "attack target must be alive!"
1017 self.heroes[action_target].hitpoints > 0 : "action target must be alive!"
1018 self.heroes[attacker_key.player_id].has_moved == false : "hero has already attacked!"
1019 self.heroes[attacker_key.player_id].move_timestamp <= getCurrentBlock().timestamp : "attacker is not allowed to enter an attack yet!"
1020 self.heroes[attacker_key.player_id].current_pod_id == self.heroes[attack_target].current_pod_id : "attack target is not in the same pod!"
1021 self.heroes[attacker_key.player_id].current_pod_id == self.heroes[action_target].current_pod_id : "action target is not in the same pod!"
1022 //TODO: Can we put in some constants or globals for design-related items in code
1023 }
1024
1025 self.heroes[attacker_key.player_id].setKeyId(newKeyId : attacker_key.key_id) //this is an okay place to do this, but a player who never attacks will never set their key_id
1026 var a_remove: Int = 0
1027 while a_remove < self.attacks.length {
1028 let attack = self.attacks[a_remove]
1029
1030 if(attack.attacker == attacker_key.player_id) { // if the player has submitted an attack already, remove it
1031 self.attacks.remove(at: a_remove)
1032 break
1033 }
1034
1035 a_remove = a_remove + 1
1036 }
1037
1038 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 ))
1039 //TODO: we can change contract state here, such as setting a block height
1040 return <-attacker_key
1041 }
1042 access(all) fun dropItem(dropper_key: @PlayerKey, item_index : Int): @PlayerKey {
1043 pre {
1044 dropper_key.game_id == self.game_id : "dropper must be from this game"
1045 }
1046 let dropped_item: Int = Int(self.heroes[dropper_key.player_id].items[item_index])
1047 self.heroes[dropper_key.player_id].items[item_index] = 0
1048 self.pods[self.heroes[dropper_key.player_id].current_pod_id].DropItemIntoPool(Item: UInt8(dropped_item))
1049 return <-dropper_key
1050 }
1051 access(all) fun equipItem(equipper_key: @PlayerKey, item_index : Int): @PlayerKey {
1052 pre{
1053 equipper_key.game_id == self.game_id : "dropper must be from this game"
1054 self.heroes[equipper_key.player_id].items[item_index] < ArenaBoyzGlobals.targetables_start : "has to pick a primary weapon to equip primary!"
1055 }
1056 let old_primary: Int = Int(self.heroes[equipper_key.player_id].items[0])
1057 self.heroes[equipper_key.player_id].items[0] = self.heroes[equipper_key.player_id].items[item_index]
1058 self.heroes[equipper_key.player_id].items[item_index] = UInt8(old_primary)
1059
1060 return <-equipper_key
1061 }
1062 //at the time handleDeath is called, all damage has already been applied to both attack target and attacker due to counter. so either
1063 //both players are already dead, or just the attacker is.
1064 access(self) fun handleDeath(dead_player: UInt32): Bool {
1065 log("handling death")
1066 var alive_heroes: Int = 0
1067 var winner_id: Int32 = -1
1068 var runner_up_id: Int32 = -1
1069 var second_runner_up_id: Int32 = -1
1070 var game_over: Bool = false
1071
1072 var heroIndex: Int = 0
1073 var dead_player_key: Int32 = -1
1074 for hero in self.heroes {
1075 if(hero.hero_id == dead_player) {
1076 dead_player_key = Int32(hero.key_id)
1077 }
1078 }
1079
1080 while heroIndex < self.heroes.length {
1081 let hero = self.heroes[heroIndex]
1082
1083 if(hero.hitpoints > 0) {
1084 alive_heroes = alive_heroes + 1
1085 winner_id = Int32(hero.key_id)
1086 log(Int32(hero.key_id))
1087 }
1088
1089 heroIndex = heroIndex + 1
1090 }
1091
1092 if(alive_heroes == 1) {
1093 self.runner_up_id = dead_player_key
1094
1095 emit GameResultEvent(game_id: self.game_id, victor_id: winner_id, ending_turn: self.turn, enrolled_players: self.enrolled_players)
1096 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, ending_turn: self.turn, enrolled_players: self.enrolled_players)
1097 game_over = true
1098 }
1099 else if(alive_heroes == 2) {
1100
1101 self.second_runner_up_id = dead_player_key
1102 }
1103 if(game_over) {
1104 //self.resetGame()
1105 return true
1106 }
1107 return false
1108 }
1109 access(self) fun resolveAttacks() {
1110 var validAttacks: [Attack] = [] //new code to remove attacks that have not been flagged
1111
1112 for attack in self.attacks {
1113 if attack.turn == self.turn {
1114 validAttacks.append(attack)
1115 }
1116 }
1117 self.attacks = validAttacks
1118
1119 var conflicts: {UInt8: ScavengeConflict} = {}
1120 var conflict_i = 0
1121 for attack in self.attacks {
1122 let item_id = attack.scavenge_choice
1123 let hero_id = attack.attacker
1124
1125 if conflicts[item_id] != nil {
1126 conflicts[item_id]?.addConflict(hero_id: hero_id, attack_index: conflict_i)
1127 } else {
1128 conflicts[item_id] = ScavengeConflict( hero_ids: [hero_id], attack_indicies: [conflict_i])
1129 }
1130 conflict_i = conflict_i + 1
1131 }
1132
1133 for conflict in conflicts.values {
1134 if conflict.hero_ids.length > 1 {
1135 // More than one hero is trying to scavenge the same item, resolve conflict
1136 let winnerIndex = Int(revertibleRandom<UInt32>())%conflict.hero_ids.length
1137 var attack_index_i = 0
1138 while attack_index_i < conflict.attack_indicies.length {
1139 let attack_index = conflict.attack_indicies[attack_index_i]
1140
1141 if attack_index_i != winnerIndex {
1142 // Losers get consolation item
1143 self.attacks[attack_index].setScavengeChoice(newScavengeChoice: 0)
1144 }
1145 // The winner keeps their original choice, no action needed
1146
1147 attack_index_i = attack_index_i + 1
1148 }
1149 }
1150 // If only one hero is involved, no conflict to resolve
1151 }
1152 var resetGame = false
1153 for attack in self.attacks {
1154 self.heroes[attack.attacker].setHasMoved(newHasMoved: true)
1155 self.heroes[attack.attacker].setShields(newShields: 0)
1156 var attack_pod: UInt32 = self.heroes[attack.attacker].current_pod_id
1157 var attacker_hp: UInt16 = self.heroes[attack.attacker].hitpoints
1158 var target_hp: UInt16 = self.heroes[attack.attack_target].hitpoints
1159 var action_target_hp: UInt16 = 0
1160 var attacker_hp_max: UInt16 = self.heroes[attack.attacker].max_hitpoints
1161 var target_hp_max: UInt16 = self.heroes[attack.attack_target].max_hitpoints
1162 var action_target_hp_max: UInt16 = 0
1163 var stimBoost: UFix64 = 1.0
1164 var scavenged_item_id: Int = 0
1165 var actionAmount: UFix64 = 0.0
1166
1167 //item and looting logic
1168 if(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] >= ArenaBoyzGlobals.throwable_start &&
1169 self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] < ArenaBoyzGlobals.healing_start) { //throwing weapon
1170 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)))
1171 var originalThrownDamage: UInt16 = thrownDamage
1172 action_target_hp = self.heroes[attack.action_target].hitpoints
1173 action_target_hp_max = self.heroes[attack.action_target].max_hitpoints
1174 if(self.heroes[attack.action_target].shields > thrownDamage) {
1175 thrownDamage = 0
1176 }
1177 else {
1178 thrownDamage = thrownDamage - self.heroes[attack.action_target].shields
1179 }
1180 if(originalThrownDamage > self.heroes[attack.action_target].shields ) {
1181 self.heroes[attack.action_target].setShields(newShields: 0)
1182 }
1183 else {
1184 self.heroes[attack.action_target].setShields(newShields: self.heroes[attack.action_target].shields - originalThrownDamage)
1185 }
1186 thrownDamage = self.CalculateDamage(baseDamage : thrownDamage, defense: self.heroes[attack.action_target].GetDefense());
1187 actionAmount = UFix64(thrownDamage)
1188 if(thrownDamage > self.heroes[attack.action_target].hitpoints) {
1189 self.heroes[attack.action_target].setHitpoints(newHitpoints: 0)
1190 }
1191 else {
1192 self.heroes[attack.action_target].setHitpoints(newHitpoints: self.heroes[attack.action_target].hitpoints - thrownDamage)
1193 }
1194 }
1195 if(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] >= ArenaBoyzGlobals.healing_start &&
1196 self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] < ArenaBoyzGlobals.shield_start) {
1197
1198 action_target_hp = self.heroes[attack.action_target].hitpoints
1199 action_target_hp_max = self.heroes[attack.action_target].max_hitpoints
1200 var hitpointsBefore : UInt16 = self.heroes[attack.action_target].hitpoints
1201 self.heroes[attack.action_target].setHitpoints(newHitpoints: 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))))
1202 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))))
1203 if(self.heroes[attack.action_target].hitpoints > self.heroes[attack.action_target].max_hitpoints) {
1204 actionAmount = UFix64(self.heroes[attack.action_target].max_hitpoints - hitpointsBefore)
1205 self.heroes[attack.action_target].setHitpoints(newHitpoints: self.heroes[attack.action_target].max_hitpoints)
1206
1207 }
1208
1209 }
1210 if(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] >= ArenaBoyzGlobals.shield_start &&
1211 self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] < ArenaBoyzGlobals.stim_start) {
1212 action_target_hp = self.heroes[attack.action_target].hitpoints
1213 action_target_hp_max = self.heroes[attack.action_target].max_hitpoints
1214 self.heroes[attack.action_target].setShields(newShields: 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))))
1215 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))))
1216 }
1217 if(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] >= ArenaBoyzGlobals.stim_start &&
1218 self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] < ArenaBoyzGlobals.medal_start) {
1219 action_target_hp = self.heroes[attack.action_target].hitpoints
1220 action_target_hp_max = self.heroes[attack.action_target].max_hitpoints
1221 self.heroes[attack.action_target].setStim(newStim : 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)))
1222 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)))
1223 }
1224 if(self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] >= ArenaBoyzGlobals.medal_start &&
1225 self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] < ArenaBoyzGlobals.damage_start) {
1226 action_target_hp = self.heroes[attack.action_target].hitpoints
1227 action_target_hp_max = self.heroes[attack.action_target].max_hitpoints
1228 self.heroes[attack.action_target].setMaxHitpoints(newMaxHitpoints : 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)))
1229 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)))
1230 }
1231 stimBoost = stimBoost + self.heroes[attack.attacker].stim
1232 self.heroes[attack.attacker].setStim(newStim : 0.0)
1233 var scavenging_iterator: Int = 0
1234 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) {
1235 var index = 0
1236 while index < self.heroes[attack.attacker].items.length {
1237 let item = self.heroes[attack.attacker].items[index]
1238 if(item == 0) { // Stop if they go over max inventory of 12, don't let them loot in this case.
1239 self.heroes[attack.attacker].items[index] = self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice]
1240 scavenged_item_id = index
1241 break
1242 }
1243 index = index + 1
1244 scavenging_iterator = scavenging_iterator + 1
1245 }
1246 }
1247
1248 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
1249 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
1250 if(item == 0) { //TODO - stop if they go over max inventory of 12, dont let them loot in this case.
1251 scavenged_item_id = scavenging_iterator
1252 break;
1253 }
1254 scavenging_iterator = scavenging_iterator + 1
1255 }
1256 var oldPrimary: UInt8 = self.heroes[attack.attacker].items[0]
1257 self.heroes[attack.attacker].items[0] = self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice]
1258
1259 if(oldPrimary != 0) { //fixes a bug if player is picking up a primary when they have no items
1260 self.heroes[attack.attacker].items[scavenged_item_id] = oldPrimary;
1261 }
1262 }
1263 //"tighten up" scavenge pool, closing in on the taken item
1264 /* var currentIndex: Int = Int(attack.scavenge_choice);
1265 while ( currentIndex < self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool.length - 1) {
1266 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]
1267 currentIndex = currentIndex + 1
1268 }*/
1269 self.heroes[attack.attacker].ProcessConsumableItems();
1270 self.pods[self.heroes[attack.attacker].current_pod_id].scavenge_pool[attack.scavenge_choice] = 0
1271
1272 //Attack damage is calculated below
1273 var isCritical: Bool = (UInt16(revertibleRandom<UInt32>() & 0xFFFF)%1000 < self.heroes[attack.attacker].GetAccuracy())
1274 var isCounterCritical: Bool = (UInt16(revertibleRandom<UInt32>() & 0xFFFF)%1000 < self.heroes[attack.attack_target].GetAccuracy())
1275 var counterAttack: Bool = (UInt16(revertibleRandom<UInt32>() & 0xFFFF)%1000 < self.heroes[attack.attack_target].GetCounter())// - shieldMod
1276 var damageDealt: UInt16 = self.heroes[attack.attacker].GetDamage()
1277
1278 var originalDamageDealt: UInt16 = damageDealt
1279 if(self.heroes[attack.attack_target].shields > damageDealt) {
1280 damageDealt = 0
1281 }
1282 else {
1283 damageDealt = damageDealt - self.heroes[attack.attack_target].shields
1284 }
1285 if(originalDamageDealt > self.heroes[attack.attack_target].shields ) {
1286 self.heroes[attack.attack_target].setShields(newShields : 0)
1287 }
1288 else {
1289 self.heroes[attack.attack_target].setShields(newShields :self.heroes[attack.attack_target].shields - originalDamageDealt)
1290 }
1291 damageDealt = self.CalculateDamage(baseDamage: damageDealt, defense: self.heroes[attack.attack_target].GetDefense())
1292
1293 if (isCritical)
1294 {
1295 damageDealt = damageDealt * 2
1296 }
1297
1298 // Process the counterattack
1299 var counterDamage: UInt16 = 0
1300 if (counterAttack)
1301 {
1302 counterDamage = self.CalculateDamage(baseDamage : self.heroes[attack.attack_target].GetDamage(), defense: self.heroes[attack.attacker].GetDefense());
1303
1304 var originalCounterDamageDealt: UInt16 = counterDamage
1305
1306 if(self.heroes[attack.attacker].shields > counterDamage) {
1307 counterDamage = 0
1308 }
1309 else {
1310 counterDamage = counterDamage - self.heroes[attack.attacker].shields
1311 }
1312
1313 if(originalCounterDamageDealt > self.heroes[attack.attacker].shields ) {
1314 self.heroes[attack.attacker].setShields(newShields : 0)
1315 }
1316 else {
1317 self.heroes[attack.attacker].setShields(newShields : self.heroes[attack.attacker].shields - originalCounterDamageDealt)
1318 }
1319 if (isCounterCritical)
1320 {
1321 counterDamage = counterDamage * 2
1322 }
1323 }
1324 // access(all) event AttackResult(attacker: UInt32, target: UInt32, damage: UInt32, critical: Bool, counter_damage: UInt32, action_target: UInt32, action_damage: UInt32, action_item: UInt8 )
1325
1326 //get attack awards before damage is dealt, so we get damage scaling
1327 if(self.heroes[attack.attack_target].attack_award == 0) {
1328 self.heroes[attack.attacker].setDamage(newDamage: self.heroes[attack.attacker].damage + UInt16(self.heroes[attack.attack_target].GetAttackAwardAmount() * stimBoost))
1329 }
1330 if(self.heroes[attack.attack_target].attack_award == 1) {
1331 self.heroes[attack.attacker].setDefense(newDefense: self.heroes[attack.attacker].defense + UInt16(self.heroes[attack.attack_target].GetAttackAwardAmount() * stimBoost))
1332 }
1333 if(self.heroes[attack.attack_target].attack_award == 2) {
1334 self.heroes[attack.attacker].setAccuracy(newAccuracy: self.heroes[attack.attacker].accuracy + UInt16(self.heroes[attack.attack_target].GetAttackAwardAmount() * stimBoost))
1335 }
1336 if(self.heroes[attack.attack_target].attack_award == 3) {
1337 self.heroes[attack.attacker].setSpeed(newSpeed: self.heroes[attack.attacker].speed + UInt16(self.heroes[attack.attack_target].GetAttackAwardAmount() * stimBoost))
1338 }
1339 if(self.heroes[attack.attack_target].attack_award == 4) {
1340 self.heroes[attack.attacker].setCounter(newCounter: self.heroes[attack.attacker].counter + UInt16(self.heroes[attack.attack_target].GetAttackAwardAmount() * stimBoost))
1341 }
1342 if(self.heroes[attack.attack_target].hitpoints < damageDealt) {
1343 self.heroes[attack.attack_target].setHitpoints(newHitpoints: 0)
1344 }
1345 else {
1346 self.heroes[attack.attack_target].setHitpoints(newHitpoints: self.heroes[attack.attack_target].hitpoints - damageDealt)
1347 }
1348 var game_over : Bool = false
1349 var event_emitted : Bool = false
1350 if(self.heroes[attack.attack_target].hitpoints <= 0) {
1351 counterDamage = 0
1352 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 )
1353 event_emitted = true
1354 self.heroes[attack.attack_target].setCurrentPodId(newCurrentPodId: 4294967295);
1355 var target_item_iterator: Int = 0
1356 while target_item_iterator < self.heroes[attack.attack_target].items.length {
1357 let target_item = self.heroes[attack.attack_target].items[target_item_iterator]
1358
1359 var attacker_item_iterator: Int = 0
1360 while attacker_item_iterator < self.heroes[attack.attacker].items.length { // process looting
1361 let attacker_item = self.heroes[attack.attacker].items[attacker_item_iterator]
1362
1363 if(attacker_item == 0) {
1364 self.heroes[attack.attacker].setItem(newItemIndex: attacker_item_iterator, newItemID: target_item)
1365 self.heroes[attack.attack_target].setItem(newItemIndex: target_item_iterator, newItemID: 0)
1366 break // exit the inner loop after setting the item
1367 }
1368
1369 attacker_item_iterator = attacker_item_iterator + 1
1370 }
1371
1372 target_item_iterator = target_item_iterator + 1
1373 }
1374 if(self.handleDeath(dead_player: attack.attack_target)) {
1375 game_over = true
1376 resetGame = true
1377 break
1378 }
1379 }
1380 if(!game_over && self.heroes[attack.action_target].hitpoints <= 0) {
1381 counterDamage = 0;
1382 if(!event_emitted) {
1383 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 )
1384 event_emitted = true
1385 }
1386 self.heroes[attack.action_target].setCurrentPodId(newCurrentPodId: 4294967295)
1387 var action_target_item_iterator: Int = 0
1388 while action_target_item_iterator < self.heroes[attack.action_target].items.length {
1389 let action_target_item = self.heroes[attack.action_target].items[action_target_item_iterator]
1390
1391 var attacker_item_iterator: Int = 0
1392 while attacker_item_iterator < self.heroes[attack.attacker].items.length { // process looting
1393 let attacker_item = self.heroes[attack.attacker].items[attacker_item_iterator]
1394
1395 if(attacker_item == 0) {
1396 self.heroes[attack.attacker].setItem(newItemIndex: attacker_item_iterator, newItemID: action_target_item)
1397 self.heroes[attack.action_target].setItem(newItemIndex: action_target_item_iterator, newItemID: 0)
1398 break // Exit the inner loop after successfully setting the item
1399 }
1400
1401 attacker_item_iterator = attacker_item_iterator + 1
1402 }
1403
1404 action_target_item_iterator = action_target_item_iterator + 1
1405 }
1406 if(self.handleDeath(dead_player : attack.action_target)) {
1407 resetGame = true
1408 game_over = true
1409 break
1410 }
1411 }
1412 if(self.heroes[attack.attacker].hitpoints < counterDamage) {
1413 self.heroes[attack.attacker].setHitpoints(newHitpoints: 0)
1414 }
1415 else {
1416 self.heroes[attack.attacker].setHitpoints(newHitpoints: self.heroes[attack.attacker].hitpoints - counterDamage)
1417 }
1418
1419
1420 if(!event_emitted) {
1421 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 )
1422 event_emitted = true
1423 }
1424 if(!game_over && self.heroes[attack.attacker].hitpoints <= 0) {
1425 self.heroes[attack.attacker].setCurrentPodId(newCurrentPodId: 4294967295)
1426 var counter_item_iterator: Int = 0
1427 while counter_item_iterator < self.heroes[attack.attacker].items.length {
1428 let counter_target_item = self.heroes[attack.attacker].items[counter_item_iterator]
1429
1430 var attacker_item_iterator: Int = 0
1431 while attacker_item_iterator < self.heroes[attack.attack_target].items.length { // process looting
1432 let attacker_item = self.heroes[attack.attack_target].items[attacker_item_iterator]
1433
1434 if(attacker_item == 0) {
1435 self.heroes[attack.attack_target].setItem(newItemIndex: attacker_item_iterator, newItemID: counter_target_item)
1436 self.heroes[attack.attacker].setItem(newItemIndex: counter_item_iterator, newItemID: 0)
1437 break // Exit the inner loop after successfully setting the item
1438 }
1439
1440 attacker_item_iterator = attacker_item_iterator + 1
1441 }
1442
1443 counter_item_iterator = counter_item_iterator + 1
1444 }
1445 if(self.handleDeath(dead_player : attack.attacker)) {
1446 resetGame = true
1447 }
1448
1449 }
1450
1451 }
1452 if(resetGame) {
1453 self.resetGame()
1454 }
1455 while(self.attacks.length > 0){
1456 self.attacks.removeLast()
1457 }
1458 }
1459 access(self) fun resetGame() {
1460 while(self.heroes.length > 0) {
1461 self.heroes.removeLast()
1462 }
1463 while(self.attacks.length > 0){
1464 self.attacks.removeLast()
1465 }
1466 while(self.pods.length > 0){
1467 self.pods.removeLast()
1468 }
1469 self.signup_keys = {}
1470 self.game_id = self.game_id + 1
1471 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
1472 self.current_player_id = -1
1473 self.seconds_per_turn = ArenaBoyzGlobals.seconds_per_turn
1474 self.turn = 0
1475 self.turn_end_timestamp = 0.0
1476 self.circle = 0
1477 self.pod_size = ArenaBoyzGlobals.heroes_per_pod
1478 self.game_started = false
1479 }
1480
1481 access(self) fun setEntryFee(entry_fee : UFix64) {
1482 self.entry_fee = entry_fee
1483 }
1484 access(all) fun CalculateDamage(baseDamage : UInt16, defense : UInt16) : UInt16
1485 {
1486 var damage : UInt16 = 0
1487 if(defense > baseDamage) {
1488 damage = 0
1489 }
1490 else {
1491 damage = baseDamage - defense
1492 }
1493 if(damage < baseDamage/ArenaBoyzGlobals.minimum_damage_divisor) {
1494 damage = baseDamage/ArenaBoyzGlobals.minimum_damage_divisor //min damage is 20% of your base
1495 }
1496 return damage
1497 }
1498 access(all) resource interface PlayerKeyInterface {
1499 access(all) fun getPlayerID(): UInt32
1500 access(all) fun getKeyID(): Int
1501 }
1502 access(all) resource PlayerKey: PlayerKeyInterface {
1503 access(contract) var player_id: UInt32
1504 access(contract) var game_id: UInt32
1505 access(contract) var key_id: Int
1506
1507 init(
1508 player_id: UInt32,
1509 game_id: UInt32,
1510 key_id: Int
1511 ) {
1512 self.player_id = player_id
1513 self.game_id = game_id
1514 self.key_id = key_id
1515 }
1516 access(all) fun getPlayerID() : UInt32 {
1517 return self.player_id
1518 }
1519 access(all) fun getKeyID() : Int {
1520 return self.key_id
1521 }
1522 access(contract) fun setGameID(game_id : UInt32) {
1523 self.game_id = game_id
1524 log(self.game_id)
1525 }
1526 access(contract) fun setPlayerID(player_id : UInt32) {
1527 self.player_id = player_id
1528 }
1529
1530 }
1531
1532 access(all) resource Admin {
1533
1534 init() {}
1535 access(all) fun initializeHeroesAndPods() {
1536 ABRoyaleGame.initializeHeroesAndPods(podSizes: ABRoyaleGame.getPodSizes(total_players: ABRoyaleGame.enrolled_players))
1537 }
1538 access(all) fun resolveAttacks() {
1539 ABRoyaleGame.resolveAttacks()
1540 }
1541 access(all) fun resetTurn() {
1542
1543 ABRoyaleGame.resetTurn(podSizes: ABRoyaleGame.getPodSizes(total_players: ABRoyaleGame.enrolled_players))
1544 }
1545
1546 access(all) fun resetGame() {
1547 ABRoyaleGame.resetGame()
1548 }
1549 access(all) fun setEntryFee(entry_fee : UFix64) {
1550 ABRoyaleGame.setEntryFee(entry_fee: entry_fee)
1551 }
1552 access(all) fun setSecondsPerTurn(newSecondsPerTurn : Int) {
1553 ABRoyaleGame.setSecondsPerTurn(newSecondsPerTurn: newSecondsPerTurn)
1554 }
1555
1556 }
1557
1558}