Skip to content

Commit

Permalink
Dry-coded the poison indicator feature.
Browse files Browse the repository at this point in the history
There's a new optional per-slot element in layouts v2 (electric
boogaloo). This element displays a poison indicator if the item in
the slot is poisoned. It's only meaningful for left/right hand slots,
but we make no effort to enforce this; hud items in those slots will
never say they're poisoned.

The indicator looks like this:

```toml
[left.poison]
offset = { x = 0.0, y = 50.0 }
[left.poison.indicator]
svg = "icons/indicator_poison.svg"
size = { x = 23.0, y = 23.0 }
color = { r = 255, g = 255, b = 255, a = 255 }
```

HUD items have a function `is_poisoned()` for checking this bit.
This call goes back to C++ to look at the associated bound object's
extra data to see if it's poisoned. We are checking this on every
draw loop right now. I'm not sure caching it would be a win, because
I have not yet investigated whether there's any reasonable invalidation
notification.

The renderer renders this layout element if the item is poisoned
and if the color for the image has non-zero alpha.

This feature is only supported on layouts v2.

This addresses the poison indicator part of bug #38.
  • Loading branch information
ceejbot committed Nov 30, 2023
1 parent b472bac commit a0c89c6
Show file tree
Hide file tree
Showing 14 changed files with 198 additions and 45 deletions.
14 changes: 14 additions & 0 deletions data/SKSE/plugins/SoulsyHUD_Layout.toml
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,13 @@ color = { r = 255, g = 255, b = 255, a = 255 }
offset = { x = 60.0, y = 0.0 }
size = { x = 30.0, y = 30.0 }

[left.poison]
offset = { x = 0.0, y = 50.0 }
[left.poison.indicator]
svg = "icons/indicator_poison.svg"
size = { x = 23.0, y = 23.0 }
color = { r = 255, g = 255, b = 255, a = 255 }

[[left.text]]
offset = { x = 125.0, y = 107.0 }
color = { r = 255, g = 255, b = 255, a = 255 }
Expand All @@ -205,6 +212,13 @@ color = { r = 255, g = 255, b = 255, a = 255 }
offset = { x = -60.0, y = 0.0 }
size = { x = 30.0, y = 30.0 }

[right.poison]
offset = { x = 0.0, y = 50.0 }
[right.poison.indicator]
svg = "icons/indicator_poison.svg"
size = { x = 23.0, y = 23.0 }
color = { r = 255, g = 255, b = 255, a = 255 }

[[right.text]]
color = { r = 255, g = 255, b = 255, a = 255 }
offset = { x = 10.0, y = 52.0 }
Expand Down
24 changes: 24 additions & 0 deletions data/SKSE/plugins/resources/icons/indicator_poison.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 1 addition & 6 deletions layouts/square/LayoutV2.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@

global_scale = 2.0

# A named location for the HUD. Use this as a shortcut for common anchor
# points. These work no matter what the player's screen resolution is, because this
# is turned into a location point at run-time.
# Values: bottom_left, bottom_right, top_left, top_right, center,
# center_top, center_bottom, left_center, right_center
anchor_name = "bottom_left"

# You can also specify the anchor point like this if a named anchor point
Expand Down Expand Up @@ -113,9 +108,9 @@ orientation = "horizontal" # defaults to vertical
[right.progress_circle]
# same

# NOT YET IMPLEMENTED.
[right.poison]
offset = { x = 20.0, y = 20.0 }
[right.poison.indicator]
svg = "poison.svg"
color = {r = 160, g = 240, b = 2, a = 255 } # same color as poison. consider using the color names?
size = { x = 10.0, y = 10.0 }
Expand Down
32 changes: 13 additions & 19 deletions layouts/square/SoulsyHUD_Layout.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,34 +95,20 @@ color = { r = 255, g = 255, b = 255, a = 128 }
# fill in a value, name it surrounded with curly braces, like the
# example.
# Possible values: count, name, kind,

