Compare commits

...

7 Commits

Author SHA1 Message Date
Mark 92a03ccbd1
Improved outfitter UI 2024-02-16 13:29:47 -08:00
Mark 69b35bd43c
API updates 2024-02-16 13:29:19 -08:00
Mark 327e2b43b3
Minor cleanup 2024-02-16 13:29:01 -08:00
Mark 5528f6ed4e
API tweaks 2024-02-16 13:28:47 -08:00
Mark 308e380241
API fixes 2024-02-16 13:25:50 -08:00
Mark 855eb0680b
Organized outfits & added descriptions 2024-02-16 13:24:52 -08:00
Mark f53e191de3
Minor fix 2024-02-16 13:23:44 -08:00
24 changed files with 804 additions and 284 deletions

View File

@ -1,8 +1,11 @@
# Specific projects # Specific projects
## Now: ## Now:
- init on resize
- hide ui element - hide ui element
- zoom limits - zoom limits
- text scrolling
- scrollbox scroll limits
- clean up content - clean up content
- Clean up state api - Clean up state api
- Clean up & document UI api - Clean up & document UI api
@ -12,6 +15,8 @@
- Selection while flying - Selection while flying
- outfitter - outfitter
- fps textbox positioning - fps textbox positioning
- shield generation curve
- clippy & rules
## Small jobs ## Small jobs
- Better planet icons in radar - Better planet icons in radar

2
assets

@ -1 +1 @@
Subproject commit b8ea8c5a04c0216304a81ebf396b7320a709d6ed Subproject commit bd425ccb10bb16661620916ff4f1a387b54ce7ca

View File

@ -2,6 +2,14 @@
name = "Plasma Engines" name = "Plasma Engines"
thumbnail = "icon::engine" thumbnail = "icon::engine"
desc = """
This is the smallest of the plasma propulsion systems produced by
Delta V Corporation, suitable for very light fighters and interceptors.
Plasma engines are a bit more powerful than ion engines of the same size,
but they are less energy efficient and produce more heat.
"""
space.engine = 20 space.engine = 20
engine.thrust = 100 engine.thrust = 100
engine.flare.sprite = "flare::ion" engine.flare.sprite = "flare::ion"
@ -14,6 +22,15 @@ steering.power = 20
thumbnail = "icon::shield" thumbnail = "icon::shield"
name = "Shield Generator" name = "Shield Generator"
desc = """
This is the standard shield generator for fighters and interceptors,
as well as for many non-combat starships. Although it is possible for
a ship to have no shield generator at all, recharging its shield matrix
only when landed in a hospitable spaceport, life in deep space is
unpredictable enough that most pilots find shield generators to be
well worth the space they take up.
"""
space.outfit = 5 space.outfit = 5
shield.generation = 10 shield.generation = 10
shield.strength = 500 shield.strength = 500
@ -24,6 +41,13 @@ shield.delay = 2.0
thumbnail = "icon::blaster" thumbnail = "icon::blaster"
name = "Blaster" name = "Blaster"
desc = """
Although not the most accurate or damaging of weapons, the Energy Blaster is popular because it
is small enough to be installed on even the tiniest of ships. One blaster is not enough to do
appreciable damage to anything larger than a fighter, but a ship equipped with several of them
becomes a force to be reckoned with.
"""
space.weapon = 10 space.weapon = 10
# Average delay between shots # Average delay between shots

View File

@ -57,8 +57,8 @@ fn init(state) {
(radar_size / 2.0 + 5), (radar_size / 2.0 + 5),
(radar_size / -2.0 - 5), (radar_size / -2.0 - 5),
3.5, 3.5, 3.5, 3.5,
Anchor::Center, Anchor::NorthWest,
Anchor::NorthWest Anchor::Center
); );
sprite::add("radar.frame.ne", "ui::radarframe", init_pos); sprite::add("radar.frame.ne", "ui::radarframe", init_pos);
@ -114,12 +114,12 @@ fn event(state, event) {
fn step(state) { fn step(state) {
radialbar::set_val("shield", radialbar::set_val("shield",
state.player_ship().get_shields() state.player_ship().current_shields()
/ state.player_ship().get_total_shields() / state.player_ship().stat_shield_strength()
); );
radialbar::set_val("hull", radialbar::set_val("hull",
state.player_ship().get_hull() state.player_ship().current_hull()
/ state.player_ship().get_total_hull() / state.player_ship().total_hull()
); );
@ -138,8 +138,8 @@ fn step(state) {
Rect( Rect(
pos.x(), pos.y(), pos.x(), pos.y(),
5.0, 5.0, 5.0, 5.0,
Anchor::Center, Anchor::NorthWest,
Anchor::NorthWest Anchor::Center
) )
); );
sprite::set_angle("radar.arrow", angle - 90.0); sprite::set_angle("radar.arrow", angle - 90.0);
@ -201,8 +201,8 @@ fn step(state) {
Rect( Rect(
pos.x(), pos.y(), pos.x(), pos.y(),
size, size, size, size,
Anchor::Center, Anchor::NorthWest,
Anchor::NorthWest Anchor::Center
) )
); );
} else { } else {
@ -211,8 +211,8 @@ fn step(state) {
Rect( Rect(
pos.x(), pos.y(), pos.x(), pos.y(),
size, size, size, size,
Anchor::Center, Anchor::NorthWest,
Anchor::NorthWest Anchor::Center
) )
); );
} }
@ -263,8 +263,8 @@ fn step(state) {
Rect( Rect(
pos.x(), pos.y(), pos.x(), pos.y(),
size, size, size, size,
Anchor::Center, Anchor::NorthWest,
Anchor::NorthWest Anchor::Center
) )
); );
} else { } else {
@ -273,8 +273,8 @@ fn step(state) {
Rect( Rect(
pos.x(), pos.y(), pos.x(), pos.y(),
size, size, size, size,
Anchor::Center, Anchor::NorthWest,
Anchor::NorthWest Anchor::Center
) )
); );
} }
@ -291,8 +291,8 @@ fn step(state) {
(radar_size / 2.0 + 5) - dx, (radar_size / 2.0 + 5) - dx,
(radar_size / -2.0 - 5) + dy, (radar_size / -2.0 - 5) + dy,
3.5, 3.5, 3.5, 3.5,
Anchor::Center, Anchor::NorthWest,
Anchor::NorthWest Anchor::Center
) )
); );
sprite::set_rect("radar.frame.se", sprite::set_rect("radar.frame.se",
@ -300,8 +300,8 @@ fn step(state) {
(radar_size / 2.0 + 5) - dx, (radar_size / 2.0 + 5) - dx,
(radar_size / -2.0 - 5) - dy, (radar_size / -2.0 - 5) - dy,
3.5, 3.5, 3.5, 3.5,
Anchor::Center, Anchor::NorthWest,
Anchor::NorthWest Anchor::Center
) )
); );
sprite::set_rect("radar.frame.sw", sprite::set_rect("radar.frame.sw",
@ -309,8 +309,8 @@ fn step(state) {
(radar_size / 2.0 + 5) + dx, (radar_size / 2.0 + 5) + dx,
(radar_size / -2.0 - 5) - dy, (radar_size / -2.0 - 5) - dy,
3.5, 3.5, 3.5, 3.5,
Anchor::Center, Anchor::NorthWest,
Anchor::NorthWest Anchor::Center
) )
); );
sprite::set_rect("radar.frame.nw", sprite::set_rect("radar.frame.nw",
@ -318,8 +318,8 @@ fn step(state) {
(radar_size / 2.0 + 5) + dx, (radar_size / 2.0 + 5) + dx,
(radar_size / -2.0 - 5) + dy, (radar_size / -2.0 - 5) + dy,
3.5, 3.5, 3.5, 3.5,
Anchor::Center, Anchor::NorthWest,
Anchor::NorthWest Anchor::Center
) )
); );
} }

