Skip to content

Commit

Permalink
Check and pay Quest.playerRequirements when starting a quest
Browse files Browse the repository at this point in the history
  • Loading branch information
Janiczek committed Oct 28, 2024
1 parent 94ba8fe commit 489bf84
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 177 deletions.
169 changes: 111 additions & 58 deletions src/Backend.elm
Original file line number Diff line number Diff line change
Expand Up @@ -258,22 +258,14 @@ getPlayerData_ worldName world player =
|> SeqDict.map
(\quest ticksGivenPerPlayer ->
let
engagements : List Quest.Engagement
engagements =
engagedPlayersCount : Int
engagedPlayersCount =
players
|> List.map (\player_ -> SPlayer.questEngagement player_ quest)

playersActive : Int
playersActive =
engagements
|> List.filter (\e -> e /= Quest.NotProgressing)
|> List.length
|> List.count (\player_ -> SeqSet.member quest player_.questsActive)

ticksPerHour : Int
ticksPerHour =
engagements
|> List.map Logic.ticksGivenPerQuestEngagement
|> List.sum
engagedPlayersCount * Logic.questTicksPerHour

ticksGiven : Int
ticksGiven =
Expand All @@ -290,7 +282,7 @@ getPlayerData_ worldName world player =
in
{ ticksGiven = ticksGiven
, ticksPerHour = ticksPerHour
, playersActive = playersActive
, playersActive = engagedPlayersCount
, ticksGivenByPlayer = ticksGivenByPlayer
}
)
Expand Down Expand Up @@ -498,9 +490,11 @@ processGameTickForQuests worldName model =
player
|> Maybe.map
(\player_ ->
quest
|> SPlayer.questEngagement player_
|> Logic.ticksGivenPerQuestEngagement
if SeqSet.member quest player_.questsActive then
Logic.questTicksPerHour

else
0
)
|> Maybe.withDefault 0
in
Expand Down Expand Up @@ -2160,51 +2154,110 @@ stopProgressing quest clientId _ worldName player model =

startProgressing : Quest.Name -> ClientId -> World -> World.Name -> SPlayer -> Model -> ( Model, Cmd BackendMsg )
startProgressing quest clientId world worldName player model =
-- TODO: there are requirements other than Quest.playerRequirements, check them as well if we don't elsewhere already
let
ensurePlayerIsInQuestProgressDict : World -> World
ensurePlayerIsInQuestProgressDict world_ =
if SPlayer.canStartProgressing quest world.tickPerIntervalCurve player then
{ world_
| questsProgress =
world_.questsProgress
|> SeqDict.update quest
(\maybePlayersProgress ->
case maybePlayersProgress of
Nothing ->
Just (Dict.singleton player.name 0)

Just playersProgress ->
playersProgress
|> Dict.update player.name
(\maybePlayerProgress ->
case maybePlayerProgress of
Nothing ->
Just 0

Just n ->
Just n
)
|> Just
)
}
playerRequirements : List Quest.PlayerRequirement
playerRequirements =
Quest.playerRequirements quest

playerAlreadyPaidRequirements : Bool
playerAlreadyPaidRequirements =
world.questRequirementsPaid
|> SeqDict.get quest
|> Maybe.withDefault Set.empty
|> Set.member player.name

