Source Code

;; wanted-hogger.clar - Quest logic for Wanted: Hogger

(define-constant err-unauthorized (err u401))
(define-constant err-hogger-defeated (err u402))
(define-constant err-invalid-edk (err u403))
(define-constant err-hogger-not-defeated (err u404))
(define-constant err-cooldown-not-complete (err u405))

(define-data-var last-reset-block uint u0)
(define-data-var current-epoch uint u0)

(define-data-var hogger-defeat-block uint u0)
(define-constant COOLDOWN_BLOCKS u10)

(define-map player-damage principal uint)
(define-data-var player-list (list 200 principal) (list))

;; Whitelisted Contract Addresses
(define-map whitelisted-edks principal bool)

(define-trait edk-trait
	(
		(tap (uint) (response (tuple (type (string-ascii 256)) (land-id uint) (land-amount uint) (energy uint)) uint))
	)
)

;; Authorization check
(define-private (is-dao-or-extension)
    (or (is-eq tx-sender 'SP2D5BGGJ956A635JG7CJQ59FTRFRB0893514EZPJ.dungeon-master) 
        (contract-call? 'SP2D5BGGJ956A635JG7CJQ59FTRFRB0893514EZPJ.dungeon-master is-extension contract-caller))
)

(define-read-only (is-authorized)
    (ok (asserts! (is-dao-or-extension) err-unauthorized))
)

;; Whitelist Functions
(define-public (set-whitelisted-edk (edk principal) (whitelisted bool))
    (begin
        (try! (is-authorized))
        (ok (map-set whitelisted-edks edk whitelisted))
    )
)

(define-read-only (is-whitelisted-edk (edk principal))
    (default-to false (map-get? whitelisted-edks edk))
)

;; Attack Hogger function
(define-public (tap (land-id uint) (edk-contract <edk-trait>))
    (let
        (
            (tapped-out (unwrap-panic (contract-call? edk-contract tap land-id)))
            (energy (get energy tapped-out))
            (player tx-sender)
            (block-in-epoch (- block-height (var-get last-reset-block)))
        )
        (asserts! (is-whitelisted-edk (contract-of edk-contract)) err-invalid-edk)
        (asserts! (not (contract-call? .hogger-v0 is-defeated)) err-hogger-defeated)

        (print {event: "attack-hogger", player: player, energy-spent: energy, block-in-epoch: block-in-epoch})
        
        ;; Tap energy and calculate damage
        (let 
            (
                (base-damage (contract-call? .combat-v1 calculate-damage energy player))
                (damage (if (<= block-in-epoch u0)
                            (/ (* base-damage u10) u100)  ;; 90% reduction
                            (if (is-eq block-in-epoch u1)
                                (/ (* base-damage u40) u100)  ;; 60% reduction
                                (if (is-eq block-in-epoch u2)
                                    (/ (* base-damage u70) u100)  ;; 30% reduction
                                    base-damage))))  ;; No reduction
                (new-hogger-health (unwrap-panic (contract-call? .hogger-v0 take-damage damage)))
            )
            
            ;; Update player's damage and add to player list if not already present
            (try! (record-player-damage player damage))

            (print {event: "attack-result", player: player, damage-dealt: damage, new-hogger-health: new-hogger-health, hogger-defeated: (is-eq new-hogger-health u0)})
            
            ;; Check if Hogger is defeated and reset the epoch if so
            (if (is-eq new-hogger-health u0)
                (begin
                    (unwrap-panic (distribute-rewards))
                    (var-set hogger-defeat-block block-height)
                )
                true
            )
            
            (ok {
              land-id: land-id,
              energy-spent: energy,
              damage: damage,
              hogger-health: new-hogger-health
            })
        )
    )
)

(define-public (start-new-epoch)
    (begin
        (asserts! (contract-call? .hogger-v0 is-defeated) err-hogger-not-defeated)
        (asserts! (>= (- block-height (var-get hogger-defeat-block)) COOLDOWN_BLOCKS) err-cooldown-not-complete)
        
        (var-set current-epoch (+ (var-get current-epoch) u1))
        (var-set last-reset-block block-height)
        (unwrap-panic (contract-call? .hogger-v0 reset-for-new-epoch))
        (clear-player-data)
        
        (print {event: "new-epoch-started", epoch: (var-get current-epoch)})
        (ok true)
    )
)

(define-read-only (can-start-new-epoch)
    (and 
        (contract-call? .hogger-v0 is-defeated)
        (>= (- block-height (var-get hogger-defeat-block)) COOLDOWN_BLOCKS)
    )
)

(define-read-only (get-epoch-info)
    (ok {
        current-epoch: (var-get current-epoch),
        last-reset-block: (var-get last-reset-block),
        hogger-defeat-block: (var-get hogger-defeat-block),
        can-start-new-epoch: (can-start-new-epoch)
    })
)

(define-private (remove-player-damage (player principal))
    (map-delete player-damage player)
)

;; Helper function to get player damage
(define-private (get-player-damage (player principal))
    (default-to u0 (map-get? player-damage player))
)

;; Distribute rewards when Hogger is defeated
(define-private (distribute-rewards)
    (begin
        (map distribute-player-reward (var-get player-list))
        (ok true)
    )
)

(define-private (distribute-player-reward (player principal))
    (let
        (
            (players (var-get player-list))
            (player-count (len players))
            (total-damage (fold + (map get-player-damage players) u0))
            (total-experience (pow u10 u8)) ;; 100 experience in total
            (total-cha (pow u10 u9)) ;; 1000 sCHA in total
            (damage (get-player-damage player))
            (experience-share (/ total-experience player-count))
            (cha-share (/ (* total-cha damage) total-damage))
        )
        (begin
            (print {event: "distributing-rewards", total-players: (len players), total-damage: total-damage})
            (try! (contract-call? .experience mint experience-share player))
            (try! (contract-call? 'SP2D5BGGJ956A635JG7CJQ59FTRFRB0893514EZPJ.dme000-governance-token dmg-mint cha-share player))
            (print {event: "rewards-distributed", player: player, experience: experience-share, cha-amount: cha-share})
            (ok true)
        )
    )
)

;; Utility function to add a player to the list and record their damage
(define-private (record-player-damage (player principal) (damage uint))
    (let
        (
            (current-player-list (var-get player-list))
            (player-exists (index-of current-player-list player))
        )
        (begin
            (map-set player-damage player (+ (get-player-damage player) damage))
            (if (is-none player-exists)
                (var-set player-list (unwrap! (as-max-len? (append current-player-list player) u200) (err u404)))
                true
            )
            (ok true)
        )
    )
)

;; Clear player data after rewards distribution
(define-private (clear-player-data)
    (begin
        (map delete-player-damage (var-get player-list))
        (var-set player-list (list))
    )
)

(define-private (delete-player-damage (player principal))
    (map-delete player-damage player)
)

;; Getters
(define-read-only (get-hogger-health)
    (contract-call? .hogger-v0 get-health)
)

(define-read-only (get-current-epoch)
    (var-get current-epoch)
)

Functions (17)

FunctionAccessArgs
is-dao-or-extensionprivate
is-authorizedread-only
set-whitelisted-edkpublicedk: principal, whitelisted: bool
is-whitelisted-edkread-onlyedk: principal
tappublicland-id: uint, edk-contract: <edk-trait>
start-new-epochpublic
can-start-new-epochread-only
get-epoch-inforead-only
remove-player-damageprivateplayer: principal
get-player-damageprivateplayer: principal
distribute-rewardsprivate
distribute-player-rewardprivateplayer: principal
record-player-damageprivateplayer: principal, damage: uint
clear-player-dataprivate
delete-player-damageprivateplayer: principal
get-hogger-healthread-only
get-current-epochread-only