[[right.text]]
alignment = "right"
offset = { x = 0.0, y = 0.0 }
color = { r = 255, g = 255, b = 255, a = 0 }
color = { r = 255, g = 255, b = 255, a = 255 }
font_size = 18.0
contents = "{count} {name}"

# Optional burn time/enchant charge bar. NOT YET IMPLEMENTED.
[right.progress_bar]
offset = { x = 20.0, y = 20.0 }
background = "bar_bg.svg"
color = { r = 255, g = 255, b = 255, a = 255 }
size = { x = 10.0 , y = 100.0 } # this is how to scale the bar, irrespective of orientation
orientation = "horizontal" # defaults to vertical

# NOT YET IMPLEMENTED.
[right.progress_arc]
# this might be an option

# NOT YET IMPLEMENTED.
[right.progress_circle]
# same

# NOT YET IMPLEMENTED.
# An optional indicator for showing if this item is poisoned.
[right.poison]
offset = { x = 20.0, y = 20.0 }
[right.poison.indicator]
svg = "poison.svg"
color = {r = 160, g = 240, b = 2, a = 255 } # same color as poison. consider using the color names?
color = {r = 160, g = 240, b = 2, a = 255 }
size = { x = 10.0, y = 10.0 }

# ----- left hand slot
Expand All @@ -149,6 +135,14 @@ svg = "hotkey_bg.svg"
size = { x = 30.0 , y = 30.0}
color = { r = 255, g = 255, b = 255, a = 128 }

# An optional indicator for showing if this item is poisoned.
[left.poison]
offset = { x = 20.0, y = 20.0 }
[left.poison.indicator]
svg = "poison.svg"
color = {r = 160, g = 240, b = 2, a = 255 }
size = { x = 10.0, y = 10.0 }

[[left.text]]
alignment = "left"
offset = { x = -50.0, y = 55.0 }
Expand Down
11 changes: 8 additions & 3 deletions src/data/huditem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ use std::collections::HashMap;
use std::ffi::CString;
use std::fmt::Display;

use cxx::let_cxx_string;
use strfmt::strfmt;

use super::base::BaseType;
use super::HasIcon;
use crate::images::icons::Icon;
use crate::plugin::{Color, ItemCategory};
use crate::plugin::{weaponIsPoisoned, Color, ItemCategory};

/// A TESForm item that the player can use or equip, with the data
/// that drives the HUD cached for fast access.
Expand Down Expand Up @@ -163,8 +164,12 @@ impl HudItem {
}

pub fn is_poisoned(&self) -> bool {
// TODO track this somehow
false
if !self.is_weapon() {
false
} else {
let_cxx_string!(form_spec = self.form_string());
weaponIsPoisoned(&form_spec)
}
}

/// Charge as a float from 0.0 to 1.0 inclusive. For enchanted weapons
Expand Down
2 changes: 1 addition & 1 deletion src/game/equippable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ namespace equippable
alchemy_potion->ForEachKeyword(KeywordAccumulator::collect);
auto& keywords = KeywordAccumulator::mKeywords;
rust::Box<HudItem> item = hud_item_from_keywords(
ItemCategory::Food, *keywords, std::move(chonker), form_string, count, false);
ItemCategory::Food, *keywords, std::move(chonker), form_string, count, false, false);
return item;
}
else
Expand Down
10 changes: 10 additions & 0 deletions src/game/gear.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@ namespace game
return worn;
}

bool isItemPoisoned(const RE::TESForm* form)
{
auto* the_player = RE::PlayerCharacter::GetSingleton();
RE::TESBoundObject* obj = nullptr;
RE::ExtraDataList* extra = nullptr;
auto count = boundObjectForForm(form, player, obj, extra_data);
if (extra_data) { return extra_data->HasType(RE::ExtraDataType::kPoison); }
return false;
}