playerCanPayRequirements : Bool
playerCanPayRequirements =
playerRequirements
|> List.all
(\req ->
case req of
Quest.SkillRequirement r ->
case r.skill of
Quest.Combat ->
let
maxCombatSkill : Int
maxCombatSkill =
Logic.questRequirementCombatSkills
|> List.map (Skill.get player.special player.addedSkillPercentages)
|> List.maximum
|> Maybe.withDefault 0
in
maxCombatSkill >= r.percentage

else
world_
Quest.Specific skill ->
Skill.get player.special player.addedSkillPercentages skill >= r.percentage

newModel =
if World.isQuestDone quest world then
model
Quest.ItemRequirementOneOf itemsNeeded ->
itemsNeeded
|> List.any
(\item ->
player.items
|> Dict.any (\_ { kind, count } -> kind == item && count >= 1)
)

else
model
|> updatePlayer worldName player.name (SPlayer.startProgressing quest world.tickPerIntervalCurve)
|> updateWorld worldName ensurePlayerIsInQuestProgressDict
Quest.CapsRequirement capsNeeded ->
player.caps >= capsNeeded
)
in
getPlayerData worldName player.name newModel
|> Maybe.map
(\data ->
( newModel
, Lamdera.sendToFrontend clientId <| CurrentPlayer data
if playerAlreadyPaidRequirements || playerCanPayRequirements then
let
ensurePlayerPresent : World -> World
ensurePlayerPresent world_ =
if SPlayer.canStartProgressing world.tickPerIntervalCurve player then
{ world_
| questsProgress =
world_.questsProgress
|> SeqDict.update quest
(\maybePlayersProgress ->
case maybePlayersProgress of
Nothing ->
Just (Dict.singleton player.name 0)

Just playersProgress ->
playersProgress
|> Dict.update player.name (Maybe.withDefault 0 >> Just)
|> Just
)
}

else
world_

newModel =
if World.isQuestDone quest world then
model

else
model
|> updatePlayer worldName player.name (SPlayer.startProgressing quest world.tickPerIntervalCurve)
|> updateWorld worldName ensurePlayerPresent
|> (if playerAlreadyPaidRequirements then
identity

else
updatePlayer worldName player.name (SPlayer.payQuestRequirements playerRequirements)
>> updateWorld worldName (notePlayerPaidRequirements quest player.name)
)
in
getPlayerData worldName player.name newModel
|> Maybe.map
(\data ->
( newModel
, Lamdera.sendToFrontend clientId <| CurrentPlayer data
)
)
)
|> Maybe.withDefault ( model, Cmd.none )
|> Maybe.withDefault ( model, Cmd.none )

else
( model, Cmd.none )


notePlayerPaidRequirements : Quest.Name -> PlayerName -> World -> World
notePlayerPaidRequirements quest player world =
{ world
| questRequirementsPaid =
world.questRequirementsPaid
|> SeqDict.update quest (Maybe.withDefault Set.empty >> Set.insert player >> Just)
}
123 changes: 45 additions & 78 deletions src/Data/Player/SPlayer.elm
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ module Data.Player.SPlayer exposing
, incSpecial
, incWins
, levelUpHereAndNow
, payQuestRequirements
, preferAmmo
, questEngagement
, readMessage
, recalculateHp
, removeAllMessages
Expand Down Expand Up @@ -51,20 +51,16 @@ import Data.Message as Message exposing (Content(..), Message)
import Data.Perk as Perk exposing (Perk)
import Data.Player exposing (SPlayer)
import Data.Quest as Quest
exposing
( Engagement(..)
, PlayerRequirement(..)
, SkillRequirement(..)
)
import Data.Skill as Skill exposing (Skill)
import Data.Special as Special
import Data.Tick as Tick exposing (TickPerIntervalCurve)
import Data.Trait as Trait
import Data.Xp as Xp
import Dict exposing (Dict)
import Dict.Extra
import Logic
import SeqDict exposing (SeqDict)
import SeqSet exposing (SeqSet)
import SeqSet
import Time exposing (Posix)


Expand Down Expand Up @@ -290,7 +286,7 @@ tick : Posix -> TickPerIntervalCurve -> SPlayer -> SPlayer
tick currentTime worldTickCurve player =
player
|> addTicks (ticksPerHourAvailableAfterQuests worldTickCurve player)
|> addQuestProgressXp currentTime
|> addTickQuestProgressXp currentTime
|> (if player.hp < player.maxHp then
addHp
(Logic.healOverTimePerTick
Expand All @@ -305,8 +301,8 @@ tick currentTime worldTickCurve player =
)


addQuestProgressXp : Posix -> SPlayer -> SPlayer
addQuestProgressXp currentTime player =
addTickQuestProgressXp : Posix -> SPlayer -> SPlayer
addTickQuestProgressXp currentTime player =
let
xpPerQuest : SeqDict Quest.Name Int
xpPerQuest =
Expand All @@ -315,10 +311,7 @@ addQuestProgressXp currentTime player =
|> List.map
(\quest ->
( quest
, quest
|> questEngagement player
|> Logic.ticksGivenPerQuestEngagement
|> (*) (Quest.xpPerTickGiven quest)
, Quest.xpPerTickGiven quest * Logic.questTicksPerHour
)
)
|> SeqDict.fromList
Expand Down Expand Up @@ -713,76 +706,19 @@ setPreferredAmmo preferredAmmo player =
{ player | preferredAmmo = preferredAmmo }


questEngagement : SPlayer -> Quest.Name -> Quest.Engagement
questEngagement player quest =
let
reqs : List PlayerRequirement
reqs =
Quest.playerRequirements quest

meetsRequirement : PlayerRequirement -> Bool
meetsRequirement req =
let
oneSkill : Int -> Skill -> Bool
oneSkill percentage skill =
Skill.get player.special player.addedSkillPercentages skill >= percentage
in
case req of
SkillRequirement { skill, percentage } ->
case skill of
Combat ->
List.any (oneSkill percentage) Skill.combatSkills

Specific skill_ ->
oneSkill percentage skill_

ItemRequirementOneOf items ->
-- TODO consume the items once adding the quest? Only in some of these cases (chimpanzee brain) and not in others (lockpick)?
let
playerItemKinds : SeqSet ItemKind.Kind
playerItemKinds =
player.items
|> Dict.values
|> List.map .kind
|> SeqSet.fromList
in
List.all (\kind -> SeqSet.member kind playerItemKinds) items

CapsRequirement amount ->
-- TODO remove the caps once adding the quest?
player.caps >= amount
in
if SeqSet.member quest player.questsActive then
if List.isEmpty reqs then
Progressing

else if List.all meetsRequirement reqs then
Progressing

else
{- TODO only allow this if the reqs not met are the skill ones!
We can't allow the user to progress if they don't meet the caps / items reqs!
-}
ProgressingSlowly

else
NotProgressing


stopProgressing : Quest.Name -> SPlayer -> SPlayer
stopProgressing quest player =
{ player | questsActive = SeqSet.remove quest player.questsActive }


canStartProgressing : Quest.Name -> TickPerIntervalCurve -> SPlayer -> Bool
canStartProgressing quest worldTickCurve player =
-- TODO is it expected that `quest` is not used?
ticksPerHourAvailableAfterQuests worldTickCurve player >= Logic.minTicksPerHourNeededForQuest
canStartProgressing : TickPerIntervalCurve -> SPlayer -> Bool
canStartProgressing worldTickCurve player =
ticksPerHourAvailableAfterQuests worldTickCurve player >= Logic.questTicksPerHour


startProgressing : Quest.Name -> TickPerIntervalCurve -> SPlayer -> SPlayer
startProgressing quest worldTickCurve player =
if canStartProgressing quest worldTickCurve player then
if canStartProgressing worldTickCurve player then
{ player | questsActive = SeqSet.insert quest player.questsActive }

else
Expand All @@ -792,11 +728,42 @@ startProgressing quest worldTickCurve player =
ticksPerHourUsedOnQuests : SPlayer -> Int
ticksPerHourUsedOnQuests player =
player.questsActive
|> SeqSet.toList
|> List.map (questEngagement player >> Logic.ticksGivenPerQuestEngagement)
|> List.sum
|> SeqSet.size
|> (*) Logic.questTicksPerHour


ticksPerHourAvailableAfterQuests : TickPerIntervalCurve -> SPlayer -> Int
ticksPerHourAvailableAfterQuests worldTickCurve player =
Tick.ticksAddedPerInterval worldTickCurve player.ticks - ticksPerHourUsedOnQuests player


payQuestRequirements : List Quest.PlayerRequirement -> SPlayer -> SPlayer
payQuestRequirements reqs player =
List.foldl
(\req accPlayer ->
case req of
Quest.SkillRequirement _ ->
accPlayer

Quest.ItemRequirementOneOf requiredItems ->
-- Only pay the first item you find
case Dict.Extra.find (\_ item -> List.member item.kind requiredItems && item.count >= 1) accPlayer.items of
Nothing ->
accPlayer

Just ( id, item ) ->
{ accPlayer
| items =
if item.count == 1 then
Dict.remove id accPlayer.items

else
Dict.insert id { item | count = max 0 (item.count - 1) } accPlayer.items
}

Quest.CapsRequirement capsRequired ->
-- It should have been checked outside this function that we _do_ have enough caps to pay
{ accPlayer | caps = max 0 (player.caps - capsRequired) }
)
player
reqs
Loading

0 comments on commit 489bf84

Please sign in to comment.