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