Compare commits

..

3 commits

Author SHA1 Message Date
e595e9d0e9 Update Rust crate rusqlite to 0.33.0 2025-02-28 12:02:51 +00:00
8205b91ec9 Please machete 2025-02-28 10:22:25 +01:00
ee5273b501 Rmove character window 2025-02-28 10:18:46 +01:00
97 changed files with 6 additions and 15203 deletions

View file

@ -5,7 +5,6 @@ members = [
"ConfigHandler",
"Networking",
"asset",
"character_window",
"context",
"controllable_thread",
"ecs",
@ -15,12 +14,10 @@ members = [
"gltf-loader",
"loading-screen",
"lua-wrapper",
"map",
"math",
"presentation",
"promise",
"ring_buffer",
"rpg_components",
"scene_update_macros",
"transaction_derive",
]

View file

@ -1,13 +0,0 @@
[package]
name = "character_window"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = { workspace = true }
paste = { workspace = true }
destructure_traitobject = { workspace = true }
downcast-rs = { workspace = true }
engine = { path = "../engine" }
rpg_components = { path = "../rpg_components" }

View file

@ -1,13 +0,0 @@
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root>
<grid x_dim="4" y_dim="1">
<label id="equip" x_slot="0" y_slot="0" text_color="black"
background='{"background_color":"#835219","border_color":"#543919","border_thickness":{"Pixel":5}}'>Equip</label>
<label id="upgrade" x_slot="1" y_slot="0" text_color="black"
background='{"background_color":"#835219","border_color":"#543919","border_thickness":{"Pixel":5}}'>Upgrade</label>
<label id="salvage" x_slot="2" y_slot="0" text_color="black"
background='{"background_color":"#835219","border_color":"#543919","border_thickness":{"Pixel":5}}'>Salvage</label>
<label id="switch_mode" x_slot="3" y_slot="0" text_color="black"
background='{"background_color":"#835219","border_color":"#543919","border_thickness":{"Pixel":5}}'>Switch</label>
</grid>
</root>

View file

@ -1,11 +0,0 @@
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root>
<grid x_dim="3" y_dim="1">
<label id="socket" x_slot="0" y_slot="0" text_color="black"
background='{"background_color":"#835219","border_color":"#543919","border_thickness":{"Pixel":5}}'>Socket</label>
<label id="salvage" x_slot="1" y_slot="0" text_color="black"
background='{"background_color":"#835219","border_color":"#543919","border_thickness":{"Pixel":5}}'>Salvage</label>
<label id="switch_mode" x_slot="2" y_slot="0" text_color="black"
background='{"background_color":"#835219","border_color":"#543919","border_thickness":{"Pixel":5}}'>Switch</label>
</grid>
</root>

View file

@ -1,9 +0,0 @@
<root>
<grid x_dim="1" y_dim="1" padding="2">
<grid x_slot="0" y_slot="0" x_dim="6" y_dim="1" background="#919191">
<icon id="addon_icon" x_slot="0" y_slot="0"></icon>
<label id="addon_type" x_slot="1" y_slot="0" x_size="3" text_color="black" text_alignment="left">StatType</label>
<label id="addon_value" x_slot="4" y_slot="0" x_size="2" text_color="black">StatValue</label>
</grid>
</grid>
</root>

View file

@ -1,8 +0,0 @@
<root>
<grid x_dim="1" y_dim="1" padding="2">
<grid x_slot="0" y_slot="0" x_dim="6" y_dim="1" background="#919191">
<icon x_slot="0" y_slot="0"></icon>
<label id="info" x_slot="1" y_slot="0" x_size="3" text_color="black"></label>
</grid>
</grid>
</root>

View file

@ -1,16 +0,0 @@
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root>
<grid x_dim="1" y_dim="10" margin="4" padding="4"
background='{"background_color":"#945b09","border_color":"#543919","border_thickness":{"Pixel":2}}'>
<grid x_slot="0" y_slot="0" x_dim="6" y_dim="1"
button_normal='{"background_color":"#835219","border_color":"#543919","border_thickness":{"Pixel":5}}'
button_selected='{"background_color":"#835219","border_color":"#c37417","border_thickness":{"Pixel":5}}'>
<button id="abilities" x_slot="1" y_slot="0" x_size="2" text_color="black">Abilities</button>
<button id="addons" x_slot="3" y_slot="0" x_size="2" text_color="black">Addons</button>
</grid>
<grid id="content" x_slot="0" y_slot="1" x_dim="1" y_dim="1" y_size="8"></grid>
<grid id="tooltip" x_slot="0" y_slot="9" x_dim="1" y_dim="1"></grid>
</grid>
</root>

View file

@ -1,35 +0,0 @@
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root>
<grid x_dim="1" y_dim="10" padding="5" margin="5"
background='{"background_color":"#945b09","border_color":"#543919","border_thickness":{"Pixel":2}}'
button_normal='{"background_color":"#945b09","border_color":"#543919","border_thickness":{"Pixel":2}}'
button_selected='{"background_color":"#945b09","border_color":"#543919","border_thickness":{"Pixel":2}}'>
<grid x_slot="0" y_slot="0" x_dim="1" y_dim="9" y_size="9" padding="0" margin="0">
<grid x_slot="0" y_slot="0" x_dim="4" y_dim="1" padding="0" margin="0">
<button id="first_ability" x_slot="0" y_slot="0" fill_type="square" select_mode="none">0</button>
<button id="second_ability" x_slot="1" y_slot="0" fill_type="square" select_mode="none">0</button>
<button id="third_ability" x_slot="2" y_slot="0" fill_type="square" select_mode="none">0</button>
<button id="fourth_ability" x_slot="3" y_slot="0" fill_type="square" select_mode="none">0</button>
</grid>
<grid id="ability_content" x_slot="0" y_slot="1" x_dim="1" y_dim="8" y_size="8"
background='{"background_color":"#945b09","border_color":"#543919","border_thickness":{"Pixel":2}}'></grid>
</grid>
<grid x_slot="0" y_slot="9" x_dim="6" y_dim="1"
background='{"background_color":"#945b09","border_color":"#543919","border_thickness":{"Pixel":2}}'>
<icon id="common" x_slot="0" y_slot="0" fill_type="square"
background='{"background_color":"white","border_color":"black","border_thickness":{"Pixel":2}}'>0</icon>
<icon id="uncommon" x_slot="1" y_slot="0" fill_type="square"
background='{"background_color":"white","border_color":"black","border_thickness":{"Pixel":2}}'>0</icon>
<icon id="magical" x_slot="2" y_slot="0" fill_type="square"
background='{"background_color":"white","border_color":"black","border_thickness":{"Pixel":2}}'>0</icon>
<icon id="rare" x_slot="3" y_slot="0" fill_type="square"
background='{"background_color":"white","border_color":"black","border_thickness":{"Pixel":2}}'>0</icon>
<icon id="epic" x_slot="4" y_slot="0" fill_type="square"
background='{"background_color":"white","border_color":"black","border_thickness":{"Pixel":2}}'>0</icon>
<icon id="legendary" x_slot="5" y_slot="0" fill_type="square"
background='{"background_color":"white","border_color":"black","border_thickness":{"Pixel":2}}'>0</icon>
</grid>
</grid>
</root>

View file

@ -1,78 +0,0 @@
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root>
<grid id="statistic_tab" x_dim="2" y_dim="1" padding="10">
<!-- stats -->
<grid x_slot="0" y_slot="0" x_dim="2" y_dim="1" margin="0" padding="0"
background='{"background_color":"#945b09","border_color":"#543919","border_thickness":{"Pixel":2}}'>
<grid x_slot="0" y_slot="0" x_dim="1" y_dim="17" margin="2" padding="2">
<label x_slot="0" y_slot="0" text_ratio="0.9">Air Resistance</label>
<label x_slot="0" y_slot="1" text_ratio="0.9">Fire Resistance</label>
<label x_slot="0" y_slot="2" text_ratio="0.9">Water Resistance</label>
<label x_slot="0" y_slot="3" text_ratio="0.9">Armor</label>
<label x_slot="0" y_slot="5" text_ratio="0.9">Air Damage</label>
<label x_slot="0" y_slot="6" text_ratio="0.9">Fire Damage</label>
<label x_slot="0" y_slot="7" text_ratio="0.9">Water Damage</label>
<label x_slot="0" y_slot="8" text_ratio="0.9">Physical Damage</label>
<label x_slot="0" y_slot="10" text_ratio="0.9">Crit Chance</label>
<label x_slot="0" y_slot="11" text_ratio="0.9">Crit Damage</label>
<label x_slot="0" y_slot="13" text_ratio="0.9">Health</label>
<label x_slot="0" y_slot="14" text_ratio="0.9">Mana</label>
<label x_slot="0" y_slot="15" text_ratio="0.9">Health Regen</label>
<label x_slot="0" y_slot="16" text_ratio="0.9">Mana Regen</label>
</grid>
<grid x_slot="1" y_slot="0" x_dim="1" y_dim="17" background="#aaaaaa" margin="2" padding="2">
<label x_slot="0" y_slot="0" text_ratio="0.9" id="air_def_info">0</label>
<label x_slot="0" y_slot="1" text_ratio="0.9" id="fire_def_info">0</label>
<label x_slot="0" y_slot="2" text_ratio="0.9" id="water_def_info">0</label>
<label x_slot="0" y_slot="3" text_ratio="0.9" id="armor_info">0</label>
<label x_slot="0" y_slot="5" text_ratio="0.9" id="air_dmg_info">0</label>
<label x_slot="0" y_slot="6" text_ratio="0.9" id="fire_dmg_info">0</label>
<label x_slot="0" y_slot="7" text_ratio="0.9" id="water_dmg_info"> 0</label>
<label x_slot="0" y_slot="8" text_ratio="0.9" id="phys_dmg_info">0</label>
<label x_slot="0" y_slot="10" text_ratio="0.9" id="crit_chance_info">0</label>
<label x_slot="0" y_slot="11" text_ratio="0.9" id="crit_dmg_info">0</label>
<label x_slot="0" y_slot="13" text_ratio="0.9" id="health_info">0</label>
<label x_slot="0" y_slot="14" text_ratio="0.9" id="mana_info">0</label>
<label x_slot="0" y_slot="15" text_ratio="0.9" id="health_regen_info">0</label>
<label x_slot="0" y_slot="16" text_ratio="0.9" id="mana_regen_info">0</label>
</grid>
</grid>
<grid x_slot="1" y_slot="0" x_dim="1" y_dim="17" margin="0" padding="0"
background='{"background_color":"#945b09","border_color":"#543919","border_thickness":{"Pixel":2}}'
button_normal='{"background_color":"#835219","border_color":"#543919","border_thickness":{"Pixel":5}}'
button_selected='{"background_color":"#835219","border_color":"#c37417","border_thickness":{"Pixel":5}}'>
<grid x_slot="0" y_slot="0" x_dim="2" y_dim="1">
<label id="character_name" x_slot="0" y_slot="0" text_ratio="0.8">Name</label>
<label id="level" x_slot="1" y_slot="0" text_ratio="0.8">0</label>
</grid>
<grid x_slot="0" y_slot="1" x_dim="1" y_dim="1">
<progressbar id="level_progress" x_slot="0" y_slot="0" background="#A69614" foreground="#E9D429">Test</progressbar>
</grid>
<grid x_slot="0" y_slot="2" x_dim="1" y_dim="4" y_size="4">
<label id="attributes" x_slot="0" y_slot="0" text_color="black">Attributes</label>
<grid x_slot="0" y_slot="1" x_dim="4" y_dim="3" y_size="3">
<label x_slot="0" y_slot="0" x_size="3" text_color="#C23519">Strength</label>
<button id="strength_field" x_slot="3" y_slot="0" text_color="black" select="true">0</button>
<label x_slot="0" y_slot="1" x_size="3" text_color="#65D01E">Agility</label>
<button id="agility_field" x_slot="3" y_slot="1" text_color="black">0</button>
<label x_slot="0" y_slot="2" x_size="3" text_color="#1D63B3">Intelligence</label>
<button id="intelligence_field" x_slot="3" y_slot="2" text_color="black">0</button>
</grid>
</grid>
</grid>
</grid>
</root>

View file

@ -1,14 +0,0 @@
<?xml-model href="gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root>
<grid x_dim="1" y_dim="7" margin="0" padding="0">
<grid x_slot="0" y_slot="0" x_dim="3" y_dim="1"
button_normal='{"background_color":"#835219","border_color":"#543919","border_thickness":{"Pixel":5}}'
button_selected='{"background_color":"#835219","border_color":"#c37417","border_thickness":{"Pixel":5}}'>
<button id="left" x_slot="0" y_slot="0" text_color="black" isolate="true">prev</button>
<label id="tab_info" x_slot="1" y_slot="0" text_color="black">1 / 1</label>
<button id="right" x_slot="2" y_slot="0" text_color="black" isolate="true">next</button>
</grid>
<grid id="content" x_slot="0" y_slot="1" x_dim="4" y_dim="4" y_size="6"></grid>
</grid>
</root>

View file

@ -1,8 +0,0 @@
<?xml-model href="gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root>
<grid x_dim="1" y_dim="1" margin="0" padding="0"
button_normal='{"background_color":"#945b09","border_color":"#543919","border_thickness":{"Pixel":0}}'
button_selected='{"background_color":"#bd7000","border_color":"#c37417","border_thickness":{"Pixel":0}}'>
<button id="button" x_slot="0" y_slot="0" select_mode="none" fill_type="square"></button>
</grid>
</root>

View file

@ -1,248 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- definition of attributes -->
<xs:attribute name="id" type="xs:string"></xs:attribute>
<xs:attribute name="x_slot" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="y_slot" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="x_dim" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="y_dim" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="x_size" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="y_size" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="normal" type="xs:string"></xs:attribute>
<xs:attribute name="selected" type="xs:string"></xs:attribute>
<xs:attribute name="click_sound" type="xs:string"></xs:attribute>
<xs:attribute name="hover_sound" type="xs:string"></xs:attribute>
<xs:attribute name="select_mode">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="none"></xs:enumeration>
<xs:enumeration value="bigger"></xs:enumeration>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="icon" type="xs:string"></xs:attribute>
<xs:attribute name="icon_margin" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="text_color" type="xs:string"></xs:attribute>
<xs:attribute name="text_ratio" type="xs:decimal"></xs:attribute>
<xs:attribute name="text_alignment">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="left"></xs:enumeration>
<xs:enumeration value="right"></xs:enumeration>
<xs:enumeration value="top"></xs:enumeration>
<xs:enumeration value="bottom"></xs:enumeration>
<xs:enumeration value="center"></xs:enumeration>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="select" type="xs:boolean"></xs:attribute>
<xs:attribute name="isolate" type="xs:boolean"></xs:attribute>
<xs:attribute name="x_offset" type="xs:integer"></xs:attribute>
<xs:attribute name="y_offset" type="xs:integer"></xs:attribute>
<xs:attribute name="width" type="xs:integer"></xs:attribute>
<xs:attribute name="height" type="xs:integer"></xs:attribute>
<xs:attribute name="padding" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="margin" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="vert_align">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="top"></xs:enumeration>
<xs:enumeration value="middle"></xs:enumeration>
<xs:enumeration value="bottom"></xs:enumeration>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="hori_align">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="left"></xs:enumeration>
<xs:enumeration value="middle"></xs:enumeration>
<xs:enumeration value="right"></xs:enumeration>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="background" type="xs:string"></xs:attribute>
<xs:attribute name="foreground" type="xs:string"></xs:attribute>
<xs:attribute name="button_normal" type="xs:string"></xs:attribute>
<xs:attribute name="button_selected" type="xs:string"></xs:attribute>
<xs:attribute name="fill_type">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="expand"></xs:enumeration>
<xs:enumeration value="square"></xs:enumeration>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="reference_width" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="reference_height" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="layer" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="line_count" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="west_neighbour" type="xs:string"></xs:attribute>
<xs:attribute name="east_neighbour" type="xs:string"></xs:attribute>
<xs:attribute name="north_neighbour" type="xs:string"></xs:attribute>
<xs:attribute name="south_neighbour" type="xs:string"></xs:attribute>
<!-- definition of complex elements -->
<xs:element name="root">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element name="grid" />
</xs:choice>
<xs:attribute ref="reference_width"></xs:attribute>
<xs:attribute ref="reference_height"></xs:attribute>
<xs:attribute ref="layer"></xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="grid">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="grid" />
<xs:element name="button" />
<xs:element name="label" />
<xs:element name="progressbar" />
<xs:element name="textfield" />
<xs:element name="icon" />
</xs:choice>
<xs:attribute ref="id"></xs:attribute>
<xs:attribute ref="x_slot"></xs:attribute>
<xs:attribute ref="y_slot"></xs:attribute>
<xs:attribute ref="x_dim"></xs:attribute>
<xs:attribute ref="y_dim"></xs:attribute>
<xs:attribute ref="x_size"></xs:attribute>
<xs:attribute ref="y_size"></xs:attribute>
<xs:attribute ref="x_offset"></xs:attribute>
<xs:attribute ref="y_offset"></xs:attribute>
<xs:attribute ref="width"></xs:attribute>
<xs:attribute ref="height"></xs:attribute>
<xs:attribute ref="padding"></xs:attribute>
<xs:attribute ref="margin"></xs:attribute>
<xs:attribute ref="vert_align"></xs:attribute>
<xs:attribute ref="hori_align"></xs:attribute>
<xs:attribute ref="background"></xs:attribute>
<xs:attribute ref="button_normal"></xs:attribute>
<xs:attribute ref="button_selected"></xs:attribute>
<xs:attribute ref="click_sound"></xs:attribute>
<xs:attribute ref="hover_sound"></xs:attribute>
<xs:attribute ref="layer"></xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="button">
<xs:complexType mixed="true">
<xs:attribute ref="id"></xs:attribute>
<xs:attribute ref="x_slot"></xs:attribute>
<xs:attribute ref="y_slot"></xs:attribute>
<xs:attribute ref="x_size"></xs:attribute>
<xs:attribute ref="y_size"></xs:attribute>
<xs:attribute ref="select"></xs:attribute>
<xs:attribute ref="isolate"></xs:attribute>
<xs:attribute ref="normal"></xs:attribute>
<xs:attribute ref="selected"></xs:attribute>
<xs:attribute ref="fill_type"></xs:attribute>
<xs:attribute ref="click_sound"></xs:attribute>
<xs:attribute ref="hover_sound"></xs:attribute>
<xs:attribute ref="select_mode"></xs:attribute>
<xs:attribute ref="icon"></xs:attribute>
<xs:attribute ref="icon_margin"></xs:attribute>
<xs:attribute ref="text_color"></xs:attribute>
<xs:attribute ref="text_ratio"></xs:attribute>
<xs:attribute ref="text_alignment"></xs:attribute>
<xs:attribute ref="west_neighbour"></xs:attribute>
<xs:attribute ref="east_neighbour"></xs:attribute>
<xs:attribute ref="north_neighbour"></xs:attribute>
<xs:attribute ref="south_neighbour"></xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="label">
<xs:complexType mixed="true">
<xs:attribute ref="id"></xs:attribute>
<xs:attribute ref="x_slot"></xs:attribute>
<xs:attribute ref="y_slot"></xs:attribute>
<xs:attribute ref="x_size"></xs:attribute>
<xs:attribute ref="y_size"></xs:attribute>
<xs:attribute ref="text_color"></xs:attribute>
<xs:attribute ref="text_ratio"></xs:attribute>
<xs:attribute ref="text_alignment"></xs:attribute>
<xs:attribute ref="background"></xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="multi_line_label">
<xs:complexType mixed="true">
<xs:attribute ref="id"></xs:attribute>
<xs:attribute ref="x_slot"></xs:attribute>
<xs:attribute ref="y_slot"></xs:attribute>
<xs:attribute ref="x_size"></xs:attribute>
<xs:attribute ref="y_size"></xs:attribute>
<xs:attribute ref="line_count"></xs:attribute>
<xs:attribute ref="text_color"></xs:attribute>
<xs:attribute ref="text_ratio"></xs:attribute>
<xs:attribute ref="text_alignment"></xs:attribute>
<xs:attribute ref="background"></xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="multi_line_textfield">
<xs:complexType mixed="true">
<xs:attribute ref="id"></xs:attribute>
<xs:attribute ref="x_slot"></xs:attribute>
<xs:attribute ref="y_slot"></xs:attribute>
<xs:attribute ref="x_size"></xs:attribute>
<xs:attribute ref="y_size"></xs:attribute>
<xs:attribute ref="line_count"></xs:attribute>
<xs:attribute ref="text_color"></xs:attribute>
<xs:attribute ref="text_ratio"></xs:attribute>
<xs:attribute ref="text_alignment"></xs:attribute>
<xs:attribute ref="background"></xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="progressbar">
<xs:complexType mixed="true">
<xs:attribute ref="id"></xs:attribute>
<xs:attribute ref="x_slot"></xs:attribute>
<xs:attribute ref="y_slot"></xs:attribute>
<xs:attribute ref="x_size"></xs:attribute>
<xs:attribute ref="y_size"></xs:attribute>
<xs:attribute ref="text_color"></xs:attribute>
<xs:attribute ref="text_ratio"></xs:attribute>
<xs:attribute ref="text_alignment"></xs:attribute>
<xs:attribute ref="background"></xs:attribute>
<xs:attribute ref="foreground"></xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="textfield">
<xs:complexType mixed="true">
<xs:attribute ref="id"></xs:attribute>
<xs:attribute ref="x_slot"></xs:attribute>
<xs:attribute ref="y_slot"></xs:attribute>
<xs:attribute ref="x_size"></xs:attribute>
<xs:attribute ref="y_size"></xs:attribute>
<xs:attribute ref="text_color"></xs:attribute>
<xs:attribute ref="text_ratio"></xs:attribute>
<xs:attribute ref="text_alignment"></xs:attribute>
<xs:attribute ref="background"></xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="icon">
<xs:complexType>
<xs:attribute ref="id"></xs:attribute>
<xs:attribute ref="x_slot"></xs:attribute>
<xs:attribute ref="y_slot"></xs:attribute>
<xs:attribute ref="x_size"></xs:attribute>
<xs:attribute ref="y_size"></xs:attribute>
<xs:attribute ref="icon"></xs:attribute>
<xs:attribute ref="margin"></xs:attribute>
<xs:attribute ref="background"></xs:attribute>
<xs:attribute ref="fill_type"></xs:attribute>
<xs:attribute ref="text_color"></xs:attribute>
<xs:attribute ref="text_ratio"></xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

View file

@ -1,38 +0,0 @@
<?xml-model href="../../gui.xsd" type="application/xml" schematypes="http://www.w3.org/2001/XMLSchema"?>
<root>
<grid id="equipment" x_dim="3" y_dim="5" padding="7" margin="5"
background='{"background_color":"#945b09","border_color":"#543919","border_thickness":{"Pixel":2}}'
button_normal='{"background_color":"#945b09","border_color":"#543919","border_thickness":{"Pixel":2}}'
button_selected='{"background_color":"#6c440b","border_color":"#543919","border_thickness":{"Pixel":2}}'>
<!-- amulets -->
<button id="amulet_0" x_slot="0" y_slot="0" icon_margin="5" fill_type="square" select_mode="none"></button>
<button id="amulet_1" x_slot="2" y_slot="0" icon_margin="5" fill_type="square" select_mode="none"></button>
<!-- helmet -->
<button id="helmet" x_slot="1" y_slot="0" icon_margin="5" fill_type="square" select_mode="none"></button>
<!-- chest plate -->
<button id="chest" x_slot="1" y_slot="1" icon_margin="5" fill_type="square" select_mode="none"></button>
<!-- gloves -->
<button id="gloves" x_slot="0" y_slot="1" icon_margin="5" fill_type="square" select_mode="none"></button>
<!-- belt -->
<button id="belt" x_slot="1" y_slot="2" icon_margin="5" fill_type="square" select_mode="none"></button>
<!-- boots -->
<button id="boots" x_slot="1" y_slot="4" icon_margin="5" fill_type="square" select_mode="none"></button>
<!-- main hand -->
<button id="main hand" x_slot="0" y_slot="4" icon_margin="5" fill_type="square" select_mode="none"></button>
<!-- off hand -->
<button id="off hand" x_slot="2" y_slot="4" icon_margin="5" fill_type="square" select_mode="none"></button>
<!-- rings -->
<button id="ring_0" x_slot="0" y_slot="2" icon_margin="5" fill_type="square" select_mode="none"></button>
<button id="ring_1" x_slot="2" y_slot="2" icon_margin="5" fill_type="square" select_mode="none"></button>
<button id="ring_2" x_slot="0" y_slot="3" icon_margin="5" fill_type="square" select_mode="none"></button>
<button id="ring_3" x_slot="2" y_slot="3" icon_margin="5" fill_type="square" select_mode="none"></button>
</grid>
</root>

View file

@ -1,13 +0,0 @@
<?xml-model href="../../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root>
<grid x_dim="4" y_dim="1">
<label id="equip" x_slot="0" y_slot="0" text_color="black"
background='{"background_color":"#835219","border_color":"#543919","border_thickness":{"Pixel":5}}'>Equip</label>
<label id="salvage" x_slot="1" y_slot="0" text_color="black"
background='{"background_color":"#835219","border_color":"#543919","border_thickness":{"Pixel":5}}'>Salvage</label>
<label id="socket" x_slot="2" y_slot="0" text_color="black"
background='{"background_color":"#835219","border_color":"#543919","border_thickness":{"Pixel":5}}'>Socket</label>
<label id="switch_mode" x_slot="3" y_slot="0" text_color="black"
background='{"background_color":"#835219","border_color":"#543919","border_thickness":{"Pixel":5}}'>Switch</label>
</grid>
</root>

View file

@ -1,23 +0,0 @@
<?xml-model href="../../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root>
<grid x_dim="1" y_dim="10" padding="5" margin="5"
background='{"background_color":"#945b09","border_color":"#543919","border_thickness":{"Pixel":2}}'
button_normal='{"background_color":"#945b09","border_color":"#543919","border_thickness":{"Pixel":2}}'
button_selected='{"background_color":"#c47503","border_color":"#543919","border_thickness":{"Pixel":2}}'>
<grid x_slot="0" y_slot="0" x_dim="5" y_dim="5" y_size="9">
<button id="reference" x_slot="2" y_slot="1" fill_type="square"></button>
<button id="first" x_slot="1" y_slot="3" fill_type="square"></button>
<button id="second" x_slot="2" y_slot="3" fill_type="square"></button>
<button id="third" x_slot="3" y_slot="3" fill_type="square"></button>
</grid>
<grid x_slot="0" y_slot="9" x_dim="3" y_dim="1">
<label id="combine" x_slot="1" y_slot="0" text_color="black"
background='{"background_color":"#835219","border_color":"#543919","border_thickness":{"Pixel":5}}'>Combine</label>
</grid>
</grid>
</root>

View file

@ -1,11 +0,0 @@
<?xml-model href="../../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root>
<grid x_dim="3" y_dim="1">
<label id="socket" x_slot="0" y_slot="0" text_color="black"
background='{"background_color":"#835219","border_color":"#543919","border_thickness":{"Pixel":5}}'>Socket</label>
<label id="combine" x_slot="1" y_slot="0" text_color="black"
background='{"background_color":"#835219","border_color":"#543919","border_thickness":{"Pixel":5}}'>Combine</label>
<label id="switch_mode" x_slot="2" y_slot="0" text_color="black"
background='{"background_color":"#835219","border_color":"#543919","border_thickness":{"Pixel":5}}'>Switch</label>
</grid>
</root>

View file

@ -1,17 +0,0 @@
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root>
<grid x_dim="1" y_dim="10" margin="4" padding="4"
background='{"background_color":"#945b09","border_color":"#543919","border_thickness":{"Pixel":2}}'>
<grid x_slot="0" y_slot="0" x_dim="3" y_dim="1"
button_normal='{"background_color":"#835219","border_color":"#543919","border_thickness":{"Pixel":5}}'
button_selected='{"background_color":"#835219","border_color":"#c37417","border_thickness":{"Pixel":5}}'>
<button id="items" x_slot="0" y_slot="0" text_color="black" isolate="true">Items</button>
<button id="jewels" x_slot="1" y_slot="0" text_color="black" isolate="true">Jewels</button>
<button id="maps" x_slot="2" y_slot="0" text_color="black" isolate="true">Maps</button>
</grid>
<grid id="content" x_slot="0" y_slot="1" x_dim="1" y_dim="1" y_size="8"></grid>
<grid id="tooltip" x_slot="0" y_slot="9" x_dim="1" y_dim="1"></grid>
</grid>
</root>

View file

@ -1,7 +0,0 @@
<?xml-model href="../../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root>
<grid x_dim="1" y_dim="1"
background='{"background_color":"#945b09","border_color":"#543919","border_thickness":{"Pixel":2}}'>
<label x_slot="0" y_slot="0" text_color="black">work in progress</label>
</grid>
</root>

View file

@ -1,11 +0,0 @@
<?xml-model href="../../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root>
<grid x_dim="3" y_dim="1">
<label id="select" x_slot="0" y_slot="0" text_color="black"
background='{"background_color":"#835219","border_color":"#543919","border_thickness":{"Pixel":5}}'>Select</label>
<label id="start" x_slot="1" y_slot="0" text_color="black"
background='{"background_color":"#835219","border_color":"#543919","border_thickness":{"Pixel":5}}'>Start</label>
<label id="switch_mode" x_slot="2" y_slot="0" text_color="black"
background='{"background_color":"#835219","border_color":"#543919","border_thickness":{"Pixel":5}}'>Switch</label>
</grid>
</root>

View file

@ -1,15 +0,0 @@
<root>
<!-- loot slot -->
<grid x_dim="1" y_dim="1" padding="5">
<grid id="snippet_grid" x_slot="0" y_slot="0" x_dim="5" y_dim="1" background="#919191" button_normal="#919191" button_selected="#aaaaaa" margin="0">
<button id="item_icon" x_slot="0" y_slot="0" select_mode="none"></button>
<!-- stats -->
<label id="strength_info" x_slot="1" y_slot="0" text_ratio="0.7" text_color="#C23519">0</label>
<label id="agility_info" x_slot="2" y_slot="0" text_ratio="0.7" text_color="#65D01E">0</label>
<label id="intelligence_info" x_slot="3" y_slot="0" text_ratio="0.7" text_color="#1D63B3">0</label>
<button id="disassemble" x_slot="4" y_slot="0" select_mode="none"></button>
</grid>
</grid>
</root>

View file

@ -1,29 +0,0 @@
<?xml-model href="gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root reference_width="1280" reference_height="720" layer="10">
<!-- header -->
<grid x_dim="7" y_dim="1" x_offset="-420" y_offset="-310" width="840" height="50"
vert_align="middle" hori_align="middle" margin="10" padding="10"
button_normal='{"background_color":"#835219","border_color":"#543919","border_thickness":{"Pixel":5}}'
button_selected='{"background_color":"#835219","border_color":"#c37417","border_thickness":{"Pixel":5}}'
background='{"background_color":"#9c610a","border_color":"#543919","border_thickness":{"Pixel":5}}'>
<icon id="left_info" x_slot="1" y_slot="0"></icon>
<button id="open_statistics" x_slot="2" y_slot="0" text_ratio="0.7" text_color="black" select_mode="none" isolate="true">Character</button>
<button id="open_inventory" x_slot="3" y_slot="0" text_ratio="0.7" text_color="black" select_mode="none" isolate="true">Inventory</button>
<button id="open_abilities" x_slot="4" y_slot="0" text_ratio="0.7" text_color="black" select_mode="none" isolate="true">Abilities</button>
<icon id="right_info" x_slot="5" y_slot="0"></icon>
<grid x_slot="6" y_slot="0" x_dim="3" y_dim="1" padding="0" margin="0">
<button id="close" x_slot="2" y_slot="0" text_ratio="0.6" text_color="black" isolate="true"
normal='{"background_color":"#a00000","border_color":"#000000","border_thickness":{"Pixel":2}}'
selected='{"background_color":"#df0707","border_color":"#000000","border_thickness":{"Pixel":2}}'
select="true" select_mode="none">X</button>
</grid>
</grid>
<grid id="tab_content" x_dim="1" y_dim="1" x_offset="-400" y_offset="-260" width="800"
height="500" vert_align="middle" hori_align="middle" margin="10" padding="10"
background='{"background_color":"#B26F0C","border_color":"#543919","border_thickness":{"Pixel":5}}'> </grid>
</root>

View file

@ -1,273 +0,0 @@
use std::marker::PhantomData;
use std::sync::{Arc, Weak};
use rpg_components::components::ability_slots::AbilitySlots;
use rpg_components::components::crafting_materials::CraftingMaterials;
use rpg_components::components::inventory::Storable;
use rpg_components::components::statistics::Statistics;
use rpg_components::config::items::ItemSettings;
use rpg_components::items::Rarities;
use crate::*;
use crate::{traits::RightSide, CharacterWindow};
pub struct AbilityPageRightSide<A: Ability + 'static> {
snippet: Arc<GuiSnippet>,
ability_index: usize,
ability_marker: PhantomData<A>,
}
impl<A: Ability + 'static> AbilityPageRightSide<A> {
const ABILITY_BUTTON_NAMES: [&'static str; 4] = [
"first_ability",
"second_ability",
"third_ability",
"fourth_ability",
];
pub fn new(
engine: &Arc<Engine>,
reference: &Weak<CharacterWindow>,
hero: Entity,
) -> Result<Self> {
let snippet = GuiSnippet::from_str(
engine.gui_handler(),
include_str!("../../resources/abilities/right_side.xml"),
)?;
let color_settings = &engine
.scene()
.resources
.get::<ItemSettings>()
.rarity_color_settings;
Self::rarity_icon_background(&snippet, "common", color_settings.common)?;
Self::rarity_icon_background(&snippet, "uncommon", color_settings.uncommon)?;
Self::rarity_icon_background(&snippet, "magical", color_settings.magical)?;
Self::rarity_icon_background(&snippet, "rare", color_settings.rare)?;
Self::rarity_icon_background(&snippet, "epic", color_settings.epic)?;
Self::rarity_icon_background(&snippet, "legendary", color_settings.legendary)?;
for (index, name) in Self::ABILITY_BUTTON_NAMES.iter().enumerate() {
let button: Arc<Button> = snippet.element(name)?;
button.set_info_icon(&engine.controller_icon(ControllerButton::RightStick)?)?;
button.set_callback({
let reference = reference.clone();
move || {
if let Some(menu) = reference.upgrade() {
let mut tabs = menu.tabs_mut();
let abilities = tabs.abilities::<A>();
abilities.right_side.ability_index = index;
abilities.update_page()?;
}
Ok(())
}
});
button.set_select_callback({
let engine = engine.clone();
let reference = reference.clone();
let weak_button = Arc::downgrade(&button);
move |selected| {
if let Some(menu) = reference.upgrade() {
if selected {
engine.on_scene(|scene| {
let entity = scene.entity(hero)?;
let abilities = entity.get_component::<AbilitySlots<A>>()?;
if let Some(book) = abilities.book(index) {
let button = weak_button.upgrade().unwrap();
let button_pos = button.position_extent();
let target_x = button_pos.0 + button_pos.2 as i32;
let target_y = button_pos.1;
let statistics = entity.get_component::<Statistics>()?;
let gui = book.create_tooltip(
engine.gui_handler(),
statistics,
(target_x, target_y),
)?;
gui.enable()?;
gui.perform_single_check(button_pos.0, button_pos.1)?;
menu.add_tooltip("active_ability", gui);
}
Ok(())
})?;
} else {
menu.remove_tooltip("active_ability");
}
}
Ok(())
}
});
button.set_custom_callback({
let engine = engine.clone();
let reference = reference.clone();
move |button| match button {
ControllerButton::Y => {
engine.on_scene_mut(|scene| {
let entity = scene.entity_mut(hero)?;
let mut multi_mut = entity.multi_mut();
let abilities = multi_mut.get::<AbilitySlots<A>>()?;
if let Some(ability) = abilities.book_mut(index) {
let materials = multi_mut.get::<CraftingMaterials>()?;
ability.upgrade(materials);
if let Some(menu) = reference.upgrade() {
menu.tabs_mut()
.abilities::<A>()
.right_side
.refresh(&engine, hero)?;
}
}
Ok(())
})?;
Ok(true)
}
_ => Ok(false),
}
})
}
Ok(Self {
snippet,
ability_index: 0,
ability_marker: PhantomData,
})
}
pub fn selected_ability(&self) -> usize {
self.ability_index
}
fn rarity_icon_background(gui: &GuiSnippet, element: &str, color: Color) -> Result<()> {
let icon: Arc<Icon> = gui.element(element)?;
icon.set_background(FillTypeInfo::Element(
ElementDescriptor::new(color, Color::Black, 2),
DisplayableFillType::Square,
))
}
fn update_crafting_count(&self, element: &str, value: u32) -> Result<()> {
let icon: Arc<Icon> = self.snippet.element(element)?;
icon.set_text(value)?;
Ok(())
}
fn update_ability_icon(&self, element: &str, image: Option<Arc<Image>>) -> Result<()> {
let button: Arc<Button> = self.snippet.element(element)?;
match image {
Some(image) => {
button.set_icon(&image)?;
}
None => {
button.clear_icon()?;
}
}
Ok(())
}
fn update_active_ability(&self, engine: &Engine, abilities: &AbilitySlots<A>) -> Result<()> {
let grid: Arc<Grid> = self.snippet.element("ability_content")?;
let (_, rows) = grid.dimensions();
for y in 0..rows {
grid.detach(0, y)?;
}
if let Some(ability) = abilities.book(self.ability_index) {
for (index, addon) in ability.addons().iter().enumerate() {
match addon.as_ref() {
Some(addon) => {
let addon_type_snippet = GuiSnippet::from_str(
engine.gui_handler(),
include_str!("../../resources/abilities/addon_type_snippet.xml"),
)?;
let addon_icon: Arc<Icon> = addon_type_snippet.element("addon_icon")?;
let addon_type: Arc<Label> = addon_type_snippet.element("addon_type")?;
let addon_value: Arc<Label> = addon_type_snippet.element("addon_value")?;
addon_icon.set_icon(&addon.icon())?;
addon_type.set_text(&format!("{}", addon.addon_type()))?;
addon_value.set_text(&addon.addon_type().val_as_str())?;
grid.attach(addon_type_snippet, 0, index, 1, 1)?;
}
None => {
let empty_addon = GuiSnippet::from_str(
engine.gui_handler(),
include_str!("../../resources/abilities/empty_addon_snippet.xml"),
)?;
grid.attach(empty_addon, 0, index, 1, 1)?;
}
}
}
}
Ok(())
}
pub fn next_ability(&mut self, engine: &Arc<Engine>, hero: Entity) -> Result<()> {
self.ability_index = (self.ability_index + 1) % 4;
self.refresh(engine, hero)
}
}
impl<A: Ability + 'static> RightSide for AbilityPageRightSide<A> {
fn refresh(&mut self, engine: &Engine, hero: Entity) -> Result<()> {
engine.on_scene(|scene| {
let entity = scene.entity(hero)?;
let crafting = entity.get_component::<CraftingMaterials>()?;
self.update_crafting_count("common", crafting.count(Rarities::Common))?;
self.update_crafting_count("uncommon", crafting.count(Rarities::Uncommon))?;
self.update_crafting_count("magical", crafting.count(Rarities::Magical))?;
self.update_crafting_count("rare", crafting.count(Rarities::Rare))?;
self.update_crafting_count("epic", crafting.count(Rarities::Epic))?;
self.update_crafting_count("legendary", crafting.count(Rarities::Legendary))?;
let abilities = entity.get_component::<AbilitySlots<A>>()?;
for (index, name) in Self::ABILITY_BUTTON_NAMES.iter().enumerate() {
self.update_ability_icon(name, abilities.book(index).map(|book| book.icon()))?;
}
self.update_active_ability(engine, abilities)?;
Ok(())
})
}
fn base(&self) -> &Arc<GuiSnippet> {
&self.snippet
}
}

View file

@ -1,306 +0,0 @@
use std::sync::{Arc, Weak};
use rpg_components::components::ability_slots::AbilitySlots;
use rpg_components::components::inventory::{Inventory, Storable};
use rpg_components::components::statistics::Statistics;
use rpg_components::items::ability_addon::AbilityAddon;
use rpg_components::items::ability_book::AbilityBook;
use crate::*;
use crate::{
content::{Content, ContentUpdate},
CharacterWindow,
};
use super::AbilityPage;
impl<A: Ability + 'static> Content<A, AbilityAddon> {
fn show_addon_tooltip(
engine: &Arc<Engine>,
hero: Entity,
addon_index: usize,
reference: &Weak<CharacterWindow>,
(x, y, w, _h): (i32, i32, u32, u32),
) -> Result<()> {
engine.on_scene(|scene| {
let entity = scene.entity(hero)?;
let inventory = entity.get_component::<Inventory<A>>()?;
let target_x = x + w as i32;
let target_y = y;
let addon = inventory.addon_at(addon_index);
let gui = addon.create_tooltip(engine.gui_handler(), (target_x, target_y))?;
gui.enable()?;
gui.perform_single_check(x, y)?;
let window = reference.upgrade().unwrap();
window.add_tooltip(format!("addon_{addon_index}"), gui);
Ok(())
})
}
fn insert_addon(
engine: &Arc<Engine>,
hero: Entity,
addon_index: usize,
ability_page: &AbilityPage<A>,
) -> Result<()> {
engine.on_scene_mut(|scene| {
let entity = scene.entity_mut(hero)?;
let mut multi_mut = entity.multi_mut();
let inventory = multi_mut.get::<Inventory<A>>()?;
let abilities = multi_mut.get::<AbilitySlots<A>>()?;
if let Some(book) = abilities.book_mut(ability_page.right_side.selected_ability()) {
if book.has_free_addon_slots() {
book.addons_mut()
.insert_addon(inventory.remove_addon(addon_index));
}
}
Ok(())
})
}
}
impl<A: Ability + 'static> ContentUpdate for Content<A, AbilityAddon> {
fn update(&mut self, engine: &Arc<Engine>, hero: Entity) -> Result<()> {
let reference = self.reference.clone();
self.update_base(engine, |button, t, index| {
button.set_icon(&t.icon())?;
button.set_custom_callback({
let engine = engine.clone();
let reference = reference.clone();
move |controller_button| {
if let ControllerButton::X = controller_button {
CharacterWindow::salvage_from_inventory::<A, _, _>(
&engine,
hero,
|inventory| inventory.remove_addon(index),
)?;
if let Some(menu) = reference.upgrade() {
let mut tabs = menu.tabs_mut();
let abilities = tabs.abilities::<A>();
abilities.update_page()?;
}
return Ok(true);
}
Ok(false)
}
});
button.set_callback({
let engine = engine.clone();
let reference = reference.clone();
move || {
if let Some(menu) = reference.upgrade() {
let mut tabs = menu.tabs_mut();
let abilities = tabs.abilities();
Self::insert_addon(&engine, hero, index, abilities)?;
abilities.update_page()?;
}
Ok(())
}
});
button.set_select_callback({
let weak_button = Arc::downgrade(&button);
let engine = engine.clone();
let reference = reference.clone();
move |selected| {
if selected {
let button_pos = weak_button.upgrade().unwrap().position_extent();
Self::show_addon_tooltip(&engine, hero, index, &reference, button_pos)?;
} else {
let window = reference.upgrade().unwrap();
window.remove_tooltip(format!("addon_{index}"));
}
Ok(())
}
});
Ok(())
})
}
fn select(&self) -> Result<()> {
self.select()
}
}
impl<A: Ability + 'static> Content<A, AbilityBook<A>> {
fn equip_book(
engine: &Arc<Engine>,
hero: Entity,
book_index: usize,
ability_page: &AbilityPage<A>,
) -> Result<()> {
engine.on_scene_mut(|scene| {
let entity = scene.entity_mut(hero)?;
let mut multi_mut = entity.multi_mut();
let inventory = multi_mut.get::<Inventory<A>>()?;
let abilitiy_slots = multi_mut.get::<AbilitySlots<A>>()?;
if let Some(old_book) = abilitiy_slots.insert_book(
inventory.remove_book(book_index),
ability_page.right_side.selected_ability(),
) {
inventory.insert_book(old_book, book_index);
}
Ok(())
})
}
fn show_book_tooltip(
engine: &Arc<Engine>,
hero: Entity,
book_index: usize,
reference: &Weak<CharacterWindow>,
(x, y, w, _h): (i32, i32, u32, u32),
) -> Result<()> {
engine.on_scene(|scene| {
let entity = scene.entity(hero)?;
let inventory = entity.get_component::<Inventory<A>>()?;
let statistics = entity.get_component::<Statistics>()?;
let target_x = x + w as i32;
let target_y = y;
let book = inventory.book_at(book_index);
let gui =
book.create_tooltip(engine.gui_handler(), statistics, (target_x, target_y))?;
gui.enable()?;
let window = reference.upgrade().unwrap();
let abilities = entity.get_component::<AbilitySlots<A>>()?;
match abilities.book(window.tabs().abilities::<A>().right_side.selected_ability()) {
Some(selected_book) => {
let button_pos = gui.position_extent();
let target_x = button_pos.0 + button_pos.2 as i32 + 2;
let target_y = button_pos.1;
let compare_gui = selected_book.create_tooltip(
engine.gui_handler(),
statistics,
(target_x, target_y),
)?;
compare_gui.enable()?;
gui.perform_double_check(&compare_gui, x, 2)?;
window.add_tooltip("active_book", compare_gui);
}
None => {
gui.perform_single_check(x, y)?;
}
}
window.add_tooltip(format!("book_{book_index}"), gui);
Ok(())
})
}
}
impl<A: Ability + 'static> ContentUpdate for Content<A, AbilityBook<A>> {
fn update(&mut self, engine: &Arc<Engine>, hero: Entity) -> Result<()> {
let reference = self.reference.clone();
self.update_base(engine, |button, t, index| {
button.set_icon(&t.icon())?;
button.set_custom_callback({
let engine = engine.clone();
let reference = reference.clone();
move |controller_button| {
if let ControllerButton::X = controller_button {
CharacterWindow::salvage_from_inventory::<A, _, _>(
&engine,
hero,
|inventory| inventory.remove_book(index),
)?;
if let Some(menu) = reference.upgrade() {
let mut tabs = menu.tabs_mut();
let abilities = tabs.abilities::<A>();
abilities.update_page()?;
}
return Ok(true);
}
Ok(false)
}
});
button.set_callback({
let engine = engine.clone();
let reference = reference.clone();
move || {
if let Some(menu) = reference.upgrade() {
let mut tabs = menu.tabs_mut();
let abilities = tabs.abilities();
Self::equip_book(&engine, hero, index, abilities)?;
abilities.update_page()?;
}
Ok(())
}
});
button.set_select_callback({
let weak_button = Arc::downgrade(&button);
let engine = engine.clone();
let reference = reference.clone();
move |selected| {
if selected {
let button_pos = weak_button.upgrade().unwrap().position_extent();
Self::show_book_tooltip(&engine, hero, index, &reference, button_pos)?;
} else {
let window = reference.upgrade().unwrap();
window.remove_tooltip(format!("book_{index}"));
window.remove_tooltip("active_book");
}
Ok(())
}
});
Ok(())
})
}
fn select(&self) -> Result<()> {
self.select()
}
}

View file

@ -1,286 +0,0 @@
mod ability_right_side;
mod content;
use std::sync::{Arc, Weak};
use anyhow::Result;
use rpg_components::components::inventory::Inventory;
use self::ability_right_side::AbilityPageRightSide;
use super::{
content::Content,
page_content::{EmptyRightSide, PageContent},
traits::{PageContentWrapper, RightSide},
CharacterWindow, Page,
};
use crate::*;
pub struct AbilityPage<A: Ability + 'static> {
close: Weak<Button>,
engine: Arc<Engine>,
hero: Entity,
grid: Arc<Grid>,
tooltip: Arc<Grid>,
content: Arc<Grid>,
modes: [Box<dyn PageContentWrapper>; 2],
current_mode: usize,
right_side: AbilityPageRightSide<A>,
}
impl<A: Ability + 'static> AbilityPage<A> {
pub fn new(
engine: &Arc<Engine>,
hero: Entity,
reference: Weak<CharacterWindow>,
close: &Arc<Button>,
) -> Result<Self> {
let grid = Grid::new(engine.gui_handler().clone(), 2, 1, false)?;
let left_base = GuiSnippet::from_str(
engine.gui_handler(),
include_str!("../../resources/abilities/left_side.xml"),
)?;
grid.attach(left_base.clone(), 0, 0, 1, 1)?;
Self::setup_content_switch(&left_base, reference.clone())?;
let tooltip = left_base.element("tooltip")?;
let content = left_base.element("content")?;
// abilities
let ability_mode = PageContent::new(
Content::new::<_, Self>(&engine, reference.clone(), {
let engine = engine.clone();
let hero = hero.clone();
move || {
let mut data = Vec::new();
engine.on_scene(|scene| {
let hero_object = scene.entity(hero)?;
let inventory = hero_object.get_component::<Inventory<A>>()?;
data = inventory.iter_books().cloned().collect();
Ok(())
})?;
Ok(data)
}
})?,
{
let ui = GuiSnippet::from_str(
engine.gui_handler(),
include_str!("../../resources/abilities/ability_tooltip.xml"),
)?;
let equip: Arc<Label> = ui.element("equip")?;
equip.set_info_icon(&engine.controller_icon(ControllerButton::A)?)?;
let upgrade: Arc<Label> = ui.element("upgrade")?;
upgrade.set_info_icon(&engine.controller_icon(ControllerButton::Y)?)?;
let salvage: Arc<Label> = ui.element("salvage")?;
salvage.set_info_icon(&engine.controller_icon(ControllerButton::X)?)?;
let switch_mode: Arc<Label> = ui.element("switch_mode")?;
switch_mode.set_info_icon(&engine.controller_icon(ControllerButton::LeftStick)?)?;
ui
},
EmptyRightSide,
);
// addons
let addons_mode = PageContent::<A, _>::new(
Content::new::<_, Self>(&engine, reference.clone(), {
let engine = engine.clone();
let hero = hero.clone();
move || {
let mut data = Vec::new();
engine.on_scene(|scene| {
let hero_object = scene.entity(hero)?;
let inventory = hero_object.get_component::<Inventory<A>>()?;
data = inventory.iter_addons().cloned().collect();
Ok(())
})?;
Ok(data)
}
})?,
{
let ui = GuiSnippet::from_str(
engine.gui_handler(),
include_str!("../../resources/abilities/addon_tooltip.xml"),
)?;
let equip: Arc<Label> = ui.element("socket")?;
equip.set_info_icon(&engine.controller_icon(ControllerButton::A)?)?;
let salvage: Arc<Label> = ui.element("salvage")?;
salvage.set_info_icon(&engine.controller_icon(ControllerButton::X)?)?;
let switch_mode: Arc<Label> = ui.element("switch_mode")?;
switch_mode.set_info_icon(&engine.controller_icon(ControllerButton::LeftStick)?)?;
ui
},
EmptyRightSide,
);
let right_side = AbilityPageRightSide::new(&engine, &reference, hero)?;
Ok(Self {
close: Arc::downgrade(close),
engine: engine.clone(),
hero,
grid,
tooltip,
content,
modes: [Box::new(ability_mode), Box::new(addons_mode)],
current_mode: 0,
right_side,
})
}
fn update_page(&mut self) -> Result<()> {
match self.current_mode {
0 => println!("update ability view"),
1 => println!("update addon view"),
_ => unreachable!(),
}
let mode = &mut self.modes[self.current_mode];
mode.content_mut().update(&self.engine, self.hero)?;
self.tooltip.attach(mode.tooltip().clone(), 0, 0, 1, 1)?;
self.content
.attach(mode.content_mut().base().clone(), 0, 0, 1, 1)?;
self.right_side.refresh(&self.engine, self.hero)?;
self.grid
.attach(self.right_side.base().clone(), 1, 0, 1, 1)?;
Ok(())
}
fn setup_content_switch(
left_base: &GuiSnippet,
reference: Weak<CharacterWindow>,
) -> Result<()> {
let switch = {
let reference = reference.clone();
move |index| {
if let Some(menu) = reference.upgrade() {
let mut tabs = menu.tabs_mut();
let me = tabs.abilities::<A>();
if me.current_mode != index {
me.current_mode = index;
me.update_page()?;
}
}
Ok(())
}
};
let switch_to_abilities = Box::new({
let switch = switch.clone();
move || switch(0)
});
let switch_to_addons = Box::new({
let switch = switch.clone();
move || switch(1)
});
left_base.set_click_callbacks(vec![
("abilities", switch_to_abilities),
("addons", switch_to_addons),
])
}
}
impl<A: Ability + 'static> Page for AbilityPage<A> {
fn enable(&mut self) -> Result<Arc<Grid>> {
println!("enable AbilityPage");
for mode in self.modes.iter_mut() {
mode.content_mut().refresh()?;
}
self.update_page()?;
Ok(self.grid.clone())
}
fn disable(&mut self) -> Result<()> {
Ok(())
}
fn select(&self) -> Result<()> {
let mode = &self.modes[self.current_mode];
mode.content().select()?;
if mode.content().is_empty() {
if let Some(close) = self.close.upgrade() {
close.select()?;
}
}
Ok(())
}
fn next_tab(&mut self) -> Result<()> {
self.modes[self.current_mode]
.content_mut()
.next_tab(&self.engine, self.hero)
}
fn previous_tab(&mut self) -> Result<()> {
self.modes[self.current_mode]
.content_mut()
.previous_tab(&self.engine, self.hero)
}
fn event(&mut self, button: ControllerButton) -> Result<bool> {
Ok(match button {
ControllerButton::LeftStick => {
self.current_mode = (self.current_mode + 1) % self.modes.len();
self.update_page()?;
self.select()?;
true
}
ControllerButton::RightStick => {
self.right_side.next_ability(&self.engine, self.hero)?;
true
}
_ => false,
})
}
}

View file

@ -1,271 +0,0 @@
use std::sync::{Arc, Weak};
use anyhow::Result;
use rpg_components::{
components::{
attributes::{Agility, Attributes, Intelligence, Strength},
item_slots::ItemSlotContainer,
level::Level,
statistics::Statistics,
},
config::{attributes::AttributeSettings, items::ItemSettings},
};
use super::{CharacterWindow, Page};
use crate::*;
pub struct CharacterPage {
engine: Arc<Engine>,
hero: Entity,
snippet: Arc<GuiSnippet>,
grid: Arc<Grid>,
}
impl CharacterPage {
pub fn new(
engine: &Arc<Engine>,
hero: Entity,
hero_name: &str,
reference: &Weak<CharacterWindow>,
) -> Result<Self> {
let snippet = GuiSnippet::from_str(
engine.gui_handler(),
include_str!("../../resources/character/statistics.xml"),
)?;
let grid: Arc<Grid> = snippet.element("statistic_tab")?;
let name: Arc<Label> = snippet.element("character_name")?;
name.set_text(hero_name)?;
let strength: Arc<Button> = snippet.element("strength_field")?;
strength.set_callback({
let update_stats = Self::create_update_stats(hero, engine, reference);
move || {
update_stats(|attributes| {
attributes.add_strength(Strength::from(1));
})
}
});
let agility: Arc<Button> = snippet.element("agility_field")?;
agility.set_callback({
let update_stats = Self::create_update_stats(hero, engine, reference);
move || {
update_stats(|attributes| {
attributes.add_agility(Agility::from(1));
})
}
});
let intelligence: Arc<Button> = snippet.element("intelligence_field")?;
intelligence.set_callback({
let update_stats = Self::create_update_stats(hero, engine, reference);
move || {
update_stats(|attributes| {
attributes.add_intelligence(Intelligence::from(1));
})
}
});
Ok(Self {
engine: engine.clone(),
hero,
snippet,
grid,
})
}
fn refresh(&self) -> Result<()> {
self.update_stats()?;
self.update_attributes()?;
Ok(())
}
fn update_stats(&self) -> Result<()> {
let air_def: Arc<Label> = self.snippet.element("air_def_info")?;
let fire_def: Arc<Label> = self.snippet.element("fire_def_info")?;
let water_def: Arc<Label> = self.snippet.element("water_def_info")?;
let armor: Arc<Label> = self.snippet.element("armor_info")?;
let air_dmg: Arc<Label> = self.snippet.element("air_dmg_info")?;
let fire_dmg: Arc<Label> = self.snippet.element("fire_dmg_info")?;
let water_dmg: Arc<Label> = self.snippet.element("water_dmg_info")?;
let phys_dmg: Arc<Label> = self.snippet.element("phys_dmg_info")?;
let crit_chance: Arc<Label> = self.snippet.element("crit_chance_info")?;
let crit_dmg: Arc<Label> = self.snippet.element("crit_dmg_info")?;
let health: Arc<Label> = self.snippet.element("health_info")?;
let health_regen: Arc<Label> = self.snippet.element("health_regen_info")?;
let mana: Arc<Label> = self.snippet.element("mana_info")?;
let mana_regen: Arc<Label> = self.snippet.element("mana_regen_info")?;
self.engine.on_scene(|scene| {
let entity = scene.entity(self.hero)?;
let statistics = entity.get_component::<Statistics>()?;
air_def.set_text(&format!("{}", statistics.air_resistance.raw()))?;
fire_def.set_text(&format!("{}", statistics.fire_resistance.raw()))?;
water_def.set_text(&format!("{}", statistics.water_resistance.raw()))?;
armor.set_text(&format!("{}", statistics.armor.raw()))?;
air_dmg.set_text(&format!("{}", statistics.air_damage.raw()))?;
fire_dmg.set_text(&format!("{}", statistics.fire_damage.raw()))?;
water_dmg.set_text(&format!("{}", statistics.water_damage.raw()))?;
phys_dmg.set_text(&format!("{}", statistics.physical_damage.raw()))?;
crit_chance.set_text(&format!("{:.2} %", statistics.critical_hit_chance.raw()))?;
crit_dmg.set_text(&format!(
"{:.2} %",
statistics.critical_hit_damage.raw() + 100.0
))?;
health.set_text(&format!("{:.0}", statistics.health.raw()))?;
health_regen.set_text(&format!("{:.2}", statistics.health_regeneration.raw()))?;
mana.set_text(&format!("{:.0}", statistics.mana.raw()))?;
mana_regen.set_text(&format!("{:.2}", statistics.mana_regeneration.raw()))?;
Ok(())
})
}
fn update_attributes(&self) -> Result<()> {
let level_label: Arc<Label> = self.snippet.element("level")?;
let level_progress: Arc<ProgressBar> = self.snippet.element("level_progress")?;
let attributes_label: Arc<Label> = self.snippet.element("attributes")?;
let strength: Arc<Button> = self.snippet.element("strength_field")?;
let agility: Arc<Button> = self.snippet.element("agility_field")?;
let intelligence: Arc<Button> = self.snippet.element("intelligence_field")?;
self.engine.on_scene(|scene| {
let entity = scene.entity(self.hero)?;
let level = entity.get_component::<Level>()?;
let attributes = entity.get_component::<Attributes>()?;
level_label.set_text(format!("Level: {}", level.level()))?;
level_progress.set_text(format!(
"{} / {}",
level.current_experience, level.experience_needed
))?;
attributes_label.set_text(format!(
"Attributes ({})",
Self::available_attribute_points(
&scene.resources.get::<AttributeSettings>(),
attributes,
level
)
))?;
strength.set_text(attributes.strength().raw())?;
agility.set_text(attributes.agility().raw())?;
intelligence.set_text(attributes.intelligence().raw())?;
Ok(())
})
}
fn available_attribute_points(
settings: &AttributeSettings,
attributes: &Attributes,
level: &Level,
) -> u32 {
let total_attribute_points = settings.meta_settings.starting_skill_points
+ level.level() * settings.meta_settings.skill_points_per_level
+ settings.starting_attributes.sum();
let attributes_spent = attributes.sum();
total_attribute_points - attributes_spent
}
fn create_update_stats<F>(
hero: Entity,
engine: &Arc<Engine>,
reference: &Weak<CharacterWindow>,
) -> impl Fn(F) -> Result<()> + Clone
where
F: Fn(&mut Attributes),
{
let engine = engine.clone();
let reference = reference.clone();
move |upgrade: F| {
let mut upgraded = false;
engine.on_scene_mut(|scene| {
let (resources, entity) = scene.entity_resource(hero)?;
let mut multi_mut = entity.multi_mut();
let attribute_settings = resources.get::<AttributeSettings>();
let item_settings = resources.get::<ItemSettings>();
let level = multi_mut.get::<Level>()?;
let attributes = multi_mut.get::<Attributes>()?;
if Self::available_attribute_points(attribute_settings, attributes, level) > 0 {
upgrade(attributes);
let statistics = multi_mut.get::<Statistics>()?;
let items = multi_mut.get::<ItemSlotContainer>()?;
statistics.update(attributes, attribute_settings, (&*items, item_settings));
upgraded = true;
}
Ok(())
})?;
if upgraded {
if let Some(menu) = reference.upgrade() {
menu.tabs().character().refresh()?;
}
}
Ok(())
}
}
}
impl Page for CharacterPage {
fn enable(&mut self) -> Result<Arc<Grid>> {
println!("enable CharacterPage");
self.refresh()?;
Ok(self.grid.clone())
}
fn disable(&mut self) -> Result<()> {
Ok(())
}
fn select(&self) -> Result<()> {
let strength: Arc<Button> = self.snippet.element("strength_field")?;
strength.select()?;
Ok(())
}
fn next_tab(&mut self) -> Result<()> {
Ok(())
}
fn previous_tab(&mut self) -> Result<()> {
Ok(())
}
fn event(&mut self, _button: ControllerButton) -> Result<bool> {
Ok(false)
}
}

View file

@ -1,204 +0,0 @@
use std::{
marker::PhantomData,
sync::{Arc, Weak},
};
use crate::*;
pub trait ContentWrapper: ContentUpdate + Send + Sync {
fn refresh(&mut self) -> Result<()>;
fn next_tab(&mut self, engine: &Arc<Engine>, hero: Entity) -> Result<()>;
fn previous_tab(&mut self, engine: &Arc<Engine>, hero: Entity) -> Result<()>;
fn base(&self) -> &Arc<GuiSnippet>;
fn is_empty(&self) -> bool;
}
pub trait ContentUpdate {
fn update(&mut self, engine: &Arc<Engine>, hero: Entity) -> Result<()>;
fn select(&self) -> Result<()>;
}
pub struct Content<A: Ability + 'static, T: Send + Sync> {
pub reference: Weak<CharacterWindow>,
base: Arc<GuiSnippet>,
data: Vec<T>,
on_enable: Box<dyn Fn() -> Result<Vec<T>> + Send + Sync + 'static>,
page: usize,
pages: usize,
ability_marker: PhantomData<A>,
}
impl<A: Ability + 'static, T: Send + Sync> Content<A, T> {
pub fn new<F, P>(
engine: &Arc<Engine>,
reference: Weak<CharacterWindow>,
on_enable: F,
) -> Result<Self>
where
F: Fn() -> Result<Vec<T>> + Send + Sync + 'static,
P: Page,
{
let base = GuiSnippet::from_str(
engine.gui_handler(),
include_str!("../resources/content.xml"),
)?;
let left: Arc<Button> = base.element("left")?;
left.set_text("<")?;
left.set_info_icon(&engine.controller_icon(ControllerButton::LeftTrigger)?)?;
left.set_callback({
let reference = reference.clone();
move || {
if let Some(window) = reference.upgrade() {
let mut tab = window.tab_mut();
let page = tab.downcast_mut::<P>();
page.previous_tab()?;
}
Ok(())
}
});
let right: Arc<Button> = base.element("right")?;
right.set_text(">")?;
right.set_info_icon(&engine.controller_icon(ControllerButton::RightTrigger)?)?;
right.set_callback({
let reference = reference.clone();
move || {
if let Some(window) = reference.upgrade() {
let mut tab = window.tab_mut();
let page = tab.downcast_mut::<P>();
page.next_tab()?;
}
Ok(())
}
});
Ok(Self {
reference,
base,
data: Vec::new(),
on_enable: Box::new(on_enable),
page: 0,
pages: 1,
ability_marker: PhantomData,
})
}
fn clear_grid(grid: &Arc<Grid>) -> Result<()> {
let (rows, columns) = grid.dimensions();
for x in 0..columns {
for y in 0..rows {
grid.detach(x, y)?;
}
}
Ok(())
}
fn set_tab(&self, label: &Arc<Label>) -> Result<()> {
label.set_text(format!("{} / {}", self.page + 1, self.pages))
}
pub fn update_base<F>(&mut self, engine: &Arc<Engine>, setup: F) -> Result<()>
where
Self: ContentWrapper,
F: Fn(&Arc<Button>, &T, usize) -> Result<()>,
{
self.refresh()?;
let grid: Arc<Grid> = self.base.element("content")?;
let label: Arc<Label> = self.base.element("tab_info")?;
Self::clear_grid(&grid)?;
self.set_tab(&label)?;
let (rows, columns) = grid.dimensions();
'outer: for y in 0..rows {
for x in 0..columns {
let index = (self.page * columns * rows) + y * columns + x;
match self.data.get(index) {
Some(t) => {
let snippet = GuiSnippet::from_str(
engine.gui_handler(),
include_str!("../resources/content_button.xml"),
)?;
let button: Arc<Button> = snippet.element("button")?;
setup(&button, t, index)?;
grid.attach(button, x, y, 1, 1)?;
}
None => break 'outer,
}
}
}
Ok(())
}
pub fn select(&self) -> Result<()> {
let grid: Arc<Grid> = self.base.element("content")?;
if let Some(child) = grid.child_at(0, 0)? {
child.gridable().unwrap().selectable().unwrap().select()?;
}
Ok(())
}
}
impl<A: Ability + 'static, T: Send + Sync> ContentWrapper for Content<A, T>
where
Content<A, T>: ContentUpdate,
{
fn refresh(&mut self) -> Result<()> {
self.data = (self.on_enable)()?;
let grid: Arc<Grid> = self.base.element("content")?;
let (rows, columns) = grid.dimensions();
self.pages = 1.max((self.data.len() as f32 / (rows * columns) as f32).ceil() as usize);
Ok(())
}
fn next_tab(&mut self, engine: &Arc<Engine>, hero: Entity) -> Result<()> {
if self.page < (self.pages - 1) {
self.page += 1;
self.update(engine, hero)?;
}
Ok(())
}
fn previous_tab(&mut self, engine: &Arc<Engine>, hero: Entity) -> Result<()> {
if self.page > 0 {
self.page -= 1;
self.update(engine, hero)?;
}
Ok(())
}
fn base(&self) -> &Arc<GuiSnippet> {
&self.base
}
fn is_empty(&self) -> bool {
self.data.is_empty()
}
}

View file

@ -1,435 +0,0 @@
use std::sync::{Arc, Weak};
use rpg_components::components::attributes::Attributes;
use rpg_components::components::inventory::{Inventory, Storable};
use rpg_components::components::item_slots::ItemSlotContainer;
use rpg_components::components::statistics::Statistics;
use rpg_components::config::attributes::AttributeSettings;
use rpg_components::config::items::ItemSettings;
use rpg_components::items::{Item, ItemAffix, Jewel, MapItem};
use crate::*;
use crate::{
content::{Content, ContentUpdate},
CharacterWindow,
};
use super::jewel_right_side::{LowerJewels, ReferenceItemSource, ReferenceObject};
impl<A: Ability + 'static> Content<A, Item> {
fn salvage_item(engine: &Arc<Engine>, hero: Entity, item_index: usize) -> Result<()> {
CharacterWindow::salvage_from_inventory::<A, _, _>(engine, hero, |inventory| {
let mut item = inventory.remove_item(item_index);
// unsocket jewels and add them into inventory
item.affixes
.iter_mut()
.filter_map(|affix| match affix {
ItemAffix::Socket(j) => j.take(),
ItemAffix::Stat(_) => None,
})
.for_each(|jewel| {
inventory.add_jewel(jewel);
});
item
})
}
fn select_to_socket(engine: &Arc<Engine>, hero: Entity, item_index: usize) -> Result<bool> {
let mut has_empty_sockets = true;
engine.on_scene_mut(|scene| {
let entity = scene.entity(hero)?;
let inventory = entity.get_component::<Inventory<A>>()?;
let item = inventory.item_at(item_index).clone();
if item.affixes.iter().any(|affix| {
if let ItemAffix::Socket(None) = affix {
true
} else {
false
}
}) {
let socket_object = scene.resources.get_mut::<Option<ReferenceObject>>();
*socket_object = Some(ReferenceObject::Item {
item,
source: ReferenceItemSource::Inventory(item_index),
});
} else {
has_empty_sockets = false;
}
Ok(())
})?;
Ok(has_empty_sockets)
}
fn equip_item(engine: &Arc<Engine>, hero: Entity, item_index: usize) -> Result<()> {
engine.on_scene_mut(|scene| {
let (resources, entity) = scene.entity_resource(hero)?;
let mut multi_mut = entity.multi_mut();
let hero_items = multi_mut.get::<ItemSlotContainer>()?;
let inventory = multi_mut.get::<Inventory<A>>()?;
let attributes = multi_mut.get::<Attributes>()?;
// remove item from inventory
let item = inventory.remove_item(item_index);
// add or swap items with equipment
if let Some(old_item) = hero_items.insert(item.clone(), attributes, &mut multi_mut)? {
inventory.insert_item(old_item, item_index);
}
// update hero stats
let statistics = multi_mut.get::<Statistics>()?;
statistics.update(
attributes,
resources.get::<AttributeSettings>(),
(&*hero_items, resources.get::<ItemSettings>()),
);
Ok(())
})
}
fn show_item_tooltip(
engine: &Arc<Engine>,
hero: Entity,
item_index: usize,
reference: &Weak<CharacterWindow>,
(x, y, w, _h): (i32, i32, u32, u32),
) -> Result<()> {
engine.on_scene(|scene| {
let entity = scene.entity(hero)?;
let inventory = entity.get_component::<Inventory<A>>()?;
let attributes = entity.get_component::<Attributes>()?;
let target_x = x + w as i32;
let target_y = y;
let item = inventory.item_at(item_index);
let gui =
item.create_tooltip(engine.gui_handler(), attributes, (target_x, target_y))?;
gui.enable()?;
let window = reference.upgrade().unwrap();
let items = entity.get_component::<ItemSlotContainer>()?;
match items.item_at(item.slot) {
Some(equipped) => {
let grid_pos = gui.position_extent();
let spacing = 2;
let start_x = grid_pos.0 + grid_pos.2 as i32 + spacing;
let start_y = grid_pos.1;
let compare_gui = equipped.create_tooltip(
engine.gui_handler(),
attributes,
(start_x, start_y),
)?;
compare_gui.enable()?;
gui.perform_double_check(&compare_gui, x, spacing as u32)?;
window.add_tooltip("equip", compare_gui);
}
None => {
gui.perform_single_check(x, y)?;
}
}
window.add_tooltip(format!("item_{item_index}"), gui);
Ok(())
})?;
Ok(())
}
}
impl<A: Ability + 'static> ContentUpdate for Content<A, Item> {
fn update(&mut self, engine: &Arc<Engine>, hero: Entity) -> Result<()> {
let reference = self.reference.clone();
self.update_base(engine, |button, t, index| {
button.set_icon(&t.icon)?;
button.set_custom_callback({
let reference = reference.clone();
let engine = engine.clone();
move |controller_button| {
Ok(match controller_button {
ControllerButton::X => {
Self::salvage_item(&engine, hero, index)?;
if let Some(menu) = reference.upgrade() {
let mut tabs = menu.tabs_mut();
let inventory = tabs.inventory::<A>();
inventory.update_page(true)?;
}
true
}
ControllerButton::Y => {
if Self::select_to_socket(&engine, hero, index)? {
if let Some(menu) = reference.upgrade() {
let mut tabs = menu.tabs_mut();
let inventory = tabs.inventory::<A>();
inventory.switch_to_jewels()?;
}
}
true
}
_ => false,
})
}
});
button.set_callback({
let reference = reference.clone();
let engine = engine.clone();
move || {
Self::equip_item(&engine, hero, index)?;
if let Some(menu) = reference.upgrade() {
let mut tabs = menu.tabs_mut();
let inventory = tabs.inventory::<A>();
inventory.update_page(true)?;
}
Ok(())
}
});
button.set_select_callback({
let weak_button = Arc::downgrade(&button);
let reference = reference.clone();
let engine = engine.clone();
move |selected| {
if selected {
let button_pos = weak_button.upgrade().unwrap().position_extent();
Self::show_item_tooltip(&engine, hero, index, &reference, button_pos)?;
} else {
let window = reference.upgrade().unwrap();
window.remove_tooltip(format!("item_{index}"));
window.remove_tooltip("equip");
}
Ok(())
}
});
Ok(())
})
}
fn select(&self) -> Result<()> {
self.select()
}
}
impl<A: Ability + 'static> Content<A, Jewel> {
fn show_jewel_tooltip(
engine: &Arc<Engine>,
hero: Entity,
item_index: usize,
reference: &Weak<CharacterWindow>,
(x, y, w, _h): (i32, i32, u32, u32),
) -> Result<()> {
engine.on_scene(|scene| {
let entity = scene.entity(hero)?;
let inventory = entity.get_component::<Inventory<A>>()?;
let target_x = x + w as i32;
let target_y = y;
let jewel = inventory.jewel_at(item_index);
let gui = jewel.create_tooltip(
engine.gui_handler(),
scene.resources.get::<ItemSettings>(),
(target_x, target_y),
)?;
gui.enable()?;
gui.perform_single_check(x, y)?;
reference
.upgrade()
.unwrap()
.add_tooltip(format!("jewel_{item_index}"), gui);
Ok(())
})?;
Ok(())
}
fn select_to_combine(engine: &Arc<Engine>, hero: Entity, jewel_index: usize) -> Result<()> {
engine.on_scene_mut(|scene| {
let entity = scene.entity(hero)?;
let inventory = entity.get_component::<Inventory<A>>()?;
let jewel = inventory.jewel_at(jewel_index).clone();
// add to reference
let socket_object = scene.resources.get_mut::<Option<ReferenceObject>>();
*socket_object = Some(ReferenceObject::Jewel {
jewel,
index: jewel_index,
});
// remove from lower if placed there
let lower_jewels = scene.resources.get_mut::<LowerJewels>();
if let Some(position) = lower_jewels.jewels.iter().position(|jewel| match jewel {
Some((_, index)) => *index == jewel_index,
None => false,
}) {
lower_jewels.jewels[position] = None;
}
Ok(())
})
}
fn select_to_lower(engine: &Arc<Engine>, hero: Entity, jewel_index: usize) -> Result<()> {
engine.on_scene_mut(|scene| {
let entity = scene.entity(hero)?;
let inventory = entity.get_component::<Inventory<A>>()?;
let jewel = inventory.jewel_at(jewel_index).clone();
// remove from reference if placed there
let socket_object = scene.resources.get_mut::<Option<ReferenceObject>>();
if let Some(ReferenceObject::Jewel { index, .. }) = socket_object {
if *index == jewel_index {
*socket_object = None;
}
}
let lower_jewels = scene.resources.get_mut::<LowerJewels>();
// check if that jewel is already added
if !lower_jewels.jewels.iter().any(|content| match content {
Some((_, index)) => *index == jewel_index,
None => false,
}) {
// search for an empty position in lower jewels
match lower_jewels.jewels.iter().position(|jewel| jewel.is_none()) {
Some(position) => lower_jewels.jewels[position] = Some((jewel, jewel_index)),
None => lower_jewels.jewels[0] = Some((jewel, jewel_index)),
}
}
Ok(())
})
}
}
impl<A: Ability + 'static> ContentUpdate for Content<A, Jewel> {
fn update(&mut self, engine: &Arc<Engine>, hero: Entity) -> Result<()> {
let reference = self.reference.clone();
self.update_base(engine, |button, t, index| {
button.set_icon(&t.icon())?;
button.set_select_callback({
let weak_button = Arc::downgrade(&button);
let engine = engine.clone();
let reference = reference.clone();
move |selected| {
if selected {
let button_pos = weak_button.upgrade().unwrap().position_extent();
Self::show_jewel_tooltip(&engine, hero, index, &reference, button_pos)?;
} else {
let window = reference.upgrade().unwrap();
window.remove_tooltip(format!("jewel_{index}"));
}
Ok(())
}
});
button.set_callback({
let engine = engine.clone();
let reference = reference.clone();
move || {
Self::select_to_lower(&engine, hero, index)?;
if let Some(menu) = reference.upgrade() {
let mut tabs = menu.tabs_mut();
let inventory = tabs.inventory::<A>();
inventory.update_page(true)?;
}
Ok(())
}
});
button.set_custom_callback({
let engine = engine.clone();
let reference = reference.clone();
move |controller_button| {
Ok(match controller_button {
ControllerButton::Y => {
Self::select_to_combine(&engine, hero, index)?;
if let Some(menu) = reference.upgrade() {
let mut tabs = menu.tabs_mut();
let inventory = tabs.inventory::<A>();
inventory.update_page(true)?;
}
true
}
_ => false,
})
}
});
Ok(())
})
}
fn select(&self) -> Result<()> {
self.select()
}
}
impl<A: Ability + 'static> ContentUpdate for Content<A, MapItem> {
fn update(&mut self, engine: &Arc<Engine>, _hero: Entity) -> Result<()> {
self.update_base(engine, |_button, _t, _index| {
// button.set_icon(&t.icon)?;
Ok(())
})
}
fn select(&self) -> Result<()> {
self.select()
}
}

View file

@ -1,531 +0,0 @@
use rpg_components::{
components::{
attributes::Attributes, character_status::CharacterStatus, inventory::Inventory,
item_slots::ItemSlotContainer, statistics::Statistics,
},
config::{attributes::AttributeSettings, items::ItemSettings},
items::{Item, ItemAffix, Tooltip},
};
use crate::*;
use std::sync::{Arc, Weak};
use super::{
super::traits::*,
jewel_right_side::{ReferenceItemSource, ReferenceObject},
};
pub struct ItemRightSide {
snippet: Arc<GuiSnippet>,
empty_icons: InventoryEmptyIcons,
}
impl ItemRightSide {
pub fn new<A: Ability + 'static>(
engine: &Arc<Engine>,
file: &str,
reference: &Weak<CharacterWindow>,
hero: Entity,
) -> Result<Self> {
let snippet = GuiSnippet::from_str(engine.gui_handler(), file)?;
let icons = InventoryEmptyIcons::new(engine)?;
let me = Self {
snippet,
empty_icons: icons,
};
me.setup::<A>(engine, reference, hero)?;
Ok(me)
}
fn setup<A: Ability + 'static>(
&self,
engine: &Arc<Engine>,
reference: &Weak<CharacterWindow>,
hero: Entity,
) -> Result<()> {
button_setup!(self, engine, reference, hero, helmet, "helmet");
button_setup!(self, engine, reference, hero, chest, "chest");
button_setup!(self, engine, reference, hero, gloves, "gloves");
button_setup!(self, engine, reference, hero, belt, "belt");
button_setup!(self, engine, reference, hero, boots, "boots");
#[rustfmt::skip]
button_setup!(self, engine, reference, hero, primary_hand, "main hand");
#[rustfmt::skip]
button_setup!(self, engine, reference, hero, secondary_hand, "off hand");
#[rustfmt::skip]
button_setup!(self, engine, reference, hero, amulet, "amulet_0", 0);
#[rustfmt::skip]
button_setup!(self, engine, reference, hero, amulet, "amulet_1", 1);
#[rustfmt::skip]
button_setup!(self, engine, reference, hero, ring, "ring_0", 0);
#[rustfmt::skip]
button_setup!(self, engine, reference, hero, ring, "ring_1", 1);
#[rustfmt::skip]
button_setup!(self, engine, reference, hero, ring, "ring_2", 2);
#[rustfmt::skip]
button_setup!(self, engine, reference, hero, ring, "ring_3", 3);
Ok(())
}
fn update_icons(&self, items: &ItemSlotContainer) -> Result<()> {
let ui = &self.snippet;
let empty_icons = &self.empty_icons;
equip_update!(ui, items, helmet, empty_icons);
equip_update!(ui, items, chest, empty_icons);
equip_update!(ui, items, boots, empty_icons);
equip_update!(ui, items, gloves, empty_icons);
equip_update!(ui, items, belt, empty_icons);
equip_update!(ui, items, primary_hand, empty_icons, "main hand");
equip_update!(ui, items, secondary_hand, empty_icons, "off hand");
equip_update!(ui, items, ring, empty_icons, "ring_0", 0);
equip_update!(ui, items, ring, empty_icons, "ring_1", 1);
equip_update!(ui, items, ring, empty_icons, "ring_2", 2);
equip_update!(ui, items, ring, empty_icons, "ring_3", 3);
equip_update!(ui, items, amulet, empty_icons, "amulet_0", 0);
equip_update!(ui, items, amulet, empty_icons, "amulet_1", 1);
Ok(())
}
fn create_tooltip(
engine: &Arc<Engine>,
item: &Item,
attributes: &Attributes,
(x, y, w, _h): (i32, i32, u32, u32),
) -> Result<Tooltip> {
let target_x = x + w as i32;
let target_y = y;
let gui = item.create_tooltip(engine.gui_handler(), attributes, (target_x, target_y))?;
gui.enable()?;
gui.perform_single_check(x, y)?;
Ok(gui)
}
}
impl RightSide for ItemRightSide {
fn refresh(&mut self, engine: &Engine, hero: Entity) -> Result<()> {
engine.on_scene(|scene| {
let hero_object = scene.entity(hero)?;
let items = hero_object.get_component::<ItemSlotContainer>()?;
self.update_icons(items)?;
Ok(())
})?;
Ok(())
}
fn base(&self) -> &Arc<GuiSnippet> {
&self.snippet
}
}
struct InventoryEmptyIcons {
helmet: Arc<Image>,
chest: Arc<Image>,
belt: Arc<Image>,
boots: Arc<Image>,
gloves: Arc<Image>,
primary_hand: Arc<Image>,
secondary_hand: Arc<Image>,
ring: Arc<Image>,
amulet: Arc<Image>,
}
impl InventoryEmptyIcons {
fn new(engine: &Engine) -> Result<Self> {
let place_holder_settings = &engine
.scene()
.resources
.get::<ItemSettings>()
.icon_place_holder_paths;
Ok(Self {
helmet: Image::from_file(place_holder_settings.helmet.clone())?
.attach_sampler(Sampler::pretty_sampler().build(engine.device())?)
.build(engine.device(), engine.queue())?,
chest: Image::from_file(place_holder_settings.chest.clone())?
.attach_sampler(Sampler::pretty_sampler().build(engine.device())?)
.build(engine.device(), engine.queue())?,
belt: Image::from_file(place_holder_settings.belt.clone())?
.attach_sampler(Sampler::pretty_sampler().build(engine.device())?)
.build(engine.device(), engine.queue())?,
boots: Image::from_file(place_holder_settings.boots.clone())?
.attach_sampler(Sampler::pretty_sampler().build(engine.device())?)
.build(engine.device(), engine.queue())?,
gloves: Image::from_file(place_holder_settings.gloves.clone())?
.attach_sampler(Sampler::pretty_sampler().build(engine.device())?)
.build(engine.device(), engine.queue())?,
primary_hand: Image::from_file(place_holder_settings.main_hand.clone())?
.attach_sampler(Sampler::pretty_sampler().build(engine.device())?)
.build(engine.device(), engine.queue())?,
secondary_hand: Image::from_file(place_holder_settings.off_hand.clone())?
.attach_sampler(Sampler::pretty_sampler().build(engine.device())?)
.build(engine.device(), engine.queue())?,
ring: Image::from_file(place_holder_settings.ring.clone())?
.attach_sampler(Sampler::pretty_sampler().build(engine.device())?)
.build(engine.device(), engine.queue())?,
amulet: Image::from_file(place_holder_settings.amulet.clone())?
.attach_sampler(Sampler::pretty_sampler().build(engine.device())?)
.build(engine.device(), engine.queue())?,
})
}
}
mod macros {
#[macro_export]
macro_rules! button_setup {
($self:ident, $engine:ident, $reference:ident, $hero:ident, $item:ident, $button:literal) => {
paste::expr! {
let [<$item _button>]: Arc<Button> = $self.snippet.element($button)?;
[<$item _button>].set_select_callback({
let engine = $engine.clone();
let reference = $reference.clone();
let weak_button = Arc::downgrade(&[<$item _button>]);
move |selected| {
if selected {
engine.on_scene(|scene| {
let entity = scene.entity($hero)?;
let attributes = entity.get_component::<Attributes>()?;
let items = entity.get_component::<ItemSlotContainer>()?;
match items.$item() {
Some($item) => {
let button_pos =
weak_button.upgrade().unwrap().position_extent();
let gui = Self::create_tooltip(
&engine,
$item,
attributes,
button_pos,
)?;
reference.upgrade().unwrap().add_tooltip("equip", gui);
}
None => {
reference.upgrade().unwrap().remove_tooltip("equip");
}
}
Ok(())
})?;
} else {
reference.upgrade().unwrap().remove_tooltip("equip");
}
Ok(())
}
});
[<$item _button>].set_callback({
let engine = $engine.clone();
let reference = $reference.clone();
move || {
let mut found_item = false;
engine.on_scene_mut(|scene| {
let (resources, entity) = scene.entity_resource($hero)?;
let mut multi_mut = entity.multi_mut();
let items = multi_mut.get::<ItemSlotContainer>()?;
let inventory = multi_mut.get::<Inventory<A>>()?;
if let Some($item) = items.[<$item>]() {
inventory.add_item($item.clone());
found_item = true;
}
if found_item {
items.[<unset_ $item>](&mut multi_mut)?;
let statistics = multi_mut.get::<Statistics>()?;
let attributes = multi_mut.get::<Attributes>()?;
statistics.update(
attributes,
resources.get::<AttributeSettings>(),
(&*items, resources.get::<ItemSettings>())
);
let status = multi_mut.get::<CharacterStatus>()?;
if status.current_health > statistics.health {
status.current_health = statistics.health.clone();
}
}
Ok(())
})?;
if found_item {
if let Some(menu) = reference.upgrade() {
let mut tabs = menu.tabs_mut();
let inventory = tabs.inventory::<A>();
inventory.update_page(true)?;
}
}
Ok(())
}
});
[<$item _button>].set_custom_callback({
let engine = $engine.clone();
let reference = $reference.clone();
move |controller_button| {
Ok(match controller_button {
ControllerButton::Y => {
let mut empty_affixes_found = false;
engine.on_scene_mut(|scene| {
let entity = scene.entity_mut($hero)?;
let items = entity.get_component::<ItemSlotContainer>()?;
if let Some(item) = items.$item().clone() {
if item.affixes.iter().any(|affix| {
if let ItemAffix::Socket(None) = affix {
true
} else {
false
}
}) {
let socket_object = scene.resources.get_mut::<Option<ReferenceObject>>();
*socket_object = Some(ReferenceObject::Item {
item,
source: ReferenceItemSource::Slots(None),
});
empty_affixes_found = true;
}
}
Ok(())
})?;
if empty_affixes_found {
let window = reference.upgrade().unwrap();
let mut tabs = window.tabs_mut();
let inventory = tabs.inventory::<A>();
inventory.switch_to_jewels()?;
}
true
}
_ => false,
})
}
});
}
};
($self:ident, $engine:ident, $reference:ident, $hero:ident, $item:ident, $button:literal, $index:literal) => {
paste::expr! {
let [<$item _button>]: Arc<Button> = $self.snippet.element($button)?;
[<$item _button>].set_select_callback({
let engine = $engine.clone();
let reference = $reference.clone();
let weak_button = Arc::downgrade(&[<$item _button>]);
move |selected| {
if selected {
engine.on_scene(|scene| {
let entity = scene.entity($hero)?;
let attributes = entity.get_component::<Attributes>()?;
let items = entity.get_component::<ItemSlotContainer>()?;
match items.$item($index) {
Some($item) => {
let button_pos =
weak_button.upgrade().unwrap().position_extent();
let gui = Self::create_tooltip(
&engine,
$item,
attributes,
button_pos,
)?;
reference.upgrade().unwrap().add_tooltip("equip", gui);
}
None => {
reference.upgrade().unwrap().remove_tooltip("equip");
}
}
Ok(())
})?;
} else {
reference.upgrade().unwrap().remove_tooltip("equip");
}
Ok(())
}
});
[<$item _button>].set_callback({
let engine = $engine.clone();
let reference = $reference.clone();
move || {
let mut found_item = false;
engine.on_scene_mut(|scene| {
let (resources, entity) = scene.entity_resource($hero)?;
let mut multi_mut = entity.multi_mut();
let items = multi_mut.get::<ItemSlotContainer>()?;
let inventory = multi_mut.get::<Inventory<A>>()?;
if let Some($item) = items.[<$item>]($index) {
inventory.add_item($item.clone());
found_item = true;
}
if found_item {
items.[<unset_ $item>]($index)?;
let statistics = multi_mut.get::<Statistics>()?;
let attributes = multi_mut.get::<Attributes>()?;
statistics.update(
attributes,
resources.get::<AttributeSettings>(),
(&*items, resources.get::<ItemSettings>())
);
let status = multi_mut.get::<CharacterStatus>()?;
if status.current_health > statistics.health {
status.current_health = statistics.health.clone();
}
}
Ok(())
})?;
if found_item {
if let Some(menu) = reference.upgrade() {
let mut tabs = menu.tabs_mut();
let inventory = tabs.inventory::<A>();
inventory.update_page(true)?;
}
}
Ok(())
}
});
[<$item _button>].set_custom_callback({
let engine = $engine.clone();
let reference = $reference.clone();
move |controller_button| {
Ok(match controller_button {
ControllerButton::Y => {
let mut empty_affixes_found = false;
engine.on_scene_mut(|scene| {
let entity = scene.entity_mut($hero)?;
let items = entity.get_component::<ItemSlotContainer>()?;
if let Some(item) = items.$item($index).clone() {
if item.affixes.iter().any(|affix| {
if let ItemAffix::Socket(None) = affix {
true
} else {
false
}
}) {
let socket_object = scene.resources.get_mut::<Option<ReferenceObject>>();
*socket_object = Some(ReferenceObject::Item {
item,
source: ReferenceItemSource::Slots(Some($index)),
});
empty_affixes_found = true;
}
}
Ok(())
})?;
if empty_affixes_found {
let window = reference.upgrade().unwrap();
let mut tabs = window.tabs_mut();
let inventory = tabs.inventory::<A>();
inventory.switch_to_jewels()?;
}
true
}
_ => false,
})
}
});
}
};
}
#[macro_export]
macro_rules! equip_update {
($gui:ident, $items:ident, $part:ident, $icons:ident) => {{
let button: Arc<Button> = $gui.element(stringify!($part))?;
match $items.$part() {
Some($part) => button.set_icon(&$part.icon)?,
None => button.set_icon(&$icons.$part)?,
}
}};
($gui:ident, $items:ident, $part:ident, $icons:ident, $name:literal) => {{
let button: Arc<Button> = $gui.element($name)?;
match $items.$part() {
Some($part) => button.set_icon(&$part.icon)?,
None => button.set_icon(&$icons.$part)?,
}
}};
($gui:ident, $items:ident, $part:ident, $icons:ident, $name:literal, $index:literal) => {{
let button: Arc<Button> = $gui.element($name)?;
match $items.$part($index) {
Some($part) => button.set_icon(&$part.icon)?,
None => button.set_icon(&$icons.$part)?,
}
}};
}
}

View file

@ -1,415 +0,0 @@
use rpg_components::{
components::{
attributes::Attributes,
inventory::{Inventory, Storable},
item_slots::ItemSlotContainer,
statistics::Statistics,
},
config::{attributes::AttributeSettings, items::ItemSettings},
items::{Item, ItemAffix, ItemSystem, Jewel},
};
use crate::*;
use std::{
cmp::Reverse,
sync::{Arc, Weak},
};
use crate::CharacterWindow;
use super::super::traits::*;
pub enum ReferenceItemSource {
Inventory(usize),
Slots(Option<usize>),
}
pub enum ReferenceObject {
Item {
item: Item,
source: ReferenceItemSource,
},
Jewel {
jewel: Jewel,
index: usize,
},
}
#[derive(Default)]
pub struct LowerJewels {
pub jewels: [Option<(Jewel, usize)>; 3],
}
pub struct JewelRightSide {
snippet: Arc<GuiSnippet>,
}
impl JewelRightSide {
pub fn new<A: Ability + 'static>(
engine: &Arc<Engine>,
file: &str,
hero: Entity,
reference: &Weak<CharacterWindow>,
) -> Result<Self> {
let snippet = GuiSnippet::from_str(engine.gui_handler(), file)?;
let combine: Arc<Label> = snippet.element("combine")?;
combine.set_info_icon(&engine.controller_icon(ControllerButton::RightStick)?)?;
engine.on_scene_mut(|scene| {
scene
.resources
.insert_if_not_exists::<Option<ReferenceObject>>();
scene.resources.insert_if_not_exists::<LowerJewels>();
Ok(())
})?;
let me = Self { snippet };
me.setup_select::<A>(engine, hero, reference)?;
Ok(me)
}
fn setup_select<A: Ability + 'static>(
&self,
engine: &Arc<Engine>,
hero: Entity,
reference: &Weak<CharacterWindow>,
) -> Result<()> {
let (top, bottom) = self.elements()?;
top.set_select_callback({
let engine = engine.clone();
let weak_top = Arc::downgrade(&top);
let reference = reference.clone();
move |selected| {
let menu = reference.upgrade().unwrap();
if selected {
let Some(button) = weak_top.upgrade() else {
return Ok(());
};
let (x, y, w, _h) = button.position_extent();
let scene = engine.scene();
let reference_info = scene.resources.get::<Option<ReferenceObject>>();
if let Some(reference_info) = reference_info {
let tooltip = match reference_info {
ReferenceObject::Item { item, .. } => item.create_tooltip(
engine.gui_handler(),
scene.entity(hero)?.get_component::<Attributes>()?,
(x + w as i32, y),
)?,
ReferenceObject::Jewel { jewel, .. } => jewel.create_tooltip(
engine.gui_handler(),
scene.resources.get::<ItemSettings>(),
(x + w as i32, y),
)?,
};
tooltip.enable()?;
tooltip.perform_single_check(x, y)?;
menu.add_tooltip("upper", tooltip);
}
} else {
menu.remove_tooltip("upper");
}
Ok(())
}
});
for (index, lower) in bottom.iter().enumerate() {
lower.set_select_callback({
let engine = engine.clone();
let weak_top = Arc::downgrade(&lower);
let reference = reference.clone();
move |selected| {
let menu = reference.upgrade().unwrap();
if selected {
let Some(button) = weak_top.upgrade() else {
return Ok(());
};
let (x, y, w, _h) = button.position_extent();
let scene = engine.scene();
let lower_info = scene.resources.get::<LowerJewels>();
if let Some((lower_jewel, _)) = &lower_info.jewels[index] {
let tooltip = lower_jewel.create_tooltip(
engine.gui_handler(),
scene.resources.get::<ItemSettings>(),
(x + w as i32, y),
)?;
tooltip.enable()?;
tooltip.perform_single_check(x, y)?;
menu.add_tooltip(format!("lower_{index}",), tooltip);
}
} else {
menu.remove_tooltip(format!("lower_{index}"));
}
Ok(())
}
});
lower.set_callback({
let engine = engine.clone();
let reference = reference.clone();
move || {
let scene = engine.scene_mut();
let lower_info = scene.resources.get_mut::<LowerJewels>();
if lower_info.jewels[index].is_some() {
lower_info.jewels[index] = None;
if let Some(menu) = reference.upgrade() {
let mut tabs = menu.tabs_mut();
let inventory = tabs.inventory::<A>();
inventory.update_page(true)?;
}
}
Ok(())
}
});
}
Ok(())
}
fn elements(&self) -> Result<(Arc<Button>, [Arc<Button>; 3])> {
let reference_element: Arc<Button> = self.snippet.element("reference")?;
let first_element: Arc<Button> = self.snippet.element("first")?;
let second_element: Arc<Button> = self.snippet.element("second")?;
let third_element: Arc<Button> = self.snippet.element("third")?;
Ok((
reference_element,
[first_element, second_element, third_element],
))
}
pub fn clear(engine: &Arc<Engine>) {
let scene = engine.scene_mut();
*scene.resources.get_mut::<Option<ReferenceObject>>() = None;
scene
.resources
.get_mut::<LowerJewels>()
.jewels
.iter_mut()
.for_each(|j| *j = None);
}
pub fn combine<A: Ability + 'static>(engine: &Arc<Engine>, hero: Entity) -> Result<bool> {
let scene = engine.scene_mut();
let (resources, entity) = scene.entity_resource(hero)?;
let mut resources = resources.multi_mut();
let reference_info = resources.get::<Option<ReferenceObject>>();
if reference_info.is_none() {
return Ok(false);
}
let lower_info = resources.get::<LowerJewels>();
if let Some(upper_info) = reference_info {
match upper_info {
ReferenceObject::Item { item, source } => {
// check that is there something in lower
// that can be socketed into the item
if !lower_info.jewels.iter().any(|jewel| jewel.is_some()) {
return Ok(false);
}
match source {
ReferenceItemSource::Inventory(index) => {
let inventory = entity.get_component_mut::<Inventory<A>>()?;
let item = inventory.item_mut_at(*index);
match item.affixes.iter_mut().find(|affix| match affix {
ItemAffix::Socket(opt) => opt.is_none(),
ItemAffix::Stat(_) => false,
}) {
Some(ItemAffix::Socket(socket)) => {
// we already made sure that lower is not empty -> unwrap is safe
let (jewel, index) = lower_info.jewels[lower_info
.jewels
.iter()
.position(|jewel| jewel.is_some())
.unwrap()]
.take()
.unwrap();
*socket = Some(jewel);
inventory.remove_jewel(index);
}
_ => {
return Ok(false);
}
}
}
ReferenceItemSource::Slots(opt_index) => {
let attribute_settings = resources.get::<AttributeSettings>();
let item_settings = resources.get::<ItemSettings>();
let mut multi_mut = entity.multi_mut();
let inventory = multi_mut.get::<Inventory<A>>()?;
let item_slots = multi_mut.get::<ItemSlotContainer>()?;
let slot = item.slot;
let item = item_slots.item_mut(slot, *opt_index).as_mut().unwrap();
match item.affixes.iter_mut().find(|affix| match affix {
ItemAffix::Socket(opt) => opt.is_none(),
ItemAffix::Stat(_) => false,
}) {
Some(ItemAffix::Socket(socket)) => {
// we already made sure that lower is not empty -> unwrap is safe
let (jewel, index) = lower_info.jewels[lower_info
.jewels
.iter()
.position(|jewel| jewel.is_some())
.unwrap()]
.take()
.unwrap();
*socket = Some(jewel);
inventory.remove_jewel(index);
}
_ => {
return Ok(false);
}
}
let statistics = multi_mut.get::<Statistics>()?;
let attributes = multi_mut.get::<Attributes>()?;
statistics.update(
attributes,
attribute_settings,
(&*item_slots, &*item_settings),
);
}
}
}
ReferenceObject::Jewel { jewel, index } => {
// check there are 3 jewels in lower
// and that all jewels match rarity and level
if !lower_info.jewels.iter().all(|j| match j {
Some((lower_jewel, _)) => {
jewel.rarity == lower_jewel.rarity && jewel.level == lower_jewel.level
}
None => false,
}) {
return Ok(false);
}
let item_settings = resources.get::<ItemSettings>();
let item_system = resources.get::<ItemSystem<A>>();
let inventory = entity.get_component_mut::<Inventory<A>>()?;
let upper_jewel = inventory.jewel_mut_at(*index);
if upper_jewel.level >= 4 {
return Ok(false);
}
upper_jewel.level += 1;
upper_jewel.update_stat(item_settings);
upper_jewel.icon = Some(item_system.jewel_icon(
upper_jewel.rarity,
upper_jewel.level,
upper_jewel.attribute,
));
let mut jewels: Vec<(Jewel, usize)> = lower_info
.jewels
.iter_mut()
.map(|j| j.take().unwrap())
.collect();
jewels.sort_by_key(|(_, index)| Reverse(*index));
jewels.iter().for_each(|(jewel, i)| {
debug_assert_eq!(*jewel, inventory.remove_jewel(*i))
});
}
}
}
Ok(true)
}
}
impl RightSide for JewelRightSide {
fn refresh(&mut self, engine: &Engine, _hero: Entity) -> Result<()> {
engine.on_scene(|scene| {
let (reference, lower) = self.elements()?;
let reference_info = scene.resources.get::<Option<ReferenceObject>>();
let lower_info = scene.resources.get::<LowerJewels>();
match reference_info.as_ref() {
Some(reference_info) => {
let icon = match reference_info {
ReferenceObject::Item { item, .. } => item.icon(),
ReferenceObject::Jewel { jewel, .. } => jewel.icon(),
};
reference.set_icon(&icon)?;
}
None => reference.clear_icon()?,
}
for (jewel, icon) in lower_info.jewels.iter().zip(lower.iter()) {
match jewel.as_ref() {
Some((jewel, _)) => icon.set_icon(&jewel.icon())?,
None => icon.clear_icon()?,
}
}
Ok(())
})
}
fn disable(&mut self, engine: &Engine, _hero: Entity) -> Result<()> {
let scene = engine.scene_mut();
*scene.resources.get_mut::<Option<ReferenceObject>>() = None;
scene
.resources
.get_mut::<LowerJewels>()
.jewels
.iter_mut()
.for_each(|j| *j = None);
Ok(())
}
fn base(&self) -> &Arc<GuiSnippet> {
&self.snippet
}
}

View file

@ -1,27 +0,0 @@
use crate::*;
use std::sync::Arc;
use super::super::traits::*;
pub struct MapRightSide {
snippet: Arc<GuiSnippet>,
}
impl MapRightSide {
pub fn new(engine: &Arc<Engine>, file: &str) -> Result<Self> {
let snippet = GuiSnippet::from_str(engine.gui_handler(), file)?;
Ok(Self { snippet })
}
}
impl RightSide for MapRightSide {
fn refresh(&mut self, _engine: &Engine, _hero: Entity) -> Result<()> {
Ok(())
}
fn base(&self) -> &Arc<GuiSnippet> {
&self.snippet
}
}

View file

@ -1,446 +0,0 @@
mod content;
mod item_right_side;
mod jewel_right_side;
mod map_right_side;
use std::marker::PhantomData;
use std::sync::{Arc, Weak};
use anyhow::Result;
use engine::prelude::*;
use rpg_components::components::inventory::Inventory;
use rpg_components::items::ability_book::Ability;
use super::page_content::PageContent;
use super::traits::*;
use super::{content::Content, CharacterWindow, Page};
use item_right_side::ItemRightSide;
use jewel_right_side::JewelRightSide;
use map_right_side::MapRightSide;
pub struct InventoryPage<A: Ability + 'static> {
close: Weak<Button>,
engine: Arc<Engine>,
hero: Entity,
grid: Arc<Grid>,
tooltip: Arc<Grid>,
content: Arc<Grid>,
modes: [Box<dyn PageContentWrapper>; 3],
current_mode: usize,
ability_marker: PhantomData<A>,
}
impl<A: Ability + 'static> InventoryPage<A> {
pub fn new(
engine: &Arc<Engine>,
hero: Entity,
reference: Weak<CharacterWindow>,
close: &Arc<Button>,
) -> Result<Self> {
let grid = Grid::new(engine.gui_handler().clone(), 2, 1, false)?;
let left_base = GuiSnippet::from_str(
engine.gui_handler(),
include_str!("../../resources/inventory/left_side.xml"),
)?;
grid.attach(left_base.clone(), 0, 0, 1, 1)?;
Self::setup_content_switch(&left_base, reference.clone())?;
let tooltip = left_base.element("tooltip")?;
let content = left_base.element("content")?;
// items
let item_mode = PageContent::<A, _>::new(
Content::new::<_, Self>(engine, reference.clone(), {
let engine = engine.clone();
let hero = hero.clone();
move || {
let mut data = Vec::new();
engine.on_scene(|scene| {
let hero_object = scene.entity(hero)?;
let inventory = hero_object.get_component::<Inventory<A>>()?;
data = inventory.iter_items().cloned().collect();
Ok(())
})?;
Ok(data)
}
})?,
{
let ui = GuiSnippet::from_str(
engine.gui_handler(),
include_str!("../../resources/inventory/items/tooltip.xml"),
)?;
let equip: Arc<Label> = ui.element("equip")?;
equip.set_info_icon(&engine.controller_icon(ControllerButton::A)?)?;
let salvage: Arc<Label> = ui.element("salvage")?;
salvage.set_info_icon(&engine.controller_icon(ControllerButton::X)?)?;
let socket: Arc<Label> = ui.element("socket")?;
socket.set_info_icon(&engine.controller_icon(ControllerButton::Y)?)?;
let switch_mode: Arc<Label> = ui.element("switch_mode")?;
switch_mode.set_info_icon(&engine.controller_icon(ControllerButton::LeftStick)?)?;
ui
},
ItemRightSide::new::<A>(
engine,
include_str!("../../resources/inventory/items/right_side.xml"),
&reference,
hero,
)?,
);
// jewels
let jewel_mode = PageContent::<A, _>::new(
Content::new::<_, Self>(engine, reference.clone(), {
let engine = engine.clone();
let hero = hero.clone();
move || {
let mut data = Vec::new();
engine.on_scene(|scene| {
let hero_object = scene.entity(hero)?;
let inventory = hero_object.get_component::<Inventory<A>>()?;
data = inventory.iter_jewels().cloned().collect();
Ok(())
})?;
Ok(data)
}
})?,
{
let ui = GuiSnippet::from_str(
engine.gui_handler(),
include_str!("../../resources/inventory/jewels/tooltip.xml"),
)?;
let socket: Arc<Label> = ui.element("socket")?;
socket.set_info_icon(&engine.controller_icon(ControllerButton::A)?)?;
let combine: Arc<Label> = ui.element("combine")?;
combine.set_info_icon(&engine.controller_icon(ControllerButton::Y)?)?;
let switch_mode: Arc<Label> = ui.element("switch_mode")?;
switch_mode.set_info_icon(&engine.controller_icon(ControllerButton::LeftStick)?)?;
ui
},
JewelRightSide::new::<A>(
engine,
include_str!("../../resources/inventory/jewels/right_side.xml"),
hero,
&reference,
)?,
);
// maps
let map_mode = PageContent::<A, _>::new(
Content::new::<_, Self>(engine, reference.clone(), {
let engine = engine.clone();
let hero: Entity = hero.clone();
move || {
let mut data = Vec::new();
engine.on_scene(|scene| {
let hero_object = scene.entity(hero)?;
let inventory = hero_object.get_component::<Inventory<A>>()?;
data = inventory.iter_maps().cloned().collect();
Ok(())
})?;
Ok(data)
}
})?,
{
let ui = GuiSnippet::from_str(
engine.gui_handler(),
include_str!("../../resources/inventory/maps/tooltip.xml"),
)?;
let select: Arc<Label> = ui.element("select")?;
select.set_info_icon(&engine.controller_icon(ControllerButton::A)?)?;
let start: Arc<Label> = ui.element("start")?;
start.set_info_icon(&engine.controller_icon(ControllerButton::X)?)?;
let switch_mode: Arc<Label> = ui.element("switch_mode")?;
switch_mode.set_info_icon(&engine.controller_icon(ControllerButton::LeftStick)?)?;
ui
},
MapRightSide::new(
engine,
include_str!("../../resources/inventory/maps/right_side.xml"),
)?,
);
Ok(Self {
close: Arc::downgrade(close),
engine: engine.clone(),
hero,
grid,
tooltip,
content,
modes: [
Box::new(item_mode),
Box::new(jewel_mode),
Box::new(map_mode),
],
current_mode: 0,
ability_marker: PhantomData,
})
}
fn switch_to_jewels(&mut self) -> Result<()> {
self.current_mode = 1;
self.update_page(true)
}
fn update_page(&mut self, select: bool) -> Result<()> {
match self.current_mode {
0 => println!("update item view"),
1 => println!("update jewel view"),
2 => println!("update map view"),
_ => unreachable!(),
}
{
let mode = &mut self.modes[self.current_mode];
self.tooltip.attach(mode.tooltip().clone(), 0, 0, 1, 1)?;
self.content
.attach(mode.content_mut().base().clone(), 0, 0, 1, 1)?;
self.grid
.attach(mode.right_side_mut().base().clone(), 1, 0, 1, 1)?;
mode.content_mut().update(&self.engine, self.hero)?;
mode.right_side_mut().refresh(&self.engine, self.hero)?;
if select {
mode.content_mut().select()?;
if mode.content_mut().is_empty() {
if let Some(close) = self.close.upgrade() {
close.select()?;
}
}
}
}
self.connect_character_page()?;
Ok(())
}
fn connect_character_page(&mut self) -> Result<()> {
let mode = &mut self.modes[self.current_mode];
match self.current_mode {
0 => {
let right_side = mode.right_side_mut().base();
let gloves: Arc<Button> = right_side.element("gloves")?;
let ring_0: Arc<Button> = right_side.element("ring_0")?;
let ring_2: Arc<Button> = right_side.element("ring_2")?;
let main_hand: Arc<Button> = right_side.element("main hand")?;
let content_grid: Arc<Grid> = mode.content_mut().base().element("content")?;
content_grid.connect(
ConnectDirection::East,
[
(0, gloves.selectable().unwrap()),
(1, ring_0.selectable().unwrap()),
(2, ring_2.selectable().unwrap()),
(3, main_hand.selectable().unwrap()),
],
)?;
}
1 => {
let right_side = mode.right_side_mut().base();
let reference: Arc<Button> = right_side.element("reference")?;
let first: Arc<Button> = right_side.element("first")?;
let content_grid: Arc<Grid> = mode.content_mut().base().element("content")?;
content_grid.connect(
ConnectDirection::East,
[
(1, reference.selectable().unwrap()),
(0, reference.selectable().unwrap()),
(3, first.selectable().unwrap()),
(2, first.selectable().unwrap()),
],
)?;
}
2 => {
// map tab is not implemented now
}
_ => unreachable!(),
}
Ok(())
}
fn setup_content_switch(
left_base: &GuiSnippet,
reference: Weak<CharacterWindow>,
) -> Result<()> {
let switch = {
let reference = reference.clone();
move |index| {
if let Some(menu) = reference.upgrade() {
let mut tabs = menu.tabs_mut();
let me = tabs.inventory::<A>();
if me.current_mode != index {
me.current_mode = index;
me.update_page(true)?;
}
}
Ok(())
}
};
let switch_to_items = Box::new({
let switch = switch.clone();
move || switch(0)
});
let switch_to_jewels = Box::new({
let switch = switch.clone();
move || switch(1)
});
let switch_to_maps = Box::new({
let switch = switch.clone();
move || switch(2)
});
left_base.set_click_callbacks(vec![
("items", switch_to_items),
("jewels", switch_to_jewels),
("maps", switch_to_maps),
])
}
}
impl<A: Ability + 'static> Page for InventoryPage<A> {
fn enable(&mut self) -> Result<Arc<Grid>> {
println!("enable InventoryPage");
for mode in self.modes.iter_mut() {
mode.content_mut().refresh()?;
}
self.update_page(false)?;
Ok(self.grid.clone())
}
fn disable(&mut self) -> Result<()> {
self.modes[self.current_mode]
.right_side_mut()
.disable(&self.engine, self.hero)
}
fn select(&self) -> Result<()> {
let mode = &self.modes[self.current_mode];
mode.content().select()?;
if mode.content().is_empty() {
if let Some(close) = self.close.upgrade() {
close.select()?;
}
}
Ok(())
}
fn next_tab(&mut self) -> Result<()> {
{
let mode = &mut self.modes[self.current_mode];
mode.content_mut().next_tab(&self.engine, self.hero)?;
mode.content_mut().select()?;
}
self.connect_character_page()
}
fn previous_tab(&mut self) -> Result<()> {
{
let mode = &mut self.modes[self.current_mode];
mode.content_mut().previous_tab(&self.engine, self.hero)?;
mode.content_mut().select()?;
}
self.connect_character_page()
}
fn event(&mut self, button: ControllerButton) -> Result<bool> {
Ok(match button {
ControllerButton::LeftStick => {
self.modes[self.current_mode]
.right_side_mut()
.disable(&self.engine, self.hero)?;
self.current_mode = (self.current_mode + 1) % self.modes.len();
self.update_page(true)?;
true
}
ControllerButton::RightStick => {
// check if jewel page is open
if self.current_mode == 1 {
if JewelRightSide::combine::<A>(&self.engine, self.hero)? {
JewelRightSide::clear(&self.engine);
self.update_page(true)?;
}
true
} else {
false
}
}
_ => false,
})
}
}

View file

@ -1,402 +0,0 @@
mod abilities;
mod character;
mod content;
mod inventory;
mod page_content;
mod traits;
use anyhow::Result;
use downcast_rs::{impl_downcast, Downcast};
use engine::prelude::*;
use rpg_components::{
components::{
crafting_materials::CraftingMaterials,
inventory::{Inventory, Storable},
},
items::ability_book::Ability,
};
use std::{
any::Any,
collections::HashMap,
ops::{Deref, DerefMut},
sync::{
atomic::{AtomicUsize, Ordering::SeqCst},
Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard,
},
};
use self::{abilities::AbilityPage, character::CharacterPage, inventory::InventoryPage};
trait Page: Any + Send + Sync + Downcast {
fn enable(&mut self) -> Result<Arc<Grid>>;
fn disable(&mut self) -> Result<()>;
fn select(&self) -> Result<()>;
fn next_tab(&mut self) -> Result<()>;
fn previous_tab(&mut self) -> Result<()>;
fn event(&mut self, button: ControllerButton) -> Result<bool>;
}
impl_downcast!(Page);
struct Tab<'a> {
index: usize,
tabs: RwLockReadGuard<'a, [Box<dyn Page>; 3]>,
}
impl<'a> Deref for Tab<'a> {
type Target = Box<dyn Page>;
fn deref(&self) -> &Self::Target {
&self.tabs[self.index]
}
}
struct TabMut<'a> {
index: usize,
tabs: RwLockWriteGuard<'a, [Box<dyn Page>; 3]>,
}
impl<'a> TabMut<'a> {
pub fn downcast_mut<T: Page>(&mut self) -> &mut T {
self.tabs[self.index].downcast_mut().unwrap()
}
}
impl<'a> Deref for TabMut<'a> {
type Target = Box<dyn Page>;
fn deref(&self) -> &Self::Target {
&self.tabs[self.index]
}
}
impl<'a> DerefMut for TabMut<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.tabs[self.index]
}
}
pub struct Tabs<'a> {
tabs: RwLockReadGuard<'a, [Box<dyn Page>; 3]>,
}
#[allow(unused)]
impl<'a> Tabs<'a> {
pub fn character(&mut self) -> &CharacterPage {
self.tabs[0].downcast_ref().unwrap()
}
pub fn inventory<A: Ability + 'static>(&mut self) -> &InventoryPage<A> {
self.tabs[1].downcast_ref().unwrap()
}
pub fn abilities<A: Ability + 'static>(&mut self) -> &AbilityPage<A> {
self.tabs[2].downcast_ref().unwrap()
}
}
pub struct TabsMut<'a> {
tabs: RwLockWriteGuard<'a, [Box<dyn Page>; 3]>,
}
#[allow(unused)]
impl<'a> TabsMut<'a> {
pub fn character(&mut self) -> &mut CharacterPage {
self.tabs[0].downcast_mut().unwrap()
}
pub fn inventory<A: Ability + 'static>(&mut self) -> &mut InventoryPage<A> {
self.tabs[1].downcast_mut().unwrap()
}
pub fn abilities<A: Ability + 'static>(&mut self) -> &mut AbilityPage<A> {
self.tabs[2].downcast_mut().unwrap()
}
}
pub struct CharacterWindow {
close: Box<dyn FutureStateChange>,
menu_gui: Arc<GuiBuilder>,
tab_content_grid: Arc<Grid>,
tooltips: Mutex<HashMap<String, Arc<GuiBuilder>>>,
tabs: RwLock<[Box<dyn Page>; 3]>,
tab: AtomicUsize,
engine: Arc<Engine>,
}
impl CharacterWindow {
pub fn new<A: Ability + 'static>(
engine: Arc<Engine>,
hero: Entity,
name: &str,
close: Box<dyn FutureStateChange>,
) -> Result<Arc<Self>> {
let menu_gui =
GuiBuilder::from_str(engine.gui_handler(), include_str!("../resources/menu.xml"))?;
let content_grid = menu_gui.element("tab_content")?;
let open_character_page: Arc<Button> = menu_gui.element("open_statistics")?;
let open_inventory_page: Arc<Button> = menu_gui.element("open_inventory")?;
let open_ability_page: Arc<Button> = menu_gui.element("open_abilities")?;
let close_button: Arc<Button> = menu_gui.element("close")?;
let character_window = Arc::new_cyclic(|me| CharacterWindow {
close,
menu_gui,
tab_content_grid: content_grid,
tooltips: Mutex::default(),
tabs: RwLock::new([
Box::new(CharacterPage::new(&engine, hero, name, me).unwrap()),
Box::new(
InventoryPage::<A>::new(&engine, hero, me.clone(), &close_button).unwrap(),
),
Box::new(AbilityPage::<A>::new(&engine, hero, me.clone(), &close_button).unwrap()),
]),
tab: AtomicUsize::new(0),
engine,
});
let open_tab = {
let weak_me = Arc::downgrade(&character_window);
move |index| {
if let Some(me) = weak_me.upgrade() {
me.tab.store(index, SeqCst);
me.tab_content_grid
.attach(me.tab_mut().enable()?, 0, 0, 1, 1)?;
me.tab().select()?;
}
Ok(())
}
};
open_character_page.set_callback({
let open_tab = open_tab.clone();
move || open_tab(0)
});
open_inventory_page.set_callback({
let open_tab = open_tab.clone();
move || open_tab(1)
});
open_ability_page.set_callback({
let open_tab = open_tab.clone();
move || open_tab(2)
});
Self::setup_menu(&character_window)?;
Ok(character_window)
}
pub fn event(&self, button: ControllerButton) -> Result<bool> {
self.tabs.write().unwrap()[self.tab.load(SeqCst)].event(button)
}
pub fn tabs<'a>(&'a self) -> Tabs<'a> {
Tabs {
tabs: self.tabs.read().unwrap(),
}
}
pub fn tabs_mut<'a>(&'a self) -> TabsMut<'a> {
TabsMut {
tabs: self.tabs.write().unwrap(),
}
}
fn tab(&self) -> Tab<'_> {
Tab {
index: self.tab.load(SeqCst),
tabs: self.tabs.read().unwrap(),
}
}
fn tab_mut(&self) -> TabMut<'_> {
TabMut {
index: self.tab.load(SeqCst),
tabs: self.tabs.write().unwrap(),
}
}
pub fn add_tooltip(&self, name: impl ToString, gui: impl Into<Arc<GuiBuilder>>) {
self.tooltips
.lock()
.unwrap()
.insert(name.to_string(), gui.into());
}
pub fn remove_tooltip(&self, name: impl ToString) {
self.tooltips.lock().unwrap().remove(&name.to_string());
}
pub fn salvage_from_inventory<A, F, S>(engine: &Engine, hero: Entity, f: F) -> Result<()>
where
A: Ability + 'static,
F: FnOnce(&mut Inventory<A>) -> S,
S: Storable,
{
engine.on_scene_mut(|scene| {
let entity = scene.entity_mut(hero)?;
let mut multi_mut = entity.multi_mut();
let crafting_materials = multi_mut.get::<CraftingMaterials>()?;
let inventory = multi_mut.get::<Inventory<A>>()?;
// remove callback
let storable = f(inventory);
crafting_materials.increment(storable.rarity());
Ok(())
})
}
}
impl TopLevelGui for CharacterWindow {
fn gui_traits(&self) -> &dyn GuiElementTraits {
self
}
fn top_gui(&self) -> Option<&dyn TopGui> {
Some(self)
}
fn elements(&self) -> Option<&HashMap<String, UiElement>> {
None
}
fn functionality(&self) -> Option<&dyn Functionality> {
None
}
fn enable(&self) -> Result<()> {
self.menu_gui.enable()?;
self.tab_content_grid
.attach(self.tab_mut().enable()?, 0, 0, 1, 1)?;
self.tab().select()?;
let close_button: Arc<Button> = self.menu_gui.element("close")?;
close_button.set_info_icon(&self.engine.controller_icon(ControllerButton::B)?)?;
let left_info: Arc<Icon> = self.menu_gui.element("left_info")?;
left_info.set_icon(&self.engine.controller_icon(ControllerButton::LeftButton)?)?;
let right_info: Arc<Icon> = self.menu_gui.element("right_info")?;
right_info.set_icon(&self.engine.controller_icon(ControllerButton::RightButton)?)?;
Ok(())
}
fn disable(&self) -> Result<()> {
self.menu_gui.disable()?;
Ok(())
}
}
impl GuiElementTraits for CharacterWindow {
fn gridable(&self) -> Option<&dyn Gridable> {
None
}
fn visibility(&self) -> Option<&dyn Visibility> {
None
}
fn downcast<'a>(&'a self) -> Option<GuiElement<'a>> {
None
}
}
impl TopGui for CharacterWindow {
fn decline(&self) -> Result<()> {
(self.close)()
}
fn next_tab(&self, second_level: bool) -> Result<()> {
match second_level {
false => {
// disable old tab
self.tab_mut().disable()?;
// add to tab index
self.tab.store((self.tab.load(SeqCst) + 1) % 3, SeqCst);
// update tab content
self.tab_content_grid
.attach(self.tab_mut().enable()?, 0, 0, 1, 1)?;
self.tab().select()?;
}
true => {
self.tab_mut().next_tab()?;
}
}
Ok(())
}
fn previous_tab(&self, second_level: bool) -> Result<()> {
match second_level {
false => {
// disable old tab
self.tab_mut().disable()?;
// subtract from tab index
if self.tab.load(SeqCst) == 0 {
self.tab.store(2, SeqCst);
} else {
self.tab.store(self.tab.load(SeqCst) - 1, SeqCst);
}
// update tab content
self.tab_content_grid
.attach(self.tab_mut().enable()?, 0, 0, 1, 1)?;
self.tab().select()?;
}
true => {
self.tab_mut().previous_tab()?;
}
}
Ok(())
}
}
impl CharacterWindow {
fn setup_menu(&self) -> Result<()> {
// let open_statistics = self.switch_tab(STATISTICS_TAB);
// let open_abilities = self.switch_tab(ABILITY_TAB);
// let open_inventory = self.switch_tab(INVENTORY_TAB);
let close = self.close.clone();
self.menu_gui.set_click_callbacks(
ClickCallbacks::default()
// .add("open_statistics", open_statistics)
// .add("open_abilities", open_abilities)
// .add("open_inventory", open_inventory)
.add("close", close)
.into(),
)?;
Ok(())
}
}

View file

@ -1,63 +0,0 @@
use crate::*;
use std::sync::Arc;
use crate::content::{Content, ContentUpdate, ContentWrapper};
use super::traits::*;
pub struct EmptyRightSide;
impl RightSide for EmptyRightSide {
fn refresh(&mut self, _engine: &Engine, _hero: Entity) -> Result<()> {
unreachable!()
}
fn base(&self) -> &Arc<GuiSnippet> {
unreachable!()
}
}
pub struct PageContent<A: Ability + 'static, T: Send + Sync> {
content: Content<A, T>,
tooltip: Arc<GuiSnippet>,
right_side: Box<dyn RightSide>,
}
impl<A: Ability + 'static, T: Send + Sync> PageContent<A, T> {
pub fn new<R>(content: Content<A, T>, tooltip: Arc<GuiSnippet>, right_side: R) -> Self
where
R: RightSide + 'static,
{
Self {
content,
tooltip,
right_side: Box::new(right_side),
}
}
}
impl<A: Ability + 'static, T: Send + Sync> PageContentWrapper for PageContent<A, T>
where
Content<A, T>: ContentUpdate,
{
fn content(&self) -> &dyn ContentWrapper {
&self.content
}
fn content_mut(&mut self) -> &mut dyn ContentWrapper {
&mut self.content
}
fn tooltip(&self) -> &Arc<GuiSnippet> {
&self.tooltip
}
fn right_side(&self) -> &dyn RightSide {
&*self.right_side
}
fn right_side_mut(&mut self) -> &mut dyn RightSide {
&mut *self.right_side
}
}

View file

@ -1,24 +0,0 @@
use crate::*;
use std::sync::Arc;
use crate::content::ContentWrapper;
#[allow(unused)]
pub trait PageContentWrapper: Send + Sync {
fn content(&self) -> &dyn ContentWrapper;
fn content_mut(&mut self) -> &mut dyn ContentWrapper;
fn tooltip(&self) -> &Arc<GuiSnippet>;
fn right_side(&self) -> &dyn RightSide;
fn right_side_mut(&mut self) -> &mut dyn RightSide;
}
pub trait RightSide: Send + Sync {
fn refresh(&mut self, engine: &Engine, hero: Entity) -> Result<()>;
fn disable(&mut self, _engine: &Engine, _hero: Entity) -> Result<()> {
Ok(())
}
fn base(&self) -> &Arc<GuiSnippet>;
}

View file

@ -5,9 +5,7 @@ authors = ["hodasemi <superschneider@t-online.de>"]
edition = "2024"
[dependencies]
utilities = { workspace = true }
audio = { workspace = true, optional = true }
assetpath = { workspace = true }
anyhow = { workspace = true }
presentation = { path = "../presentation" }

View file

@ -6,8 +6,6 @@ edition = "2024"
[dependencies]
# needed
destructure_traitobject = { workspace = true }
itertools = { workspace = true }
serde = { workspace = true }
ron = { workspace = true }
paste = { workspace = true }
@ -20,8 +18,6 @@ assetpath = { workspace = true }
shaderc = { workspace = true }
config_handler = { path = "../ConfigHandler" }
lua-wrapper = { path = "../lua-wrapper" }
scene_update_macros = { path = "../scene_update_macros" }
asset = { path = "../asset" }
loading_screen = { path = "../loading-screen" }
context = { path = "../context", features = ["bundle_sdl2", "sound"] }

View file

@ -14,11 +14,6 @@ pub use crate::engine::{
engine_settings::*,
};
pub use serde::{
Deserialize, Deserializer, Serialize, Serializer,
ser::{SerializeMap, SerializeSeq, SerializeStruct},
};
pub use ecs::*;
pub use ron;

View file

@ -2,6 +2,7 @@ use crate::prelude::*;
use asset::GltfBoundingBox as GltfBB;
use serde::{Deserialize, Serialize};
use utilities::prelude::cgmath::Vector3;
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]

View file

@ -1,6 +1,7 @@
use cgmath::{Matrix2, Rad, Vector2, Vector3, Zero};
use cgmath::Matrix4;
use serde::{Deserialize, Serialize};
use crate::prelude::*;
use anyhow::Result;

View file

@ -2,6 +2,7 @@ use crate::prelude::*;
use asset::*;
use serde::{Deserialize, Serialize};
use utilities::prelude::cgmath::{Deg, InnerSpace, Matrix4, vec3};
use std::sync::{Arc, Mutex, MutexGuard};

View file

@ -2,6 +2,7 @@ use std::{collections::HashMap, time::Duration};
use anyhow::Result;
use engine::prelude::*;
use serde::{Deserialize, Serialize};
use crate::entityparser::AnimationData;

View file

@ -1,6 +1,6 @@
use std::fmt;
use engine::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum EntityTags {

View file

@ -4,6 +4,7 @@ use assetpath::AssetPath;
use anyhow::Result;
use engine::prelude::*;
use serde::{Deserialize, Serialize};
use crate::animation_info::AnimationType;

View file

@ -1,13 +0,0 @@
[package]
name = "map"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = { workspace = true }
rusqlite = { workspace = true }
assetpath = { workspace = true }
destructure_traitobject = { workspace = true }
engine = { path = "../engine" }
ecs = { path = "../ecs" }

View file

@ -1,89 +0,0 @@
// engine
use engine::prelude::*;
// std
use std::mem;
use std::sync::{Arc, Mutex, RwLock};
use super::map_db::MapDataBase;
// sql
use anyhow::Result;
pub struct AsyncDBAccess {
sql: Arc<Mutex<MapDataBase>>,
async_thread: RwLock<AsyncThread<Result<()>>>,
queue: RwLock<Vec<DispatchCommand>>,
}
impl AsyncDBAccess {
pub fn new(map_db: MapDataBase) -> AsyncDBAccess {
AsyncDBAccess {
sql: Arc::new(Mutex::new(map_db)),
async_thread: RwLock::new(AsyncThread::spawn(move || Ok(()))),
queue: RwLock::new(Vec::new()),
}
}
pub fn add<F>(&self, f: F) -> Result<()>
where
F: FnOnce(&MapDataBase) -> Result<()> + Send + Sync + 'static,
{
let sql_clone = self.sql.clone();
self.queue
.write()
.unwrap()
.push(DispatchCommand::new(sql_clone, f));
Ok(())
}
pub fn dispatch(&self) -> Result<()> {
let mut async_thread = self.async_thread.write().unwrap();
let mut queue = self.queue.write().unwrap();
// check if async thread is returned
if async_thread.check()? {
// check if there are calls to be dispatched
if !queue.is_empty() {
// move calls into queue to call it in a thread
let mut dispatch_queue = Vec::new();
mem::swap(&mut dispatch_queue, &mut queue);
*async_thread = AsyncThread::spawn(move || {
for dispatch_call in dispatch_queue {
dispatch_call.call()?;
}
Ok(())
});
}
}
Ok(())
}
}
unsafe impl Sync for AsyncDBAccess {}
struct DispatchCommand {
sql: Arc<Mutex<MapDataBase>>,
command: Box<dyn FnOnce(&MapDataBase) -> Result<()> + Send + Sync>,
}
impl DispatchCommand {
pub fn new<F>(sql: Arc<Mutex<MapDataBase>>, command: F) -> Self
where
F: FnOnce(&MapDataBase) -> Result<()> + Send + Sync + 'static,
{
DispatchCommand {
sql,
command: Box::new(command),
}
}
pub fn call(self) -> Result<()> {
let sql_lock = self.sql.lock().unwrap();
(self.command)(&*sql_lock)
}
}

View file

@ -1,8 +0,0 @@
mod async_db;
pub mod map;
pub mod mapdata;
mod surface;
mod tile;
mod map_db;

File diff suppressed because it is too large Load diff

View file

@ -1,943 +0,0 @@
use std::{collections::HashMap, path::Path};
use anyhow::Result;
use engine::prelude::cgmath::{Rad, Vector2};
// sql
use rusqlite::{
types::{ToSql, Type},
Connection, Error,
};
use super::mapdata::Coordinate;
struct DBMetaInfo {
name: String,
version: String,
width: u32,
height: u32,
}
pub struct DBTextureInfo {
pub index: u32,
pub name: String,
}
pub struct DBEntityInfo {
pub x_tile: u32,
pub y_tile: u32,
pub world: Vector2<f32>,
pub rotation: Rad<f32>,
pub entity: String,
}
pub struct DBNPCSpawnInfo {
pub x_tile: u32,
pub y_tile: u32,
pub radius: f32,
pub min_count: u32,
pub max_count: u32,
pub normal_npc: String,
pub elite_npc: String,
}
pub struct DBBossSpawnInfo {
pub x_tile: u32,
pub y_tile: u32,
pub boss: String,
}
#[derive(Clone)]
pub struct EntityDBType {
table_name: String,
}
impl EntityDBType {
pub fn entity() -> Self {
Self {
table_name: "entitypositions".to_string(),
}
}
pub fn spawn_location() -> Self {
Self {
table_name: "spawnlocations".to_string(),
}
}
pub fn leave_location() -> Self {
Self {
table_name: "leavelocations".to_string(),
}
}
}
pub struct MapDBVersions;
#[allow(unused)]
impl MapDBVersions {
const VERSION_0_1_0: &'static str = "0.1.0";
const VERSION_0_1_1: &'static str = "0.1.1";
const VERSION_0_2_0: &'static str = "0.2.0";
}
pub struct MapDataBase {
sql: Connection,
name: String,
version: String,
width: u32,
height: u32,
}
impl MapDataBase {
pub fn new(path: impl AsRef<Path>, name: &str, width: u32, height: u32) -> Result<Self> {
let mut me = Self::open_file(path)?;
me.create_tables()?;
me.init_default_values(name, width, height)?;
me.read_meta_data()?;
Ok(me)
}
pub fn load(path: impl AsRef<Path>) -> Result<Self> {
let mut me = Self::open_file(path)?;
me.check_version()?;
me.read_meta_data()?;
Ok(me)
}
fn open_file(path: impl AsRef<Path>) -> Result<Self> {
Ok(Self {
sql: Connection::open(path)?,
name: Default::default(),
version: Default::default(),
width: 0,
height: 0,
})
}
}
impl MapDataBase {
pub fn name(&self) -> &str {
&self.name
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
}
impl MapDataBase {
fn init_default_values(&self, name: &str, width: u32, height: u32) -> Result<()> {
// meta data
self.sql.execute(
"INSERT INTO meta (name, version, width, height)
VALUES (?1, ?2, ?3, ?4)",
&[
&name.to_string() as &dyn ToSql,
&MapDBVersions::VERSION_0_1_1.to_string() as &dyn ToSql,
&width,
&height,
],
)?;
// tiles
let tile_count = width * height;
let mut insert_tiles = "INSERT INTO tiles (id, texture) VALUES ".to_string();
for i in 0..tile_count {
if i != 0 {
insert_tiles.push(',');
}
insert_tiles.push_str(format!("({}, 1)", i + 1).as_str());
}
self.sql.execute(insert_tiles.as_str(), [])?;
// heights
let height_point_count = (width + 1) * (height + 1);
let mut insert_height_points = "INSERT INTO heights (id, height) VALUES".to_string();
for i in 0..height_point_count {
if i != 0 {
insert_height_points.push(',');
}
insert_height_points.push_str(format!("({}, 0.0)", i + 1).as_str());
}
self.sql.execute(insert_height_points.as_str(), [])?;
// textures
self.sql.execute(
"INSERT INTO textures (id, name) VALUES (1, ?1)",
&[&"grass"],
)?;
Ok(())
}
fn create_tables(&self) -> Result<()> {
// --------------------------------------------------------
// -------- meta information (name, width, height) --------
// --------------------------------------------------------
self.create_meta_table()?;
// --------------------------------------------------------
// --- tiles information (which tile has which texture) ---
// --------------------------------------------------------
self.create_tiles_table()?;
// --------------------------------------------------------
// ------------ textures (name of the texture) ------------
// --------------------------------------------------------
self.create_textures_table()?;
// --------------------------------------------------------
// ----------------------- heights ------------------------
// --------------------------------------------------------
self.create_heights_table()?;
// --------------------------------------------------------
// ------------------- entity positions -------------------
// --------------------------------------------------------
self.create_entity_positions_table()?;
self.create_spawn_table()?;
self.create_leave_table()?;
self.create_mob_spawn_table()?;
self.create_boss_spawn_table()?;
Ok(())
}
fn create_meta_table(&self) -> Result<()> {
self.sql.execute(
"CREATE TABLE IF NOT EXISTS meta (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
version TEXT NOT NULL,
width INTEGER NOT NULL,
height INTEGER NOT NULL
)",
[],
)?;
self.sql.execute("DELETE FROM meta", [])?;
Ok(())
}
fn create_tiles_table(&self) -> Result<()> {
self.sql.execute(
"CREATE TABLE IF NOT EXISTS tiles (
id INTEGER PRIMARY KEY,
texture INTEGER NOT NULL
)",
[],
)?;
self.sql.execute("DELETE FROM tiles", [])?;
Ok(())
}
fn create_textures_table(&self) -> Result<()> {
self.sql.execute(
"CREATE TABLE IF NOT EXISTS textures (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
)",
[],
)?;
self.sql.execute("DELETE FROM textures", [])?;
Ok(())
}
fn create_heights_table(&self) -> Result<()> {
self.sql.execute(
"CREATE TABLE IF NOT EXISTS heights (
id INTEGER PRIMARY KEY,
height REAL NOT NULL
)",
[],
)?;
self.sql.execute("DELETE FROM heights", [])?;
Ok(())
}
fn create_entity_positions_table(&self) -> Result<()> {
self.sql.execute(
"CREATE TABLE IF NOT EXISTS entitypositions (
x_tile INTEGER NOT NULL,
y_tile INTEGER NOT NULL,
entity_id TEXT NOT NULL,
x_world REAL NOT NULL,
y_world REAL NOT NULL,
rotation REAL NOT NULL,
primary key (x_tile, y_tile)
)",
[],
)?;
self.sql.execute("DELETE FROM entitypositions", [])?;
Ok(())
}
fn create_spawn_table(&self) -> Result<()> {
self.sql.execute(
"CREATE TABLE IF NOT EXISTS spawnlocations (
x_tile INTEGER NOT NULL,
y_tile INTEGER NOT NULL,
entity_id TEXT NOT NULL,
x_world REAL NOT NULL,
y_world REAL NOT NULL,
rotation REAL NOT NULL,
primary key (x_tile, y_tile)
)",
[],
)?;
self.sql.execute("DELETE FROM spawnlocations", [])?;
Ok(())
}
fn create_leave_table(&self) -> Result<()> {
self.sql.execute(
"CREATE TABLE IF NOT EXISTS leavelocations (
x_tile INTEGER NOT NULL,
y_tile INTEGER NOT NULL,
entity_id TEXT NOT NULL,
x_world REAL NOT NULL,
y_world REAL NOT NULL,
rotation REAL NOT NULL,
primary key (x_tile, y_tile)
)",
[],
)?;
self.sql.execute("DELETE FROM leavelocations", [])?;
Ok(())
}
fn create_mob_spawn_table(&self) -> Result<()> {
self.sql.execute(
"CREATE TABLE IF NOT EXISTS mobspawnlocations (
x_tile INTEGER NOT NULL,
y_tile INTEGER NOT NULL,
radius REAL NOT NULL,
normal_npc TEXT,
elite_npc TEXT,
min_count INTEGER,
max_count INTEGER,
primary key (x_tile, y_tile)
)",
[],
)?;
self.sql.execute("DELETE FROM mobspawnlocations", [])?;
Ok(())
}
fn create_boss_spawn_table(&self) -> Result<()> {
self.sql.execute(
"CREATE TABLE IF NOT EXISTS bossspawnlocations (
x_tile INTEGER NOT NULL,
y_tile INTEGER NOT NULL,
boss TEXT,
primary key (x_tile, y_tile)
)",
[],
)?;
self.sql.execute("DELETE FROM bossspawnlocations", [])?;
Ok(())
}
}
impl MapDataBase {
fn read_meta_data(&mut self) -> Result<()> {
let info = self.sql.query_row(
"SELECT name, version, width, height FROM meta WHERE id=1",
[],
|row| {
Ok(DBMetaInfo {
name: row.get(0)?,
version: row.get(1)?,
width: row.get(2)?,
height: row.get(3)?,
})
},
)?;
self.name = info.name;
self.version = info.version;
self.width = info.width;
self.height = info.height;
Ok(())
}
pub fn read_tiles(&self) -> Result<HashMap<u32, u32>> {
let mut tiles_stmt = self.sql.prepare("SELECT * FROM tiles")?;
let mapped_tiles = tiles_stmt.query_map([], |row| Ok((row.get(0)?, row.get(1)?)))?;
let mut tiles = HashMap::new();
for row in mapped_tiles {
let (id, tile_id) = row?;
tiles.insert(id, tile_id);
}
Ok(tiles)
}
pub fn read_heights(&self) -> Result<HashMap<u32, f64>> {
let mut height_stmt = self.sql.prepare("SELECT * FROM heights")?;
let mapped_heights = height_stmt.query_map([], |row| Ok((row.get(0)?, row.get(1)?)))?;
let mut heights = HashMap::new();
for row in mapped_heights {
let (id, height) = row?;
heights.insert(id, height);
}
Ok(heights)
}
pub fn read_textures(&self) -> Result<Vec<DBTextureInfo>> {
let mut texture_stmt = self.sql.prepare("SELECT * FROM textures")?;
let mapped_textures = texture_stmt.query_map([], |row| {
Ok(DBTextureInfo {
index: row.get(0)?,
name: row.get(1)?,
})
})?;
let mut textures = Vec::new();
for row in mapped_textures {
textures.push(row?);
}
Ok(textures)
}
fn read_ent_pos(&self, table_name: &str) -> Result<Vec<DBEntityInfo>> {
let mut entity_pos_stmt = self.sql.prepare(&format!("SELECT * FROM {}", table_name))?;
let mapped_entity_pos = entity_pos_stmt.query_map([], |row| {
Ok(DBEntityInfo {
x_tile: row.get(0)?,
y_tile: row.get(1)?,
world: Vector2::new(row.get(3)?, row.get(4)?),
rotation: Rad(row.get(5)?),
entity: row.get(2)?,
})
})?;
let mut entity_positions = Vec::new();
for row in mapped_entity_pos {
entity_positions.push(row?);
}
Ok(entity_positions)
}
pub fn read_entities(&self) -> Result<Vec<DBEntityInfo>> {
self.read_ent_pos("entitypositions")
}
pub fn read_spawn_locations(&self) -> Result<Vec<DBEntityInfo>> {
self.read_ent_pos("spawnlocations")
}
pub fn read_leave_locations(&self) -> Result<Vec<DBEntityInfo>> {
self.read_ent_pos("leavelocations")
}
pub fn read_mob_spawn_locations(&self) -> Result<Vec<DBNPCSpawnInfo>> {
let mut entity_pos_stmt = self.sql.prepare("SELECT * FROM mobspawnlocations")?;
let mapped_entity_pos = entity_pos_stmt.query_map([], |row| {
Ok(DBNPCSpawnInfo {
x_tile: row.get(0)?,
y_tile: row.get(1)?,
radius: row.get(2)?,
min_count: row.get(5)?,
max_count: row.get(6)?,
normal_npc: match row.get(3) {
Ok(npc) => npc,
Err(err) => match &err {
Error::InvalidColumnType(_, _, row_type) => match row_type {
Type::Null => String::new(),
_ => return Err(err),
},
_ => return Err(err),
},
},
elite_npc: match row.get(4) {
Ok(npc) => npc,
Err(err) => match &err {
Error::InvalidColumnType(_, _, row_type) => match row_type {
Type::Null => String::new(),
_ => return Err(err),
},
_ => return Err(err),
},
},
})
})?;
let mut entity_positions = Vec::new();
for row in mapped_entity_pos {
entity_positions.push(row?);
}
Ok(entity_positions)
}
pub fn read_boss_spawn_locations(&self) -> Result<Vec<DBBossSpawnInfo>> {
let mut boss_pos_stmt = self.sql.prepare("SELECT * FROM bossspawnlocations")?;
let mapped_boss_pos = boss_pos_stmt.query_map([], |row| {
Ok(DBBossSpawnInfo {
x_tile: row.get(0)?,
y_tile: row.get(1)?,
boss: match row.get(2) {
Ok(boss) => boss,
Err(err) => match &err {
Error::InvalidColumnType(_, _, row_type) => match row_type {
Type::Null => String::new(),
_ => return Err(err),
},
_ => return Err(err),
},
},
})
})?;
let mut boss_positions = Vec::new();
for row in mapped_boss_pos {
boss_positions.push(row?);
}
Ok(boss_positions)
}
}
impl MapDataBase {
pub fn insert_npc_spawn(
&self,
tile: (u32, u32),
radius: f32,
min_count: u32,
max_count: u32,
) -> Result<()> {
let params: &[&dyn ToSql] = &[&tile.0, &tile.1, &(radius as f64), &min_count, &max_count];
self.sql.execute(
"INSERT INTO mobspawnlocations (x_tile, y_tile, radius, min_count, max_count)
VALUES (?1, ?2, ?3, ?4, ?5)",
params,
)?;
Ok(())
}
pub fn remove_npc_spawn(&self, tile: (u32, u32)) -> Result<()> {
self.sql.execute(
"DELETE FROM mobspawnlocations
WHERE x_tile=?1 AND y_tile=?2",
&[&tile.0, &tile.1],
)?;
Ok(())
}
pub fn insert_boss_spawn(&self, tile: (u32, u32)) -> Result<()> {
self.sql.execute(
"INSERT INTO bossspawnlocations (x_tile, y_tile)
VALUES (?1, ?2)",
[&tile.0, &tile.1],
)?;
Ok(())
}
pub fn remove_boss_spawn(&self, tile: (u32, u32)) -> Result<()> {
self.sql.execute(
"DELETE FROM bossspawnlocations
WHERE x_tile=?1 AND y_tile=?2",
&[&tile.0, &tile.1],
)?;
Ok(())
}
pub fn update_boss_name(&self, coordinate: &Coordinate, name: &str) -> Result<()> {
self.sql.execute(
"UPDATE bossspawnlocations
SET boss=?3
WHERE x_tile=?1 AND y_tile=?2",
&[&coordinate.x, &coordinate.y, &name as &dyn ToSql],
)?;
Ok(())
}
pub fn insert_entity(
&self,
entity_db_type: EntityDBType,
tile: (u32, u32),
entity_name: String,
position: Vector2<f32>,
rotation: Rad<f32>,
) -> Result<()> {
self.sql.execute(
&format!(
"INSERT INTO {} (x_tile, y_tile, entity_id, x_world, y_world, rotation)
VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
entity_db_type.table_name
),
&[
&tile.0,
&tile.1,
&entity_name as &dyn ToSql,
&(position.x as f64),
&(position.y as f64),
&(rotation.0 as f64),
],
)?;
Ok(())
}
pub fn remove_entity(&self, entity_db_type: EntityDBType, tile: (u32, u32)) -> Result<()> {
self.sql.execute(
&format!(
"DELETE FROM {}
WHERE x_tile=?1 AND y_tile=?2",
entity_db_type.table_name
),
&[&tile.0, &tile.1],
)?;
Ok(())
}
pub fn set_texture(&self, tile_index: u32, texture_index: u32) -> Result<()> {
self.sql.execute(
"UPDATE tiles
SET texture=?1
WHERE id=?2",
&[&texture_index, &tile_index],
)?;
Ok(())
}
pub fn insert_new_texture(&self, texture_index: u32, texture_name: String) -> Result<()> {
self.sql.execute(
"INSERT INTO textures (id, name)
VALUES (?1, ?2)",
&[&texture_index, &texture_name as &dyn ToSql],
)?;
Ok(())
}
pub fn remove_texture(&self, texture_index: u32) -> Result<()> {
self.sql.execute(
"DELETE FROM textures
WHERE id=?1",
&[&texture_index],
)?;
Ok(())
}
pub fn update_texture_index(&self, broken_index: u32, last_index: u32) -> Result<()> {
self.sql.execute(
"UPDATE tiles
SET texture=?1
WHERE texture=?2",
&[&broken_index, &last_index],
)?;
self.sql.execute(
"UPDATE textures
SET id=?1
WHERE id=?2",
&[&broken_index, &last_index],
)?;
Ok(())
}
pub fn update_height(&self, index: u32, height: f32) -> Result<()> {
self.sql.execute(
"UPDATE heights
SET height=?1
WHERE id=?2",
&[&(height as f64), &index as &dyn ToSql],
)?;
Ok(())
}
pub fn update_npc_spawn_radius(&self, coordinate: &Coordinate, radius: f32) -> Result<()> {
self.sql.execute(
"UPDATE mobspawnlocations
SET radius=?3
WHERE x_tile=?1 AND y_tile=?2",
&[&coordinate.x, &coordinate.y, &(radius as f64) as &dyn ToSql],
)?;
Ok(())
}
pub fn update_npc_spawn_min_npc_count(
&self,
coordinate: &Coordinate,
min_npc_count: u32,
) -> Result<()> {
self.sql.execute(
"UPDATE mobspawnlocations
SET min_count=?3
WHERE x_tile=?1 AND y_tile=?2",
&[&coordinate.x, &coordinate.y, &min_npc_count],
)?;
Ok(())
}
pub fn update_npc_spawn_max_npc_count(
&self,
coordinate: &Coordinate,
max_npc_count: u32,
) -> Result<()> {
self.sql.execute(
"UPDATE mobspawnlocations
SET max_count=?3
WHERE x_tile=?1 AND y_tile=?2",
&[&coordinate.x, &coordinate.y, &max_npc_count],
)?;
Ok(())
}
pub fn update_npc_spawn_normal_npc(
&self,
coordinate: &Coordinate,
normal_npc: String,
) -> Result<()> {
self.sql.execute(
"UPDATE mobspawnlocations
SET normal_npc=?3
WHERE x_tile=?1 AND y_tile=?2",
&[&coordinate.x, &coordinate.y, &normal_npc as &dyn ToSql],
)?;
Ok(())
}
pub fn update_npc_spawn_elite_npc(
&self,
coordinate: &Coordinate,
elite_npc: String,
) -> Result<()> {
self.sql.execute(
"UPDATE mobspawnlocations
SET elite_npc=?3
WHERE x_tile=?1 AND y_tile=?2",
&[&coordinate.x, &coordinate.y, &elite_npc as &dyn ToSql],
)?;
Ok(())
}
}
impl MapDataBase {
fn get_version(&self) -> Result<String, rusqlite::Error> {
Ok(
match self
.sql
.query_row("SELECT version FROM meta WHERE id=1", [], |row| row.get(0))
{
Ok(version) => version,
Err(err) => {
// everything before version 0.1.1 did not have a version and can be considered as version 0.1.0
if let Error::SqliteFailure(_sqlite_error, msg) = &err {
if let Some(msg) = msg {
if msg.contains("version") {
return Ok(MapDBVersions::VERSION_0_1_0.to_string());
}
}
}
return Err(err);
}
},
)
}
fn check_version(&self) -> Result<()> {
let mut version = self.get_version()?;
// incrementally upgrade to the current version
loop {
match version.as_str() {
MapDBVersions::VERSION_0_1_0 => {
// update mob spawn table
{
// Add new columns to the table
self.sql.execute(
"ALTER TABLE mobspawnlocations
ADD COLUMN radius REAL",
[],
)?;
self.sql.execute(
"ALTER TABLE mobspawnlocations
ADD COLUMN normal_npc TEXT",
[],
)?;
self.sql.execute(
"ALTER TABLE mobspawnlocations
ADD COLUMN elite_npc TEXT",
[],
)?;
self.sql.execute(
"ALTER TABLE mobspawnlocations
ADD COLUMN min_count INTEGER",
[],
)?;
self.sql.execute(
"ALTER TABLE mobspawnlocations
ADD COLUMN max_count INTEGER",
[],
)?;
// create default values that were used at this time
let radius: f32 = 5.0;
let min_count: u32 = 3;
let max_count: u32 = 7;
// fill new data
self.sql.execute(
"UPDATE mobspawnlocations
SET
radius = ?1,
min_count = ?2,
max_count = ?3;",
&[&radius as &dyn ToSql, &min_count, &max_count],
)?;
}
// update meta table, it is possible that version 0.1.0 has no version inside the meta table
// thus the old information is read, the table is recreated and the data is inserted back into it
{
// read old meta data table
let (name, width, height): (String, u32, u32) = self.sql.query_row(
"SELECT name, width, height FROM meta WHERE id=1",
[],
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
)?;
// remove old meta table
self.sql.execute("DROP TABLE meta", [])?;
// create new meta table
self.create_meta_table()?;
// insert meta information back into table
self.sql.execute(
"INSERT INTO meta (name, version, width, height)
VALUES (?1, ?2, ?3, ?4)",
&[
&name,
&MapDBVersions::VERSION_0_1_1.to_string() as &dyn ToSql,
&width,
&height,
],
)?;
}
// update version string
version = MapDBVersions::VERSION_0_1_1.to_string();
}
MapDBVersions::VERSION_0_1_1 => {
// add boss spawn table
self.create_boss_spawn_table()?;
// update version in meta table
self.sql.execute(
"UPDATE meta
SET
version = ?1;",
[&MapDBVersions::VERSION_0_2_0.to_string() as &dyn ToSql],
)?;
// update version string
version = MapDBVersions::VERSION_0_2_0.to_string();
}
// break at the current version
MapDBVersions::VERSION_0_2_0 => {
break;
}
_ => unreachable!("version string {} not known", version),
}
}
Ok(())
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,113 +0,0 @@
use anyhow::Result;
use engine::prelude::*;
use std::collections::HashMap;
pub struct Surface {
point_cloud: Vec<Vec<cgmath::Vector3<f32>>>,
texture_ids: Vec<Vec<u32>>,
}
impl Surface {
pub fn new(
width: u32,
height: u32,
point_cloud: Vec<Vec<cgmath::Vector3<f32>>>,
tiles: HashMap<u32, u32>,
) -> Result<Surface> {
let texture_ids = {
let mut texture_ids = Vec::with_capacity(width as usize);
for x in 0..width {
let mut tmp_y_ids = Vec::with_capacity(height as usize);
for y in 0..height {
let index = (x + 1) + (y * width);
// index - 1 since sql starts indices at 1
let texture_id = match tiles.get(&index) {
Some(tex_id) => tex_id - 1,
None => {
return Err(anyhow::Error::msg(format!(
"Index ({}) in tiles_map (Surface) not found",
index
)))
}
};
tmp_y_ids.push(texture_id);
}
texture_ids.push(tmp_y_ids);
}
texture_ids
};
Ok(Surface {
point_cloud,
texture_ids,
})
}
pub fn texture_id(&self, x: u32, y: u32) -> u32 {
self.texture_ids[x as usize][y as usize]
}
pub fn set_texture_id(&mut self, x: u32, y: u32, texture_id: u32) {
self.texture_ids[x as usize][y as usize] = texture_id;
}
pub fn point(&self, x: u32, y: u32) -> cgmath::Vector3<f32> {
self.point_cloud[x as usize][y as usize]
}
pub fn change_height(&mut self, x: u32, y: u32, height: f32) -> f32 {
self.point_cloud[x as usize][y as usize].z += height;
self.point_cloud[x as usize][y as usize].z
}
/*
pub fn check_plain_2_fixed(
&mut self,
x1_fixed: u32,
y1_fixed: u32,
x2_fixed: u32,
y2_fixed: u32,
) {
let ux1 = x1_fixed as usize;
let uy1 = y1_fixed as usize;
let ux2 = x2_fixed as usize;
let uy2 = y2_fixed as usize;
// fixed points
let bp1 = self.point_cloud[ux1][uy1].try_borrow()?;
let p1 = bp1.deref();
let bp2 = self.point_cloud[ux2][uy2].try_borrow()?;
let p2 = bp2.deref();
// points that need to be checked
let bp3 = self.point_cloud[ux1][uy2].try_borrow()?;
let p3 = bp3.deref();
let bp4 = self.point_cloud[ux2][uy1].try_borrow()?;
let p4 = bp4.deref();
let p12 = p1 - p2;
let p13 = p1 - p3;
let p14 = p1 - p4;
let cross = p12.cross(p13);
let dot = cgmath::dot(cross, p14);
if dot == 0.0 {
// everything is alright
return;
}
// TODO: elevate p3 and p4 to correct height
}
*/
}

View file

@ -1,191 +0,0 @@
use engine::prelude::cgmath::{Vector2, Vector3};
use super::{mapdata::Coordinate, surface::Surface};
#[derive(Clone, Copy, Debug)]
pub enum SlopeDirection {
LeftToRight,
RightToLeft,
TopToBottom,
BottomToTop,
DiagonalBottomLeftToTopRight,
DiagonalTopLeftToBottomRight,
DiagonalTopRightToBottomLeft,
DiagonalBottomRightToTopLeft,
}
impl SlopeDirection {
pub fn from_direction(direction: Vector2<f32>) -> SlopeDirection {
if direction.x < 0.0 {
if direction.y < 0.0 {
if Self::almost_eq(direction.x, direction.y) {
SlopeDirection::DiagonalTopRightToBottomLeft
} else if direction.x < direction.y {
SlopeDirection::RightToLeft
} else {
SlopeDirection::TopToBottom
}
} else if Self::almost_eq(direction.x.abs(), direction.y) {
SlopeDirection::DiagonalBottomRightToTopLeft
} else if direction.x.abs() < direction.y {
SlopeDirection::BottomToTop
} else {
SlopeDirection::RightToLeft
}
} else if direction.y < 0.0 {
if Self::almost_eq(direction.x, direction.y.abs()) {
SlopeDirection::DiagonalTopLeftToBottomRight
} else if direction.x < direction.y.abs() {
SlopeDirection::TopToBottom
} else {
SlopeDirection::LeftToRight
}
} else if Self::almost_eq(direction.x, direction.y) {
SlopeDirection::DiagonalBottomLeftToTopRight
} else if direction.x < direction.y {
SlopeDirection::BottomToTop
} else {
SlopeDirection::LeftToRight
}
}
#[inline]
fn almost_eq(f1: f32, f2: f32) -> bool {
let epsilon = 0.001;
(f1 + epsilon > f2) && (f1 - epsilon < f2)
}
}
#[derive(Debug)]
pub struct Tile {
pub left_bottom: Vector3<f32>,
pub right_bottom: Vector3<f32>,
pub left_top: Vector3<f32>,
pub right_top: Vector3<f32>,
}
impl Tile {
pub fn new(base_coord: Coordinate, surface: &Surface) -> Tile {
Tile {
left_bottom: surface.point(base_coord.x, base_coord.y),
right_bottom: surface.point(base_coord.x + 1, base_coord.y),
left_top: surface.point(base_coord.x, base_coord.y + 1),
right_top: surface.point(base_coord.x + 1, base_coord.y + 1),
}
}
/// checks for the slope in given direction with given threshold
///
/// returns `true` when slope is below threshold, `false` otherwise
pub fn check_slope(
&self,
threshold: f32,
direction: SlopeDirection,
increase_only: bool,
) -> bool {
match direction {
SlopeDirection::LeftToRight => {
let bottom_difference = self.left_bottom.z - self.right_bottom.z;
// check bottom slope against threshold
if !Self::check_directed_slope(bottom_difference, threshold, increase_only) {
return false;
}
let top_difference = self.left_top.z - self.right_top.z;
//check top slope against threshold
if !Self::check_directed_slope(top_difference, threshold, increase_only) {
return false;
}
}
SlopeDirection::RightToLeft => {
let bottom_difference = self.right_bottom.z - self.left_bottom.z;
if !Self::check_directed_slope(bottom_difference, threshold, increase_only) {
return false;
}
let top_difference = self.right_top.z - self.left_top.z;
if !Self::check_directed_slope(top_difference, threshold, increase_only) {
return false;
}
}
SlopeDirection::BottomToTop => {
let left_difference = self.left_bottom.z - self.left_top.z;
if !Self::check_directed_slope(left_difference, threshold, increase_only) {
return false;
}
let right_difference = self.right_bottom.z - self.right_top.z;
if !Self::check_directed_slope(right_difference, threshold, increase_only) {
return false;
}
}
SlopeDirection::TopToBottom => {
let left_difference = self.left_top.z - self.left_bottom.z;
if !Self::check_directed_slope(left_difference, threshold, increase_only) {
return false;
}
let right_difference = self.right_top.z - self.right_bottom.z;
if !Self::check_directed_slope(right_difference, threshold, increase_only) {
return false;
}
}
SlopeDirection::DiagonalBottomLeftToTopRight => {
let diagonal_difference = self.left_bottom.z - self.right_top.z;
if !Self::check_directed_slope(diagonal_difference, threshold, increase_only) {
return false;
}
}
SlopeDirection::DiagonalBottomRightToTopLeft => {
let diagonal_difference = self.right_bottom.z - self.left_top.z;
if !Self::check_directed_slope(diagonal_difference, threshold, increase_only) {
return false;
}
}
SlopeDirection::DiagonalTopLeftToBottomRight => {
let diagonal_difference = self.left_top.z - self.right_bottom.z;
if !Self::check_directed_slope(diagonal_difference, threshold, increase_only) {
return false;
}
}
SlopeDirection::DiagonalTopRightToBottomLeft => {
let diagonal_difference = self.right_top.z - self.left_bottom.z;
if !Self::check_directed_slope(diagonal_difference, threshold, increase_only) {
return false;
}
}
}
true
}
#[inline]
fn check_directed_slope(difference: f32, threshold: f32, increase_only: bool) -> bool {
// check slope against threshold
if difference.abs() > threshold {
// if increase only, check for increase
if increase_only {
if difference < 0.0 {
return false;
}
} else {
return false;
}
}
true
}
}

View file

@ -1,12 +0,0 @@
[package]
name = "rpg_components"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = { workspace = true }
paste = { workspace = true }
assetpath = { workspace = true }
serde = { workspace = true }
engine = { path = "../engine" }

View file

@ -1,20 +0,0 @@
<?xml-model href="gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root reference_width="1280" reference_height="720" layer="11">
<grid id="addon_grid" x_dim="1" y_dim="3" padding="4" margin="2"
x_offset="0" y_offset="0" vert_align="top" hori_align="left"
background='{"background_color":"#794c0a","border_color":"#543919","border_thickness":{"Pixel":2}}'
width="300" height="120">
<!-- header -->
<grid x_slot="0" y_slot="0" x_dim="3" y_dim="1">
<icon id="addon_icon" x_slot="0" y_slot="0"></icon>
<label x_slot="1" y_slot="0" text_color="black">Addon</label>
</grid>
<label id="rarity_label" x_slot="0" y_slot="1">Rarity</label>
<grid x_slot="0" y_slot="2" x_dim="2" y_dim="1" padding="3" margin="3">
<label id="type" x_slot="0" y_slot="0" text_color="black">Type</label>
<label id="value" x_slot="1" y_slot="0" text_color="black">Value</label>
</grid>
</grid>
</root>

View file

@ -1,28 +0,0 @@
<?xml-model href="gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root reference_width="1280" reference_height="720" layer="11">
<grid id="item_grid" x_dim="1" y_dim="4" margin="2" padding="4" x_offset="0" y_offset="0" vert_align="top" hori_align="left"
background='{"background_color":"#794c0a","border_color":"#543919","border_thickness":{"Pixel":2}}'
width="300" height="160">
<!-- header -->
<grid x_slot="0" y_slot="0" x_dim="5" y_dim="1">
<icon id="ability_icon" x_slot="0" y_slot="0"></icon>
<label id="rarity_label" x_slot="1" y_slot="0" x_size="2" text_color="black" text_alignment="left">Ability Name</label>
<label id="level" x_slot="3" y_slot="0" x_size="2" text_color="black" text_alignment="left">Lvl: 1</label>
</grid>
<grid x_slot="0" y_slot="1" x_dim="5" y_dim="1">
<label id="ability_name" x_slot="0" y_slot="0" x_size="3" text_color="black" text_alignment="left">Rarity</label>
<label id="slot_info" x_slot="3" y_slot="0" x_size="2" text_color="black" text_alignment="left">SlotInfo</label>
</grid>
<grid x_slot="0" y_slot="2" x_dim="2" y_dim="1">
<label id="mana_costs" x_slot="0" y_slot="0" text_color="blue">1</label>
<label id="damage" x_slot="1" y_slot="0" text_color="black">62</label>
</grid>
<grid x_slot="0" y_slot="3" x_dim="2" y_dim="1">
<label x_slot="0" y_slot="0" text_color="black">Cooldown</label>
<label id="cooldown" x_slot="1" y_slot="0" text_color="black">3.0</label>
</grid>
</grid>
</root>

Binary file not shown.

Before

(image error) Size: 11 KiB

View file

@ -1,248 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- definition of attributes -->
<xs:attribute name="id" type="xs:string"></xs:attribute>
<xs:attribute name="x_slot" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="y_slot" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="x_dim" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="y_dim" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="x_size" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="y_size" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="normal" type="xs:string"></xs:attribute>
<xs:attribute name="selected" type="xs:string"></xs:attribute>
<xs:attribute name="click_sound" type="xs:string"></xs:attribute>
<xs:attribute name="hover_sound" type="xs:string"></xs:attribute>
<xs:attribute name="select_mode">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="none"></xs:enumeration>
<xs:enumeration value="bigger"></xs:enumeration>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="icon" type="xs:string"></xs:attribute>
<xs:attribute name="icon_margin" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="text_color" type="xs:string"></xs:attribute>
<xs:attribute name="text_ratio" type="xs:decimal"></xs:attribute>
<xs:attribute name="text_alignment">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="left"></xs:enumeration>
<xs:enumeration value="right"></xs:enumeration>
<xs:enumeration value="top"></xs:enumeration>
<xs:enumeration value="bottom"></xs:enumeration>
<xs:enumeration value="center"></xs:enumeration>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="select" type="xs:boolean"></xs:attribute>
<xs:attribute name="isolate" type="xs:boolean"></xs:attribute>
<xs:attribute name="x_offset" type="xs:integer"></xs:attribute>
<xs:attribute name="y_offset" type="xs:integer"></xs:attribute>
<xs:attribute name="width" type="xs:integer"></xs:attribute>
<xs:attribute name="height" type="xs:integer"></xs:attribute>
<xs:attribute name="padding" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="margin" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="vert_align">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="top"></xs:enumeration>
<xs:enumeration value="middle"></xs:enumeration>
<xs:enumeration value="bottom"></xs:enumeration>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="hori_align">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="left"></xs:enumeration>
<xs:enumeration value="middle"></xs:enumeration>
<xs:enumeration value="right"></xs:enumeration>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="background" type="xs:string"></xs:attribute>
<xs:attribute name="foreground" type="xs:string"></xs:attribute>
<xs:attribute name="button_normal" type="xs:string"></xs:attribute>
<xs:attribute name="button_selected" type="xs:string"></xs:attribute>
<xs:attribute name="fill_type">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="expand"></xs:enumeration>
<xs:enumeration value="square"></xs:enumeration>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="reference_width" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="reference_height" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="layer" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="line_count" type="xs:nonNegativeInteger"></xs:attribute>
<xs:attribute name="west_neighbour" type="xs:string"></xs:attribute>
<xs:attribute name="east_neighbour" type="xs:string"></xs:attribute>
<xs:attribute name="north_neighbour" type="xs:string"></xs:attribute>
<xs:attribute name="south_neighbour" type="xs:string"></xs:attribute>
<!-- definition of complex elements -->
<xs:element name="root">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element name="grid" />
</xs:choice>
<xs:attribute ref="reference_width"></xs:attribute>
<xs:attribute ref="reference_height"></xs:attribute>
<xs:attribute ref="layer"></xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="grid">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="grid" />
<xs:element name="button" />
<xs:element name="label" />
<xs:element name="progressbar" />
<xs:element name="textfield" />
<xs:element name="icon" />
</xs:choice>
<xs:attribute ref="id"></xs:attribute>
<xs:attribute ref="x_slot"></xs:attribute>
<xs:attribute ref="y_slot"></xs:attribute>
<xs:attribute ref="x_dim"></xs:attribute>
<xs:attribute ref="y_dim"></xs:attribute>
<xs:attribute ref="x_size"></xs:attribute>
<xs:attribute ref="y_size"></xs:attribute>
<xs:attribute ref="x_offset"></xs:attribute>
<xs:attribute ref="y_offset"></xs:attribute>
<xs:attribute ref="width"></xs:attribute>
<xs:attribute ref="height"></xs:attribute>
<xs:attribute ref="padding"></xs:attribute>
<xs:attribute ref="margin"></xs:attribute>
<xs:attribute ref="vert_align"></xs:attribute>
<xs:attribute ref="hori_align"></xs:attribute>
<xs:attribute ref="background"></xs:attribute>
<xs:attribute ref="button_normal"></xs:attribute>
<xs:attribute ref="button_selected"></xs:attribute>
<xs:attribute ref="click_sound"></xs:attribute>
<xs:attribute ref="hover_sound"></xs:attribute>
<xs:attribute ref="layer"></xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="button">
<xs:complexType mixed="true">
<xs:attribute ref="id"></xs:attribute>
<xs:attribute ref="x_slot"></xs:attribute>
<xs:attribute ref="y_slot"></xs:attribute>
<xs:attribute ref="x_size"></xs:attribute>
<xs:attribute ref="y_size"></xs:attribute>
<xs:attribute ref="select"></xs:attribute>
<xs:attribute ref="isolate"></xs:attribute>
<xs:attribute ref="normal"></xs:attribute>
<xs:attribute ref="selected"></xs:attribute>
<xs:attribute ref="fill_type"></xs:attribute>
<xs:attribute ref="click_sound"></xs:attribute>
<xs:attribute ref="hover_sound"></xs:attribute>
<xs:attribute ref="select_mode"></xs:attribute>
<xs:attribute ref="icon"></xs:attribute>
<xs:attribute ref="icon_margin"></xs:attribute>
<xs:attribute ref="text_color"></xs:attribute>
<xs:attribute ref="text_ratio"></xs:attribute>
<xs:attribute ref="text_alignment"></xs:attribute>
<xs:attribute ref="west_neighbour"></xs:attribute>
<xs:attribute ref="east_neighbour"></xs:attribute>
<xs:attribute ref="north_neighbour"></xs:attribute>
<xs:attribute ref="south_neighbour"></xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="label">
<xs:complexType mixed="true">
<xs:attribute ref="id"></xs:attribute>
<xs:attribute ref="x_slot"></xs:attribute>
<xs:attribute ref="y_slot"></xs:attribute>
<xs:attribute ref="x_size"></xs:attribute>
<xs:attribute ref="y_size"></xs:attribute>
<xs:attribute ref="text_color"></xs:attribute>
<xs:attribute ref="text_ratio"></xs:attribute>
<xs:attribute ref="text_alignment"></xs:attribute>
<xs:attribute ref="background"></xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="multi_line_label">
<xs:complexType mixed="true">
<xs:attribute ref="id"></xs:attribute>
<xs:attribute ref="x_slot"></xs:attribute>
<xs:attribute ref="y_slot"></xs:attribute>
<xs:attribute ref="x_size"></xs:attribute>
<xs:attribute ref="y_size"></xs:attribute>
<xs:attribute ref="line_count"></xs:attribute>
<xs:attribute ref="text_color"></xs:attribute>
<xs:attribute ref="text_ratio"></xs:attribute>
<xs:attribute ref="text_alignment"></xs:attribute>
<xs:attribute ref="background"></xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="multi_line_textfield">
<xs:complexType mixed="true">
<xs:attribute ref="id"></xs:attribute>
<xs:attribute ref="x_slot"></xs:attribute>
<xs:attribute ref="y_slot"></xs:attribute>
<xs:attribute ref="x_size"></xs:attribute>
<xs:attribute ref="y_size"></xs:attribute>
<xs:attribute ref="line_count"></xs:attribute>
<xs:attribute ref="text_color"></xs:attribute>
<xs:attribute ref="text_ratio"></xs:attribute>
<xs:attribute ref="text_alignment"></xs:attribute>
<xs:attribute ref="background"></xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="progressbar">
<xs:complexType mixed="true">
<xs:attribute ref="id"></xs:attribute>
<xs:attribute ref="x_slot"></xs:attribute>
<xs:attribute ref="y_slot"></xs:attribute>
<xs:attribute ref="x_size"></xs:attribute>
<xs:attribute ref="y_size"></xs:attribute>
<xs:attribute ref="text_color"></xs:attribute>
<xs:attribute ref="text_ratio"></xs:attribute>
<xs:attribute ref="text_alignment"></xs:attribute>
<xs:attribute ref="background"></xs:attribute>
<xs:attribute ref="foreground"></xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="textfield">
<xs:complexType mixed="true">
<xs:attribute ref="id"></xs:attribute>
<xs:attribute ref="x_slot"></xs:attribute>
<xs:attribute ref="y_slot"></xs:attribute>
<xs:attribute ref="x_size"></xs:attribute>
<xs:attribute ref="y_size"></xs:attribute>
<xs:attribute ref="text_color"></xs:attribute>
<xs:attribute ref="text_ratio"></xs:attribute>
<xs:attribute ref="text_alignment"></xs:attribute>
<xs:attribute ref="background"></xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="icon">
<xs:complexType>
<xs:attribute ref="id"></xs:attribute>
<xs:attribute ref="x_slot"></xs:attribute>
<xs:attribute ref="y_slot"></xs:attribute>
<xs:attribute ref="x_size"></xs:attribute>
<xs:attribute ref="y_size"></xs:attribute>
<xs:attribute ref="icon"></xs:attribute>
<xs:attribute ref="margin"></xs:attribute>
<xs:attribute ref="background"></xs:attribute>
<xs:attribute ref="fill_type"></xs:attribute>
<xs:attribute ref="text_color"></xs:attribute>
<xs:attribute ref="text_ratio"></xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

View file

@ -1,7 +0,0 @@
<?xml-model href="gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root>
<grid x_dim="4" y_dim="1">
<icon id="socket_icon" x_slot="0" y_slot="0"></icon>
<label id="stat_type" x_slot="1" y_slot="0" x_size="3" text_color="black" text_alignment="left">StatType</label>
</grid>
</root>

View file

@ -1,21 +0,0 @@
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root reference_width="1280" reference_height="720" layer="11">
<grid id="item_grid" x_dim="1" x_offset="0" y_offset="0" vert_align="top" padding="4" margin="2"
background='{"background_color":"#794c0a","border_color":"#543919","border_thickness":{"Pixel":2}}'
hori_align="left" y_dim="3" width="300" height="120">
<!-- header -->
<grid x_slot="0" y_slot="0" x_dim="6" y_dim="1">
<icon id="item_icon" x_slot="0" y_slot="0"></icon>
<label id="slot_label" x_slot="1" y_slot="0" x_size="3" text_color="black">Slot</label>
<label id="level_label" x_slot="4" y_slot="0" x_size="2" text_color="black">Level</label>
</grid>
<label id="rarity_label" x_slot="0" y_slot="1">Rarity</label>
<grid x_slot="0" y_slot="2" x_dim="3" y_dim="1" padding="3" margin="3">
<label x_slot="0" y_slot="0" text_color="#C23519" id="strength_field" text_ratio="0.9">0</label>
<label x_slot="1" y_slot="0" text_color="#65D01E" id="agility_field" text_ratio="0.9">0</label>
<label x_slot="2" y_slot="0" text_color="#1D63B3" id="intelligence_field" text_ratio="0.9">0</label>
</grid>
</grid>
</root>

View file

@ -1,22 +0,0 @@
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root reference_width="1280" reference_height="720" layer="11">
<grid id="item_grid" x_dim="1" x_offset="0" y_offset="0" vert_align="top"
hori_align="left" width="300" padding="4" margin="2"
background='{"background_color":"#794c0a","border_color":"#543919","border_thickness":{"Pixel":2}}'
y_dim="4" height="160">
<!-- header -->
<grid x_slot="0" y_slot="0" x_dim="6" y_dim="1">
<icon id="item_icon" x_slot="0" y_slot="0"></icon>
<label id="slot_label" x_slot="1" y_slot="0" x_size="3" text_color="black">Slot</label>
<label id="level_label" x_slot="4" y_slot="0" x_size="2" text_color="black">Level</label>
</grid>
<label id="rarity_label" x_slot="0" y_slot="1">Rarity</label>
<grid x_slot="0" y_slot="2" x_dim="3" y_dim="1" padding="3" margin="3">
<label x_slot="0" y_slot="0" text_color="#C23519" id="strength_field" text_ratio="0.9">0</label>
<label x_slot="1" y_slot="0" text_color="#65D01E" id="agility_field" text_ratio="0.9">0</label>
<label x_slot="2" y_slot="0" text_color="#1D63B3" id="intelligence_field" text_ratio="0.9">0</label>
</grid>
</grid>
</root>

View file

@ -1,22 +0,0 @@
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root reference_width="1280" reference_height="720" layer="11">
<grid id="item_grid" x_dim="1" x_offset="0" y_offset="0" vert_align="top"
hori_align="left" width="300" padding="4" margin="2"
background='{"background_color":"#794c0a","border_color":"#543919","border_thickness":{"Pixel":2}}'
y_dim="5" height="200">
<!-- header -->
<grid x_slot="0" y_slot="0" x_dim="6" y_dim="1">
<icon id="item_icon" x_slot="0" y_slot="0"></icon>
<label id="slot_label" x_slot="1" y_slot="0" x_size="3" text_color="black">Slot</label>
<label id="level_label" x_slot="4" y_slot="0" x_size="2" text_color="black">Level</label>
</grid>
<label id="rarity_label" x_slot="0" y_slot="1">Rarity</label>
<grid x_slot="0" y_slot="2" x_dim="3" y_dim="1" padding="3" margin="3">
<label x_slot="0" y_slot="0" text_color="#C23519" id="strength_field" text_ratio="0.9">0</label>
<label x_slot="1" y_slot="0" text_color="#65D01E" id="agility_field" text_ratio="0.9">0</label>
<label x_slot="2" y_slot="0" text_color="#1D63B3" id="intelligence_field" text_ratio="0.9">0</label>
</grid>
</grid>
</root>

View file

@ -1,22 +0,0 @@
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root reference_width="1280" reference_height="720" layer="11">
<grid id="item_grid" x_dim="1" x_offset="0" y_offset="0" vert_align="top"
hori_align="left" width="300" padding="4" margin="2"
background='{"background_color":"#794c0a","border_color":"#543919","border_thickness":{"Pixel":2}}'
y_dim="6" height="240">
<!-- header -->
<grid x_slot="0" y_slot="0" x_dim="6" y_dim="1">
<icon id="item_icon" x_slot="0" y_slot="0"></icon>
<label id="slot_label" x_slot="1" y_slot="0" x_size="3" text_color="black">Slot</label>
<label id="level_label" x_slot="4" y_slot="0" x_size="2" text_color="black">Level</label>
</grid>
<label id="rarity_label" x_slot="0" y_slot="1">Rarity</label>
<grid x_slot="0" y_slot="2" x_dim="3" y_dim="1" padding="3" margin="3">
<label x_slot="0" y_slot="0" text_color="#C23519" id="strength_field" text_ratio="0.9">0</label>
<label x_slot="1" y_slot="0" text_color="#65D01E" id="agility_field" text_ratio="0.9">0</label>
<label x_slot="2" y_slot="0" text_color="#1D63B3" id="intelligence_field" text_ratio="0.9">0</label>
</grid>
</grid>
</root>

View file

@ -1,22 +0,0 @@
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root reference_width="1280" reference_height="720" layer="11">
<grid id="item_grid" x_dim="1" x_offset="0" y_offset="0" vert_align="top"
hori_align="left" width="300" padding="4" margin="2"
background='{"background_color":"#794c0a","border_color":"#543919","border_thickness":{"Pixel":2}}'
y_dim="7" height="280">
<!-- header -->
<grid x_slot="0" y_slot="0" x_dim="6" y_dim="1">
<icon id="item_icon" x_slot="0" y_slot="0"></icon>
<label id="slot_label" x_slot="1" y_slot="0" x_size="3" text_color="black">Slot</label>
<label id="level_label" x_slot="4" y_slot="0" x_size="2" text_color="black">Level</label>
</grid>
<label id="rarity_label" x_slot="0" y_slot="1">Rarity</label>
<grid x_slot="0" y_slot="2" x_dim="3" y_dim="1" padding="3" margin="3">
<label x_slot="0" y_slot="0" text_color="#C23519" id="strength_field" text_ratio="0.9">0</label>
<label x_slot="1" y_slot="0" text_color="#65D01E" id="agility_field" text_ratio="0.9">0</label>
<label x_slot="2" y_slot="0" text_color="#1D63B3" id="intelligence_field" text_ratio="0.9">0</label>
</grid>
</grid>
</root>

View file

@ -1,22 +0,0 @@
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root reference_width="1280" reference_height="720" layer="11">
<grid id="item_grid" x_dim="1" x_offset="0" y_offset="0" vert_align="top"
hori_align="left" width="300" padding="4" margin="2"
background='{"background_color":"#794c0a","border_color":"#543919","border_thickness":{"Pixel":2}}'
y_dim="8" height="320">
<!-- header -->
<grid x_slot="0" y_slot="0" x_dim="6" y_dim="1">
<icon id="item_icon" x_slot="0" y_slot="0"></icon>
<label id="slot_label" x_slot="1" y_slot="0" x_size="3" text_color="black">Slot</label>
<label id="level_label" x_slot="4" y_slot="0" x_size="2" text_color="black">Level</label>
</grid>
<label id="rarity_label" x_slot="0" y_slot="1">Rarity</label>
<grid x_slot="0" y_slot="2" x_dim="3" y_dim="1" padding="3" margin="3">
<label x_slot="0" y_slot="0" text_color="#C23519" id="strength_field" text_ratio="0.9">0</label>
<label x_slot="1" y_slot="0" text_color="#65D01E" id="agility_field" text_ratio="0.9">0</label>
<label x_slot="2" y_slot="0" text_color="#1D63B3" id="intelligence_field" text_ratio="0.9">0</label>
</grid>
</grid>
</root>

View file

@ -1,22 +0,0 @@
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root reference_width="1280" reference_height="720" layer="11">
<grid id="item_grid" x_dim="1" x_offset="0" y_offset="0" vert_align="top"
hori_align="left" width="300" padding="4" margin="2"
background='{"background_color":"#794c0a","border_color":"#543919","border_thickness":{"Pixel":2}}'
y_dim="9" height="360">
<!-- header -->
<grid x_slot="0" y_slot="0" x_dim="6" y_dim="1">
<icon id="item_icon" x_slot="0" y_slot="0"></icon>
<label id="slot_label" x_slot="1" y_slot="0" x_size="3" text_color="black">Slot</label>
<label id="level_label" x_slot="4" y_slot="0" x_size="2" text_color="black">Level</label>
</grid>
<label id="rarity_label" x_slot="0" y_slot="1">Rarity</label>
<grid x_slot="0" y_slot="2" x_dim="3" y_dim="1" padding="3" margin="3">
<label x_slot="0" y_slot="0" text_color="#C23519" id="strength_field" text_ratio="0.9">0</label>
<label x_slot="1" y_slot="0" text_color="#65D01E" id="agility_field" text_ratio="0.9">0</label>
<label x_slot="2" y_slot="0" text_color="#1D63B3" id="intelligence_field" text_ratio="0.9">0</label>
</grid>
</grid>
</root>

View file

@ -1,22 +0,0 @@
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root reference_width="1280" reference_height="720" layer="11">
<grid id="item_grid" x_dim="1" x_offset="0" y_offset="0" vert_align="top"
hori_align="left" width="300" padding="4" margin="2"
background='{"background_color":"#794c0a","border_color":"#543919","border_thickness":{"Pixel":2}}'
y_dim="10" height="400">
<!-- header -->
<grid x_slot="0" y_slot="0" x_dim="6" y_dim="1">
<icon id="item_icon" x_slot="0" y_slot="0"></icon>
<label id="slot_label" x_slot="1" y_slot="0" x_size="3" text_color="black">Slot</label>
<label id="level_label" x_slot="4" y_slot="0" x_size="2" text_color="black">Level</label>
</grid>
<label id="rarity_label" x_slot="0" y_slot="1">Rarity</label>
<grid x_slot="0" y_slot="2" x_dim="3" y_dim="1" padding="3" margin="3">
<label x_slot="0" y_slot="0" text_color="#C23519" id="strength_field" text_ratio="0.9">0</label>
<label x_slot="1" y_slot="0" text_color="#65D01E" id="agility_field" text_ratio="0.9">0</label>
<label x_slot="2" y_slot="0" text_color="#1D63B3" id="intelligence_field" text_ratio="0.9">0</label>
</grid>
</grid>
</root>

View file

@ -1,22 +0,0 @@
<?xml-model href="gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root reference_width="1280" reference_height="720" layer="11">
<grid id="main_grid" x_dim="1" x_offset="0" y_offset="0" vert_align="top" padding="4" margin="2"
background='{"background_color":"#794c0a","border_color":"#543919","border_thickness":{"Pixel":2}}'
hori_align="left" y_dim="3" width="300" height="120">
<!-- header -->
<grid x_slot="0" y_slot="0" x_dim="3" y_dim="1">
<icon id="jewel_icon" x_slot="0" y_slot="0"></icon>
<label id="rarity_label" x_slot="1" y_slot="0" x_size="2" text_color="black">Level</label>
</grid>
<grid x_slot="0" y_slot="1" x_dim="4" y_dim="1" padding="3" margin="3">
<label id="attribute_type" x_slot="0" y_slot="0" x_size="3" text_color="black">Attribute</label>
<label id="attribute_value" x_slot="3" y_slot="0" text_color="black">Value</label>
</grid>
<grid x_slot="0" y_slot="2" x_dim="4" y_dim="1" padding="3" margin="3">
<label id="stat_type" x_slot="0" y_slot="0" x_size="3" text_color="black">Stat</label>
<label id="stat_value" x_slot="3" y_slot="0" text_color="black">Value</label>
</grid>
</grid>
</root>

View file

@ -1,7 +0,0 @@
<?xml-model href="gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root>
<grid x_dim="3" y_dim="1">
<label id="stat_type" x_slot="0" y_slot="0" x_size="2" text_color="black" text_alignment="left">StatType</label>
<label id="stat_value" x_slot="2" y_slot="0" text_color="black" text_alignment="right">StatValue</label>
</grid>
</root>

View file

@ -1,169 +0,0 @@
use anyhow::Result;
use cgmath::{Vector2, Zero};
use engine::prelude::*;
use paste;
use std::{
slice::{Iter, IterMut},
str::FromStr,
};
use crate::{components::inventory::Storable, items::ItemSystem};
use crate::{config::save_game::SaveGame, items::ability_book::Ability};
use crate::{
damage_type::DamageType,
items::{ability_addon::AbilityAddonTypes, ability_book::AbilityBook, Rarities},
};
use super::{character_status::CharacterStatus, statistics::Statistics};
macro_rules! load {
($me: ident, $item_system:ident, $save_game:ident, $($index:literal,)+) => {
paste::expr! {
$(
if $save_game.[<ability_ $index>].used {
let ability = &$save_game.[<ability_ $index>];
let mut addons = Vec::new();
for addon in ability.addons.iter() {
let mut split = addon.split('|');
let rarity = Rarities::from_str(&split.nth(0).unwrap())?;
let addon_type = AbilityAddonTypes::from_str(&split.nth(0).unwrap())?;
addons.push(Some($item_system.addon(rarity, addon_type)));
}
let book = $item_system.ability_book(
&ability.name,
ability.rarity,
addons,
ability.level,
);
$me.abilities[$index] = Some(book);
}
)+
}
};
}
macro_rules! store {
($me: ident, $save_game:ident, $($index:literal,)+) => {
paste::expr! {
$(
if let Some(book) = &$me.abilities[$index] {
let ability = &mut $save_game.[<ability_ $index>];
ability.used = true;
ability.name = book.ability().name().to_string();
ability.rarity = book.rarity();
ability.level = book.level();
for addon in book.addons().iter() {
if let Some(addon) = addon {
ability.addons.push(format!("{}|{}", addon.rarity(), addon.addon_type()));
}
}
}
)+
}
};
}
// stupid workaround for serde Deserialize
pub const MAX_ABILITIES: usize = 4;
pub struct AbilitySlots<A: Ability> {
pub direction: Vector2<f32>,
abilities: [Option<AbilityBook<A>>; MAX_ABILITIES],
}
impl<A: Ability> AbilitySlots<A> {
pub fn empty() -> Self {
Self {
direction: Vector2::zero(),
abilities: Default::default(),
}
}
pub fn load(item_system: &ItemSystem<A>, save_game: &SaveGame) -> Result<Self> {
let mut me = Self::empty();
load!(me, item_system, save_game, 0, 1, 2, 3,);
Ok(me)
}
pub fn store(&self, save_game: &mut SaveGame) {
store!(self, save_game, 0, 1, 2, 3,);
}
pub fn insert_book(&mut self, book: AbilityBook<A>, index: usize) -> Option<AbilityBook<A>> {
match self.abilities[index].clone() {
Some(ability) => {
if ability != book {
self.abilities[index] = Some(book);
} else {
self.abilities[index] = Some(book);
}
Some(ability)
}
None => {
self.abilities[index] = Some(book);
None
}
}
}
// pub fn clear_book(&mut self, index: usize) {
// self.abilities[index] = None;
// }
pub fn book(&self, index: usize) -> Option<&AbilityBook<A>> {
self.abilities[index].as_ref()
}
pub fn book_mut(&mut self, index: usize) -> Option<&mut AbilityBook<A>> {
self.abilities[index].as_mut()
}
pub fn iter(&self) -> Iter<'_, Option<AbilityBook<A>>> {
self.abilities.iter()
}
pub fn iter_mut(&mut self) -> IterMut<'_, Option<AbilityBook<A>>> {
self.abilities.iter_mut()
}
pub fn apply_damage(
base_damage: u32,
damage_type: DamageType,
enemy_statistics: &Statistics,
enemy_status: &mut CharacterStatus,
) {
let resistance = enemy_statistics.calculate_resistance(damage_type);
if resistance < base_damage {
let damage = base_damage - resistance;
enemy_status.apply_damage(damage as f32);
}
}
}
impl<A: Ability + 'static> EntityComponent for AbilitySlots<A> {
fn name(&self) -> &str {
Self::debug_name()
}
}
impl<A: Ability> ComponentDebug for AbilitySlots<A> {
fn debug_name() -> &'static str {
"AbilitySlots"
}
}

View file

@ -1,325 +0,0 @@
use anyhow::bail;
use engine::prelude::{image::RgbaImage, *};
use std::{cmp::Ordering, slice::Iter, str::FromStr};
use crate::{
config::attributes::{AttributeColorSettings, StartingAttributes},
items::{ability_book::Ability, ItemSystem},
};
generate_stat!(Agility, u32, "Agility");
generate_stat!(Intelligence, u32, "Intelligence");
generate_stat!(Strength, u32, "Strength");
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd)]
pub enum Attribute {
Agility,
Intelligence,
Strength,
}
impl Attribute {
pub fn random() -> Self {
match Random::range(0, 3) {
0 => Self::Agility,
1 => Self::Intelligence,
2 => Self::Strength,
_ => unreachable!(),
}
}
pub fn iter() -> Iter<'static, Self> {
use Attribute::*;
static ATTRIBUTES: [Attribute; 3] = [Agility, Intelligence, Strength];
ATTRIBUTES.iter()
}
pub fn apply_color<A: Ability>(
&self,
base_image: &RgbaImage,
color_settins: &AttributeColorSettings,
) -> RgbaImage {
ItemSystem::<A>::apply_color(base_image, color_settins.from_attribute(*self))
}
}
impl FromStr for Attribute {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"agility" => Ok(Self::Agility),
"intelligence" => Ok(Self::Intelligence),
"strength" => Ok(Self::Strength),
_ => bail!("could not parse attribute from {s}"),
}
}
}
impl std::fmt::Display for Attribute {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Attribute::Agility => write!(f, "agility"),
Attribute::Intelligence => write!(f, "intelligence"),
Attribute::Strength => write!(f, "strength"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Attributes {
agility: Agility,
intelligence: Intelligence,
strength: Strength,
pub bonus_agility: Agility,
pub bonus_intelligence: Intelligence,
pub bonus_strength: Strength,
}
impl Attributes {
pub fn zero() -> Self {
Attributes {
agility: Agility::default(),
intelligence: Intelligence::default(),
strength: Strength::default(),
bonus_agility: Agility::default(),
bonus_intelligence: Intelligence::default(),
bonus_strength: Strength::default(),
}
}
pub fn new(starting_attributes: &StartingAttributes) -> Self {
Attributes {
agility: Agility::from(starting_attributes.agility),
intelligence: Intelligence::from(starting_attributes.intelligence),
strength: Strength::from(starting_attributes.strength),
bonus_agility: Agility::default(),
bonus_intelligence: Intelligence::default(),
bonus_strength: Strength::default(),
}
}
pub fn load(strength: u32, agility: u32, intelligence: u32) -> Self {
Attributes {
agility: Agility::from(agility),
intelligence: Intelligence::from(intelligence),
strength: Strength::from(strength),
bonus_agility: Agility::default(),
bonus_intelligence: Intelligence::default(),
bonus_strength: Strength::default(),
}
}
pub fn apply_boni(&mut self, strength: Strength, agility: Agility, intelligence: Intelligence) {
self.strength = strength;
self.agility = agility;
self.intelligence = intelligence;
}
pub fn random_with_base(sum: u32, starting_attributes: &StartingAttributes) -> Self {
let me = Self::new(starting_attributes);
let mut random = Self::zero();
random.randomize(sum);
me + random
}
pub fn agility(&self) -> Agility {
self.agility + self.bonus_agility
}
pub fn strength(&self) -> Strength {
self.strength + self.bonus_strength
}
pub fn intelligence(&self) -> Intelligence {
self.intelligence + self.bonus_intelligence
}
pub fn base_agility(&self) -> Agility {
self.agility
}
pub fn base_strength(&self) -> Strength {
self.strength
}
pub fn base_intelligence(&self) -> Intelligence {
self.intelligence
}
pub fn add_agility(&mut self, agility: Agility) {
self.agility += agility;
}
pub fn add_strength(&mut self, strength: Strength) {
self.strength += strength;
}
pub fn add_intelligence(&mut self, intelligence: Intelligence) {
self.intelligence += intelligence;
}
pub fn base_sum(&self) -> u32 {
self.agility.raw() + self.intelligence.raw() + self.strength.raw()
}
}
impl Attributes {
pub fn randomize(&mut self, sum: u32) {
enum AttributeTypes {
Strength,
Agility,
Intelligence,
}
let mut attrs = vec![
AttributeTypes::Strength,
AttributeTypes::Agility,
AttributeTypes::Intelligence,
];
let n = Random::range(0, 3) as usize;
let attr_type = attrs.remove(n);
let val1 = match attr_type {
AttributeTypes::Strength => {
self.strength.set(Random::range(0, sum + 1));
self.strength.raw()
}
AttributeTypes::Agility => {
self.agility.set(Random::range(0, sum + 1));
self.agility.raw()
}
AttributeTypes::Intelligence => {
self.intelligence.set(Random::range(0, sum + 1));
self.intelligence.raw()
}
};
let n = Random::range(0, 2) as usize;
let attr_type = attrs.remove(n);
let val2 = match attr_type {
AttributeTypes::Strength => {
self.strength.set(Random::range(0, (sum - val1) + 1));
self.strength.raw()
}
AttributeTypes::Agility => {
self.agility.set(Random::range(0, (sum - val1) + 1));
self.agility.raw()
}
AttributeTypes::Intelligence => {
self.intelligence.set(Random::range(0, (sum - val1) + 1));
self.intelligence.raw()
}
};
let attr_type = attrs.remove(0);
match attr_type {
AttributeTypes::Strength => {
self.strength.set(sum - (val1 + val2));
self.strength.raw()
}
AttributeTypes::Agility => {
self.agility.set(sum - (val1 + val2));
self.agility.raw()
}
AttributeTypes::Intelligence => {
self.intelligence.set(sum - (val1 + val2));
self.intelligence.raw()
}
};
debug_assert!(sum == self.sum());
}
pub fn sum(&self) -> u32 {
self.agility.raw() + self.intelligence.raw() + self.strength.raw()
}
}
impl std::ops::Add for Attributes {
type Output = Self;
fn add(self, other: Attributes) -> Self {
Attributes {
agility: self.agility + other.agility,
strength: self.strength + other.strength,
intelligence: self.intelligence + other.intelligence,
bonus_agility: Agility::default(),
bonus_intelligence: Intelligence::default(),
bonus_strength: Strength::default(),
}
}
}
impl PartialEq for Attributes {
fn eq(&self, other: &Self) -> bool {
self.intelligence == other.intelligence
&& self.agility == other.agility
&& self.strength == other.strength
}
}
impl Eq for Attributes {}
impl PartialOrd for Attributes {
fn partial_cmp(&self, other: &Attributes) -> Option<Ordering> {
let strength = self.strength.raw().cmp(&other.strength.raw());
if strength == Ordering::Less {
return Some(strength);
}
let agility = self.agility.raw().cmp(&other.agility.raw());
if agility == Ordering::Less {
return Some(agility);
}
let intelligence = self.intelligence.raw().cmp(&other.intelligence.raw());
if intelligence == Ordering::Less {
return Some(intelligence);
}
if strength == Ordering::Equal
&& agility == Ordering::Equal
&& intelligence == Ordering::Equal
{
return Some(Ordering::Equal);
}
if strength == Ordering::Greater
&& agility == Ordering::Greater
&& intelligence == Ordering::Greater
{
return Some(Ordering::Greater);
}
None
}
}
impl EntityComponent for Attributes {
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for Attributes {
fn debug_name() -> &'static str {
"Attributes"
}
}

View file

@ -1,109 +0,0 @@
use std::time::Duration;
use anyhow::Result;
use engine::prelude::*;
use super::statistics::*;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CharacterStatus {
pub current_health: Health,
pub current_mana: Mana,
pub last_tick: Option<Duration>,
}
impl CharacterStatus {
pub fn new_full(stats: &Statistics) -> Self {
CharacterStatus {
current_health: stats.health,
current_mana: stats.mana,
last_tick: None,
}
}
pub fn regen_tick(&mut self, stats: &Statistics) {
if !self.is_dead() {
self.add_health(stats.health_regeneration.raw(), stats);
self.add_mana(stats.mana_regeneration.raw(), stats);
}
}
pub fn use_ability(&mut self, cost: f32) -> bool {
self.use_mana(cost)
}
pub fn add_health(&mut self, health: impl Into<Health>, stats: &Statistics) {
let health_bonus = health.into();
self.current_health = if self.current_health + health_bonus > stats.health {
stats.health
} else {
self.current_health + health_bonus
}
}
pub fn add_mana(&mut self, mana: impl Into<Mana>, stats: &Statistics) {
let mana_bonus = mana.into();
self.current_mana = if self.current_mana + mana_bonus > stats.mana {
stats.mana
} else {
self.current_mana + mana_bonus
}
}
pub fn apply_damage(&mut self, cost: f32) -> bool {
self.reduce_health(cost)
}
// returns false if there isn't enough mana left, otherwise true
fn use_mana(&mut self, mana: impl Into<Mana>) -> bool {
let mana = mana.into();
if mana > self.current_mana {
return false;
}
self.current_mana = self.current_mana - mana;
true
}
// returns false if there isn't enough health present
fn reduce_health(&mut self, health: impl Into<Health>) -> bool {
let health = health.into();
if health > self.current_health {
self.current_health = Health::from(0.0);
return false;
}
self.current_health = self.current_health - health;
true
}
pub fn is_dead(&self) -> bool {
self.current_health == Health::from(0.0)
}
}
impl EntityComponent for CharacterStatus {
fn enable(&mut self, _world: &mut World) -> Result<()> {
self.last_tick = None;
Ok(())
}
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for CharacterStatus {
fn debug_name() -> &'static str {
"CharacterStatus"
}
}

View file

@ -1,98 +0,0 @@
use engine::prelude::*;
use paste::paste;
use crate::{config::save_game::SaveGame, items::Rarities};
macro_rules! check_consume {
($self: ident, $var: ident, $amount: ident) => {
paste! {
if $self.$var < $amount {
false
} else {
$self.$var = $self.$var - $amount;
true
}
}
};
}
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
pub struct CraftingMaterials {
common: u32,
uncommon: u32,
magical: u32,
rare: u32,
epic: u32,
legendary: u32,
}
impl CraftingMaterials {
pub fn load(save_game: &SaveGame) -> Self {
let mut me = Self::default();
me.common = save_game.crafting_materials.common;
me.uncommon = save_game.crafting_materials.uncommon;
me.magical = save_game.crafting_materials.magical;
me.rare = save_game.crafting_materials.rare;
me.epic = save_game.crafting_materials.epic;
me.legendary = save_game.crafting_materials.legendary;
me
}
pub fn store(&self, save_game: &mut SaveGame) {
save_game.crafting_materials.common = self.common;
save_game.crafting_materials.uncommon = self.uncommon;
save_game.crafting_materials.magical = self.magical;
save_game.crafting_materials.rare = self.rare;
save_game.crafting_materials.epic = self.epic;
save_game.crafting_materials.legendary = self.legendary;
}
pub fn count(&self, rarity: Rarities) -> u32 {
match rarity {
Rarities::Common => self.common,
Rarities::Uncommon => self.uncommon,
Rarities::Magical => self.magical,
Rarities::Rare => self.rare,
Rarities::Epic => self.epic,
Rarities::Legendary => self.legendary,
}
}
pub fn increment(&mut self, rarity: Rarities) {
match rarity {
Rarities::Common => self.common += 1,
Rarities::Uncommon => self.uncommon += 1,
Rarities::Magical => self.magical += 1,
Rarities::Rare => self.rare += 1,
Rarities::Epic => self.epic += 1,
Rarities::Legendary => self.legendary += 1,
}
}
pub fn consume(&mut self, rarity: Rarities, amount: u32) -> bool {
match rarity {
Rarities::Common => check_consume!(self, common, amount),
Rarities::Uncommon => check_consume!(self, uncommon, amount),
Rarities::Magical => check_consume!(self, magical, amount),
Rarities::Rare => check_consume!(self, rare, amount),
Rarities::Epic => check_consume!(self, rare, amount),
Rarities::Legendary => check_consume!(self, legendary, amount),
}
}
}
impl EntityComponent for CraftingMaterials {
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for CraftingMaterials {
fn debug_name() -> &'static str {
"CraftingMaterials"
}
}

View file

@ -1,233 +0,0 @@
use anyhow::Result;
use engine::prelude::*;
use std::slice::Iter;
use std::sync::Arc;
use crate::{
config::save_game::SaveGame,
items::{
ability_addon::AbilityAddon,
ability_book::{Ability, AbilityBook},
Item, ItemSystem, Jewel, MapItem, Rarities,
},
};
pub trait Storable {
fn rarity(&self) -> Rarities;
fn icon(&self) -> Arc<Image>;
}
#[derive(Default, Clone)]
pub struct Inventory<A: Ability> {
items: Vec<Item>,
addons: Vec<AbilityAddon>,
books: Vec<AbilityBook<A>>,
jewels: Vec<Jewel>,
maps: Vec<MapItem>,
}
impl<A: Ability> Inventory<A> {
// ------- items --------
pub fn add_item(&mut self, item: Item) {
self.items.push(item);
}
pub fn insert_item(&mut self, item: Item, index: usize) {
self.items.insert(index, item);
}
pub fn remove_item(&mut self, index: usize) -> Item {
self.items.remove(index)
}
pub fn iter_items(&self) -> Iter<'_, Item> {
self.items.iter()
}
pub fn item_at(&self, index: usize) -> &Item {
&self.items[index]
}
pub fn item_mut_at(&mut self, index: usize) -> &mut Item {
&mut self.items[index]
}
// ------- jewels --------
pub fn add_jewel(&mut self, jewel: Jewel) {
self.jewels.push(jewel);
}
pub fn insert_jewel(&mut self, jewel: Jewel, index: usize) {
self.jewels.insert(index, jewel);
}
pub fn remove_jewel(&mut self, index: usize) -> Jewel {
self.jewels.remove(index)
}
pub fn iter_jewels(&self) -> Iter<'_, Jewel> {
self.jewels.iter()
}
pub fn jewel_at(&self, index: usize) -> &Jewel {
&self.jewels[index]
}
pub fn jewel_mut_at(&mut self, index: usize) -> &mut Jewel {
&mut self.jewels[index]
}
// ------- maps --------
pub fn add_map(&mut self, map: MapItem) {
self.maps.push(map);
}
pub fn insert_map(&mut self, map: MapItem, index: usize) {
self.maps.insert(index, map);
}
pub fn remove_map(&mut self, index: usize) -> MapItem {
self.maps.remove(index)
}
pub fn iter_maps(&self) -> Iter<'_, MapItem> {
self.maps.iter()
}
pub fn maps_at(&self, index: usize) -> &MapItem {
&self.maps[index]
}
// ------- addons --------
pub fn add_addon(&mut self, addon: AbilityAddon) {
self.addons.push(addon);
}
// pub fn insert_addon(&mut self, addon: AbilityAddon, index: usize) {
// self.addons.insert(index, addon);
// }
pub fn remove_addon(&mut self, index: usize) -> AbilityAddon {
self.addons.remove(index)
}
pub fn addon(&self, index: usize) -> &AbilityAddon {
&self.addons[index]
}
pub fn iter_addons(&self) -> Iter<'_, AbilityAddon> {
self.addons.iter()
}
pub fn addon_at(&self, index: usize) -> &AbilityAddon {
&self.addons[index]
}
// ------- books --------
pub fn add_book(&mut self, book: AbilityBook<A>) {
self.books.push(book);
}
pub fn insert_book(&mut self, book: AbilityBook<A>, index: usize) {
self.books.insert(index, book);
}
pub fn remove_book(&mut self, index: usize) -> AbilityBook<A> {
self.books.remove(index)
}
pub fn iter_books(&self) -> Iter<'_, AbilityBook<A>> {
self.books.iter()
}
pub fn book_at(&self, index: usize) -> &AbilityBook<A> {
&self.books[index]
}
}
impl<A: Ability> Inventory<A> {
pub fn load(save_game: &SaveGame, item_system: &ItemSystem<A>) -> Result<Self> {
let mut inventory: Inventory<A> = Default::default();
for item_string in save_game.inventory.items.iter() {
if !item_string.is_empty() {
inventory.add_item(Item::from_persistent(item_string.split('|'), item_system)?);
}
}
for addon_string in save_game.inventory.addons.iter() {
if !addon_string.is_empty() {
inventory.add_addon(AbilityAddon::from_persistent(
addon_string.split('|'),
item_system,
)?);
}
}
for book_string in save_game.inventory.books.iter() {
if !book_string.is_empty() {
inventory.add_book(AbilityBook::from_persistent(
book_string.split('|'),
item_system,
)?);
}
}
for jewel_string in save_game.inventory.jewels.iter() {
if !jewel_string.is_empty() {
let mut jewel = Jewel::from_persistent(&mut jewel_string.split('|'))?;
jewel.icon =
Some(item_system.jewel_icon(jewel.rarity, jewel.level, jewel.attribute));
jewel.update_stat(&item_system.item_settings);
inventory.add_jewel(jewel);
}
}
for map_string in save_game.inventory.maps.iter() {
if !map_string.is_empty() {
inventory.add_map(MapItem::from_persistent(
map_string.split('|'),
item_system,
)?)
}
}
Ok(inventory)
}
pub fn store(&self, save_game: &mut SaveGame) {
for item in self.items.iter() {
save_game.inventory.items.push(item.into_persistent());
}
for addon in self.addons.iter() {
save_game.inventory.addons.push(addon.into_persistent());
}
for book in self.books.iter() {
save_game.inventory.books.push(book.into_persistent());
}
for jewel in self.jewels.iter() {
save_game.inventory.jewels.push(jewel.into_persistent());
}
for map in self.maps.iter() {
save_game.inventory.maps.push(map.into_persistent());
}
}
}
impl<A: Ability + 'static> EntityComponent for Inventory<A> {
fn name(&self) -> &str {
Self::debug_name()
}
}
impl<A: Ability> ComponentDebug for Inventory<A> {
fn debug_name() -> &'static str {
"Inventory"
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,96 +0,0 @@
use anyhow::Result;
use engine::prelude::*;
use crate::config::experience::ExperienceSettings;
use super::npc_type::NPCType;
pub struct LevelUpEvent;
pub struct Level {
current_level: u32,
pub current_experience: u32,
pub experience_needed: u32,
exp_settings: ExperienceSettings,
}
impl Clone for Level {
fn clone(&self) -> Self {
Self {
current_level: self.current_level,
current_experience: self.current_experience,
experience_needed: self.experience_needed,
exp_settings: self.exp_settings.clone(),
}
}
}
impl Level {
pub fn new(exp_settings: &ExperienceSettings) -> Self {
Level {
current_level: 1,
current_experience: 0,
experience_needed: exp_settings.player_experience_needed(1),
exp_settings: exp_settings.clone(),
}
}
pub fn load(level: u32, current_experience: u32, exp_settings: &ExperienceSettings) -> Self {
Level {
current_level: level,
current_experience,
experience_needed: exp_settings.player_experience_needed(level),
exp_settings: exp_settings.clone(),
}
}
/// returns `true` at level up
pub fn add_experience(&mut self, mob_level: u32, npc_type: impl Into<NPCType>) -> Result<bool> {
self.add_experience_raw(self.exp_settings.mob_experience(mob_level, npc_type))
}
pub fn add_experience_raw(&mut self, exp: u32) -> Result<bool> {
self.current_experience += exp;
while self.current_experience >= self.experience_needed {
self.current_level += 1;
self.current_experience -= self.experience_needed;
self.experience_needed = self
.exp_settings
.player_experience_needed(self.current_level);
if self.current_experience < self.experience_needed {
return Ok(true);
}
}
Ok(false)
}
pub fn level(&self) -> u32 {
self.current_level
}
pub fn reset_current(&mut self) {
self.current_experience = 0;
}
}
impl EntityComponent for Level {
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for Level {
fn debug_name() -> &'static str {
"Level"
}
}

View file

@ -1,138 +0,0 @@
macro_rules! generate_stat {
($struct_name:ident, $data_type:ty, $display_name:expr) => {
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)]
pub struct $struct_name($data_type);
impl $struct_name {
pub const DISPLAY_NAME: &'static str = $display_name;
pub fn set(&mut self, v: $data_type) {
self.0 = v;
}
pub fn raw(&self) -> $data_type {
self.0
}
pub fn as_str(&self) -> &str {
$display_name
}
}
impl std::fmt::Display for $struct_name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, $display_name)
}
}
impl From<$data_type> for $struct_name {
fn from(value: $data_type) -> Self {
$struct_name(value)
}
}
impl From<&$struct_name> for $struct_name {
fn from(value: &$struct_name) -> Self {
value.clone()
}
}
impl std::ops::Add for $struct_name {
type Output = Self;
fn add(self, other: $struct_name) -> Self {
$struct_name(self.0 + other.0)
}
}
impl std::ops::Sub for $struct_name {
type Output = Self;
fn sub(self, other: $struct_name) -> Self {
$struct_name(self.0 - other.0)
}
}
impl std::ops::Mul for $struct_name {
type Output = Self;
fn mul(self, other: $struct_name) -> Self {
$struct_name(self.0 * other.0)
}
}
impl std::ops::AddAssign for $struct_name {
fn add_assign(&mut self, other: $struct_name) {
self.0 += other.0
}
}
impl std::ops::SubAssign for $struct_name {
fn sub_assign(&mut self, other: $struct_name) {
self.0 -= other.0
}
}
impl std::ops::MulAssign for $struct_name {
fn mul_assign(&mut self, other: $struct_name) {
self.0 *= other.0
}
}
impl std::ops::MulAssign<f32> for $struct_name {
fn mul_assign(&mut self, other: f32) {
self.0 = (self.0 as f32 * other) as $data_type;
}
}
impl PartialOrd for $struct_name {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
self.0.partial_cmp(&other.0)
}
}
impl PartialEq for $struct_name {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Eq for $struct_name {}
};
}
use super::attributes::Attributes;
pub trait AttributeAssociation {
fn attribute_level(&self, attributes: &Attributes) -> u32;
}
macro_rules! associate_agility {
($struct_name:ident) => {
impl AttributeAssociation for $struct_name {
fn attribute_level(&self, attributes: &Attributes) -> u32 {
attributes.agility().raw()
}
}
};
}
macro_rules! associate_intelligence {
($struct_name:ident) => {
impl AttributeAssociation for $struct_name {
fn attribute_level(&self, attributes: &Attributes) -> u32 {
attributes.intelligence().raw()
}
}
};
}
macro_rules! associate_strength {
($struct_name:ident) => {
impl AttributeAssociation for $struct_name {
fn attribute_level(&self, attributes: &Attributes) -> u32 {
attributes.strength().raw()
}
}
};
}

View file

@ -1,13 +0,0 @@
#[macro_use]
pub mod macros;
pub mod ability_slots;
pub mod attributes;
pub mod character_status;
pub mod crafting_materials;
pub mod inventory;
pub mod item_slots;
pub mod level;
pub mod npc_type;
pub mod statistic_types;
pub mod statistics;

View file

@ -1,101 +0,0 @@
use std::fmt::Display;
use std::str::FromStr;
use anyhow::{anyhow, Result};
use engine::prelude::*;
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct NPCNormal;
impl EntityComponent for NPCNormal {
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for NPCNormal {
fn debug_name() -> &'static str {
"NPCNormal"
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct NPCElite;
impl EntityComponent for NPCElite {
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for NPCElite {
fn debug_name() -> &'static str {
"NPCElite"
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct NPCBoss;
impl EntityComponent for NPCBoss {
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for NPCBoss {
fn debug_name() -> &'static str {
"NPCBoss"
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum NPCType {
#[default]
Normal,
Elite,
Boss,
}
impl From<NPCNormal> for NPCType {
fn from(_: NPCNormal) -> Self {
Self::Normal
}
}
impl From<NPCElite> for NPCType {
fn from(_: NPCElite) -> Self {
Self::Elite
}
}
impl From<NPCBoss> for NPCType {
fn from(_: NPCBoss) -> Self {
Self::Boss
}
}
impl FromStr for NPCType {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
match s.to_lowercase().as_str() {
"normal" => Ok(Self::Normal),
"elite" => Ok(Self::Elite),
"boss" => Ok(Self::Boss),
_ => Err(anyhow!("could not parse {s} as NPCType")),
}
}
}
impl Display for NPCType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
NPCType::Normal => write!(f, "normal"),
NPCType::Elite => write!(f, "elite"),
NPCType::Boss => write!(f, "boss"),
}
}
}

View file

@ -1,581 +0,0 @@
use anyhow::Result;
use engine::prelude::*;
use std::fmt;
use super::{attributes::Attributes, statistics::*};
use crate::components::macros::AttributeAssociation;
use crate::{config::items::ItemSettings, items::Rarities};
macro_rules! apply {
(
$self:ident, $attrib:ident,
($level: ident, $rarity_multiplier:ident, $item_settings:ident),
[$($elem:ident: $ty:ty $(,)? )+]
) => {
paste::paste! {
match &mut $self {
$(
Self::$elem(v) => {
v.set($level as $ty * $rarity_multiplier as $ty * $item_settings.$attrib.[< $elem:snake >]);
}
)+
}
}
};
}
#[derive(Debug, PartialEq, Eq)]
pub enum StrengthStatisticTypes {
AirResistance(AirResistance),
FireResistance(FireResistance),
WaterResistance(WaterResistance),
PhysicalDamage(PhysicalDamage),
Health(Health),
HealthRegeneration(HealthRegeneration),
}
impl StrengthStatisticTypes {
// total amount of types
pub const TYPE_COUNT: u32 = 6;
pub fn random() -> Self {
Random::range(0, Self::TYPE_COUNT).into()
}
pub fn apply_for_jewel(
mut self,
level: u32,
rarity: Rarities,
item_settings: &ItemSettings,
) -> Self {
let rarity_multiplier = item_settings.jewel_rarity_multiplier.from_rarity(rarity);
apply!(
self,
per_strength_stats,
(level, rarity_multiplier, item_settings),
[
AirResistance: u32,
FireResistance: u32,
WaterResistance: u32,
PhysicalDamage: u32,
Health: f32,
HealthRegeneration: f32
]
);
self
}
}
impl From<u32> for StrengthStatisticTypes {
fn from(n: u32) -> Self {
match n {
0 => Self::AirResistance(AirResistance::default()),
1 => Self::FireResistance(FireResistance::default()),
2 => Self::WaterResistance(WaterResistance::default()),
3 => Self::PhysicalDamage(PhysicalDamage::default()),
4 => Self::Health(Health::default()),
5 => Self::HealthRegeneration(HealthRegeneration::default()),
_ => panic!("can only convert number below TYPE_COUNT"),
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum AgilityStatisticTypes {
Armor(Armor),
CriticalHitChance(CriticalHitChance),
CriticalHitDamage(CriticalHitDamage),
}
impl AgilityStatisticTypes {
// total amount of types
pub const TYPE_COUNT: u32 = 3;
pub fn random() -> Self {
Random::range(0, Self::TYPE_COUNT).into()
}
pub fn apply_for_jewel(
mut self,
level: u32,
rarity: Rarities,
item_settings: &ItemSettings,
) -> Self {
let rarity_multiplier = item_settings.jewel_rarity_multiplier.from_rarity(rarity);
apply!(
self,
per_agility_stats,
(level, rarity_multiplier, item_settings),
[
Armor: u32,
CriticalHitChance: f32,
CriticalHitDamage: f32
]
);
self
}
}
impl From<u32> for AgilityStatisticTypes {
fn from(n: u32) -> Self {
match n {
0 => Self::Armor(Armor::default()),
1 => Self::CriticalHitChance(CriticalHitChance::default()),
2 => Self::CriticalHitDamage(CriticalHitDamage::default()),
_ => panic!("can only convert number below TYPE_COUNT"),
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum IntelligenceStatisticTypes {
AirDamage(AirDamage),
FireDamage(FireDamage),
WaterDamage(WaterDamage),
Mana(Mana),
ManaRegeneration(ManaRegeneration),
}
impl IntelligenceStatisticTypes {
// total amount of types
pub const TYPE_COUNT: u32 = 5;
pub fn random() -> Self {
Random::range(0, Self::TYPE_COUNT).into()
}
pub fn apply_for_jewel(
mut self,
level: u32,
rarity: Rarities,
item_settings: &ItemSettings,
) -> Self {
let rarity_multiplier = item_settings.jewel_rarity_multiplier.from_rarity(rarity);
apply!(
self,
per_intelligence_stats,
(level, rarity_multiplier, item_settings),
[
AirDamage: u32,
FireDamage: u32,
WaterDamage: u32,
Mana: f32,
ManaRegeneration: f32,
]
);
self
}
}
impl From<u32> for IntelligenceStatisticTypes {
fn from(n: u32) -> Self {
match n {
0 => Self::AirDamage(AirDamage::default()),
1 => Self::FireDamage(FireDamage::default()),
2 => Self::WaterDamage(WaterDamage::default()),
3 => Self::Mana(Mana::default()),
4 => Self::ManaRegeneration(ManaRegeneration::default()),
_ => panic!("can only convert number below TYPE_COUNT"),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub enum StatisticType {
AirResistance(AirResistance),
AirDamage(AirDamage),
FireResistance(FireResistance),
FireDamage(FireDamage),
WaterResistance(WaterResistance),
WaterDamage(WaterDamage),
Armor(Armor),
PhysicalDamage(PhysicalDamage),
CriticalHitChance(CriticalHitChance),
CriticalHitDamage(CriticalHitDamage),
Health(Health),
HealthRegeneration(HealthRegeneration),
Mana(Mana),
ManaRegeneration(ManaRegeneration),
}
impl StatisticType {
// total amount of types
pub const TYPE_COUNT: u32 = 14;
pub fn apply_for_item(&mut self, attributes: &Attributes, item_settings: &ItemSettings) {
match self {
// air
StatisticType::AirResistance(air_resistance) => air_resistance.set(
air_resistance.attribute_level(attributes)
* item_settings.per_strength_stats.air_resistance,
),
StatisticType::AirDamage(air_damage) => air_damage.set(
air_damage.attribute_level(attributes)
* item_settings.per_intelligence_stats.air_damage,
),
// fire
StatisticType::FireResistance(fire_resistance) => fire_resistance.set(
fire_resistance.attribute_level(attributes)
* item_settings.per_strength_stats.fire_resistance,
),
StatisticType::FireDamage(fire_damage) => fire_damage.set(
fire_damage.attribute_level(attributes)
* item_settings.per_intelligence_stats.fire_damage,
),
// water
StatisticType::WaterResistance(water_resistance) => water_resistance.set(
water_resistance.attribute_level(attributes)
* item_settings.per_strength_stats.water_resistance,
),
StatisticType::WaterDamage(water_damage) => water_damage.set(
water_damage.attribute_level(attributes)
* item_settings.per_intelligence_stats.water_damage,
),
// physical
StatisticType::Armor(armor) => {
armor.set(armor.attribute_level(attributes) * item_settings.per_agility_stats.armor)
}
StatisticType::PhysicalDamage(physical_damage) => physical_damage.set(
physical_damage.attribute_level(attributes)
* item_settings.per_strength_stats.physical_damage,
),
// crit
StatisticType::CriticalHitChance(crit_chance) => crit_chance.set(
crit_chance.attribute_level(attributes) as f32
* item_settings.per_agility_stats.critical_hit_chance,
),
StatisticType::CriticalHitDamage(crit_damage) => crit_damage.set(
crit_damage.attribute_level(attributes) as f32
* item_settings.per_agility_stats.critical_hit_damage,
),
// health
StatisticType::Health(health) => health.set(
health.attribute_level(attributes) as f32 * item_settings.per_strength_stats.health,
),
StatisticType::HealthRegeneration(health_regeneration) => health_regeneration.set(
health_regeneration.attribute_level(attributes) as f32
* item_settings.per_strength_stats.health_regeneration,
),
// mana
StatisticType::Mana(mana) => mana.set(
mana.attribute_level(attributes) as f32 * item_settings.per_intelligence_stats.mana,
),
StatisticType::ManaRegeneration(mana_regeneration) => mana_regeneration.set(
mana_regeneration.attribute_level(attributes) as f32
* item_settings.per_intelligence_stats.mana_regeneration,
),
}
}
pub fn apply_for_jewel(&mut self, rarity: Rarities, level: u32, item_settings: &ItemSettings) {
let factor = level * item_settings.jewel_rarity_multiplier.from_rarity(rarity);
match self {
// air
StatisticType::AirResistance(air_resistance) => {
air_resistance.set(factor * item_settings.per_strength_stats.air_resistance)
}
StatisticType::AirDamage(air_damage) => {
air_damage.set(factor * item_settings.per_intelligence_stats.air_damage)
}
// fire
StatisticType::FireResistance(fire_resistance) => {
fire_resistance.set(factor * item_settings.per_strength_stats.fire_resistance)
}
StatisticType::FireDamage(fire_damage) => {
fire_damage.set(factor * item_settings.per_intelligence_stats.fire_damage)
}
// water
StatisticType::WaterResistance(water_resistance) => {
water_resistance.set(factor * item_settings.per_strength_stats.water_resistance)
}
StatisticType::WaterDamage(water_damage) => {
water_damage.set(factor * item_settings.per_intelligence_stats.water_damage)
}
// physical
StatisticType::Armor(armor) => {
armor.set(factor * item_settings.per_agility_stats.armor)
}
StatisticType::PhysicalDamage(physical_damage) => {
physical_damage.set(factor * item_settings.per_strength_stats.physical_damage)
}
// crit
StatisticType::CriticalHitChance(crit_chance) => {
crit_chance.set(factor as f32 * item_settings.per_agility_stats.critical_hit_chance)
}
StatisticType::CriticalHitDamage(crit_damage) => {
crit_damage.set(factor as f32 * item_settings.per_agility_stats.critical_hit_damage)
}
// health
StatisticType::Health(health) => {
health.set(factor as f32 * item_settings.per_strength_stats.health)
}
StatisticType::HealthRegeneration(health_regeneration) => health_regeneration
.set(factor as f32 * item_settings.per_strength_stats.health_regeneration),
// mana
StatisticType::Mana(mana) => {
mana.set(factor as f32 * item_settings.per_intelligence_stats.mana)
}
StatisticType::ManaRegeneration(mana_regeneration) => mana_regeneration
.set(factor as f32 * item_settings.per_intelligence_stats.mana_regeneration),
}
}
pub fn same_type(&self, other: &Self) -> bool {
matches!(
(self, other),
(Self::AirResistance(_), Self::AirResistance(_))
| (Self::AirDamage(_), Self::AirDamage(_))
| (Self::FireResistance(_), Self::FireResistance(_))
| (Self::FireDamage(_), Self::FireDamage(_))
| (Self::WaterResistance(_), Self::WaterResistance(_))
| (Self::WaterDamage(_), Self::WaterDamage(_))
| (Self::Armor(_), Self::Armor(_))
| (Self::PhysicalDamage(_), Self::PhysicalDamage(_))
| (Self::CriticalHitChance(_), Self::CriticalHitChance(_))
| (Self::CriticalHitDamage(_), Self::CriticalHitDamage(_))
| (Self::Health(_), Self::Health(_))
| (Self::HealthRegeneration(_), Self::HealthRegeneration(_))
| (Self::Mana(_), Self::Mana(_))
| (Self::ManaRegeneration(_), Self::ManaRegeneration(_))
)
}
pub fn display_value(&self) -> String {
match *self {
// air
StatisticType::AirResistance(v) => format!("{}", v.raw()),
StatisticType::AirDamage(v) => format!("{}", v.raw()),
// fire
StatisticType::FireResistance(v) => format!("{}", v.raw()),
StatisticType::FireDamage(v) => format!("{}", v.raw()),
// water
StatisticType::WaterResistance(v) => format!("{}", v.raw()),
StatisticType::WaterDamage(v) => format!("{}", v.raw()),
// physical
StatisticType::Armor(v) => format!("{}", v.raw()),
StatisticType::PhysicalDamage(v) => format!("{}", v.raw()),
// crit
StatisticType::CriticalHitChance(v) => format!("{:.2} %", v.raw()),
StatisticType::CriticalHitDamage(v) => format!("{:.2} %", v.raw()),
// health
StatisticType::Health(v) => format!("{:.0}", v.raw()),
StatisticType::HealthRegeneration(v) => format!("{:.2}", v.raw()),
// mana
StatisticType::Mana(v) => format!("{:.0}", v.raw()),
StatisticType::ManaRegeneration(v) => format!("{:.2}", v.raw()),
}
}
}
impl std::ops::AddAssign for StatisticType {
fn add_assign(&mut self, other: StatisticType) {
match (self, &other) {
(Self::AirResistance(me), Self::AirResistance(val)) => *me += *val,
(Self::AirDamage(me), Self::AirDamage(val)) => *me += *val,
(Self::FireResistance(me), Self::FireResistance(val)) => *me += *val,
(Self::FireDamage(me), Self::FireDamage(val)) => *me += *val,
(Self::WaterResistance(me), Self::WaterResistance(val)) => *me += *val,
(Self::WaterDamage(me), Self::WaterDamage(val)) => *me += *val,
(Self::Armor(me), Self::Armor(val)) => *me += *val,
(Self::PhysicalDamage(me), Self::PhysicalDamage(val)) => *me += *val,
(Self::CriticalHitChance(me), Self::CriticalHitChance(val)) => *me += *val,
(Self::CriticalHitDamage(me), Self::CriticalHitDamage(val)) => *me += *val,
(Self::Health(me), Self::Health(val)) => *me += *val,
(Self::HealthRegeneration(me), Self::HealthRegeneration(val)) => *me += *val,
(Self::Mana(me), Self::Mana(val)) => *me += *val,
(Self::ManaRegeneration(me), Self::ManaRegeneration(val)) => *me += *val,
_ => panic!("can not add different types"),
}
}
}
impl From<u32> for StatisticType {
fn from(n: u32) -> Self {
match n {
// air
0 => Self::AirResistance(AirResistance::default()),
1 => Self::AirDamage(AirDamage::default()),
// fire
2 => Self::FireResistance(FireResistance::default()),
3 => Self::FireDamage(FireDamage::default()),
// water
4 => Self::WaterResistance(WaterResistance::default()),
5 => Self::WaterDamage(WaterDamage::default()),
// physical
6 => Self::Armor(Armor::default()),
7 => Self::PhysicalDamage(PhysicalDamage::default()),
// Crit
8 => Self::CriticalHitChance(CriticalHitChance::default()),
9 => Self::CriticalHitDamage(CriticalHitDamage::default()),
// health
10 => Self::Health(Health::default()),
11 => Self::HealthRegeneration(HealthRegeneration::default()),
// mana
12 => Self::Mana(Mana::default()),
13 => Self::ManaRegeneration(ManaRegeneration::default()),
_ => panic!("can only convert number below TYPE_COUNT"),
}
}
}
impl From<AgilityStatisticTypes> for StatisticType {
fn from(agility_statistics: AgilityStatisticTypes) -> Self {
match agility_statistics {
AgilityStatisticTypes::CriticalHitChance(v) => StatisticType::CriticalHitChance(v),
AgilityStatisticTypes::CriticalHitDamage(v) => StatisticType::CriticalHitDamage(v),
AgilityStatisticTypes::Armor(v) => StatisticType::Armor(v),
}
}
}
impl From<IntelligenceStatisticTypes> for StatisticType {
fn from(intelligence_statistics: IntelligenceStatisticTypes) -> Self {
match intelligence_statistics {
IntelligenceStatisticTypes::AirDamage(v) => StatisticType::AirDamage(v),
IntelligenceStatisticTypes::FireDamage(v) => StatisticType::FireDamage(v),
IntelligenceStatisticTypes::WaterDamage(v) => StatisticType::WaterDamage(v),
IntelligenceStatisticTypes::Mana(v) => StatisticType::Mana(v),
IntelligenceStatisticTypes::ManaRegeneration(v) => StatisticType::ManaRegeneration(v),
}
}
}
impl From<StrengthStatisticTypes> for StatisticType {
fn from(strength_statistics: StrengthStatisticTypes) -> Self {
match strength_statistics {
StrengthStatisticTypes::AirResistance(v) => StatisticType::AirResistance(v),
StrengthStatisticTypes::FireResistance(v) => StatisticType::FireResistance(v),
StrengthStatisticTypes::WaterResistance(v) => StatisticType::WaterResistance(v),
StrengthStatisticTypes::PhysicalDamage(v) => StatisticType::PhysicalDamage(v),
StrengthStatisticTypes::Health(v) => StatisticType::Health(v),
StrengthStatisticTypes::HealthRegeneration(v) => StatisticType::HealthRegeneration(v),
}
}
}
impl std::str::FromStr for StatisticType {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
match s {
"Air Resistance" => Ok(Self::AirResistance(AirResistance::default())),
"Air Damage" => Ok(Self::AirDamage(AirDamage::default())),
// fire
"Fire Resistance" => Ok(Self::FireResistance(FireResistance::default())),
"Fire Damage" => Ok(Self::FireDamage(FireDamage::default())),
// water
"Water Resistance" => Ok(Self::WaterResistance(WaterResistance::default())),
"Water Damage" => Ok(Self::WaterDamage(WaterDamage::default())),
// physical
"Armor" => Ok(Self::Armor(Armor::default())),
"Physical Damage" => Ok(Self::PhysicalDamage(PhysicalDamage::default())),
// Crit
"Critical Hit Chance" => Ok(Self::CriticalHitChance(CriticalHitChance::default())),
"Critical Hit Damage" => Ok(Self::CriticalHitDamage(CriticalHitDamage::default())),
// health
"Health" => Ok(Self::Health(Health::default())),
"Health Regeneration" => Ok(Self::HealthRegeneration(HealthRegeneration::default())),
// mana
"Mana" => Ok(Self::Mana(Mana::default())),
"Mana Regeneration" => Ok(Self::ManaRegeneration(ManaRegeneration::default())),
_ => {
return Err(anyhow::Error::msg(format!(
"Failed parsing StatisticTypes from {}",
s
)))
}
}
}
}
impl fmt::Display for StatisticType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
// air
StatisticType::AirResistance(_) => write!(f, "Air Resistance"),
StatisticType::AirDamage(_) => write!(f, "Air Damage"),
// fire
StatisticType::FireResistance(_) => write!(f, "Fire Resistance"),
StatisticType::FireDamage(_) => write!(f, "Fire Damage"),
// water
StatisticType::WaterResistance(_) => write!(f, "Water Resistance"),
StatisticType::WaterDamage(_) => write!(f, "Water Damage"),
// physical
StatisticType::Armor(_) => write!(f, "Armor"),
StatisticType::PhysicalDamage(_) => write!(f, "Physical Damage"),
// crit
StatisticType::CriticalHitChance(_) => write!(f, "Critical Hit Chance"),
StatisticType::CriticalHitDamage(_) => write!(f, "Critical Hit Damage"),
// health
StatisticType::Health(_) => write!(f, "Health"),
StatisticType::HealthRegeneration(_) => write!(f, "Health Regeneration"),
// mana
StatisticType::Mana(_) => write!(f, "Mana"),
StatisticType::ManaRegeneration(_) => write!(f, "Mana Regeneration"),
}
}
}

View file

@ -1,265 +0,0 @@
use engine::prelude::*;
use crate::{
config::{
attributes::{AgilitySettings, AttributeSettings, IntelligenceSettings, StrengthSettings},
items::ItemSettings,
},
damage_type::DamageType,
};
use super::{
attributes::{Agility, Attributes, Intelligence, Strength},
item_slots::ItemSlotContainer,
macros::AttributeAssociation,
statistic_types::StatisticType,
};
// --------------- elements ---------------
// Air
generate_stat!(AirResistance, u32, "Air Resistance");
associate_strength!(AirResistance);
generate_stat!(AirDamage, u32, "Air Damage");
associate_intelligence!(AirDamage);
// fire
generate_stat!(FireResistance, u32, "Fire Resistance");
associate_strength!(FireResistance);
generate_stat!(FireDamage, u32, "Fire Damage");
associate_intelligence!(FireDamage);
// water
generate_stat!(WaterResistance, u32, "Water Resistance");
associate_strength!(WaterResistance);
generate_stat!(WaterDamage, u32, "Water Damage");
associate_intelligence!(WaterDamage);
// physical
generate_stat!(Armor, u32, "Armor");
associate_agility!(Armor);
generate_stat!(PhysicalDamage, u32, "Physical Damage");
associate_strength!(PhysicalDamage);
// ---------------- special ---------------
// crit
generate_stat!(CriticalHitChance, f32, "Crit Chance");
associate_agility!(CriticalHitChance);
generate_stat!(CriticalHitDamage, f32, "Crit Damage");
associate_agility!(CriticalHitDamage);
// ----------------- base -----------------
// health
generate_stat!(Health, f32, "Health");
associate_strength!(Health);
impl std::ops::Add<HealthRegeneration> for Health {
type Output = Self;
fn add(self, other: HealthRegeneration) -> Self {
Health(self.0 + other.0)
}
}
impl std::ops::AddAssign<HealthRegeneration> for Health {
fn add_assign(&mut self, other: HealthRegeneration) {
self.0 += other.0
}
}
generate_stat!(HealthRegeneration, f32, "Health Regeneration");
associate_strength!(HealthRegeneration);
// mana
generate_stat!(Mana, f32, "Mana");
associate_intelligence!(Mana);
impl std::ops::Add<ManaRegeneration> for Mana {
type Output = Self;
fn add(self, other: ManaRegeneration) -> Self {
Mana(self.0 + other.0)
}
}
impl std::ops::AddAssign<ManaRegeneration> for Mana {
fn add_assign(&mut self, other: ManaRegeneration) {
self.0 += other.0
}
}
generate_stat!(ManaRegeneration, f32, "Mana Regeneration");
associate_intelligence!(ManaRegeneration);
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct Statistics {
// air
pub air_resistance: AirResistance,
pub air_damage: AirDamage,
// fire
pub fire_resistance: FireResistance,
pub fire_damage: FireDamage,
// water
pub water_resistance: WaterResistance,
pub water_damage: WaterDamage,
// physical
pub armor: Armor,
pub physical_damage: PhysicalDamage,
// crit
pub critical_hit_chance: CriticalHitChance,
pub critical_hit_damage: CriticalHitDamage,
// health
pub health: Health,
pub health_regeneration: HealthRegeneration,
// mana
pub mana: Mana,
pub mana_regeneration: ManaRegeneration,
}
impl Statistics {
pub fn update<'a, 'b>(
&mut self,
attributes: &mut Attributes,
attribute_settings: &AttributeSettings,
items: impl Into<Option<(&'a ItemSlotContainer, &'a ItemSettings)>>,
) {
*self = Self::default();
let items = items.into();
if let Some((items, item_settings)) = items {
let (agility, strength, intelligence) = items.collect_attribute_bonuses(item_settings);
attributes.bonus_agility = strength;
attributes.bonus_strength = agility;
attributes.bonus_intelligence = intelligence;
}
// agility
self.apply_agility(&attributes.agility(), &attribute_settings.agility_settings);
// strength
self.apply_strength(
&attributes.strength(),
&attribute_settings.strength_settings,
);
// intelligence
self.apply_intelligence(
&attributes.intelligence(),
&attribute_settings.intelligence_settings,
);
// apply items
if let Some((items, _)) = items {
let item_bonuses = items.collect_stat_bonuses();
for item_bonus in item_bonuses.into_iter() {
self.apply_bonus(item_bonus);
}
}
}
pub fn calculate_resistance(&self, damage_type: DamageType) -> u32 {
match damage_type {
DamageType::Air => self.air_resistance.raw(),
DamageType::Fire => self.fire_resistance.raw(),
DamageType::Water => self.water_resistance.raw(),
DamageType::Physical => self.armor.raw(),
}
}
fn apply_agility(&mut self, agility: &Agility, agility_settings: &AgilitySettings) {
self.armor += Armor(agility.raw() * agility_settings.armor);
self.critical_hit_chance +=
CriticalHitChance(agility.raw() as f32 * agility_settings.crit_chance);
self.critical_hit_damage +=
CriticalHitDamage(agility.raw() as f32 * agility_settings.crit_damage);
}
fn apply_strength(&mut self, strength: &Strength, strength_settings: &StrengthSettings) {
self.health += Health(strength.raw() as f32 * strength_settings.health);
self.health_regeneration +=
HealthRegeneration(strength.raw() as f32 * strength_settings.health_regen);
self.physical_damage += PhysicalDamage(strength.raw() * strength_settings.physical_damage);
self.air_resistance += AirResistance(strength.raw() * strength_settings.air_resistance);
self.fire_resistance += FireResistance(strength.raw() * strength_settings.fire_resistance);
self.water_resistance +=
WaterResistance(strength.raw() * strength_settings.water_resistance);
}
fn apply_intelligence(
&mut self,
intelligence: &Intelligence,
intelligence_settings: &IntelligenceSettings,
) {
self.mana += Mana(intelligence.raw() as f32 * intelligence_settings.mana);
self.mana_regeneration +=
ManaRegeneration(intelligence.raw() as f32 * intelligence_settings.mana_regen);
self.air_damage += AirDamage(intelligence.raw() * intelligence_settings.air_damage);
self.fire_damage += FireDamage(intelligence.raw() * intelligence_settings.fire_damage);
self.water_damage += WaterDamage(intelligence.raw() * intelligence_settings.water_damage);
}
fn apply_bonus(&mut self, stat_type: StatisticType) {
match stat_type {
StatisticType::AirResistance(air_resistance) => self.air_resistance += air_resistance,
StatisticType::AirDamage(air_damage) => self.air_damage += air_damage,
StatisticType::FireResistance(fire_resistance) => {
self.fire_resistance += fire_resistance
}
StatisticType::FireDamage(fire_damage) => self.fire_damage += fire_damage,
StatisticType::WaterResistance(water_resistance) => {
self.water_resistance += water_resistance
}
StatisticType::WaterDamage(water_damage) => self.water_damage += water_damage,
StatisticType::Armor(armor) => self.armor += armor,
StatisticType::PhysicalDamage(physical_damage) => {
self.physical_damage += physical_damage
}
StatisticType::CriticalHitChance(crit_chance) => {
self.critical_hit_chance += crit_chance
}
StatisticType::CriticalHitDamage(crit_damage) => {
self.critical_hit_damage += crit_damage
}
StatisticType::Health(health) => self.health += health,
StatisticType::HealthRegeneration(health_regen) => {
self.health_regeneration += health_regen
}
StatisticType::Mana(mana) => self.mana += mana,
StatisticType::ManaRegeneration(mana_regen) => self.mana_regeneration += mana_regen,
}
}
}
impl EntityComponent for Statistics {
fn name(&self) -> &str {
Self::debug_name()
}
}
impl ComponentDebug for Statistics {
fn debug_name() -> &'static str {
"Statistics"
}
}

View file

@ -1,182 +0,0 @@
use assetpath::AssetPath;
use engine::prelude::*;
create_settings_section!(
AbilityIcons,
"Icons",
{
// ability book
book: AssetPath,
// ability add ons
damage: AssetPath,
projectile_speed: AssetPath,
bounce: AssetPath,
explosion: AssetPath,
size: AssetPath,
additional_projectiles: AssetPath,
cool_down: AssetPath,
distance: AssetPath,
addon_background: AssetPath,
}
);
create_settings_section!(
AbilityLevel,
"AbilityLevel",
{
starting_cost: u32,
cost_per_level: u32,
},
Serialize, Deserialize,
);
create_settings_section!(
AbilitySlotCount,
"RarityAddOnSlots",
{
common: u32,
uncommon: u32,
magical: u32,
rare: u32,
epic: u32,
legendary: u32,
}
);
impl_from_rarity!(AbilitySlotCount, u32);
create_settings_section!(
IntelligencePageRequirements,
"IntelligencePerAbilityPage",
{
first: u32,
second: u32,
third: u32,
fourth: u32,
}
);
create_settings_section!(
ProjectileSpeedSettings,
"ProjectileSpeed",
{
common: f32,
uncommon: f32,
magical: f32,
rare: f32,
epic: f32,
legendary: f32,
}
);
impl_from_rarity!(ProjectileSpeedSettings, f32);
create_settings_section!(
DamageSettings,
"Damage",
{
common: u32,
uncommon: u32,
magical: u32,
rare: u32,
epic: u32,
legendary: u32,
}
);
impl_from_rarity!(DamageSettings, u32);
create_settings_section!(
ExplosionSettings,
"Explosion",
{
common: f32,
uncommon: f32,
magical: f32,
rare: f32,
epic: f32,
legendary: f32,
}
);
impl_from_rarity!(ExplosionSettings, f32);
create_settings_section!(
SizeSettings,
"Size",
{
common: f32,
uncommon: f32,
magical: f32,
rare: f32,
epic: f32,
legendary: f32,
}
);
impl_from_rarity!(SizeSettings, f32);
create_settings_section!(
AdditionalProjectilesSettings,
"AdditionalProjectiles",
{
common: u32,
uncommon: u32,
magical: u32,
rare: u32,
epic: u32,
legendary: u32,
}
);
impl_from_rarity!(AdditionalProjectilesSettings, u32);
create_settings_section!(
CoolDownReductionSettings,
"CoolDownReduction",
{
common: f32,
uncommon: f32,
magical: f32,
rare: f32,
epic: f32,
legendary: f32,
}
);
impl_from_rarity!(CoolDownReductionSettings, f32);
create_settings_section!(
DistanceSettings,
"Distance",
{
common: f32,
uncommon: f32,
magical: f32,
rare: f32,
epic: f32,
legendary: f32,
}
);
impl_from_rarity!(DistanceSettings, f32);
create_settings_container!(
AbilitySettings,
{
icons: AbilityIcons,
level: AbilityLevel,
slot_count: AbilitySlotCount,
page_requirements: IntelligencePageRequirements,
projectile_speed: ProjectileSpeedSettings,
damage: DamageSettings,
explosion: ExplosionSettings,
size: SizeSettings,
additional_projectiles: AdditionalProjectilesSettings,
cool_down: CoolDownReductionSettings,
distance: DistanceSettings,
}
);

View file

@ -1,95 +0,0 @@
use engine::prelude::*;
use crate::components::attributes::Attribute;
create_settings_section!(
AgilitySettings,
"Agility",
{
armor: u32,
crit_chance: f32,
crit_damage: f32,
}
);
create_settings_section!(
StrengthSettings,
"Strength",
{
physical_damage: u32,
air_resistance: u32,
fire_resistance: u32,
water_resistance: u32,
health: f32,
health_regen: f32,
}
);
create_settings_section!(
IntelligenceSettings,
"Intelligence",
{
air_damage: u32,
fire_damage: u32,
water_damage: u32,
mana: f32,
mana_regen: f32,
}
);
create_settings_section!(
MetaSection,
"Meta",
{
starting_skill_points: u32,
skill_points_per_level: u32,
}
);
create_settings_section!(
StartingAttributes,
"StartingAttributes",
{
strength: u32,
agility: u32,
intelligence: u32,
}
);
create_settings_section!(
AttributeColorSettings,
"AttributeColorSettings",
{
strength: Color,
agility: Color,
intelligence: Color,
}
);
impl AttributeColorSettings {
pub fn from_attribute(&self, attribute: Attribute) -> Color {
match attribute {
Attribute::Agility => self.agility,
Attribute::Intelligence => self.intelligence,
Attribute::Strength => self.strength,
}
}
}
impl StartingAttributes {
pub fn sum(&self) -> u32 {
self.strength + self.agility + self.intelligence
}
}
create_settings_container!(
AttributeSettings,
{
meta_settings: MetaSection,
agility_settings: AgilitySettings,
strength_settings: StrengthSettings,
intelligence_settings: IntelligenceSettings,
starting_attributes: StartingAttributes,
attribute_color_settings: AttributeColorSettings,
}
);

View file

@ -1,65 +0,0 @@
use engine::prelude::*;
use crate::components::npc_type::NPCType;
create_settings_section!(
ExperienceCurveSettings,
"Curve",
{
exponential: f32,
square: f32,
linear: f32,
starting_point: f32,
},
Serialize, Deserialize,
);
create_settings_section!(
MobExperienceSettings,
"Mobs",
{
linear: f32,
starting_point: f32,
elite_multiplier: f32,
boss_multiplier: f32,
},
Serialize, Deserialize,
);
create_settings_container!(
ExperienceSettings,
{
mob_experience: MobExperienceSettings,
experience_curve: ExperienceCurveSettings,
},
Serialize, Deserialize,
);
impl ExperienceSettings {
pub fn player_experience_needed(&self, level: u32) -> u32 {
let exponential = self.experience_curve.exponential;
let square = self.experience_curve.square;
let linear = self.experience_curve.linear;
let base = self.experience_curve.starting_point;
let level = level as f32;
// x^exponential + square * x^2 + linear * x + base
(level.powf(exponential) + square * level * level + linear * level + base) as u32
}
pub fn mob_experience(&self, level: u32, npc_type: impl Into<NPCType>) -> u32 {
let linear = self.mob_experience.linear;
let base = self.mob_experience.starting_point;
let level = level as f32;
let mut exp = linear * level + base;
match npc_type.into() {
NPCType::Normal => (),
NPCType::Elite => exp *= self.mob_experience.elite_multiplier,
NPCType::Boss => exp *= self.mob_experience.boss_multiplier,
}
exp as u32
}
}

View file

@ -1,194 +0,0 @@
use assetpath::AssetPath;
use engine::prelude::*;
create_settings_section!(
GeneralItemSettings,
"Meta",
{
attribute_multiplier: f32,
icon_target_width: u32,
icon_target_height: u32,
drop_chance: f32,
drop_chance_reference_level: u32,
drop_chance_elite_multiplier: f32,
drop_chance_boss_multiplier: f32,
jewel_level_multiplier: u32,
}
);
create_settings_section!(
RarityColorSettings,
"RarityColors",
{
common: Color,
uncommon: Color,
magical: Color,
rare: Color,
epic: Color,
legendary: Color,
}
);
impl_from_rarity!(RarityColorSettings, Color);
create_settings_section!(
RaritySlotSettings,
"RarityStatisticSlots",
{
common: u32,
uncommon: u32,
magical: u32,
rare: u32,
epic: u32,
legendary: u32,
}
);
impl_from_rarity!(RaritySlotSettings, u32);
create_settings_section!(
RarityDropRates,
"RarityDropRates",
{
common: f32,
uncommon: f32,
magical: f32,
rare: f32,
epic: f32,
legendary: f32,
}
);
impl_from_rarity!(RarityDropRates, f32);
create_settings_section!(
TypeDropRates,
"TypeDropRates",
{
item: f32,
ability_addon: f32,
ability_book: f32,
}
);
impl RarityDropRates {
pub fn verify(&self) {
let sum =
self.common + self.uncommon + self.magical + self.rare + self.epic + self.legendary;
assert!((sum - 1.0).abs() < 0.0001, "sum: {}", sum);
}
}
create_settings_section!(
PerStrengthLevelStatistics,
"PerStrengthStats",
{
health: f32,
health_regeneration: f32,
physical_damage: u32,
air_resistance: u32,
fire_resistance: u32,
water_resistance: u32,
}
);
create_settings_section!(
PerAgilityLevelStatistics,
"PerAgilityStats",
{
armor: u32,
critical_hit_chance: f32,
critical_hit_damage: f32,
}
);
create_settings_section!(
PerIntelligenceLevelStatistics,
"PerIntelligenceStats",
{
air_damage: u32,
fire_damage: u32,
water_damage: u32,
mana: f32,
mana_regeneration: f32,
}
);
create_settings_section!(
ItemIconPaths,
"ItemIcons",
{
amulet: AssetPath,
background: AssetPath,
boots: AssetPath,
chest: AssetPath,
helmet: AssetPath,
jewel: AssetPath,
ring: AssetPath,
belt: AssetPath,
gloves: AssetPath,
main_hand: AssetPath,
off_hand: AssetPath,
}
);
create_settings_section!(
ItemPlaceHolderIconPaths,
"ItemPlaceHolderIcons",
{
amulet: AssetPath,
boots: AssetPath,
chest: AssetPath,
helmet: AssetPath,
ring: AssetPath,
belt: AssetPath,
gloves: AssetPath,
main_hand: AssetPath,
off_hand: AssetPath,
}
);
create_settings_section!(
JewelIconPaths,
"JewelIcons",
{
first: AssetPath,
second: AssetPath,
third: AssetPath,
fourth: AssetPath,
}
);
create_settings_section!(
JewelRarityMultiplier,
"JewelRarityMultiplier",
{
common: u32,
uncommon: u32,
magical: u32,
rare: u32,
epic: u32,
legendary: u32,
}
);
impl_from_rarity!(JewelRarityMultiplier, u32);
create_settings_container!(
ItemSettings,
{
general: GeneralItemSettings,
icon_paths: ItemIconPaths,
icon_place_holder_paths: ItemPlaceHolderIconPaths,
jewel_paths: JewelIconPaths,
jewel_rarity_multiplier: JewelRarityMultiplier,
rarity_color_settings: RarityColorSettings,
rarity_slot_settings: RaritySlotSettings,
rarity_drop_rates: RarityDropRates,
type_drop_rates: TypeDropRates,
per_strength_stats: PerStrengthLevelStatistics,
per_agility_stats: PerAgilityLevelStatistics,
per_intelligence_stats: PerIntelligenceLevelStatistics,
}
);

View file

@ -1,25 +0,0 @@
#[macro_use]
pub mod create_section {
macro_rules! impl_from_rarity {
($struct_name:ident, $data_type:ty) => {
impl $struct_name {
pub fn from_rarity(&self, rarity: crate::items::Rarities) -> $data_type {
match rarity {
crate::items::Rarities::Common => self.common,
crate::items::Rarities::Uncommon => self.uncommon,
crate::items::Rarities::Magical => self.magical,
crate::items::Rarities::Rare => self.rare,
crate::items::Rarities::Epic => self.epic,
crate::items::Rarities::Legendary => self.legendary,
}
}
}
};
}
}
pub mod abilities;
pub mod attributes;
pub mod experience;
pub mod items;
pub mod save_game;

View file

@ -1,402 +0,0 @@
use anyhow::Result;
use engine::prelude::*;
use crate::{
components::{
ability_slots::AbilitySlots, attributes::Attributes, character_status::CharacterStatus,
crafting_materials::CraftingMaterials, inventory::Inventory, item_slots::ItemSlotContainer,
level::Level, statistics::Statistics,
},
items::{ItemAffix, ItemSystem, Rarities, ability_book::Ability},
};
use std::env::var;
use super::{attributes::AttributeSettings, experience::ExperienceSettings, items::ItemSettings};
#[cfg(target_os = "windows")]
pub fn save_game_dir(game: &str) -> String {
let b = var("LOCALAPPDATA").expect("couldn't get local appdata variable");
format!("{b}\\{game}\\saves\\")
}
#[cfg(target_os = "linux")]
pub fn save_game_dir(game: &str) -> String {
let b = var("HOME").expect("couldn't get HOME variable");
format!("{b}/.local/share/{game}/saves/")
}
create_settings_section!(
General,
"General",
{
level: u32,
exp: u32,
name: String,
strength: u32,
agility: u32,
intelligence: u32,
}
);
create_settings_section!(
HelmetSlot,
"Helmet",
{
used: bool,
level: u32,
strength: u32,
agility: u32,
intelligence: u32,
rarity: Rarities,
[affixes: ItemAffix],
}
);
create_settings_section!(
ChestPlateSlot,
"ChestPlate",
{
used: bool,
level: u32,
strength: u32,
agility: u32,
intelligence: u32,
rarity: Rarities,
[affixes: ItemAffix],
}
);
create_settings_section!(
BeltSlot,
"Belt",
{
used: bool,
level: u32,
strength: u32,
agility: u32,
intelligence: u32,
rarity: Rarities,
[affixes: ItemAffix],
}
);
create_settings_section!(
GlovesSlot,
"Gloves",
{
used: bool,
level: u32,
strength: u32,
agility: u32,
intelligence: u32,
rarity: Rarities,
[affixes: ItemAffix],
}
);
create_settings_section!(
BootsSlot,
"Boots",
{
used: bool,
level: u32,
strength: u32,
agility: u32,
intelligence: u32,
rarity: Rarities,
[affixes: ItemAffix],
}
);
create_settings_section!(
FirstRingSlot,
"Ring0",
{
used: bool,
level: u32,
strength: u32,
agility: u32,
intelligence: u32,
rarity: Rarities,
[affixes: ItemAffix],
}
);
create_settings_section!(
SecondRingSlot,
"Ring1",
{
used: bool,
level: u32,
strength: u32,
agility: u32,
intelligence: u32,
rarity: Rarities,
[affixes: ItemAffix],
}
);
create_settings_section!(
ThirdRingSlot,
"Ring2",
{
used: bool,
level: u32,
strength: u32,
agility: u32,
intelligence: u32,
rarity: Rarities,
[affixes: ItemAffix],
}
);
create_settings_section!(
FourthRingSlot,
"Ring3",
{
used: bool,
level: u32,
strength: u32,
agility: u32,
intelligence: u32,
rarity: Rarities,
[affixes: ItemAffix],
}
);
create_settings_section!(
FirstAmuletSlot,
"Amulet0",
{
used: bool,
level: u32,
strength: u32,
agility: u32,
intelligence: u32,
rarity: Rarities,
[affixes: ItemAffix],
}
);
create_settings_section!(
SecondAmuletSlot,
"Amulet1",
{
used: bool,
level: u32,
strength: u32,
agility: u32,
intelligence: u32,
rarity: Rarities,
[affixes: ItemAffix],
}
);
create_settings_section!(
MainHandSlot,
"MainHand",
{
used: bool,
level: u32,
strength: u32,
agility: u32,
intelligence: u32,
rarity: Rarities,
[affixes: ItemAffix],
}
);
create_settings_section!(
OffHandSlot,
"OffHand",
{
used: bool,
level: u32,
strength: u32,
agility: u32,
intelligence: u32,
rarity: Rarities,
[affixes: ItemAffix],
}
);
create_settings_section!(
InventorySave,
"Inventory",
{
[items: String],
[addons: String],
[books: String],
[jewels: String],
[maps: String],
}
);
create_settings_section!(
FirstAbilitySlot,
"FirstAbilitySlot",
{
used: bool,
name: String,
rarity: Rarities,
level: u32,
[addons: String],
}
);
create_settings_section!(
SecondAbilitySlot,
"SecondAbilitySlot",
{
used: bool,
name: String,
rarity: Rarities,
level: u32,
[addons: String],
}
);
create_settings_section!(
ThirdAbilitySlot,
"ThirdAbilitySlot",
{
used: bool,
name: String,
rarity: Rarities,
level: u32,
[addons: String],
}
);
create_settings_section!(
FourthAbilitySlot,
"FourthAbilitySlot",
{
used: bool,
name: String,
rarity: Rarities,
level: u32,
[addons: String],
}
);
create_settings_section!(
CraftingMaterialInfo,
"CraftingMaterials",
{
common: u32,
uncommon: u32,
magical: u32,
rare: u32,
epic: u32,
legendary: u32,
}
);
create_settings_section!(
PassivesInfo,
"Passives",
{
blood_mage: u32,
vampire: u32,
hermes: u32,
thick_skinned: u32,
super_brain: u32,
survivalist: u32,
soul_catcher: u32,
pyromancer: u32,
thunder_god: u32,
sea_monster: u32,
gladiator: u32,
lucky_devil: u32,
rogue: u32,
alchemist: u32,
}
);
create_settings_container!(
SaveGame,
{
general: General,
// items
helmet: HelmetSlot,
chest_plate: ChestPlateSlot,
belt: BeltSlot,
gloves: GlovesSlot,
boots: BootsSlot,
// rings
ring_0: FirstRingSlot,
ring_1: SecondRingSlot,
ring_2: ThirdRingSlot,
ring_3: FourthRingSlot,
// amulets
amulet_0: FirstAmuletSlot,
amulet_1: SecondAmuletSlot,
// hands
main_hand: MainHandSlot,
off_hand: OffHandSlot,
// inventory
inventory: InventorySave,
// abilities
ability_0: FirstAbilitySlot,
ability_1: SecondAbilitySlot,
ability_2: ThirdAbilitySlot,
ability_3: FourthAbilitySlot,
crafting_materials: CraftingMaterialInfo,
passives: PassivesInfo,
}
);
impl SaveGame {
pub fn to_entity_object<A: Ability + 'static>(
self,
world: &mut World,
) -> Result<(Entity, String)> {
let mut entity_object = AssetHandler::create(world).empty_entity();
entity_object.insert_component(Draw::new(Vec::new()));
entity_object.insert_component(Audio::new(world.resources.get_mut::<Context>(), None)?);
Location::new_and_setup(&mut entity_object)?;
let experience_settings = world.resources.get::<ExperienceSettings>();
let attribute_settings = world.resources.get::<AttributeSettings>();
let item_settings = world.resources.get::<ItemSettings>();
let item_system = world.resources.get::<ItemSystem<A>>();
let level = Level::load(self.general.level, self.general.exp, experience_settings);
let mut attributes = Attributes::load(
self.general.strength,
self.general.agility,
self.general.intelligence,
);
let inventory = Inventory::load(&self, &item_system)?;
let abilities = AbilitySlots::load(item_system, &self)?;
let crafting_materials = CraftingMaterials::load(&self);
let items = ItemSlotContainer::load(&self, &item_system)?;
let mut statistics = Statistics::default();
statistics.update(&mut attributes, attribute_settings, (&items, item_settings));
let current_status = CharacterStatus::new_full(&statistics);
entity_object.insert_component(level);
entity_object.insert_component(attributes);
entity_object.insert_component(inventory);
entity_object.insert_component(abilities);
entity_object.insert_component(crafting_materials);
entity_object.insert_component(items);
entity_object.insert_component(statistics);
entity_object.insert_component(current_status);
Ok((world.add_entity(entity_object)?, self.general.name))
}
}

View file

@ -1,64 +0,0 @@
use anyhow::Result;
use engine::prelude::*;
use std::convert::TryFrom;
#[derive(Clone, Debug, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DamageType {
Physical,
Fire,
Water,
Air,
// TODO: More
}
impl Default for DamageType {
fn default() -> Self {
Self::Physical
}
}
impl std::str::FromStr for DamageType {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
match s {
"Physical" => Ok(DamageType::Physical),
"Fire" => Ok(DamageType::Fire),
"Water" => Ok(DamageType::Water),
"Air" => Ok(DamageType::Air),
_ => {
return Err(anyhow::Error::msg(format!(
"Failed parsing DamageType from {}",
s
)))
}
}
}
}
impl std::fmt::Display for DamageType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
DamageType::Physical => write!(f, "Physical"),
DamageType::Fire => write!(f, "Fire"),
DamageType::Water => write!(f, "Water"),
DamageType::Air => write!(f, "Air"),
}
}
}
impl Into<Color> for DamageType {
fn into(self) -> Color {
// when changing colors, make sure you also change them in data/gui/xml/editor/abilityeditor/damage_type.xml
match self {
DamageType::Physical => {
Color::try_from("#A0522D").expect("could not convert sienna brown")
}
DamageType::Fire => Color::try_from("#F62817").expect("could not convert fire red"),
DamageType::Water => Color::try_from("#105EE3").expect("could not convert blue"),
DamageType::Air => Color::try_from("#20A6EB").expect("could not convert light blue"),
}
}
}

View file

@ -1,468 +0,0 @@
use anyhow::Result;
use assetpath::AssetPath;
use engine::prelude::*;
use std::{
fmt,
fmt::Debug,
hash::{Hash, Hasher},
slice::Iter,
str::FromStr,
};
use std::sync::Arc;
use crate::{components::inventory::Storable, config::abilities::AbilitySettings};
use super::{ability_book::Ability, ItemSystem, Rarities, Tooltip};
const COOL_DOWN_REDUCTION_CAP: f32 = 0.7;
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
pub enum AbilityAddonTypes {
Damage(u32),
ProjectileSpeed(f32),
Bounce,
Explosion(f32), // radius
Size(f32),
Projectiles(u32),
CoolDown(f32), // %
Distance(f32), // m
// TODO: Pierce, // bool
}
impl AbilityAddonTypes {
pub const COUNT: u32 = 8;
pub fn iter() -> Iter<'static, Self> {
use AbilityAddonTypes::*;
static ADD_ON_TYPES: [AbilityAddonTypes; AbilityAddonTypes::COUNT as usize] = [
Damage(0),
ProjectileSpeed(0.0),
Bounce,
Explosion(0.0),
Size(0.0),
Projectiles(0),
CoolDown(0.0),
Distance(0.0),
];
ADD_ON_TYPES.iter()
}
pub fn random() -> Self {
let n = Random::range(0, Self::COUNT);
Self::from(n)
}
pub fn val_as_str(&self) -> String {
match self {
Self::Damage(v) => format!("{} (*lvl)", v),
Self::ProjectileSpeed(v) => format!("+ {:.1}", v),
Self::Bounce => "Enabled".to_string(),
Self::Explosion(v) => format!("+ {:.1}", v),
Self::Size(v) => format!("+ {:.0}%", v * 100.0),
Self::Projectiles(v) => format!("+ {}", v),
Self::CoolDown(v) => format!("- {:.0}%", v * 100.0),
Self::Distance(v) => format!("+ {}", v),
}
}
pub fn apply_rarity(&mut self, rarity: Rarities, ability_settings: &AbilitySettings) {
match self {
Self::Damage(v) => *v = ability_settings.damage.from_rarity(rarity),
Self::ProjectileSpeed(v) => *v = ability_settings.projectile_speed.from_rarity(rarity),
Self::Bounce => (),
Self::Explosion(v) => *v = ability_settings.explosion.from_rarity(rarity),
Self::Size(v) => *v = ability_settings.size.from_rarity(rarity),
Self::Projectiles(v) => {
*v = ability_settings.additional_projectiles.from_rarity(rarity)
}
Self::CoolDown(v) => {
*v = ability_settings.cool_down.from_rarity(rarity);
}
Self::Distance(v) => *v = ability_settings.distance.from_rarity(rarity),
}
}
pub fn get_path<'a>(&self, ability_settings: &'a AbilitySettings) -> &'a AssetPath {
match self {
Self::Damage(_) => &ability_settings.icons.damage,
Self::ProjectileSpeed(_) => &ability_settings.icons.projectile_speed,
Self::Bounce => &ability_settings.icons.bounce,
Self::Explosion(_) => &ability_settings.icons.explosion,
Self::Size(_) => &ability_settings.icons.size,
Self::Projectiles(_) => &ability_settings.icons.additional_projectiles,
Self::CoolDown(_) => &ability_settings.icons.cool_down,
Self::Distance(_) => &ability_settings.icons.distance,
}
}
pub fn into_zero(self) -> Self {
match self {
AbilityAddonTypes::Damage(_) => AbilityAddonTypes::Damage(0),
AbilityAddonTypes::ProjectileSpeed(_) => AbilityAddonTypes::ProjectileSpeed(0.0),
AbilityAddonTypes::Bounce => AbilityAddonTypes::Bounce,
AbilityAddonTypes::Explosion(_) => AbilityAddonTypes::Explosion(0.0),
AbilityAddonTypes::Size(_) => AbilityAddonTypes::Size(0.0),
AbilityAddonTypes::Projectiles(_) => AbilityAddonTypes::Projectiles(0),
AbilityAddonTypes::CoolDown(_) => AbilityAddonTypes::CoolDown(0.0),
AbilityAddonTypes::Distance(_) => AbilityAddonTypes::Distance(0.0),
}
}
}
impl std::str::FromStr for AbilityAddonTypes {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
match s {
"Damage" => Ok(Self::Damage(0)),
"Projectile Speed" => Ok(Self::ProjectileSpeed(0.0)),
"Bounce" => Ok(Self::Bounce),
"Explosion" => Ok(Self::Explosion(0.0)),
"Size" => Ok(Self::Size(0.0)),
"Projectiles" => Ok(Self::Projectiles(0)),
"Cool Down" => Ok(Self::CoolDown(0.0)),
"Distance" => Ok(Self::Distance(0.0)),
_ => {
return Err(anyhow::Error::msg(format!(
"Failed parsing AbilityAddonTypes from {}",
s
)))
}
}
}
}
impl fmt::Display for AbilityAddonTypes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Damage(_) => write!(f, "Damage"),
Self::ProjectileSpeed(_) => write!(f, "Projectile Speed"),
Self::Bounce => write!(f, "Bounce"),
Self::Explosion(_) => write!(f, "Explosion"),
Self::Size(_) => write!(f, "Size"),
Self::Projectiles(_) => write!(f, "Projectiles"),
Self::CoolDown(_) => write!(f, "Cool Down"),
Self::Distance(_) => write!(f, "Distance"),
}
}
}
impl From<u32> for AbilityAddonTypes {
fn from(n: u32) -> Self {
match n {
0 => Self::Damage(0),
1 => Self::ProjectileSpeed(0.0),
2 => Self::Bounce,
3 => Self::Explosion(0.0),
4 => Self::Size(0.0),
5 => Self::Projectiles(0),
6 => Self::CoolDown(0.0),
7 => Self::Distance(0.0),
_ => panic!(
"can't convert AbilityAddonTypes from bigger than {}",
Self::COUNT
),
}
}
}
#[allow(clippy::derive_hash_xor_eq)]
impl Hash for AbilityAddonTypes {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
Self::Damage(_) => 0u32.hash(state),
Self::ProjectileSpeed(_) => 1u32.hash(state),
Self::Bounce => 2u32.hash(state),
Self::Explosion(_) => 3u32.hash(state),
Self::Size(_) => 4u32.hash(state),
Self::Projectiles(_) => 5u32.hash(state),
Self::CoolDown(_) => 6u32.hash(state),
Self::Distance(_) => 7u32.hash(state),
}
}
}
impl Eq for AbilityAddonTypes {}
#[derive(Debug, Clone)]
pub struct AbilityAddon {
icon: Arc<Image>,
addon_type: AbilityAddonTypes,
rarity: Rarities,
}
impl AbilityAddon {
pub fn new(rarity: Rarities, addon_type: AbilityAddonTypes, icon: Arc<Image>) -> Self {
AbilityAddon {
addon_type,
rarity,
icon,
}
}
pub fn load(
mut addon_type: AbilityAddonTypes,
rarity: Rarities,
icon: Arc<Image>,
ability_settings: &AbilitySettings,
) -> Self {
addon_type.apply_rarity(rarity, ability_settings);
AbilityAddon {
addon_type,
rarity,
icon,
}
}
pub fn addon_type(&self) -> &AbilityAddonTypes {
&self.addon_type
}
pub fn into_persistent(&self) -> String {
format!("{}|{}", self.addon_type(), self.rarity())
}
pub fn from_persistent<'a, A: Ability>(
mut split: impl Iterator<Item = &'a str>,
item_system: &ItemSystem<A>,
) -> Result<Self> {
let addon_type = AbilityAddonTypes::from_str(split.next().unwrap())?;
let rarity = Rarities::from_str(split.next().unwrap())?;
Ok(item_system.addon(rarity, addon_type))
}
pub fn create_tooltip(
&self,
gui_handler: &Arc<GuiHandler>,
position: (i32, i32),
) -> Result<Tooltip> {
let gui = GuiBuilder::from_str(
gui_handler,
include_str!("../../resources/addon_snippet.xml"),
)?;
let icon: Arc<Icon> = gui.element("addon_icon")?;
let rarity_label: Arc<Label> = gui.element("rarity_label")?;
let type_label: Arc<Label> = gui.element("type")?;
let value_label: Arc<Label> = gui.element("value")?;
let grid: Arc<Grid> = gui.element("addon_grid")?;
grid.change_position_unscaled(position.0, position.1)?;
icon.set_icon(&self.icon())?;
rarity_label.set_text(&format!("{}", self.rarity()))?;
type_label.set_text(&format!("{}", self.addon_type()))?;
value_label.set_text(&self.addon_type().val_as_str())?;
Ok(Tooltip::new(grid, gui, gui_handler.clone()))
}
}
impl PartialEq for AbilityAddon {
fn eq(&self, other: &Self) -> bool {
self.addon_type == other.addon_type && self.rarity == other.rarity
}
}
impl Storable for AbilityAddon {
fn rarity(&self) -> Rarities {
self.rarity
}
fn icon(&self) -> Arc<Image> {
self.icon.clone()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct AbilityAddonCollection {
attached_addons: usize,
addons: Vec<Option<AbilityAddon>>,
}
impl AbilityAddonCollection {
pub fn new(rarity: Rarities, ability_settings: &AbilitySettings) -> Self {
let addons = (0..ability_settings.slot_count.from_rarity(rarity))
.map(|_| None)
.collect();
Self {
attached_addons: 0,
addons,
}
}
pub fn load(
mut addons: Vec<Option<AbilityAddon>>,
rarity: Rarities,
ability_settings: &AbilitySettings,
) -> Self {
let attached = addons.len();
let max = ability_settings.slot_count.from_rarity(rarity) as usize;
assert!(attached <= max);
let difference = max - attached;
// fill not set addons with None
for _ in 0..difference {
addons.push(None);
}
Self {
attached_addons: attached,
addons,
}
}
pub fn iter(&self) -> Iter<'_, Option<AbilityAddon>> {
self.addons.iter()
}
pub fn len(&self) -> usize {
self.addons.len()
}
pub fn is_empty(&self) -> bool {
self.addons.is_empty()
}
pub fn has_free_addon_slots(&self) -> bool {
self.attached_addons < self.addons.len()
}
pub fn attached_count(&self) -> usize {
self.attached_addons
}
pub fn insert_addon(&mut self, addon: AbilityAddon) {
if !self.has_free_addon_slots() {
panic!("Ability Book does not have any free slots left");
}
self.addons[self.attached_addons] = Some(addon);
self.attached_addons += 1;
}
#[inline]
fn map<F>(&self, mut f: F)
where
F: FnMut(&AbilityAddonTypes),
{
for addon in self.addons.iter().flatten() {
f(addon.addon_type());
}
}
pub fn damage(&self) -> u32 {
let mut damage = 0;
self.map(|addon_type| {
if let AbilityAddonTypes::Damage(dmg) = addon_type {
damage += dmg;
}
});
damage
}
pub fn projectile_speed(&self) -> f32 {
let mut speed = 0.0;
self.map(|addon_type| {
if let AbilityAddonTypes::ProjectileSpeed(s) = addon_type {
speed += s;
}
});
speed
}
pub fn bounce(&self) -> bool {
let mut bounce = false;
self.map(|addon_type| {
if let AbilityAddonTypes::Bounce = addon_type {
bounce = true;
}
});
bounce
}
pub fn explosion_radius(&self) -> f32 {
let mut radius = 0.0;
self.map(|addon_type| {
if let AbilityAddonTypes::Explosion(r) = addon_type {
radius += r
}
});
radius
}
pub fn size(&self) -> f32 {
let mut size = 1.0;
self.map(|addon_type| {
if let AbilityAddonTypes::Size(s) = addon_type {
size += s;
}
});
size
}
pub fn additional_projectiles(&self) -> u32 {
let mut projectiles = 0;
self.map(|addon_type| {
if let AbilityAddonTypes::Projectiles(p) = addon_type {
projectiles += p;
}
});
projectiles
}
pub fn distance(&self) -> f32 {
let mut distance = 0.0;
self.map(|addon_type| {
if let AbilityAddonTypes::Distance(d) = addon_type {
distance += d;
}
});
distance
}
pub fn cool_down_reduction(&self) -> f32 {
let mut cdr = 0.0;
self.map(|addon_type| {
if let AbilityAddonTypes::CoolDown(cd) = addon_type {
cdr += cd;
}
});
let cap = COOL_DOWN_REDUCTION_CAP;
cdr.min(cap)
}
}

View file

@ -1,373 +0,0 @@
use anyhow::Result;
use assetpath::AssetPath;
use cgmath::{Vector2, Vector3};
use engine::prelude::*;
use std::{fmt::Debug, str::FromStr, sync::Arc, time::Duration};
use crate::{
components::{
character_status::CharacterStatus, crafting_materials::CraftingMaterials,
inventory::Storable, statistics::Statistics,
},
config::abilities::{AbilityLevel, AbilitySettings},
damage_type::DamageType,
};
use super::{
ItemSystem, Rarities, Tooltip,
ability_addon::{AbilityAddon, AbilityAddonCollection, AbilityAddonTypes},
};
pub trait Ability: Send + Sync + Clone + Default {
fn create(context: &Context, asset_path: impl Into<AssetPath>) -> Result<Self>;
fn name(&self) -> &str;
fn icon_path(&self) -> &AssetPath;
fn cool_down(&self) -> Duration;
fn mana_cost(&self) -> u32;
fn mana_cost_per_level(&self) -> u32;
fn damage_type(&self) -> DamageType;
fn base_damage(&self) -> u32;
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CastInformation {
time: PersistentDuration,
location: Vector3<f32>,
direction: Vector2<f32>,
}
#[derive(Clone)]
pub struct AbilityBook<A: Ability> {
ability: A,
// meta
icon: Arc<Image>,
rarity: Rarities,
// addons
addons: AbilityAddonCollection,
level: u32,
ability_level_settings: AbilityLevel,
// cool down
last_cast: Option<CastInformation>,
}
impl<A: Ability> AbilityBook<A> {
pub fn new(
ability: A,
icon: Arc<Image>,
rarity: Rarities,
ability_settings: &AbilitySettings,
) -> Self {
AbilityBook {
ability,
// meta
icon,
rarity,
// addons
addons: AbilityAddonCollection::new(rarity, ability_settings),
level: 1,
ability_level_settings: ability_settings.level.clone(),
// cool down
last_cast: None,
}
}
pub fn load(
ability: A,
icon: Arc<Image>,
rarity: Rarities,
addons: Vec<Option<AbilityAddon>>,
ability_settings: &AbilitySettings,
level: u32,
) -> Self {
AbilityBook {
ability,
icon,
rarity,
addons: AbilityAddonCollection::load(addons, rarity, ability_settings),
level,
ability_level_settings: ability_settings.level.clone(),
last_cast: None,
}
}
pub fn check_mana(&self, character_status: &mut CharacterStatus) -> Result<bool> {
let mana_costs = self.mana_cost();
Ok(character_status.use_ability(mana_costs as f32))
}
#[allow(clippy::too_many_arguments)]
pub fn validate_use(
&mut self,
now: Duration,
character_status: &mut CharacterStatus,
location: &Location,
) -> Result<bool> {
// don't allow anything while being animation locked
if let Some(cast_information) = &self.last_cast {
let total_cool_down = Duration::from_secs_f32({
let d = self.ability.cool_down();
d.as_secs_f32() * (1.0 - self.addons.cool_down_reduction())
});
if (now - cast_information.time.into()) <= total_cool_down {
return Ok(false);
}
}
if !self.check_mana(character_status)? {
return Ok(false);
}
// {
// // TODO: further separation of animation types (bows, ...)
// let animation_type = match self.ability.data().settings.parameter.damage_type {
// DamageType::Physical => AnimationType::Attack,
// _ => AnimationType::Cast,
// };
// animation_info.set_animation(
// animation,
// draw,
// Some(animation_type),
// now,
// true,
// false,
// )?;
// }
self.set_cast(now, location);
Ok(true)
}
fn set_cast(&mut self, now: Duration, location: &Location) {
self.last_cast = Some(CastInformation {
time: now.into(),
location: location.position(),
direction: location.direction(),
});
}
pub fn last_cast(&self) -> &Option<CastInformation> {
&self.last_cast
}
pub fn check_cool_down(&mut self, now: Duration) -> Option<f32> {
match &self.last_cast {
Some(cast_information) => {
let total_cool_down = Duration::from_secs_f32(
self.ability.cool_down().as_secs_f32()
* (1.0 - self.addons.cool_down_reduction()),
);
let diff = now - cast_information.time.into();
if diff <= total_cool_down {
Some((total_cool_down - diff).as_secs_f32())
} else {
self.last_cast = None;
None
}
}
None => None,
}
}
pub fn ability(&self) -> &A {
&self.ability
}
pub fn ability_mut(&mut self) -> &mut A {
&mut self.ability
}
pub fn addons(&self) -> &AbilityAddonCollection {
&self.addons
}
pub fn addons_mut(&mut self) -> &mut AbilityAddonCollection {
&mut self.addons
}
pub fn has_free_addon_slots(&self) -> bool {
self.addons.has_free_addon_slots()
}
pub fn attached_count(&self) -> usize {
self.addons.attached_count()
}
pub fn level(&self) -> u32 {
self.level
}
pub fn upgrade_cost(&self) -> u32 {
self.ability_level_settings.starting_cost
+ (self.level - 1) * self.ability_level_settings.cost_per_level
}
pub fn upgrade(&mut self, crafting_materials: &mut CraftingMaterials) -> bool {
if crafting_materials.consume(self.rarity, self.upgrade_cost()) {
self.level += 1;
true
} else {
false
}
}
// only used at a copy for ability upgrade tool tip
pub fn dummy_uprade(&mut self) {
self.level += 1;
}
pub fn mana_cost(&self) -> u32 {
self.ability.mana_cost() + self.ability.mana_cost_per_level() * (self.level - 1)
}
pub fn damage(&self, statistics: &Statistics) -> u32 {
// calculate damage of base ability
let ability_base_damage = self.ability.base_damage();
// get bonus damage from statistics
let stats_damage = match self.ability.damage_type() {
DamageType::Air => statistics.air_damage.raw(),
DamageType::Fire => statistics.fire_damage.raw(),
DamageType::Water => statistics.water_damage.raw(),
DamageType::Physical => statistics.physical_damage.raw(),
};
// damage from addons multiplied with level
let addon_damage = self.addons.damage() * self.level;
// sum up
ability_base_damage + (stats_damage * self.level) + addon_damage
}
pub fn into_persistent(&self) -> String {
let mut base = format!("{}|{}|{}", self.ability().name(), self.rarity(), self.level);
for addon in self.addons.iter().flatten() {
base = format!("{}|{}_{}", base, addon.addon_type(), addon.rarity());
}
base
}
pub fn from_persistent<'a>(
mut split: impl Iterator<Item = &'a str>,
item_system: &ItemSystem<A>,
) -> Result<Self> {
let name = split.next().unwrap();
let rarity = Rarities::from_str(split.next().unwrap())?;
let level = u32::from_str(split.next().unwrap())?;
let mut addons = Vec::new();
for addon in split {
let mut addon_split = addon.split('_');
let addon_type = AbilityAddonTypes::from_str(addon_split.next().unwrap())?;
let addon_rarity = Rarities::from_str(addon_split.next().unwrap())?;
addons.push(Some(item_system.addon(addon_rarity, addon_type)));
}
Ok(item_system.ability_book(name, rarity, addons, level))
}
pub fn create_tooltip(
&self,
gui_handler: &Arc<GuiHandler>,
statistics: &Statistics,
position: (i32, i32),
) -> Result<Tooltip> {
let gui = GuiBuilder::from_str(
gui_handler,
include_str!("../../resources/book_snippet.xml"),
)?;
let ability_name: Arc<Label> = gui.element("ability_name")?;
let rarity_label: Arc<Label> = gui.element("rarity_label")?;
let inspector_grid: Arc<Grid> = gui.element("item_grid")?;
let ability_icon: Arc<Icon> = gui.element("ability_icon")?;
let slot_info: Arc<Label> = gui.element("slot_info")?;
let level: Arc<Label> = gui.element("level")?;
inspector_grid.change_position_unscaled(position.0, position.1)?;
ability_icon.set_icon(&self.icon())?;
ability_name.set_text(self.ability().name())?;
rarity_label.set_text(&format!("{}", self.rarity()))?;
level.set_text(&format!("Lvl: {}", self.level()))?;
slot_info.set_text(&format!(
"Slots: {}/{}",
self.attached_count(),
self.addons().len()
))?;
let mana_costs: Arc<Label> = gui.element("mana_costs")?;
let damage: Arc<Label> = gui.element("damage")?;
let cooldown: Arc<Label> = gui.element("cooldown")?;
mana_costs.set_text(self.mana_cost())?;
damage.set_text(self.damage(statistics))?;
damage.set_text_color(self.ability.damage_type().into())?;
cooldown.set_text(format!(
"{:.1} s",
self.ability.cool_down().as_secs_f32() * (1.0 - self.addons().cool_down_reduction())
))?;
Ok(Tooltip::new(inspector_grid, gui, gui_handler.clone()))
}
}
impl<A: Ability> Debug for AbilityBook<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AbilityBook")
.field("rarity", &self.rarity)
.field("attached_addons", &self.attached_count())
.field("addons", &self.addons)
.field("last_cast", &self.last_cast)
.finish()
}
}
impl<A: Ability> PartialEq for AbilityBook<A> {
fn eq(&self, other: &Self) -> bool {
self.ability.name() == other.ability.name()
&& self.rarity == other.rarity
&& self.addons == other.addons
&& self.level == other.level
}
}
impl<A: Ability> Storable for AbilityBook<A> {
fn rarity(&self) -> Rarities {
self.rarity
}
fn icon(&self) -> Arc<Image> {
self.icon.clone()
}
}

View file

@ -1,356 +0,0 @@
use anyhow::Result;
use engine::prelude::*;
use std::str::FromStr;
use std::sync::Arc;
use crate::{
components::{
attributes::Attributes,
inventory::Storable,
statistic_types::{
AgilityStatisticTypes, IntelligenceStatisticTypes, StatisticType,
StrengthStatisticTypes,
},
},
config::items::ItemSettings,
};
use super::{ability_book::Ability, ItemSlots, ItemSystem, Jewel, Rarities, Tooltip};
const ITEM_SNIPPETS: [&'static str; 8] = [
include_str!("../../resources/items/slots_0.xml"),
include_str!("../../resources/items/slots_1.xml"),
include_str!("../../resources/items/slots_2.xml"),
include_str!("../../resources/items/slots_3.xml"),
include_str!("../../resources/items/slots_4.xml"),
include_str!("../../resources/items/slots_5.xml"),
include_str!("../../resources/items/slots_6.xml"),
include_str!("../../resources/items/slots_7.xml"),
];
#[derive(Debug, PartialEq, Clone)]
pub enum ItemAffix {
Socket(Option<Jewel>),
Stat(StatisticType),
}
impl ItemAffix {
pub fn from_persistent<'a>(
affix_type: &str,
split: &mut impl Iterator<Item = &'a str>,
) -> Result<Self> {
Ok(match affix_type {
"socket" => {
let socket_content = split.next().unwrap();
match socket_content {
"empty" => ItemAffix::Socket(None),
"some" => ItemAffix::Socket(Some(Jewel::from_persistent(split)?)),
_ => unreachable!(),
}
}
"stat" => ItemAffix::Stat(StatisticType::from_str(split.next().unwrap())?),
_ => unreachable!(),
})
}
fn squash<'a>(v: impl Iterator<Item = &'a Self>) -> (Vec<StatisticType>, Vec<Option<Jewel>>) {
let mut jewels = Vec::new();
let mut stats = Vec::new();
for affix in v {
match affix {
ItemAffix::Socket(j) => jewels.push(j.clone()),
ItemAffix::Stat(s) => Self::insert_stat(s, &mut stats),
}
}
(stats, jewels)
}
fn insert_stat(val: &StatisticType, vec: &mut Vec<StatisticType>) {
match vec
.iter_mut()
.find(|stat| StatisticType::same_type(stat, val))
{
Some(stat) => {
*stat += val.clone();
}
None => vec.push(val.clone()),
}
}
}
impl std::fmt::Display for ItemAffix {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ItemAffix::Socket(jewel) => match jewel {
Some(jewel) => write!(f, "socket|some|{}", jewel.into_persistent()),
None => write!(f, "socket|empty"),
},
ItemAffix::Stat(stat) => write!(f, "stat|{}", stat),
}
}
}
impl FromStr for ItemAffix {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
let mut split = s.split('|');
let affix_type = split.next().unwrap();
Self::from_persistent(affix_type, &mut split)
}
}
#[derive(Debug, Clone)]
pub struct Item {
pub rarity: Rarities,
pub slot: ItemSlots,
pub level: u32,
pub attributes: Attributes,
pub affixes: Vec<ItemAffix>,
pub icon: Arc<Image>,
}
impl Item {
const INSPECTOR_OFFSET: usize = 3;
pub fn empty(rarity: Rarities, slot: ItemSlots, level: u32, icon: Arc<Image>) -> Self {
Self {
rarity,
slot,
level,
attributes: Attributes::zero(),
affixes: Vec::new(),
icon,
}
}
pub fn into_persistent(&self) -> String {
let mut base = format!(
"{}|{}|{}|{}|{}|{}",
self.slot,
self.rarity,
self.level,
self.attributes.strength().raw(),
self.attributes.agility().raw(),
self.attributes.intelligence().raw(),
);
for affix in self.affixes.iter() {
base = format!("{base}|{affix}");
}
base
}
pub fn from_persistent<'a, A: Ability>(
mut split: impl Iterator<Item = &'a str>,
item_system: &ItemSystem<A>,
) -> Result<Self> {
let slot = ItemSlots::from_str(split.next().unwrap())?;
let rarity = Rarities::from_str(split.next().unwrap())?;
let level = split.next().unwrap().parse::<u32>()?;
let strength = split.next().unwrap().parse::<u32>()?;
let agility = split.next().unwrap().parse::<u32>()?;
let intelligence = split.next().unwrap().parse::<u32>()?;
let mut affixes = Vec::new();
while let Some(affix_type) = split.next() {
affixes.push({
let mut affix = ItemAffix::from_persistent(affix_type, &mut split)?;
if let ItemAffix::Socket(Some(jewel)) = &mut affix {
jewel.icon =
Some(item_system.jewel_icon(jewel.rarity, jewel.level, jewel.attribute));
}
affix
});
}
let attributes = Attributes::load(strength, agility, intelligence);
Ok(item_system.item(rarity, slot, level, attributes, affixes))
}
pub fn randomize_attributes(&mut self, item_settings: &ItemSettings) {
// sum of stats is the level
self.attributes
.randomize((self.level as f32 * item_settings.general.attribute_multiplier) as u32);
}
fn stat_type(&self, attribute_number: u32) -> Option<StatisticType> {
if attribute_number <= self.attributes.strength().raw()
&& self.attributes.strength().raw() != 0
{
return Some(StrengthStatisticTypes::random().into());
} else if attribute_number
<= (self.attributes.strength().raw() + self.attributes.agility().raw())
&& self.attributes.agility().raw() != 0
{
return Some(AgilityStatisticTypes::random().into());
} else if self.attributes.intelligence().raw() != 0 {
return Some(IntelligenceStatisticTypes::random().into());
}
None
}
pub fn randomize_stats(&mut self, item_settings: &ItemSettings) {
self.affixes = (0..item_settings.rarity_slot_settings.from_rarity(self.rarity))
.map(|_| {
if Random::range_f32(0.0, 1.0) < 0.05 {
ItemAffix::Socket(None)
} else {
let mut stat_type = None;
while stat_type.is_none() {
stat_type = self.stat_type(Random::range(0, self.attributes.sum()));
}
let mut stat_type = stat_type.unwrap();
stat_type.apply_for_item(&self.attributes, item_settings);
ItemAffix::Stat(stat_type)
}
})
.collect();
}
pub fn create_tooltip(
&self,
gui_handler: &Arc<GuiHandler>,
attributes: &Attributes,
position: (i32, i32),
) -> Result<Tooltip> {
let (stats, jewels) = ItemAffix::squash(self.affixes.iter());
let count = stats.len() + jewels.len();
let inspector_snippet = GuiBuilder::from_str(gui_handler, &ITEM_SNIPPETS[count])?;
let item_icon: Arc<Icon> = inspector_snippet.element("item_icon")?;
let slot_label: Arc<Label> = inspector_snippet.element("slot_label")?;
let level_label: Arc<Label> = inspector_snippet.element("level_label")?;
let rarity_label: Arc<Label> = inspector_snippet.element("rarity_label")?;
let strength_field: Arc<Label> = inspector_snippet.element("strength_field")?;
let agility_field: Arc<Label> = inspector_snippet.element("agility_field")?;
let intelligence_field: Arc<Label> = inspector_snippet.element("intelligence_field")?;
let inspector_grid: Arc<Grid> = inspector_snippet.element("item_grid")?;
inspector_grid.change_position_unscaled(position.0, position.1)?;
item_icon.set_icon(&self.icon)?;
slot_label.set_text(&format!("{}", self.slot))?;
level_label.set_text(&format!("Lvl {}", self.level))?;
rarity_label.set_text(&format!("{}", self.rarity))?;
strength_field.set_text(&format!("{}", self.attributes.strength().raw()))?;
agility_field.set_text(&format!("{}", self.attributes.agility().raw()))?;
intelligence_field.set_text(&format!("{}", self.attributes.intelligence().raw()))?;
let too_low_stat_background = FillTypeInfo::Element(
ElementDescriptor::new(Color::try_from("#AB7474")?, Color::try_from("#824040")?, 2),
DisplayableFillType::Expand,
);
if attributes.strength().raw() < self.attributes.strength().raw() {
strength_field.set_background(too_low_stat_background.clone())?;
}
if attributes.agility().raw() < self.attributes.agility().raw() {
agility_field.set_background(too_low_stat_background.clone())?;
}
if attributes.intelligence().raw() < self.attributes.intelligence().raw() {
intelligence_field.set_background(too_low_stat_background)?;
}
let mut index = Self::INSPECTOR_OFFSET;
for stat in stats {
let stat_type_snippet = GuiSnippet::from_str(
gui_handler,
include_str!("../../resources/stat_type_snippet.xml"),
)?;
let stat_type_label: Arc<Label> = stat_type_snippet.element("stat_type")?;
let stat_value_label: Arc<Label> = stat_type_snippet.element("stat_value")?;
stat_type_label.set_text(&format!("{}", stat))?;
stat_value_label.set_text(&stat.display_value())?;
inspector_grid.attach(stat_type_snippet, 0, index, 1, 1)?;
index += 1;
}
for jewel in jewels {
let socket_snippet = GuiSnippet::from_str(
gui_handler,
include_str!("../../resources/item_socket_snippet.xml"),
)?;
let socket_icon: Arc<Icon> = socket_snippet.element("socket_icon")?;
let stat_type: Arc<Label> = socket_snippet.element("stat_type")?;
match jewel {
Some(jewel) => {
socket_icon.set_icon(&jewel.icon())?;
stat_type.set_text(format!("{}", jewel.stat))?;
}
None => {
socket_icon.set_icon(
&Image::from_slice(include_bytes!("../../resources/circle.png"))?
.attach_sampler(Sampler::pretty_sampler().build(gui_handler.device())?)
.build(gui_handler.device(), gui_handler.queue())?,
)?;
stat_type.set_text("Empty Socket")?
}
}
inspector_grid.attach(socket_snippet, 0, index, 1, 1)?;
index += 1;
}
Ok(Tooltip::new(
inspector_grid,
inspector_snippet,
gui_handler.clone(),
))
}
}
impl PartialEq for Item {
fn eq(&self, other: &Self) -> bool {
self.rarity == other.rarity
&& self.slot == other.slot
&& self.level == other.level
&& self.attributes == other.attributes
&& self.affixes == other.affixes
}
}
impl Storable for Item {
fn rarity(&self) -> Rarities {
self.rarity
}
fn icon(&self) -> Arc<Image> {
self.icon.clone()
}
}

View file

@ -1,116 +0,0 @@
use anyhow::Result;
use assetpath::AssetPath;
use engine::prelude::*;
use std::{fmt, slice::Iter};
use crate::config::items::ItemSettings;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ItemSlots {
Helmet,
ChestPlate,
Belt,
Gloves,
Boots,
Ring,
Amulet,
MainHand,
OffHand,
}
impl ItemSlots {
const COUNT: u32 = 9;
pub fn iter() -> Iter<'static, ItemSlots> {
use ItemSlots::*;
static SLOTS: [ItemSlots; ItemSlots::COUNT as usize] = [
Helmet, ChestPlate, Belt, Gloves, Boots, Ring, Amulet, MainHand, OffHand,
];
SLOTS.iter()
}
pub fn random() -> Self {
let n = Random::range(0, Self::COUNT);
n.into()
}
pub fn get_path<'a>(&self, item_settings: &'a ItemSettings) -> &'a AssetPath {
use ItemSlots::*;
match self {
Helmet => &item_settings.icon_paths.helmet,
ChestPlate => &item_settings.icon_paths.chest,
Belt => &item_settings.icon_paths.belt,
Gloves => &item_settings.icon_paths.gloves,
Boots => &item_settings.icon_paths.boots,
Ring => &item_settings.icon_paths.ring,
Amulet => &item_settings.icon_paths.amulet,
MainHand => &item_settings.icon_paths.main_hand,
OffHand => &item_settings.icon_paths.off_hand,
}
}
}
impl From<u32> for ItemSlots {
fn from(n: u32) -> Self {
use ItemSlots::*;
match n {
0 => Helmet,
1 => ChestPlate,
2 => Belt,
3 => Gloves,
4 => Boots,
5 => Ring,
6 => Amulet,
7 => MainHand,
8 => OffHand,
_ => panic!("can only convert number below COUNT"),
}
}
}
impl std::str::FromStr for ItemSlots {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
match s {
"Helmet" => Ok(Self::Helmet),
"Chest Plate" => Ok(Self::ChestPlate),
"Belt" => Ok(Self::Belt),
"Gloves" => Ok(Self::Gloves),
"Boots" => Ok(Self::Boots),
"Ring" => Ok(Self::Ring),
"Amulet" => Ok(Self::Amulet),
"Main Hand" => Ok(Self::MainHand),
"Off Hand" => Ok(Self::OffHand),
_ => {
return Err(anyhow::Error::msg(format!(
"Failed parsing ItemSlots from {}",
s
)))
}
}
}
}
impl fmt::Display for ItemSlots {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use ItemSlots::*;
match self {
Helmet => write!(f, "Helmet"),
ChestPlate => write!(f, "Chest Plate"),
Belt => write!(f, "Belt"),
Gloves => write!(f, "Gloves"),
Boots => write!(f, "Boots"),
Ring => write!(f, "Ring"),
Amulet => write!(f, "Amulet"),
MainHand => write!(f, "Main Hand"),
OffHand => write!(f, "Off Hand"),
}
}
}

View file

@ -1,791 +0,0 @@
use anyhow::Result;
use assetpath::AssetPath;
use engine::prelude::*;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use image::{DynamicImage, ImageBuffer, Pixel, Rgba, RgbaImage, imageops::FilterType};
use crate::components::attributes::{Attribute, Attributes};
use crate::components::inventory::Storable;
use crate::components::statistic_types::{
AgilityStatisticTypes, IntelligenceStatisticTypes, StatisticType, StrengthStatisticTypes,
};
use crate::config::abilities::AbilitySettings;
use crate::config::attributes::AttributeSettings;
use crate::config::items::ItemSettings;
use super::ability_addon::{AbilityAddon, AbilityAddonTypes};
use super::ability_book::{Ability, AbilityBook};
use super::{Item, ItemAffix, ItemSlots, Jewel, Rarities};
#[derive(Debug, Clone, PartialEq)]
pub enum Loot<A: Ability> {
Item(Item),
AbilityBook(AbilityBook<A>),
AbilityAddOn(AbilityAddon),
Jewel(Jewel),
}
impl<A: Ability> Loot<A> {
pub fn storable(&self) -> &dyn Storable {
match self {
Loot::Item(item) => item,
Loot::AbilityBook(book) => book,
Loot::AbilityAddOn(addon) => addon,
Loot::Jewel(jewel) => jewel,
}
}
}
pub struct ItemSystem<A: Ability> {
pub item_settings: ItemSettings,
ability_settings: AbilitySettings,
item_icon_combinations: HashMap<(Rarities, ItemSlots), Arc<Image>>,
ability_icon_combinations: HashMap<(Rarities, String), Arc<Image>>,
addon_icon_combinations: HashMap<(Rarities, AbilityAddonTypes), Arc<Image>>,
jewel_icon_combinations: HashMap<(Rarities, u32, Attribute), Arc<Image>>,
abilities: Vec<A>,
}
impl<A: Ability> ItemSystem<A> {
pub fn new(
context: &Context,
item_settings: &ItemSettings,
ability_settings: &AbilitySettings,
attribute_settings: &AttributeSettings,
ability_directory: &AssetPath,
) -> Result<Self> {
// verify that drop chances sum up to 1.0
item_settings.rarity_drop_rates.verify();
let abilities = search_dir_recursively(&ability_directory.full_path(), ".abil")?
.into_iter()
.map(|path| A::create(context, path))
.collect::<Result<Vec<A>>>()?;
let (
item_icon_combinations,
ability_icon_combinations,
addon_icon_combinations,
jewel_icon_combinations,
) = {
// images for item slots
let mut slot_images = Vec::new();
for slot in ItemSlots::iter() {
let path = slot.get_path(item_settings);
if !path.is_empty() {
slot_images.push((*slot, Self::dyn_image(path)?));
}
}
// image for ability book
let mut ability_images = Vec::new();
for loader in abilities.iter() {
let path = loader.icon_path();
if !path.is_empty() {
ability_images.push((loader.name().to_string(), Self::dyn_image(path)?));
}
}
// images for ability addons
let mut ability_addon_images = Vec::new();
for addon in AbilityAddonTypes::iter() {
let path = addon.get_path(ability_settings);
if !path.is_empty() {
ability_addon_images.push((*addon, Self::dyn_image(path)?));
}
}
// images for jewels
let mut jewel_images = Vec::new();
jewel_images.push((0, Self::dyn_image(&item_settings.jewel_paths.first)?));
jewel_images.push((1, Self::dyn_image(&item_settings.jewel_paths.second)?));
jewel_images.push((2, Self::dyn_image(&item_settings.jewel_paths.third)?));
jewel_images.push((3, Self::dyn_image(&item_settings.jewel_paths.fourth)?));
// resize all icons to have the same size
let (
item_icons,
base_background,
ability_icons,
ability_addon_icons,
addon_background,
jewel_icons,
) = Self::equalize_images(
slot_images,
Self::dyn_image(&item_settings.icon_paths.background)?,
ability_images,
ability_addon_images,
Self::dyn_image(&ability_settings.icons.addon_background)?,
jewel_images,
item_settings,
);
// create item icon combination for every slot and rarity
let mut item_icon_combinations = HashMap::new();
for (slot, icon) in item_icons.iter() {
for rarity in Rarities::iter() {
let final_image = Self::blend_background(
icon,
&rarity.apply_color::<A>(
&base_background,
&item_settings.rarity_color_settings,
),
);
let (width, height) = final_image.dimensions();
item_icon_combinations.insert(
(*rarity, *slot),
Self::create_icon(
context.device(),
context.queue(),
width,
height,
final_image.into_raw(),
)?,
);
}
}
// create book icon combination for every rarity
let mut ability_icon_combinations = HashMap::new();
for (name, image) in ability_icons.iter() {
for rarity in Rarities::iter() {
let final_image = Self::blend_background(
&image,
&rarity.apply_color::<A>(
&base_background,
&item_settings.rarity_color_settings,
),
);
let (width, height) = final_image.dimensions();
ability_icon_combinations.insert(
(*rarity, name.clone()),
Self::create_icon(
context.device(),
context.queue(),
width,
height,
final_image.into_raw(),
)?,
);
}
}
// create addon icon combinatiosn for every rarity and addon type
let mut addon_icon_combinations = HashMap::new();
for (addon_type, icon) in ability_addon_icons.iter() {
for rarity in Rarities::iter() {
let final_image = Self::blend_apply(
&rarity.apply_color::<A>(
&addon_background,
&item_settings.rarity_color_settings,
),
icon,
|rarity_color, icon_color| {
// let (r1, g1, b1, a1) = rarity_color.channels4();
let c2 = icon_color.channels();
if c2[3] == 0 {
rarity_color
} else {
Rgba([c2[0], c2[1], c2[2], 255])
}
},
);
let (width, height) = final_image.dimensions();
addon_icon_combinations.insert(
(*rarity, *addon_type),
Self::create_icon(
context.device(),
context.queue(),
width,
height,
final_image.into_raw(),
)?,
);
}
}
// create jewel icons for every rarity
let mut jewel_icon_combinations = HashMap::new();
for (level, icon) in jewel_icons.iter() {
for attribute in Attribute::iter() {
let attribute_icon = attribute
.apply_color::<A>(&icon, &attribute_settings.attribute_color_settings);
for rarity in Rarities::iter() {
let final_image = Self::blend_background(
&attribute_icon,
&rarity.apply_color::<A>(
&base_background,
&item_settings.rarity_color_settings,
),
);
let (width, height) = final_image.dimensions();
jewel_icon_combinations.insert(
(*rarity, *level + 1, *attribute),
Self::create_icon(
context.device(),
context.queue(),
width,
height,
final_image.into_raw(),
)?,
);
}
}
}
(
item_icon_combinations,
ability_icon_combinations,
addon_icon_combinations,
jewel_icon_combinations,
)
};
Ok(ItemSystem {
item_settings: item_settings.clone(),
ability_settings: ability_settings.clone(),
item_icon_combinations,
ability_icon_combinations,
addon_icon_combinations,
jewel_icon_combinations,
abilities,
})
}
#[inline]
fn create_icon(
device: &Arc<Device>,
queue: &Arc<Mutex<Queue>>,
width: u32,
height: u32,
data: Vec<u8>,
) -> Result<Arc<Image>> {
Image::from_raw(data, width, height)
.format(VK_FORMAT_R8G8B8A8_UNORM)
.attach_sampler(Sampler::nearest_sampler().build(device)?)
.build(device, queue)
}
/// only here for debugging
pub fn get_legendary_random_loot(&self, level: u32) -> Loot<A> {
// decide which type of loot gets dropped
let loot_type_p = Random::range_f32(0.0, 1.0);
let rarity = Rarities::Legendary;
// decide if an item gets dropped
if loot_type_p >= 0.0 && loot_type_p <= self.item_settings.type_drop_rates.item {
Loot::Item(self.random_item(level, rarity))
}
// decide if an ability addon gets dropped
else if loot_type_p > self.item_settings.type_drop_rates.item
&& loot_type_p
<= self.item_settings.type_drop_rates.item
+ self.item_settings.type_drop_rates.ability_addon
{
Loot::AbilityAddOn(self.random_ability_addon(rarity))
}
// drop an ability book
else {
if Coin::flip(0.5) {
Loot::AbilityBook(self.random_ability_book(rarity))
} else {
Loot::Jewel(self.random_jewel(level, rarity))
}
}
}
pub fn get_random_loot(
&self,
level: u32,
drop_chance_multiplier: Option<f32>,
) -> Option<Loot<A>> {
let drop_chance = match drop_chance_multiplier {
Some(multiplier) => multiplier * self.item_settings.general.drop_chance,
None => self.item_settings.general.drop_chance,
};
// decide if loot gets dropped at all
if !Coin::flip(drop_chance) {
return None;
}
// decide which type of loot gets dropped
let loot_type_p = Random::range_f32(0.0, 1.0);
let rarity = Rarities::random(&self.item_settings, level);
// decide if an item gets dropped
if loot_type_p >= 0.0 && loot_type_p <= self.item_settings.type_drop_rates.item {
Some(Loot::Item(self.random_item(level, rarity)))
}
// decide if an ability addon gets dropped
else if loot_type_p > self.item_settings.type_drop_rates.item
&& loot_type_p
<= self.item_settings.type_drop_rates.item
+ self.item_settings.type_drop_rates.ability_addon
{
Some(Loot::AbilityAddOn(self.random_ability_addon(rarity)))
}
// drop an ability book
else {
if Coin::flip(0.5) {
Some(Loot::AbilityBook(self.random_ability_book(rarity)))
} else {
Some(Loot::Jewel(self.random_jewel(1, rarity)))
}
}
}
pub fn random_ability_book(&self, rarity: Rarities) -> AbilityBook<A> {
let ability = self.random_ability();
let ability_icon = self.ability_icon(ability.name(), rarity);
AbilityBook::new(ability, ability_icon, rarity, &self.ability_settings)
}
pub fn ability_addon(
&self,
rarity: Rarities,
mut addon_type: AbilityAddonTypes,
) -> AbilityAddon {
let icon = self.addon_icon(rarity, addon_type);
addon_type.apply_rarity(rarity, &self.ability_settings);
AbilityAddon::new(rarity, addon_type, icon)
}
pub fn random_ability_addon(&self, rarity: Rarities) -> AbilityAddon {
self.ability_addon(rarity, AbilityAddonTypes::random())
}
pub fn random_item(&self, level: u32, rarity: Rarities) -> Item {
let slot = ItemSlots::random();
let mut item = Item::empty(rarity, slot, level, self.item_icon(rarity, slot));
item.randomize_attributes(&self.item_settings);
item.randomize_stats(&self.item_settings);
item
}
pub fn random_jewel(&self, level: u32, rarity: Rarities) -> Jewel {
let attribute = Attribute::random();
let stat = match attribute {
Attribute::Agility => AgilityStatisticTypes::random()
.apply_for_jewel(level, rarity, &self.item_settings)
.into(),
Attribute::Intelligence => IntelligenceStatisticTypes::random()
.apply_for_jewel(level, rarity, &self.item_settings)
.into(),
Attribute::Strength => StrengthStatisticTypes::random()
.apply_for_jewel(level, rarity, &self.item_settings)
.into(),
};
self.jewel(rarity, level, attribute, stat)
}
pub fn item(
&self,
rarity: Rarities,
slot: ItemSlots,
level: u32,
attributes: Attributes,
mut affixes: Vec<ItemAffix>,
) -> Item {
for affix in affixes.iter_mut() {
match affix {
ItemAffix::Socket(_) => (),
ItemAffix::Stat(stat) => stat.apply_for_item(&attributes, &self.item_settings),
}
}
Item {
rarity,
slot,
level,
attributes,
affixes,
icon: self.item_icon(rarity, slot),
}
}
pub fn addon(&self, rarity: Rarities, addon_type: AbilityAddonTypes) -> AbilityAddon {
AbilityAddon::load(
addon_type,
rarity,
self.addon_icon(rarity, addon_type),
&self.ability_settings,
)
}
pub fn ability_book(
&self,
ability_name: &str,
rarity: Rarities,
addons: Vec<Option<AbilityAddon>>,
level: u32,
) -> AbilityBook<A> {
AbilityBook::load(
self.find_ability(ability_name),
self.ability_icon(ability_name, rarity),
rarity,
addons,
&self.ability_settings,
level,
)
}
pub fn jewel(
&self,
rarity: Rarities,
level: u32,
attribute: Attribute,
stat: StatisticType,
) -> Jewel {
Jewel {
rarity,
level,
attribute,
stat,
icon: Some(self.jewel_icon(rarity, level, attribute)),
}
}
fn dyn_image(path: &AssetPath) -> Result<DynamicImage> {
Ok(image::open(&path.full_path())?)
}
#[inline]
pub fn item_icon(&self, rarity: Rarities, slot: ItemSlots) -> Arc<Image> {
self.item_icon_combinations
.get(&(rarity, slot))
.unwrap_or_else(|| {
panic!(
"no icon for combination ({:?} {:?}) present\navailable: {:#?}",
rarity,
slot,
self.item_icon_combinations.keys()
)
})
.clone()
}
#[inline]
pub fn addon_icon(&self, rarity: Rarities, addon_type: AbilityAddonTypes) -> Arc<Image> {
let addon_type = addon_type.into_zero();
self.addon_icon_combinations
.get(&(rarity, addon_type))
.unwrap_or_else(|| {
panic!(
"no addon icon present for rarity ({}) and addon type ({:?})\navailable: {:#?}",
rarity,
addon_type,
self.addon_icon_combinations.keys()
)
})
.clone()
}
#[inline]
pub fn ability_icon(&self, name: impl ToString, rarity: Rarities) -> Arc<Image> {
self.ability_icon_combinations
.get(&(rarity, name.to_string()))
.unwrap_or_else(|| {
panic!(
"no book icon present for rarity ({})\navailable: {:#?}",
rarity,
self.ability_icon_combinations.keys()
)
})
.clone()
}
#[inline]
pub fn jewel_icon(&self, rarity: Rarities, level: u32, attribute: Attribute) -> Arc<Image> {
self.jewel_icon_combinations
.get(&(rarity, level, attribute))
.unwrap_or_else(|| {
panic!(
"no jewel icon present for rarity ({})\navailable: {:#?}",
rarity,
self.jewel_icon_combinations.keys()
)
})
.clone()
}
#[inline]
pub fn find_ability(&self, name: &str) -> A {
self.abilities
.iter()
.find(|a| a.name() == name)
.unwrap_or_else(|| panic!("no ability with name ({}) found", name))
.clone()
}
#[inline]
pub fn random_ability(&self) -> A {
let n = Random::range(0, self.abilities.len() as u32);
self.abilities[n as usize].clone()
}
fn equalize_images(
item_icons: Vec<(ItemSlots, DynamicImage)>,
background: DynamicImage,
ability_images: Vec<(String, DynamicImage)>,
addon_icons: Vec<(AbilityAddonTypes, DynamicImage)>,
addon_background: DynamicImage,
jewel_images: Vec<(u32, DynamicImage)>,
item_settings: &ItemSettings,
) -> (
Vec<(ItemSlots, RgbaImage)>,
RgbaImage,
Vec<(String, RgbaImage)>,
Vec<(AbilityAddonTypes, RgbaImage)>,
RgbaImage,
Vec<(u32, RgbaImage)>,
) {
let rgba_background = background
.resize(
item_settings.general.icon_target_width,
item_settings.general.icon_target_height,
FilterType::Gaussian,
)
.to_rgba8();
let add_on_background = addon_background
.resize(
item_settings.general.icon_target_width,
item_settings.general.icon_target_height,
FilterType::Gaussian,
)
.to_rgba8();
let icon_results = item_icons
.into_iter()
.map(|(slot, image)| {
(
slot,
image
.resize(
item_settings.general.icon_target_width,
item_settings.general.icon_target_height,
FilterType::Gaussian,
)
.to_rgba8(),
)
})
.collect();
let ability_icon = ability_images
.into_iter()
.map(|(name, image)| {
(
name,
image
.resize(
item_settings.general.icon_target_width,
item_settings.general.icon_target_height,
FilterType::Gaussian,
)
.to_rgba8(),
)
})
.collect();
let ability_addons = addon_icons
.into_iter()
.map(|(addon, image)| {
(
addon,
image
.resize(
item_settings.general.icon_target_width,
item_settings.general.icon_target_height,
FilterType::Gaussian,
)
.to_rgba8(),
)
})
.collect();
let jewel_results = jewel_images
.into_iter()
.map(|(n, image)| {
(
n,
image
.resize(
item_settings.general.icon_target_width,
item_settings.general.icon_target_height,
FilterType::Gaussian,
)
.to_rgba8(),
)
})
.collect();
(
icon_results,
rgba_background,
ability_icon,
ability_addons,
add_on_background,
jewel_results,
)
}
pub fn blend_background(i1: &RgbaImage, i2: &RgbaImage) -> RgbaImage {
let (width, height) = i1.dimensions();
let mut target = ImageBuffer::new(width, height);
for ((s1, s2), t) in i1.pixels().zip(i2.pixels()).zip(target.pixels_mut()) {
let mut source_clone = *s1;
source_clone.blend(s2);
*t = source_clone;
}
target
}
pub fn blend_apply<F>(i1: &RgbaImage, i2: &RgbaImage, mut apply: F) -> RgbaImage
where
F: FnMut(Rgba<u8>, &Rgba<u8>) -> Rgba<u8> + Copy,
{
let (width, height) = i1.dimensions();
let mut target = ImageBuffer::new(width, height);
for ((s1, s2), t) in i1.pixels().zip(i2.pixels()).zip(target.pixels_mut()) {
*t = apply(*s1, s2);
}
target
}
#[inline]
pub fn apply_color(base_image: &RgbaImage, result_color: Color) -> RgbaImage {
let mut result_image = base_image.clone();
for color in result_image.pixels_mut() {
let c = color.channels();
*color = Self::blend_mul((c[0], c[1], c[2], c[3]), result_color.into());
}
result_image
}
#[inline]
fn blend_mul((r, g, b, a): (u8, u8, u8, u8), c: [u8; 3]) -> Rgba<u8> {
Rgba([
Self::mul(r, c[0]),
Self::mul(g, c[1]),
Self::mul(b, c[2]),
a,
])
}
#[inline]
fn mul(l: u8, r: u8) -> u8 {
let lhs = l as f32 / 255.0;
let rhs = r as f32 / 255.0;
(lhs * rhs * 255.0) as u8
}
}
#[test]
fn validate_item_icons() {
let item_settings = ItemSettings::default();
let mut slots = Vec::new();
for slot in ItemSlots::iter() {
let path = slot.get_path(&item_settings);
slots.push((*slot, path));
}
let mut item_icon_combinations = std::collections::HashMap::new();
for (slot, path) in slots.iter() {
for rarity in Rarities::iter() {
item_icon_combinations.insert((*slot, *rarity), (*path).clone());
}
}
for slot in ItemSlots::iter() {
for rarity in Rarities::iter() {
item_icon_combinations
.get(&(*slot, *rarity))
.unwrap_or_else(|| panic!("item not present for {:?} + {}", slot, rarity));
}
}
}
#[test]
fn validate_addon_icons() {
let ability_settings = AbilitySettings::default();
let mut addon_types = Vec::new();
for addon_type in AbilityAddonTypes::iter() {
let path = addon_type.get_path(&ability_settings);
addon_types.push((*addon_type, path));
}
let mut addon_icon_combinations = std::collections::HashMap::new();
for (addon_type, path) in addon_types.iter() {
for rarity in Rarities::iter() {
addon_icon_combinations.insert((*rarity, *addon_type), (*path).clone());
}
}
for addon_type in AbilityAddonTypes::iter() {
for rarity in Rarities::iter() {
addon_icon_combinations
.get(&(*rarity, *addon_type))
.unwrap_or_else(|| panic!("item not present for {:?} + {}", addon_type, rarity));
}
}
}

View file

@ -1,124 +0,0 @@
use std::{
str::{from_utf8, FromStr},
sync::Arc,
};
use anyhow::Result;
use engine::prelude::*;
use super::{Rarities, Tooltip};
use crate::{
components::{attributes::Attribute, inventory::Storable, statistic_types::StatisticType},
config::items::ItemSettings,
};
#[derive(Clone, Debug)]
pub struct Jewel {
pub rarity: Rarities,
pub level: u32,
pub attribute: Attribute,
pub stat: StatisticType,
pub icon: Option<Arc<Image>>,
}
impl PartialEq for Jewel {
fn eq(&self, other: &Self) -> bool {
self.rarity == other.rarity && self.level == other.level
}
}
impl Jewel {
pub fn into_persistent(&self) -> String {
format!(
"{}|{}|{}|{}",
self.rarity, self.level, self.attribute, self.stat
)
}
pub fn from_persistent<'a>(split: &mut impl Iterator<Item = &'a str>) -> Result<Self> {
let rarity = Rarities::from_str(split.next().unwrap())?;
let level = split.next().unwrap().parse::<u32>()?;
let attribute = Attribute::from_str(split.next().unwrap())?;
let stat = StatisticType::from_str(split.next().unwrap())?;
Ok(Self {
rarity,
level,
attribute,
stat,
icon: None,
})
}
pub fn update_stat(&mut self, item_settings: &ItemSettings) {
self.stat
.apply_for_jewel(self.rarity, self.level, item_settings);
}
pub fn create_tooltip(
&self,
gui_handler: &Arc<GuiHandler>,
item_settings: &ItemSettings,
position: (i32, i32),
) -> Result<Tooltip> {
let inspector_snippet: Arc<GuiBuilder> = GuiBuilder::from_str(
gui_handler,
include_str!("../../resources/jewel_tooltip.xml"),
)?;
let main_grid: Arc<Grid> = inspector_snippet.element("main_grid")?;
main_grid.change_position_unscaled(position.0, position.1)?;
let jewel_icon: Arc<Icon> = inspector_snippet.element("jewel_icon")?;
let rarity_label: Arc<Label> = inspector_snippet.element("rarity_label")?;
let attribute_type: Arc<Label> = inspector_snippet.element("attribute_type")?;
let attribute_value: Arc<Label> = inspector_snippet.element("attribute_value")?;
let stat_type: Arc<Label> = inspector_snippet.element("stat_type")?;
let stat_value: Arc<Label> = inspector_snippet.element("stat_value")?;
jewel_icon.set_icon(&self.icon())?;
rarity_label.set_text(format!("{} ({})", self.rarity, self.level))?;
attribute_type.set_text({
let mut s = self.attribute.to_string();
s.replace_range(
0..1,
from_utf8(&[s.as_bytes()[0].to_ascii_uppercase()]).unwrap(),
);
s
})?;
attribute_value.set_text(
item_settings
.jewel_rarity_multiplier
.from_rarity(self.rarity)
* self.level
* item_settings.general.jewel_level_multiplier,
)?;
stat_type.set_text(&self.stat)?;
stat_value.set_text(self.stat.display_value())?;
Ok(Tooltip::new(
main_grid,
inspector_snippet,
gui_handler.clone(),
))
}
}
impl Storable for Jewel {
fn rarity(&self) -> Rarities {
self.rarity
}
fn icon(&self) -> Arc<Image> {
self.icon.clone().unwrap()
}
}

View file

@ -1,45 +0,0 @@
use std::sync::Arc;
use anyhow::Result;
use engine::prelude::*;
use crate::components::inventory::Storable;
use super::{ability_book::Ability, ItemSystem, Rarities};
#[derive(Clone)]
pub struct MapItem {
rarity: Rarities,
icon: Arc<Image>,
}
impl MapItem {
pub fn into_persistent(&self) -> String {
String::new()
}
pub fn from_persistent<'a, A: Ability>(
mut _split: impl Iterator<Item = &'a str>,
_item_system: &ItemSystem<A>,
) -> Result<Self> {
todo!()
}
pub fn create_tooltip(
&self,
_gui_handler: &Arc<GuiHandler>,
_position: (i32, i32),
) -> Result<Arc<GuiBuilder>> {
todo!()
}
}
impl Storable for MapItem {
fn rarity(&self) -> Rarities {
self.rarity
}
fn icon(&self) -> Arc<Image> {
self.icon.clone()
}
}

View file

@ -1,17 +0,0 @@
pub mod ability_addon;
pub mod ability_book;
mod item;
mod item_slots;
mod item_system;
mod jewel;
mod map_item;
mod rarities;
mod tooltip;
pub use item::*;
pub use item_slots::*;
pub use item_system::{ItemSystem, Loot};
pub use jewel::Jewel;
pub use map_item::MapItem;
pub use rarities::*;
pub use tooltip::*;

View file

@ -1,134 +0,0 @@
use anyhow::Result;
use engine::prelude::*;
use image::RgbaImage;
use std::{fmt, slice::Iter};
use crate::config::items::{ItemSettings, RarityColorSettings};
use super::{ability_book::Ability, ItemSystem};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Rarities {
Common,
Uncommon,
Magical,
Rare,
Epic,
Legendary,
}
impl Rarities {
const COUNT: u32 = 6;
pub fn iter() -> Iter<'static, Rarities> {
use Rarities::*;
static RARITIES: [Rarities; Rarities::COUNT as usize] =
[Common, Uncommon, Magical, Rare, Epic, Legendary];
RARITIES.iter()
}
pub fn random(item_settings: &ItemSettings, level: u32) -> Self {
let p = Coin::raw();
let multiplier = level as f32 / item_settings.general.drop_chance_reference_level as f32;
// legendary
let mut lower = 0.0;
let mut upper = item_settings.rarity_drop_rates.legendary * multiplier;
if Self::is_between(p, lower, upper) {
return Self::Legendary;
}
// epic
lower = upper;
upper = upper + item_settings.rarity_drop_rates.epic * multiplier;
if Self::is_between(p, lower, upper) {
return Self::Epic;
}
// rare
lower = upper;
upper = upper + item_settings.rarity_drop_rates.rare * multiplier;
if Self::is_between(p, lower, upper) {
return Self::Rare;
}
// magical
lower = upper;
upper = upper + item_settings.rarity_drop_rates.magical * multiplier;
if Self::is_between(p, lower, upper) {
return Self::Magical;
}
// uncommon
lower = upper;
upper = upper + item_settings.rarity_drop_rates.uncommon * multiplier;
if Self::is_between(p, lower, upper) {
return Self::Uncommon;
}
// common
Self::Common
}
#[inline]
fn is_between(p: f32, l: f32, h: f32) -> bool {
p >= l && p <= h
}
pub(crate) fn apply_color<A: Ability>(
&self,
base_image: &RgbaImage,
rarity_color_settings: &RarityColorSettings,
) -> RgbaImage {
ItemSystem::<A>::apply_color(base_image, rarity_color_settings.from_rarity(*self))
}
}
impl Default for Rarities {
fn default() -> Self {
Self::Common
}
}
impl std::str::FromStr for Rarities {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
match s {
"Common" => Ok(Self::Common),
"Uncommon" => Ok(Self::Uncommon),
"Magical" => Ok(Self::Magical),
"Rare" => Ok(Self::Rare),
"Epic" => Ok(Self::Epic),
"Legendary" => Ok(Self::Legendary),
_ => {
return Err(anyhow::Error::msg(format!(
"Failed parsing Rarities from {}",
s
)))
}
}
}
}
impl fmt::Display for Rarities {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::Common => write!(f, "Common"),
Self::Uncommon => write!(f, "Uncommon"),
Self::Magical => write!(f, "Magical"),
Self::Rare => write!(f, "Rare"),
Self::Epic => write!(f, "Epic"),
Self::Legendary => write!(f, "Legendary"),
}
}
}

View file

@ -1,136 +0,0 @@
use anyhow::{bail, Result};
use engine::prelude::*;
use std::sync::Arc;
pub struct FittingResult {
pub fit: bool,
pub start: i32,
pub extent: u32,
}
pub struct Tooltip {
grid: Arc<Grid>,
gui: Arc<GuiBuilder>,
gui_handler: Arc<GuiHandler>,
}
impl Tooltip {
pub fn new(grid: Arc<Grid>, gui: Arc<GuiBuilder>, gui_handler: Arc<GuiHandler>) -> Self {
Self {
grid,
gui,
gui_handler,
}
}
pub fn enable(&self) -> Result<()> {
self.gui.enable()
}
pub fn check_fitting(&self) -> Result<(FittingResult, FittingResult)> {
if !self.grid.visible() {
bail!("enable item tooltip first");
}
let (x, y, w, h) = self.grid.position_extent();
Ok((
FittingResult {
fit: (x + w as i32) <= self.gui_handler.width() as i32,
start: x,
extent: w,
},
FittingResult {
fit: (y + h as i32) <= self.gui_handler.height() as i32,
start: y,
extent: h,
},
))
}
pub fn move_to(&self, x: i32, y: i32) -> Result<()> {
self.grid.change_position_unscaled(x, y)
}
pub fn position_extent(&self) -> (i32, i32, u32, u32) {
self.grid.position_extent()
}
pub fn perform_single_check(&self, x: i32, y: i32) -> Result<()> {
let (width_fitting, height_fitting) = self.check_fitting()?;
let mut width_shift = None;
let mut height_shift = None;
let gui_pos = self.position_extent();
if !width_fitting.fit {
width_shift = Some(x - gui_pos.2 as i32);
}
if !height_fitting.fit {
height_shift = Some(y - gui_pos.3 as i32);
}
if width_shift.is_some() || height_shift.is_some() {
self.move_to(
width_shift.unwrap_or(gui_pos.0),
height_shift.unwrap_or(gui_pos.1),
)?;
}
Ok(())
}
pub fn perform_double_check(&self, other: &Self, x: i32, spacing: u32) -> Result<()> {
let (_left_width_fitting, left_height_fitting) = self.check_fitting()?;
let (right_width_fitting, right_height_fitting) = other.check_fitting()?;
let mut width_shift = None;
let mut height_shift = None;
let left_gui_pos = self.position_extent();
let right_gui_pos = other.position_extent();
if !right_width_fitting.fit {
width_shift = Some(x - right_gui_pos.2 as i32 - spacing as i32);
}
let window_height = self.gui_handler.height();
if !left_height_fitting.fit {
height_shift = Some(window_height as i32 - left_gui_pos.3 as i32 - spacing as i32);
}
if !right_height_fitting.fit {
let right = window_height as i32 - right_gui_pos.3 as i32 - spacing as i32;
match height_shift {
Some(current) => {
if current > right {
height_shift = Some(right);
}
}
None => {
height_shift = Some(right);
}
}
}
if width_shift.is_some() || height_shift.is_some() {
let width = width_shift.unwrap_or(right_gui_pos.0);
let height = height_shift.unwrap_or(right_gui_pos.1);
other.move_to(width, height)?;
self.move_to(width - spacing as i32 - left_gui_pos.2 as i32, height)?;
}
Ok(())
}
}
impl Into<Arc<GuiBuilder>> for Tooltip {
fn into(self) -> Arc<GuiBuilder> {
self.gui
}
}

View file

@ -1,4 +0,0 @@
pub mod components;
pub mod config;
pub mod damage_type;
pub mod items;