void equipItemByFormAndSlot(RE::TESForm* form, RE::BGSEquipSlot*& slot, RE::PlayerCharacter*& player)
{
auto slot_is_left = slot == left_hand_equip_slot();
Expand Down
1 change: 1 addition & 0 deletions src/game/gear.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace game
RE::TESBoundObject*& outval,
RE::ExtraDataList*& outextra);

bool isItemPoisoned(const RE::TESForm* form);
bool isItemWorn(RE::TESBoundObject*& object, RE::PlayerCharacter*& the_player);
// bottleneck for equipping everything
void equipItemByFormAndSlot(RE::TESForm* form, RE::BGSEquipSlot*& slot, RE::PlayerCharacter*& the_player);
Expand Down
6 changes: 6 additions & 0 deletions src/game/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ namespace player
return useAltGrip;
}

bool weaponIsPoisoned(const std::string& form_spec)
{
auto* const form = formSpecToFormItem(form_spec);
return game::isItemPoisoned(form);
}

rust::String specEquippedLeft()
{
auto* player = RE::PlayerCharacter::GetSingleton();
Expand Down
1 change: 1 addition & 0 deletions src/game/player.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ namespace player

void consumePotion(const std::string& form_spec);
void poison_weapon(RE::PlayerCharacter*& a_player, RE::AlchemyItem*& a_poison, uint32_t a_count);
bool weaponIsPoisoned(const std::string& form_spec);

bool hasItemOrSpell(const std::string& form_spec);
uint32_t itemCount(const std::string& form_spec);
Expand Down
11 changes: 11 additions & 0 deletions src/layouts/layout_v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,17 @@ impl HudLayout1 {
hotkey_bg_size: slot.hotkey_size.scale(self.global_scale),
hotkey_bg_color: slot.hotkey_bg_color.clone(),
hotkey_bg_image: "key_bg.svg".to_string(),

poison_image: "".to_string(),
poison_color: Color {
r: 0,
g: 0,
b: 0,
a: 0,
},
poison_center: Point { x: 0.0, y: 0.0 },
poison_size: Point { x: 0.0, y: 0.0 },

text,
}
}
Expand Down
102 changes: 86 additions & 16 deletions src/layouts/layout_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ pub struct SlotElement {
background: Option<ImageElement>,
hotkey: Option<HotkeyElement>,
progress_bar: Option<ProgressElement>,
poison: Option<PoisonElement>,
}