View File

@ -7,8 +7,8 @@ fn init(state) {
"ui::planet::button", "ui::planet::button",
Rect( Rect(
99.0, 128.0, 73.898, 18.708, 99.0, 128.0, 73.898, 18.708,
Anchor::NorthWest, Anchor::Center,
Anchor::Center Anchor::NorthWest
) )
); );
@ -23,8 +23,8 @@ fn init(state) {
}, },
Rect( Rect(
-180.0, 142.0, 274.0, 135.0, -180.0, 142.0, 274.0, 135.0,
Anchor::NorthWest, Anchor::Center,
Anchor::Center Anchor::NorthWest
) )
); );
sprite::set_mask("landscape", "ui::landscapemask"); sprite::set_mask("landscape", "ui::landscapemask");
@ -40,17 +40,17 @@ fn init(state) {
); );
// If this is not set, the button will // If this is not set, the button will
// not receive events // not receive events
sprite::set_disable_events("frame", true); sprite::disable_events("frame", true);
textbox::add( textbox::add(
"title", 10.0, 10.0, "title", 10.0, 10.0,
Color(1.0, 1.0, 1.0, 1.0),
Rect( Rect(
-70.79, 138.0, 59.867, 10.0, -70.79, 138.0, 59.867, 10.0,
Anchor::NorthWest, Anchor::Center,
Anchor::Center Anchor::NorthWest
), )
Color(1.0, 1.0, 1.0, 1.0)
); );
textbox::align_center("title"); textbox::align_center("title");
textbox::font_serif("title"); textbox::font_serif("title");
@ -61,12 +61,12 @@ fn init(state) {
textbox::add( textbox::add(
"desc", 7.5, 8.0, "desc", 7.5, 8.0,
Color(1.0, 1.0, 1.0, 1.0),
Rect( Rect(
-178.92, -20.3, 343.0, 81.467, -178.92, -20.3, 343.0, 81.467,
Anchor::NorthWest, Anchor::Center,
Anchor::Center Anchor::NorthWest
), )
Color(1.0, 1.0, 1.0, 1.0)
); );
textbox::font_sans("desc"); textbox::font_sans("desc");
if state.player_ship().is_landed() { if state.player_ship().is_landed() {

View File

@ -3,6 +3,11 @@ fn init(state) {
conf::show_starfield(true); conf::show_starfield(true);
conf::show_phys(false); conf::show_phys(false);
if !state.player_ship().is_landed() {
print("UI error: player isn't landed when initializing outfitter scene");
return;
}
sprite::add( sprite::add(
"se_box", "se_box",
"ui::outfitterbox", "ui::outfitterbox",
@ -15,12 +20,13 @@ fn init(state) {
textbox::add( textbox::add(
"exit_text", 10.0, 10.0, "exit_text", 10.0, 10.0,
Color(1.0, 1.0, 1.0, 1.0),
Rect( Rect(
122.71, 48.0, 51.0, 12.0, 122.71, 48.0, 51.0, 12.0,
Anchor::NorthWest, Anchor::SouthWest,
Anchor::SouthWest Anchor::NorthWest
),
Color(1.0, 1.0, 1.0, 1.0) )
); );
textbox::font_serif("exit_text"); textbox::font_serif("exit_text");
textbox::align_center("exit_text"); textbox::align_center("exit_text");
@ -31,8 +37,8 @@ fn init(state) {
"ui::button", "ui::button",
Rect( Rect(
113.35, 52.0, 69.8, 18.924, 113.35, 52.0, 69.8, 18.924,
Anchor::NorthWest, Anchor::SouthWest,
Anchor::SouthWest Anchor::NorthWest
) )
); );
@ -53,53 +59,46 @@ fn init(state) {
"icon::gypsum", "icon::gypsum",
Rect( Rect(
111.0, -95.45, 90.0, 90.0, 111.0, -95.45, 90.0, 90.0,
Anchor::Center, Anchor::NorthWest,
Anchor::NorthWest Anchor::Center
) )
); );
sprite::preserve_aspect("ship_thumb", true); sprite::preserve_aspect("ship_thumb", true);
textbox::add( textbox::add(
"ship_name", 10.0, 10.0, "ship_name", 10.0, 10.0,
Color(1.0, 1.0, 1.0, 1.0),
Rect( Rect(
111.0, -167.27, 145.0, 10.0, 111.0, -167.27, 145.0, 10.0,
Anchor::Center, Anchor::NorthWest,
Anchor::NorthWest Anchor::Center
), )
Color(1.0, 1.0, 1.0, 1.0)
); );
textbox::font_serif("ship_name"); textbox::font_serif("ship_name");
textbox::align_center("ship_name"); textbox::align_center("ship_name");
textbox::set_text("ship_name", "Hyperion");
textbox::add( textbox::add(
"ship_type", 7.0, 8.5, "ship_type", 7.0, 8.5,
Color(0.7, 0.7, 0.7, 1.0),
Rect( Rect(
111.0, -178.0, 145.0, 8.5, 111.0, -178.0, 145.0, 8.5,
Anchor::Center, Anchor::NorthWest,
Anchor::NorthWest Anchor::Center
), )
Color(0.7, 0.7, 0.7, 1.0)
); );
textbox::font_sans("ship_type"); textbox::font_sans("ship_type");
textbox::align_center("ship_type"); textbox::align_center("ship_type");
if state.player_ship().is_some() {
textbox::set_text("ship_type", state.player_ship().display_name());
}
textbox::add( textbox::add(
"ship_stats", 7.0, 8.5, "ship_stats", 7.0, 8.5,
Color(1.0, 1.0, 1.0, 1.0),
Rect( Rect(
38.526, -192.332, 144.948, 154.5, 38.526, -192.332, 144.948, 154.5,
Anchor::NorthWest, Anchor::NorthWest,
Anchor::NorthWest, Anchor::NorthWest,
), )
Color(1.0, 1.0, 1.0, 1.0)
); );
textbox::font_mono("ship_stats"); textbox::font_mono("ship_stats");
textbox::set_text("ship_stats", "Earth");
sprite::add( sprite::add(
"outfit_bg", "outfit_bg",
@ -116,8 +115,8 @@ fn init(state) {
"icon::engine", "icon::engine",
Rect( Rect(
-166.0, -109.0, 90.0, 90.0, -166.0, -109.0, 90.0, 90.0,
Anchor::Center, Anchor::NorthEast,
Anchor::NorthEast Anchor::Center
) )
); );
sprite::preserve_aspect("outfit_thumb", true); sprite::preserve_aspect("outfit_thumb", true);
@ -125,76 +124,182 @@ fn init(state) {
textbox::add( textbox::add(
"outfit_name", 16.0, 16.0, "outfit_name", 16.0, 16.0,
Color(1.0, 1.0, 1.0, 1.0),
Rect( Rect(
-312.0, -20.0, 200.0, 16.0, -312.0, -20.0, 200.0, 16.0,
Anchor::NorthWest,
Anchor::NorthEast, Anchor::NorthEast,
), Anchor::NorthWest
Color(1.0, 1.0, 1.0, 1.0) )
); );
textbox::font_serif("outfit_name"); textbox::font_serif("outfit_name");
textbox::weight_bold("outfit_name"); textbox::weight_bold("outfit_name");
textbox::set_text("outfit_name", "Earth"); textbox::set_text("outfit_name", "");
textbox::add( textbox::add(
"outfit_desc", 7.0, 8.5, "outfit_desc", 8.5, 9.0,
Color(1.0, 1.0, 1.0, 1.0),
Rect( Rect(
-166.0, -219.0, 260.0, 78.0, -166.0, -219.0, 260.0, 78.0,
Anchor::Center,
Anchor::NorthEast, Anchor::NorthEast,
), Anchor::Center
Color(1.0, 1.0, 1.0, 1.0) )
); );
textbox::font_serif("outfit_desc"); textbox::font_serif("outfit_desc");
textbox::set_text("outfit_desc", "Earth"); textbox::set_text("outfit_desc", "");
textbox::add( textbox::add(
"outfit_stats", 7.0, 8.5, "outfit_stats", 8.5, 9.0,
Color(1.0, 1.0, 1.0, 1.0),
Rect( Rect(
-295.0, -271.0, 164.0, 216.0, -295.0, -271.0, 164.0, 216.0,
Anchor::NorthWest,
Anchor::NorthEast, Anchor::NorthEast,
), Anchor::NorthWest
Color(1.0, 1.0, 1.0, 1.0) )
); );
textbox::font_mono("outfit_stats"); textbox::font_mono("outfit_stats");
textbox::set_text("outfit_stats", "Earth"); textbox::set_text("outfit_stats", "");
sprite::add(
"buy_button",
"ui::button",
Rect(
-110.71, -281.0, 69.8, 18.924,
Anchor::NorthEast,
Anchor::NorthWest
)
);
textbox::add(
"buy_text", 10.0, 10.0,
Color(1.0, 1.0, 1.0, 1.0),
Rect(
-100.84, -285.34, 51.0, 12.0,
Anchor::NorthEast,
Anchor::NorthWest
)
);
textbox::font_serif("buy_text");
textbox::align_center("buy_text");
textbox::set_text("buy_text", "Buy");
sprite::add(
"sell_button",
"ui::button",
Rect(
-110.71, -306.2, 69.8, 18.924,
Anchor::NorthEast,
Anchor::NorthWest
)
);
textbox::add(
"sell_text", 10.0, 10.0,
Color(1.0, 1.0, 1.0, 1.0),
Rect(
-100.84, -311.15, 51.0, 12.0,
Anchor::NorthEast,
Anchor::NorthWest
)
);
textbox::font_serif("sell_text");
textbox::align_center("sell_text");
textbox::set_text("sell_text", "Sell");
let times_five = false;
let times_ten = false;
let times_hundred = false;
textbox::add(
"five_text", 7.5, 7.5,
Color(0.5, 0.5, 0.5, 1.0),
Rect(
-110.71, -331.2, 69.8, 18.924,
Anchor::NorthEast,
Anchor::NorthWest
)
);
textbox::font_mono("five_text");
textbox::set_text("five_text", "[shift] x5");
textbox::add(
"ten_text", 7.5, 7.5,
Color(0.5, 0.5, 0.5, 1.0),
Rect(
-110.71, -341.2, 69.8, 18.924,
Anchor::NorthEast,
Anchor::NorthWest
)
);
textbox::font_mono("ten_text");
textbox::set_text("ten_text", "[ctrl] x10");
textbox::add(
"hundred_text", 7.5, 7.5,
Color(0.5, 0.5, 0.5, 1.0),
Rect(
-110.71, -351.2, 69.8, 18.924,
Anchor::NorthEast,
Anchor::NorthWest
)
);
textbox::font_mono("hundred_text");
textbox::set_text("hundred_text", "[alt] x100");
// A string containing the selected outfit's index.
// This is always set---if we have an outfitter, we must have at least one outfit.
let selected_outfit = false;
{
let scrollbox_width = state.window_size().x() - (190 + 16 + 300 + 16);
if scrollbox_width <= 0 {
scrollbox_width = 1;
}
// width should be calculated as a fraction of screen width // width should be calculated as a fraction of screen width
let scrollbox_rect = Rect( let scrollbox_rect = Rect(
222.0, -16.0, 470.0, 480.0, 222.0, -16.0, scrollbox_width, 480.0,
Anchor::NorthWest,
Anchor::NorthWest, Anchor::NorthWest,
Anchor::NorthWest
); );
scrollbox::add("outfit_list", scrollbox_rect); scrollbox::add("outfit_list", scrollbox_rect);
let selected_outfit = false;
{
// p cannot be saved in the global scope. // p cannot be saved in the global scope.
let p = state.player_ship(); let p = state.player_ship();
if p.is_landed() {
let s = ""; let s = "";
let x = scrollbox_rect.pos().x() + 45.0; let x = scrollbox_rect.pos().x() + 45.0;
let y = scrollbox_rect.pos().y() - 45.0; let y = scrollbox_rect.pos().y() - 45.0;
for xxx in ["1","2","3"] {
for i in p.landed_on().outfitter() { for i in p.landed_on().outfitter() {
if selected_outfit == false {
selected_outfit = i.index();
update_outfit_info(selected_outfit);
}
s = s + i.display_name() + "\n"; s = s + i.display_name() + "\n";
let thumb_name = "outfit.thumb." + i.index() + xxx; let thumb_name = "outfit.thumb." + i.index();
let backg_name = "outfit.backg." + i.index() + xxx; let backg_name = "outfit.backg." + i.index();
let title_name = "outfit.title." + i.index() + xxx; let title_name = "outfit.title." + i.index();
sprite::add( sprite::add(
backg_name, backg_name,
"ui::outfitbg", "ui::outfitbg",
Rect( Rect(
x, y, 90.0, 90.0, x, y, 90.0, 90.0,
Anchor::Center, Anchor::NorthWest,
Anchor::NorthWest Anchor::Center
) )
); );
sprite::preserve_aspect(backg_name, true); sprite::preserve_aspect(backg_name, true);
@ -205,8 +310,8 @@ fn init(state) {
i.thumbnail(), i.thumbnail(),
Rect( Rect(
x, y, 75.0, 75.0, x, y, 75.0, 75.0,
Anchor::Center, Anchor::NorthWest,
Anchor::NorthWest Anchor::Center
) )
); );
sprite::preserve_aspect(thumb_name, true); sprite::preserve_aspect(thumb_name, true);
@ -215,12 +320,12 @@ fn init(state) {
textbox::add( textbox::add(
title_name, title_name,
10.0, 10.0, 10.0, 10.0,
Color(1.0, 1.0, 1.0, 1.0),
Rect( Rect(
x, y - 50.0, 90.0, 10.0, x, y - 50.0, 90.0, 10.0,
Anchor::Center,
Anchor::NorthWest, Anchor::NorthWest,
), Anchor::Center
Color(1.0, 1.0, 1.0, 1.0) )
); );
textbox::font_sans(title_name); textbox::font_sans(title_name);
textbox::align_center(title_name); textbox::align_center(title_name);
@ -236,63 +341,93 @@ fn init(state) {
} }
} }
} }
textbox::set_text("outfit_stats", s);
} update_ship_info(state);
}
} }
fn event(state, event) { fn event(state, event) {
if type_of(event) == "MouseHoverEvent" { if type_of(event) == "MouseHoverEvent" {
let element = event.element(); let element = event.element();
if element == "exit_button" {
if (
element == "exit_button"
|| element == "buy_button"
|| element == "sell_button"
){
if event.is_enter() { if event.is_enter() {
sprite::jump_to("exit_button", "on:top", 0.1); sprite::jump_to(element, "on:top", 0.1);
} else { } else {
sprite::jump_to("exit_button", "off:top", 0.1); sprite::jump_to(element, "off:top", 0.1);
} }
} }
if element.starts_with("outfit.backg.") && element != selected_outfit {
if element.starts_with("outfit.backg.") {
if event.element().split("outfit.backg.".len())[1] == selected_outfit {
if event.is_enter() {
sprite::jump_to(element, "hoverselected:top", 0.1);
} else {
sprite::jump_to(element, "selected:top", 0.1);
}
} else {
if event.is_enter() { if event.is_enter() {
sprite::jump_to(element, "hover:top", 0.1); sprite::jump_to(element, "hover:top", 0.1);
} else { } else {
sprite::jump_to(element, "off:top", 0.1); sprite::jump_to(element, "off:top", 0.1);
} }
} }
}
return PlayerDirective::None; return PlayerDirective::None;
} }
// TODO: this occasionally breaks because of sprite ordering.
// Clicks go to se_box instead!
if type_of(event) == "MouseClickEvent" { if type_of(event) == "MouseClickEvent" {
if !event.is_down() { if !event.is_down() {
return PlayerDirective::None; return PlayerDirective::None;
} }
print(event.element());
let element = event.element(); let element = event.element();
if element == "exit_button" { if element == "exit_button" {
ui::go_to_scene("landed"); ui::go_to_scene("landed");
return PlayerDirective::None; return PlayerDirective::None;
} }
if element.starts_with("outfit.backg.") && element != selected_outfit { if (
element.starts_with("outfit.backg.") &&
event.element().split("outfit.backg.".len())[1] != selected_outfit
) {
if selected_outfit != false { if selected_outfit != false {
sprite::jump_to(selected_outfit, "off:top", 0.1); sprite::jump_to("outfit.backg." + selected_outfit, "off:top", 0.1);
} }
sprite::jump_to(element, "selected:top", 0.1); /// We can't click on this sprite without first hovering!
selected_outfit = element; sprite::jump_to(element, "hoverselected:top", 0.1);
selected_outfit = event.element().split("outfit.backg.".len())[1];
update_outfit_info(selected_outfit);
return PlayerDirective::None; return PlayerDirective::None;
} }
return; return;
} }
if type_of(event) == "KeyboardEvent" {
if event.key() == "up" {
times_five = event.is_down();
}
if event.key() == "left" {
times_ten = event.is_down();
}
if event.key() == "right" {
times_hundred = event.is_down();
}
update_outfit_box(times_five, times_ten, times_hundred);
return PlayerDirective::None;
}
if type_of(event) == "PlayerShipStateEvent" { if type_of(event) == "PlayerShipStateEvent" {
if !state.player_ship().is_landed() { if !state.player_ship().is_landed() {
ui::go_to_scene("flying"); ui::go_to_scene("flying");
@ -300,3 +435,142 @@ fn event(state, event) {
return PlayerDirective::None; return PlayerDirective::None;
} }
} }
fn update_outfit_box(times_five, times_ten, times_hundred) {
let times = 1;
if times_five {
times *= 5;
textbox::set_color("five_text", Color(1.0, 1.0, 1.0, 1.0));
} else {
textbox::set_color("five_text", Color(0.5, 0.5, 0.5, 1.0));
}
if times_ten {
times *= 10;
textbox::set_color("ten_text", Color(1.0, 1.0, 1.0, 1.0));
} else {
textbox::set_color("ten_text", Color(0.5, 0.5, 0.5, 1.0));
}
if times_hundred {
times *= 100;
textbox::set_color("hundred_text", Color(1.0, 1.0, 1.0, 1.0));
} else {
textbox::set_color("hundred_text", Color(0.5, 0.5, 0.5, 1.0));
}
if times != 1 {
textbox::set_text("buy_text", "Buy x" + times);
textbox::set_text("sell_text", "Sell x" + times);
} else {
textbox::set_text("buy_text", "Buy");
textbox::set_text("sell_text", "Sell");
}
}
fn update_outfit_info(selected_outfit) {
let outfit = ct::get_outfit(selected_outfit);
if outfit.is_some() {
let stats = "";
let tlen = 20;
if outfit.stat_thrust() != 0 {
let s = "thrust ";
s.pad(tlen, " ");
stats += s + outfit.stat_thrust();
stats += "\n";
}
if outfit.stat_steer_power() != 0 {
let s = "steer power ";
s.pad(tlen, " ");
stats += s + outfit.stat_steer_power();
stats += "\n";
}
if outfit.stat_shield_strength() != 0 {
let s = "shield strength ";
s.pad(tlen, " ");
stats += s + outfit.stat_shield_strength();
stats += "\n";
}
if outfit.stat_shield_generation() != 0 {
let s = "shield regen";
s.pad(tlen, " ");
stats += s + outfit.stat_shield_generation();
stats += "\n";
}
if outfit.stat_shield_delay() != 0 {
let s = "shield delay ";
s.pad(tlen, " ");
stats += s + outfit.stat_shield_delay();
stats += "\n";
}
if outfit.stat_shield_dps() != 0 {
let s = "shield dps ";
s.pad(tlen, " ");
stats += s + outfit.stat_shield_dps();
stats += "\n";
}
sprite::set_sprite("outfit_thumb", outfit.thumbnail());
textbox::set_text("outfit_name", outfit.display_name());
textbox::set_text("outfit_desc", outfit.desc());
textbox::set_text("outfit_stats", stats);
}
}
fn update_ship_info(state) {
let ship = state.player_ship();
if ship.is_some() {
let stats = "";
let tlen = 20;
// TODO: outfits add mass
// TODO: calculate radial acceleration
{
let s = "shield strength ";
s.pad(tlen, " ");
stats += s + ship.stat_shield_strength();
stats += "\n";
}
{
let s = "hull strength ";
s.pad(tlen, " ");
stats += s + ship.total_hull();
stats += "\n\n";
}
{
let s = "mass ";
s.pad(tlen, " ");
stats += s + ship.empty_mass();
stats += "\n";
}
{
let s = "thrust ";
s.pad(tlen, " ");
stats += s + ship.stat_thrust();
stats += "\n";
}
{
let s = "steer power ";
s.pad(tlen, " ");
stats += s + ship.stat_steer_power();
stats += "\n";
}
{
let s = "max shield regen";
s.pad(tlen, " ");
stats += s + ship.stat_max_shield_generation();
stats += "\n";
}
sprite::set_sprite("ship_thumb", ship.thumbnail());
textbox::set_text("ship_name", "Hyperion");
textbox::set_text("ship_type", state.player_ship().display_name());
textbox::set_text("ship_stats", stats);
}
}

View File

@ -137,7 +137,7 @@ impl From<ImmutableString> for ContentIndex {
impl From<ContentIndex> for ImmutableString { impl From<ContentIndex> for ImmutableString {
fn from(value: ContentIndex) -> Self { fn from(value: ContentIndex) -> Self {
ImmutableString::from(Arc::into_inner(value.0).unwrap()) ImmutableString::from(value.0.as_ref().clone())
} }
} }

View File

@ -12,7 +12,7 @@ pub(crate) mod system;
pub use config::Config; pub use config::Config;
pub use effect::*; pub use effect::*;
pub use faction::{Faction, Relationship}; pub use faction::{Faction, Relationship};
pub use outfit::{Gun, Outfit, Projectile, ProjectileCollider}; pub use outfit::*;
pub use outfitspace::OutfitSpace; pub use outfitspace::OutfitSpace;
pub use ship::{ pub use ship::{
CollapseEffectSpawner, CollapseEvent, EffectCollapseEvent, EnginePoint, GunPoint, Ship, CollapseEffectSpawner, CollapseEvent, EffectCollapseEvent, EnginePoint, GunPoint, Ship,

View File

@ -23,6 +23,7 @@ pub(crate) mod syntax {
pub struct Outfit { pub struct Outfit {
pub thumbnail: ContentIndex, pub thumbnail: ContentIndex,
pub name: String, pub name: String,
pub desc: String,
pub engine: Option<Engine>, pub engine: Option<Engine>,
pub steering: Option<Steering>, pub steering: Option<Steering>,
pub space: outfitspace::syntax::OutfitSpace, pub space: outfitspace::syntax::OutfitSpace,
@ -145,15 +146,12 @@ pub struct Outfit {
/// The name of this outfit /// The name of this outfit
pub display_name: String, pub display_name: String,
/// The description of this outfit
pub desc: String,
/// Thie outfit's index /// Thie outfit's index
pub index: ContentIndex, pub index: ContentIndex,
/// How much engine thrust this outfit produces
pub engine_thrust: f32,
/// How much steering power this outfit provids
pub steer_power: f32,
/// The engine flare sprite this outfit creates. /// The engine flare sprite this outfit creates.
/// Its location and size is determined by a ship's /// Its location and size is determined by a ship's
/// engine points. /// engine points.
@ -165,6 +163,23 @@ pub struct Outfit {
/// Jump to this edge when engines turn off /// Jump to this edge when engines turn off
pub engine_flare_on_stop: Option<SectionEdge>, pub engine_flare_on_stop: Option<SectionEdge>,
/// This outfit's gun stats.
/// If this is some, this outfit requires a gun point.
pub gun: Option<Gun>,
/// The stats this outfit provides
pub stats: OutfitStats,
}
/// Outfit statistics
#[derive(Debug, Clone)]
pub struct OutfitStats {
/// How much engine thrust this outfit produces
pub engine_thrust: f32,
/// How much steering power this outfit provids
pub steer_power: f32,
/// Shield hit points /// Shield hit points
pub shield_strength: f32, pub shield_strength: f32,
@ -173,10 +188,37 @@ pub struct Outfit {
/// Wait this many seconds after taking damage before regenerating shields /// Wait this many seconds after taking damage before regenerating shields
pub shield_delay: f32, pub shield_delay: f32,
}
/// This outfit's gun stats. impl OutfitStats {
/// If this is some, this outfit requires a gun point. /// Create a new `OutfitStats`, with all values set to zero.
pub gun: Option<Gun>, pub fn zero() -> Self {
Self {
engine_thrust: 0.0,
steer_power: 0.0,
shield_strength: 0.0,
shield_generation: 0.0,
shield_delay: 0.0,
}
}
/// Add all the stats in `other` to the stats in `self`.
/// Sheld delay is not affected.
pub fn add(&mut self, other: &Self) {
self.engine_thrust += other.engine_thrust;
self.steer_power += other.steer_power;
self.shield_strength += other.shield_strength;
self.shield_generation += other.shield_generation;
}
/// Subtract all the stats in `other` from the stats in `self`.
/// Sheld delay is not affected.
pub fn subtract(&mut self, other: &Self) {
self.engine_thrust -= other.engine_thrust;
self.steer_power -= other.steer_power;
self.shield_strength -= other.shield_strength;
self.shield_generation -= other.shield_generation;
}
} }
/// Defines a projectile's collider /// Defines a projectile's collider
@ -284,15 +326,12 @@ impl crate::Build for Outfit {
display_name: outfit.name, display_name: outfit.name,
thumbnail, thumbnail,
gun, gun,
engine_thrust: 0.0, desc: outfit.desc,
steer_power: 0.0,
engine_flare_sprite: None, engine_flare_sprite: None,
engine_flare_on_start: None, engine_flare_on_start: None,
engine_flare_on_stop: None, engine_flare_on_stop: None,
space: OutfitSpace::from(outfit.space), space: OutfitSpace::from(outfit.space),
shield_delay: 0.0, stats: OutfitStats::zero(),
shield_generation: 0.0,
shield_strength: 0.0,
}; };
// Engine stats // Engine stats
@ -307,7 +346,7 @@ impl crate::Build for Outfit {
} }
Some(t) => t.clone(), Some(t) => t.clone(),
}; };
o.engine_thrust = engine.thrust; o.stats.engine_thrust = engine.thrust;
o.engine_flare_sprite = Some(sprite.clone()); o.engine_flare_sprite = Some(sprite.clone());
// Flare animation will traverse this edge when the player presses the thrust key // Flare animation will traverse this edge when the player presses the thrust key
@ -381,14 +420,14 @@ impl crate::Build for Outfit {
// Steering stats // Steering stats
if let Some(steer) = outfit.steering { if let Some(steer) = outfit.steering {
o.steer_power = steer.power; o.stats.steer_power = steer.power;
} }
// Shield stats // Shield stats
if let Some(shield) = outfit.shield { if let Some(shield) = outfit.shield {
o.shield_delay = shield.delay.unwrap_or(0.0); o.stats.shield_delay = shield.delay.unwrap_or(0.0);
o.shield_generation = shield.generation.unwrap_or(0.0); o.stats.shield_generation = shield.generation.unwrap_or(0.0);
o.shield_strength = shield.strength.unwrap_or(0.0); o.stats.shield_strength = shield.strength.unwrap_or(0.0);
} }
content.outfits.insert(o.index.clone(), Arc::new(o)); content.outfits.insert(o.index.clone(), Arc::new(o));

View File

@ -145,6 +145,7 @@ pub struct LandableSystemObject {
pub image: Arc<Sprite>, pub image: Arc<Sprite>,
/// The outfits we can buy here /// The outfits we can buy here
/// If this is empty, this landable has no outfitter.
pub outfitter: Vec<Arc<Outfit>>, pub outfitter: Vec<Arc<Outfit>>,
} }

View File

@ -2,7 +2,6 @@ use crate::globaluniform::{AtlasArray, AtlasImageLocation};
use anyhow::Result; use anyhow::Result;
use bytemuck::Zeroable; use bytemuck::Zeroable;
use galactica_content::Content; use galactica_content::Content;
use galactica_packer::SpriteAtlasImage;
use galactica_util::constants::ASSET_CACHE; use galactica_util::constants::ASSET_CACHE;
use image::GenericImageView; use image::GenericImageView;
use log::info; use log::info;
@ -72,16 +71,6 @@ impl RawTexture {
} }
} }
#[derive(Debug, Clone)]
pub struct Texture {
pub index: u32, // Index in texture array
pub len: u32, // Number of frames
pub frame_duration: f32, // Frames per second
pub aspect: f32, // width / height
pub repeat: u32, // How to re-play this texture
pub location: Vec<SpriteAtlasImage>,
}
pub struct TextureArray { pub struct TextureArray {
pub bind_group: wgpu::BindGroup, pub bind_group: wgpu::BindGroup,
pub bind_group_layout: BindGroupLayout, pub bind_group_layout: BindGroupLayout,

View File

@ -0,0 +1,121 @@
use galactica_content::{Content, Outfit};
use log::error;
use rhai::{CustomType, FnNamespace, FuncRegistration, ImmutableString, Module, TypeBuilder};
use std::sync::Arc;
pub fn build_ct_module(ct_src: Arc<Content>) -> Module {
let mut module = Module::new();
module.set_id("GalacticaContentModule");
let ct = ct_src.clone();
let _ = FuncRegistration::new("get_outfit")
.with_namespace(FnNamespace::Internal)
.set_into_module(&mut module, move |outfit_name: ImmutableString| {
let outfit = ct.outfits.get(&outfit_name.clone().into());
match outfit {
Some(o) => OutfitState::new(o.clone()),
None => {
error!("called `ct::get_outfit` with an invalid outfit `{outfit_name:?}`");
OutfitState::new_none()
}
}
});
return module;
}
#[derive(Debug, Clone)]
pub struct OutfitState {
outfit: Option<Arc<Outfit>>,
}
impl OutfitState {
pub fn new(outfit: Arc<Outfit>) -> Self {
Self {
outfit: Some(outfit),
}
}
pub fn new_none() -> Self {
Self { outfit: None }
}
}
impl CustomType for OutfitState {
fn build(mut builder: TypeBuilder<Self>) {
builder
.with_name("OutfitState")
.with_fn("is_some", |s: &mut Self| s.outfit.is_some())
.with_fn("display_name", |s: &mut Self| {
s.outfit
.as_ref()
.map(|x| x.display_name.clone())
.unwrap_or("".to_string())
})
.with_fn("desc", |s: &mut Self| {
s.outfit
.as_ref()
.map(|x| x.desc.clone())
.unwrap_or("".to_string())
})
.with_fn("index", |s: &mut Self| {
s.outfit
.as_ref()
.map(|x| x.index.to_string())
.unwrap_or("".to_string())
})
.with_fn("thumbnail", |s: &mut Self| {
s.outfit
.as_ref()
.map(|x| x.thumbnail.index.to_string())
.unwrap_or("".to_string())
})
//
// Stat getters
//
.with_fn("stat_thrust", |s: &mut Self| {
s.outfit
.as_ref()
.map(|x| x.stats.steer_power)
.unwrap_or(0.0)
})
.with_fn("stat_steer_power", |s: &mut Self| {
s.outfit
.as_ref()
.map(|x| x.stats.steer_power)
.unwrap_or(0.0)
})
.with_fn("stat_shield_strength", |s: &mut Self| {
s.outfit
.as_ref()
.map(|x| x.stats.shield_strength)
.unwrap_or(0.0)
})
.with_fn("stat_shield_generation", |s: &mut Self| {
s.outfit
.as_ref()
.map(|x| x.stats.shield_generation)
.unwrap_or(0.0)
})
.with_fn("stat_shield_delay", |s: &mut Self| {
s.outfit
.as_ref()
.map(|x| x.stats.shield_delay)
.unwrap_or(0.0)
})
.with_fn("stat_shield_dps", |s: &mut Self| {
s.outfit
.as_ref()
.map(|x| {
if x.gun.is_some() {
(1.0 / x.gun.as_ref().unwrap().rate)
* x.gun.as_ref().unwrap().projectile.damage
} else {
0.0
}
})
.unwrap_or(0.0)
});
}
}

View File

@ -1,4 +1,5 @@
mod conf; mod conf;
mod ct;
mod radialbar; mod radialbar;
mod scrollbox; mod scrollbox;
mod sprite; mod sprite;
@ -6,6 +7,7 @@ mod textbox;
mod ui; mod ui;
pub use conf::build_conf_module; pub use conf::build_conf_module;
pub use ct::{build_ct_module, OutfitState};
pub use radialbar::build_radialbar_module; pub use radialbar::build_radialbar_module;
pub use scrollbox::build_scrollbox_module; pub use scrollbox::build_scrollbox_module;
pub use sprite::build_sprite_module; pub use sprite::build_sprite_module;

View File

@ -62,6 +62,32 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
} }
}); });
let state = state_src.clone();
let ct = ct_src.clone();
let _ = FuncRegistration::new("set_sprite")
.with_namespace(FnNamespace::Internal)
.set_into_module(
&mut module,
move |name: ImmutableString, sprite: ImmutableString| {
let mut ui_state = state.borrow_mut();
match ui_state.get_mut_by_name(&name) {
Some(UiElement::Sprite(x)) => {
let m = ct.sprites.get(&sprite.clone().into()).clone();
if m.is_none() {
error!("called `sprite::set_sprite` with an invalid sprite `{sprite}`");
return;
}
x.set_sprite(m.unwrap().clone())
}
_ => {
error!("called `sprite::set_sprite` on an invalid name `{sprite}`")
}
}
},
);
let state = state_src.clone(); let state = state_src.clone();
let ct = ct_src.clone(); let ct = ct_src.clone();
let _ = FuncRegistration::new("set_mask") let _ = FuncRegistration::new("set_mask")
@ -198,6 +224,7 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
e.set_color(x); e.set_color(x);
}); });
// TODO: fix click collider when preserving aspect
let state = state_src.clone(); let state = state_src.clone();
let _ = FuncRegistration::new("preserve_aspect") let _ = FuncRegistration::new("preserve_aspect")
.with_namespace(FnNamespace::Internal) .with_namespace(FnNamespace::Internal)
@ -221,8 +248,9 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
e.set_preserve_aspect(x); e.set_preserve_aspect(x);
}); });
// TODO: maybe remove?
let state = state_src.clone(); let state = state_src.clone();
let _ = FuncRegistration::new("set_disable_events") let _ = FuncRegistration::new("disable_events")
.with_namespace(FnNamespace::Internal) .with_namespace(FnNamespace::Internal)
.set_into_module(&mut module, move |name: ImmutableString, x: bool| { .set_into_module(&mut module, move |name: ImmutableString, x: bool| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
@ -230,13 +258,13 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
Some(UiElement::SubElement { element, .. }) => match &mut **element { Some(UiElement::SubElement { element, .. }) => match &mut **element {
UiElement::Sprite(x) => x, UiElement::Sprite(x) => x,
_ => { _ => {
error!("called `sprite::set_disable_events` on an invalid name `{name}`"); error!("called `sprite::disable_events` on an invalid name `{name}`");
return; return;
} }
}, },
Some(UiElement::Sprite(x)) => x, Some(UiElement::Sprite(x)) => x,
_ => { _ => {
error!("called `sprite::set_disable_events` on an invalid name `{name}`"); error!("called `sprite::disable_events` on an invalid name `{name}`");
return; return;
} }
}; };

View File

@ -23,8 +23,8 @@ pub fn build_textbox_module(
move |name: ImmutableString, move |name: ImmutableString,
font_size: f32, font_size: f32,
line_height: f32, line_height: f32,
rect: Rect, color: Color,
color: Color| { rect: Rect| {
let mut ui_state = state.borrow_mut(); let mut ui_state = state.borrow_mut();
if ui_state.contains_name(&name) { if ui_state.contains_name(&name) {
@ -72,6 +72,32 @@ pub fn build_textbox_module(
}, },
); );
let state = state_src.clone();
let font = font_src.clone();
let _ = FuncRegistration::new("set_color")
.with_namespace(FnNamespace::Internal)
.set_into_module(&mut module, move |name: ImmutableString, color: Color| {
let mut ui_state = state.borrow_mut();
let e = match ui_state.get_mut_by_name(&name) {
Some(UiElement::SubElement { element, .. }) => match &mut **element {
UiElement::Text(x) => x,
_ => {
error!("called `textbox::set_color` on an invalid name `{name}`");
return;
}
},
Some(UiElement::Text(x)) => x,
_ => {
error!("called `textbox::set_color` on an invalid name `{name}`");
return;
}
};
e.set_color(&mut font.borrow_mut(), color);
});
let state = state_src.clone(); let state = state_src.clone();
let font = font_src.clone(); let font = font_src.clone();
let _ = FuncRegistration::new("align_left") let _ = FuncRegistration::new("align_left")

View File

@ -10,19 +10,11 @@ pub enum Anchor {
} }
#[export_module] #[export_module]
#[allow(non_upper_case_globals)]
pub mod anchor_mod { pub mod anchor_mod {
#[allow(non_upper_case_globals)]
pub const Center: Anchor = Anchor::Center; pub const Center: Anchor = Anchor::Center;
#[allow(non_upper_case_globals)]
pub const NorthWest: Anchor = Anchor::NorthWest; pub const NorthWest: Anchor = Anchor::NorthWest;
#[allow(non_upper_case_globals)]
pub const NorthEast: Anchor = Anchor::NorthEast; pub const NorthEast: Anchor = Anchor::NorthEast;
#[allow(non_upper_case_globals)]
pub const SouthWest: Anchor = Anchor::SouthWest; pub const SouthWest: Anchor = Anchor::SouthWest;
#[allow(non_upper_case_globals)]
pub const SouthEast: Anchor = Anchor::SouthEast; pub const SouthEast: Anchor = Anchor::SouthEast;
} }

View File

@ -17,7 +17,7 @@ pub struct Rect {
} }
impl Rect { impl Rect {
pub fn new(x: f32, y: f32, w: f32, h: f32, anchor_self: Anchor, anchor_parent: Anchor) -> Self { pub fn new(x: f32, y: f32, w: f32, h: f32, anchor_parent: Anchor, anchor_self: Anchor,) -> Self {
Self { Self {
pos: Point2::new(x, y), pos: Point2::new(x, y),
dim: Vector2::new(w, h), dim: Vector2::new(w, h),

View File

@ -6,6 +6,7 @@ mod state;
pub use directive::*; pub use directive::*;
pub use event::*; pub use event::*;
pub use functions::OutfitState;
pub use helpers::{anchor::*, color::*, rect::*, vector::*}; pub use helpers::{anchor::*, color::*, rect::*, vector::*};
pub use state::*; pub use state::*;
@ -52,6 +53,7 @@ pub fn register_into_engine(
// Modules // Modules
engine.register_static_module("ui", functions::build_ui_module(state_src.clone()).into()); engine.register_static_module("ui", functions::build_ui_module(state_src.clone()).into());
engine.register_static_module("ct", functions::build_ct_module(ct_src.clone()).into());
engine.register_static_module( engine.register_static_module(
"sprite", "sprite",
functions::build_sprite_module(ct_src.clone(), state_src.clone()).into(), functions::build_sprite_module(ct_src.clone(), state_src.clone()).into(),

View File

@ -1,15 +1,16 @@
use galactica_content::{Outfit, Ship, SystemObject}; use galactica_content::{Ship, SystemObject};
use galactica_system::{ use galactica_system::{
data::{self}, data::{self},
phys::{objects::PhysShip, PhysSimShipHandle}, phys::{objects::PhysShip, PhysSimShipHandle},
}; };
use galactica_util::to_degrees; use galactica_util::to_degrees;
use log::error; use log::error;
use nalgebra::Vector2;
use rapier2d::dynamics::RigidBody; use rapier2d::dynamics::RigidBody;
use rhai::{Array, CustomType, Dynamic, ImmutableString, TypeBuilder}; use rhai::{Array, CustomType, Dynamic, ImmutableString, TypeBuilder};
use std::sync::Arc; use std::sync::Arc;
use super::{Color, UiVector}; use super::{functions::OutfitState, Color, UiVector};
use crate::{RenderInput, RenderState}; use crate::{RenderInput, RenderState};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -89,18 +90,16 @@ impl CustomType for ShipState {
.with_fn("content_index", |s: &mut Self| { .with_fn("content_index", |s: &mut Self| {
s.get_content().display_name.clone() s.get_content().display_name.clone()
}) })
.with_fn("thumbnail", |s: &mut Self| { .with_fn("thumbnail", |s: &mut Self| -> ImmutableString {
s.get_content().thumbnail.clone() s.get_content().thumbnail.index.clone().into()
}) })
.with_fn("landed_on", |s: &mut Self| s.landed_on()) .with_fn("landed_on", |s: &mut Self| s.landed_on())
.with_fn("get_shields", |s: &mut Self| { .with_fn("current_shields", |s: &mut Self| {
s.get_ship().get_data().get_shields() s.get_ship().get_data().get_shields()
}) })
.with_fn("get_total_shields", |s: &mut Self| { .with_fn("total_hull", |s: &mut Self| s.get_content().hull)
s.get_ship().get_data().get_outfits().get_total_shields() .with_fn("empty_mass", |s: &mut Self| s.get_content().mass)
}) .with_fn("current_hull", |s: &mut Self| {
.with_fn("get_total_hull", |s: &mut Self| s.get_content().hull)
.with_fn("get_hull", |s: &mut Self| {
s.get_ship().get_data().get_hull() s.get_ship().get_data().get_hull()
}) })
.with_fn("get_size", |s: &mut Self| s.get_content().size) .with_fn("get_size", |s: &mut Self| s.get_content().size)
@ -113,25 +112,37 @@ impl CustomType for ShipState {
let h = s.get_ship().get_data().get_faction(); let h = s.get_ship().get_data().get_faction();
let c = h.color; let c = h.color;
Color::new(c[0], c[1], c[2], 1.0) Color::new(c[0], c[1], c[2], 1.0)
}); })
} //
} // Stat getters
//
#[derive(Debug, Clone)] .with_fn("stat_thrust", |s: &mut Self| {
pub struct OutfitState { s.get_ship()
outfit: Arc<Outfit>, .get_data()
} .get_outfits()
.get_stats()
impl OutfitState {} .engine_thrust
})
impl CustomType for OutfitState { .with_fn("stat_steer_power", |s: &mut Self| {
fn build(mut builder: TypeBuilder<Self>) { s.get_ship()
builder .get_data()
.with_name("OutfitState") .get_outfits()
.with_fn("display_name", |s: &mut Self| s.outfit.display_name.clone()) .get_stats()
.with_fn("index", |s: &mut Self| s.outfit.index.to_string()) .steer_power
.with_fn("thumbnail", |s: &mut Self| { })
s.outfit.thumbnail.index.to_string() .with_fn("stat_shield_strength", |s: &mut Self| {
s.get_ship()
.get_data()
.get_outfits()
.get_stats()
.shield_strength
})
.with_fn("stat_max_shield_generation", |s: &mut Self| {
s.get_ship()
.get_data()
.get_outfits()
.get_stats()
.shield_generation
}); });
} }
} }
@ -153,7 +164,7 @@ impl SystemObjectState {
.unwrap() .unwrap()
.outfitter .outfitter
{ {
a.push(Dynamic::from(OutfitState { outfit: o.clone() })); a.push(Dynamic::from(OutfitState::new(o.clone())));
} }
return a; return a;
} }
@ -234,6 +245,7 @@ impl CustomType for SystemObjectState {
pub struct State { pub struct State {
input: Arc<RenderInput>, input: Arc<RenderInput>,
window_aspect: f32, window_aspect: f32,
window_size: Vector2<u32>,
} }
// TODO: remove this // TODO: remove this
@ -245,6 +257,7 @@ impl State {
Self { Self {
input: input.clone(), input: input.clone(),
window_aspect: state.window_aspect, window_aspect: state.window_aspect,
window_size: Vector2::new(state.window_size.width, state.window_size.height),
} }
} }
@ -284,6 +297,12 @@ impl CustomType for State {
.with_fn("player_ship", Self::player_ship) .with_fn("player_ship", Self::player_ship)
.with_fn("ships", Self::ships) .with_fn("ships", Self::ships)
.with_fn("objects", Self::objects) .with_fn("objects", Self::objects)
.with_fn("window_aspect", |s: &mut Self| s.window_aspect); .with_fn("window_aspect", |s: &mut Self| s.window_aspect)
.with_fn("window_size", |s: &mut Self| {
UiVector::new(
s.window_size.x as f32 / s.input.ct.config.ui_scale,
s.window_size.y as f32 / s.input.ct.config.ui_scale,
)
});
} }
} }

