Skip to content

Commit

Permalink
feat: implement small eleental artifacts
Browse files Browse the repository at this point in the history
add card target select in request.
fix bug in match.start with skills.
add position and id when creating DamageValue.
support equip and remove artifact.
fix bug in DiceCostValue initilalization.
  • Loading branch information
zyr17 committed Aug 25, 2023
1 parent 843f8c5 commit 3e1444c
Show file tree
Hide file tree
Showing 15 changed files with 365 additions and 63 deletions.
9 changes: 8 additions & 1 deletion agents/random_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,14 @@ def resp_use_card(
raise ValueError('Not enough dice')
selected.sort()

target = None
if len(req.targets):
target = req.targets[
int(self.random() * len(req.targets))
]

return UseCardResponse(
request = req,
cost_ids = selected
cost_ids = selected,
target = target,
)
3 changes: 2 additions & 1 deletion server/card/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from .event.others import Strategize
from .support import Supports
from .equipment.artifact import Artifacts


Cards = Strategize | Supports
Cards = Strategize | Supports | Artifacts
3 changes: 3 additions & 0 deletions server/card/equipment/artifact/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .version_3_3 import SmallElementalArtifact

Artifacts = SmallElementalArtifact | SmallElementalArtifact
69 changes: 69 additions & 0 deletions server/card/equipment/artifact/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from typing import Literal, List, Any

from server.struct import ObjectPosition
from ....object_base import CardBase
from ....struct import DiceCost
from ....consts import ObjectType, DiceCostLabels, ObjectPositionType
from ....action import MoveObjectAction, RemoveObjectAction
from ....struct import CardActionTarget


class ArtifactBase(CardBase):
"""
Base class of artifacts.
"""
name: str
type: Literal[ObjectType.ARTIFACT] = ObjectType.ARTIFACT
cost_label: int = DiceCostLabels.CARD.value | DiceCostLabels.ARTIFACT.value

version: str
cost: DiceCost
usage: int

def act(self):
"""
when this support card is activated from hand, this function is called
to update the status.
"""
raise NotImplementedError()

def get_targets(self, match: Any) -> List[CardActionTarget]:
# can quip on all self alive charactors
ret: List[CardActionTarget] = []
for charactor in match.player_tables[
self.position.player_id].charactors:
if charactor.is_alive:
ret.append(CardActionTarget(
target_position = charactor.position.copy(deep = True),
target_id = charactor.id,
))
return ret

def get_actions(
self, target: CardActionTarget | None, match: Any
) -> List[MoveObjectAction | RemoveObjectAction]:
"""
Act the artifact. will place it into artifact area.
TODO: when artifact is equipped, remove the old one.
"""
assert target is not None
ret: List[MoveObjectAction | RemoveObjectAction] = []
position = target.target_position
id = target.target_id
assert position.area == ObjectPositionType.CHARACTOR
assert position.player_id == self.position.player_id
charactors = match.player_tables[position.player_id].charactors
for charactor in charactors:
if charactor.id == id:
# check if need to remove current artifact
if charactor.artifact is not None:
ret.append(RemoveObjectAction(
object_position = charactor.artifact.position,
object_id = charactor.artifact.id,
))
ret.append(MoveObjectAction(
object_position = self.position,
object_id = self.id,
target_position = position.copy(),
))
return ret
101 changes: 101 additions & 0 deletions server/card/equipment/artifact/version_3_3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from typing import Literal
from .base import ArtifactBase
from ....struct import DiceCost
from ....modifiable_values import DiceCostValue
from ....consts import ElementType, ObjectPositionType, DiceCostLabels
from ....event import RoundPrepareEventArguments
from ....action import ActionBase


class SmallElementalArtifact(ArtifactBase):
"""
Seven artifacts that decrease elemental cost.
"""

name: Literal[
"Broken Rime's Echo", # cryo
"Laurel Coronet", # dendro
"Mask of Solitude Basalt", # geo
"Thunder Summoner's Crown", # electro
"Viridescent Venerer's Diadem", # anemo
"Wine-Stained Tricorne", # hydro
"Witch's Scorching Hat", # pyro
]

version: Literal["4.0"] = "4.0"
usage: int = 1
cost: DiceCost = DiceCost(any_dice_number=2)
element: ElementType = ElementType.NONE

def __init__(self, *argv, **kwargs):
super().__init__(*argv, **kwargs)
if self.name == "Broken Rime's Echo":
self.element = ElementType.CRYO
elif self.name == "Laurel Coronet":
self.element = ElementType.DENDRO
elif self.name == "Mask of Solitude Basalt":
self.element = ElementType.GEO
elif self.name == "Thunder Summoner's Crown":
self.element = ElementType.ELECTRO
elif self.name == "Viridescent Venerer's Diadem":
self.element = ElementType.ANEMO
elif self.name == "Wine-Stained Tricorne":
self.element = ElementType.HYDRO
elif self.name == "Witch's Scorching Hat":
self.element = ElementType.PYRO

def event_handler_ROUND_PREPARE(self, event: RoundPrepareEventArguments) \
-> list[ActionBase]:
"""
When in round prepare, reset usage
"""
self.usage = 1
return []

def act(self):
"""
When activated, reset usage
"""
self.usage = 1

def value_modifier_DICE_COST(
self,
value: DiceCostValue,
mode: Literal['TEST', 'REAL'],
) -> DiceCostValue:
"""
When charactor equipped with this artifact and used skill, decrease
the elemental cost by 1. If element not match, decrease any dice cost
by 1.
"""
if (
self.usage > 0
and self.position.area == ObjectPositionType.CHARACTOR
): # has usage and equipped
label = value.cost.label
if label & (
DiceCostLabels.NORMAL_ATTACK.value
| DiceCostLabels.ELEMENTAL_SKILL.value
| DiceCostLabels.ELEMENTAL_BURST.value
) == 0: # no label match
return value
position = value.position
if position.area != ObjectPositionType.CHARACTOR:
# cost not from charactor
return value
assert self.position.charactor_id != -1
if position.charactor_id != self.position.charactor_id:
# not same charactor
return value
# can decrease cost
used = 0
if (value.cost.elemental_dice_color == self.element
and value.cost.elemental_dice_number > 0):
value.cost.elemental_dice_number -= 1
used += 1
elif value.cost.any_dice_number > 0:
value.cost.any_dice_number -= 1
used += 1
if mode == 'REAL':
self.usage -= used
return value
14 changes: 11 additions & 3 deletions server/card/event/others.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
Event cards that not belong to any other categories.
"""

from typing import Literal
from typing import Any, List, Literal

from ...object_base import CardBase
from ...action import ActionBase, DrawCardAction
from ...struct import DiceCost
from ...struct import DiceCost, CardActionTarget


class Strategize(CardBase):
Expand All @@ -15,9 +16,16 @@ class Strategize(CardBase):
same_dice_number = 1
)

def get_actions(self) -> list[ActionBase]:
def get_targets(self, match: Any) -> List[CardActionTarget]:
# no targets
return []

def get_actions(
self, target: CardActionTarget | None, match: Any
) -> list[ActionBase]:
"""
Act the card. Draw two cards.
"""
assert target is None # no targets
return [DrawCardAction(player_id = self.position.player_id,
number = 2)]
12 changes: 9 additions & 3 deletions server/card/support/support_base.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from typing import Literal, List
from typing import Literal, List, Any
from ...object_base import CardBase
from ...consts import ObjectType, ObjectPositionType, DiceCostLabels
from ...struct import DiceCost
from ...action import Actions, RemoveObjectAction, MoveObjectAction
from ...struct import ObjectPosition
from ...struct import ObjectPosition, CardActionTarget


class SupportBase(CardBase):
Expand Down Expand Up @@ -43,7 +43,13 @@ def act(self):
"""
raise NotImplementedError()

def get_actions(self) -> List[MoveObjectAction]:
def get_targets(self, match: Any) -> List[CardActionTarget]:
# TODO: when support area number exceeded, select one to remove.
return []

def get_actions(
self, target: CardActionTarget | None, match: Any
) -> List[MoveObjectAction]:
"""
Act the support. will place it into support area.
TODO: when support area number exceeded, remove one selected support.
Expand Down
5 changes: 3 additions & 2 deletions server/charactor/charactor_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
ObjectType, WeaponType, ElementType, FactionType, ObjectPositionType
)
from ..object_base import (
ObjectBase, SkillBase, ArtifactBase, WeaponBase, TalentBase
ObjectBase, SkillBase, WeaponBase, TalentBase
)
from ..struct import ObjectPosition
from ..status import CharactorStatus
from ..card.equipment.artifact import Artifacts


class CharactorBase(ObjectBase):
Expand Down Expand Up @@ -43,7 +44,7 @@ class CharactorBase(ObjectBase):

# charactor status
weapon: WeaponBase | None = None
artifact: ArtifactBase | None = None
artifact: Artifacts | None = None
talent: TalentBase | None = None
status: List[CharactorStatus] = []
element_application: List[ElementType] = []
Expand Down
7 changes: 6 additions & 1 deletion server/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ def __repr__(self):


class DiceCostLabels(int, Enum):
SKILL = 0x1
SWITCH_CHARACTOR = 0x1
CARD = 0x2
COMPANION = 0x4
LOCATION = 0x8
Expand All @@ -331,6 +331,11 @@ class DiceCostLabels(int, Enum):
TALENT = 0x80
FOOD = 0x100
ARCANE = 0x200
NORMAL_ATTACK = 0x400
ELEMENTAL_SKILL = 0x800
ELEMENTAL_BURST = 0x1000
CHARGED_ATTACK = 0x2000
PLUNGING_ATTACK = 0x4000

def __str__(self):
return self.value
Expand Down
16 changes: 14 additions & 2 deletions server/interaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import Literal, List
from enum import Enum
from .consts import DieColor
from .struct import DiceCost
from .struct import DiceCost, CardActionTarget


class RequestActionType(str, Enum):
Expand Down Expand Up @@ -118,6 +118,7 @@ class UseCardRequest(RequestBase):
type: Literal[RequestActionType.COMBAT, RequestActionType.QUICK]
card_id: int
dice_colors: List[DieColor]
targets: List[CardActionTarget]
cost: DiceCost


Expand Down Expand Up @@ -258,15 +259,26 @@ class UseCardResponse(ResponseBase):
name: Literal['UseCardResponse'] = 'UseCardResponse'
request: UseCardRequest
cost_ids: List[int]
target: CardActionTarget | None
# TODO: choose target

@property
def is_valid(self) -> bool:
"""
Check whether the response is valid.
"""
# dice color right
cost_colors = [self.request.dice_colors[i] for i in self.cost_ids]
return self.request.cost.is_valid(cost_colors)
if not self.request.cost.is_valid(cost_colors):
return False
# if has targets, response target should not be None
if self.target is None and len(self.request.targets) > 0:
return False
# if response target is not None, it should be in targets
if self.target is not None:
if self.target not in self.request.targets:
return False
return True


Requests = (
Expand Down
Loading

0 comments on commit 3e1444c

Please sign in to comment.