impl HudLayout2 {
Expand Down Expand Up @@ -116,6 +117,12 @@ impl HudLayout2 {
.map(|xs| self.flatten_text(xs, &center))
.collect();

let poison = slot.poison.clone().unwrap_or_default();
let poison_image = poison.indicator.svg;
let poison_size = poison.indicator.size.scale(self.global_scale);
let poison_color = poison.indicator.color;
let poison_center = center.translate(&poison.offset.scale(self.global_scale));

SlotFlattened {
element,
center: center.clone(),
Expand All @@ -131,6 +138,10 @@ impl HudLayout2 {
hotkey_bg_size: hkbg.size.scale(self.global_scale),
hotkey_bg_color: hkbg.color,
hotkey_bg_image: hkbg.svg,
poison_size,
poison_image,
poison_color,
poison_center,
text,
}
}
Expand Down Expand Up @@ -217,6 +228,21 @@ pub struct ProgressElement {
color: Color,
}

#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
pub struct PoisonElement {
offset: Point,
indicator: ImageElement,
}

impl Default for PoisonElement {
fn default() -> Self {
PoisonElement {
offset: Point { x: 0.0, y: 0.0 },
indicator: ImageElement::default(),
}
}
}

impl From<&HudLayout2> for LayoutFlattened {
fn from(v: &HudLayout2) -> Self {
let mut slots = vec![
Expand Down Expand Up @@ -259,21 +285,62 @@ mod tests {
use super::*;
use crate::layouts::{resolutionHeight, Layout};

// #[test]
// #[ignore]
// fn default_layout_valid() {
// // The github runner compilation step can't find this file. I have no idea why not.
// let buf = include_str!("../../data/SKSE/plugins/SoulsyHUD_layout.toml");
// let builtin: Layout = toml::from_str(buf).expect("layout should be valid toml");
// match builtin {
// Layout::Version1(_) => unreachable!(),
// Layout::Version2(v) => {
// assert_eq!(v.anchor_name, NamedAnchor::BottomLeft);
// assert_eq!(v.anchor_point().x, 150.0);
// assert_eq!(v.anchor_point().y, 1290.0);
// }
// }
// }
#[test]
fn default_layout_valid() {
// The github runner compilation step can't find this file. I have no idea why not.
let buf = include_str!("../../data/SKSE/plugins/SoulsyHUD_layout.toml");
match toml::from_str::<HudLayout2>(buf) {
Ok(v) => {
assert_eq!(v.anchor_point().x, 150.0);
}
Err(e) => {
eprintln!("{e:#?}");
unreachable!();
}
}
let builtin: Layout = toml::from_str(buf).expect("layout should be valid toml");
match builtin {
Layout::Version1(_) => unreachable!(),
Layout::Version2(v) => {
assert_eq!(v.anchor_name, NamedAnchor::BottomLeft);
assert_eq!(v.anchor_point().x, 150.0);
assert_eq!(v.anchor_point().y, 1290.0);
let right_poison = v
.right
.poison
.as_ref()
.expect("the right slot should have a poison indicator");
assert_eq!(
right_poison.indicator.svg,
"icons/indicator_poison.svg".to_string()
);
let _left_poison = v
.left
.poison
.as_ref()
.expect("the left slot should have a poison indicator");

let flattened = Layout::Version2(v.clone()).flatten();
assert_eq!(flattened.anchor, v.anchor_point());
let right_slot = flattened
.slots
.iter()
.find(|slot| slot.element == HudElement::Right)
.expect("the flattened layout needs to have a right slot");
assert_eq!(right_slot.poison_image, right_poison.indicator.svg);
assert_eq!(right_slot.poison_color, right_poison.indicator.color);
let slot_center = Point {
x: flattened.anchor.x + (v.right.offset.x * flattened.global_scale),
y: flattened.anchor.y + (v.right.offset.y * flattened.global_scale),
};
assert_eq!(right_slot.center, slot_center);
assert_eq!(
right_slot.poison_center,
slot_center.translate(&right_poison.offset)
);
}
}
}

#[test]
fn centered_layout_valid() {
Expand Down Expand Up @@ -377,6 +444,9 @@ mod tests {
};
assert_eq!(right_slot.center, slot_center);
assert_eq!(layout.right.text.len(), right_slot.text.len());
assert_eq!(layout.right.text[0].font_size * layout.global_scale, right_slot.text[0].font_size);
assert_eq!(
layout.right.text[0].font_size * layout.global_scale,
right_slot.text[0].font_size
);
}
}
8 changes: 8 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ pub mod plugin {
hotkey_bg_color: Color,
hotkey_bg_image: String,

poison_size: Point,
poison_center: Point,
poison_color: Color,
poison_image: String,

text: Vec<TextFlattened>,
}

Expand Down Expand Up @@ -485,6 +490,9 @@ pub mod plugin {
/// Check if the player still has items from this form in their inventory.
fn hasItemOrSpell(form_spec: &CxxString) -> bool;

/// Is this weapon poisoned?
fn weaponIsPoisoned(form_spec: &CxxString) -> bool;

/// Does the player have a bow or crossbow equipped?
fn hasRangedEquipped() -> bool;
/// Get a vec of form specs for all relevant ammo in the player's inventory.
Expand Down
Loading

0 comments on commit a0c89c6

Please sign in to comment.