View File

@ -50,6 +50,10 @@ impl UiSprite {
} }
} }
pub fn set_sprite(&mut self, sprite: Arc<Sprite>) {
self.anim = SpriteAutomaton::new(sprite);
}
pub fn set_mask(&mut self, mask: Option<Arc<Sprite>>) { pub fn set_mask(&mut self, mask: Option<Arc<Sprite>>) {
self.mask = mask; self.mask = mask;
} }

View File

@ -64,6 +64,11 @@ impl UiTextBox {
self.reflow(font); self.reflow(font);
} }
pub fn set_color(&mut self, font: &mut FontSystem, color: api::Color) {
self.color = color;
self.reflow(font);
}
pub fn set_align(&mut self, font: &mut FontSystem, align: Align) { pub fn set_align(&mut self, font: &mut FontSystem, align: Align) {
self.justify = align; self.justify = align;
self.reflow(font); self.reflow(font);

View File

@ -1,6 +1,6 @@
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use galactica_content::{ContentIndex, GunPoint, Outfit, OutfitSpace}; use galactica_content::{ContentIndex, GunPoint, Outfit, OutfitSpace, OutfitStats};
/// Possible outcomes when adding an outfit /// Possible outcomes when adding an outfit
pub enum OutfitAddResult { pub enum OutfitAddResult {
@ -61,12 +61,17 @@ pub struct OutfitSet {
/// if value is Some, this point is taken. /// if value is Some, this point is taken.
gun_points: HashMap<GunPoint, Option<Arc<Outfit>>>, gun_points: HashMap<GunPoint, Option<Arc<Outfit>>>,
/// Outfit values /// The combined stats of all outfits in this set.
/// This isn't strictly necessary, but we don't want to /// There are two things to note here:
/// re-compute this on each frame. /// First, shield_delay is always zero. That is handled
engine_thrust: f32, /// seperately, since it is different for every outfit.
steer_power: f32, /// Second, shield_generation represents the MAXIMUM POSSIBLE
shield_strength: f32, /// shield generation, after all delays have expired.
///
/// Note that this field isn't strictly necessary... we could compute stats
/// by iterating over the outfits in this set. We don't want to do this every
/// frame, though, so we keep track of the total sum here.
stats: OutfitStats,
/// All shield generators in this outfit set /// All shield generators in this outfit set
// These can't be summed into one value, since each has a // These can't be summed into one value, since each has a
@ -81,10 +86,7 @@ impl OutfitSet {
total_space: available_space, total_space: available_space,
used_space: OutfitSpace::new(), used_space: OutfitSpace::new(),
gun_points: gun_points.iter().map(|x| (x.clone(), None)).collect(), gun_points: gun_points.iter().map(|x| (x.clone(), None)).collect(),
stats: OutfitStats::zero(),
engine_thrust: 0.0,
steer_power: 0.0,
shield_strength: 0.0,
shield_generators: Vec::new(), shield_generators: Vec::new(),
} }
} }
@ -111,13 +113,12 @@ impl OutfitSet {
self.used_space += o.space; self.used_space += o.space;
self.engine_thrust += o.engine_thrust; self.stats.add(&o.stats);
self.steer_power += o.steer_power;
self.shield_strength += o.shield_strength;
self.shield_generators.push(ShieldGenerator { self.shield_generators.push(ShieldGenerator {
outfit: o.clone(), outfit: o.clone(),
delay: o.shield_delay, delay: o.stats.shield_delay,
generation: o.shield_generation, generation: o.stats.shield_generation,
}); });
if self.outfits.contains_key(&o.index) { if self.outfits.contains_key(&o.index) {
@ -143,9 +144,7 @@ impl OutfitSet {
self.used_space -= o.space; self.used_space -= o.space;
self.engine_thrust -= o.engine_thrust; self.stats.subtract(&o.stats);
self.steer_power -= o.steer_power;
self.shield_strength -= o.shield_strength;
{ {
// This index will exist, since we checked the hashmap // This index will exist, since we checked the hashmap
@ -183,11 +182,6 @@ impl OutfitSet {
self.shield_generators.iter() self.shield_generators.iter()
} }
/// Get maximum possible shield regen
pub fn get_max_shield_regen(&self) -> f32 {
self.shield_generators.iter().map(|x| x.generation).sum()
}
/// Get the outfit attached to the given gun point /// Get the outfit attached to the given gun point
/// Will panic if this gunpoint is not in this outfit set. /// Will panic if this gunpoint is not in this outfit set.
pub fn get_gun(&self, point: &GunPoint) -> Option<Arc<Outfit>> { pub fn get_gun(&self, point: &GunPoint) -> Option<Arc<Outfit>> {
@ -204,18 +198,13 @@ impl OutfitSet {
&self.used_space &self.used_space
} }
/// Total foward thrust /// Get the combined stats of all outfits in this set.
pub fn get_engine_thrust(&self) -> f32 { /// There are two things to note here:
self.engine_thrust /// First, shield_delay is always zero. That is handled
} /// seperately, since it is different for every outfit.
/// Second, shield_generation represents the MAXIMUM POSSIBLE
/// Total steer power /// shield generation, after all delays have expired.
pub fn get_steer_power(&self) -> f32 { pub fn get_stats(&self) -> &OutfitStats {
self.steer_power &self.stats
}
/// Total shield strength
pub fn get_total_shields(&self) -> f32 {
self.shield_strength
} }
} }

View File

@ -178,7 +178,7 @@ impl ShipData {
/// Add an outfit to this ship /// Add an outfit to this ship
pub fn add_outfit(&mut self, o: &Arc<Outfit>) -> super::OutfitAddResult { pub fn add_outfit(&mut self, o: &Arc<Outfit>) -> super::OutfitAddResult {
let r = self.outfits.add(o); let r = self.outfits.add(o);
self.shields = self.outfits.get_total_shields(); self.shields = self.outfits.get_stats().shield_strength;
return r; return r;
} }
@ -245,8 +245,8 @@ impl ShipData {
} }
// Regenerate shields // Regenerate shields
if self.shields != self.outfits.get_total_shields() { if self.shields != self.outfits.get_stats().shield_strength {
self.shields = self.outfits.get_total_shields(); self.shields = self.outfits.get_stats().shield_strength;
} }
} }
@ -260,12 +260,12 @@ impl ShipData {
// Regenerate shields // Regenerate shields
let time_since = self.last_hit.elapsed().as_secs_f32(); let time_since = self.last_hit.elapsed().as_secs_f32();
if self.shields != self.outfits.get_total_shields() { if self.shields != self.outfits.get_stats().shield_strength {
for g in self.outfits.iter_shield_generators() { for g in self.outfits.iter_shield_generators() {
if time_since >= g.delay { if time_since >= g.delay {
self.shields += g.generation * t; self.shields += g.generation * t;
if self.shields > self.outfits.get_total_shields() { if self.shields > self.outfits.get_stats().shield_strength {
self.shields = self.outfits.get_total_shields(); self.shields = self.outfits.get_stats().shield_strength;
break; break;
} }
} }

View File

@ -390,21 +390,21 @@ impl PhysShip {
if self.controls.thrust { if self.controls.thrust {
rigid_body.apply_impulse( rigid_body.apply_impulse(
vector![engine_force.x, engine_force.y] vector![engine_force.x, engine_force.y]
* self.data.get_outfits().get_engine_thrust(), * self.data.get_outfits().get_stats().engine_thrust,
true, true,
); );
} }
if self.controls.right { if self.controls.right {
rigid_body.apply_torque_impulse( rigid_body.apply_torque_impulse(
self.data.get_outfits().get_steer_power() * -100.0 * res.t, self.data.get_outfits().get_stats().steer_power * -100.0 * res.t,
true, true,
); );
} }
if self.controls.left { if self.controls.left {
rigid_body.apply_torque_impulse( rigid_body.apply_torque_impulse(
self.data.get_outfits().get_steer_power() * 100.0 * res.t, self.data.get_outfits().get_stats().steer_power * 100.0 * res.t,
true, true,
); );
} }