Compare commits
10 Commits
b64d9c12f6
...
23451eeb4b
Author | SHA1 | Date |
---|---|---|
Mark | 23451eeb4b | |
Mark | b648ef369f | |
Mark | bb269285bb | |
Mark | c8f2001426 | |
Mark | b170f3f53f | |
Mark | 55319d6872 | |
Mark | 5dab73ec24 | |
Mark | f56fd7ea49 | |
Mark | acb5b9d31c | |
Mark | e8c9622832 |
|
@ -816,6 +816,7 @@ dependencies = [
|
||||||
"rapier2d",
|
"rapier2d",
|
||||||
"rhai",
|
"rhai",
|
||||||
"serde",
|
"serde",
|
||||||
|
"smartstring",
|
||||||
"toml",
|
"toml",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
@ -876,6 +877,7 @@ dependencies = [
|
||||||
"galactica-content",
|
"galactica-content",
|
||||||
"galactica-playeragent",
|
"galactica-playeragent",
|
||||||
"galactica-util",
|
"galactica-util",
|
||||||
|
"log",
|
||||||
"nalgebra",
|
"nalgebra",
|
||||||
"rand",
|
"rand",
|
||||||
"rapier2d",
|
"rapier2d",
|
||||||
|
|
|
@ -75,3 +75,4 @@ clap = { version = "4.4.18", features = ["derive"] }
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
log4rs = { version = "1.2.0", features = ["console_appender"] }
|
log4rs = { version = "1.2.0", features = ["console_appender"] }
|
||||||
rhai = { version = "1.17.1", features = ["f32_float", "no_custom_syntax"] }
|
rhai = { version = "1.17.1", features = ["f32_float", "no_custom_syntax"] }
|
||||||
|
smartstring = { version = "1.0.1" }
|
||||||
|
|
15
TODO.md
15
TODO.md
|
@ -1,15 +1,15 @@
|
||||||
# Specific projects
|
# Specific projects
|
||||||
|
|
||||||
## Now:
|
## Now:
|
||||||
- Replace handles with Arcs in content
|
|
||||||
- clean up content
|
- clean up content
|
||||||
- Clean up state api
|
- Clean up state api
|
||||||
- Persistent variables
|
- Clean up & document UI api
|
||||||
|
- Persistent variables in ui scripts
|
||||||
- Better planet icons
|
- Better planet icons
|
||||||
- Clean up scripting errors
|
- Clean up scripting errors
|
||||||
- Mouse colliders
|
- Mouse colliders
|
||||||
- UI captures input?
|
- Fade sprites and text in scrollbox
|
||||||
- No UI zoom scroll
|
- Selection while flying
|
||||||
- outfitter
|
- outfitter
|
||||||
- fps textbox positioning
|
- fps textbox positioning
|
||||||
|
|
||||||
|
@ -24,12 +24,10 @@
|
||||||
- No wobble for ai ships & autopilot
|
- No wobble for ai ships & autopilot
|
||||||
- 🌟 User-configurable outfit space types
|
- 🌟 User-configurable outfit space types
|
||||||
- 🌟 Sticky radar
|
- 🌟 Sticky radar
|
||||||
- Configurable radar
|
|
||||||
- 🌟 Ship damage events
|
- 🌟 Ship damage events
|
||||||
- Better landing animation (slow down)
|
- Better landing animation (slow down)
|
||||||
- Land from farther away
|
- Land from farther away
|
||||||
- Ship collapse: damage + force events
|
- Ship collapse: damage + force events
|
||||||
- Redesign UI elements
|
|
||||||
- Background haze: 3d perlin?
|
- Background haze: 3d perlin?
|
||||||
- nova dust parallax
|
- nova dust parallax
|
||||||
- Motion blur
|
- Motion blur
|
||||||
|
@ -38,13 +36,12 @@
|
||||||
- Reverse engines + flares
|
- Reverse engines + flares
|
||||||
- Turn flares (physics by location?)
|
- Turn flares (physics by location?)
|
||||||
- Angled engines & guns
|
- Angled engines & guns
|
||||||
|
- Unified content dir
|
||||||
|
|
||||||
## Misc fixes & Optimizations
|
## Misc fixes & Optimizations
|
||||||
- 🌟 Better errors when content/asset dirs don't exist
|
- 🌟 Better errors when content/asset dirs don't exist
|
||||||
- Clear `// TODO:` comments
|
- Clear `// TODO:` comments
|
||||||
- Correct drawing order (player on top, landing ships)
|
- Correct drawing order (player on top, landing ships)
|
||||||
- Faster handles (better than a hashmap?)
|
|
||||||
- Check for handle leaks
|
|
||||||
- Better physshiphandle
|
- Better physshiphandle
|
||||||
- Clean up & faster frame timings (average)
|
- Clean up & faster frame timings (average)
|
||||||
- 🌟 Handle lost focus
|
- 🌟 Handle lost focus
|
||||||
|
@ -150,6 +147,7 @@
|
||||||
- Muzzle effect
|
- Muzzle effect
|
||||||
- Effect / sprite color variation
|
- Effect / sprite color variation
|
||||||
- UI Animations
|
- UI Animations
|
||||||
|
- in-game console?
|
||||||
|
|
||||||
|
|
||||||
## Game & Story
|
## Game & Story
|
||||||
|
@ -183,7 +181,6 @@
|
||||||
- Outfit pipeline
|
- Outfit pipeline
|
||||||
- Collision detection
|
- Collision detection
|
||||||
- Ship AI
|
- Ship AI
|
||||||
- Handles
|
|
||||||
- Content specification and pipeline
|
- Content specification and pipeline
|
||||||
- How packer and optimizations work, and why
|
- How packer and optimizations work, and why
|
||||||
- How big should sprite resolutions be? How about frame rate?
|
- How big should sprite resolutions be? How about frame rate?
|
||||||
|
|
2
assets
2
assets
|
@ -1 +1 @@
|
||||||
Subproject commit fba4f1083b5a07a10445cf28bcae4bb05c2cede6
|
Subproject commit 1400f7bb89f1190a11a7371bb23778881073a49f
|
|
@ -1,4 +1,5 @@
|
||||||
[outfit."plasma engines"]
|
[outfit."plasma engines"]
|
||||||
|
name = "Plasma Engines"
|
||||||
thumbnail = "icon::engine"
|
thumbnail = "icon::engine"
|
||||||
|
|
||||||
space.engine = 20
|
space.engine = 20
|
||||||
|
@ -11,6 +12,7 @@ steering.power = 20
|
||||||
|
|
||||||
[outfit."shield generator"]
|
[outfit."shield generator"]
|
||||||
thumbnail = "icon::shield"
|
thumbnail = "icon::shield"
|
||||||
|
name = "Shield Generator"
|
||||||
|
|
||||||
space.outfit = 5
|
space.outfit = 5
|
||||||
shield.generation = 10
|
shield.generation = 10
|
||||||
|
@ -19,7 +21,8 @@ shield.delay = 2.0
|
||||||
|
|
||||||
|
|
||||||
[outfit."blaster"]
|
[outfit."blaster"]
|
||||||
thumbnail = "icon::shield"
|
thumbnail = "icon::blaster"
|
||||||
|
name = "Blaster"
|
||||||
|
|
||||||
space.weapon = 10
|
space.weapon = 10
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
[ship."Gypsum"]
|
[ship."gypsum"]
|
||||||
|
name = "Gypsum"
|
||||||
sprite = "ship::gypsum"
|
sprite = "ship::gypsum"
|
||||||
thumbnail = "icon::gypsum"
|
thumbnail = "icon::gypsum"
|
||||||
size = 100
|
size = 100
|
||||||
|
@ -65,7 +66,7 @@ collision = [
|
||||||
|
|
||||||
|
|
||||||
# Scripted explosion
|
# Scripted explosion
|
||||||
[[ship."Gypsum".collapse.event]]
|
[[ship."gypsum".collapse.event]]
|
||||||
time = 4.9
|
time = 4.9
|
||||||
effects = [
|
effects = [
|
||||||
#[rustfmt:skip],
|
#[rustfmt:skip],
|
||||||
|
@ -76,7 +77,7 @@ effects = [
|
||||||
]
|
]
|
||||||
|
|
||||||
# Scripted explosion
|
# Scripted explosion
|
||||||
[[ship."Gypsum".collapse.event]]
|
[[ship."gypsum".collapse.event]]
|
||||||
time = 0.0
|
time = 0.0
|
||||||
effects = [
|
effects = [
|
||||||
#[rustfmt:skip],
|
#[rustfmt:skip],
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
# TODO: big objects in one config
|
# TODO: big objects in one config
|
||||||
|
# TODO: satisfy conditions to land
|
||||||
|
|
||||||
[system."12 Autumn Above"]
|
[system."12 Autumn Above"]
|
||||||
|
name = "12 Autumn Above"
|
||||||
object.star.sprite = "star::star"
|
object.star.sprite = "star::star"
|
||||||
object.star.position = [0.0, 0.0, 30.0]
|
object.star.position = [0.0, 0.0, 30.0]
|
||||||
object.star.size = 2000
|
object.star.size = 2000
|
||||||
|
@ -13,10 +14,9 @@ object.earth.position.angle = 0
|
||||||
object.earth.position.z = 10.0
|
object.earth.position.z = 10.0
|
||||||
object.earth.size = 1000
|
object.earth.size = 1000
|
||||||
|
|
||||||
# TODO: satisfy conditions to land
|
|
||||||
object.earth.landable = true
|
object.earth.landable.name = "Earth"
|
||||||
object.earth.name = "Earth"
|
object.earth.landable.desc = """
|
||||||
object.earth.desc = """
|
|
||||||
The ancestral home world of humanity, Earth has a population twice that of any other inhabited planet.
|
The ancestral home world of humanity, Earth has a population twice that of any other inhabited planet.
|
||||||
Sprawling cities cover large portions of its surface, many of them overcrowded and dangerous.
|
Sprawling cities cover large portions of its surface, many of them overcrowded and dangerous.
|
||||||
Some people work to scrape together enough money to leave, while at the same time others, born
|
Some people work to scrape together enough money to leave, while at the same time others, born
|
||||||
|
@ -28,8 +28,13 @@ one planet has a greater population than a hundred planets elsewhere. As a resul
|
||||||
settlements of less than a million are grouped together into planetary districts that
|
settlements of less than a million are grouped together into planetary districts that
|
||||||
elect a single representative between them - a source of much frustration in the frontier worlds.
|
elect a single representative between them - a source of much frustration in the frontier worlds.
|
||||||
"""
|
"""
|
||||||
object.earth.image = "ui::landscape::test"
|
object.earth.landable.image = "ui::landscape::test"
|
||||||
|
|
||||||
|
object.earth.landable.outfitter = [
|
||||||
|
"plasma engines",
|
||||||
|
"shield generator",
|
||||||
|
"blaster",
|
||||||
|
]
|
||||||
|
|
||||||
object.luna.sprite = "planet::luna"
|
object.luna.sprite = "planet::luna"
|
||||||
object.luna.position.center = "earth"
|
object.luna.position.center = "earth"
|
||||||
|
|
|
@ -82,6 +82,31 @@ fn event(state, event) {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if type_of(event) == "ScrollEvent" {
|
||||||
|
return ui::set_camera_zoom(
|
||||||
|
ui::get_camera_zoom()
|
||||||
|
- (5.0 * event.val())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if type_of(event) == "KeyboardEvent" {
|
||||||
|
if event.key() == "up" {
|
||||||
|
return PlayerDirective::Engine(event.is_down());
|
||||||
|
}
|
||||||
|
if event.key() == "left" {
|
||||||
|
return PlayerDirective::TurnLeft(event.is_down());
|
||||||
|
}
|
||||||
|
if event.key() == "right" {
|
||||||
|
return PlayerDirective::TurnRight(event.is_down());
|
||||||
|
}
|
||||||
|
if event.key() == "space" {
|
||||||
|
return PlayerDirective::Guns(event.is_down());
|
||||||
|
}
|
||||||
|
if event.key() == "L" && event.is_down() {
|
||||||
|
return PlayerDirective::Land;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn step(state) {
|
fn step(state) {
|
||||||
|
@ -131,7 +156,7 @@ fn step(state) {
|
||||||
// Ships
|
// Ships
|
||||||
{
|
{
|
||||||
for s in state.ships() {
|
for s in state.ships() {
|
||||||
let uid = s.get_uid();
|
let uid = s.phys_uid();
|
||||||
let sprite_name = `radar.ship.${uid}`;
|
let sprite_name = `radar.ship.${uid}`;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -200,7 +225,8 @@ fn step(state) {
|
||||||
// System objects
|
// System objects
|
||||||
{
|
{
|
||||||
for o in state.objects() {
|
for o in state.objects() {
|
||||||
let sprite_name = `radar.object.${o.get_label()}`;
|
let l = o.get_index();
|
||||||
|
let sprite_name = `radar.object.${l}`;
|
||||||
|
|
||||||
if !o.is_some() {
|
if !o.is_some() {
|
||||||
if sprite::exists(sprite_name) {
|
if sprite::exists(sprite_name) {
|
||||||
|
|
|
@ -55,7 +55,7 @@ fn init(state) {
|
||||||
textbox::font_serif("title");
|
textbox::font_serif("title");
|
||||||
textbox::weight_bold("title");
|
textbox::weight_bold("title");
|
||||||
if player.is_landed() {
|
if player.is_landed() {
|
||||||
textbox::set_text("title", player.landed_on().name());
|
textbox::set_text("title", player.landed_on().display_name());
|
||||||
}
|
}
|
||||||
|
|
||||||
textbox::add(
|
textbox::add(
|
||||||
|
@ -78,9 +78,9 @@ fn event(state, event) {
|
||||||
let element = event.element();
|
let element = event.element();
|
||||||
if element == "button" {
|
if element == "button" {
|
||||||
if event.is_enter() {
|
if event.is_enter() {
|
||||||
sprite::take_edge("button", "on:top", 0.1);
|
sprite::jump_to("button", "on:top", 0.1);
|
||||||
} else {
|
} else {
|
||||||
sprite::take_edge("button", "off:top", 0.1);
|
sprite::jump_to("button", "off:top", 0.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -100,6 +100,22 @@ fn event(state, event) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if type_of(event) == "KeyboardEvent" {
|
||||||
|
if !event.is_down() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.key() == "L" {
|
||||||
|
return PlayerDirective::UnLand;
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.key() == "O" {
|
||||||
|
ui::go_to_scene("outfitter");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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");
|
||||||
|
|
|
@ -84,7 +84,7 @@ fn init(state) {
|
||||||
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() {
|
if state.player_ship().is_some() {
|
||||||
textbox::set_text("ship_type", state.player_ship().name());
|
textbox::set_text("ship_type", state.player_ship().display_name());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -161,6 +161,80 @@ fn init(state) {
|
||||||
textbox::font_mono("outfit_stats");
|
textbox::font_mono("outfit_stats");
|
||||||
textbox::set_text("outfit_stats", "Earth");
|
textbox::set_text("outfit_stats", "Earth");
|
||||||
|
|
||||||
|
|
||||||
|
// width should be calculated as a fraction of screen width
|
||||||
|
|
||||||
|
let scrollbox_rect = Rect(
|
||||||
|
222.0, -16.0, 470.0, 480.0,
|
||||||
|
Anchor::NorthWest,
|
||||||
|
Anchor::NorthWest,
|
||||||
|
);
|
||||||
|
|
||||||
|
scrollbox::add("outfit_list", scrollbox_rect);
|
||||||
|
|
||||||
|
let p = state.player_ship();
|
||||||
|
if p.is_landed() {
|
||||||
|
let s = "";
|
||||||
|
let x = scrollbox_rect.pos().x() + 45.0;
|
||||||
|
let y = scrollbox_rect.pos().y() - 45.0;
|
||||||
|
for xxx in ["1","2","3"] {
|
||||||
|
for i in p.landed_on().outfitter() {
|
||||||
|
s = s + i.display_name() + "\n";
|
||||||
|
|
||||||
|
let thumb_name = "outfit.thumb." + i.index() + xxx;
|
||||||
|
let backg_name = "outfit.backg." + i.index() + xxx;
|
||||||
|
let title_name = "outfit.title." + i.index() + xxx;
|
||||||
|
|
||||||
|
sprite::add(
|
||||||
|
backg_name,
|
||||||
|
"ui::outfitbg",
|
||||||
|
Rect(
|
||||||
|
x, y, 90.0, 90.0,
|
||||||
|
Anchor::Center,
|
||||||
|
Anchor::NorthWest
|
||||||
|
)
|
||||||
|
);
|
||||||
|
sprite::preserve_aspect(backg_name, true);
|
||||||
|
scrollbox::add_element("outfit_list", backg_name);
|
||||||
|
|
||||||
|
sprite::add(
|
||||||
|
thumb_name,
|
||||||
|
i.thumbnail(),
|
||||||
|
Rect(
|
||||||
|
x, y, 75.0, 75.0,
|
||||||
|
Anchor::Center,
|
||||||
|
Anchor::NorthWest
|
||||||
|
)
|
||||||
|
);
|
||||||
|
sprite::preserve_aspect(thumb_name, true);
|
||||||
|
scrollbox::add_element("outfit_list", thumb_name);
|
||||||
|
|
||||||
|
textbox::add(
|
||||||
|
title_name,
|
||||||
|
10.0, 10.0,
|
||||||
|
Rect(
|
||||||
|
x, y - 50.0, 90.0, 10.0,
|
||||||
|
Anchor::Center,
|
||||||
|
Anchor::NorthWest,
|
||||||
|
),
|
||||||
|
Color(1.0, 1.0, 1.0, 1.0)
|
||||||
|
);
|
||||||
|
textbox::font_sans(title_name);
|
||||||
|
textbox::align_center(title_name);
|
||||||
|
textbox::set_text(title_name, i.display_name());
|
||||||
|
scrollbox::add_element("outfit_list", title_name);
|
||||||
|
|
||||||
|
x = x + 120.0;
|
||||||
|
if x > (
|
||||||
|
scrollbox_rect.pos().x() + scrollbox_rect.dim().x() - 45.0
|
||||||
|
) {
|
||||||
|
x = scrollbox_rect.pos().x() + 45.0;
|
||||||
|
y = y - 120.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
textbox::set_text("outfit_stats", s);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -169,9 +243,9 @@ fn event(state, event) {
|
||||||
let element = event.element();
|
let element = event.element();
|
||||||
if element == "exit_button" {
|
if element == "exit_button" {
|
||||||
if event.is_enter() {
|
if event.is_enter() {
|
||||||
sprite::take_edge("exit_button", "on:top", 0.1);
|
sprite::jump_to("exit_button", "on:top", 0.1);
|
||||||
} else {
|
} else {
|
||||||
sprite::take_edge("exit_button", "off:top", 0.1);
|
sprite::jump_to("exit_button", "off:top", 0.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -30,3 +30,4 @@ rapier2d = { workspace = true }
|
||||||
lazy_static = { workspace = true }
|
lazy_static = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
rhai = { workspace = true }
|
rhai = { workspace = true }
|
||||||
|
smartstring = { workspace = true }
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
//! This module defines lightweight handles for every content type.
|
|
||||||
//! This isn't strictly necessary (we could refer to them using their string keys),
|
|
||||||
//! but this approach doesn't require frequent string cloning, and
|
|
||||||
//! gives each content type a distinct Rust type.
|
|
||||||
//!
|
|
||||||
//! We could also use raw references to content types, but that creates a mess of lifetimes
|
|
||||||
//! in our code. It's managable, but the approach here is simpler and easier to understand.
|
|
||||||
use std::{cmp::Eq, hash::Hash};
|
|
||||||
|
|
||||||
/// A lightweight representation of a sprite
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
pub struct SpriteHandle {
|
|
||||||
pub(crate) index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A lightweight representation of system body
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
pub struct SystemObjectHandle {
|
|
||||||
/// TODO: pub in crate
|
|
||||||
pub system_handle: SystemHandle,
|
|
||||||
/// The index of this object in system.objects
|
|
||||||
pub body_index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A lightweight representation of an outfit
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
pub struct OutfitHandle {
|
|
||||||
/// TODO: pub in crate, currently for debug (same with all other handles)
|
|
||||||
pub index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A lightweight representation of a gun
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
pub struct GunHandle {
|
|
||||||
/// TODO
|
|
||||||
pub index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A lightweight representation of a ship
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
pub struct ShipHandle {
|
|
||||||
/// TODO
|
|
||||||
pub index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A lightweight representation of a star system
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
pub struct SystemHandle {
|
|
||||||
/// TODO
|
|
||||||
pub index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A lightweight representation of a faction
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
pub struct FactionHandle {
|
|
||||||
/// TODO
|
|
||||||
pub index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A lightweight representation of an effect
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
pub struct EffectHandle {
|
|
||||||
pub(crate) index: usize,
|
|
||||||
}
|
|
|
@ -1,9 +1,6 @@
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
//! This subcrate is responsible for loading, parsing, validating game content,
|
//! This subcrate is responsible for loading, parsing, validating game content.
|
||||||
//! which is usually stored in `./content`.
|
|
||||||
|
|
||||||
mod handle;
|
|
||||||
mod part;
|
mod part;
|
||||||
mod spriteautomaton;
|
mod spriteautomaton;
|
||||||
mod util;
|
mod util;
|
||||||
|
@ -11,18 +8,21 @@ mod util;
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use galactica_packer::{SpriteAtlas, SpriteAtlasImage};
|
use galactica_packer::{SpriteAtlas, SpriteAtlasImage};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
|
use rhai::ImmutableString;
|
||||||
|
use serde::{Deserialize, Deserializer};
|
||||||
|
use smartstring::{LazyCompact, SmartString};
|
||||||
use std::{
|
use std::{
|
||||||
//cell::OnceCell,
|
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
|
fmt::Display,
|
||||||
fs::File,
|
fs::File,
|
||||||
io::Read,
|
io::Read,
|
||||||
num::NonZeroU32,
|
num::NonZeroU32,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use toml;
|
use toml;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
pub use handle::*;
|
|
||||||
pub use part::*;
|
pub use part::*;
|
||||||
pub use spriteautomaton::*;
|
pub use spriteautomaton::*;
|
||||||
|
|
||||||
|
@ -123,11 +123,49 @@ trait Build {
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The type we use to index content objects
|
||||||
|
/// These are only unique WITHIN each "type" of object!
|
||||||
|
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
||||||
|
pub struct ContentIndex(Arc<SmartString<LazyCompact>>);
|
||||||
|
|
||||||
|
impl From<ImmutableString> for ContentIndex {
|
||||||
|
fn from(value: ImmutableString) -> Self {
|
||||||
|
Self::new(&value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContentIndex {
|
||||||
|
/// Make a new ContentIndex from a strign
|
||||||
|
pub fn new(s: &str) -> Self {
|
||||||
|
Self(SmartString::from(s).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a &str to this index's content
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
self.0.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d> Deserialize<'d> for ContentIndex {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'d>,
|
||||||
|
{
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
Ok(Self::new(&s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ContentIndex {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
std::fmt::Display::fmt(&self.0, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Stores temporary data while building context objects
|
/// Stores temporary data while building context objects
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct ContentBuildContext {
|
pub(crate) struct ContentBuildContext {
|
||||||
/// Map effect names to handles
|
pub effect_index: HashMap<ContentIndex, Arc<Effect>>,
|
||||||
pub effect_index: HashMap<String, EffectHandle>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContentBuildContext {
|
impl ContentBuildContext {
|
||||||
|
@ -141,22 +179,29 @@ impl ContentBuildContext {
|
||||||
/// Represents static game content
|
/// Represents static game content
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Content {
|
pub struct Content {
|
||||||
/// Sprites
|
|
||||||
pub sprites: Vec<Sprite>,
|
|
||||||
/// Map strings to texture names.
|
|
||||||
/// This is only necessary because we need to hard-code a few texture names for UI elements.
|
|
||||||
sprite_index: HashMap<String, SpriteHandle>,
|
|
||||||
|
|
||||||
/// Keeps track of which images are in which texture
|
/// Keeps track of which images are in which texture
|
||||||
sprite_atlas: SpriteAtlas,
|
sprite_atlas: SpriteAtlas,
|
||||||
|
|
||||||
outfits: Vec<Outfit>,
|
/// Sprites defined in this game
|
||||||
guns: Vec<Gun>, // TODO: merge with outfit
|
pub sprites: HashMap<ContentIndex, Arc<Sprite>>,
|
||||||
ships: Vec<Ship>,
|
|
||||||
systems: Vec<System>,
|
/// Outfits defined in this game
|
||||||
factions: Vec<Faction>,
|
pub outfits: HashMap<ContentIndex, Arc<Outfit>>,
|
||||||
effects: Vec<Effect>,
|
|
||||||
config: Config,
|
/// Ships defined in this game
|
||||||
|
pub ships: HashMap<ContentIndex, Arc<Ship>>,
|
||||||
|
|
||||||
|
/// Systems defined in this game
|
||||||
|
pub systems: HashMap<ContentIndex, Arc<System>>,
|
||||||
|
|
||||||
|
/// Factions defined in this game
|
||||||
|
pub factions: HashMap<ContentIndex, Arc<Faction>>,
|
||||||
|
|
||||||
|
/// Effects defined in this game
|
||||||
|
pub effects: HashMap<ContentIndex, Arc<Effect>>,
|
||||||
|
|
||||||
|
/// Game configuration
|
||||||
|
pub config: Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loading methods
|
// Loading methods
|
||||||
|
@ -224,14 +269,12 @@ impl Content {
|
||||||
},
|
},
|
||||||
|
|
||||||
sprite_atlas: atlas,
|
sprite_atlas: atlas,
|
||||||
systems: Vec::new(),
|
systems: HashMap::new(),
|
||||||
ships: Vec::new(),
|
ships: HashMap::new(),
|
||||||
guns: Vec::new(),
|
outfits: HashMap::new(),
|
||||||
outfits: Vec::new(),
|
sprites: HashMap::new(),
|
||||||
sprites: Vec::new(),
|
factions: HashMap::new(),
|
||||||
factions: Vec::new(),
|
effects: HashMap::new(),
|
||||||
effects: Vec::new(),
|
|
||||||
sprite_index: HashMap::new(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: enforce sprite and image limits
|
// TODO: enforce sprite and image limits
|
||||||
|
@ -285,23 +328,6 @@ impl Content {
|
||||||
|
|
||||||
// Access methods
|
// Access methods
|
||||||
impl Content {
|
impl Content {
|
||||||
/// Iterate over all valid system handles
|
|
||||||
pub fn iter_systems(&self) -> impl Iterator<Item = SystemHandle> {
|
|
||||||
(0..self.systems.len()).map(|x| SystemHandle { index: x })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a handle from a sprite name
|
|
||||||
pub fn get_sprite_handle(&self, name: &str) -> Option<SpriteHandle> {
|
|
||||||
self.sprite_index.get(name).map(|x| *x)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a sprite from a handle
|
|
||||||
pub fn get_sprite(&self, h: SpriteHandle) -> &Sprite {
|
|
||||||
// In theory, this could fail if h has a bad index, but that shouldn't ever happen.
|
|
||||||
// The only handles that exist should be created by this crate.
|
|
||||||
return &self.sprites[h.index as usize];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the list of atlas files we may use
|
/// Get the list of atlas files we may use
|
||||||
pub fn atlas_files(&self) -> &Vec<String> {
|
pub fn atlas_files(&self) -> &Vec<String> {
|
||||||
return &self.sprite_atlas.atlas_list;
|
return &self.sprite_atlas.atlas_list;
|
||||||
|
@ -316,63 +342,4 @@ impl Content {
|
||||||
pub fn get_image(&self, idx: NonZeroU32) -> &SpriteAtlasImage {
|
pub fn get_image(&self, idx: NonZeroU32) -> &SpriteAtlasImage {
|
||||||
&self.sprite_atlas.get_by_idx(idx)
|
&self.sprite_atlas.get_by_idx(idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an outfit from a handle
|
|
||||||
pub fn get_outfit(&self, h: OutfitHandle) -> &Outfit {
|
|
||||||
return &self.outfits[h.index];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a gun from a handle
|
|
||||||
pub fn get_gun(&self, h: GunHandle) -> &Gun {
|
|
||||||
return &self.guns[h.index];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a ship from a handle
|
|
||||||
pub fn get_ship(&self, h: ShipHandle) -> &Ship {
|
|
||||||
return &self.ships[h.index];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a system from a handle
|
|
||||||
pub fn get_system(&self, h: SystemHandle) -> &System {
|
|
||||||
return &self.systems[h.index];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a system object from a handle
|
|
||||||
pub fn get_system_object(&self, h: SystemObjectHandle) -> &SystemObject {
|
|
||||||
return &self.get_system(h.system_handle).objects[h.body_index];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a faction from a handle
|
|
||||||
pub fn get_faction(&self, h: FactionHandle) -> &Faction {
|
|
||||||
return &self.factions[h.index];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get an effect from a handle
|
|
||||||
pub fn get_effect(&self, h: EffectHandle) -> &Effect {
|
|
||||||
return &self.effects[h.index];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get content configuration
|
|
||||||
pub fn get_config(&self) -> &Config {
|
|
||||||
return &self.config;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
TODO: don't pass content around?
|
|
||||||
static mut CONTENT: OnceCell<Content> = OnceCell::new();
|
|
||||||
|
|
||||||
/// Initialize content::CONTENT with the given paths
|
|
||||||
pub fn init(content_dir: PathBuf, asset_dir: PathBuf, atlas_index: PathBuf) -> Result<()> {
|
|
||||||
let content = Content::load_dir(content_dir, asset_dir, atlas_index)?;
|
|
||||||
unsafe {
|
|
||||||
match CONTENT.set(content) {
|
|
||||||
Ok(()) => {}
|
|
||||||
Err(_) => {
|
|
||||||
bail!("cannot initialize content, already set.")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use crate::{handle::SpriteHandle, Content, ContentBuildContext, EffectHandle};
|
use crate::{Content, ContentBuildContext, ContentIndex, Sprite};
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use galactica_util::to_radians;
|
use galactica_util::to_radians;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::{Content, ContentBuildContext, EffectHandle, StartEdge};
|
use crate::{Content, ContentBuildContext, ContentIndex, StartEdge};
|
||||||
// Raw serde syntax structs.
|
// Raw serde syntax structs.
|
||||||
// These are never seen by code outside this crate.
|
// These are never seen by code outside this crate.
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Effect {
|
pub struct Effect {
|
||||||
pub sprite: String,
|
pub sprite: ContentIndex,
|
||||||
pub size: f32,
|
pub size: f32,
|
||||||
pub size_rng: Option<f32>,
|
pub size_rng: Option<f32>,
|
||||||
pub lifetime: TextOrFloat,
|
pub lifetime: TextOrFloat,
|
||||||
|
@ -59,10 +61,11 @@ pub(crate) mod syntax {
|
||||||
self,
|
self,
|
||||||
_build_context: &mut ContentBuildContext,
|
_build_context: &mut ContentBuildContext,
|
||||||
content: &mut Content,
|
content: &mut Content,
|
||||||
) -> Result<EffectHandle> {
|
name: &str,
|
||||||
let sprite = match content.sprite_index.get(&self.sprite) {
|
) -> Result<Arc<super::Effect>> {
|
||||||
|
let sprite = match content.sprites.get(&self.sprite) {
|
||||||
None => bail!("sprite `{}` doesn't exist", self.sprite),
|
None => bail!("sprite `{}` doesn't exist", self.sprite),
|
||||||
Some(t) => *t,
|
Some(t) => t.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let lifetime = match self.lifetime {
|
let lifetime = match self.lifetime {
|
||||||
|
@ -70,12 +73,11 @@ pub(crate) mod syntax {
|
||||||
TextOrFloat::Text(s) => {
|
TextOrFloat::Text(s) => {
|
||||||
if s == "inherit" {
|
if s == "inherit" {
|
||||||
// Match lifetime of first section of sprite
|
// Match lifetime of first section of sprite
|
||||||
let sprite = content.get_sprite(sprite);
|
match &sprite.start_at {
|
||||||
let sec = match sprite.start_at {
|
StartEdge::Top { section } | StartEdge::Bot { section } => {
|
||||||
StartEdge::Top { section } => sprite.get_section(section),
|
section.frame_duration * section.frames.len() as f32
|
||||||
StartEdge::Bot { section } => sprite.get_section(section),
|
}
|
||||||
};
|
}
|
||||||
sec.frame_duration * sec.frames.len() as f32
|
|
||||||
} else {
|
} else {
|
||||||
bail!("bad effect lifetime, must be float or \"inherit\"",)
|
bail!("bad effect lifetime, must be float or \"inherit\"",)
|
||||||
}
|
}
|
||||||
|
@ -107,11 +109,8 @@ pub(crate) mod syntax {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let handle = EffectHandle {
|
let e = Arc::new(super::Effect {
|
||||||
index: content.effects.len(),
|
name: name.to_string(),
|
||||||
};
|
|
||||||
content.effects.push(super::Effect {
|
|
||||||
handle,
|
|
||||||
sprite,
|
sprite,
|
||||||
velocity,
|
velocity,
|
||||||
size: self.size,
|
size: self.size,
|
||||||
|
@ -126,7 +125,11 @@ pub(crate) mod syntax {
|
||||||
fade_rng: self.fade_rng.unwrap_or(0.0),
|
fade_rng: self.fade_rng.unwrap_or(0.0),
|
||||||
});
|
});
|
||||||
|
|
||||||
return Ok(handle);
|
content
|
||||||
|
.effects
|
||||||
|
.insert(ContentIndex::new(&e.name), e.clone());
|
||||||
|
|
||||||
|
return Ok(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,22 +138,23 @@ pub(crate) mod syntax {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum EffectReference {
|
pub enum EffectReference {
|
||||||
Label(String),
|
Label(ContentIndex),
|
||||||
Effect(Effect),
|
Effect(Effect),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EffectReference {
|
impl EffectReference {
|
||||||
pub fn to_handle(
|
pub fn resolve(
|
||||||
self,
|
self,
|
||||||
build_context: &mut ContentBuildContext,
|
build_context: &mut ContentBuildContext,
|
||||||
content: &mut Content,
|
content: &mut Content,
|
||||||
) -> Result<EffectHandle> {
|
name: &str,
|
||||||
|
) -> Result<Arc<super::Effect>> {
|
||||||
// We do not insert anything into build_context here,
|
// We do not insert anything into build_context here,
|
||||||
// since inline effects cannot be referenced by name.
|
// since inline effects cannot be referenced by name.
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
Self::Effect(e) => e.add_to(build_context, content)?,
|
Self::Effect(e) => e.add_to(build_context, content, name)?,
|
||||||
Self::Label(l) => match build_context.effect_index.get(&l) {
|
Self::Label(l) => match build_context.effect_index.get(&l) {
|
||||||
Some(h) => *h,
|
Some(h) => h.clone(),
|
||||||
None => bail!("no effect named `{}`", l),
|
None => bail!("no effect named `{}`", l),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -161,11 +165,11 @@ pub(crate) mod syntax {
|
||||||
/// The effect a projectile will spawn when it hits something
|
/// The effect a projectile will spawn when it hits something
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Effect {
|
pub struct Effect {
|
||||||
/// This effect's handle
|
|
||||||
pub handle: EffectHandle,
|
|
||||||
|
|
||||||
/// The sprite to use for this effect.
|
/// The sprite to use for this effect.
|
||||||
pub sprite: SpriteHandle,
|
pub sprite: Arc<Sprite>,
|
||||||
|
|
||||||
|
/// This effect's name
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
/// The height of this effect, in game units.
|
/// The height of this effect, in game units.
|
||||||
pub size: f32,
|
pub size: f32,
|
||||||
|
@ -240,9 +244,11 @@ impl crate::Build for Effect {
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
for (effect_name, effect) in effects {
|
for (effect_name, effect) in effects {
|
||||||
let h = effect
|
let h = effect
|
||||||
.add_to(build_context, content)
|
.add_to(build_context, content, &effect_name)
|
||||||
.with_context(|| format!("while evaluating effect `{}`", effect_name))?;
|
.with_context(|| format!("while evaluating effect `{}`", effect_name))?;
|
||||||
build_context.effect_index.insert(effect_name, h);
|
build_context
|
||||||
|
.effect_index
|
||||||
|
.insert(ContentIndex::new(&effect_name), h);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use crate::{handle::FactionHandle, Content, ContentBuildContext};
|
use crate::{Content, ContentBuildContext, ContentIndex};
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::ContentIndex;
|
||||||
// Raw serde syntax structs.
|
// Raw serde syntax structs.
|
||||||
// These are never seen by code outside this crate.
|
// These are never seen by code outside this crate.
|
||||||
|
|
||||||
|
@ -15,7 +17,7 @@ pub(crate) mod syntax {
|
||||||
pub struct Faction {
|
pub struct Faction {
|
||||||
pub display_name: String,
|
pub display_name: String,
|
||||||
pub color: [f32; 3],
|
pub color: [f32; 3],
|
||||||
pub relationship: HashMap<String, super::Relationship>,
|
pub relationship: HashMap<ContentIndex, super::Relationship>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,18 +46,18 @@ pub enum Relationship {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Faction {
|
pub struct Faction {
|
||||||
/// The name of this faction
|
/// The name of this faction
|
||||||
pub name: String,
|
pub index: ContentIndex,
|
||||||
|
|
||||||
|
/// The pretty name of this faction
|
||||||
|
pub display_name: String,
|
||||||
|
|
||||||
/// This faction's color.
|
/// This faction's color.
|
||||||
/// Format is RGB, with each color between 0 and 1.
|
/// Format is RGB, with each color between 0 and 1.
|
||||||
pub color: [f32; 3],
|
pub color: [f32; 3],
|
||||||
|
|
||||||
/// This faction's handle
|
|
||||||
pub handle: FactionHandle,
|
|
||||||
|
|
||||||
/// Relationships between this faction and other factions
|
/// Relationships between this faction and other factions
|
||||||
/// This is guaranteed to contain an entry for ALL factions.
|
/// This is guaranteed to contain an entry for ALL factions.
|
||||||
pub relationships: HashMap<FactionHandle, Relationship>,
|
pub relationships: HashMap<ContentIndex, Relationship>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::Build for Faction {
|
impl crate::Build for Faction {
|
||||||
|
@ -66,34 +68,21 @@ impl crate::Build for Faction {
|
||||||
_build_context: &mut ContentBuildContext,
|
_build_context: &mut ContentBuildContext,
|
||||||
content: &mut Content,
|
content: &mut Content,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Keeps track of position in faction array.
|
for (faction_name, faction) in &factions {
|
||||||
// This lets us build FactionHandles before finishing all factions.
|
|
||||||
let faction_names: Vec<String> = factions.keys().map(|x| x.to_owned()).collect();
|
|
||||||
|
|
||||||
// Indexing will break if this is false.
|
|
||||||
assert!(content.factions.len() == 0);
|
|
||||||
|
|
||||||
for f_idx in 0..faction_names.len() {
|
|
||||||
let faction_name = &faction_names[f_idx];
|
|
||||||
let faction = &factions[faction_name];
|
|
||||||
|
|
||||||
// Handle for this faction
|
|
||||||
let h = FactionHandle { index: f_idx };
|
|
||||||
|
|
||||||
// Compute relationships
|
// Compute relationships
|
||||||
let mut relationships = HashMap::new();
|
let mut relationships = HashMap::new();
|
||||||
for i in 0..faction_names.len() {
|
for (other_name, other_faction) in &factions {
|
||||||
let f_other = &faction_names[i];
|
if let Some(r) = faction.relationship.get(&ContentIndex::new(other_name)) {
|
||||||
let h_other = FactionHandle { index: i };
|
relationships.insert(ContentIndex::new(other_name), *r);
|
||||||
if let Some(r) = faction.relationship.get(f_other) {
|
|
||||||
relationships.insert(h_other, *r);
|
|
||||||
} else {
|
} else {
|
||||||
// Default relationship, if not specified
|
// Default relationship, if not specified
|
||||||
|
|
||||||
// Look at reverse direction...
|
// Look at reverse direction...
|
||||||
let other = factions[f_other].relationship.get(faction_name);
|
let other = other_faction
|
||||||
|
.relationship
|
||||||
|
.get(&ContentIndex::new(&faction_name));
|
||||||
relationships.insert(
|
relationships.insert(
|
||||||
h_other,
|
ContentIndex::new(other_name),
|
||||||
// ... and pick a relationship based on that.
|
// ... and pick a relationship based on that.
|
||||||
match other {
|
match other {
|
||||||
Some(Relationship::Hostile) => Relationship::Hostile {},
|
Some(Relationship::Hostile) => Relationship::Hostile {},
|
||||||
|
@ -116,12 +105,15 @@ impl crate::Build for Faction {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
content.factions.push(Self {
|
content.factions.insert(
|
||||||
name: faction_name.to_owned(),
|
ContentIndex::new(faction_name),
|
||||||
handle: h,
|
Arc::new(Self {
|
||||||
|
index: ContentIndex::new(faction_name),
|
||||||
|
display_name: faction.display_name.to_owned(),
|
||||||
relationships,
|
relationships,
|
||||||
color: faction.color,
|
color: faction.color,
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
handle::SpriteHandle, resolve_edge_as_edge, Content, ContentBuildContext, EffectHandle,
|
resolve_edge_as_edge, Content, ContentBuildContext, ContentIndex, Effect, OutfitSpace,
|
||||||
OutfitHandle, OutfitSpace, SectionEdge,
|
SectionEdge, Sprite,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
use crate::{effect, part::outfitspace, sprite::syntax::SectionEdge, ContentBuildContext};
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
effect, part::outfitspace, sprite::syntax::SectionEdge, ContentBuildContext, ContentIndex,
|
||||||
|
};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use galactica_util::to_radians;
|
use galactica_util::to_radians;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -17,7 +21,8 @@ pub(crate) mod syntax {
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Outfit {
|
pub struct Outfit {
|
||||||
pub thumbnail: String,
|
pub thumbnail: ContentIndex,
|
||||||
|
pub name: 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,
|
||||||
|
@ -41,7 +46,7 @@ pub(crate) mod syntax {
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct EngineFlare {
|
pub struct EngineFlare {
|
||||||
pub sprite: String,
|
pub sprite: ContentIndex,
|
||||||
pub on_start: Option<SectionEdge>,
|
pub on_start: Option<SectionEdge>,
|
||||||
pub on_stop: Option<SectionEdge>,
|
pub on_stop: Option<SectionEdge>,
|
||||||
}
|
}
|
||||||
|
@ -64,30 +69,33 @@ pub(crate) mod syntax {
|
||||||
build_context: &mut ContentBuildContext,
|
build_context: &mut ContentBuildContext,
|
||||||
content: &mut crate::Content,
|
content: &mut crate::Content,
|
||||||
) -> Result<super::Gun> {
|
) -> Result<super::Gun> {
|
||||||
let projectile_sprite_handle = match content.sprite_index.get(&self.projectile.sprite) {
|
let projectile_sprite = match content
|
||||||
|
.sprites
|
||||||
|
.get(&ContentIndex::new(&self.projectile.sprite))
|
||||||
|
{
|
||||||
None => bail!(
|
None => bail!(
|
||||||
"projectile sprite `{}` doesn't exist",
|
"projectile sprite `{}` doesn't exist",
|
||||||
self.projectile.sprite,
|
self.projectile.sprite,
|
||||||
),
|
),
|
||||||
Some(t) => *t,
|
Some(t) => t.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let impact_effect = match self.projectile.impact_effect {
|
let impact_effect = match self.projectile.impact_effect {
|
||||||
Some(e) => Some(e.to_handle(build_context, content)?),
|
Some(e) => Some(e.resolve(build_context, content, "")?),
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let expire_effect = match self.projectile.expire_effect {
|
let expire_effect = match self.projectile.expire_effect {
|
||||||
Some(e) => Some(e.to_handle(build_context, content)?),
|
Some(e) => Some(e.resolve(build_context, content, "")?),
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
return Ok(super::Gun {
|
return Ok(super::Gun {
|
||||||
rate: self.rate,
|
rate: self.rate,
|
||||||
rate_rng: self.rate_rng.unwrap_or(0.0),
|
rate_rng: self.rate_rng.unwrap_or(0.0),
|
||||||
projectile: super::Projectile {
|
projectile: Arc::new(super::Projectile {
|
||||||
force: self.projectile.force,
|
force: self.projectile.force,
|
||||||
sprite: projectile_sprite_handle,
|
sprite: projectile_sprite,
|
||||||
size: self.projectile.size,
|
size: self.projectile.size,
|
||||||
size_rng: self.projectile.size_rng,
|
size_rng: self.projectile.size_rng,
|
||||||
speed: self.projectile.speed,
|
speed: self.projectile.speed,
|
||||||
|
@ -102,7 +110,7 @@ pub(crate) mod syntax {
|
||||||
impact_effect,
|
impact_effect,
|
||||||
expire_effect,
|
expire_effect,
|
||||||
collider: self.projectile.collider,
|
collider: self.projectile.collider,
|
||||||
},
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,16 +137,16 @@ pub(crate) mod syntax {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Outfit {
|
pub struct Outfit {
|
||||||
/// This outfit's thumbnail
|
/// This outfit's thumbnail
|
||||||
pub thumbnail: SpriteHandle,
|
pub thumbnail: Arc<Sprite>,
|
||||||
|
|
||||||
/// How much space this outfit requires
|
/// How much space this outfit requires
|
||||||
pub space: OutfitSpace,
|
pub space: OutfitSpace,
|
||||||
|
|
||||||
/// This outfit's handle
|
|
||||||
pub handle: OutfitHandle,
|
|
||||||
|
|
||||||
/// The name of this outfit
|
/// The name of this outfit
|
||||||
pub name: String,
|
pub display_name: String,
|
||||||
|
|
||||||
|
/// Thie outfit's index
|
||||||
|
pub index: ContentIndex,
|
||||||
|
|
||||||
/// How much engine thrust this outfit produces
|
/// How much engine thrust this outfit produces
|
||||||
pub engine_thrust: f32,
|
pub engine_thrust: f32,
|
||||||
|
@ -149,7 +157,7 @@ pub struct Outfit {
|
||||||
/// 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.
|
||||||
pub engine_flare_sprite: Option<SpriteHandle>,
|
pub engine_flare_sprite: Option<Arc<Sprite>>,
|
||||||
|
|
||||||
/// Jump to this edge when engines turn on
|
/// Jump to this edge when engines turn on
|
||||||
pub engine_flare_on_start: Option<SectionEdge>,
|
pub engine_flare_on_start: Option<SectionEdge>,
|
||||||
|
@ -191,7 +199,7 @@ pub struct BallCollider {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Gun {
|
pub struct Gun {
|
||||||
/// The projectile this gun produces
|
/// The projectile this gun produces
|
||||||
pub projectile: Projectile,
|
pub projectile: Arc<Projectile>,
|
||||||
|
|
||||||
/// Average delay between projectiles, in seconds.
|
/// Average delay between projectiles, in seconds.
|
||||||
pub rate: f32,
|
pub rate: f32,
|
||||||
|
@ -205,7 +213,7 @@ pub struct Gun {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Projectile {
|
pub struct Projectile {
|
||||||
/// The projectile sprite
|
/// The projectile sprite
|
||||||
pub sprite: SpriteHandle,
|
pub sprite: Arc<Sprite>,
|
||||||
|
|
||||||
/// The average size of this projectile
|
/// The average size of this projectile
|
||||||
/// (height in game units)
|
/// (height in game units)
|
||||||
|
@ -234,10 +242,10 @@ pub struct Projectile {
|
||||||
pub angle_rng: f32,
|
pub angle_rng: f32,
|
||||||
|
|
||||||
/// The effect this projectile will spawn when it hits something
|
/// The effect this projectile will spawn when it hits something
|
||||||
pub impact_effect: Option<EffectHandle>,
|
pub impact_effect: Option<Arc<Effect>>,
|
||||||
|
|
||||||
/// The effect this projectile will spawn when it expires
|
/// The effect this projectile will spawn when it expires
|
||||||
pub expire_effect: Option<EffectHandle>,
|
pub expire_effect: Option<Arc<Effect>>,
|
||||||
|
|
||||||
/// Collider parameters for this projectile
|
/// Collider parameters for this projectile
|
||||||
pub collider: ProjectileCollider,
|
pub collider: ProjectileCollider,
|
||||||
|
@ -252,10 +260,6 @@ impl crate::Build for Outfit {
|
||||||
content: &mut Content,
|
content: &mut Content,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
for (outfit_name, outfit) in outfits {
|
for (outfit_name, outfit) in outfits {
|
||||||
let handle = OutfitHandle {
|
|
||||||
index: content.outfits.len(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let gun = match outfit.gun {
|
let gun = match outfit.gun {
|
||||||
None => None,
|
None => None,
|
||||||
Some(g) => Some(
|
Some(g) => Some(
|
||||||
|
@ -264,7 +268,7 @@ impl crate::Build for Outfit {
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
let thumb_handle = match content.sprite_index.get(&outfit.thumbnail) {
|
let thumbnail = match content.sprites.get(&outfit.thumbnail) {
|
||||||
None => {
|
None => {
|
||||||
return Err(anyhow!(
|
return Err(anyhow!(
|
||||||
"thumbnail sprite `{}` doesn't exist",
|
"thumbnail sprite `{}` doesn't exist",
|
||||||
|
@ -272,14 +276,14 @@ impl crate::Build for Outfit {
|
||||||
))
|
))
|
||||||
.with_context(|| format!("in outfit `{}`", outfit_name));
|
.with_context(|| format!("in outfit `{}`", outfit_name));
|
||||||
}
|
}
|
||||||
Some(t) => *t,
|
Some(t) => t.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut o = Self {
|
let mut o = Self {
|
||||||
thumbnail: thumb_handle,
|
index: ContentIndex::new(&outfit_name),
|
||||||
|
display_name: outfit.name,
|
||||||
|
thumbnail,
|
||||||
gun,
|
gun,
|
||||||
handle,
|
|
||||||
name: outfit_name.clone(),
|
|
||||||
engine_thrust: 0.0,
|
engine_thrust: 0.0,
|
||||||
steer_power: 0.0,
|
steer_power: 0.0,
|
||||||
engine_flare_sprite: None,
|
engine_flare_sprite: None,
|
||||||
|
@ -293,7 +297,7 @@ impl crate::Build for Outfit {
|
||||||
|
|
||||||
// Engine stats
|
// Engine stats
|
||||||
if let Some(engine) = outfit.engine {
|
if let Some(engine) = outfit.engine {
|
||||||
let sprite_handle = match content.sprite_index.get(&engine.flare.sprite) {
|
let sprite = match content.sprites.get(&engine.flare.sprite) {
|
||||||
None => {
|
None => {
|
||||||
return Err(anyhow!(
|
return Err(anyhow!(
|
||||||
"flare sprite `{}` doesn't exist",
|
"flare sprite `{}` doesn't exist",
|
||||||
|
@ -301,11 +305,10 @@ impl crate::Build for Outfit {
|
||||||
))
|
))
|
||||||
.with_context(|| format!("in outfit `{}`", outfit_name));
|
.with_context(|| format!("in outfit `{}`", outfit_name));
|
||||||
}
|
}
|
||||||
Some(t) => *t,
|
Some(t) => t.clone(),
|
||||||
};
|
};
|
||||||
o.engine_thrust = engine.thrust;
|
o.engine_thrust = engine.thrust;
|
||||||
o.engine_flare_sprite = Some(sprite_handle);
|
o.engine_flare_sprite = Some(sprite.clone());
|
||||||
let sprite = content.get_sprite(sprite_handle);
|
|
||||||
|
|
||||||
// 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
|
||||||
// This leads from the idle animation to the transition animation
|
// This leads from the idle animation to the transition animation
|
||||||
|
@ -315,11 +318,9 @@ impl crate::Build for Outfit {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let x = x.unwrap();
|
let x = x.unwrap();
|
||||||
let mut e = resolve_edge_as_edge(&x.val, 0.0, |x| {
|
let mut e = resolve_edge_as_edge(&sprite.sections, &x.val, 0.0)
|
||||||
sprite.get_section_handle_by_name(x)
|
|
||||||
})
|
|
||||||
.with_context(|| format!("in outfit `{}`", outfit_name))?;
|
.with_context(|| format!("in outfit `{}`", outfit_name))?;
|
||||||
match e {
|
match &mut e {
|
||||||
// Inherit duration from transition sequence
|
// Inherit duration from transition sequence
|
||||||
SectionEdge::Top {
|
SectionEdge::Top {
|
||||||
section,
|
section,
|
||||||
|
@ -329,7 +330,7 @@ impl crate::Build for Outfit {
|
||||||
section,
|
section,
|
||||||
ref mut duration,
|
ref mut duration,
|
||||||
} => {
|
} => {
|
||||||
*duration = sprite.get_section(section).frame_duration;
|
*duration = section.frame_duration;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(anyhow!(
|
return Err(anyhow!(
|
||||||
|
@ -351,11 +352,9 @@ impl crate::Build for Outfit {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let x = x.unwrap();
|
let x = x.unwrap();
|
||||||
let mut e = resolve_edge_as_edge(&x.val, 0.0, |x| {
|
let mut e = resolve_edge_as_edge(&sprite.sections, &x.val, 0.0)
|
||||||
sprite.get_section_handle_by_name(x)
|
|
||||||
})
|
|
||||||
.with_context(|| format!("in outfit `{}`", outfit_name))?;
|
.with_context(|| format!("in outfit `{}`", outfit_name))?;
|
||||||
match e {
|
match &mut e {
|
||||||
// Inherit duration from transition sequence
|
// Inherit duration from transition sequence
|
||||||
SectionEdge::Top {
|
SectionEdge::Top {
|
||||||
section,
|
section,
|
||||||
|
@ -365,7 +364,7 @@ impl crate::Build for Outfit {
|
||||||
section,
|
section,
|
||||||
ref mut duration,
|
ref mut duration,
|
||||||
} => {
|
} => {
|
||||||
*duration = sprite.get_section(section).frame_duration;
|
*duration = section.frame_duration;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(anyhow!(
|
return Err(anyhow!(
|
||||||
|
@ -392,7 +391,7 @@ impl crate::Build for Outfit {
|
||||||
o.shield_strength = shield.strength.unwrap_or(0.0);
|
o.shield_strength = shield.strength.unwrap_or(0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
content.outfits.push(o);
|
content.outfits.insert(o.index.clone(), Arc::new(o));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
|
@ -2,12 +2,15 @@ use anyhow::{bail, Context, Result};
|
||||||
use galactica_util::to_radians;
|
use galactica_util::to_radians;
|
||||||
use nalgebra::{Point2, Rotation2, Vector2};
|
use nalgebra::{Point2, Rotation2, Vector2};
|
||||||
use rapier2d::geometry::{Collider, ColliderBuilder};
|
use rapier2d::geometry::{Collider, ColliderBuilder};
|
||||||
use std::{collections::HashMap, fmt::Debug, hash::Hash};
|
use std::{collections::HashMap, fmt::Debug, hash::Hash, sync::Arc};
|
||||||
|
|
||||||
use crate::{handle::SpriteHandle, Content, ContentBuildContext, EffectHandle, OutfitSpace};
|
use crate::{Content, ContentBuildContext, ContentIndex, Effect, OutfitSpace, Sprite};
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
use crate::part::{effect::syntax::EffectReference, outfitspace};
|
use crate::{
|
||||||
|
part::{effect::syntax::EffectReference, outfitspace},
|
||||||
|
ContentIndex,
|
||||||
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
// Raw serde syntax structs.
|
// Raw serde syntax structs.
|
||||||
|
@ -15,8 +18,9 @@ pub(crate) mod syntax {
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Ship {
|
pub struct Ship {
|
||||||
pub sprite: String,
|
pub name: String,
|
||||||
pub thumbnail: String,
|
pub sprite: ContentIndex,
|
||||||
|
pub thumbnail: ContentIndex,
|
||||||
pub size: f32,
|
pub size: f32,
|
||||||
pub engines: Vec<Engine>,
|
pub engines: Vec<Engine>,
|
||||||
pub guns: Vec<Gun>,
|
pub guns: Vec<Gun>,
|
||||||
|
@ -91,14 +95,17 @@ pub(crate) mod syntax {
|
||||||
/// Represents a ship chassis.
|
/// Represents a ship chassis.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Ship {
|
pub struct Ship {
|
||||||
/// This ship's name
|
/// This ship's display name
|
||||||
pub name: String,
|
pub display_name: String,
|
||||||
|
|
||||||
|
/// This object's index
|
||||||
|
pub index: ContentIndex,
|
||||||
|
|
||||||
/// This ship's sprite
|
/// This ship's sprite
|
||||||
pub sprite: SpriteHandle,
|
pub sprite: Arc<Sprite>,
|
||||||
|
|
||||||
/// This ship's thumbnail
|
/// This ship's thumbnail
|
||||||
pub thumbnail: SpriteHandle,
|
pub thumbnail: Arc<Sprite>,
|
||||||
|
|
||||||
/// The size of this ship.
|
/// The size of this ship.
|
||||||
/// Measured as unrotated height,
|
/// Measured as unrotated height,
|
||||||
|
@ -122,9 +129,6 @@ pub struct Ship {
|
||||||
/// Collision shape for this ship
|
/// Collision shape for this ship
|
||||||
pub collider: CollisionDebugWrapper,
|
pub collider: CollisionDebugWrapper,
|
||||||
|
|
||||||
/// Remove later
|
|
||||||
pub aspect: f32,
|
|
||||||
|
|
||||||
/// Reduction in angular velocity over time
|
/// Reduction in angular velocity over time
|
||||||
pub angular_drag: f32,
|
pub angular_drag: f32,
|
||||||
|
|
||||||
|
@ -217,7 +221,7 @@ pub struct ShipDamage {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DamageEffectSpawner {
|
pub struct DamageEffectSpawner {
|
||||||
/// The effect to create
|
/// The effect to create
|
||||||
pub effect: EffectHandle,
|
pub effect: Arc<Effect>,
|
||||||
|
|
||||||
/// How often to create this effect
|
/// How often to create this effect
|
||||||
pub frequency: f32,
|
pub frequency: f32,
|
||||||
|
@ -231,7 +235,7 @@ pub struct DamageEffectSpawner {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CollapseEffectSpawner {
|
pub struct CollapseEffectSpawner {
|
||||||
/// The effect to create
|
/// The effect to create
|
||||||
pub effect: EffectHandle,
|
pub effect: Arc<Effect>,
|
||||||
|
|
||||||
/// How many effects to create
|
/// How many effects to create
|
||||||
pub count: f32,
|
pub count: f32,
|
||||||
|
@ -267,26 +271,25 @@ impl crate::Build for Ship {
|
||||||
ct: &mut Content,
|
ct: &mut Content,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
for (ship_name, ship) in ship {
|
for (ship_name, ship) in ship {
|
||||||
let sprite = match ct.sprite_index.get(&ship.sprite) {
|
let sprite = match ct.sprites.get(&ship.sprite) {
|
||||||
None => bail!(
|
None => bail!(
|
||||||
"In ship `{}`: sprite `{}` doesn't exist",
|
"In ship `{}`: sprite `{}` doesn't exist",
|
||||||
ship_name,
|
ship_name,
|
||||||
ship.sprite
|
ship.sprite
|
||||||
),
|
),
|
||||||
Some(t) => *t,
|
Some(t) => t.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let thumbnail = match ct.sprite_index.get(&ship.thumbnail) {
|
let thumbnail = match ct.sprites.get(&ship.thumbnail) {
|
||||||
None => bail!(
|
None => bail!(
|
||||||
"In ship `{}`: thumbnail sprite `{}` doesn't exist",
|
"In ship `{}`: thumbnail sprite `{}` doesn't exist",
|
||||||
ship_name,
|
ship_name,
|
||||||
ship.thumbnail
|
ship.thumbnail
|
||||||
),
|
),
|
||||||
Some(t) => *t,
|
Some(t) => t.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let size = ship.size;
|
let size = ship.size;
|
||||||
let aspect = ct.get_sprite(sprite).aspect;
|
|
||||||
|
|
||||||
let collapse = {
|
let collapse = {
|
||||||
if let Some(c) = ship.collapse {
|
if let Some(c) = ship.collapse {
|
||||||
|
@ -295,11 +298,11 @@ impl crate::Build for Ship {
|
||||||
effects.push(CollapseEffectSpawner {
|
effects.push(CollapseEffectSpawner {
|
||||||
effect: e
|
effect: e
|
||||||
.effect
|
.effect
|
||||||
.to_handle(build_context, ct)
|
.resolve(build_context, ct, "")
|
||||||
.with_context(|| format!("while loading ship `{}`", ship_name))?,
|
.with_context(|| format!("while loading ship `{}`", ship_name))?,
|
||||||
count: e.count,
|
count: e.count,
|
||||||
pos: e.pos.map(|p| {
|
pos: e.pos.map(|p| {
|
||||||
Point2::new(p[0] * (size / 2.0) * aspect, p[1] * size / 2.0)
|
Point2::new(p[0] * (size / 2.0) * sprite.aspect, p[1] * size / 2.0)
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -313,14 +316,14 @@ impl crate::Build for Ship {
|
||||||
effects.push(CollapseEffectSpawner {
|
effects.push(CollapseEffectSpawner {
|
||||||
effect: g
|
effect: g
|
||||||
.effect
|
.effect
|
||||||
.to_handle(build_context, ct)
|
.resolve(build_context, ct, "")
|
||||||
.with_context(|| {
|
.with_context(|| {
|
||||||
format!("while loading ship `{}`", ship_name)
|
format!("while loading ship `{}`", ship_name)
|
||||||
})?,
|
})?,
|
||||||
count: g.count,
|
count: g.count,
|
||||||
pos: g.pos.map(|p| {
|
pos: g.pos.map(|p| {
|
||||||
Point2::new(
|
Point2::new(
|
||||||
p[0] * (size / 2.0) * aspect,
|
p[0] * (size / 2.0) * sprite.aspect,
|
||||||
p[1] * size / 2.0,
|
p[1] * size / 2.0,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
@ -357,11 +360,11 @@ impl crate::Build for Ship {
|
||||||
effects.push(DamageEffectSpawner {
|
effects.push(DamageEffectSpawner {
|
||||||
effect: e
|
effect: e
|
||||||
.effect
|
.effect
|
||||||
.to_handle(build_context, ct)
|
.resolve(build_context, ct, "")
|
||||||
.with_context(|| format!("while loading ship `{}`", ship_name))?,
|
.with_context(|| format!("while loading ship `{}`", ship_name))?,
|
||||||
frequency: e.frequency,
|
frequency: e.frequency,
|
||||||
pos: e.pos.map(|p| {
|
pos: e.pos.map(|p| {
|
||||||
Point2::new(p[0] * (size / 2.0) * aspect, p[1] * size / 2.0)
|
Point2::new(p[0] * (size / 2.0) * sprite.aspect, p[1] * size / 2.0)
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -388,7 +391,7 @@ impl crate::Build for Ship {
|
||||||
// Angle adjustment, since sprites point north
|
// Angle adjustment, since sprites point north
|
||||||
// and 0 degrees is east in the game
|
// and 0 degrees is east in the game
|
||||||
pos: Rotation2::new(to_radians(-90.0))
|
pos: Rotation2::new(to_radians(-90.0))
|
||||||
* Vector2::new(g.x * size * aspect / 2.0, g.y * size / 2.0),
|
* Vector2::new(g.x * size * sprite.aspect / 2.0, g.y * size / 2.0),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -418,7 +421,7 @@ impl crate::Build for Ship {
|
||||||
// If we don't, rapier2 will compute local points pre-rotation,
|
// If we don't, rapier2 will compute local points pre-rotation,
|
||||||
// which will break effect placement on top of ships (i.e, collapse effects)
|
// which will break effect placement on top of ships (i.e, collapse effects)
|
||||||
Rotation2::new(to_radians(-90.0))
|
Rotation2::new(to_radians(-90.0))
|
||||||
* Point2::new(x[0] * (size / 2.0) * aspect, x[1] * size / 2.0)
|
* Point2::new(x[0] * (size / 2.0) * sprite.aspect, x[1] * size / 2.0)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -427,13 +430,15 @@ impl crate::Build for Ship {
|
||||||
.build()
|
.build()
|
||||||
};
|
};
|
||||||
|
|
||||||
ct.ships.push(Self {
|
ct.ships.insert(
|
||||||
sprite,
|
ContentIndex::new(&ship_name),
|
||||||
|
Arc::new(Self {
|
||||||
|
sprite: sprite.clone(),
|
||||||
thumbnail,
|
thumbnail,
|
||||||
aspect,
|
|
||||||
collapse,
|
collapse,
|
||||||
damage,
|
damage,
|
||||||
name: ship_name,
|
index: ContentIndex::new(&ship_name),
|
||||||
|
display_name: ship.name,
|
||||||
mass: ship.mass,
|
mass: ship.mass,
|
||||||
space: OutfitSpace::from(ship.space),
|
space: OutfitSpace::from(ship.space),
|
||||||
angular_drag: ship.angular_drag,
|
angular_drag: ship.angular_drag,
|
||||||
|
@ -445,7 +450,7 @@ impl crate::Build for Ship {
|
||||||
.engines
|
.engines
|
||||||
.iter()
|
.iter()
|
||||||
.map(|e| EnginePoint {
|
.map(|e| EnginePoint {
|
||||||
pos: Vector2::new(e.x * size * aspect / 2.0, e.y * size / 2.0),
|
pos: Vector2::new(e.x * size * sprite.aspect / 2.0, e.y * size / 2.0),
|
||||||
size: e.size,
|
size: e.size,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
@ -453,7 +458,8 @@ impl crate::Build for Ship {
|
||||||
guns,
|
guns,
|
||||||
|
|
||||||
collider: CollisionDebugWrapper(collider),
|
collider: CollisionDebugWrapper(collider),
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use crate::{handle::SpriteHandle, Content, ContentBuildContext};
|
use crate::{Content, ContentBuildContext, ContentIndex};
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
use crate::{AnimSectionHandle, Content};
|
use crate::{Content, ContentIndex};
|
||||||
use anyhow::{bail, Ok, Result};
|
use anyhow::{bail, Ok, Result};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{collections::HashMap, path::PathBuf};
|
use std::{collections::HashMap, path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
// Raw serde syntax structs.
|
// Raw serde syntax structs.
|
||||||
// These are never seen by code outside this crate.
|
// These are never seen by code outside this crate.
|
||||||
|
@ -49,7 +49,7 @@ pub(crate) mod syntax {
|
||||||
/// The proper, full sprite definition
|
/// The proper, full sprite definition
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct CompleteSprite {
|
pub struct CompleteSprite {
|
||||||
pub section: HashMap<String, SpriteSection>,
|
pub section: HashMap<ContentIndex, SpriteSection>,
|
||||||
pub start_at: SectionEdge,
|
pub start_at: SectionEdge,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,14 +63,28 @@ pub(crate) mod syntax {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpriteSection {
|
impl SpriteSection {
|
||||||
pub fn add_to<F>(
|
pub fn resolve_edges(
|
||||||
&self,
|
&self,
|
||||||
ct: &mut Content,
|
sections: &HashMap<ContentIndex, Arc<super::SpriteSection>>,
|
||||||
get_handle: F,
|
sec: &mut super::SpriteSection,
|
||||||
) -> Result<((u32, u32), super::SpriteSection)>
|
) -> Result<()> {
|
||||||
where
|
let edge_top = match &self.top {
|
||||||
F: Fn(&str) -> Option<AnimSectionHandle>,
|
Some(x) => super::resolve_edge_as_edge(sections, &x.val, sec.frame_duration)?,
|
||||||
{
|
None => super::SectionEdge::Stop,
|
||||||
|
};
|
||||||
|
|
||||||
|
let edge_bot = match &self.bot {
|
||||||
|
Some(x) => super::resolve_edge_as_edge(sections, &x.val, sec.frame_duration)?,
|
||||||
|
None => super::SectionEdge::Stop,
|
||||||
|
};
|
||||||
|
|
||||||
|
sec.edge_bot = edge_bot;
|
||||||
|
sec.edge_top = edge_top;
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_to(&self, ct: &mut Content) -> Result<((u32, u32), super::SpriteSection)> {
|
||||||
// Make sure all frames have the same size and add them
|
// Make sure all frames have the same size and add them
|
||||||
// to the frame vector
|
// to the frame vector
|
||||||
let mut dim = None;
|
let mut dim = None;
|
||||||
|
@ -111,23 +125,14 @@ pub(crate) mod syntax {
|
||||||
bail!("frame duration must be positive (and therefore nonzero).")
|
bail!("frame duration must be positive (and therefore nonzero).")
|
||||||
}
|
}
|
||||||
|
|
||||||
let edge_top = match &self.top {
|
|
||||||
Some(x) => super::resolve_edge_as_edge(&x.val, frame_duration, &get_handle)?,
|
|
||||||
None => super::SectionEdge::Stop,
|
|
||||||
};
|
|
||||||
|
|
||||||
let edge_bot = match &self.bot {
|
|
||||||
Some(x) => super::resolve_edge_as_edge(&x.val, frame_duration, &get_handle)?,
|
|
||||||
None => super::SectionEdge::Stop,
|
|
||||||
};
|
|
||||||
|
|
||||||
return Ok((
|
return Ok((
|
||||||
dim,
|
dim,
|
||||||
super::SpriteSection {
|
super::SpriteSection {
|
||||||
frames,
|
frames,
|
||||||
frame_duration,
|
frame_duration,
|
||||||
edge_top,
|
// These are changed later, after all sections are built
|
||||||
edge_bot,
|
edge_top: super::SectionEdge::Stop,
|
||||||
|
edge_bot: super::SectionEdge::Stop,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -141,27 +146,8 @@ pub(crate) mod syntax {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A handle for an animation section inside a sprite
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
pub enum AnimSectionHandle {
|
|
||||||
/// The hidden section
|
|
||||||
Hidden,
|
|
||||||
|
|
||||||
/// An index into this sprite's section array
|
|
||||||
Idx(usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AnimSectionHandle {
|
|
||||||
fn get_idx(&self) -> Option<usize> {
|
|
||||||
match self {
|
|
||||||
Self::Hidden => None,
|
|
||||||
Self::Idx(idx) => Some(*idx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An edge between two animation sections
|
/// An edge between two animation sections
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum SectionEdge {
|
pub enum SectionEdge {
|
||||||
/// Stop at the last frame of this section
|
/// Stop at the last frame of this section
|
||||||
Stop,
|
Stop,
|
||||||
|
@ -169,7 +155,7 @@ pub enum SectionEdge {
|
||||||
/// Play the given section from the bottm
|
/// Play the given section from the bottm
|
||||||
Bot {
|
Bot {
|
||||||
/// The section to play
|
/// The section to play
|
||||||
section: AnimSectionHandle,
|
section: Arc<SpriteSection>,
|
||||||
|
|
||||||
/// The length of this edge, in seconds
|
/// The length of this edge, in seconds
|
||||||
duration: f32,
|
duration: f32,
|
||||||
|
@ -178,7 +164,7 @@ pub enum SectionEdge {
|
||||||
/// Play the given section from the top
|
/// Play the given section from the top
|
||||||
Top {
|
Top {
|
||||||
/// The section to play
|
/// The section to play
|
||||||
section: AnimSectionHandle,
|
section: Arc<SpriteSection>,
|
||||||
|
|
||||||
/// The length of this edge, in seconds
|
/// The length of this edge, in seconds
|
||||||
duration: f32,
|
duration: f32,
|
||||||
|
@ -198,18 +184,18 @@ pub enum SectionEdge {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Where to start an animation
|
/// Where to start an animation
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum StartEdge {
|
pub enum StartEdge {
|
||||||
/// Play the given section from the bottm
|
/// Play the given section from the bottm
|
||||||
Bot {
|
Bot {
|
||||||
/// The section to play
|
/// The section to play
|
||||||
section: AnimSectionHandle,
|
section: Arc<SpriteSection>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Play the given section from the top
|
/// Play the given section from the top
|
||||||
Top {
|
Top {
|
||||||
/// The section to play
|
/// The section to play
|
||||||
section: AnimSectionHandle,
|
section: Arc<SpriteSection>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,80 +217,50 @@ impl Into<SectionEdge> for StartEdge {
|
||||||
/// Represents a sprite that may be used in the game.
|
/// Represents a sprite that may be used in the game.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Sprite {
|
pub struct Sprite {
|
||||||
/// The name of this sprite
|
/// This object's index
|
||||||
pub name: String,
|
pub index: ContentIndex,
|
||||||
|
|
||||||
/// This sprite's handle
|
|
||||||
pub handle: SpriteHandle,
|
|
||||||
|
|
||||||
/// Where this sprite starts playing
|
/// Where this sprite starts playing
|
||||||
pub start_at: StartEdge,
|
pub start_at: StartEdge,
|
||||||
|
|
||||||
/// This sprite's animation sections
|
/// This sprite's animation sections
|
||||||
sections: Vec<SpriteSection>,
|
pub sections: HashMap<ContentIndex, Arc<SpriteSection>>,
|
||||||
|
|
||||||
/// Allows us to get sprite sections by name
|
|
||||||
sections_by_name: HashMap<String, AnimSectionHandle>,
|
|
||||||
|
|
||||||
/// Aspect ratio of this sprite (width / height)
|
/// Aspect ratio of this sprite (width / height)
|
||||||
pub aspect: f32,
|
pub aspect: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref HIDDEN_SECTION: SpriteSection = SpriteSection {
|
/// The universal "hidden" sprite section, available in all sprites.
|
||||||
|
/// A SpriteAutomaton in this section will not be drawn.
|
||||||
|
pub static ref HIDDEN_SPRITE_SECTION: Arc<SpriteSection> = Arc::new(SpriteSection {
|
||||||
frames: vec![0],
|
frames: vec![0],
|
||||||
frame_duration: 0.0,
|
frame_duration: 0.0,
|
||||||
edge_bot: SectionEdge::Stop,
|
edge_bot: SectionEdge::Stop,
|
||||||
edge_top: SectionEdge::Stop,
|
edge_top: SectionEdge::Stop,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sprite {
|
impl Sprite {
|
||||||
/// Get an animation section from a handle
|
|
||||||
pub fn get_section(&self, section: AnimSectionHandle) -> &SpriteSection {
|
|
||||||
match section {
|
|
||||||
AnimSectionHandle::Hidden => &HIDDEN_SECTION,
|
|
||||||
AnimSectionHandle::Idx(idx) => &self.sections[idx],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get an animation section by name.
|
|
||||||
/// Returns None for invalid names
|
|
||||||
pub fn get_section_by_name(&self, name: &str) -> Option<&SpriteSection> {
|
|
||||||
match self.sections_by_name.get(name) {
|
|
||||||
None => return None,
|
|
||||||
Some(h) => Some(self.get_section(*h)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get an animation section's handle by name.
|
|
||||||
/// Returns None for invalid names
|
|
||||||
pub fn get_section_handle_by_name(&self, name: &str) -> Option<AnimSectionHandle> {
|
|
||||||
match self.sections_by_name.get(name) {
|
|
||||||
None => return None,
|
|
||||||
Some(h) => Some(*h),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the index of the texture of this sprite's first frame
|
/// Get the index of the texture of this sprite's first frame
|
||||||
pub fn get_first_frame(&self) -> u32 {
|
pub fn get_first_frame(&self) -> u32 {
|
||||||
match self.start_at {
|
match &self.start_at {
|
||||||
StartEdge::Bot { section } => *self.get_section(section).frames.last().unwrap(),
|
StartEdge::Bot { section } => *section.frames.last().unwrap(),
|
||||||
StartEdge::Top { section } => *self.get_section(section).frames.first().unwrap(),
|
StartEdge::Top { section } => *section.frames.first().unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get this sprite's starting section
|
/// Get this sprite's starting section
|
||||||
pub fn get_start_section(&self) -> AnimSectionHandle {
|
pub fn get_start_section(&self) -> Arc<SpriteSection> {
|
||||||
match self.start_at {
|
match &self.start_at {
|
||||||
StartEdge::Bot { section } => section,
|
StartEdge::Bot { section } => section.clone(),
|
||||||
StartEdge::Top { section } => section,
|
StartEdge::Top { section } => section.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate this sprite's sections
|
/// Iterate this sprite's sections
|
||||||
pub fn iter_sections(&self) -> impl Iterator<Item = &SpriteSection> {
|
pub fn iter_sections(&self) -> impl Iterator<Item = &Arc<SpriteSection>> {
|
||||||
self.sections.iter()
|
self.sections.values()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,66 +283,73 @@ pub struct SpriteSection {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve an edge specification string as a StartEdge
|
/// Resolve an edge specification string as a StartEdge
|
||||||
pub fn resolve_edge_as_start<F>(s: &str, get_handle: F) -> Result<super::StartEdge>
|
pub fn resolve_edge_as_start(
|
||||||
where
|
sections: &HashMap<ContentIndex, Arc<SpriteSection>>,
|
||||||
F: Fn(&str) -> Option<AnimSectionHandle>,
|
edge_string: &str,
|
||||||
{
|
) -> Result<super::StartEdge> {
|
||||||
let e = resolve_edge_as_edge(s, 0.0, get_handle)
|
let e = resolve_edge_as_edge(sections, edge_string, 0.0)
|
||||||
.with_context(|| format!("while resolving start edge"))?;
|
.with_context(|| format!("while resolving start edge"))?;
|
||||||
match e {
|
match e {
|
||||||
super::SectionEdge::Bot { section, .. } => Ok(super::StartEdge::Bot { section }),
|
super::SectionEdge::Bot { section, .. } => Ok(super::StartEdge::Bot { section }),
|
||||||
super::SectionEdge::Top { section, .. } => Ok(super::StartEdge::Top { section }),
|
super::SectionEdge::Top { section, .. } => Ok(super::StartEdge::Top { section }),
|
||||||
_ => {
|
_ => {
|
||||||
bail!("bad section start specification `{}`", s);
|
bail!("bad section start specification `{}`", edge_string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve an edge specifiation string as a SectionEdge
|
/// Resolve an edge specifiation string as a SectionEdge
|
||||||
pub fn resolve_edge_as_edge<F>(s: &str, duration: f32, get_handle: F) -> Result<super::SectionEdge>
|
pub fn resolve_edge_as_edge(
|
||||||
where
|
sections: &HashMap<ContentIndex, Arc<SpriteSection>>,
|
||||||
F: Fn(&str) -> Option<AnimSectionHandle>,
|
edge_string: &str,
|
||||||
{
|
duration: f32,
|
||||||
if s == "hidden" {
|
) -> Result<super::SectionEdge> {
|
||||||
|
if edge_string == "hidden" {
|
||||||
return Ok(super::SectionEdge::Top {
|
return Ok(super::SectionEdge::Top {
|
||||||
section: crate::AnimSectionHandle::Hidden,
|
section: crate::HIDDEN_SPRITE_SECTION.clone(),
|
||||||
duration,
|
duration,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if s == "stop" {
|
if edge_string == "stop" {
|
||||||
return Ok(super::SectionEdge::Stop);
|
return Ok(super::SectionEdge::Stop);
|
||||||
}
|
}
|
||||||
|
|
||||||
if s == "reverse" {
|
if edge_string == "reverse" {
|
||||||
return Ok(super::SectionEdge::Reverse { duration });
|
return Ok(super::SectionEdge::Reverse { duration });
|
||||||
}
|
}
|
||||||
|
|
||||||
if s == "repeat" {
|
if edge_string == "repeat" {
|
||||||
return Ok(super::SectionEdge::Repeat { duration });
|
return Ok(super::SectionEdge::Repeat { duration });
|
||||||
}
|
}
|
||||||
|
|
||||||
let (s, p) = match s.split_once(":") {
|
let (section_name, start_point) = match edge_string.split_once(":") {
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
None => {
|
None => {
|
||||||
bail!("bad section edge specification `{}`", s);
|
bail!("bad section edge specification `{}`", edge_string);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let section = match get_handle(s) {
|
let section = match sections.get(&ContentIndex::new(section_name)) {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => {
|
None => {
|
||||||
return Err(anyhow!("bad section edge specification `{}`", s))
|
return Err(anyhow!("bad section edge specification `{}`", section_name))
|
||||||
.with_context(|| format!("section `{}` doesn't exist", s));
|
.with_context(|| format!("section `{}` doesn't exist", section_name));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match p {
|
match start_point {
|
||||||
"top" => Ok(super::SectionEdge::Top { section, duration }),
|
"top" => Ok(super::SectionEdge::Top {
|
||||||
"bot" => Ok(super::SectionEdge::Bot { section, duration }),
|
section: section.clone(),
|
||||||
|
duration,
|
||||||
|
}),
|
||||||
|
"bot" => Ok(super::SectionEdge::Bot {
|
||||||
|
section: section.clone(),
|
||||||
|
duration,
|
||||||
|
}),
|
||||||
_ => {
|
_ => {
|
||||||
return Err(anyhow!("bad section edge specification `{}`", s))
|
return Err(anyhow!("bad section edge specification `{}`", section_name))
|
||||||
.with_context(|| format!("invalid target `{}`", p));
|
.with_context(|| format!("invalid target `{}`", start_point));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -419,93 +382,60 @@ impl crate::Build for Sprite {
|
||||||
let img = &ct.sprite_atlas.get_by_idx(idx);
|
let img = &ct.sprite_atlas.get_by_idx(idx);
|
||||||
let aspect = img.w / img.h;
|
let aspect = img.w / img.h;
|
||||||
|
|
||||||
let h = SpriteHandle {
|
let section = Arc::new(SpriteSection {
|
||||||
index: ct.sprites.len(),
|
|
||||||
};
|
|
||||||
|
|
||||||
ct.sprite_index.insert(sprite_name.clone(), h);
|
|
||||||
|
|
||||||
ct.sprites.push(Self {
|
|
||||||
name: sprite_name,
|
|
||||||
start_at: StartEdge::Top {
|
|
||||||
section: AnimSectionHandle::Idx(0),
|
|
||||||
},
|
|
||||||
sections: vec![SpriteSection {
|
|
||||||
frames: vec![img.idx.into()],
|
frames: vec![img.idx.into()],
|
||||||
// We implement unanimated sprites with a very fast framerate
|
// We implement unanimated sprites with a very fast framerate
|
||||||
// and STOP endpoints.
|
// and STOP endpoints.
|
||||||
frame_duration: 0.01,
|
frame_duration: 0.01,
|
||||||
edge_top: SectionEdge::Stop,
|
edge_top: SectionEdge::Stop,
|
||||||
edge_bot: SectionEdge::Stop,
|
edge_bot: SectionEdge::Stop,
|
||||||
}],
|
});
|
||||||
sections_by_name: HashMap::new(),
|
|
||||||
handle: h,
|
let sprite = Arc::new(Self {
|
||||||
|
index: ContentIndex::new(&sprite_name),
|
||||||
|
start_at: StartEdge::Top {
|
||||||
|
section: section.clone(),
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
let mut h = HashMap::new();
|
||||||
|
h.insert(ContentIndex::new("anim"), section);
|
||||||
|
h
|
||||||
|
},
|
||||||
aspect,
|
aspect,
|
||||||
});
|
});
|
||||||
|
ct.sprites.insert(sprite.index.clone(), sprite);
|
||||||
}
|
}
|
||||||
syntax::Sprite::OneSection(s) => {
|
syntax::Sprite::OneSection(s) => {
|
||||||
let sprite_handle = SpriteHandle {
|
|
||||||
index: ct.sprites.len(),
|
|
||||||
};
|
|
||||||
ct.sprite_index.insert(sprite_name.clone(), sprite_handle);
|
|
||||||
|
|
||||||
let (dim, section) = s
|
let (dim, section) = s
|
||||||
.add_to(ct, |s| {
|
.add_to(ct)
|
||||||
if s == "anim" {
|
|
||||||
Some(AnimSectionHandle::Idx(0))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.with_context(|| format!("while parsing sprite `{}`", sprite_name))?;
|
.with_context(|| format!("while parsing sprite `{}`", sprite_name))?;
|
||||||
let aspect = dim.0 as f32 / dim.1 as f32;
|
let aspect = dim.0 as f32 / dim.1 as f32;
|
||||||
|
|
||||||
let mut sections = Vec::new();
|
let section = Arc::new(section);
|
||||||
sections.push(section);
|
|
||||||
|
|
||||||
ct.sprites.push(Self {
|
let sprite = Arc::new(Self {
|
||||||
name: sprite_name,
|
index: ContentIndex::new(&sprite_name),
|
||||||
sections,
|
|
||||||
start_at: StartEdge::Top {
|
start_at: StartEdge::Top {
|
||||||
section: AnimSectionHandle::Idx(0),
|
section: section.clone(),
|
||||||
},
|
},
|
||||||
sections_by_name: {
|
sections: {
|
||||||
let mut h = HashMap::new();
|
let mut h = HashMap::new();
|
||||||
h.insert("anim".to_string(), AnimSectionHandle::Idx(0));
|
h.insert(ContentIndex::new("anim"), section.clone());
|
||||||
h
|
h
|
||||||
},
|
},
|
||||||
handle: sprite_handle,
|
|
||||||
aspect,
|
aspect,
|
||||||
});
|
});
|
||||||
|
ct.sprites.insert(sprite.index.clone(), sprite);
|
||||||
}
|
}
|
||||||
syntax::Sprite::Complete(s) => {
|
syntax::Sprite::Complete(s) => {
|
||||||
let mut section_names = HashMap::new();
|
let mut sections = HashMap::new();
|
||||||
for (name, _) in &s.section {
|
|
||||||
section_names
|
|
||||||
.insert(name.to_owned(), AnimSectionHandle::Idx(section_names.len()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let sprite_handle = SpriteHandle {
|
|
||||||
index: ct.sprites.len(),
|
|
||||||
};
|
|
||||||
ct.sprite_index.insert(sprite_name.clone(), sprite_handle);
|
|
||||||
|
|
||||||
let start_at =
|
|
||||||
resolve_edge_as_start(&s.start_at.val, |x| section_names.get(x).copied())
|
|
||||||
.with_context(|| format!("while loading sprite `{}`", sprite_name))?;
|
|
||||||
|
|
||||||
let mut sections = Vec::with_capacity(section_names.len());
|
|
||||||
let mut dim = None;
|
let mut dim = None;
|
||||||
|
|
||||||
// Make sure we add sections in order
|
for (name, sec) in &s.section {
|
||||||
let mut names = section_names.iter().collect::<Vec<_>>();
|
let (d, s) = sec
|
||||||
names.sort_by(|a, b| (a.1).get_idx().unwrap().cmp(&(b.1).get_idx().unwrap()));
|
.add_to(ct)
|
||||||
|
.with_context(|| format!("while parsing section `{}`", name))
|
||||||
for (k, _) in names {
|
|
||||||
let v = s.section.get(k).unwrap();
|
|
||||||
let (d, s) = v
|
|
||||||
.add_to(ct, |x| section_names.get(x).copied())
|
|
||||||
.with_context(|| format!("while parsing section `{}`", k))
|
|
||||||
.with_context(|| format!("while parsing sprite `{}`", sprite_name))?;
|
.with_context(|| format!("while parsing sprite `{}`", sprite_name))?;
|
||||||
|
|
||||||
// Make sure all dimensions are the same
|
// Make sure all dimensions are the same
|
||||||
|
@ -515,23 +445,35 @@ impl crate::Build for Sprite {
|
||||||
bail!(
|
bail!(
|
||||||
"could not load sprite `{}`, image sizes in section `{}` are different",
|
"could not load sprite `{}`, image sizes in section `{}` are different",
|
||||||
sprite_name,
|
sprite_name,
|
||||||
k
|
name
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
sections.push(s);
|
sections.insert(name.clone(), Arc::new(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We need to clone this here, thanks to self-reference.
|
||||||
|
let tmp_sections = sections.clone();
|
||||||
|
|
||||||
|
for (name, sec) in &s.section {
|
||||||
|
let parsed_sec = sections.get_mut(name).unwrap();
|
||||||
|
let parsed_sec = Arc::make_mut(parsed_sec);
|
||||||
|
sec.resolve_edges(&tmp_sections, parsed_sec)?;
|
||||||
|
}
|
||||||
|
|
||||||
let dim = dim.unwrap();
|
let dim = dim.unwrap();
|
||||||
let aspect = dim.0 as f32 / dim.1 as f32;
|
let aspect = dim.0 as f32 / dim.1 as f32;
|
||||||
|
|
||||||
ct.sprites.push(Self {
|
let start_at = resolve_edge_as_start(§ions, &s.start_at.val)
|
||||||
name: sprite_name,
|
.with_context(|| format!("while loading sprite `{}`", sprite_name))?;
|
||||||
sections,
|
|
||||||
|
let sprite = Arc::new(Self {
|
||||||
|
index: ContentIndex::new(&sprite_name),
|
||||||
start_at,
|
start_at,
|
||||||
handle: sprite_handle,
|
sections,
|
||||||
sections_by_name: section_names,
|
|
||||||
aspect,
|
aspect,
|
||||||
});
|
});
|
||||||
|
ct.sprites.insert(sprite.index.clone(), sprite);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,43 @@
|
||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use galactica_util::to_radians;
|
use galactica_util::to_radians;
|
||||||
use nalgebra::{Point2, Point3};
|
use nalgebra::{Point2, Point3};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
use crate::{
|
sync::Arc,
|
||||||
handle::SpriteHandle, util::Polar, Content, ContentBuildContext, SystemHandle,
|
|
||||||
SystemObjectHandle,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::{util::Polar, Content, ContentBuildContext, ContentIndex, Outfit, Sprite};
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::ContentIndex;
|
||||||
// Raw serde syntax structs.
|
// Raw serde syntax structs.
|
||||||
// These are never seen by code outside this crate.
|
// These are never seen by code outside this crate.
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct System {
|
pub struct System {
|
||||||
pub object: HashMap<String, Object>,
|
pub name: String,
|
||||||
|
pub object: HashMap<ContentIndex, Object>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Object {
|
pub struct Object {
|
||||||
pub sprite: String,
|
pub sprite: ContentIndex,
|
||||||
pub position: Position,
|
pub position: Position,
|
||||||
|
|
||||||
pub size: f32,
|
pub size: f32,
|
||||||
|
|
||||||
pub radius: Option<f32>,
|
pub radius: Option<f32>,
|
||||||
pub angle: Option<f32>,
|
pub angle: Option<f32>,
|
||||||
pub landable: Option<bool>,
|
pub landable: Option<Landable>,
|
||||||
pub name: Option<String>,
|
}
|
||||||
pub desc: Option<String>,
|
|
||||||
pub image: Option<String>,
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Landable {
|
||||||
|
pub name: String,
|
||||||
|
pub desc: String,
|
||||||
|
pub image: ContentIndex,
|
||||||
|
pub outfitter: Vec<ContentIndex>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -52,14 +58,14 @@ pub(crate) mod syntax {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum CoordinatesTwo {
|
pub enum CoordinatesTwo {
|
||||||
Label(String),
|
Label(ContentIndex),
|
||||||
Coords([f32; 2]),
|
Coords([f32; 2]),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToString for CoordinatesTwo {
|
impl ToString for CoordinatesTwo {
|
||||||
fn to_string(&self) -> String {
|
fn to_string(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Self::Label(s) => s.to_owned(),
|
Self::Label(s) => s.to_string(),
|
||||||
Self::Coords(v) => format!("{:?}", v),
|
Self::Coords(v) => format!("{:?}", v),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,14 +74,14 @@ pub(crate) mod syntax {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum CoordinatesThree {
|
pub enum CoordinatesThree {
|
||||||
Label(String),
|
Label(ContentIndex),
|
||||||
Coords([f32; 3]),
|
Coords([f32; 3]),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToString for CoordinatesThree {
|
impl ToString for CoordinatesThree {
|
||||||
fn to_string(&self) -> String {
|
fn to_string(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Self::Label(s) => s.to_owned(),
|
Self::Label(s) => s.to_string(),
|
||||||
Self::Coords(v) => format!("{:?}", v),
|
Self::Coords(v) => format!("{:?}", v),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,14 +94,14 @@ pub(crate) mod syntax {
|
||||||
/// Represents a star system
|
/// Represents a star system
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct System {
|
pub struct System {
|
||||||
/// This star system's name
|
/// This object's name
|
||||||
pub name: String,
|
pub display_name: String,
|
||||||
|
|
||||||
/// This star system's handle
|
/// This object's index
|
||||||
pub handle: SystemHandle,
|
pub index: ContentIndex,
|
||||||
|
|
||||||
/// Objects in this system
|
/// Objects in this system
|
||||||
pub objects: Vec<SystemObject>,
|
pub objects: HashMap<ContentIndex, Arc<SystemObject>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents an orbiting body in a star system
|
/// Represents an orbiting body in a star system
|
||||||
|
@ -104,11 +110,11 @@ pub struct System {
|
||||||
/// System objects to not interact with the physics engine.
|
/// System objects to not interact with the physics engine.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SystemObject {
|
pub struct SystemObject {
|
||||||
/// This object's sprite
|
/// This object's index
|
||||||
pub sprite: SpriteHandle,
|
pub index: ContentIndex,
|
||||||
|
|
||||||
/// This object's handle
|
/// This object's sprite
|
||||||
pub handle: SystemObjectHandle,
|
pub sprite: Arc<Sprite>,
|
||||||
|
|
||||||
/// This object's size.
|
/// This object's size.
|
||||||
/// Measured as height in game units.
|
/// Measured as height in game units.
|
||||||
|
@ -124,26 +130,29 @@ pub struct SystemObject {
|
||||||
pub angle: f32,
|
pub angle: f32,
|
||||||
|
|
||||||
/// If true, ships may land on this object
|
/// If true, ships may land on this object
|
||||||
pub landable: bool,
|
pub landable: Option<LandableSystemObject>,
|
||||||
|
}
|
||||||
|
|
||||||
/// The pretty display name of this object
|
#[derive(Debug, Clone)]
|
||||||
pub name: Option<String>,
|
pub struct LandableSystemObject {
|
||||||
|
/// This object's name
|
||||||
|
pub display_name: String,
|
||||||
|
|
||||||
/// The system-unique label of this object
|
/// The description of this object
|
||||||
pub label: String,
|
pub desc: String,
|
||||||
|
|
||||||
/// The description of this object (shown on landed ui)
|
/// This object's image
|
||||||
pub desc: Option<String>,
|
pub image: Arc<Sprite>,
|
||||||
|
|
||||||
/// This object's image (shown on landed ui)
|
/// The outfits we can buy here
|
||||||
pub image: Option<SpriteHandle>,
|
pub outfitter: Vec<Arc<Outfit>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function for resolve_position, never called on its own.
|
/// Helper function for resolve_position, never called on its own.
|
||||||
fn resolve_coordinates(
|
fn resolve_coordinates(
|
||||||
objects: &HashMap<String, syntax::Object>,
|
objects: &HashMap<ContentIndex, syntax::Object>,
|
||||||
cor: &syntax::CoordinatesThree,
|
cor: &syntax::CoordinatesThree,
|
||||||
mut cycle_detector: HashSet<String>,
|
mut cycle_detector: HashSet<ContentIndex>,
|
||||||
) -> Result<Point3<f32>> {
|
) -> Result<Point3<f32>> {
|
||||||
match cor {
|
match cor {
|
||||||
syntax::CoordinatesThree::Coords(c) => Ok((*c).into()),
|
syntax::CoordinatesThree::Coords(c) => Ok((*c).into()),
|
||||||
|
@ -160,7 +169,7 @@ fn resolve_coordinates(
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
cycle_detector.insert(l.to_owned());
|
cycle_detector.insert(l.clone());
|
||||||
|
|
||||||
let p = match objects.get(l) {
|
let p = match objects.get(l) {
|
||||||
Some(p) => p,
|
Some(p) => p,
|
||||||
|
@ -174,9 +183,9 @@ fn resolve_coordinates(
|
||||||
|
|
||||||
/// Given an object, resolve its position as a Point3.
|
/// Given an object, resolve its position as a Point3.
|
||||||
fn resolve_position(
|
fn resolve_position(
|
||||||
objects: &HashMap<String, syntax::Object>,
|
objects: &HashMap<ContentIndex, syntax::Object>,
|
||||||
obj: &syntax::Object,
|
obj: &syntax::Object,
|
||||||
cycle_detector: HashSet<String>,
|
cycle_detector: HashSet<ContentIndex>,
|
||||||
) -> Result<Point3<f32>> {
|
) -> Result<Point3<f32>> {
|
||||||
match &obj.position {
|
match &obj.position {
|
||||||
syntax::Position::Cartesian(c) => Ok(resolve_coordinates(objects, &c, cycle_detector)?),
|
syntax::Position::Cartesian(c) => Ok(resolve_coordinates(objects, &c, cycle_detector)?),
|
||||||
|
@ -208,99 +217,78 @@ impl crate::Build for System {
|
||||||
content: &mut Content,
|
content: &mut Content,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
for (system_name, system) in system {
|
for (system_name, system) in system {
|
||||||
let mut objects = Vec::new();
|
let mut objects = HashMap::new();
|
||||||
|
|
||||||
let system_handle = SystemHandle {
|
for (index, object) in &system.object {
|
||||||
index: content.systems.len(),
|
|
||||||
};
|
|
||||||
|
|
||||||
for (label, obj) in &system.object {
|
|
||||||
let mut cycle_detector = HashSet::new();
|
let mut cycle_detector = HashSet::new();
|
||||||
cycle_detector.insert(label.clone());
|
cycle_detector.insert(index.clone());
|
||||||
|
|
||||||
let sprite_handle = match content.sprite_index.get(&obj.sprite) {
|
let sprite = match content.sprites.get(&object.sprite) {
|
||||||
None => bail!(
|
None => bail!(
|
||||||
"In system `{}`: sprite `{}` doesn't exist",
|
"In system `{}`: sprite `{}` doesn't exist",
|
||||||
system_name,
|
system_name,
|
||||||
obj.sprite
|
object.sprite
|
||||||
),
|
),
|
||||||
Some(t) => *t,
|
Some(t) => t.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let image_handle = match &obj.image {
|
let landable = 'landable: {
|
||||||
Some(x) => match content.sprite_index.get(x) {
|
if object.landable.is_none() {
|
||||||
|
break 'landable None;
|
||||||
|
}
|
||||||
|
let l = object.landable.as_ref().unwrap();
|
||||||
|
|
||||||
|
let image = match content.sprites.get(&l.image) {
|
||||||
None => bail!(
|
None => bail!(
|
||||||
"In system `{}`: sprite `{}` doesn't exist",
|
"In system `{}`: sprite `{}` doesn't exist",
|
||||||
system_name,
|
system_name,
|
||||||
obj.sprite
|
object.sprite
|
||||||
),
|
),
|
||||||
Some(t) => Some(*t),
|
Some(t) => t.clone(),
|
||||||
},
|
|
||||||
None => None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if obj.landable.unwrap_or(false) {
|
let mut outfitter = Vec::new();
|
||||||
if obj.name.is_none() {
|
for o in &l.outfitter {
|
||||||
return Err(anyhow!("if an object is landable, it must have a name"))
|
match content.outfits.get(&o) {
|
||||||
.with_context(|| format!("in object labeled `{}`", label))
|
Some(x) => outfitter.push(x.clone()),
|
||||||
.with_context(|| format!("in system `{}`", system_name));
|
None => {
|
||||||
|
bail!("In system `{}`: outfit `{}` doesn't exist", system_name, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
if obj.desc.is_none() {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"if an object is landable, it must have a description"
|
|
||||||
))
|
|
||||||
.with_context(|| format!("in object labeled `{}`", label))
|
|
||||||
.with_context(|| format!("in system `{}`", system_name));
|
|
||||||
}
|
|
||||||
|
|
||||||
if obj.image.is_none() {
|
|
||||||
return Err(anyhow!("if an object is landable, it must have an image"))
|
|
||||||
.with_context(|| format!("in object labeled `{}`", label))
|
|
||||||
.with_context(|| format!("in system `{}`", system_name));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
objects.push(SystemObject {
|
break 'landable Some(LandableSystemObject {
|
||||||
label: label.clone(),
|
image,
|
||||||
sprite: sprite_handle,
|
outfitter,
|
||||||
image: image_handle,
|
display_name: l.name.clone(),
|
||||||
pos: resolve_position(&system.object, &obj, cycle_detector)
|
|
||||||
.with_context(|| format!("in object {:#?}", label))?,
|
|
||||||
size: obj.size,
|
|
||||||
angle: to_radians(obj.angle.unwrap_or(0.0)),
|
|
||||||
handle: SystemObjectHandle {
|
|
||||||
system_handle,
|
|
||||||
body_index: 0,
|
|
||||||
},
|
|
||||||
landable: obj.landable.unwrap_or(false),
|
|
||||||
name: obj.name.as_ref().map(|x| x.clone()),
|
|
||||||
// TODO: better linebreaks, handle double spaces
|
// TODO: better linebreaks, handle double spaces
|
||||||
// Tabs
|
// Tabs
|
||||||
desc: obj
|
desc: l.desc.replace("\n", " ").replace("<br>", "\n"),
|
||||||
.desc
|
|
||||||
.as_ref()
|
|
||||||
.map(|x| x.replace("\n", " ").replace("<br>", "\n")),
|
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
objects.insert(
|
||||||
|
index.clone(),
|
||||||
|
Arc::new(SystemObject {
|
||||||
|
index: index.clone(),
|
||||||
|
sprite,
|
||||||
|
pos: resolve_position(&system.object, &object, cycle_detector)
|
||||||
|
.with_context(|| format!("in object {:#?}", index))?,
|
||||||
|
size: object.size,
|
||||||
|
angle: to_radians(object.angle.unwrap_or(0.0)),
|
||||||
|
landable: landable,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by z-distance. This is important, since these are
|
content.systems.insert(
|
||||||
// rendered in this order. We need far objects to be behind
|
ContentIndex::new(&system_name),
|
||||||
// near objects!
|
Arc::new(Self {
|
||||||
objects.sort_by(|a, b| b.pos.z.total_cmp(&a.pos.z));
|
index: ContentIndex::new(&system_name),
|
||||||
|
display_name: system.name,
|
||||||
// Update object handles
|
|
||||||
let mut i = 0;
|
|
||||||
for o in &mut objects {
|
|
||||||
o.handle.body_index = i;
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
content.systems.push(Self {
|
|
||||||
handle: system_handle,
|
|
||||||
name: system_name,
|
|
||||||
objects,
|
objects,
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::{AnimSectionHandle, Content, SectionEdge, SpriteHandle, StartEdge};
|
use crate::{SectionEdge, Sprite, SpriteSection, StartEdge};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// A single frame's state
|
/// A single frame's state
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -43,11 +44,11 @@ enum AnimDirection {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SpriteAutomaton {
|
pub struct SpriteAutomaton {
|
||||||
/// The sprite we're animating
|
/// The sprite we're animating
|
||||||
sprite: SpriteHandle,
|
sprite: Arc<Sprite>,
|
||||||
|
|
||||||
/// Which animation section we're on
|
/// Which animation section we're on
|
||||||
/// This MUST be a section from this Automaton's sprite
|
/// This MUST be a section from this Automaton's sprite
|
||||||
current_section: AnimSectionHandle,
|
current_section: Arc<SpriteSection>,
|
||||||
|
|
||||||
/// Which frame we're on
|
/// Which frame we're on
|
||||||
current_frame: usize,
|
current_frame: usize,
|
||||||
|
@ -75,39 +76,35 @@ pub struct SpriteAutomaton {
|
||||||
|
|
||||||
impl SpriteAutomaton {
|
impl SpriteAutomaton {
|
||||||
/// Create a new AnimAutomaton
|
/// Create a new AnimAutomaton
|
||||||
pub fn new(ct: &Content, sprite_handle: SpriteHandle) -> Self {
|
pub fn new(sprite: Arc<Sprite>) -> Self {
|
||||||
let sprite = ct.get_sprite(sprite_handle);
|
let (current_section, texture, current_direction) = {
|
||||||
|
match &sprite.start_at {
|
||||||
let (current_section, texture, current_direction) = match sprite.start_at {
|
|
||||||
StartEdge::Top { section } => (
|
StartEdge::Top { section } => (
|
||||||
section,
|
section,
|
||||||
*sprite.get_section(section).frames.first().unwrap(),
|
*section.frames.first().unwrap(),
|
||||||
AnimDirection::Down,
|
AnimDirection::Down,
|
||||||
),
|
),
|
||||||
StartEdge::Bot { section } => (
|
StartEdge::Bot { section } => {
|
||||||
section,
|
(section, *section.frames.last().unwrap(), AnimDirection::Up)
|
||||||
*sprite.get_section(section).frames.last().unwrap(),
|
}
|
||||||
AnimDirection::Up,
|
}
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let sec = sprite.get_section(current_section);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
sprite: sprite.handle,
|
sprite: sprite.clone(),
|
||||||
current_frame: 0,
|
current_frame: 0,
|
||||||
|
|
||||||
current_edge_progress: match current_direction {
|
current_edge_progress: match current_direction {
|
||||||
AnimDirection::Down => 0.0,
|
AnimDirection::Down => 0.0,
|
||||||
AnimDirection::Up => sec.frame_duration,
|
AnimDirection::Up => current_section.frame_duration,
|
||||||
AnimDirection::Stop => unreachable!("how'd you get here?"),
|
AnimDirection::Stop => unreachable!("how'd you get here?"),
|
||||||
},
|
},
|
||||||
|
|
||||||
current_edge_duration: sec.frame_duration,
|
current_edge_duration: current_section.frame_duration,
|
||||||
next_edge_override: None,
|
next_edge_override: None,
|
||||||
|
|
||||||
current_direction,
|
current_direction,
|
||||||
current_section,
|
current_section: current_section.clone(),
|
||||||
last_texture: texture,
|
last_texture: texture,
|
||||||
next_texture: texture,
|
next_texture: texture,
|
||||||
}
|
}
|
||||||
|
@ -119,14 +116,11 @@ impl SpriteAutomaton {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Force a transition to the given section right now
|
/// Force a transition to the given section right now
|
||||||
pub fn jump_to(&mut self, ct: &Content, start: SectionEdge) {
|
pub fn jump_to(&mut self, start: &SectionEdge) {
|
||||||
self.take_edge(ct, start);
|
self.take_edge(start);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take_edge(&mut self, ct: &Content, e: SectionEdge) {
|
fn take_edge(&mut self, e: &SectionEdge) {
|
||||||
let sprite = ct.get_sprite(self.sprite);
|
|
||||||
let current_section = sprite.get_section(self.current_section);
|
|
||||||
|
|
||||||
let last = match self.current_direction {
|
let last = match self.current_direction {
|
||||||
AnimDirection::Stop => self.next_texture,
|
AnimDirection::Stop => self.next_texture,
|
||||||
AnimDirection::Down => self.next_texture,
|
AnimDirection::Down => self.next_texture,
|
||||||
|
@ -141,7 +135,7 @@ impl SpriteAutomaton {
|
||||||
self.current_frame = 0;
|
self.current_frame = 0;
|
||||||
}
|
}
|
||||||
AnimDirection::Down => {
|
AnimDirection::Down => {
|
||||||
self.current_frame = current_section.frames.len() - 1;
|
self.current_frame = self.current_section.frames.len() - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,29 +143,28 @@ impl SpriteAutomaton {
|
||||||
self.current_direction = AnimDirection::Stop;
|
self.current_direction = AnimDirection::Stop;
|
||||||
}
|
}
|
||||||
SectionEdge::Top { section, duration } => {
|
SectionEdge::Top { section, duration } => {
|
||||||
self.current_section = section;
|
self.current_section = section.clone();
|
||||||
self.current_edge_duration = duration;
|
self.current_edge_duration = *duration;
|
||||||
self.current_frame = 0;
|
self.current_frame = 0;
|
||||||
self.current_direction = AnimDirection::Down;
|
self.current_direction = AnimDirection::Down;
|
||||||
}
|
}
|
||||||
SectionEdge::Bot { section, duration } => {
|
SectionEdge::Bot { section, duration } => {
|
||||||
let s = sprite.get_section(section);
|
self.current_section = section.clone();
|
||||||
self.current_section = section;
|
self.current_frame = section.frames.len() - 1;
|
||||||
self.current_frame = s.frames.len() - 1;
|
self.current_edge_duration = *duration;
|
||||||
self.current_edge_duration = duration;
|
|
||||||
self.current_direction = AnimDirection::Up;
|
self.current_direction = AnimDirection::Up;
|
||||||
}
|
}
|
||||||
SectionEdge::Repeat { duration } => {
|
SectionEdge::Repeat { duration } => {
|
||||||
match self.current_direction {
|
match self.current_direction {
|
||||||
AnimDirection::Stop => {}
|
AnimDirection::Stop => {}
|
||||||
AnimDirection::Up => {
|
AnimDirection::Up => {
|
||||||
self.current_frame = current_section.frames.len() - 1;
|
self.current_frame = self.current_section.frames.len() - 1;
|
||||||
}
|
}
|
||||||
AnimDirection::Down => {
|
AnimDirection::Down => {
|
||||||
self.current_frame = 0;
|
self.current_frame = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.current_edge_duration = duration;
|
self.current_edge_duration = *duration;
|
||||||
}
|
}
|
||||||
SectionEdge::Reverse { duration } => {
|
SectionEdge::Reverse { duration } => {
|
||||||
match self.current_direction {
|
match self.current_direction {
|
||||||
|
@ -180,7 +173,7 @@ impl SpriteAutomaton {
|
||||||
// Jump to SECOND frame, since we've already shown the
|
// Jump to SECOND frame, since we've already shown the
|
||||||
// first during the fade transition
|
// first during the fade transition
|
||||||
self.current_frame = {
|
self.current_frame = {
|
||||||
if current_section.frames.len() == 1 {
|
if self.current_section.frames.len() == 1 {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
1
|
1
|
||||||
|
@ -190,45 +183,39 @@ impl SpriteAutomaton {
|
||||||
}
|
}
|
||||||
AnimDirection::Down => {
|
AnimDirection::Down => {
|
||||||
self.current_frame = {
|
self.current_frame = {
|
||||||
if current_section.frames.len() == 1 {
|
if self.current_section.frames.len() == 1 {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
current_section.frames.len() - 2
|
self.current_section.frames.len() - 2
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.current_direction = AnimDirection::Up;
|
self.current_direction = AnimDirection::Up;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.current_edge_duration = duration;
|
self.current_edge_duration = *duration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.current_direction {
|
match self.current_direction {
|
||||||
AnimDirection::Stop => {
|
AnimDirection::Stop => {
|
||||||
let current_section = sprite.get_section(self.current_section);
|
self.next_texture = self.current_section.frames[self.current_frame];
|
||||||
self.next_texture = current_section.frames[self.current_frame];
|
self.last_texture = self.current_section.frames[self.current_frame];
|
||||||
self.last_texture = current_section.frames[self.current_frame];
|
|
||||||
}
|
}
|
||||||
AnimDirection::Down => {
|
AnimDirection::Down => {
|
||||||
let current_section = sprite.get_section(self.current_section);
|
|
||||||
self.last_texture = last;
|
self.last_texture = last;
|
||||||
self.next_texture = current_section.frames[self.current_frame];
|
self.next_texture = self.current_section.frames[self.current_frame];
|
||||||
self.current_edge_progress = 0.0;
|
self.current_edge_progress = 0.0;
|
||||||
}
|
}
|
||||||
AnimDirection::Up => {
|
AnimDirection::Up => {
|
||||||
let current_section = sprite.get_section(self.current_section);
|
|
||||||
self.next_texture = last;
|
self.next_texture = last;
|
||||||
self.last_texture = current_section.frames[self.current_frame];
|
self.last_texture = self.current_section.frames[self.current_frame];
|
||||||
self.current_edge_progress = self.current_edge_duration;
|
self.current_edge_progress = self.current_edge_duration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Step this animation by `t` seconds
|
/// Step this animation by `t` seconds
|
||||||
pub fn step(&mut self, ct: &Content, t: f32) {
|
pub fn step(&mut self, t: f32) {
|
||||||
let sprite = ct.get_sprite(self.sprite);
|
|
||||||
let current_section = sprite.get_section(self.current_section);
|
|
||||||
|
|
||||||
// Current_fade and current_frame keep track of where we are in the current section.
|
// Current_fade and current_frame keep track of where we are in the current section.
|
||||||
// current_frame indexes this section frames. When it exceeds the number of frames
|
// current_frame indexes this section frames. When it exceeds the number of frames
|
||||||
// or falls below zero (when moving in reverse), we switch to the next section.
|
// or falls below zero (when moving in reverse), we switch to the next section.
|
||||||
|
@ -240,15 +227,16 @@ impl SpriteAutomaton {
|
||||||
// Note that frame_duration may be zero!
|
// Note that frame_duration may be zero!
|
||||||
// This is only possible in the hidden texture, since
|
// This is only possible in the hidden texture, since
|
||||||
// user-provided sections are always checked to be positive.
|
// user-provided sections are always checked to be positive.
|
||||||
assert!(current_section.frame_duration >= 0.0);
|
assert!(self.current_section.frame_duration >= 0.0);
|
||||||
|
|
||||||
match self.current_direction {
|
match self.current_direction {
|
||||||
AnimDirection::Stop => {
|
AnimDirection::Stop => {
|
||||||
// Edge case: we're stopped and got a request to transition.
|
// Edge case: we're stopped and got a request to transition.
|
||||||
// we should transition right away.
|
// we should transition right away.
|
||||||
|
|
||||||
if let Some(e) = self.next_edge_override.take() {
|
if self.next_edge_override.is_some() {
|
||||||
self.take_edge(ct, e);
|
let e = self.next_edge_override.take().unwrap();
|
||||||
|
self.take_edge(&e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -259,21 +247,21 @@ impl SpriteAutomaton {
|
||||||
|
|
||||||
// We're stepping foward and finished this frame
|
// We're stepping foward and finished this frame
|
||||||
if self.current_edge_progress > self.current_edge_duration {
|
if self.current_edge_progress > self.current_edge_duration {
|
||||||
if self.current_frame < current_section.frames.len() - 1 {
|
if self.current_frame < self.current_section.frames.len() - 1 {
|
||||||
self.current_frame += 1;
|
self.current_frame += 1;
|
||||||
self.last_texture = self.next_texture;
|
self.last_texture = self.next_texture;
|
||||||
self.next_texture = current_section.frames[self.current_frame];
|
self.next_texture = self.current_section.frames[self.current_frame];
|
||||||
self.current_edge_progress = 0.0;
|
self.current_edge_progress = 0.0;
|
||||||
self.current_edge_duration = current_section.frame_duration;
|
self.current_edge_duration = self.current_section.frame_duration;
|
||||||
} else {
|
} else {
|
||||||
let e = {
|
let e = {
|
||||||
if self.next_edge_override.is_some() {
|
if self.next_edge_override.is_some() {
|
||||||
self.next_edge_override.take().unwrap()
|
self.next_edge_override.take().unwrap()
|
||||||
} else {
|
} else {
|
||||||
current_section.edge_bot.clone()
|
self.current_section.edge_bot.clone()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.take_edge(ct, e);
|
self.take_edge(&e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -286,18 +274,18 @@ impl SpriteAutomaton {
|
||||||
if self.current_frame > 0 {
|
if self.current_frame > 0 {
|
||||||
self.current_frame -= 1;
|
self.current_frame -= 1;
|
||||||
self.next_texture = self.last_texture;
|
self.next_texture = self.last_texture;
|
||||||
self.last_texture = current_section.frames[self.current_frame];
|
self.last_texture = self.current_section.frames[self.current_frame];
|
||||||
self.current_edge_progress = current_section.frame_duration;
|
self.current_edge_progress = self.current_section.frame_duration;
|
||||||
self.current_edge_duration = current_section.frame_duration;
|
self.current_edge_duration = self.current_section.frame_duration;
|
||||||
} else {
|
} else {
|
||||||
let e = {
|
let e = {
|
||||||
if self.next_edge_override.is_some() {
|
if self.next_edge_override.is_some() {
|
||||||
self.next_edge_override.take().unwrap()
|
self.next_edge_override.take().unwrap()
|
||||||
} else {
|
} else {
|
||||||
current_section.edge_top.clone()
|
self.current_section.edge_top.clone()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.take_edge(ct, e);
|
self.take_edge(&e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -314,7 +302,7 @@ impl SpriteAutomaton {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the sprite this automaton is using
|
/// Get the sprite this automaton is using
|
||||||
pub fn get_sprite(&self) -> SpriteHandle {
|
pub fn get_sprite(&self) -> Arc<Sprite> {
|
||||||
self.sprite
|
self.sprite.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use galactica_content::{Content, FactionHandle, OutfitHandle, ShipHandle, SystemHandle};
|
use galactica_content::{Content, ContentIndex};
|
||||||
use galactica_playeragent::PlayerAgent;
|
use galactica_playeragent::PlayerAgent;
|
||||||
use galactica_system::data::ShipPersonality;
|
use galactica_system::data::ShipPersonality;
|
||||||
use galactica_system::phys::{PhysImage, PhysSim, PhysSimShipHandle, PhysStepResources};
|
use galactica_system::phys::{PhysImage, PhysSim, PhysSimShipHandle, PhysStepResources};
|
||||||
|
use galactica_system::PlayerDirective;
|
||||||
use galactica_util::timing::Timing;
|
use galactica_util::timing::Timing;
|
||||||
use nalgebra::Point2;
|
use nalgebra::Point2;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -24,82 +25,101 @@ unsafe impl<'a> Send for Game {}
|
||||||
impl<'a> Game {
|
impl<'a> Game {
|
||||||
pub fn make_player(&mut self) -> PhysSimShipHandle {
|
pub fn make_player(&mut self) -> PhysSimShipHandle {
|
||||||
let player = self.phys_sim.add_ship(
|
let player = self.phys_sim.add_ship(
|
||||||
&self.ct,
|
self.ct
|
||||||
ShipHandle { index: 0 },
|
.ships
|
||||||
FactionHandle { index: 0 },
|
.get(&ContentIndex::new("gypsum"))
|
||||||
|
.unwrap()
|
||||||
|
.clone(),
|
||||||
|
self.ct
|
||||||
|
.factions
|
||||||
|
.get(&ContentIndex::new("player"))
|
||||||
|
.unwrap()
|
||||||
|
.clone(),
|
||||||
ShipPersonality::Player,
|
ShipPersonality::Player,
|
||||||
Point2::new(0.0, 4000.0),
|
Point2::new(0.0, 4000.0),
|
||||||
);
|
);
|
||||||
|
|
||||||
let s = self.phys_sim.get_ship_mut(&player).unwrap();
|
let s = self.phys_sim.get_ship_mut(&player).unwrap();
|
||||||
s.add_outfits(
|
s.add_outfits([
|
||||||
&self.ct,
|
self.ct
|
||||||
[
|
.outfits
|
||||||
OutfitHandle { index: 0 },
|
.get(&ContentIndex::new("plasma engines"))
|
||||||
OutfitHandle { index: 1 },
|
.unwrap()
|
||||||
OutfitHandle { index: 2 },
|
.clone(),
|
||||||
],
|
self.ct
|
||||||
);
|
.outfits
|
||||||
|
.get(&ContentIndex::new("shield generator"))
|
||||||
|
.unwrap()
|
||||||
|
.clone(),
|
||||||
|
self.ct
|
||||||
|
.outfits
|
||||||
|
.get(&ContentIndex::new("blaster"))
|
||||||
|
.unwrap()
|
||||||
|
.clone(),
|
||||||
|
self.ct
|
||||||
|
.outfits
|
||||||
|
.get(&ContentIndex::new("blaster"))
|
||||||
|
.unwrap()
|
||||||
|
.clone(),
|
||||||
|
]);
|
||||||
|
|
||||||
return player;
|
return player;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(ct: Arc<Content>) -> Self {
|
pub fn new(ct: Arc<Content>) -> Self {
|
||||||
let mut phys_sim = PhysSim::new(&ct, SystemHandle { index: 0 });
|
let mut phys_sim = PhysSim::new();
|
||||||
|
|
||||||
let a = phys_sim.add_ship(
|
let a = phys_sim.add_ship(
|
||||||
&ct,
|
ct.ships.get(&ContentIndex::new("gypsum")).unwrap().clone(),
|
||||||
ShipHandle { index: 0 },
|
ct.factions
|
||||||
FactionHandle { index: 1 },
|
.get(&ContentIndex::new("enemy"))
|
||||||
ShipPersonality::Point,
|
.unwrap()
|
||||||
Point2::new(1000.0, 0.0),
|
.clone(),
|
||||||
);
|
|
||||||
|
|
||||||
let s = phys_sim.get_ship_mut(&a).unwrap();
|
|
||||||
s.add_outfits(
|
|
||||||
&ct,
|
|
||||||
[
|
|
||||||
OutfitHandle { index: 0 },
|
|
||||||
OutfitHandle { index: 1 },
|
|
||||||
OutfitHandle { index: 2 },
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
let a = phys_sim.add_ship(
|
|
||||||
&ct,
|
|
||||||
ShipHandle { index: 0 },
|
|
||||||
FactionHandle { index: 1 },
|
|
||||||
ShipPersonality::Point,
|
ShipPersonality::Point,
|
||||||
Point2::new(1000.0, 4000.0),
|
Point2::new(1000.0, 4000.0),
|
||||||
);
|
);
|
||||||
|
|
||||||
let s = phys_sim.get_ship_mut(&a).unwrap();
|
let s = phys_sim.get_ship_mut(&a).unwrap();
|
||||||
s.add_outfits(
|
s.add_outfits([
|
||||||
&ct,
|
ct.outfits
|
||||||
[
|
.get(&ContentIndex::new("plasma engines"))
|
||||||
OutfitHandle { index: 0 },
|
.unwrap()
|
||||||
OutfitHandle { index: 1 },
|
.clone(),
|
||||||
OutfitHandle { index: 2 },
|
ct.outfits
|
||||||
],
|
.get(&ContentIndex::new("shield generator"))
|
||||||
);
|
.unwrap()
|
||||||
|
.clone(),
|
||||||
|
ct.outfits
|
||||||
|
.get(&ContentIndex::new("blaster"))
|
||||||
|
.unwrap()
|
||||||
|
.clone(),
|
||||||
|
]);
|
||||||
|
|
||||||
let a = phys_sim.add_ship(
|
let a = phys_sim.add_ship(
|
||||||
&ct,
|
ct.ships.get(&ContentIndex::new("gypsum")).unwrap().clone(),
|
||||||
ShipHandle { index: 0 },
|
ct.factions
|
||||||
FactionHandle { index: 0 },
|
.get(&ContentIndex::new("player"))
|
||||||
|
.unwrap()
|
||||||
|
.clone(),
|
||||||
ShipPersonality::Dummy,
|
ShipPersonality::Dummy,
|
||||||
Point2::new(200.0, 2000.0),
|
Point2::new(200.0, 2000.0),
|
||||||
);
|
);
|
||||||
|
|
||||||
let s = phys_sim.get_ship_mut(&a).unwrap();
|
let s = phys_sim.get_ship_mut(&a).unwrap();
|
||||||
s.add_outfits(
|
s.add_outfits([
|
||||||
&ct,
|
ct.outfits
|
||||||
[
|
.get(&ContentIndex::new("plasma engines"))
|
||||||
OutfitHandle { index: 0 },
|
.unwrap()
|
||||||
OutfitHandle { index: 1 },
|
.clone(),
|
||||||
OutfitHandle { index: 2 },
|
ct.outfits
|
||||||
],
|
.get(&ContentIndex::new("shield generator"))
|
||||||
);
|
.unwrap()
|
||||||
|
.clone(),
|
||||||
|
ct.outfits
|
||||||
|
.get(&ContentIndex::new("blaster"))
|
||||||
|
.unwrap()
|
||||||
|
.clone(),
|
||||||
|
]);
|
||||||
|
|
||||||
Game {
|
Game {
|
||||||
ct,
|
ct,
|
||||||
|
@ -111,8 +131,10 @@ impl<'a> Game {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_player_controls(&mut self, player: &mut PlayerAgent) {
|
pub fn apply_directive(&mut self, directive: PlayerDirective, player: &PlayerAgent) {
|
||||||
self.phys_sim.update_player_controls(&self.ct, player)
|
match directive {
|
||||||
|
_ => self.phys_sim.apply_directive(directive, player),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step(&mut self, phys_img: &PhysImage) {
|
pub fn step(&mut self, phys_img: &PhysImage) {
|
||||||
|
|
|
@ -2,13 +2,10 @@ mod game;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use galactica_content::{Content, SystemHandle};
|
use galactica_content::Content;
|
||||||
use galactica_playeragent::{PlayerAgent, PlayerStatus};
|
use galactica_playeragent::PlayerAgent;
|
||||||
use galactica_render::RenderInput;
|
use galactica_render::{InputEvent, RenderInput};
|
||||||
use galactica_system::{
|
use galactica_system::phys::PhysImage;
|
||||||
data::ShipState,
|
|
||||||
phys::{PhysImage, PhysSimShipHandle},
|
|
||||||
};
|
|
||||||
use galactica_util::constants::ASSET_CACHE;
|
use galactica_util::constants::ASSET_CACHE;
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use log4rs::{
|
use log4rs::{
|
||||||
|
@ -17,15 +14,13 @@ use log4rs::{
|
||||||
encode::pattern::PatternEncoder,
|
encode::pattern::PatternEncoder,
|
||||||
Config,
|
Config,
|
||||||
};
|
};
|
||||||
use nalgebra::Vector2;
|
|
||||||
use std::{
|
use std::{
|
||||||
fs,
|
fs,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::Instant,
|
|
||||||
};
|
};
|
||||||
use winit::{
|
use winit::{
|
||||||
event::{Event, KeyboardInput, WindowEvent},
|
event::{ElementState, Event, KeyboardInput, MouseButton, MouseScrollDelta, WindowEvent},
|
||||||
event_loop::{ControlFlow, EventLoop},
|
event_loop::{ControlFlow, EventLoop},
|
||||||
window::WindowBuilder,
|
window::WindowBuilder,
|
||||||
};
|
};
|
||||||
|
@ -128,31 +123,21 @@ fn try_main() -> Result<()> {
|
||||||
let mut game = game::Game::new(content.clone());
|
let mut game = game::Game::new(content.clone());
|
||||||
let p = game.make_player();
|
let p = game.make_player();
|
||||||
|
|
||||||
let mut player = Arc::new(PlayerAgent::new(p.0));
|
let player = Arc::new(PlayerAgent::new(&content, p.0));
|
||||||
Arc::get_mut(&mut player).unwrap().set_camera_aspect(
|
|
||||||
gpu.window().inner_size().width as f32 / gpu.window().inner_size().height as f32,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut phys_img = Arc::new(PhysImage::new());
|
let mut phys_img = Arc::new(PhysImage::new());
|
||||||
let mut last_run = Instant::now();
|
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
match event {
|
match event {
|
||||||
Event::RedrawRequested(window_id) if window_id == gpu.window().id() => {
|
Event::RedrawRequested(window_id) if window_id == gpu.window().id() => {
|
||||||
let render_input = RenderInput {
|
match gpu.render(RenderInput {
|
||||||
camera_pos: player.camera.pos,
|
|
||||||
camera_zoom: player.camera.zoom,
|
|
||||||
current_time: game.get_current_time(),
|
current_time: game.get_current_time(),
|
||||||
ct: content.clone(),
|
ct: content.clone(),
|
||||||
phys_img: phys_img.clone(),
|
phys_img: phys_img.clone(),
|
||||||
player: player.clone(),
|
player: player.clone(),
|
||||||
time_since_last_run: last_run.elapsed().as_secs_f32(),
|
// TODO: this is a hack for testing.
|
||||||
current_system: SystemHandle { index: 0 },
|
current_system: content.systems.values().next().unwrap().clone(),
|
||||||
timing: game.get_timing().clone(),
|
timing: game.get_timing().clone(),
|
||||||
};
|
}) {
|
||||||
last_run = Instant::now();
|
|
||||||
|
|
||||||
match gpu.render(render_input) {
|
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(wgpu::SurfaceError::Lost) => gpu.resize(&content),
|
Err(wgpu::SurfaceError::Lost) => gpu.resize(&content),
|
||||||
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
|
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
|
||||||
|
@ -162,41 +147,8 @@ fn try_main() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::MainEventsCleared => {
|
Event::MainEventsCleared => {
|
||||||
game.update_player_controls(Arc::get_mut(&mut player).unwrap());
|
|
||||||
game.step(&phys_img);
|
game.step(&phys_img);
|
||||||
game.update_image(Arc::get_mut(&mut phys_img).unwrap());
|
game.update_image(Arc::get_mut(&mut phys_img).unwrap());
|
||||||
|
|
||||||
// TODO: clean up
|
|
||||||
let player_status = {
|
|
||||||
let pos = {
|
|
||||||
let o = phys_img.get_ship(&PhysSimShipHandle(player.ship.unwrap()));
|
|
||||||
if let Some(o) = o {
|
|
||||||
match o.ship.get_data().get_state() {
|
|
||||||
ShipState::Landing { .. }
|
|
||||||
| ShipState::UnLanding { .. }
|
|
||||||
| ShipState::Collapsing { .. }
|
|
||||||
| ShipState::Flying { .. } => Some(*o.rigidbody.translation()),
|
|
||||||
|
|
||||||
ShipState::Landed { target } => {
|
|
||||||
let b = content.get_system_object(*target);
|
|
||||||
Some(Vector2::new(b.pos.x, b.pos.y))
|
|
||||||
}
|
|
||||||
|
|
||||||
ShipState::Dead => None,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
PlayerStatus { pos }
|
|
||||||
};
|
|
||||||
|
|
||||||
// This must be updated BEFORE rendering!
|
|
||||||
Arc::get_mut(&mut player)
|
|
||||||
.unwrap()
|
|
||||||
.step(&content, player_status);
|
|
||||||
Arc::get_mut(&mut player).unwrap().input.clear_inputs();
|
|
||||||
gpu.window().request_redraw();
|
gpu.window().request_redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,7 +157,7 @@ fn try_main() -> Result<()> {
|
||||||
window_id,
|
window_id,
|
||||||
} if window_id == gpu.window().id() => match event {
|
} if window_id == gpu.window().id() => match event {
|
||||||
WindowEvent::Focused(_state) => {
|
WindowEvent::Focused(_state) => {
|
||||||
//game.set_paused(!state);
|
// TODO: handle focus loss
|
||||||
}
|
}
|
||||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||||
WindowEvent::KeyboardInput {
|
WindowEvent::KeyboardInput {
|
||||||
|
@ -217,42 +169,94 @@ fn try_main() -> Result<()> {
|
||||||
},
|
},
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
Arc::get_mut(&mut player)
|
let directive = gpu
|
||||||
.unwrap()
|
.process_input(
|
||||||
.input
|
RenderInput {
|
||||||
.process_key(state, key);
|
current_time: game.get_current_time(),
|
||||||
|
ct: content.clone(),
|
||||||
|
phys_img: phys_img.clone(),
|
||||||
|
player: player.clone(),
|
||||||
|
current_system: content.systems.values().next().unwrap().clone(),
|
||||||
|
timing: game.get_timing().clone(),
|
||||||
|
},
|
||||||
|
InputEvent::Keyboard {
|
||||||
|
down: state == &ElementState::Pressed,
|
||||||
|
key: *key,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
game.apply_directive(directive, &player);
|
||||||
}
|
}
|
||||||
WindowEvent::CursorMoved { position, .. } => {
|
WindowEvent::CursorMoved { position, .. } => {
|
||||||
Arc::get_mut(&mut player)
|
let directive = gpu
|
||||||
.unwrap()
|
.process_input(
|
||||||
.input
|
RenderInput {
|
||||||
.process_mouse(position);
|
current_time: game.get_current_time(),
|
||||||
|
ct: content.clone(),
|
||||||
|
phys_img: phys_img.clone(),
|
||||||
|
player: player.clone(),
|
||||||
|
current_system: content.systems.values().next().unwrap().clone(),
|
||||||
|
timing: game.get_timing().clone(),
|
||||||
|
},
|
||||||
|
InputEvent::MouseMove(position.cast()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
game.apply_directive(directive, &player);
|
||||||
}
|
}
|
||||||
WindowEvent::MouseInput { state, button, .. } => {
|
WindowEvent::MouseInput { state, button, .. } => {
|
||||||
Arc::get_mut(&mut player)
|
let down = state == &ElementState::Pressed;
|
||||||
|
let event = match button {
|
||||||
|
MouseButton::Left => Some(InputEvent::MouseLeftClick(down)),
|
||||||
|
MouseButton::Right => Some(InputEvent::MouseRightClick(down)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(event) = event {
|
||||||
|
let directive = gpu
|
||||||
|
.process_input(
|
||||||
|
RenderInput {
|
||||||
|
current_time: game.get_current_time(),
|
||||||
|
ct: content.clone(),
|
||||||
|
phys_img: phys_img.clone(),
|
||||||
|
player: player.clone(),
|
||||||
|
current_system: content
|
||||||
|
.systems
|
||||||
|
.values()
|
||||||
|
.next()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.input
|
.clone(),
|
||||||
.process_click(state, button);
|
timing: game.get_timing().clone(),
|
||||||
|
},
|
||||||
|
event,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
game.apply_directive(directive, &player);
|
||||||
}
|
}
|
||||||
WindowEvent::MouseWheel { delta, phase, .. } => {
|
}
|
||||||
Arc::get_mut(&mut player)
|
WindowEvent::MouseWheel { delta, .. } => {
|
||||||
.unwrap()
|
let directive = gpu
|
||||||
.input
|
.process_input(
|
||||||
.process_scroll(delta, phase);
|
RenderInput {
|
||||||
|
current_time: game.get_current_time(),
|
||||||
|
ct: content.clone(),
|
||||||
|
phys_img: phys_img.clone(),
|
||||||
|
player: player.clone(),
|
||||||
|
current_system: content.systems.values().next().unwrap().clone(),
|
||||||
|
timing: game.get_timing().clone(),
|
||||||
|
},
|
||||||
|
InputEvent::Scroll(match delta {
|
||||||
|
MouseScrollDelta::LineDelta(_h, v) => *v,
|
||||||
|
MouseScrollDelta::PixelDelta(v) => v.x as f32,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
game.apply_directive(directive, &player);
|
||||||
}
|
}
|
||||||
WindowEvent::Resized(_) => {
|
WindowEvent::Resized(_) => {
|
||||||
gpu.resize(&content);
|
gpu.resize(&content);
|
||||||
Arc::get_mut(&mut player).unwrap().set_camera_aspect(
|
|
||||||
gpu.window().inner_size().width as f32
|
|
||||||
/ gpu.window().inner_size().height as f32,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
WindowEvent::ScaleFactorChanged { .. } => {
|
WindowEvent::ScaleFactorChanged { .. } => {
|
||||||
gpu.resize(&content);
|
gpu.resize(&content);
|
||||||
Arc::get_mut(&mut player).unwrap().set_camera_aspect(
|
gpu.window().request_redraw();
|
||||||
gpu.window().inner_size().width as f32
|
|
||||||
/ gpu.window().inner_size().height as f32,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
use nalgebra::Vector2;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct Camera {
|
|
||||||
/// Camera center
|
|
||||||
pub pos: Vector2<f32>,
|
|
||||||
|
|
||||||
/// Camera zoom
|
|
||||||
/// (How many game units tall is the viewport?)
|
|
||||||
pub zoom: f32,
|
|
||||||
|
|
||||||
/// Aspect ratio of viewport (width / height)
|
|
||||||
pub aspect: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Camera {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
pos: Vector2::new(0.0, 0.0),
|
|
||||||
zoom: 500.0,
|
|
||||||
aspect: 1.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,151 +0,0 @@
|
||||||
use winit::{
|
|
||||||
dpi::PhysicalPosition,
|
|
||||||
event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct InputStatus {
|
|
||||||
// Parameters
|
|
||||||
scroll_speed: f32,
|
|
||||||
|
|
||||||
mouse_position: PhysicalPosition<f32>,
|
|
||||||
|
|
||||||
// Continuous keys
|
|
||||||
key_left: bool,
|
|
||||||
key_right: bool,
|
|
||||||
key_thrust: bool,
|
|
||||||
key_guns: bool,
|
|
||||||
key_leftclick: bool,
|
|
||||||
|
|
||||||
// One-shot keys (automatically released at the end of each frame)
|
|
||||||
key_land: bool,
|
|
||||||
v_scroll: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InputStatus {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
InputStatus {
|
|
||||||
key_left: false,
|
|
||||||
key_right: false,
|
|
||||||
key_thrust: false,
|
|
||||||
key_guns: false,
|
|
||||||
key_land: false,
|
|
||||||
key_leftclick: false,
|
|
||||||
mouse_position: PhysicalPosition { x: 0.0, y: 0.0 },
|
|
||||||
v_scroll: 0.0,
|
|
||||||
scroll_speed: 10.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn release_all(&mut self) {
|
|
||||||
self.key_left = false;
|
|
||||||
self.key_right = false;
|
|
||||||
self.key_thrust = false;
|
|
||||||
self.key_guns = false;
|
|
||||||
self.key_land = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called at the end of every frame,
|
|
||||||
/// resets one-shot keys.
|
|
||||||
pub fn clear_inputs(&mut self) {
|
|
||||||
self.key_land = false;
|
|
||||||
self.v_scroll = 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process_key(&mut self, state: &ElementState, key: &VirtualKeyCode) {
|
|
||||||
let down = state == &ElementState::Pressed;
|
|
||||||
match key {
|
|
||||||
VirtualKeyCode::Left => {
|
|
||||||
self.key_left = down;
|
|
||||||
if down {
|
|
||||||
self.key_right = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
VirtualKeyCode::Right => {
|
|
||||||
self.key_right = down;
|
|
||||||
if down {
|
|
||||||
self.key_left = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
VirtualKeyCode::Up => self.key_thrust = down,
|
|
||||||
VirtualKeyCode::Space => self.key_guns = down,
|
|
||||||
VirtualKeyCode::L => self.key_land = down,
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process_mouse(&mut self, position: &PhysicalPosition<f64>) {
|
|
||||||
self.mouse_position = PhysicalPosition {
|
|
||||||
x: position.x as f32,
|
|
||||||
y: position.y as f32,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process_click(&mut self, state: &ElementState, key: &MouseButton) {
|
|
||||||
let down = state == &ElementState::Pressed;
|
|
||||||
match key {
|
|
||||||
MouseButton::Left => self.key_leftclick = down,
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process_scroll(&mut self, delta: &MouseScrollDelta, _phase: &TouchPhase) {
|
|
||||||
match delta {
|
|
||||||
MouseScrollDelta::LineDelta(_h, v) => {
|
|
||||||
self.v_scroll -= self.scroll_speed * v;
|
|
||||||
}
|
|
||||||
// TODO: handle this better
|
|
||||||
MouseScrollDelta::PixelDelta(v) => {
|
|
||||||
self.v_scroll -= v.x as f32;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public get-state methods
|
|
||||||
impl InputStatus {
|
|
||||||
/// Has the player applied vertical scroll?
|
|
||||||
/// This is measured in lines, scaled by scroll_speed
|
|
||||||
///
|
|
||||||
/// A positive value means scroll up, a negative value means scroll down.
|
|
||||||
/// This is reset to zero at the end of each frame.
|
|
||||||
pub fn get_v_scroll(&self) -> f32 {
|
|
||||||
self.v_scroll
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the current mouse position
|
|
||||||
pub fn get_mouse_pos(&self) -> PhysicalPosition<f32> {
|
|
||||||
self.mouse_position
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is the player pressing the "turn left" key?
|
|
||||||
pub fn pressed_left(&self) -> bool {
|
|
||||||
self.key_left
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is the player pressing the "turn right" key?
|
|
||||||
pub fn pressed_right(&self) -> bool {
|
|
||||||
self.key_right
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is the player pressing the "fowards" key?
|
|
||||||
pub fn pressed_thrust(&self) -> bool {
|
|
||||||
self.key_thrust
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is the player pressing the "fire guns" key?
|
|
||||||
pub fn pressed_guns(&self) -> bool {
|
|
||||||
self.key_guns
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is the player pressing the left mouse button?
|
|
||||||
pub fn pressed_leftclick(&self) -> bool {
|
|
||||||
self.key_leftclick
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Has the player pressed the "land" key?
|
|
||||||
/// (One-shot, reset to false at the start of each frame)
|
|
||||||
pub fn pressed_land(&self) -> bool {
|
|
||||||
self.key_land
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +1,5 @@
|
||||||
mod camera;
|
|
||||||
mod inputstatus;
|
|
||||||
mod playeragent;
|
mod playeragent;
|
||||||
mod playerstatus;
|
mod playerstatus;
|
||||||
|
|
||||||
pub use camera::Camera;
|
|
||||||
pub use inputstatus::InputStatus;
|
|
||||||
pub use playeragent::PlayerAgent;
|
pub use playeragent::PlayerAgent;
|
||||||
pub use playerstatus::PlayerStatus;
|
pub use playerstatus::PlayerStatus;
|
||||||
|
|
|
@ -1,25 +1,24 @@
|
||||||
use galactica_content::{Content, SystemHandle, SystemObjectHandle};
|
use galactica_content::{Content, ContentIndex, SystemObject};
|
||||||
use rapier2d::geometry::ColliderHandle;
|
use rapier2d::geometry::ColliderHandle;
|
||||||
|
use std::sync::Arc;
|
||||||
use crate::{camera::Camera, inputstatus::InputStatus, PlayerStatus};
|
|
||||||
|
|
||||||
/// What the player has selected
|
/// What the player has selected
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum PlayerSelection {
|
pub enum PlayerSelection {
|
||||||
/// We have nothing selected
|
/// We have nothing selected
|
||||||
None,
|
None,
|
||||||
|
|
||||||
/// We have a system body selected
|
/// We have a system body selected
|
||||||
OrbitingBody(SystemObjectHandle),
|
OrbitingBody(Arc<SystemObject>),
|
||||||
|
|
||||||
/// We have a ship selected
|
/// We have a ship selected
|
||||||
Ship(ColliderHandle),
|
Ship(ColliderHandle),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlayerSelection {
|
impl PlayerSelection {
|
||||||
pub fn get_planet(&self) -> Option<SystemObjectHandle> {
|
pub fn get_planet(&self) -> Option<&Arc<SystemObject>> {
|
||||||
match self {
|
match self {
|
||||||
Self::OrbitingBody(h) => Some(*h),
|
Self::OrbitingBody(h) => Some(h),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,39 +31,22 @@ pub struct PlayerAgent {
|
||||||
|
|
||||||
/// What the player has selected
|
/// What the player has selected
|
||||||
pub selection: PlayerSelection,
|
pub selection: PlayerSelection,
|
||||||
|
|
||||||
/// This player's camera
|
|
||||||
pub camera: Camera,
|
|
||||||
|
|
||||||
/// What buttons this player is pressing
|
|
||||||
pub input: InputStatus,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlayerAgent {
|
impl PlayerAgent {
|
||||||
pub fn new(ship: ColliderHandle) -> Self {
|
pub fn new(ct: &Content, ship: ColliderHandle) -> Self {
|
||||||
Self {
|
Self {
|
||||||
input: InputStatus::new(),
|
|
||||||
camera: Camera::new(),
|
|
||||||
ship: Some(ship),
|
ship: Some(ship),
|
||||||
selection: PlayerSelection::OrbitingBody(SystemObjectHandle {
|
selection: PlayerSelection::OrbitingBody(
|
||||||
system_handle: SystemHandle { index: 0 },
|
ct.systems
|
||||||
body_index: 1,
|
.values()
|
||||||
}),
|
.next()
|
||||||
}
|
.unwrap()
|
||||||
}
|
.objects
|
||||||
|
.get(&ContentIndex::new("earth"))
|
||||||
pub fn set_camera_aspect(&mut self, v: f32) {
|
.unwrap()
|
||||||
self.camera.aspect = v
|
.clone(),
|
||||||
}
|
),
|
||||||
|
|
||||||
pub fn step(&mut self, ct: &Content, status: PlayerStatus) {
|
|
||||||
if self.input.get_v_scroll() != 0.0 {
|
|
||||||
self.camera.zoom = (self.camera.zoom + self.input.get_v_scroll())
|
|
||||||
.clamp(ct.get_config().zoom_min, ct.get_config().zoom_max);
|
|
||||||
}
|
|
||||||
|
|
||||||
if status.pos.is_some() {
|
|
||||||
self.camera.pos = status.pos.unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bytemuck;
|
use bytemuck;
|
||||||
use galactica_content::Content;
|
use galactica_content::Content;
|
||||||
use galactica_system::data::ShipState;
|
use galactica_system::{data::ShipState, phys::PhysSimShipHandle, PlayerDirective};
|
||||||
use galactica_util::to_radians;
|
use galactica_util::to_radians;
|
||||||
use glyphon::{FontSystem, Resolution, SwashCache, TextAtlas, TextRenderer};
|
use glyphon::{FontSystem, Resolution, SwashCache, TextAtlas, TextRenderer};
|
||||||
use nalgebra::{Point2, Point3};
|
use nalgebra::{Point2, Point3, Vector2};
|
||||||
use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
|
use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
|
||||||
use wgpu;
|
use wgpu;
|
||||||
use winit;
|
use winit::{self};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
globaluniform::{GlobalDataContent, GlobalUniform, ObjectData},
|
globaluniform::{GlobalDataContent, GlobalUniform, ObjectData},
|
||||||
|
@ -17,7 +17,7 @@ use crate::{
|
||||||
texturearray::TextureArray,
|
texturearray::TextureArray,
|
||||||
ui::UiScriptExecutor,
|
ui::UiScriptExecutor,
|
||||||
vertexbuffer::{consts::SPRITE_INDICES, types::ObjectInstance},
|
vertexbuffer::{consts::SPRITE_INDICES, types::ObjectInstance},
|
||||||
RenderInput, RenderState, VertexBuffers,
|
InputEvent, RenderInput, RenderState, VertexBuffers,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A high-level GPU wrapper. Reads game state (via RenderInput), produces pretty pictures.
|
/// A high-level GPU wrapper. Reads game state (via RenderInput), produces pretty pictures.
|
||||||
|
@ -120,21 +120,20 @@ impl GPUState {
|
||||||
glyphon::fontdb::Database::new(),
|
glyphon::fontdb::Database::new(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let conf = ct.get_config();
|
for font in &ct.config.font_files {
|
||||||
for font in &conf.font_files {
|
|
||||||
text_font_system.db_mut().load_font_file(font)?;
|
text_font_system.db_mut().load_font_file(font)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: nice error if no family with this name is found
|
// TODO: nice error if no family with this name is found
|
||||||
text_font_system
|
text_font_system
|
||||||
.db_mut()
|
.db_mut()
|
||||||
.set_sans_serif_family(conf.font_sans.clone());
|
.set_sans_serif_family(ct.config.font_sans.clone());
|
||||||
text_font_system
|
text_font_system
|
||||||
.db_mut()
|
.db_mut()
|
||||||
.set_serif_family(conf.font_serif.clone());
|
.set_serif_family(ct.config.font_serif.clone());
|
||||||
text_font_system
|
text_font_system
|
||||||
.db_mut()
|
.db_mut()
|
||||||
.set_monospace_family(conf.font_mono.clone());
|
.set_monospace_family(ct.config.font_mono.clone());
|
||||||
//text_font_system
|
//text_font_system
|
||||||
// .db_mut()
|
// .db_mut()
|
||||||
// .set_cursive_family(conf.font_cursive.clone());
|
// .set_cursive_family(conf.font_cursive.clone());
|
||||||
|
@ -260,6 +259,8 @@ impl GPUState {
|
||||||
self.config.height = new_size.height;
|
self.config.height = new_size.height;
|
||||||
self.surface.configure(&self.device, &self.config);
|
self.surface.configure(&self.device, &self.config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: this takes a long time. fix!
|
||||||
self.starfield.update_buffer(ct, &mut self.state);
|
self.starfield.update_buffer(ct, &mut self.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,28 +276,64 @@ impl GPUState {
|
||||||
self.starfield.update_buffer(ct, &mut self.state);
|
self.starfield.update_buffer(ct, &mut self.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handle user input
|
||||||
|
pub fn process_input(
|
||||||
|
&mut self,
|
||||||
|
input: RenderInput,
|
||||||
|
event: InputEvent,
|
||||||
|
) -> Result<PlayerDirective> {
|
||||||
|
let input = Arc::new(input);
|
||||||
|
self.ui.process_input(&mut self.state, input, event)
|
||||||
|
}
|
||||||
|
|
||||||
/// Main render function. Draws sprites on a window.
|
/// Main render function. Draws sprites on a window.
|
||||||
pub fn render(&mut self, input: RenderInput) -> Result<(), wgpu::SurfaceError> {
|
pub fn render(&mut self, input: RenderInput) -> Result<(), wgpu::SurfaceError> {
|
||||||
let input = Arc::new(input);
|
let input = Arc::new(input);
|
||||||
|
|
||||||
|
if let Some(ship) = input.player.ship {
|
||||||
|
let o = input.phys_img.get_ship(&PhysSimShipHandle(ship));
|
||||||
|
if let Some(o) = o {
|
||||||
|
match o.ship.get_data().get_state() {
|
||||||
|
ShipState::Landing { .. }
|
||||||
|
| ShipState::UnLanding { .. }
|
||||||
|
| ShipState::Collapsing { .. }
|
||||||
|
| ShipState::Flying { .. } => self
|
||||||
|
.ui
|
||||||
|
.state
|
||||||
|
.borrow_mut()
|
||||||
|
.camera
|
||||||
|
.set_pos(*o.rigidbody.translation()),
|
||||||
|
|
||||||
|
ShipState::Landed { target } => self
|
||||||
|
.ui
|
||||||
|
.state
|
||||||
|
.borrow_mut()
|
||||||
|
.camera
|
||||||
|
.set_pos(Vector2::new(target.pos.x, target.pos.y)),
|
||||||
|
|
||||||
|
ShipState::Dead => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update global values
|
// Update global values
|
||||||
self.state.queue.write_buffer(
|
self.state.queue.write_buffer(
|
||||||
&self.state.global_uniform.data_buffer,
|
&self.state.global_uniform.data_buffer,
|
||||||
0,
|
0,
|
||||||
bytemuck::cast_slice(&[GlobalDataContent {
|
bytemuck::cast_slice(&[GlobalDataContent {
|
||||||
camera_position_x: input.camera_pos.x,
|
camera_position_x: self.ui.state.borrow().camera.get_pos().x,
|
||||||
camera_position_y: input.camera_pos.y,
|
camera_position_y: self.ui.state.borrow().camera.get_pos().y,
|
||||||
camera_zoom: input.camera_zoom,
|
camera_zoom: self.ui.state.borrow().camera.get_zoom(),
|
||||||
camera_zoom_min: input.ct.get_config().zoom_min,
|
camera_zoom_min: input.ct.config.zoom_min,
|
||||||
camera_zoom_max: input.ct.get_config().zoom_max,
|
camera_zoom_max: input.ct.config.zoom_max,
|
||||||
window_size_w: self.state.window_size.width as f32,
|
window_size_w: self.state.window_size.width as f32,
|
||||||
window_size_h: self.state.window_size.height as f32,
|
window_size_h: self.state.window_size.height as f32,
|
||||||
window_scale: self.state.window.scale_factor() as f32,
|
window_scale: self.state.window.scale_factor() as f32,
|
||||||
window_aspect: self.state.window_aspect,
|
window_aspect: self.state.window_aspect,
|
||||||
starfield_sprite: input.ct.get_config().starfield_texture.into(),
|
starfield_sprite: input.ct.config.starfield_texture.into(),
|
||||||
starfield_tile_size: input.ct.get_config().starfield_size,
|
starfield_tile_size: input.ct.config.starfield_size,
|
||||||
starfield_size_min: input.ct.get_config().starfield_min_size,
|
starfield_size_min: input.ct.config.starfield_min_size,
|
||||||
starfield_size_max: input.ct.get_config().starfield_max_size,
|
starfield_size_max: input.ct.config.starfield_max_size,
|
||||||
}]),
|
}]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -339,8 +376,10 @@ impl GPUState {
|
||||||
|
|
||||||
// Game coordinates (relative to camera) of ne and sw corners of screen.
|
// Game coordinates (relative to camera) of ne and sw corners of screen.
|
||||||
// Used to skip off-screen sprites.
|
// Used to skip off-screen sprites.
|
||||||
let clip_ne = Point2::new(-self.state.window_aspect, 1.0) * input.camera_zoom;
|
let clip_ne = Point2::new(-self.state.window_aspect, 1.0)
|
||||||
let clip_sw = Point2::new(self.state.window_aspect, -1.0) * input.camera_zoom;
|
* self.ui.state.borrow().camera.get_zoom();
|
||||||
|
let clip_sw = Point2::new(self.state.window_aspect, -1.0)
|
||||||
|
* self.ui.state.borrow().camera.get_zoom();
|
||||||
|
|
||||||
// Order matters, it determines what is drawn on top.
|
// Order matters, it determines what is drawn on top.
|
||||||
// The order inside ships and projectiles doesn't matter,
|
// The order inside ships and projectiles doesn't matter,
|
||||||
|
@ -425,7 +464,9 @@ impl GPUState {
|
||||||
},
|
},
|
||||||
(*self.ui.state)
|
(*self.ui.state)
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.get_textareas(&input, &self.state.window),
|
.get_textareas(&input, &self.state.window)
|
||||||
|
.iter()
|
||||||
|
.map(|x| x.get_textarea()),
|
||||||
&mut self.state.text_cache,
|
&mut self.state.text_cache,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -468,7 +509,7 @@ impl GPUState {
|
||||||
ship_pos = Point3::new(pos.x, pos.y, 1.0);
|
ship_pos = Point3::new(pos.x, pos.y, 1.0);
|
||||||
let ship_rot = r.rotation();
|
let ship_rot = r.rotation();
|
||||||
ship_ang = ship_rot.angle();
|
ship_ang = ship_rot.angle();
|
||||||
ship_cnt = input.ct.get_ship(s.ship.get_data().get_content());
|
ship_cnt = s.ship.get_data().get_content();
|
||||||
}
|
}
|
||||||
|
|
||||||
ShipState::UnLanding { current_z, .. } | ShipState::Landing { current_z, .. } => {
|
ShipState::UnLanding { current_z, .. } | ShipState::Landing { current_z, .. } => {
|
||||||
|
@ -477,23 +518,23 @@ impl GPUState {
|
||||||
ship_pos = Point3::new(pos.x, pos.y, *current_z);
|
ship_pos = Point3::new(pos.x, pos.y, *current_z);
|
||||||
let ship_rot = r.rotation();
|
let ship_rot = r.rotation();
|
||||||
ship_ang = ship_rot.angle();
|
ship_ang = ship_rot.angle();
|
||||||
ship_cnt = input.ct.get_ship(s.ship.get_data().get_content());
|
ship_cnt = s.ship.get_data().get_content();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Position adjusted for parallax
|
// Position adjusted for parallax
|
||||||
// TODO: adjust parallax for zoom?
|
// TODO: adjust parallax for zoom?
|
||||||
// 1.0 is z-coordinate, which is constant for ships
|
// 1.0 is z-coordinate, which is constant for ships
|
||||||
let pos: Point2<f32> =
|
let pos: Point2<f32> = (Point2::new(ship_pos.x, ship_pos.y)
|
||||||
(Point2::new(ship_pos.x, ship_pos.y) - input.camera_pos) / ship_pos.z;
|
- self.ui.state.borrow().camera.get_pos())
|
||||||
|
/ ship_pos.z;
|
||||||
|
|
||||||
// Game dimensions of this sprite post-scale.
|
// Game dimensions of this sprite post-scale.
|
||||||
// Post-scale width or height, whichever is larger.
|
// Post-scale width or height, whichever is larger.
|
||||||
// This is in game units.
|
// This is in game units.
|
||||||
//
|
//
|
||||||
// We take the maximum to account for rotated sprites.
|
// We take the maximum to account for rotated sprites.
|
||||||
let m =
|
let m = (ship_cnt.size / ship_pos.z) * ship_cnt.sprite.aspect.max(1.0);
|
||||||
(ship_cnt.size / ship_pos.z) * input.ct.get_sprite(ship_cnt.sprite).aspect.max(1.0);
|
|
||||||
|
|
||||||
// Don't draw sprites that are off the screen
|
// Don't draw sprites that are off the screen
|
||||||
if pos.x < screen_clip.0.x - m
|
if pos.x < screen_clip.0.x - m
|
||||||
|
@ -590,14 +631,14 @@ impl GPUState {
|
||||||
// Position adjusted for parallax
|
// Position adjusted for parallax
|
||||||
// TODO: adjust parallax for zoom?
|
// TODO: adjust parallax for zoom?
|
||||||
// 1.0 is z-coordinate, which is constant for projectiles
|
// 1.0 is z-coordinate, which is constant for projectiles
|
||||||
let pos = (proj_pos - input.camera_pos) / 1.0;
|
let pos = (proj_pos - self.ui.state.borrow().camera.get_pos()) / 1.0;
|
||||||
|
|
||||||
// Game dimensions of this sprite post-scale.
|
// Game dimensions of this sprite post-scale.
|
||||||
// Post-scale width or height, whichever is larger.
|
// Post-scale width or height, whichever is larger.
|
||||||
// This is in game units.
|
// This is in game units.
|
||||||
//
|
//
|
||||||
// We take the maximum to account for rotated sprites.
|
// We take the maximum to account for rotated sprites.
|
||||||
let m = (proj_cnt.size / 1.0) * input.ct.get_sprite(proj_cnt.sprite).aspect.max(1.0);
|
let m = (proj_cnt.size / 1.0) * proj_cnt.sprite.aspect.max(1.0);
|
||||||
|
|
||||||
// Don't draw sprites that are off the screen
|
// Don't draw sprites that are off the screen
|
||||||
if pos.x < screen_clip.0.x - m
|
if pos.x < screen_clip.0.x - m
|
||||||
|
@ -641,18 +682,25 @@ impl GPUState {
|
||||||
// NE and SW corners of screen
|
// NE and SW corners of screen
|
||||||
screen_clip: (Point2<f32>, Point2<f32>),
|
screen_clip: (Point2<f32>, Point2<f32>),
|
||||||
) {
|
) {
|
||||||
let system = input.ct.get_system(input.current_system);
|
// TODO: sort once, give objects state
|
||||||
|
|
||||||
for o in &system.objects {
|
// Sort by z-distance. This is important, since these are
|
||||||
|
// rendered in this order. We need far objects to be behind
|
||||||
|
// near objects!
|
||||||
|
let mut v: Vec<_> = input.current_system.objects.values().collect();
|
||||||
|
v.sort_by(|a, b| b.pos.z.total_cmp(&a.pos.z));
|
||||||
|
|
||||||
|
for o in v {
|
||||||
// Position adjusted for parallax
|
// Position adjusted for parallax
|
||||||
let pos: Point2<f32> = (Point2::new(o.pos.x, o.pos.y) - input.camera_pos) / o.pos.z;
|
let pos: Point2<f32> =
|
||||||
|
(Point2::new(o.pos.x, o.pos.y) - self.ui.state.borrow().camera.get_pos()) / o.pos.z;
|
||||||
|
|
||||||
// Game dimensions of this sprite post-scale.
|
// Game dimensions of this sprite post-scale.
|
||||||
// Post-scale width or height, whichever is larger.
|
// Post-scale width or height, whichever is larger.
|
||||||
// This is in game units.
|
// This is in game units.
|
||||||
//
|
//
|
||||||
// We take the maximum to account for rotated sprites.
|
// We take the maximum to account for rotated sprites.
|
||||||
let m = (o.size / o.pos.z) * input.ct.get_sprite(o.sprite).aspect.max(1.0);
|
let m = (o.size / o.pos.z) * o.sprite.aspect.max(1.0);
|
||||||
|
|
||||||
// Don't draw sprites that are off the screen
|
// Don't draw sprites that are off the screen
|
||||||
if pos.x < screen_clip.0.x - m
|
if pos.x < screen_clip.0.x - m
|
||||||
|
@ -680,8 +728,7 @@ impl GPUState {
|
||||||
}]),
|
}]),
|
||||||
);
|
);
|
||||||
|
|
||||||
let sprite = input.ct.get_sprite(o.sprite);
|
let texture_a = o.sprite.get_first_frame(); // ANIMATE
|
||||||
let texture_a = sprite.get_first_frame(); // ANIMATE
|
|
||||||
|
|
||||||
// Push this object's instance
|
// Push this object's instance
|
||||||
self.state.push_object_buffer(ObjectInstance {
|
self.state.push_object_buffer(ObjectInstance {
|
||||||
|
@ -708,19 +755,14 @@ impl GPUState {
|
||||||
// Position adjusted for parallax
|
// Position adjusted for parallax
|
||||||
// TODO: adjust parallax for zoom?
|
// TODO: adjust parallax for zoom?
|
||||||
// 1.0 is z-coordinate, which is constant for projectiles
|
// 1.0 is z-coordinate, which is constant for projectiles
|
||||||
let adjusted_pos = (pos - input.camera_pos) / 1.0;
|
let adjusted_pos = (pos - self.ui.state.borrow().camera.get_pos()) / 1.0;
|
||||||
|
|
||||||
// Game dimensions of this sprite post-scale.
|
// Game dimensions of this sprite post-scale.
|
||||||
// Post-scale width or height, whichever is larger.
|
// Post-scale width or height, whichever is larger.
|
||||||
// This is in game units.
|
// This is in game units.
|
||||||
//
|
//
|
||||||
// We take the maximum to account for rotated sprites.
|
// We take the maximum to account for rotated sprites.
|
||||||
let m = (p.effect.size / 1.0)
|
let m = (p.effect.size / 1.0) * p.effect.anim.get_sprite().aspect.max(1.0);
|
||||||
* input
|
|
||||||
.ct
|
|
||||||
.get_sprite(p.effect.anim.get_sprite())
|
|
||||||
.aspect
|
|
||||||
.max(1.0);
|
|
||||||
|
|
||||||
// Don't draw sprites that are off the screen
|
// Don't draw sprites that are off the screen
|
||||||
if adjusted_pos.x < screen_clip.0.x - m
|
if adjusted_pos.x < screen_clip.0.x - m
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
use winit::{dpi::PhysicalPosition, event::VirtualKeyCode};
|
||||||
|
|
||||||
|
/// Input received from the user
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum InputEvent {
|
||||||
|
/// Mouse was moved
|
||||||
|
MouseMove(PhysicalPosition<f32>),
|
||||||
|
|
||||||
|
/// Mouse left button was clicked.
|
||||||
|
/// True if pressed, false if released.
|
||||||
|
MouseLeftClick(bool),
|
||||||
|
|
||||||
|
/// Mouse left button was clicked.
|
||||||
|
/// True if pressed, false if released.
|
||||||
|
MouseRightClick(bool),
|
||||||
|
|
||||||
|
/// Mouse was scrolled.
|
||||||
|
/// Value is number of lines, positive or negative.
|
||||||
|
Scroll(f32),
|
||||||
|
|
||||||
|
/// A key was pressed or released
|
||||||
|
Keyboard {
|
||||||
|
/// True if pressed, false if released
|
||||||
|
down: bool,
|
||||||
|
|
||||||
|
/// The key that was pressed
|
||||||
|
key: VirtualKeyCode,
|
||||||
|
},
|
||||||
|
}
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
mod globaluniform;
|
mod globaluniform;
|
||||||
mod gpustate;
|
mod gpustate;
|
||||||
|
mod inputevent;
|
||||||
mod pipeline;
|
mod pipeline;
|
||||||
mod renderinput;
|
mod renderinput;
|
||||||
mod renderstate;
|
mod renderstate;
|
||||||
|
@ -18,7 +19,8 @@ mod texturearray;
|
||||||
mod ui;
|
mod ui;
|
||||||
mod vertexbuffer;
|
mod vertexbuffer;
|
||||||
|
|
||||||
pub use gpustate::GPUState;
|
pub use gpustate::*;
|
||||||
|
pub use inputevent::*;
|
||||||
pub use renderinput::RenderInput;
|
pub use renderinput::RenderInput;
|
||||||
use renderstate::*;
|
use renderstate::*;
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,18 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use galactica_content::{Content, SystemHandle};
|
use galactica_content::{Content, System};
|
||||||
use galactica_playeragent::PlayerAgent;
|
use galactica_playeragent::PlayerAgent;
|
||||||
use galactica_system::phys::PhysImage;
|
use galactica_system::phys::PhysImage;
|
||||||
use galactica_util::timing::Timing;
|
use galactica_util::timing::Timing;
|
||||||
use nalgebra::Vector2;
|
|
||||||
|
|
||||||
/// Bundles parameters passed to a single call to GPUState::render
|
/// Bundles parameters passed to a single call to GPUState::render
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RenderInput {
|
pub struct RenderInput {
|
||||||
/// Camera position, in world units
|
|
||||||
pub camera_pos: Vector2<f32>,
|
|
||||||
|
|
||||||
/// Player ship data
|
/// Player ship data
|
||||||
pub player: Arc<PlayerAgent>,
|
pub player: Arc<PlayerAgent>,
|
||||||
|
|
||||||
/// The system we're currently in
|
/// The system we're currently in
|
||||||
pub current_system: SystemHandle,
|
pub current_system: Arc<System>,
|
||||||
|
|
||||||
/// Height of screen, in world units
|
|
||||||
pub camera_zoom: f32,
|
|
||||||
|
|
||||||
/// The world state to render
|
/// The world state to render
|
||||||
pub phys_img: Arc<PhysImage>,
|
pub phys_img: Arc<PhysImage>,
|
||||||
|
@ -28,9 +21,6 @@ pub struct RenderInput {
|
||||||
/// The current time, in seconds
|
/// The current time, in seconds
|
||||||
pub current_time: f32,
|
pub current_time: f32,
|
||||||
|
|
||||||
/// The amount of time that has passed since the last frame was drawn
|
|
||||||
pub time_since_last_run: f32,
|
|
||||||
|
|
||||||
/// Game content
|
/// Game content
|
||||||
pub ct: Arc<Content>,
|
pub ct: Arc<Content>,
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ impl<'a> VertexBuffers {
|
||||||
ui_counter: 0,
|
ui_counter: 0,
|
||||||
radialbar_counter: 0,
|
radialbar_counter: 0,
|
||||||
starfield_counter: 0,
|
starfield_counter: 0,
|
||||||
starfield_limit: ct.get_config().starfield_instance_limit,
|
starfield_limit: ct.config.starfield_instance_limit,
|
||||||
|
|
||||||
object: VertexBuffer::new::<TexturedVertex, ObjectInstance>(
|
object: VertexBuffer::new::<TexturedVertex, ObjectInstance>(
|
||||||
"object",
|
"object",
|
||||||
|
@ -54,7 +54,7 @@ impl<'a> VertexBuffers {
|
||||||
&device,
|
&device,
|
||||||
Some(SPRITE_VERTICES),
|
Some(SPRITE_VERTICES),
|
||||||
Some(SPRITE_INDICES),
|
Some(SPRITE_INDICES),
|
||||||
ct.get_config().starfield_instance_limit,
|
ct.config.starfield_instance_limit,
|
||||||
),
|
),
|
||||||
|
|
||||||
ui: VertexBuffer::new::<TexturedVertex, UiInstance>(
|
ui: VertexBuffer::new::<TexturedVertex, UiInstance>(
|
||||||
|
|
|
@ -30,36 +30,32 @@ impl Starfield {
|
||||||
pub fn regenerate(&mut self, ct: &Content) {
|
pub fn regenerate(&mut self, ct: &Content) {
|
||||||
// TODO: save seed in system, regenerate on jump
|
// TODO: save seed in system, regenerate on jump
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let sz = ct.get_config().starfield_size as f32 / 2.0;
|
let sz = ct.config.starfield_size as f32 / 2.0;
|
||||||
self.stars = (0..ct.get_config().starfield_count)
|
self.stars = (0..ct.config.starfield_count)
|
||||||
.map(|_| StarfieldStar {
|
.map(|_| StarfieldStar {
|
||||||
pos: Point3::new(
|
pos: Point3::new(
|
||||||
rng.gen_range(-sz..=sz),
|
rng.gen_range(-sz..=sz),
|
||||||
rng.gen_range(-sz..=sz),
|
rng.gen_range(-sz..=sz),
|
||||||
rng.gen_range(
|
rng.gen_range(ct.config.starfield_min_dist..=ct.config.starfield_max_dist),
|
||||||
ct.get_config().starfield_min_dist..=ct.get_config().starfield_max_dist,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
size: rng.gen_range(
|
|
||||||
ct.get_config().starfield_min_size..ct.get_config().starfield_max_size,
|
|
||||||
),
|
),
|
||||||
|
size: rng.gen_range(ct.config.starfield_min_size..ct.config.starfield_max_size),
|
||||||
tint: Vector2::new(rng.gen_range(0.0..=1.0), rng.gen_range(0.0..=1.0)),
|
tint: Vector2::new(rng.gen_range(0.0..=1.0), rng.gen_range(0.0..=1.0)),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_buffer(&mut self, ct: &Content, state: &mut RenderState) {
|
pub fn update_buffer(&mut self, ct: &Content, state: &mut RenderState) {
|
||||||
let sz = ct.get_config().starfield_size as f32;
|
let sz = ct.config.starfield_size as f32;
|
||||||
|
|
||||||
// Compute window size in starfield tiles
|
// Compute window size in starfield tiles
|
||||||
let mut nw_tile = {
|
let mut nw_tile = {
|
||||||
// Game coordinates (relative to camera) of nw corner of screen.
|
// Game coordinates (relative to camera) of nw corner of screen.
|
||||||
let clip_nw = Point2::new(state.window_aspect, 1.0) * ct.get_config().zoom_max;
|
let clip_nw = Point2::new(state.window_aspect, 1.0) * ct.config.zoom_max;
|
||||||
|
|
||||||
// Parallax correction.
|
// Parallax correction.
|
||||||
// Also, adjust v for mod to work properly
|
// Also, adjust v for mod to work properly
|
||||||
// (v is centered at 0)
|
// (v is centered at 0)
|
||||||
let v: Point2<f32> = clip_nw * ct.get_config().starfield_min_dist;
|
let v: Point2<f32> = clip_nw * ct.config.starfield_min_dist;
|
||||||
let v_adj = Point2::new(v.x + (sz / 2.0), v.y + (sz / 2.0));
|
let v_adj = Point2::new(v.x + (sz / 2.0), v.y + (sz / 2.0));
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
|
@ -83,8 +79,8 @@ impl Starfield {
|
||||||
|
|
||||||
// Truncate tile grid to buffer size
|
// Truncate tile grid to buffer size
|
||||||
// (The window won't be full of stars if our instance limit is too small)
|
// (The window won't be full of stars if our instance limit is too small)
|
||||||
while ((nw_tile.x * 2 + 1) * (nw_tile.y * 2 + 1) * ct.get_config().starfield_count as i32)
|
while ((nw_tile.x * 2 + 1) * (nw_tile.y * 2 + 1) * ct.config.starfield_count as i32)
|
||||||
> ct.get_config().starfield_instance_limit as i32
|
> ct.config.starfield_instance_limit as i32
|
||||||
{
|
{
|
||||||
nw_tile -= Vector2::new(1, 1);
|
nw_tile -= Vector2::new(1, 1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
use rhai::{plugin::*, Dynamic, Module};
|
||||||
|
|
||||||
|
#[export_module]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
pub mod player_directive_module {
|
||||||
|
use galactica_system::PlayerDirective;
|
||||||
|
|
||||||
|
pub const None: PlayerDirective = PlayerDirective::None;
|
||||||
|
pub const Land: PlayerDirective = PlayerDirective::Land;
|
||||||
|
pub const UnLand: PlayerDirective = PlayerDirective::UnLand;
|
||||||
|
|
||||||
|
pub fn Engine(state: bool) -> PlayerDirective {
|
||||||
|
PlayerDirective::Engine(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn TurnLeft(state: bool) -> PlayerDirective {
|
||||||
|
PlayerDirective::TurnLeft(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn TurnRight(state: bool) -> PlayerDirective {
|
||||||
|
PlayerDirective::TurnRight(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Guns(state: bool) -> PlayerDirective {
|
||||||
|
PlayerDirective::Guns(state)
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,3 +38,31 @@ impl CustomType for PlayerShipStateEvent {
|
||||||
builder.with_name("PlayerShipStateEvent");
|
builder.with_name("PlayerShipStateEvent");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct KeyboardEvent {
|
||||||
|
pub down: bool,
|
||||||
|
pub key: ImmutableString,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CustomType for KeyboardEvent {
|
||||||
|
fn build(mut builder: TypeBuilder<Self>) {
|
||||||
|
builder
|
||||||
|
.with_name("KeyboardEvent")
|
||||||
|
.with_fn("is_down", |s: &mut Self| s.down)
|
||||||
|
.with_fn("key", |s: &mut Self| s.key.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ScrollEvent {
|
||||||
|
pub val: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CustomType for ScrollEvent {
|
||||||
|
fn build(mut builder: TypeBuilder<Self>) {
|
||||||
|
builder
|
||||||
|
.with_name("ScrollEvent")
|
||||||
|
.with_fn("val", |s: &mut Self| s.val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
mod conf;
|
mod conf;
|
||||||
mod radialbar;
|
mod radialbar;
|
||||||
|
mod scrollbox;
|
||||||
mod sprite;
|
mod sprite;
|
||||||
mod textbox;
|
mod textbox;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
pub use conf::build_conf_module;
|
pub use conf::build_conf_module;
|
||||||
pub use radialbar::build_radialbar_module;
|
pub use radialbar::build_radialbar_module;
|
||||||
|
pub use scrollbox::build_scrollbox_module;
|
||||||
pub use sprite::build_sprite_module;
|
pub use sprite::build_sprite_module;
|
||||||
pub use textbox::build_textbox_module;
|
pub use textbox::build_textbox_module;
|
||||||
pub use ui::build_ui_module;
|
pub use ui::build_ui_module;
|
||||||
|
|
|
@ -3,7 +3,7 @@ use rhai::{FnNamespace, FuncRegistration, ImmutableString, Module};
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
use super::super::{Color, Rect};
|
use super::super::{Color, Rect};
|
||||||
use crate::ui::{elements::RadialBar, UiElement, UiState};
|
use crate::ui::{elements::UiRadialBar, UiElement, UiState};
|
||||||
|
|
||||||
pub fn build_radialbar_module(state_src: Rc<RefCell<UiState>>) -> Module {
|
pub fn build_radialbar_module(state_src: Rc<RefCell<UiState>>) -> Module {
|
||||||
let mut module = Module::new();
|
let mut module = Module::new();
|
||||||
|
@ -23,11 +23,13 @@ pub fn build_radialbar_module(state_src: Rc<RefCell<UiState>>) -> Module {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui_state.names.push(name.clone());
|
ui_state.add_element(UiElement::RadialBar(UiRadialBar::new(
|
||||||
ui_state.elements.insert(
|
|
||||||
name.clone(),
|
name.clone(),
|
||||||
UiElement::RadialBar(RadialBar::new(name.clone(), stroke, color, rect, 1.0)),
|
stroke,
|
||||||
);
|
color,
|
||||||
|
rect,
|
||||||
|
1.0,
|
||||||
|
)));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
use log::error;
|
||||||
|
use rhai::{FnNamespace, FuncRegistration, ImmutableString, Module};
|
||||||
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
|
use super::super::Rect;
|
||||||
|
use crate::ui::{elements::UiScrollbox, UiElement, UiState};
|
||||||
|
|
||||||
|
pub fn build_scrollbox_module(state_src: Rc<RefCell<UiState>>) -> Module {
|
||||||
|
let mut module = Module::new();
|
||||||
|
module.set_id("GalacticaScrollboxModule");
|
||||||
|
|
||||||
|
let state = state_src.clone();
|
||||||
|
let _ = FuncRegistration::new("add")
|
||||||
|
.with_namespace(FnNamespace::Internal)
|
||||||
|
.set_into_module(&mut module, move |name: ImmutableString, rect: Rect| {
|
||||||
|
let mut ui_state = state.borrow_mut();
|
||||||
|
|
||||||
|
if ui_state.elements.contains_key(&name) {
|
||||||
|
error!("tried to make a scrollbox using an existing name `{name}`");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui_state.add_element(UiElement::Scrollbox(UiScrollbox::new(name.clone(), rect)));
|
||||||
|
});
|
||||||
|
|
||||||
|
let state = state_src.clone();
|
||||||
|
let _ = FuncRegistration::new("add_element")
|
||||||
|
.with_namespace(FnNamespace::Internal)
|
||||||
|
.set_into_module(
|
||||||
|
&mut module,
|
||||||
|
move |name: ImmutableString, target: ImmutableString| {
|
||||||
|
let mut ui_state = state.borrow_mut();
|
||||||
|
match ui_state.get_mut_by_name(&name) {
|
||||||
|
Some(UiElement::Scrollbox(_)) => {
|
||||||
|
match ui_state.get_mut_by_name(&target) {
|
||||||
|
Some(UiElement::Text(_)) | Some(UiElement::Sprite(_)) => {
|
||||||
|
let e = match ui_state.remove_element_incomplete(&target) {
|
||||||
|
Some(UiElement::Sprite(s)) => {
|
||||||
|
Rc::new(RefCell::new(UiElement::Sprite(s)))
|
||||||
|
}
|
||||||
|
Some(UiElement::Text(t)) => {
|
||||||
|
Rc::new(RefCell::new(UiElement::Text(t)))
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add a subelement pointing to this sprite
|
||||||
|
ui_state.add_element(UiElement::SubElement {
|
||||||
|
parent: name.clone(),
|
||||||
|
element: e.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add this sprite to a scrollbox
|
||||||
|
match ui_state.get_mut_by_name(&name) {
|
||||||
|
Some(UiElement::Scrollbox(s)) => {
|
||||||
|
s.add_element(e);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Some(_) => {
|
||||||
|
error!("cannot add `{name}` to scrollbox `{name}`, invalid type.")
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
error!("called `scrollbox::add_element` with a non-existing target `{target}`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
error!("called `scrollbox::add_element` on an invalid name `{name}`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return module;
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ use log::error;
|
||||||
use rhai::{FnNamespace, FuncRegistration, ImmutableString, Module};
|
use rhai::{FnNamespace, FuncRegistration, ImmutableString, Module};
|
||||||
use std::{cell::RefCell, rc::Rc, sync::Arc};
|
use std::{cell::RefCell, rc::Rc, sync::Arc};
|
||||||
|
|
||||||
use crate::ui::{elements::Sprite, UiElement, UiState};
|
use crate::ui::{elements::UiSprite, UiElement, UiState};
|
||||||
|
|
||||||
use super::super::{Color, Rect};
|
use super::super::{Color, Rect};
|
||||||
|
|
||||||
|
@ -17,12 +17,12 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
|
||||||
.with_namespace(FnNamespace::Internal)
|
.with_namespace(FnNamespace::Internal)
|
||||||
.set_into_module(
|
.set_into_module(
|
||||||
&mut module,
|
&mut module,
|
||||||
move |name: ImmutableString, sprite: ImmutableString, rect: Rect| {
|
move |name: ImmutableString, sprite_name: ImmutableString, rect: Rect| {
|
||||||
let mut ui_state = state.borrow_mut();
|
let mut ui_state = state.borrow_mut();
|
||||||
|
|
||||||
let sprite_handle = ct.get_sprite_handle(sprite.as_str());
|
let sprite = ct.sprites.get(&sprite_name.clone().into());
|
||||||
if sprite_handle.is_none() {
|
if sprite.is_none() {
|
||||||
error!("made a sprite using an invalid source `{sprite}`");
|
error!("made a sprite using an invalid source `{sprite_name}`");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,11 +31,11 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui_state.names.push(name.clone());
|
ui_state.add_element(UiElement::Sprite(UiSprite::new(
|
||||||
ui_state.elements.insert(
|
|
||||||
name.clone(),
|
name.clone(),
|
||||||
UiElement::Sprite(Sprite::new(&ct, name.clone(), sprite_handle.unwrap(), rect)),
|
sprite.unwrap().clone(),
|
||||||
);
|
rect,
|
||||||
|
)));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -56,8 +56,7 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
|
||||||
.set_into_module(&mut module, move |name: ImmutableString| {
|
.set_into_module(&mut module, move |name: ImmutableString| {
|
||||||
let mut ui_state = state.borrow_mut();
|
let mut ui_state = state.borrow_mut();
|
||||||
if ui_state.elements.contains_key(&name) {
|
if ui_state.elements.contains_key(&name) {
|
||||||
ui_state.elements.remove(&name).unwrap();
|
ui_state.remove_element(&name);
|
||||||
ui_state.names.retain(|x| *x != name);
|
|
||||||
} else {
|
} else {
|
||||||
error!("called `sprite::remove` on an invalid name `{name}`")
|
error!("called `sprite::remove` on an invalid name `{name}`")
|
||||||
}
|
}
|
||||||
|
@ -74,12 +73,12 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
|
||||||
|
|
||||||
match ui_state.get_mut_by_name(&name) {
|
match ui_state.get_mut_by_name(&name) {
|
||||||
Some(UiElement::Sprite(x)) => {
|
Some(UiElement::Sprite(x)) => {
|
||||||
let m = ct.get_sprite_handle(mask.as_str());
|
let m = ct.sprites.get(&mask.clone().into()).clone();
|
||||||
if m.is_none() {
|
if m.is_none() {
|
||||||
error!("called `set_sprite_mask` with an invalid mask `{mask}`");
|
error!("called `set_sprite_mask` with an invalid mask `{mask}`");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
x.set_mask(m)
|
x.set_mask(m.cloned())
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -90,8 +89,7 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
|
||||||
);
|
);
|
||||||
|
|
||||||
let state = state_src.clone();
|
let state = state_src.clone();
|
||||||
let ct = ct_src.clone();
|
let _ = FuncRegistration::new("jump_to")
|
||||||
let _ = FuncRegistration::new("take_edge")
|
|
||||||
.with_namespace(FnNamespace::Internal)
|
.with_namespace(FnNamespace::Internal)
|
||||||
.set_into_module(
|
.set_into_module(
|
||||||
&mut module,
|
&mut module,
|
||||||
|
@ -100,27 +98,25 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
|
||||||
|
|
||||||
match ui_state.get_mut_by_name(&name) {
|
match ui_state.get_mut_by_name(&name) {
|
||||||
Some(UiElement::Sprite(x)) => {
|
Some(UiElement::Sprite(x)) => {
|
||||||
let sprite_handle = x.anim.get_sprite();
|
let sprite = x.anim.get_sprite();
|
||||||
let sprite = &ct.get_sprite(sprite_handle);
|
|
||||||
|
|
||||||
let edge = resolve_edge_as_edge(edge_name.as_str(), duration, |x| {
|
let edge =
|
||||||
sprite.get_section_handle_by_name(x)
|
resolve_edge_as_edge(&sprite.sections, edge_name.as_str(), duration);
|
||||||
});
|
|
||||||
let edge = match edge {
|
let edge = match edge {
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
error!(
|
error!(
|
||||||
"called `sprite::take_edge` on an invalid edge `{}` on sprite `{}`",
|
"called `sprite::jump_to` on an invalid edge `{}` on sprite `{}`",
|
||||||
edge_name, sprite.name
|
edge_name, sprite.index
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
};
|
};
|
||||||
|
|
||||||
x.anim.jump_to(&ct, edge);
|
x.anim.jump_to(&edge);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
error!("called `sprite::take_edge` on an invalid name `{name}`")
|
error!("called `sprite::jump_to` on an invalid name `{name}`")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,7 +4,7 @@ use rhai::{FnNamespace, FuncRegistration, ImmutableString, Module};
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
use super::super::{Color, Rect};
|
use super::super::{Color, Rect};
|
||||||
use crate::ui::{elements::TextBox, UiElement, UiState};
|
use crate::ui::{elements::UiTextBox, UiElement, UiState};
|
||||||
|
|
||||||
pub fn build_textbox_module(
|
pub fn build_textbox_module(
|
||||||
font_src: Rc<RefCell<FontSystem>>,
|
font_src: Rc<RefCell<FontSystem>>,
|
||||||
|
@ -32,18 +32,14 @@ pub fn build_textbox_module(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui_state.names.push(name.clone());
|
ui_state.add_element(UiElement::Text(UiTextBox::new(
|
||||||
ui_state.elements.insert(
|
|
||||||
name.clone(),
|
|
||||||
UiElement::Text(TextBox::new(
|
|
||||||
&mut font.borrow_mut(),
|
&mut font.borrow_mut(),
|
||||||
name.clone(),
|
name.clone(),
|
||||||
font_size,
|
font_size,
|
||||||
line_height,
|
line_height,
|
||||||
rect,
|
rect,
|
||||||
color,
|
color,
|
||||||
)),
|
)));
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -15,5 +15,17 @@ pub fn build_ui_module(state_src: Rc<RefCell<UiState>>) -> Module {
|
||||||
ui_state.set_scene(scene);
|
ui_state.set_scene(scene);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let state = state_src.clone();
|
||||||
|
let _ = FuncRegistration::new("get_camera_zoom")
|
||||||
|
.with_namespace(FnNamespace::Internal)
|
||||||
|
.set_into_module(&mut module, move || state.borrow().camera.get_zoom());
|
||||||
|
|
||||||
|
let state = state_src.clone();
|
||||||
|
let _ = FuncRegistration::new("set_camera_zoom")
|
||||||
|
.with_namespace(FnNamespace::Internal)
|
||||||
|
.set_into_module(&mut module, move |z: f32| {
|
||||||
|
state.borrow_mut().camera.set_zoom(z)
|
||||||
|
});
|
||||||
|
|
||||||
return module;
|
return module;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
use nalgebra::{Point2, Vector2};
|
use nalgebra::{Point2, Vector2};
|
||||||
use rhai::{CustomType, TypeBuilder};
|
use rhai::{CustomType, TypeBuilder};
|
||||||
use winit::{dpi::LogicalSize, window::Window};
|
use winit::{
|
||||||
|
dpi::{LogicalSize, PhysicalPosition},
|
||||||
|
window::Window,
|
||||||
|
};
|
||||||
|
|
||||||
use super::anchor::Anchor;
|
use super::{anchor::Anchor, vector::UiVector};
|
||||||
use crate::{RenderInput, RenderState};
|
use crate::RenderState;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Rect {
|
pub struct Rect {
|
||||||
|
@ -78,7 +81,11 @@ impl Rect {
|
||||||
|
|
||||||
impl CustomType for Rect {
|
impl CustomType for Rect {
|
||||||
fn build(mut builder: TypeBuilder<Self>) {
|
fn build(mut builder: TypeBuilder<Self>) {
|
||||||
builder.with_name("Rect").with_fn("Rect", Self::new);
|
builder
|
||||||
|
.with_name("Rect")
|
||||||
|
.with_fn("Rect", Self::new)
|
||||||
|
.with_fn("pos", |s: &mut Self| UiVector::new(s.pos.x, s.pos.y))
|
||||||
|
.with_fn("dim", |s: &mut Self| UiVector::new(s.dim.x, s.dim.y));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,17 +107,16 @@ impl CenteredRect {
|
||||||
return (pt.y < ne.y && pt.y > sw.y) && (pt.x > ne.x && pt.x < sw.x);
|
return (pt.y < ne.y && pt.y > sw.y) && (pt.x > ne.x && pt.x < sw.x);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains_mouse(&self, input: &RenderInput, state: &RenderState) -> bool {
|
pub fn contains_mouse(&self, state: &RenderState, mouse_pos: &PhysicalPosition<f32>) -> bool {
|
||||||
let fac = state.window.scale_factor() as f32;
|
let fac = state.window.scale_factor() as f32;
|
||||||
let window_size = Vector2::new(
|
let window_size = Vector2::new(
|
||||||
state.window_size.width as f32 / fac,
|
state.window_size.width as f32 / fac,
|
||||||
state.window_size.height as f32 / fac,
|
state.window_size.height as f32 / fac,
|
||||||
);
|
);
|
||||||
|
|
||||||
let pos = input.player.input.get_mouse_pos();
|
|
||||||
let mouse_pos = Point2::new(
|
let mouse_pos = Point2::new(
|
||||||
pos.x / fac - window_size.x / 2.0,
|
mouse_pos.x / fac - window_size.x / 2.0,
|
||||||
window_size.y / 2.0 - pos.y / fac,
|
window_size.y / 2.0 - mouse_pos.y / fac,
|
||||||
);
|
);
|
||||||
|
|
||||||
return self.contains_point(mouse_pos);
|
return self.contains_point(mouse_pos);
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
|
mod directive;
|
||||||
mod event;
|
mod event;
|
||||||
mod functions;
|
mod functions;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
|
pub use directive::*;
|
||||||
pub use event::*;
|
pub use event::*;
|
||||||
use glyphon::FontSystem;
|
|
||||||
pub use helpers::{anchor::*, color::*, rect::*, vector::*};
|
pub use helpers::{anchor::*, color::*, rect::*, vector::*};
|
||||||
use log::debug;
|
|
||||||
pub use state::*;
|
pub use state::*;
|
||||||
|
|
||||||
use super::UiState;
|
use super::UiState;
|
||||||
use galactica_content::Content;
|
use galactica_content::Content;
|
||||||
use rhai::{exported_module, Dynamic, Engine};
|
use galactica_system::PlayerDirective;
|
||||||
|
use glyphon::FontSystem;
|
||||||
|
use rhai::{exported_module, Engine};
|
||||||
use std::{cell::RefCell, rc::Rc, sync::Arc};
|
use std::{cell::RefCell, rc::Rc, sync::Arc};
|
||||||
|
|
||||||
pub fn register_into_engine(
|
pub fn register_into_engine(
|
||||||
|
@ -29,21 +31,27 @@ pub fn register_into_engine(
|
||||||
.build_type::<State>()
|
.build_type::<State>()
|
||||||
.build_type::<ShipState>()
|
.build_type::<ShipState>()
|
||||||
.build_type::<SystemObjectState>()
|
.build_type::<SystemObjectState>()
|
||||||
|
.build_type::<OutfitState>()
|
||||||
// Events
|
// Events
|
||||||
.build_type::<MouseClickEvent>()
|
.build_type::<MouseClickEvent>()
|
||||||
.build_type::<MouseHoverEvent>()
|
.build_type::<MouseHoverEvent>()
|
||||||
.build_type::<PlayerShipStateEvent>()
|
.build_type::<PlayerShipStateEvent>()
|
||||||
|
.build_type::<KeyboardEvent>()
|
||||||
|
.build_type::<ScrollEvent>()
|
||||||
// Bigger modules
|
// Bigger modules
|
||||||
.register_type_with_name::<Anchor>("Anchor")
|
.register_type_with_name::<Anchor>("Anchor")
|
||||||
.register_static_module("Anchor", exported_module!(anchor_mod).into());
|
.register_static_module("Anchor", exported_module!(anchor_mod).into())
|
||||||
|
.register_type_with_name::<PlayerDirective>("PlayerDirective")
|
||||||
|
.register_static_module(
|
||||||
|
"PlayerDirective",
|
||||||
|
exported_module!(player_directive_module).into(),
|
||||||
|
);
|
||||||
|
|
||||||
// Extra functions
|
// Extra functions
|
||||||
engine.register_fn("print", move |d: Dynamic| {
|
engine.register_fn("clamp", |x: f32, l: f32, h: f32| x.clamp(l, h));
|
||||||
debug!("{:?}", d);
|
|
||||||
});
|
|
||||||
engine.register_fn("clamp", move |x: f32, l: f32, h: f32| x.clamp(l, h));
|
|
||||||
|
|
||||||
// Modules
|
// Modules
|
||||||
|
engine.register_static_module("ui", functions::build_ui_module(state_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(),
|
||||||
|
@ -56,7 +64,10 @@ pub fn register_into_engine(
|
||||||
"radialbar",
|
"radialbar",
|
||||||
functions::build_radialbar_module(state_src.clone()).into(),
|
functions::build_radialbar_module(state_src.clone()).into(),
|
||||||
);
|
);
|
||||||
engine.register_static_module("ui", functions::build_ui_module(state_src.clone()).into());
|
engine.register_static_module(
|
||||||
|
"scrollbox",
|
||||||
|
functions::build_scrollbox_module(state_src.clone()).into(),
|
||||||
|
);
|
||||||
engine.register_static_module(
|
engine.register_static_module(
|
||||||
"conf",
|
"conf",
|
||||||
functions::build_conf_module(state_src.clone()).into(),
|
functions::build_conf_module(state_src.clone()).into(),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use galactica_content::{Ship, SystemObject, SystemObjectHandle};
|
use galactica_content::{Outfit, Ship, SystemObject};
|
||||||
use galactica_system::{
|
use galactica_system::{
|
||||||
data::{self},
|
data::{self},
|
||||||
phys::{objects::PhysShip, PhysSimShipHandle},
|
phys::{objects::PhysShip, PhysSimShipHandle},
|
||||||
|
@ -19,14 +19,15 @@ pub struct ShipState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShipState {
|
impl ShipState {
|
||||||
|
// All functions passed to rhai MUST be mut,
|
||||||
|
// even getters.
|
||||||
fn get_content(&mut self) -> &Ship {
|
fn get_content(&mut self) -> &Ship {
|
||||||
let ship = self
|
let ship = self
|
||||||
.input
|
.input
|
||||||
.phys_img
|
.phys_img
|
||||||
.get_ship(self.ship.as_ref().unwrap())
|
.get_ship(self.ship.as_ref().unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let handle = ship.ship.get_data().get_content();
|
ship.ship.get_data().get_content()
|
||||||
self.input.ct.get_ship(handle)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_ship(&mut self) -> &PhysShip {
|
fn get_ship(&mut self) -> &PhysShip {
|
||||||
|
@ -48,20 +49,13 @@ impl ShipState {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn landed_on(&mut self) -> SystemObjectState {
|
fn landed_on(&mut self) -> SystemObjectState {
|
||||||
let input = self.input.clone();
|
|
||||||
match self.get_ship().get_data().get_state() {
|
match self.get_ship().get_data().get_state() {
|
||||||
data::ShipState::Landed { target } => {
|
data::ShipState::Landed { target } => {
|
||||||
return SystemObjectState {
|
return SystemObjectState {
|
||||||
input,
|
object: Some(target.clone()),
|
||||||
object: Some(*target),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return SystemObjectState {
|
|
||||||
input,
|
|
||||||
object: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_ => return SystemObjectState { object: None },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,8 +83,15 @@ impl CustomType for ShipState {
|
||||||
.with_fn("is_collapsing", |s: &mut Self| {
|
.with_fn("is_collapsing", |s: &mut Self| {
|
||||||
s.get_ship().get_data().get_state().is_collapsing()
|
s.get_ship().get_data().get_state().is_collapsing()
|
||||||
})
|
})
|
||||||
.with_fn("name", |s: &mut Self| s.get_content().name.clone())
|
.with_fn("display_name", |s: &mut Self| {
|
||||||
.with_fn("thumbnail", |s: &mut Self| s.get_content().thumbnail)
|
s.get_content().display_name.clone()
|
||||||
|
})
|
||||||
|
.with_fn("content_index", |s: &mut Self| {
|
||||||
|
s.get_content().display_name.clone()
|
||||||
|
})
|
||||||
|
.with_fn("thumbnail", |s: &mut Self| {
|
||||||
|
s.get_content().thumbnail.clone()
|
||||||
|
})
|
||||||
.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("get_shields", |s: &mut Self| {
|
||||||
s.get_ship().get_data().get_shields()
|
s.get_ship().get_data().get_shields()
|
||||||
|
@ -103,28 +104,58 @@ impl CustomType for ShipState {
|
||||||
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)
|
||||||
.with_fn("get_uid", |s: &mut Self| format!("{}", s.get_ship().uid))
|
.with_fn("phys_uid", |s: &mut Self| format!("{}", s.get_ship().uid))
|
||||||
.with_fn("get_pos", |s: &mut Self| {
|
.with_fn("get_pos", |s: &mut Self| {
|
||||||
let t = s.get_body().translation();
|
let t = s.get_body().translation();
|
||||||
UiVector::new(t.x, t.y)
|
UiVector::new(t.x, t.y)
|
||||||
})
|
})
|
||||||
.with_fn("get_faction_color", |s: &mut Self| {
|
.with_fn("get_faction_color", |s: &mut Self| {
|
||||||
let h = s.get_ship().get_data().get_faction();
|
let h = s.get_ship().get_data().get_faction();
|
||||||
let c = s.input.ct.get_faction(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)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct OutfitState {
|
||||||
|
outfit: Arc<Outfit>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutfitState {}
|
||||||
|
|
||||||
|
impl CustomType for OutfitState {
|
||||||
|
fn build(mut builder: TypeBuilder<Self>) {
|
||||||
|
builder
|
||||||
|
.with_name("OutfitState")
|
||||||
|
.with_fn("display_name", |s: &mut Self| s.outfit.display_name.clone())
|
||||||
|
.with_fn("index", |s: &mut Self| s.outfit.index.to_string())
|
||||||
|
.with_fn("thumbnail", |s: &mut Self| {
|
||||||
|
s.outfit.thumbnail.index.to_string()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SystemObjectState {
|
pub struct SystemObjectState {
|
||||||
object: Option<SystemObjectHandle>,
|
object: Option<Arc<SystemObject>>,
|
||||||
input: Arc<RenderInput>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SystemObjectState {
|
impl SystemObjectState {
|
||||||
fn get_content(&mut self) -> &SystemObject {
|
fn outfitter(&mut self) -> Array {
|
||||||
self.input.ct.get_system_object(self.object.unwrap())
|
let mut a = Array::new();
|
||||||
|
for o in &self
|
||||||
|
.object
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.landable
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.outfitter
|
||||||
|
{
|
||||||
|
a.push(Dynamic::from(OutfitState { outfit: o.clone() }));
|
||||||
|
}
|
||||||
|
return a;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,58 +163,70 @@ impl CustomType for SystemObjectState {
|
||||||
fn build(mut builder: TypeBuilder<Self>) {
|
fn build(mut builder: TypeBuilder<Self>) {
|
||||||
builder
|
builder
|
||||||
.with_name("SystemObjectState")
|
.with_name("SystemObjectState")
|
||||||
|
.with_fn("outfitter", Self::outfitter)
|
||||||
//
|
//
|
||||||
// Get landable name
|
// Get landable name
|
||||||
.with_fn("name", |s: &mut Self| {
|
.with_fn("display_name", |s: &mut Self| {
|
||||||
s.get_content()
|
s.object
|
||||||
.name
|
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|x| x.to_string())
|
.unwrap()
|
||||||
|
.landable
|
||||||
|
.as_ref()
|
||||||
|
.map(|x| x.display_name.clone())
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
error!("UI called `name()` on a system object which doesn't provide one");
|
error!("UI called `name()` on a system object which isn't landable");
|
||||||
"".to_string()
|
"".to_string()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
//
|
//
|
||||||
// Get landable description
|
// Get landable description
|
||||||
.with_fn("desc", |s: &mut Self| {
|
.with_fn("desc", |s: &mut Self| {
|
||||||
s.get_content()
|
s.object
|
||||||
.desc
|
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|x| x.to_string())
|
.unwrap()
|
||||||
|
.landable
|
||||||
|
.as_ref()
|
||||||
|
.map(|x| x.desc.clone())
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
error!("UI called `name()` on a system object which doesn't provide one");
|
error!("UI called `desc()` on a system object which isn't landable");
|
||||||
"".to_string()
|
"".to_string()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
//
|
//
|
||||||
// Get landable landscape image
|
// Get landable landscape image
|
||||||
.with_fn("image", |s: &mut Self| {
|
.with_fn("image", |s: &mut Self| {
|
||||||
let handle = s.get_content().image;
|
s.object
|
||||||
if let Some(handle) = handle {
|
.as_ref()
|
||||||
s.input.ct.get_sprite(handle).name.clone()
|
.unwrap()
|
||||||
} else {
|
.landable
|
||||||
error!("UI called `image()` on a system object which doesn't provide one");
|
.as_ref()
|
||||||
|
.map(|x| x.image.index.to_string())
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
error!("UI called `image()` on a system object which isn't landable");
|
||||||
"".to_string()
|
"".to_string()
|
||||||
}
|
})
|
||||||
})
|
})
|
||||||
.with_fn("is_some", |s: &mut Self| s.object.is_some())
|
.with_fn("is_some", |s: &mut Self| s.object.is_some())
|
||||||
.with_fn("==", |a: &mut Self, b: Self| a.object == b.object)
|
.with_fn("is_landable", |s: &mut Self| {
|
||||||
.with_fn("get_size", |s: &mut Self| s.get_content().size)
|
s.object.as_ref().unwrap().landable.is_some()
|
||||||
.with_fn("get_label", |s: &mut Self| {
|
})
|
||||||
ImmutableString::from(&s.get_content().label)
|
.with_fn("==", |a: &mut Self, b: Self| match (&a.object, &b.object) {
|
||||||
|
(None, _) => false,
|
||||||
|
(_, None) => false,
|
||||||
|
(Some(a), Some(b)) => a.index == b.index,
|
||||||
|
})
|
||||||
|
.with_fn("get_size", |s: &mut Self| s.object.as_ref().unwrap().size)
|
||||||
|
.with_fn("get_index", |s: &mut Self| {
|
||||||
|
ImmutableString::from(s.object.as_ref().unwrap().index.as_str())
|
||||||
})
|
})
|
||||||
.with_fn("get_angle", |s: &mut Self| {
|
.with_fn("get_angle", |s: &mut Self| {
|
||||||
to_degrees(s.get_content().angle)
|
to_degrees(s.object.as_ref().unwrap().angle)
|
||||||
})
|
})
|
||||||
.with_fn("get_pos", |s: &mut Self| {
|
.with_fn("get_pos", |s: &mut Self| {
|
||||||
let t = s.get_content().pos;
|
let t = s.object.as_ref().unwrap().pos;
|
||||||
UiVector::new(t.x, t.y)
|
UiVector::new(t.x, t.y)
|
||||||
})
|
})
|
||||||
.with_fn("get_pos_z", |s: &mut Self| {
|
.with_fn("get_pos_z", |s: &mut Self| s.object.as_ref().unwrap().pos.z);
|
||||||
let t = s.get_content().pos;
|
|
||||||
t.z
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,11 +268,9 @@ impl State {
|
||||||
|
|
||||||
pub fn objects(&mut self) -> Array {
|
pub fn objects(&mut self) -> Array {
|
||||||
let mut a = Array::new();
|
let mut a = Array::new();
|
||||||
let s = self.input.current_system;
|
for (_, o) in &self.input.current_system.objects {
|
||||||
for o in &self.input.ct.get_system(s).objects {
|
|
||||||
a.push(Dynamic::from(SystemObjectState {
|
a.push(Dynamic::from(SystemObjectState {
|
||||||
input: self.input.clone(),
|
object: Some(o.clone()),
|
||||||
object: Some(o.handle),
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
return a;
|
return a;
|
||||||
|
@ -243,7 +284,6 @@ 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("camera_zoom", |s: &mut Self| s.input.camera_zoom);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
use nalgebra::Vector2;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct Camera {
|
||||||
|
/// The position of the camera, in game units
|
||||||
|
pos: Vector2<f32>,
|
||||||
|
|
||||||
|
/// The height of the viewport, in game units.
|
||||||
|
zoom: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Camera {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
pos: Vector2::new(0.0, 0.0),
|
||||||
|
zoom: 500.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_pos(&mut self, pos: Vector2<f32>) {
|
||||||
|
self.pos = pos
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pos(&self) -> Vector2<f32> {
|
||||||
|
self.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_zoom(&mut self, zoom: f32) {
|
||||||
|
self.zoom = zoom
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_zoom(&self) -> f32 {
|
||||||
|
self.zoom
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,13 @@
|
||||||
use glyphon::{Attrs, Buffer, Color, Family, FontSystem, Metrics, Shaping, TextArea, TextBounds};
|
use glyphon::{Attrs, Buffer, Color, Family, FontSystem, Metrics, Shaping, TextBounds};
|
||||||
|
use std::rc::Rc;
|
||||||
use winit::window::Window;
|
use winit::window::Window;
|
||||||
|
|
||||||
use crate::{RenderInput, RenderState};
|
use crate::{RenderInput, RenderState};
|
||||||
|
|
||||||
|
use super::OwnedTextArea;
|
||||||
|
|
||||||
pub(crate) struct FpsIndicator {
|
pub(crate) struct FpsIndicator {
|
||||||
buffer: Buffer,
|
buffer: Rc<Buffer>,
|
||||||
update_counter: u32,
|
update_counter: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +24,7 @@ impl FpsIndicator {
|
||||||
);
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
buffer,
|
buffer: Rc::new(buffer),
|
||||||
update_counter: 0,
|
update_counter: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,29 +32,31 @@ impl FpsIndicator {
|
||||||
|
|
||||||
impl FpsIndicator {
|
impl FpsIndicator {
|
||||||
pub fn step(&mut self, input: &RenderInput, font: &mut FontSystem) {
|
pub fn step(&mut self, input: &RenderInput, font: &mut FontSystem) {
|
||||||
|
let buffer = Rc::get_mut(&mut self.buffer).unwrap();
|
||||||
|
|
||||||
if self.update_counter > 0 {
|
if self.update_counter > 0 {
|
||||||
self.update_counter -= 1;
|
self.update_counter -= 1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.update_counter = 100;
|
self.update_counter = 100;
|
||||||
|
|
||||||
self.buffer.set_text(
|
buffer.set_text(
|
||||||
font,
|
font,
|
||||||
&input.timing.get_string(),
|
&input.timing.get_string(),
|
||||||
Attrs::new().family(Family::Monospace),
|
Attrs::new().family(Family::Monospace),
|
||||||
Shaping::Basic,
|
Shaping::Basic,
|
||||||
);
|
);
|
||||||
self.buffer.shape_until_scroll(font);
|
buffer.shape_until_scroll(font);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b: 'a> FpsIndicator {
|
impl<'a, 'b: 'a> FpsIndicator {
|
||||||
pub fn get_textarea(&'b self, input: &RenderInput, _window: &Window) -> TextArea<'a> {
|
pub fn get_textarea(&'b self, input: &RenderInput, _window: &Window) -> OwnedTextArea {
|
||||||
TextArea {
|
OwnedTextArea {
|
||||||
buffer: &self.buffer,
|
buffer: self.buffer.clone(),
|
||||||
left: 10.0,
|
left: 10.0,
|
||||||
top: 400.0,
|
top: 400.0,
|
||||||
scale: input.ct.get_config().ui_scale,
|
scale: input.ct.config.ui_scale,
|
||||||
bounds: TextBounds {
|
bounds: TextBounds {
|
||||||
left: 10,
|
left: 10,
|
||||||
top: 400,
|
top: 400,
|
||||||
|
|
|
@ -1,9 +1,38 @@
|
||||||
mod fpsindicator;
|
mod fpsindicator;
|
||||||
mod radialbar;
|
mod radialbar;
|
||||||
|
mod scrollbox;
|
||||||
mod sprite;
|
mod sprite;
|
||||||
mod textbox;
|
mod textbox;
|
||||||
|
|
||||||
pub(super) use fpsindicator::*;
|
pub(super) use fpsindicator::*;
|
||||||
pub(super) use radialbar::*;
|
pub(super) use radialbar::*;
|
||||||
|
pub(super) use scrollbox::*;
|
||||||
pub(super) use sprite::*;
|
pub(super) use sprite::*;
|
||||||
pub(super) use textbox::*;
|
pub(super) use textbox::*;
|
||||||
|
|
||||||
|
use glyphon::{Buffer, Color, TextArea, TextBounds};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
/// A hack that lets us easily construct TextAreas
|
||||||
|
/// for [`UiTextBox`]es wrapped in Rcs.
|
||||||
|
pub struct OwnedTextArea {
|
||||||
|
pub buffer: Rc<Buffer>,
|
||||||
|
pub left: f32,
|
||||||
|
pub top: f32,
|
||||||
|
pub scale: f32,
|
||||||
|
pub bounds: TextBounds,
|
||||||
|
pub default_color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OwnedTextArea {
|
||||||
|
pub fn get_textarea(&self) -> TextArea {
|
||||||
|
TextArea {
|
||||||
|
buffer: &self.buffer,
|
||||||
|
top: self.top,
|
||||||
|
left: self.left,
|
||||||
|
scale: self.scale,
|
||||||
|
bounds: self.bounds,
|
||||||
|
default_color: self.default_color,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use super::super::api::Rect;
|
||||||
use crate::{ui::api::Color, vertexbuffer::types::RadialBarInstance, RenderInput, RenderState};
|
use crate::{ui::api::Color, vertexbuffer::types::RadialBarInstance, RenderInput, RenderState};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RadialBar {
|
pub struct UiRadialBar {
|
||||||
pub name: ImmutableString,
|
pub name: ImmutableString,
|
||||||
rect: Rect,
|
rect: Rect,
|
||||||
stroke: f32,
|
stroke: f32,
|
||||||
|
@ -14,7 +14,7 @@ pub struct RadialBar {
|
||||||
progress: f32,
|
progress: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RadialBar {
|
impl UiRadialBar {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
name: ImmutableString,
|
name: ImmutableString,
|
||||||
stroke: f32,
|
stroke: f32,
|
||||||
|
@ -38,16 +38,14 @@ impl RadialBar {
|
||||||
pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) {
|
pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) {
|
||||||
let rect = self
|
let rect = self
|
||||||
.rect
|
.rect
|
||||||
.to_centered(&state.window, input.ct.get_config().ui_scale);
|
.to_centered(&state.window, input.ct.config.ui_scale);
|
||||||
|
|
||||||
state.push_radialbar_buffer(RadialBarInstance {
|
state.push_radialbar_buffer(RadialBarInstance {
|
||||||
position: [rect.pos.x, rect.pos.y],
|
position: [rect.pos.x, rect.pos.y],
|
||||||
diameter: rect.dim.x.min(rect.dim.y),
|
diameter: rect.dim.x.min(rect.dim.y),
|
||||||
stroke: self.stroke * input.ct.get_config().ui_scale,
|
stroke: self.stroke * input.ct.config.ui_scale,
|
||||||
color: self.color.as_array(),
|
color: self.color.as_array(),
|
||||||
angle: self.progress * TAU,
|
angle: self.progress * TAU,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step(&mut self, _input: &RenderInput, _state: &mut RenderState) {}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
use nalgebra::Vector2;
|
||||||
|
use rhai::{Dynamic, ImmutableString};
|
||||||
|
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||||
|
use winit::window::Window;
|
||||||
|
|
||||||
|
use super::{super::api::Rect, OwnedTextArea};
|
||||||
|
use crate::{ui::UiElement, InputEvent, RenderInput, RenderState};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct UiScrollbox {
|
||||||
|
pub name: ImmutableString,
|
||||||
|
pub rect: Rect,
|
||||||
|
pub offset: Vector2<f32>,
|
||||||
|
pub elements: HashMap<ImmutableString, Rc<RefCell<UiElement>>>,
|
||||||
|
|
||||||
|
has_mouse: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiScrollbox {
|
||||||
|
pub fn new(name: ImmutableString, rect: Rect) -> Self {
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
rect,
|
||||||
|
elements: HashMap::new(),
|
||||||
|
offset: Vector2::new(0.0, 0.0),
|
||||||
|
has_mouse: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_element(&mut self, e: Rc<RefCell<UiElement>>) {
|
||||||
|
let name = e.borrow().get_name().clone();
|
||||||
|
self.elements.insert(name, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_element(&mut self, sprite: &ImmutableString) {
|
||||||
|
self.elements.remove(sprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn step(&mut self, t: f32) {
|
||||||
|
for (_name, e) in &self.elements {
|
||||||
|
match &mut *e.clone().borrow_mut() {
|
||||||
|
UiElement::Sprite(sprite) => sprite.step(t),
|
||||||
|
UiElement::RadialBar(_) => {}
|
||||||
|
UiElement::Text(..) => {}
|
||||||
|
UiElement::Scrollbox(..) => {}
|
||||||
|
UiElement::SubElement { .. } => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_event(
|
||||||
|
&mut self,
|
||||||
|
input: &RenderInput,
|
||||||
|
state: &mut RenderState,
|
||||||
|
event: &InputEvent,
|
||||||
|
) -> Option<Dynamic> {
|
||||||
|
let r = self
|
||||||
|
.rect
|
||||||
|
.to_centered(&state.window, input.ct.config.ui_scale);
|
||||||
|
|
||||||
|
// TODO: handle only if used in event()
|
||||||
|
// i.e, scrollable sprites shouldn't break scrollboxes
|
||||||
|
// First, check if this event is captured by any sub-elements
|
||||||
|
for (_, e) in &mut self.elements {
|
||||||
|
let arg = match &mut *e.borrow_mut() {
|
||||||
|
UiElement::Sprite(sprite) => sprite.handle_event(&input, state, &event),
|
||||||
|
UiElement::Scrollbox(sbox) => sbox.handle_event(&input, state, &event),
|
||||||
|
|
||||||
|
UiElement::RadialBar(_) | UiElement::Text(..) => None,
|
||||||
|
|
||||||
|
// Subelements are intentionally skipped,
|
||||||
|
// they should be handled by their parent's `handle_event` method.
|
||||||
|
UiElement::SubElement { .. } => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if arg.is_some() {
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no inner events were captured, handle self events.
|
||||||
|
match event {
|
||||||
|
InputEvent::MouseMove(pos) => {
|
||||||
|
if r.contains_mouse(state, pos) && !self.has_mouse {
|
||||||
|
self.has_mouse = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.contains_mouse(state, pos) && self.has_mouse {
|
||||||
|
self.has_mouse = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InputEvent::Scroll(x) => {
|
||||||
|
if self.has_mouse {
|
||||||
|
self.offset.y -= x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiScrollbox {
|
||||||
|
pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) {
|
||||||
|
for (_name, e) in &self.elements {
|
||||||
|
match &*e.clone().borrow() {
|
||||||
|
UiElement::Sprite(sprite) => {
|
||||||
|
sprite.push_to_buffer_with_offset(input, state, self.offset)
|
||||||
|
}
|
||||||
|
UiElement::RadialBar(..) => {}
|
||||||
|
UiElement::Text(..) => {}
|
||||||
|
UiElement::Scrollbox(..) => {}
|
||||||
|
UiElement::SubElement { .. } => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: don't allocate here
|
||||||
|
impl<'a> UiScrollbox {
|
||||||
|
pub fn get_textareas(&'a self, input: &RenderInput, window: &Window) -> Vec<OwnedTextArea> {
|
||||||
|
let mut v = Vec::with_capacity(32);
|
||||||
|
for e in self.elements.values() {
|
||||||
|
match &*e.clone().borrow() {
|
||||||
|
UiElement::Text(x) => {
|
||||||
|
v.push(x.get_textarea_with_offset(input, window, self.offset))
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +1,18 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::super::api::Rect;
|
use super::super::api::Rect;
|
||||||
use crate::{
|
use crate::{
|
||||||
ui::{api::Color, event::Event},
|
ui::api::{Color, MouseClickEvent, MouseHoverEvent},
|
||||||
vertexbuffer::types::UiInstance,
|
vertexbuffer::types::UiInstance,
|
||||||
RenderInput, RenderState,
|
InputEvent, RenderInput, RenderState,
|
||||||
};
|
};
|
||||||
use galactica_content::{Content, SpriteAutomaton, SpriteHandle};
|
use galactica_content::{Sprite, SpriteAutomaton};
|
||||||
use galactica_util::to_radians;
|
use galactica_util::to_radians;
|
||||||
use rhai::ImmutableString;
|
use nalgebra::Vector2;
|
||||||
|
use rhai::{Dynamic, ImmutableString};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Sprite {
|
pub struct UiSprite {
|
||||||
pub anim: SpriteAutomaton,
|
pub anim: SpriteAutomaton,
|
||||||
pub name: ImmutableString,
|
pub name: ImmutableString,
|
||||||
|
|
||||||
|
@ -21,32 +24,29 @@ pub struct Sprite {
|
||||||
preserve_aspect: bool,
|
preserve_aspect: bool,
|
||||||
|
|
||||||
rect: Rect,
|
rect: Rect,
|
||||||
mask: Option<SpriteHandle>,
|
mask: Option<Arc<Sprite>>,
|
||||||
color: Color,
|
color: Color,
|
||||||
|
|
||||||
/// If true, ignore mouse events until click is released
|
|
||||||
waiting_for_release: bool,
|
|
||||||
has_mouse: bool,
|
has_mouse: bool,
|
||||||
has_click: bool,
|
has_click: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sprite {
|
impl UiSprite {
|
||||||
pub fn new(ct: &Content, name: ImmutableString, sprite: SpriteHandle, rect: Rect) -> Self {
|
pub fn new(name: ImmutableString, sprite: Arc<Sprite>, rect: Rect) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
anim: SpriteAutomaton::new(&ct, sprite),
|
anim: SpriteAutomaton::new(sprite),
|
||||||
rect,
|
rect,
|
||||||
angle: 0.0,
|
angle: 0.0,
|
||||||
color: Color::new(1.0, 1.0, 1.0, 1.0),
|
color: Color::new(1.0, 1.0, 1.0, 1.0),
|
||||||
mask: None,
|
mask: None,
|
||||||
has_mouse: false,
|
has_mouse: false,
|
||||||
has_click: false,
|
has_click: false,
|
||||||
waiting_for_release: false,
|
|
||||||
preserve_aspect: false,
|
preserve_aspect: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_mask(&mut self, mask: Option<SpriteHandle>) {
|
pub fn set_mask(&mut self, mask: Option<Arc<Sprite>>) {
|
||||||
self.mask = mask;
|
self.mask = mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,15 +65,27 @@ impl Sprite {
|
||||||
pub fn set_preserve_aspect(&mut self, preserve_aspect: bool) {
|
pub fn set_preserve_aspect(&mut self, preserve_aspect: bool) {
|
||||||
self.preserve_aspect = preserve_aspect;
|
self.preserve_aspect = preserve_aspect;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiSprite {
|
||||||
pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) {
|
pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) {
|
||||||
|
self.push_to_buffer_with_offset(input, state, Vector2::new(0.0, 0.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_to_buffer_with_offset(
|
||||||
|
&self,
|
||||||
|
input: &RenderInput,
|
||||||
|
state: &mut RenderState,
|
||||||
|
offset: Vector2<f32>,
|
||||||
|
) {
|
||||||
let mut rect = self
|
let mut rect = self
|
||||||
.rect
|
.rect
|
||||||
.to_centered(&state.window, input.ct.get_config().ui_scale);
|
.to_centered(&state.window, input.ct.config.ui_scale);
|
||||||
|
rect.pos += offset;
|
||||||
|
|
||||||
if self.preserve_aspect {
|
if self.preserve_aspect {
|
||||||
let rect_aspect = rect.dim.x / rect.dim.y;
|
let rect_aspect = rect.dim.x / rect.dim.y;
|
||||||
let sprite_aspect = input.ct.get_sprite(self.anim.get_sprite()).aspect;
|
let sprite_aspect = self.anim.get_sprite().aspect;
|
||||||
|
|
||||||
// "wide rect" case => match height, reduce width
|
// "wide rect" case => match height, reduce width
|
||||||
if rect_aspect > sprite_aspect {
|
if rect_aspect > sprite_aspect {
|
||||||
|
@ -97,64 +109,74 @@ impl Sprite {
|
||||||
texture_fade: anim_state.fade,
|
texture_fade: anim_state.fade,
|
||||||
mask_index: self
|
mask_index: self
|
||||||
.mask
|
.mask
|
||||||
|
.as_ref()
|
||||||
.map(|x| {
|
.map(|x| {
|
||||||
let sprite = input.ct.get_sprite(x);
|
let texture_b = x.get_first_frame(); // TODO: animate?
|
||||||
let texture_b = sprite.get_first_frame(); // TODO: animate?
|
|
||||||
[1, texture_b]
|
[1, texture_b]
|
||||||
})
|
})
|
||||||
.unwrap_or([0, 0]),
|
.unwrap_or([0, 0]),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_events(&mut self, input: &RenderInput, state: &mut RenderState) -> Event {
|
pub fn handle_event(
|
||||||
|
&mut self,
|
||||||
|
input: &RenderInput,
|
||||||
|
state: &mut RenderState,
|
||||||
|
event: &InputEvent,
|
||||||
|
) -> Option<Dynamic> {
|
||||||
let r = self
|
let r = self
|
||||||
.rect
|
.rect
|
||||||
.to_centered(&state.window, input.ct.get_config().ui_scale);
|
.to_centered(&state.window, input.ct.config.ui_scale);
|
||||||
|
|
||||||
if self.waiting_for_release && self.has_mouse && !input.player.input.pressed_leftclick() {
|
|
||||||
self.waiting_for_release = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.waiting_for_release
|
|
||||||
&& self.has_mouse
|
|
||||||
&& !self.has_click
|
|
||||||
&& input.player.input.pressed_leftclick()
|
|
||||||
{
|
|
||||||
self.has_click = true;
|
|
||||||
return Event::MouseClick;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.has_mouse && self.has_click && !input.player.input.pressed_leftclick() {
|
|
||||||
self.has_click = false;
|
|
||||||
return Event::MouseRelease;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release mouse when cursor leaves box
|
// Release mouse when cursor leaves box
|
||||||
if self.has_click && !self.has_mouse {
|
if self.has_click && !self.has_mouse {
|
||||||
self.has_click = false;
|
self.has_click = false;
|
||||||
return Event::MouseRelease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.contains_mouse(input, state) && !self.has_mouse {
|
match event {
|
||||||
if input.player.input.pressed_leftclick() {
|
InputEvent::MouseMove(pos) => {
|
||||||
// If we're holding click when the cursor enters,
|
if r.contains_mouse(state, pos) && !self.has_mouse {
|
||||||
// don't trigger the `Click` event.
|
|
||||||
self.waiting_for_release = true;
|
|
||||||
}
|
|
||||||
self.has_mouse = true;
|
self.has_mouse = true;
|
||||||
return Event::MouseHover;
|
return Some(Dynamic::from(MouseHoverEvent {
|
||||||
|
enter: true,
|
||||||
|
element: self.name.clone(),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !r.contains_mouse(input, state) && self.has_mouse {
|
if !r.contains_mouse(state, pos) && self.has_mouse {
|
||||||
self.waiting_for_release = false;
|
|
||||||
self.has_mouse = false;
|
self.has_mouse = false;
|
||||||
return Event::MouseUnhover;
|
return Some(Dynamic::from(MouseHoverEvent {
|
||||||
|
enter: false,
|
||||||
|
element: self.name.clone(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Event::None;
|
InputEvent::MouseLeftClick(pressed) => {
|
||||||
|
if self.has_mouse && !self.has_click && *pressed {
|
||||||
|
self.has_click = true;
|
||||||
|
return Some(Dynamic::from(MouseClickEvent {
|
||||||
|
down: true,
|
||||||
|
element: self.name.clone(),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step(&mut self, input: &RenderInput, _state: &mut RenderState) {
|
if self.has_mouse && self.has_click && !*pressed {
|
||||||
self.anim.step(&input.ct, input.time_since_last_run);
|
self.has_click = false;
|
||||||
|
return Some(Dynamic::from(MouseClickEvent {
|
||||||
|
down: false,
|
||||||
|
element: self.name.clone(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn step(&mut self, t: f32) {
|
||||||
|
self.anim.step(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,28 @@
|
||||||
use glyphon::{
|
use glyphon::{
|
||||||
cosmic_text::Align, Attrs, AttrsOwned, Buffer, Color, FamilyOwned, FontSystem, Metrics,
|
cosmic_text::Align, Attrs, AttrsOwned, Buffer, Color, FamilyOwned, FontSystem, Metrics,
|
||||||
Shaping, Style, TextArea, TextBounds, Weight,
|
Shaping, Style, TextBounds, Weight,
|
||||||
};
|
};
|
||||||
use nalgebra::Vector2;
|
use nalgebra::Vector2;
|
||||||
use rhai::ImmutableString;
|
use rhai::ImmutableString;
|
||||||
|
use std::rc::Rc;
|
||||||
use winit::window::Window;
|
use winit::window::Window;
|
||||||
|
|
||||||
use super::super::api::Rect;
|
use super::{super::api::Rect, OwnedTextArea};
|
||||||
use crate::{ui::api, RenderInput};
|
use crate::{ui::api, RenderInput};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TextBox {
|
pub struct UiTextBox {
|
||||||
pub name: ImmutableString,
|
pub name: ImmutableString,
|
||||||
|
|
||||||
text: String,
|
text: String,
|
||||||
justify: Align,
|
justify: Align,
|
||||||
rect: Rect,
|
rect: Rect,
|
||||||
buffer: Buffer,
|
buffer: Rc<Buffer>,
|
||||||
color: api::Color,
|
color: api::Color,
|
||||||
attrs: AttrsOwned,
|
attrs: AttrsOwned,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextBox {
|
impl UiTextBox {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
font: &mut FontSystem,
|
font: &mut FontSystem,
|
||||||
name: ImmutableString,
|
name: ImmutableString,
|
||||||
|
@ -38,7 +39,7 @@ impl TextBox {
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
rect,
|
rect,
|
||||||
buffer,
|
buffer: Rc::new(buffer),
|
||||||
color,
|
color,
|
||||||
justify: Align::Left,
|
justify: Align::Left,
|
||||||
attrs: AttrsOwned::new(Attrs::new()),
|
attrs: AttrsOwned::new(Attrs::new()),
|
||||||
|
@ -47,14 +48,14 @@ impl TextBox {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reflow(&mut self, font: &mut FontSystem) {
|
fn reflow(&mut self, font: &mut FontSystem) {
|
||||||
self.buffer
|
let buffer = Rc::get_mut(&mut self.buffer).unwrap();
|
||||||
.set_text(font, &self.text, self.attrs.as_attrs(), Shaping::Advanced);
|
buffer.set_text(font, &self.text, self.attrs.as_attrs(), Shaping::Advanced);
|
||||||
|
|
||||||
for l in &mut self.buffer.lines {
|
for l in &mut buffer.lines {
|
||||||
l.set_align(Some(self.justify));
|
l.set_align(Some(self.justify));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.buffer.shape_until_scroll(font);
|
buffer.shape_until_scroll(font);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_text(&mut self, font: &mut FontSystem, text: &str) {
|
pub fn set_text(&mut self, font: &mut FontSystem, text: &str) {
|
||||||
|
@ -84,11 +85,19 @@ impl TextBox {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b: 'a> TextBox {
|
impl<'a, 'b: 'a> UiTextBox {
|
||||||
pub fn get_textarea(&'b self, input: &RenderInput, window: &Window) -> TextArea<'a> {
|
pub fn get_textarea(&'b self, input: &RenderInput, window: &Window) -> OwnedTextArea {
|
||||||
let rect = self
|
self.get_textarea_with_offset(input, window, Vector2::new(0.0, 0.0))
|
||||||
.rect
|
}
|
||||||
.to_centered(window, input.ct.get_config().ui_scale);
|
|
||||||
|
pub fn get_textarea_with_offset(
|
||||||
|
&'b self,
|
||||||
|
input: &RenderInput,
|
||||||
|
window: &Window,
|
||||||
|
offset: Vector2<f32>,
|
||||||
|
) -> OwnedTextArea {
|
||||||
|
let mut rect = self.rect.to_centered(window, input.ct.config.ui_scale);
|
||||||
|
rect.pos += offset;
|
||||||
|
|
||||||
// Glypon works with physical pixels, so we must do some conversion
|
// Glypon works with physical pixels, so we must do some conversion
|
||||||
let fac = window.scale_factor() as f32;
|
let fac = window.scale_factor() as f32;
|
||||||
|
@ -99,11 +108,11 @@ impl<'a, 'b: 'a> TextBox {
|
||||||
let corner_sw = corner_ne + rect.dim * fac;
|
let corner_sw = corner_ne + rect.dim * fac;
|
||||||
let c = self.color.as_array_u8();
|
let c = self.color.as_array_u8();
|
||||||
|
|
||||||
TextArea {
|
OwnedTextArea {
|
||||||
buffer: &self.buffer,
|
buffer: self.buffer.clone(),
|
||||||
top: corner_ne.y,
|
top: corner_ne.y,
|
||||||
left: corner_ne.x,
|
left: corner_ne.x,
|
||||||
scale: input.ct.get_config().ui_scale,
|
scale: input.ct.config.ui_scale,
|
||||||
bounds: TextBounds {
|
bounds: TextBounds {
|
||||||
top: (corner_ne.y) as i32,
|
top: (corner_ne.y) as i32,
|
||||||
bottom: (corner_sw.y) as i32,
|
bottom: (corner_sw.y) as i32,
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
pub enum Event {
|
|
||||||
None,
|
|
||||||
MouseClick,
|
|
||||||
MouseRelease,
|
|
||||||
MouseHover,
|
|
||||||
MouseUnhover,
|
|
||||||
}
|
|
|
@ -1,20 +1,17 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use galactica_content::Content;
|
use galactica_content::Content;
|
||||||
use galactica_system::phys::PhysSimShipHandle;
|
use galactica_system::{phys::PhysSimShipHandle, PlayerDirective};
|
||||||
use galactica_util::rhai_error_to_anyhow;
|
use galactica_util::rhai_error_to_anyhow;
|
||||||
use log::debug;
|
use log::{debug, error};
|
||||||
use rhai::{
|
use rhai::{Dynamic, Engine, ImmutableString, Scope};
|
||||||
packages::{BasicArrayPackage, BasicStringPackage, LogicPackage, MoreStringPackage, Package},
|
|
||||||
Dynamic, Engine, ImmutableString, Scope,
|
|
||||||
};
|
|
||||||
use std::{cell::RefCell, num::NonZeroU32, rc::Rc, sync::Arc};
|
use std::{cell::RefCell, num::NonZeroU32, rc::Rc, sync::Arc};
|
||||||
|
use winit::event::VirtualKeyCode;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
api::{self, MouseClickEvent, MouseHoverEvent, PlayerShipStateEvent},
|
api::{self, KeyboardEvent, PlayerShipStateEvent, ScrollEvent},
|
||||||
event::Event,
|
|
||||||
UiConfig, UiElement, UiState,
|
UiConfig, UiElement, UiState,
|
||||||
};
|
};
|
||||||
use crate::{ui::api::State, RenderInput, RenderState};
|
use crate::{ui::api::State, InputEvent, RenderInput, RenderState};
|
||||||
|
|
||||||
pub(crate) struct UiScriptExecutor {
|
pub(crate) struct UiScriptExecutor {
|
||||||
engine: Engine,
|
engine: Engine,
|
||||||
|
@ -31,14 +28,8 @@ impl UiScriptExecutor {
|
||||||
let scope = Scope::new();
|
let scope = Scope::new();
|
||||||
let elements = Rc::new(RefCell::new(UiState::new(ct.clone(), state)));
|
let elements = Rc::new(RefCell::new(UiState::new(ct.clone(), state)));
|
||||||
|
|
||||||
let mut engine = Engine::new_raw();
|
// TODO: document all functions rhai provides
|
||||||
|
let mut engine = Engine::new();
|
||||||
// Required for array iteration
|
|
||||||
// We may need to add more packages here later.
|
|
||||||
engine.register_global_module(BasicArrayPackage::new().as_shared_module());
|
|
||||||
engine.register_global_module(LogicPackage::new().as_shared_module());
|
|
||||||
engine.register_global_module(BasicStringPackage::new().as_shared_module());
|
|
||||||
engine.register_global_module(MoreStringPackage::new().as_shared_module());
|
|
||||||
|
|
||||||
engine.set_max_expr_depths(0, 0);
|
engine.set_max_expr_depths(0, 0);
|
||||||
// Enables custom operators
|
// Enables custom operators
|
||||||
|
@ -64,6 +55,129 @@ impl UiScriptExecutor {
|
||||||
(*self.state).borrow().config.clone()
|
(*self.state).borrow().config.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn process_input(
|
||||||
|
&mut self,
|
||||||
|
state: &mut RenderState,
|
||||||
|
input: Arc<RenderInput>,
|
||||||
|
event: InputEvent,
|
||||||
|
) -> Result<PlayerDirective> {
|
||||||
|
let current_scene = (*self.state).borrow().get_scene().clone();
|
||||||
|
if current_scene.is_none() {
|
||||||
|
return Ok(PlayerDirective::None);
|
||||||
|
}
|
||||||
|
let mut arg: Option<Dynamic> = None;
|
||||||
|
|
||||||
|
// First, check if this event is captured by any ui elements.
|
||||||
|
for (_, e) in &mut self.state.borrow_mut().elements {
|
||||||
|
arg = match e {
|
||||||
|
UiElement::Sprite(sprite) => sprite.handle_event(&input, state, &event),
|
||||||
|
UiElement::Scrollbox(sbox) => sbox.handle_event(&input, state, &event),
|
||||||
|
|
||||||
|
UiElement::RadialBar(_) | UiElement::Text(..) => None,
|
||||||
|
|
||||||
|
// Subelements are intentionally skipped,
|
||||||
|
// they should be handled by their parent's `handle_event` method.
|
||||||
|
UiElement::SubElement { .. } => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if arg.is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If nothing was caught, check global events
|
||||||
|
if arg.is_none() {
|
||||||
|
arg = match event {
|
||||||
|
InputEvent::Scroll(val) => Some(Dynamic::from(ScrollEvent { val })),
|
||||||
|
InputEvent::Keyboard { down, key } => {
|
||||||
|
let str = match key {
|
||||||
|
VirtualKeyCode::A => Some("A"),
|
||||||
|
VirtualKeyCode::B => Some("B"),
|
||||||
|
VirtualKeyCode::C => Some("C"),
|
||||||
|
VirtualKeyCode::D => Some("D"),
|
||||||
|
VirtualKeyCode::E => Some("E"),
|
||||||
|
VirtualKeyCode::F => Some("F"),
|
||||||
|
VirtualKeyCode::G => Some("G"),
|
||||||
|
VirtualKeyCode::H => Some("H"),
|
||||||
|
VirtualKeyCode::I => Some("I"),
|
||||||
|
VirtualKeyCode::J => Some("J"),
|
||||||
|
VirtualKeyCode::K => Some("K"),
|
||||||
|
VirtualKeyCode::L => Some("L"),
|
||||||
|
VirtualKeyCode::M => Some("M"),
|
||||||
|
VirtualKeyCode::N => Some("N"),
|
||||||
|
VirtualKeyCode::O => Some("O"),
|
||||||
|
VirtualKeyCode::P => Some("P"),
|
||||||
|
VirtualKeyCode::Q => Some("Q"),
|
||||||
|
VirtualKeyCode::R => Some("R"),
|
||||||
|
VirtualKeyCode::S => Some("S"),
|
||||||
|
VirtualKeyCode::T => Some("T"),
|
||||||
|
VirtualKeyCode::U => Some("U"),
|
||||||
|
VirtualKeyCode::V => Some("V"),
|
||||||
|
VirtualKeyCode::W => Some("W"),
|
||||||
|
VirtualKeyCode::X => Some("X"),
|
||||||
|
VirtualKeyCode::Y => Some("Y"),
|
||||||
|
VirtualKeyCode::Z => Some("Z"),
|
||||||
|
VirtualKeyCode::Up => Some("up"),
|
||||||
|
VirtualKeyCode::Down => Some("down"),
|
||||||
|
VirtualKeyCode::Left => Some("left"),
|
||||||
|
VirtualKeyCode::Right => Some("right"),
|
||||||
|
VirtualKeyCode::Space => Some("space"),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(str) = str {
|
||||||
|
Some(Dynamic::from(KeyboardEvent {
|
||||||
|
down,
|
||||||
|
key: ImmutableString::from(str),
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(arg) = arg {
|
||||||
|
self.run_event_callback(state, input, arg)
|
||||||
|
} else {
|
||||||
|
return Ok(PlayerDirective::None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_event_callback(
|
||||||
|
&mut self,
|
||||||
|
state: &mut RenderState,
|
||||||
|
input: Arc<RenderInput>,
|
||||||
|
arg: Dynamic,
|
||||||
|
) -> Result<PlayerDirective> {
|
||||||
|
let current_scene = (*self.state).borrow().get_scene().clone();
|
||||||
|
if current_scene.is_none() {
|
||||||
|
return Ok(PlayerDirective::None);
|
||||||
|
}
|
||||||
|
let current_scene = current_scene.unwrap();
|
||||||
|
let ct = (*self.state).borrow().ct.clone();
|
||||||
|
|
||||||
|
let d: Dynamic = rhai_error_to_anyhow(self.engine.call_fn(
|
||||||
|
&mut self.scope,
|
||||||
|
ct.config.ui_scenes.get(current_scene.as_str()).unwrap(),
|
||||||
|
"event",
|
||||||
|
(State::new(state, input.clone()), arg.clone()),
|
||||||
|
))
|
||||||
|
.with_context(|| format!("while handling event `{:?}`", arg))
|
||||||
|
.with_context(|| format!("in ui scene `{}`", current_scene))?;
|
||||||
|
|
||||||
|
if d.is::<PlayerDirective>() {
|
||||||
|
return Ok(d.cast());
|
||||||
|
} else if !(d.is_unit()) {
|
||||||
|
error!(
|
||||||
|
"`event()` in UI scene `{current_scene}` returned invalid type `{}`",
|
||||||
|
d
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(PlayerDirective::None);
|
||||||
|
}
|
||||||
|
|
||||||
/// Change the current scene
|
/// Change the current scene
|
||||||
pub fn set_scene(&mut self, state: &RenderState, input: Arc<RenderInput>) -> Result<()> {
|
pub fn set_scene(&mut self, state: &RenderState, input: Arc<RenderInput>) -> Result<()> {
|
||||||
let current_scene = (*self.state).borrow().get_scene().clone();
|
let current_scene = (*self.state).borrow().get_scene().clone();
|
||||||
|
@ -87,12 +201,12 @@ impl UiScriptExecutor {
|
||||||
let mut elm = self.state.borrow_mut();
|
let mut elm = self.state.borrow_mut();
|
||||||
elm.clear();
|
elm.clear();
|
||||||
drop(elm);
|
drop(elm);
|
||||||
let ct = (*self.state).borrow().ct.clone();
|
|
||||||
|
|
||||||
|
let ct = (*self.state).borrow().ct.clone();
|
||||||
rhai_error_to_anyhow(
|
rhai_error_to_anyhow(
|
||||||
self.engine.call_fn(
|
self.engine.call_fn(
|
||||||
&mut self.scope,
|
&mut self.scope,
|
||||||
ct.get_config()
|
ct.config
|
||||||
.ui_scenes
|
.ui_scenes
|
||||||
.get(current_scene.as_ref().unwrap().as_str())
|
.get(current_scene.as_ref().unwrap().as_str())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
@ -114,7 +228,7 @@ impl UiScriptExecutor {
|
||||||
if (*self.state).borrow().get_scene().is_none() {
|
if (*self.state).borrow().get_scene().is_none() {
|
||||||
(*self.state)
|
(*self.state)
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.set_scene(ImmutableString::from(&ct.get_config().start_ui_scene));
|
.set_scene(ImmutableString::from(&ct.config.start_ui_scene));
|
||||||
}
|
}
|
||||||
self.set_scene(state, input.clone())?;
|
self.set_scene(state, input.clone())?;
|
||||||
let current_scene = (*self.state).borrow().get_scene().clone();
|
let current_scene = (*self.state).borrow().get_scene().clone();
|
||||||
|
@ -122,9 +236,8 @@ impl UiScriptExecutor {
|
||||||
(*self.state).borrow_mut().step(state, input.clone());
|
(*self.state).borrow_mut().step(state, input.clone());
|
||||||
|
|
||||||
// Run step() (if it is defined)
|
// Run step() (if it is defined)
|
||||||
|
|
||||||
let ast = ct
|
let ast = ct
|
||||||
.get_config()
|
.config
|
||||||
.ui_scenes
|
.ui_scenes
|
||||||
.get(current_scene.as_ref().unwrap().as_str())
|
.get(current_scene.as_ref().unwrap().as_str())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -158,79 +271,25 @@ impl UiScriptExecutor {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
} {
|
} {
|
||||||
rhai_error_to_anyhow(
|
self.run_event_callback(state, input.clone(), Dynamic::from(PlayerShipStateEvent {}))?;
|
||||||
self.engine.call_fn(
|
|
||||||
&mut self.scope,
|
|
||||||
ct.get_config()
|
|
||||||
.ui_scenes
|
|
||||||
.get(current_scene.as_ref().unwrap().as_str())
|
|
||||||
.unwrap(),
|
|
||||||
"event",
|
|
||||||
(State::new(state, input.clone()), PlayerShipStateEvent {}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.with_context(|| format!("while handling player state change event"))
|
|
||||||
.with_context(|| format!("in ui scene `{}`", current_scene.as_ref().unwrap()))?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let len = (*self.state).borrow().len();
|
let len = (*self.state).borrow().len();
|
||||||
for i in 0..len {
|
for i in 0..len {
|
||||||
let event_arg = match (*self.state).borrow_mut().get_mut_by_idx(i).unwrap() {
|
match (*self.state).borrow_mut().get_mut_by_idx(i).unwrap() {
|
||||||
UiElement::Sprite(sprite) => {
|
UiElement::Sprite(sprite) => {
|
||||||
// Draw and update sprites
|
|
||||||
sprite.step(&input, state);
|
|
||||||
sprite.push_to_buffer(&input, state);
|
sprite.push_to_buffer(&input, state);
|
||||||
let event = sprite.check_events(&input, state);
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::None => None,
|
|
||||||
|
|
||||||
Event::MouseClick => Some(Dynamic::from(MouseClickEvent {
|
|
||||||
down: true,
|
|
||||||
element: sprite.name.clone(),
|
|
||||||
})),
|
|
||||||
|
|
||||||
Event::MouseRelease => Some(Dynamic::from(MouseClickEvent {
|
|
||||||
down: false,
|
|
||||||
element: sprite.name.clone(),
|
|
||||||
})),
|
|
||||||
|
|
||||||
Event::MouseHover => Some(Dynamic::from(MouseHoverEvent {
|
|
||||||
enter: true,
|
|
||||||
element: sprite.name.clone(),
|
|
||||||
})),
|
|
||||||
|
|
||||||
Event::MouseUnhover => Some(Dynamic::from(MouseHoverEvent {
|
|
||||||
enter: false,
|
|
||||||
element: sprite.name.clone(),
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UiElement::RadialBar(x) => {
|
UiElement::RadialBar(x) => {
|
||||||
// Draw and update radialbar
|
|
||||||
x.step(&input, state);
|
|
||||||
x.push_to_buffer(&input, state);
|
x.push_to_buffer(&input, state);
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UiElement::Text(..) => None,
|
UiElement::Scrollbox(x) => {
|
||||||
};
|
x.push_to_buffer(&input, state);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(event_arg) = event_arg {
|
UiElement::SubElement { .. } | UiElement::Text(..) => {}
|
||||||
rhai_error_to_anyhow(
|
|
||||||
self.engine.call_fn(
|
|
||||||
&mut self.scope,
|
|
||||||
ct.get_config()
|
|
||||||
.ui_scenes
|
|
||||||
.get(current_scene.as_ref().unwrap().as_str())
|
|
||||||
.unwrap(),
|
|
||||||
"event",
|
|
||||||
(State::new(state, input.clone()), event_arg.clone()),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.with_context(|| format!("while handling event `{:?}`", event_arg))
|
|
||||||
.with_context(|| format!("in ui scene `{}`", current_scene.as_ref().unwrap()))?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
mod api;
|
mod api;
|
||||||
mod event;
|
mod camera;
|
||||||
|
mod elements;
|
||||||
mod executor;
|
mod executor;
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
mod elements;
|
pub(crate) use camera::*;
|
||||||
|
|
||||||
pub(crate) use executor::UiScriptExecutor;
|
pub(crate) use executor::UiScriptExecutor;
|
||||||
pub(crate) use state::*;
|
pub(crate) use state::*;
|
||||||
|
|
|
@ -1,19 +1,39 @@
|
||||||
use galactica_content::Content;
|
use galactica_content::Content;
|
||||||
use glyphon::TextArea;
|
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
use rhai::ImmutableString;
|
use rhai::ImmutableString;
|
||||||
use std::collections::HashMap;
|
use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc, time::Instant};
|
||||||
use std::sync::Arc;
|
|
||||||
use winit::window::Window;
|
use winit::window::Window;
|
||||||
|
|
||||||
use super::elements::{FpsIndicator, RadialBar, Sprite, TextBox};
|
use super::{
|
||||||
|
elements::{FpsIndicator, OwnedTextArea, UiRadialBar, UiScrollbox, UiSprite, UiTextBox},
|
||||||
|
Camera,
|
||||||
|
};
|
||||||
use crate::{RenderInput, RenderState};
|
use crate::{RenderInput, RenderState};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum UiElement {
|
pub enum UiElement {
|
||||||
Sprite(Sprite),
|
Sprite(UiSprite),
|
||||||
RadialBar(RadialBar),
|
RadialBar(UiRadialBar),
|
||||||
Text(TextBox),
|
Text(UiTextBox),
|
||||||
|
Scrollbox(UiScrollbox),
|
||||||
|
|
||||||
|
/// This is a sub-element managed by another element
|
||||||
|
SubElement {
|
||||||
|
parent: ImmutableString,
|
||||||
|
element: Rc<RefCell<UiElement>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiElement {
|
||||||
|
pub fn get_name(&self) -> ImmutableString {
|
||||||
|
match self {
|
||||||
|
Self::Sprite(x) => x.name.clone(),
|
||||||
|
Self::RadialBar(x) => x.name.clone(),
|
||||||
|
Self::Text(x) => x.name.clone(),
|
||||||
|
Self::Scrollbox(x) => x.name.clone(),
|
||||||
|
Self::SubElement { element, .. } => element.borrow().get_name(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -31,8 +51,13 @@ pub(crate) struct UiState {
|
||||||
|
|
||||||
show_timings: bool,
|
show_timings: bool,
|
||||||
fps_indicator: FpsIndicator,
|
fps_indicator: FpsIndicator,
|
||||||
|
last_step: Instant,
|
||||||
|
|
||||||
pub config: UiConfig,
|
pub config: UiConfig,
|
||||||
|
|
||||||
|
/// The player's camera.
|
||||||
|
/// Only used when drawing physics.
|
||||||
|
pub camera: Camera,
|
||||||
}
|
}
|
||||||
// TODO: remove this
|
// TODO: remove this
|
||||||
unsafe impl Send for UiState {}
|
unsafe impl Send for UiState {}
|
||||||
|
@ -52,6 +77,8 @@ impl UiState {
|
||||||
show_phys: false,
|
show_phys: false,
|
||||||
show_starfield: false,
|
show_starfield: false,
|
||||||
},
|
},
|
||||||
|
last_step: Instant::now(),
|
||||||
|
camera: Camera::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +122,7 @@ impl UiState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_scene(&mut self, scene: ImmutableString) {
|
pub fn set_scene(&mut self, scene: ImmutableString) {
|
||||||
if !self.ct.get_config().ui_scenes.contains_key(scene.as_str()) {
|
if !self.ct.config.ui_scenes.contains_key(scene.as_str()) {
|
||||||
error!("tried to switch to ui scene `{scene}`, which doesn't exist");
|
error!("tried to switch to ui scene `{scene}`, which doesn't exist");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -105,15 +132,57 @@ impl UiState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step(&mut self, state: &mut RenderState, input: Arc<RenderInput>) {
|
pub fn step(&mut self, state: &mut RenderState, input: Arc<RenderInput>) {
|
||||||
|
let t = self.last_step.elapsed().as_secs_f32();
|
||||||
|
for (_, e) in &mut self.elements {
|
||||||
|
match e {
|
||||||
|
UiElement::Sprite(sprite) => sprite.step(t),
|
||||||
|
UiElement::Scrollbox(sbox) => sbox.step(t),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.show_timings {
|
if self.show_timings {
|
||||||
self.fps_indicator
|
self.fps_indicator
|
||||||
.step(&input, &mut state.text_font_system.borrow_mut());
|
.step(&input, &mut state.text_font_system.borrow_mut());
|
||||||
}
|
}
|
||||||
|
self.last_step = Instant::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_element(&mut self, e: UiElement) {
|
||||||
|
self.names.push(e.get_name().clone());
|
||||||
|
self.elements.insert(e.get_name().clone(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove an element from this sprite.
|
||||||
|
// This does NOT remove subelements from their parent sprites.
|
||||||
|
pub fn remove_element_incomplete(&mut self, name: &ImmutableString) -> Option<UiElement> {
|
||||||
|
let e = self.elements.remove(name);
|
||||||
|
self.names.retain(|x| *x != name);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove an element from this sprite and from all subsprites.
|
||||||
|
pub fn remove_element(&mut self, name: &ImmutableString) {
|
||||||
|
let e = self.elements.remove(name);
|
||||||
|
self.names.retain(|x| *x != name);
|
||||||
|
|
||||||
|
match e {
|
||||||
|
Some(UiElement::SubElement { parent, element }) => {
|
||||||
|
let x = Rc::into_inner(element).unwrap().into_inner();
|
||||||
|
let parent = self.elements.get_mut(&parent).unwrap();
|
||||||
|
match parent {
|
||||||
|
UiElement::Scrollbox(s) => s.remove_element(&x.get_name()),
|
||||||
|
_ => unreachable!("invalid subelement parent"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: don't allocate here, return an iterator
|
||||||
impl<'a> UiState {
|
impl<'a> UiState {
|
||||||
pub fn get_textareas(&'a mut self, input: &RenderInput, window: &Window) -> Vec<TextArea<'a>> {
|
pub fn get_textareas(&'a self, input: &RenderInput, window: &Window) -> Vec<OwnedTextArea> {
|
||||||
let mut v = Vec::with_capacity(32);
|
let mut v = Vec::with_capacity(32);
|
||||||
|
|
||||||
if self.current_scene.is_none() {
|
if self.current_scene.is_none() {
|
||||||
|
@ -124,9 +193,10 @@ impl<'a> UiState {
|
||||||
v.push(self.fps_indicator.get_textarea(input, window))
|
v.push(self.fps_indicator.get_textarea(input, window))
|
||||||
}
|
}
|
||||||
|
|
||||||
for t in self.elements.values() {
|
for e in self.elements.values() {
|
||||||
match &t {
|
match &e {
|
||||||
UiElement::Text(x) => v.push(x.get_textarea(input, window)),
|
UiElement::Text(t) => v.push(t.get_textarea(input, window)),
|
||||||
|
UiElement::Scrollbox(b) => v.extend(b.get_textareas(input, window)),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,3 +25,4 @@ rapier2d = { workspace = true }
|
||||||
nalgebra = { workspace = true }
|
nalgebra = { workspace = true }
|
||||||
crossbeam = { workspace = true }
|
crossbeam = { workspace = true }
|
||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
|
log = { workspace = true }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use galactica_content::{GunPoint, Outfit, OutfitHandle, OutfitSpace};
|
use galactica_content::{ContentIndex, GunPoint, Outfit, OutfitSpace};
|
||||||
|
|
||||||
/// Possible outcomes when adding an outfit
|
/// Possible outcomes when adding an outfit
|
||||||
pub enum OutfitAddResult {
|
pub enum OutfitAddResult {
|
||||||
|
@ -34,7 +34,7 @@ pub enum OutfitRemoveResult {
|
||||||
/// A simple data class, used to keep track of delayed shield generators
|
/// A simple data class, used to keep track of delayed shield generators
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct ShieldGenerator {
|
pub(crate) struct ShieldGenerator {
|
||||||
pub outfit: OutfitHandle,
|
pub outfit: Arc<Outfit>,
|
||||||
pub delay: f32,
|
pub delay: f32,
|
||||||
pub generation: f32,
|
pub generation: f32,
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ pub(crate) struct ShieldGenerator {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct OutfitSet {
|
pub struct OutfitSet {
|
||||||
/// What outfits does this statsum contain?
|
/// What outfits does this statsum contain?
|
||||||
outfits: HashMap<OutfitHandle, u32>,
|
outfits: HashMap<ContentIndex, (Arc<Outfit>, u32)>,
|
||||||
|
|
||||||
/// Space available in this outfitset.
|
/// Space available in this outfitset.
|
||||||
/// set at creation and never changes.
|
/// set at creation and never changes.
|
||||||
|
@ -59,7 +59,7 @@ pub struct OutfitSet {
|
||||||
/// The gun points available in this ship.
|
/// The gun points available in this ship.
|
||||||
/// If value is None, this point is free.
|
/// If value is None, this point is free.
|
||||||
/// if value is Some, this point is taken.
|
/// if value is Some, this point is taken.
|
||||||
gun_points: HashMap<GunPoint, Option<OutfitHandle>>,
|
gun_points: HashMap<GunPoint, Option<Arc<Outfit>>>,
|
||||||
|
|
||||||
/// Outfit values
|
/// Outfit values
|
||||||
/// This isn't strictly necessary, but we don't want to
|
/// This isn't strictly necessary, but we don't want to
|
||||||
|
@ -89,7 +89,7 @@ impl OutfitSet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn add(&mut self, o: &Outfit) -> OutfitAddResult {
|
pub(super) fn add(&mut self, o: &Arc<Outfit>) -> OutfitAddResult {
|
||||||
if !(self.total_space - self.used_space).can_contain(&o.space) {
|
if !(self.total_space - self.used_space).can_contain(&o.space) {
|
||||||
return OutfitAddResult::NotEnoughSpace("TODO".to_string());
|
return OutfitAddResult::NotEnoughSpace("TODO".to_string());
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@ impl OutfitSet {
|
||||||
let mut added = false;
|
let mut added = false;
|
||||||
for (_, outfit) in &mut self.gun_points {
|
for (_, outfit) in &mut self.gun_points {
|
||||||
if outfit.is_none() {
|
if outfit.is_none() {
|
||||||
*outfit = Some(o.handle);
|
*outfit = Some(o.clone());
|
||||||
added = true;
|
added = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,29 +115,29 @@ impl OutfitSet {
|
||||||
self.steer_power += o.steer_power;
|
self.steer_power += o.steer_power;
|
||||||
self.shield_strength += o.shield_strength;
|
self.shield_strength += o.shield_strength;
|
||||||
self.shield_generators.push(ShieldGenerator {
|
self.shield_generators.push(ShieldGenerator {
|
||||||
outfit: o.handle,
|
outfit: o.clone(),
|
||||||
delay: o.shield_delay,
|
delay: o.shield_delay,
|
||||||
generation: o.shield_generation,
|
generation: o.shield_generation,
|
||||||
});
|
});
|
||||||
|
|
||||||
if self.outfits.contains_key(&o.handle) {
|
if self.outfits.contains_key(&o.index) {
|
||||||
*self.outfits.get_mut(&o.handle).unwrap() += 1;
|
self.outfits.get_mut(&o.index).unwrap().1 += 1;
|
||||||
} else {
|
} else {
|
||||||
self.outfits.insert(o.handle, 1);
|
self.outfits.insert(o.index.clone(), (o.clone(), 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
return OutfitAddResult::Ok;
|
return OutfitAddResult::Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn remove(&mut self, o: &Outfit) -> OutfitRemoveResult {
|
pub(super) fn remove(&mut self, o: &Arc<Outfit>) -> OutfitRemoveResult {
|
||||||
if !self.outfits.contains_key(&o.handle) {
|
if !self.outfits.contains_key(&o.index) {
|
||||||
return OutfitRemoveResult::NotExist;
|
return OutfitRemoveResult::NotExist;
|
||||||
} else {
|
} else {
|
||||||
let n = *self.outfits.get(&o.handle).unwrap();
|
let n = self.outfits.get_mut(&o.index).unwrap();
|
||||||
if n == 1u32 {
|
if n.1 == 1u32 {
|
||||||
self.outfits.remove(&o.handle);
|
self.outfits.remove(&o.index);
|
||||||
} else {
|
} else {
|
||||||
*self.outfits.get_mut(&o.handle).unwrap() -= 1;
|
self.outfits.get_mut(&o.index).unwrap().1 -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,7 +152,7 @@ impl OutfitSet {
|
||||||
let index = self
|
let index = self
|
||||||
.shield_generators
|
.shield_generators
|
||||||
.iter()
|
.iter()
|
||||||
.position(|g| g.outfit == o.handle)
|
.position(|g| g.outfit.index == o.index)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
self.shield_generators.remove(index);
|
self.shield_generators.remove(index);
|
||||||
}
|
}
|
||||||
|
@ -165,16 +165,16 @@ impl OutfitSet {
|
||||||
impl OutfitSet {
|
impl OutfitSet {
|
||||||
/// The number of outfits in this set
|
/// The number of outfits in this set
|
||||||
pub fn len(&self) -> u32 {
|
pub fn len(&self) -> u32 {
|
||||||
self.outfits.iter().map(|(_, x)| x).sum()
|
self.outfits.iter().map(|(_, (_, x))| x).sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over all outfits
|
/// Iterate over all outfits
|
||||||
pub fn iter_outfits(&self) -> impl Iterator<Item = (&OutfitHandle, &u32)> {
|
pub fn iter_outfits(&self) -> impl Iterator<Item = &(Arc<Outfit>, u32)> {
|
||||||
self.outfits.iter()
|
self.outfits.values()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over all gun points
|
/// Iterate over all gun points
|
||||||
pub fn iter_gun_points(&self) -> impl Iterator<Item = (&GunPoint, &Option<OutfitHandle>)> {
|
pub fn iter_gun_points(&self) -> impl Iterator<Item = (&GunPoint, &Option<Arc<Outfit>>)> {
|
||||||
self.gun_points.iter()
|
self.gun_points.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,7 +190,7 @@ impl OutfitSet {
|
||||||
|
|
||||||
/// 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<OutfitHandle> {
|
pub fn get_gun(&self, point: &GunPoint) -> Option<Arc<Outfit>> {
|
||||||
self.gun_points.get(point).unwrap().clone()
|
self.gun_points.get(point).unwrap().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use galactica_content::{Content, FactionHandle, GunPoint, Outfit, ShipHandle, SystemObjectHandle};
|
use galactica_content::{Faction, GunPoint, Outfit, Ship, SystemObject};
|
||||||
use nalgebra::Isometry2;
|
use nalgebra::Isometry2;
|
||||||
use rand::{rngs::ThreadRng, Rng};
|
use rand::{rngs::ThreadRng, Rng};
|
||||||
use std::{collections::HashMap, time::Instant};
|
use std::{collections::HashMap, sync::Arc, time::Instant};
|
||||||
|
|
||||||
use super::{OutfitSet, ShipAutoPilot, ShipPersonality, ShipState};
|
use super::{OutfitSet, ShipAutoPilot, ShipPersonality, ShipState};
|
||||||
|
|
||||||
|
@ -9,8 +9,8 @@ use super::{OutfitSet, ShipAutoPilot, ShipPersonality, ShipState};
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ShipData {
|
pub struct ShipData {
|
||||||
// Metadata values
|
// Metadata values
|
||||||
ct_handle: ShipHandle,
|
ship: Arc<Ship>,
|
||||||
faction: FactionHandle,
|
faction: Arc<Faction>,
|
||||||
outfits: OutfitSet,
|
outfits: OutfitSet,
|
||||||
|
|
||||||
personality: ShipPersonality,
|
personality: ShipPersonality,
|
||||||
|
@ -34,16 +34,14 @@ pub struct ShipData {
|
||||||
impl ShipData {
|
impl ShipData {
|
||||||
/// Create a new ShipData
|
/// Create a new ShipData
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
ct: &Content,
|
ship: Arc<Ship>,
|
||||||
ct_handle: ShipHandle,
|
faction: Arc<Faction>,
|
||||||
faction: FactionHandle,
|
|
||||||
personality: ShipPersonality,
|
personality: ShipPersonality,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let s = ct.get_ship(ct_handle);
|
|
||||||
ShipData {
|
ShipData {
|
||||||
ct_handle,
|
ship: ship.clone(),
|
||||||
faction,
|
faction,
|
||||||
outfits: OutfitSet::new(s.space, &s.guns),
|
outfits: OutfitSet::new(ship.space, &ship.guns),
|
||||||
personality,
|
personality,
|
||||||
last_hit: Instant::now(),
|
last_hit: Instant::now(),
|
||||||
rng: rand::thread_rng(),
|
rng: rand::thread_rng(),
|
||||||
|
@ -54,9 +52,9 @@ impl ShipData {
|
||||||
},
|
},
|
||||||
|
|
||||||
// Initial stats
|
// Initial stats
|
||||||
hull: s.hull,
|
hull: ship.hull,
|
||||||
shields: 0.0,
|
shields: 0.0,
|
||||||
gun_cooldowns: s.guns.iter().map(|x| (x.clone(), 0.0)).collect(),
|
gun_cooldowns: ship.guns.iter().map(|x| (x.clone(), 0.0)).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,11 +78,11 @@ impl ShipData {
|
||||||
/// That is the simulation's responsiblity.
|
/// That is the simulation's responsiblity.
|
||||||
///
|
///
|
||||||
/// Will panic if we're not flying.
|
/// Will panic if we're not flying.
|
||||||
pub fn start_land_on(&mut self, target_handle: SystemObjectHandle) {
|
pub fn start_land_on(&mut self, target: Arc<SystemObject>) {
|
||||||
match self.state {
|
match self.state {
|
||||||
ShipState::Flying { .. } => {
|
ShipState::Flying { .. } => {
|
||||||
self.state = ShipState::Landing {
|
self.state = ShipState::Landing {
|
||||||
target: target_handle,
|
target,
|
||||||
current_z: 1.0,
|
current_z: 1.0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -108,9 +106,11 @@ impl ShipData {
|
||||||
/// Finish landing sequence
|
/// Finish landing sequence
|
||||||
/// Will panic if we're not landing
|
/// Will panic if we're not landing
|
||||||
pub fn finish_land_on(&mut self) {
|
pub fn finish_land_on(&mut self) {
|
||||||
match self.state {
|
match &self.state {
|
||||||
ShipState::Landing { target, .. } => {
|
ShipState::Landing { target, .. } => {
|
||||||
self.state = ShipState::Landed { target };
|
self.state = ShipState::Landed {
|
||||||
|
target: target.clone(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
unreachable!("Called `finish_land_on` on a ship that isn't landing!")
|
unreachable!("Called `finish_land_on` on a ship that isn't landing!")
|
||||||
|
@ -123,14 +123,13 @@ impl ShipData {
|
||||||
/// That is the simulation's responsiblity.
|
/// That is the simulation's responsiblity.
|
||||||
///
|
///
|
||||||
/// Will panic if we're not flying.
|
/// Will panic if we're not flying.
|
||||||
pub fn start_unland_to(&mut self, ct: &Content, to_position: Isometry2<f32>) {
|
pub fn start_unland_to(&mut self, to_position: Isometry2<f32>) {
|
||||||
match self.state {
|
match &self.state {
|
||||||
ShipState::Landed { target } => {
|
ShipState::Landed { target } => {
|
||||||
let obj = ct.get_system_object(target);
|
|
||||||
self.state = ShipState::UnLanding {
|
self.state = ShipState::UnLanding {
|
||||||
to_position,
|
to_position,
|
||||||
from: target,
|
from: target.clone(),
|
||||||
current_z: obj.pos.z,
|
current_z: target.pos.z,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -177,14 +176,14 @@ impl ShipData {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add an outfit to this ship
|
/// Add an outfit to this ship
|
||||||
pub fn add_outfit(&mut self, o: &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_total_shields();
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove an outfit from this ship
|
/// Remove an outfit from this ship
|
||||||
pub fn remove_outfit(&mut self, o: &Outfit) -> super::OutfitRemoveResult {
|
pub fn remove_outfit(&mut self, o: &Arc<Outfit>) -> super::OutfitRemoveResult {
|
||||||
self.outfits.remove(o)
|
self.outfits.remove(o)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,16 +191,14 @@ impl ShipData {
|
||||||
/// Will panic if `which` isn't a point on this ship.
|
/// Will panic if `which` isn't a point on this ship.
|
||||||
/// Returns `true` if this gun was fired,
|
/// Returns `true` if this gun was fired,
|
||||||
/// and `false` if it is on cooldown or empty.
|
/// and `false` if it is on cooldown or empty.
|
||||||
pub(crate) fn fire_gun(&mut self, ct: &Content, which: &GunPoint) -> bool {
|
pub(crate) fn fire_gun(&mut self, which: &GunPoint) -> bool {
|
||||||
let c = self.gun_cooldowns.get_mut(which).unwrap();
|
let c = self.gun_cooldowns.get_mut(which).unwrap();
|
||||||
|
|
||||||
if *c > 0.0 {
|
if *c > 0.0 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let g = self.outfits.get_gun(which);
|
if let Some(g) = self.outfits.get_gun(which) {
|
||||||
if g.is_some() {
|
|
||||||
let g = ct.get_outfit(g.unwrap());
|
|
||||||
let gun = g.gun.as_ref().unwrap();
|
let gun = g.gun.as_ref().unwrap();
|
||||||
*c = 0f32.max(gun.rate + self.rng.gen_range(-gun.rate_rng..=gun.rate_rng));
|
*c = 0f32.max(gun.rate + self.rng.gen_range(-gun.rate_rng..=gun.rate_rng));
|
||||||
return true;
|
return true;
|
||||||
|
@ -288,13 +285,12 @@ impl ShipData {
|
||||||
&self.state
|
&self.state
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a handle to this ship's content
|
/// Get this ship's content
|
||||||
pub fn get_content(&self) -> ShipHandle {
|
pub fn get_content(&self) -> &Arc<Ship> {
|
||||||
self.ct_handle
|
&self.ship
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get this ship's current hull.
|
/// Get this ship's current hull
|
||||||
/// Use content handle to get maximum hull
|
|
||||||
pub fn get_hull(&self) -> f32 {
|
pub fn get_hull(&self) -> f32 {
|
||||||
self.hull
|
self.hull
|
||||||
}
|
}
|
||||||
|
@ -316,12 +312,7 @@ impl ShipData {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get this ship's faction
|
/// Get this ship's faction
|
||||||
pub fn get_faction(&self) -> FactionHandle {
|
pub fn get_faction(&self) -> &Arc<Faction> {
|
||||||
self.faction
|
&self.faction
|
||||||
}
|
|
||||||
|
|
||||||
/// Get this ship's content handle
|
|
||||||
pub fn get_ship(&self) -> ShipHandle {
|
|
||||||
self.ct_handle
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use std::num::NonZeroU32;
|
use galactica_content::SystemObject;
|
||||||
|
|
||||||
use galactica_content::SystemObjectHandle;
|
|
||||||
use rapier2d::math::Isometry;
|
use rapier2d::math::Isometry;
|
||||||
|
use std::{num::NonZeroU32, sync::Arc};
|
||||||
|
|
||||||
/// A ship autopilot.
|
/// A ship autopilot.
|
||||||
/// An autopilot is a lightweight ShipController that
|
/// An autopilot is a lightweight ShipController that
|
||||||
|
@ -14,7 +13,7 @@ pub enum ShipAutoPilot {
|
||||||
/// Automatically arrange for landing on the given object
|
/// Automatically arrange for landing on the given object
|
||||||
Landing {
|
Landing {
|
||||||
/// The body we want to land on
|
/// The body we want to land on
|
||||||
target: SystemObjectHandle,
|
target: Arc<SystemObject>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,14 +39,14 @@ pub enum ShipState {
|
||||||
/// This ship is landed on a planet
|
/// This ship is landed on a planet
|
||||||
Landed {
|
Landed {
|
||||||
/// The planet this ship is landed on
|
/// The planet this ship is landed on
|
||||||
target: SystemObjectHandle,
|
target: Arc<SystemObject>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// This ship is landing on a planet
|
/// This ship is landing on a planet
|
||||||
/// (playing the animation)
|
/// (playing the animation)
|
||||||
Landing {
|
Landing {
|
||||||
/// The planet we're landing on
|
/// The planet we're landing on
|
||||||
target: SystemObjectHandle,
|
target: Arc<SystemObject>,
|
||||||
|
|
||||||
/// Our current z-coordinate
|
/// Our current z-coordinate
|
||||||
current_z: f32,
|
current_z: f32,
|
||||||
|
@ -60,7 +59,7 @@ pub enum ShipState {
|
||||||
to_position: Isometry<f32>,
|
to_position: Isometry<f32>,
|
||||||
|
|
||||||
/// The planet we're taking off from
|
/// The planet we're taking off from
|
||||||
from: SystemObjectHandle,
|
from: Arc<SystemObject>,
|
||||||
|
|
||||||
/// Our current z-coordinate
|
/// Our current z-coordinate
|
||||||
current_z: f32,
|
current_z: f32,
|
||||||
|
@ -69,9 +68,9 @@ pub enum ShipState {
|
||||||
|
|
||||||
impl ShipState {
|
impl ShipState {
|
||||||
/// What planet is this ship landed on?
|
/// What planet is this ship landed on?
|
||||||
pub fn landed_on(&self) -> Option<SystemObjectHandle> {
|
pub fn landed_on(&self) -> Option<Arc<SystemObject>> {
|
||||||
match self {
|
match self {
|
||||||
Self::Landed { target } => Some(*target),
|
Self::Landed { target } => Some(target.clone()),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,3 +6,6 @@
|
||||||
|
|
||||||
pub mod data;
|
pub mod data;
|
||||||
pub mod phys;
|
pub mod phys;
|
||||||
|
mod playerdirective;
|
||||||
|
|
||||||
|
pub use playerdirective::PlayerDirective;
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use galactica_content::{Content, EffectHandle, EffectVelocity, SpriteAutomaton};
|
use galactica_content::{Effect, EffectVelocity, SpriteAutomaton};
|
||||||
use nalgebra::{Point2, Rotation2, Vector2};
|
use nalgebra::{Point2, Rotation2, Vector2};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use rapier2d::dynamics::{RevoluteJointBuilder, RigidBodyBuilder, RigidBodyHandle, RigidBodyType};
|
use rapier2d::dynamics::{RevoluteJointBuilder, RigidBodyBuilder, RigidBodyHandle, RigidBodyType};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::phys::{PhysStepResources, PhysWrapper};
|
use crate::phys::{PhysStepResources, PhysWrapper};
|
||||||
|
|
||||||
|
@ -32,16 +33,13 @@ pub struct PhysEffect {
|
||||||
impl PhysEffect {
|
impl PhysEffect {
|
||||||
/// Create a new effect inside `Wrapper`
|
/// Create a new effect inside `Wrapper`
|
||||||
pub fn new(
|
pub fn new(
|
||||||
ct: &Content,
|
|
||||||
wrapper: &mut PhysWrapper,
|
wrapper: &mut PhysWrapper,
|
||||||
effect: EffectHandle,
|
effect: Arc<Effect>,
|
||||||
// Where to spawn the particle, in world space.
|
// Where to spawn the particle, in world space.
|
||||||
pos: Vector2<f32>,
|
pos: Vector2<f32>,
|
||||||
parent: RigidBodyHandle,
|
parent: RigidBodyHandle,
|
||||||
target: Option<RigidBodyHandle>,
|
target: Option<RigidBodyHandle>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let effect = ct.get_effect(effect);
|
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let parent_body = wrapper.get_rigid_body(parent).unwrap();
|
let parent_body = wrapper.get_rigid_body(parent).unwrap();
|
||||||
let parent_angle = parent_body.rotation().angle();
|
let parent_angle = parent_body.rotation().angle();
|
||||||
|
@ -113,7 +111,7 @@ impl PhysEffect {
|
||||||
};
|
};
|
||||||
|
|
||||||
PhysEffect {
|
PhysEffect {
|
||||||
anim: SpriteAutomaton::new(ct, effect.sprite),
|
anim: SpriteAutomaton::new(effect.sprite.clone()),
|
||||||
rigid_body,
|
rigid_body,
|
||||||
lifetime: 0f32.max(
|
lifetime: 0f32.max(
|
||||||
effect.lifetime + rng.gen_range(-effect.lifetime_rng..=effect.lifetime_rng),
|
effect.lifetime + rng.gen_range(-effect.lifetime_rng..=effect.lifetime_rng),
|
||||||
|
@ -153,7 +151,7 @@ impl PhysEffect {
|
||||||
);
|
);
|
||||||
|
|
||||||
PhysEffect {
|
PhysEffect {
|
||||||
anim: SpriteAutomaton::new(ct, effect.sprite),
|
anim: SpriteAutomaton::new(effect.sprite.clone()),
|
||||||
rigid_body,
|
rigid_body,
|
||||||
lifetime: 0f32.max(
|
lifetime: 0f32.max(
|
||||||
effect.lifetime + rng.gen_range(-effect.lifetime_rng..=effect.lifetime_rng),
|
effect.lifetime + rng.gen_range(-effect.lifetime_rng..=effect.lifetime_rng),
|
||||||
|
@ -174,7 +172,7 @@ impl PhysEffect {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.anim.step(&res.ct, res.t);
|
self.anim.step(res.t);
|
||||||
self.lifetime -= res.t;
|
self.lifetime -= res.t;
|
||||||
|
|
||||||
if self.lifetime <= 0.0 {
|
if self.lifetime <= 0.0 {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use galactica_content::{AnimationState, Content, FactionHandle, Projectile, SpriteAutomaton};
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use galactica_content::{AnimationState, Faction, Projectile, SpriteAutomaton};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
|
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
|
||||||
|
|
||||||
|
@ -10,7 +12,7 @@ use super::PhysEffect;
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PhysProjectile {
|
pub struct PhysProjectile {
|
||||||
/// This projectile's game data
|
/// This projectile's game data
|
||||||
pub content: Projectile,
|
pub content: Arc<Projectile>,
|
||||||
|
|
||||||
/// This projectile's sprite animation state
|
/// This projectile's sprite animation state
|
||||||
anim: SpriteAutomaton,
|
anim: SpriteAutomaton,
|
||||||
|
@ -19,7 +21,7 @@ pub struct PhysProjectile {
|
||||||
lifetime: f32,
|
lifetime: f32,
|
||||||
|
|
||||||
/// The faction this projectile belongs to
|
/// The faction this projectile belongs to
|
||||||
pub faction: FactionHandle,
|
pub faction: Arc<Faction>,
|
||||||
|
|
||||||
/// This projectile's rigidbody
|
/// This projectile's rigidbody
|
||||||
pub rigid_body: RigidBodyHandle,
|
pub rigid_body: RigidBodyHandle,
|
||||||
|
@ -38,17 +40,16 @@ pub struct PhysProjectile {
|
||||||
impl PhysProjectile {
|
impl PhysProjectile {
|
||||||
/// Create a new projectile
|
/// Create a new projectile
|
||||||
pub fn new(
|
pub fn new(
|
||||||
ct: &Content,
|
content: Arc<Projectile>,
|
||||||
content: Projectile, // TODO: use a handle?
|
|
||||||
rigid_body: RigidBodyHandle,
|
rigid_body: RigidBodyHandle,
|
||||||
faction: FactionHandle,
|
faction: Arc<Faction>,
|
||||||
collider: ColliderHandle,
|
collider: ColliderHandle,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let size_rng = content.size_rng;
|
let size_rng = content.size_rng;
|
||||||
let lifetime = content.lifetime;
|
let lifetime = content.lifetime;
|
||||||
PhysProjectile {
|
PhysProjectile {
|
||||||
anim: SpriteAutomaton::new(ct, content.sprite),
|
anim: SpriteAutomaton::new(content.sprite.clone()),
|
||||||
rigid_body,
|
rigid_body,
|
||||||
collider,
|
collider,
|
||||||
content,
|
content,
|
||||||
|
@ -67,31 +68,24 @@ impl PhysProjectile {
|
||||||
wrapper: &mut PhysWrapper,
|
wrapper: &mut PhysWrapper,
|
||||||
) {
|
) {
|
||||||
self.lifetime -= res.t;
|
self.lifetime -= res.t;
|
||||||
self.anim.step(&res.ct, res.t);
|
self.anim.step(res.t);
|
||||||
|
|
||||||
if self.lifetime <= 0.0 {
|
if self.lifetime <= 0.0 {
|
||||||
self.destroy(res, new, wrapper, true);
|
self.destroy(new, wrapper, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Destroy this projectile without creating an expire effect
|
/// Destroy this projectile without creating an expire effect
|
||||||
pub(in crate::phys) fn destroy_silent(
|
pub(in crate::phys) fn destroy_silent(
|
||||||
&mut self,
|
&mut self,
|
||||||
res: &PhysStepResources,
|
|
||||||
new: &mut NewObjects,
|
new: &mut NewObjects,
|
||||||
wrapper: &mut PhysWrapper,
|
wrapper: &mut PhysWrapper,
|
||||||
) {
|
) {
|
||||||
self.destroy(res, new, wrapper, false);
|
self.destroy(new, wrapper, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Destroy this projectile
|
/// Destroy this projectile
|
||||||
fn destroy(
|
fn destroy(&mut self, new: &mut NewObjects, wrapper: &mut PhysWrapper, expire: bool) {
|
||||||
&mut self,
|
|
||||||
res: &PhysStepResources,
|
|
||||||
new: &mut NewObjects,
|
|
||||||
wrapper: &mut PhysWrapper,
|
|
||||||
expire: bool,
|
|
||||||
) {
|
|
||||||
if self.is_destroyed {
|
if self.is_destroyed {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -100,11 +94,10 @@ impl PhysProjectile {
|
||||||
if expire {
|
if expire {
|
||||||
match &self.content.expire_effect {
|
match &self.content.expire_effect {
|
||||||
None => {}
|
None => {}
|
||||||
Some(handle) => {
|
Some(effect) => {
|
||||||
new.effects.push(PhysEffect::new(
|
new.effects.push(PhysEffect::new(
|
||||||
&res.ct,
|
|
||||||
wrapper,
|
wrapper,
|
||||||
*handle,
|
effect.clone(),
|
||||||
*rb.translation(),
|
*rb.translation(),
|
||||||
self.rigid_body,
|
self.rigid_body,
|
||||||
None,
|
None,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use galactica_content::{CollapseEvent, Content, Ship};
|
use galactica_content::{CollapseEvent, Ship};
|
||||||
use nalgebra::{Point2, Vector2};
|
use nalgebra::{Point2, Vector2};
|
||||||
use rand::{rngs::ThreadRng, Rng};
|
use rand::{rngs::ThreadRng, Rng};
|
||||||
use rapier2d::{
|
use rapier2d::{
|
||||||
|
@ -37,14 +37,13 @@ impl ShipCollapseSequence {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pick a random points inside a ship's collider
|
/// Pick a random points inside a ship's collider
|
||||||
fn random_in_ship(&mut self, ct: &Content, ship: &Ship, collider: &Collider) -> Vector2<f32> {
|
fn random_in_ship(&mut self, ship: &Ship, collider: &Collider) -> Vector2<f32> {
|
||||||
let mut y = 0.0;
|
let mut y = 0.0;
|
||||||
let mut x = 0.0;
|
let mut x = 0.0;
|
||||||
let mut a = false;
|
let mut a = false;
|
||||||
while !a {
|
while !a {
|
||||||
x = self.rng.gen_range(-1.0..=1.0) * ship.size / 2.0;
|
x = self.rng.gen_range(-1.0..=1.0) * ship.size / 2.0;
|
||||||
y = self.rng.gen_range(-1.0..=1.0) * ship.size * ct.get_sprite(ship.sprite).aspect
|
y = self.rng.gen_range(-1.0..=1.0) * ship.size * ship.sprite.aspect / 2.0;
|
||||||
/ 2.0;
|
|
||||||
a = collider.shape().contains_local_point(&Point2::new(x, y));
|
a = collider.shape().contains_local_point(&Point2::new(x, y));
|
||||||
}
|
}
|
||||||
Vector2::new(x, y)
|
Vector2::new(x, y)
|
||||||
|
@ -62,7 +61,7 @@ impl ShipCollapseSequence {
|
||||||
) {
|
) {
|
||||||
let rigid_body = wrapper.get_rigid_body(rigid_body_handle).unwrap().clone();
|
let rigid_body = wrapper.get_rigid_body(rigid_body_handle).unwrap().clone();
|
||||||
let collider = wrapper.get_collider(collider_handle).unwrap().clone();
|
let collider = wrapper.get_collider(collider_handle).unwrap().clone();
|
||||||
let ship_content = res.ct.get_ship(ship_data.get_content());
|
let ship_content = ship_data.get_content();
|
||||||
let ship_pos = rigid_body.translation();
|
let ship_pos = rigid_body.translation();
|
||||||
let ship_rot = rigid_body.rotation();
|
let ship_rot = rigid_body.rotation();
|
||||||
|
|
||||||
|
@ -84,14 +83,13 @@ impl ShipCollapseSequence {
|
||||||
let pos: Vector2<f32> = if let Some(pos) = spawner.pos {
|
let pos: Vector2<f32> = if let Some(pos) = spawner.pos {
|
||||||
Vector2::new(pos.x, pos.y)
|
Vector2::new(pos.x, pos.y)
|
||||||
} else {
|
} else {
|
||||||
self.random_in_ship(&res.ct, ship_content, &collider)
|
self.random_in_ship(&ship_content, &collider)
|
||||||
};
|
};
|
||||||
let pos = ship_pos + (ship_rot * pos);
|
let pos = ship_pos + (ship_rot * pos);
|
||||||
|
|
||||||
new.effects.push(PhysEffect::new(
|
new.effects.push(PhysEffect::new(
|
||||||
&res.ct,
|
|
||||||
wrapper,
|
wrapper,
|
||||||
spawner.effect,
|
spawner.effect.clone(),
|
||||||
pos,
|
pos,
|
||||||
rigid_body_handle,
|
rigid_body_handle,
|
||||||
None,
|
None,
|
||||||
|
@ -124,15 +122,14 @@ impl ShipCollapseSequence {
|
||||||
let pos = if let Some(pos) = spawner.pos {
|
let pos = if let Some(pos) = spawner.pos {
|
||||||
Vector2::new(pos.x, pos.y)
|
Vector2::new(pos.x, pos.y)
|
||||||
} else {
|
} else {
|
||||||
self.random_in_ship(&res.ct, ship_content, &collider)
|
self.random_in_ship(&ship_content, &collider)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Position, adjusted for ship rotation
|
// Position, adjusted for ship rotation
|
||||||
let pos = ship_pos + (ship_rot * pos);
|
let pos = ship_pos + (ship_rot * pos);
|
||||||
new.effects.push(PhysEffect::new(
|
new.effects.push(PhysEffect::new(
|
||||||
&res.ct,
|
|
||||||
wrapper,
|
wrapper,
|
||||||
spawner.effect,
|
spawner.effect.clone(),
|
||||||
pos,
|
pos,
|
||||||
rigid_body_handle,
|
rigid_body_handle,
|
||||||
None,
|
None,
|
||||||
|
|
|
@ -25,7 +25,7 @@ impl PointShipController {
|
||||||
impl ShipControllerStruct for PointShipController {
|
impl ShipControllerStruct for PointShipController {
|
||||||
fn update_controls(
|
fn update_controls(
|
||||||
&mut self,
|
&mut self,
|
||||||
res: &PhysStepResources,
|
_res: &PhysStepResources,
|
||||||
img: &PhysImage,
|
img: &PhysImage,
|
||||||
this_ship: PhysSimShipHandle,
|
this_ship: PhysSimShipHandle,
|
||||||
) -> Option<ShipControls> {
|
) -> Option<ShipControls> {
|
||||||
|
@ -40,14 +40,14 @@ impl ShipControllerStruct for PointShipController {
|
||||||
let my_position = this_rigidbody.translation();
|
let my_position = this_rigidbody.translation();
|
||||||
let my_rotation = this_rigidbody.rotation();
|
let my_rotation = this_rigidbody.rotation();
|
||||||
let my_angvel = this_rigidbody.angvel();
|
let my_angvel = this_rigidbody.angvel();
|
||||||
let my_faction = res.ct.get_faction(my_ship.ship.data.get_faction());
|
let my_faction = my_ship.ship.data.get_faction();
|
||||||
|
|
||||||
// Iterate all possible targets
|
// Iterate all possible targets
|
||||||
let mut hostile_ships = img.iter_ships().filter(
|
let mut hostile_ships = img.iter_ships().filter(
|
||||||
// Target only flying ships we're hostile towards
|
// Target only flying ships we're hostile towards
|
||||||
|s| match my_faction
|
|s| match my_faction
|
||||||
.relationships
|
.relationships
|
||||||
.get(&s.ship.data.get_faction())
|
.get(&s.ship.data.get_faction().index)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
{
|
{
|
||||||
Relationship::Hostile => match s.ship.data.get_state() {
|
Relationship::Hostile => match s.ship.data.get_state() {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use galactica_content::{
|
use galactica_content::{
|
||||||
AnimationState, Content, EnginePoint, FactionHandle, GunPoint, OutfitHandle,
|
AnimationState, EnginePoint, Faction, GunPoint, Outfit, ProjectileCollider, Ship,
|
||||||
ProjectileCollider, ShipHandle, SpriteAutomaton,
|
SpriteAutomaton,
|
||||||
};
|
};
|
||||||
use nalgebra::{vector, Point2, Rotation2, Vector2};
|
use nalgebra::{vector, Point2, Rotation2, Vector2};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
@ -66,24 +68,22 @@ pub struct PhysShip {
|
||||||
impl PhysShip {
|
impl PhysShip {
|
||||||
/// Make a new ship
|
/// Make a new ship
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
ct: &Content,
|
ship: Arc<Ship>,
|
||||||
handle: ShipHandle,
|
|
||||||
personality: ShipPersonality,
|
personality: ShipPersonality,
|
||||||
faction: FactionHandle,
|
faction: Arc<Faction>,
|
||||||
rigid_body: RigidBodyHandle,
|
rigid_body: RigidBodyHandle,
|
||||||
collider: ColliderHandle,
|
collider: ColliderHandle,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let ship_ct = ct.get_ship(handle);
|
|
||||||
PhysShip {
|
PhysShip {
|
||||||
uid: get_phys_id(),
|
uid: get_phys_id(),
|
||||||
anim: SpriteAutomaton::new(ct, ship_ct.sprite),
|
anim: SpriteAutomaton::new(ship.sprite.clone()),
|
||||||
rigid_body,
|
rigid_body,
|
||||||
collider,
|
collider,
|
||||||
data: ShipData::new(ct, handle, faction, personality),
|
data: ShipData::new(ship.clone(), faction, personality),
|
||||||
engine_anim: Vec::new(),
|
engine_anim: Vec::new(),
|
||||||
controls: ShipControls::new(),
|
controls: ShipControls::new(),
|
||||||
last_controls: ShipControls::new(),
|
last_controls: ShipControls::new(),
|
||||||
collapse_sequence: Some(ShipCollapseSequence::new(ship_ct.collapse.length)),
|
collapse_sequence: Some(ShipCollapseSequence::new(ship.collapse.length)),
|
||||||
is_destroyed: false,
|
is_destroyed: false,
|
||||||
controller: match personality {
|
controller: match personality {
|
||||||
ShipPersonality::Dummy | ShipPersonality::Player => ShipController::new_null(),
|
ShipPersonality::Dummy | ShipPersonality::Player => ShipController::new_null(),
|
||||||
|
@ -105,32 +105,28 @@ impl PhysShip {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.data.step(res.t);
|
self.data.step(res.t);
|
||||||
self.anim.step(&res.ct, res.t);
|
self.anim.step(res.t);
|
||||||
|
|
||||||
for (_, e) in &mut self.engine_anim {
|
for (_, e) in &mut self.engine_anim {
|
||||||
e.step(&res.ct, res.t);
|
e.step(res.t);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flare animations
|
// Flare animations
|
||||||
if !self.controls.thrust && self.last_controls.thrust {
|
if !self.controls.thrust && self.last_controls.thrust {
|
||||||
let flare = self.get_flare(&res.ct);
|
let flare = self.get_flare();
|
||||||
if flare.is_some() {
|
if let Some(flare) = flare {
|
||||||
let flare_outfit = flare.unwrap();
|
|
||||||
let flare = res.ct.get_outfit(flare_outfit);
|
|
||||||
if flare.engine_flare_on_stop.is_some() {
|
if flare.engine_flare_on_stop.is_some() {
|
||||||
for (_, e) in &mut self.engine_anim {
|
for (_, e) in &mut self.engine_anim {
|
||||||
e.jump_to(&res.ct, flare.engine_flare_on_stop.unwrap());
|
e.jump_to(flare.engine_flare_on_stop.as_ref().unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else if self.controls.thrust && !self.last_controls.thrust {
|
} else if self.controls.thrust && !self.last_controls.thrust {
|
||||||
let flare = self.get_flare(&res.ct);
|
let flare = self.get_flare();
|
||||||
if flare.is_some() {
|
if let Some(flare) = flare {
|
||||||
let flare_outfit = flare.unwrap();
|
|
||||||
let flare = res.ct.get_outfit(flare_outfit);
|
|
||||||
if flare.engine_flare_on_start.is_some() {
|
if flare.engine_flare_on_start.is_some() {
|
||||||
for (_, e) in &mut self.engine_anim {
|
for (_, e) in &mut self.engine_anim {
|
||||||
e.jump_to(&res.ct, flare.engine_flare_on_start.unwrap());
|
e.jump_to(flare.engine_flare_on_start.as_ref().unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -162,12 +158,11 @@ impl PhysShip {
|
||||||
// Compute new controls
|
// Compute new controls
|
||||||
let controls = match autopilot {
|
let controls = match autopilot {
|
||||||
ShipAutoPilot::Landing { target } => {
|
ShipAutoPilot::Landing { target } => {
|
||||||
let target_obj = res.ct.get_system_object(*target);
|
|
||||||
let controls = autopilot::auto_landing(
|
let controls = autopilot::auto_landing(
|
||||||
res,
|
res,
|
||||||
img,
|
img,
|
||||||
PhysSimShipHandle(self.collider),
|
PhysSimShipHandle(self.collider),
|
||||||
Vector2::new(target_obj.pos.x, target_obj.pos.y),
|
Vector2::new(target.pos.x, target.pos.y),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Try to land the ship.
|
// Try to land the ship.
|
||||||
|
@ -176,7 +171,7 @@ impl PhysShip {
|
||||||
let landed = 'landed: {
|
let landed = 'landed: {
|
||||||
let r = wrapper.get_rigid_body(self.rigid_body).unwrap();
|
let r = wrapper.get_rigid_body(self.rigid_body).unwrap();
|
||||||
|
|
||||||
let t_pos = Vector2::new(target_obj.pos.x, target_obj.pos.y);
|
let t_pos = Vector2::new(target.pos.x, target.pos.y);
|
||||||
let s_pos = Vector2::new(
|
let s_pos = Vector2::new(
|
||||||
r.position().translation.x,
|
r.position().translation.x,
|
||||||
r.position().translation.y,
|
r.position().translation.y,
|
||||||
|
@ -186,13 +181,13 @@ impl PhysShip {
|
||||||
// Can't just set_active(false), since we still need that collider's mass.
|
// Can't just set_active(false), since we still need that collider's mass.
|
||||||
|
|
||||||
// We're in land range...
|
// We're in land range...
|
||||||
if (t_pos - s_pos).magnitude() > target_obj.size / 2.0 {
|
if (t_pos - s_pos).magnitude() > target.size / 2.0 {
|
||||||
break 'landed false;
|
break 'landed false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// And we'll stay in land range long enough.
|
// And we'll stay in land range long enough.
|
||||||
if (t_pos - (s_pos + r.velocity_at_point(r.center_of_mass()) * 2.0))
|
if (t_pos - (s_pos + r.velocity_at_point(r.center_of_mass()) * 2.0))
|
||||||
.magnitude() > target_obj.size / 2.0
|
.magnitude() > target.size / 2.0
|
||||||
{
|
{
|
||||||
break 'landed false;
|
break 'landed false;
|
||||||
}
|
}
|
||||||
|
@ -202,7 +197,7 @@ impl PhysShip {
|
||||||
Group::GROUP_1,
|
Group::GROUP_1,
|
||||||
Group::empty(),
|
Group::empty(),
|
||||||
));
|
));
|
||||||
self.data.start_land_on(*target);
|
self.data.start_land_on(target.clone());
|
||||||
break 'landed true;
|
break 'landed true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -229,7 +224,7 @@ impl PhysShip {
|
||||||
if self.controls.guns {
|
if self.controls.guns {
|
||||||
// TODO: don't allocate here. This is a hack to satisfy the borrow checker,
|
// TODO: don't allocate here. This is a hack to satisfy the borrow checker,
|
||||||
// convert this to a refcell or do the replace dance.
|
// convert this to a refcell or do the replace dance.
|
||||||
let pairs: Vec<(GunPoint, Option<OutfitHandle>)> = self
|
let pairs: Vec<(GunPoint, Option<Arc<Outfit>>)> = self
|
||||||
.data
|
.data
|
||||||
.get_outfits()
|
.get_outfits()
|
||||||
.iter_gun_points()
|
.iter_gun_points()
|
||||||
|
@ -237,7 +232,7 @@ impl PhysShip {
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for (gun_point, outfit) in pairs {
|
for (gun_point, outfit) in pairs {
|
||||||
if self.data.fire_gun(&res.ct, &gun_point) {
|
if self.data.fire_gun(&gun_point) {
|
||||||
let outfit = outfit.unwrap();
|
let outfit = outfit.unwrap();
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
@ -250,20 +245,17 @@ impl PhysShip {
|
||||||
rigid_body.velocity_at_point(rigid_body.center_of_mass());
|
rigid_body.velocity_at_point(rigid_body.center_of_mass());
|
||||||
|
|
||||||
let pos = ship_pos + (ship_rot * gun_point.pos);
|
let pos = ship_pos + (ship_rot * gun_point.pos);
|
||||||
|
let gun = outfit.gun.as_ref().unwrap();
|
||||||
|
|
||||||
let outfit = res.ct.get_outfit(outfit);
|
let spread =
|
||||||
let outfit = outfit.gun.as_ref().unwrap();
|
rng.gen_range(-gun.projectile.angle_rng..=gun.projectile.angle_rng);
|
||||||
|
|
||||||
let spread = rng.gen_range(
|
|
||||||
-outfit.projectile.angle_rng..=outfit.projectile.angle_rng,
|
|
||||||
);
|
|
||||||
let vel = ship_vel
|
let vel = ship_vel
|
||||||
+ (Rotation2::new(ship_ang + spread)
|
+ (Rotation2::new(ship_ang + spread)
|
||||||
* Vector2::new(
|
* Vector2::new(
|
||||||
outfit.projectile.speed
|
gun.projectile.speed
|
||||||
+ rng.gen_range(
|
+ rng.gen_range(
|
||||||
-outfit.projectile.speed_rng
|
-gun.projectile.speed_rng
|
||||||
..=outfit.projectile.speed_rng,
|
..=gun.projectile.speed_rng,
|
||||||
),
|
),
|
||||||
0.0,
|
0.0,
|
||||||
));
|
));
|
||||||
|
@ -274,7 +266,7 @@ impl PhysShip {
|
||||||
.linvel(vel)
|
.linvel(vel)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let mut collider = match &outfit.projectile.collider {
|
let mut collider = match &gun.projectile.collider {
|
||||||
ProjectileCollider::Ball(b) => ColliderBuilder::ball(b.radius)
|
ProjectileCollider::Ball(b) => ColliderBuilder::ball(b.radius)
|
||||||
.sensor(true)
|
.sensor(true)
|
||||||
.active_events(ActiveEvents::COLLISION_EVENTS)
|
.active_events(ActiveEvents::COLLISION_EVENTS)
|
||||||
|
@ -290,10 +282,9 @@ impl PhysShip {
|
||||||
let collider = wrapper.insert_collider(collider, rigid_body);
|
let collider = wrapper.insert_collider(collider, rigid_body);
|
||||||
|
|
||||||
new.projectiles.push(PhysProjectile::new(
|
new.projectiles.push(PhysProjectile::new(
|
||||||
&res.ct,
|
gun.projectile.clone(),
|
||||||
outfit.projectile.clone(),
|
|
||||||
rigid_body,
|
rigid_body,
|
||||||
self.data.get_faction(),
|
self.data.get_faction().clone(),
|
||||||
collider,
|
collider,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -306,8 +297,6 @@ impl PhysShip {
|
||||||
current_z,
|
current_z,
|
||||||
from,
|
from,
|
||||||
} => {
|
} => {
|
||||||
let from_obj = res.ct.get_system_object(*from);
|
|
||||||
|
|
||||||
let controls = autopilot::auto_landing(
|
let controls = autopilot::auto_landing(
|
||||||
&res,
|
&res,
|
||||||
img,
|
img,
|
||||||
|
@ -315,7 +304,7 @@ impl PhysShip {
|
||||||
Vector2::new(to_position.translation.x, to_position.translation.y),
|
Vector2::new(to_position.translation.x, to_position.translation.y),
|
||||||
);
|
);
|
||||||
let r = wrapper.get_rigid_body_mut(self.rigid_body).unwrap();
|
let r = wrapper.get_rigid_body_mut(self.rigid_body).unwrap();
|
||||||
let max_d = (Vector2::new(from_obj.pos.x, from_obj.pos.y)
|
let max_d = (Vector2::new(from.pos.x, from.pos.y)
|
||||||
- Vector2::new(to_position.translation.x, to_position.translation.y))
|
- Vector2::new(to_position.translation.x, to_position.translation.y))
|
||||||
.magnitude();
|
.magnitude();
|
||||||
let now_d = (r.translation()
|
let now_d = (r.translation()
|
||||||
|
@ -324,7 +313,7 @@ impl PhysShip {
|
||||||
let f = now_d / max_d;
|
let f = now_d / max_d;
|
||||||
|
|
||||||
let current_z = *current_z;
|
let current_z = *current_z;
|
||||||
let zdist = 1.0 - from_obj.pos.z;
|
let zdist = 1.0 - from.pos.z;
|
||||||
|
|
||||||
if current_z <= 1.0 {
|
if current_z <= 1.0 {
|
||||||
// Finish unlanding ship
|
// Finish unlanding ship
|
||||||
|
@ -351,18 +340,17 @@ impl PhysShip {
|
||||||
}
|
}
|
||||||
|
|
||||||
ShipState::Landing { target, current_z } => {
|
ShipState::Landing { target, current_z } => {
|
||||||
let target_obj = res.ct.get_system_object(*target);
|
|
||||||
let controls = autopilot::auto_landing(
|
let controls = autopilot::auto_landing(
|
||||||
&res,
|
&res,
|
||||||
img,
|
img,
|
||||||
PhysSimShipHandle(self.collider),
|
PhysSimShipHandle(self.collider),
|
||||||
Vector2::new(target_obj.pos.x, target_obj.pos.y),
|
Vector2::new(target.pos.x, target.pos.y),
|
||||||
);
|
);
|
||||||
|
|
||||||
let current_z = *current_z;
|
let current_z = *current_z;
|
||||||
let zdist = target_obj.pos.z - 1.0;
|
let zdist = target.pos.z - 1.0;
|
||||||
|
|
||||||
if current_z >= target_obj.pos.z {
|
if current_z >= target.pos.z {
|
||||||
// Finish landing ship
|
// Finish landing ship
|
||||||
self.data.finish_land_on();
|
self.data.finish_land_on();
|
||||||
let r = wrapper.get_rigid_body_mut(self.rigid_body).unwrap();
|
let r = wrapper.get_rigid_body_mut(self.rigid_body).unwrap();
|
||||||
|
@ -432,7 +420,7 @@ impl PhysShip {
|
||||||
let rigid_body = wrapper.get_rigid_body(self.rigid_body).unwrap().clone();
|
let rigid_body = wrapper.get_rigid_body(self.rigid_body).unwrap().clone();
|
||||||
let collider = wrapper.get_collider(self.collider).unwrap().clone();
|
let collider = wrapper.get_collider(self.collider).unwrap().clone();
|
||||||
|
|
||||||
let ship_content = res.ct.get_ship(self.data.get_content());
|
let ship_content = self.data.get_content();
|
||||||
let ship_pos = rigid_body.translation();
|
let ship_pos = rigid_body.translation();
|
||||||
let ship_rot = rigid_body.rotation();
|
let ship_rot = rigid_body.rotation();
|
||||||
let ship_ang = ship_rot.angle();
|
let ship_ang = ship_rot.angle();
|
||||||
|
@ -451,7 +439,7 @@ impl PhysShip {
|
||||||
while !a {
|
while !a {
|
||||||
x = rng.gen_range(-1.0..=1.0) * ship_content.size / 2.0;
|
x = rng.gen_range(-1.0..=1.0) * ship_content.size / 2.0;
|
||||||
y = rng.gen_range(-1.0..=1.0)
|
y = rng.gen_range(-1.0..=1.0)
|
||||||
* ship_content.size * res.ct.get_sprite(ship_content.sprite).aspect
|
* ship_content.size * ship_content.sprite.aspect
|
||||||
/ 2.0;
|
/ 2.0;
|
||||||
a = collider.shape().contains_local_point(&Point2::new(x, y));
|
a = collider.shape().contains_local_point(&Point2::new(x, y));
|
||||||
}
|
}
|
||||||
|
@ -461,9 +449,8 @@ impl PhysShip {
|
||||||
let pos = ship_pos + (Rotation2::new(ship_ang) * pos);
|
let pos = ship_pos + (Rotation2::new(ship_ang) * pos);
|
||||||
|
|
||||||
new.effects.push(PhysEffect::new(
|
new.effects.push(PhysEffect::new(
|
||||||
&res.ct,
|
|
||||||
wrapper,
|
wrapper,
|
||||||
e.effect,
|
e.effect.clone(),
|
||||||
pos,
|
pos,
|
||||||
self.rigid_body,
|
self.rigid_body,
|
||||||
None,
|
None,
|
||||||
|
@ -476,12 +463,11 @@ impl PhysShip {
|
||||||
|
|
||||||
/// Public mutable
|
/// Public mutable
|
||||||
impl PhysShip {
|
impl PhysShip {
|
||||||
fn get_flare(&mut self, ct: &Content) -> Option<OutfitHandle> {
|
fn get_flare(&mut self) -> Option<Arc<Outfit>> {
|
||||||
// TODO: better way to pick flare sprite
|
// TODO: better way to pick flare sprite
|
||||||
for (h, _) in self.data.get_outfits().iter_outfits() {
|
for (h, _) in self.data.get_outfits().iter_outfits() {
|
||||||
let c = ct.get_outfit(*h);
|
if h.engine_flare_sprite.is_some() {
|
||||||
if c.engine_flare_sprite.is_some() {
|
return Some(h.clone());
|
||||||
return Some(*h);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return None;
|
return None;
|
||||||
|
@ -489,37 +475,47 @@ impl PhysShip {
|
||||||
|
|
||||||
/// Re-create this ship's engine flare animations
|
/// Re-create this ship's engine flare animations
|
||||||
/// Should be called whenever we change outfits
|
/// Should be called whenever we change outfits
|
||||||
fn update_flares(&mut self, ct: &Content) {
|
fn update_flares(&mut self) {
|
||||||
let flare_outfit = self.get_flare(ct);
|
let flare = self.get_flare();
|
||||||
if flare_outfit.is_none() {
|
if flare.is_none() {
|
||||||
self.engine_anim = Vec::new();
|
self.engine_anim = Vec::new();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let flare = ct
|
|
||||||
.get_outfit(flare_outfit.unwrap())
|
|
||||||
.engine_flare_sprite
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
self.engine_anim = ct
|
self.engine_anim = self
|
||||||
.get_ship(self.data.get_content())
|
.data
|
||||||
|
.get_content()
|
||||||
.engines
|
.engines
|
||||||
.iter()
|
.iter()
|
||||||
.map(|e| (e.clone(), SpriteAutomaton::new(ct, flare)))
|
.map(|e| {
|
||||||
|
(
|
||||||
|
e.clone(),
|
||||||
|
SpriteAutomaton::new(
|
||||||
|
flare
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.engine_flare_sprite
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.clone(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add one outfit to this ship
|
/// Add one outfit to this ship
|
||||||
pub fn add_outfit(&mut self, ct: &Content, o: OutfitHandle) {
|
pub fn add_outfit(&mut self, o: Arc<Outfit>) {
|
||||||
self.data.add_outfit(ct.get_outfit(o));
|
self.data.add_outfit(&o);
|
||||||
self.update_flares(ct);
|
self.update_flares();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add many outfits to this ship
|
/// Add many outfits to this ship
|
||||||
pub fn add_outfits(&mut self, ct: &Content, outfits: impl IntoIterator<Item = OutfitHandle>) {
|
pub fn add_outfits(&mut self, outfits: impl IntoIterator<Item = Arc<Outfit>>) {
|
||||||
for o in outfits {
|
for o in outfits {
|
||||||
self.data.add_outfit(ct.get_outfit(o));
|
self.data.add_outfit(&o);
|
||||||
}
|
}
|
||||||
self.update_flares(ct);
|
self.update_flares();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
use galactica_content::Faction;
|
||||||
use galactica_content::Relationship;
|
use galactica_content::Relationship;
|
||||||
use galactica_content::{Content, FactionHandle, ShipHandle, SystemHandle};
|
use galactica_content::Ship;
|
||||||
use galactica_playeragent::PlayerAgent;
|
use galactica_playeragent::PlayerAgent;
|
||||||
|
use log::error;
|
||||||
use nalgebra::{Isometry2, Point2, Rotation2, Vector2};
|
use nalgebra::{Isometry2, Point2, Rotation2, Vector2};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use rapier2d::{
|
use rapier2d::{
|
||||||
|
@ -8,6 +10,7 @@ use rapier2d::{
|
||||||
geometry::{ColliderHandle, Group, InteractionGroups},
|
geometry::{ColliderHandle, Group, InteractionGroups},
|
||||||
};
|
};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::PhysEffectImage;
|
use super::PhysEffectImage;
|
||||||
use super::{
|
use super::{
|
||||||
|
@ -15,6 +18,7 @@ use super::{
|
||||||
PhysImage, PhysProjectileImage, PhysShipImage, PhysStepResources, PhysWrapper,
|
PhysImage, PhysProjectileImage, PhysShipImage, PhysStepResources, PhysWrapper,
|
||||||
};
|
};
|
||||||
use crate::data::{ShipAutoPilot, ShipPersonality, ShipState};
|
use crate::data::{ShipAutoPilot, ShipPersonality, ShipState};
|
||||||
|
use crate::PlayerDirective;
|
||||||
|
|
||||||
// TODO: replace with a more generic handle
|
// TODO: replace with a more generic handle
|
||||||
/// A handle for a ship in this simulation
|
/// A handle for a ship in this simulation
|
||||||
|
@ -49,13 +53,8 @@ impl NewObjects {
|
||||||
|
|
||||||
/// Manages the physics state of one system
|
/// Manages the physics state of one system
|
||||||
pub struct PhysSim {
|
pub struct PhysSim {
|
||||||
/// The system this sim is attached to
|
|
||||||
_system: SystemHandle,
|
|
||||||
|
|
||||||
wrapper: PhysWrapper,
|
wrapper: PhysWrapper,
|
||||||
|
|
||||||
new: NewObjects,
|
new: NewObjects,
|
||||||
|
|
||||||
effects: Vec<PhysEffect>,
|
effects: Vec<PhysEffect>,
|
||||||
projectiles: HashMap<ColliderHandle, PhysProjectile>,
|
projectiles: HashMap<ColliderHandle, PhysProjectile>,
|
||||||
ships: HashMap<ColliderHandle, PhysShip>,
|
ships: HashMap<ColliderHandle, PhysShip>,
|
||||||
|
@ -63,10 +62,9 @@ pub struct PhysSim {
|
||||||
|
|
||||||
// Private methods
|
// Private methods
|
||||||
impl PhysSim {
|
impl PhysSim {
|
||||||
pub(super) fn start_unland_ship(&mut self, ct: &Content, collider: ColliderHandle) {
|
pub(super) fn start_unland_ship(&mut self, collider: ColliderHandle) {
|
||||||
let ship = self.ships.get_mut(&collider).unwrap();
|
let ship = self.ships.get_mut(&collider).unwrap();
|
||||||
let obj = ship.data.get_state().landed_on().unwrap();
|
let obj = ship.data.get_state().landed_on().unwrap();
|
||||||
let obj = ct.get_system_object(obj);
|
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let radius = rng.gen_range(500.0..=1500.0);
|
let radius = rng.gen_range(500.0..=1500.0);
|
||||||
|
@ -75,7 +73,7 @@ impl PhysSim {
|
||||||
let target_trans = Vector2::new(obj.pos.x, obj.pos.y) + target_offset;
|
let target_trans = Vector2::new(obj.pos.x, obj.pos.y) + target_offset;
|
||||||
let target_pos = Isometry2::new(target_trans, angle);
|
let target_pos = Isometry2::new(target_trans, angle);
|
||||||
|
|
||||||
ship.data.start_unland_to(ct, target_pos);
|
ship.data.start_unland_to(target_pos);
|
||||||
|
|
||||||
let r = self.wrapper.get_rigid_body_mut(ship.rigid_body).unwrap();
|
let r = self.wrapper.get_rigid_body_mut(ship.rigid_body).unwrap();
|
||||||
r.set_enabled(true);
|
r.set_enabled(true);
|
||||||
|
@ -87,7 +85,6 @@ impl PhysSim {
|
||||||
|
|
||||||
pub(super) fn collide_projectile_ship(
|
pub(super) fn collide_projectile_ship(
|
||||||
&mut self,
|
&mut self,
|
||||||
res: &mut PhysStepResources,
|
|
||||||
projectile_h: ColliderHandle,
|
projectile_h: ColliderHandle,
|
||||||
ship_h: ColliderHandle,
|
ship_h: ColliderHandle,
|
||||||
) {
|
) {
|
||||||
|
@ -99,8 +96,11 @@ impl PhysSim {
|
||||||
let projectile = projectile.unwrap();
|
let projectile = projectile.unwrap();
|
||||||
let ship = ship.unwrap();
|
let ship = ship.unwrap();
|
||||||
|
|
||||||
let f = res.ct.get_faction(projectile.faction);
|
let r = projectile
|
||||||
let r = f.relationships.get(&ship.data.get_faction()).unwrap();
|
.faction
|
||||||
|
.relationships
|
||||||
|
.get(&ship.data.get_faction().index)
|
||||||
|
.unwrap();
|
||||||
let destory_projectile = match r {
|
let destory_projectile = match r {
|
||||||
Relationship::Hostile => match ship.data.get_state() {
|
Relationship::Hostile => match ship.data.get_state() {
|
||||||
ShipState::Flying { .. } => {
|
ShipState::Flying { .. } => {
|
||||||
|
@ -131,9 +131,8 @@ impl PhysSim {
|
||||||
None => {}
|
None => {}
|
||||||
Some(x) => {
|
Some(x) => {
|
||||||
self.effects.push(PhysEffect::new(
|
self.effects.push(PhysEffect::new(
|
||||||
&res.ct,
|
|
||||||
&mut self.wrapper,
|
&mut self.wrapper,
|
||||||
*x,
|
x.clone(),
|
||||||
pos,
|
pos,
|
||||||
projectile.rigid_body,
|
projectile.rigid_body,
|
||||||
Some(ship.rigid_body),
|
Some(ship.rigid_body),
|
||||||
|
@ -141,7 +140,7 @@ impl PhysSim {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
projectile.destroy_silent(res, &mut self.new, &mut self.wrapper);
|
projectile.destroy_silent(&mut self.new, &mut self.wrapper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,11 +148,9 @@ impl PhysSim {
|
||||||
// Public methods
|
// Public methods
|
||||||
impl PhysSim {
|
impl PhysSim {
|
||||||
/// Create a new physics system
|
/// Create a new physics system
|
||||||
pub fn new(_ct: &Content, system: SystemHandle) -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
_system: system,
|
|
||||||
wrapper: PhysWrapper::new(),
|
wrapper: PhysWrapper::new(),
|
||||||
|
|
||||||
new: NewObjects::new(),
|
new: NewObjects::new(),
|
||||||
effects: Vec::new(),
|
effects: Vec::new(),
|
||||||
projectiles: HashMap::new(),
|
projectiles: HashMap::new(),
|
||||||
|
@ -164,19 +161,17 @@ impl PhysSim {
|
||||||
/// Add a ship to this physics system
|
/// Add a ship to this physics system
|
||||||
pub fn add_ship(
|
pub fn add_ship(
|
||||||
&mut self,
|
&mut self,
|
||||||
ct: &Content,
|
ship: Arc<Ship>,
|
||||||
handle: ShipHandle,
|
faction: Arc<Faction>,
|
||||||
faction: FactionHandle,
|
|
||||||
personality: ShipPersonality,
|
personality: ShipPersonality,
|
||||||
position: Point2<f32>,
|
position: Point2<f32>,
|
||||||
) -> PhysSimShipHandle {
|
) -> PhysSimShipHandle {
|
||||||
let ship_content = ct.get_ship(handle);
|
let mut cl = ship.collider.0.clone();
|
||||||
let mut cl = ship_content.collider.0.clone();
|
|
||||||
// TODO: additonal ship mass from outfits and cargo
|
// TODO: additonal ship mass from outfits and cargo
|
||||||
|
|
||||||
let rb = RigidBodyBuilder::dynamic()
|
let rb = RigidBodyBuilder::dynamic()
|
||||||
.angular_damping(ship_content.angular_drag)
|
.angular_damping(ship.angular_drag)
|
||||||
.linear_damping(ship_content.linear_drag)
|
.linear_damping(ship.linear_drag)
|
||||||
.translation(Vector2::new(position.x, position.y))
|
.translation(Vector2::new(position.x, position.y))
|
||||||
.can_sleep(false);
|
.can_sleep(false);
|
||||||
|
|
||||||
|
@ -190,14 +185,14 @@ impl PhysSim {
|
||||||
|
|
||||||
self.ships.insert(
|
self.ships.insert(
|
||||||
collider,
|
collider,
|
||||||
PhysShip::new(ct, handle, personality, faction, ridid_body, collider),
|
PhysShip::new(ship, personality, faction, ridid_body, collider),
|
||||||
);
|
);
|
||||||
|
|
||||||
return PhysSimShipHandle(collider);
|
return PhysSimShipHandle(collider);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update a player ship's controls
|
/// Update a player ship's controls
|
||||||
pub fn update_player_controls(&mut self, ct: &Content, player: &PlayerAgent) {
|
pub fn apply_directive(&mut self, directive: PlayerDirective, player: &PlayerAgent) {
|
||||||
if player.ship.is_none() {
|
if player.ship.is_none() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -211,35 +206,49 @@ impl PhysSim {
|
||||||
|
|
||||||
ShipState::Flying {
|
ShipState::Flying {
|
||||||
autopilot: ShipAutoPilot::None,
|
autopilot: ShipAutoPilot::None,
|
||||||
} => {
|
} => match directive {
|
||||||
ship_object.controls.guns = player.input.pressed_guns();
|
PlayerDirective::None => {}
|
||||||
ship_object.controls.left = player.input.pressed_left();
|
PlayerDirective::Engine(x) => ship_object.controls.thrust = x,
|
||||||
ship_object.controls.right = player.input.pressed_right();
|
PlayerDirective::TurnLeft(x) => ship_object.controls.left = x,
|
||||||
ship_object.controls.thrust = player.input.pressed_thrust();
|
PlayerDirective::TurnRight(x) => ship_object.controls.right = x,
|
||||||
|
PlayerDirective::Guns(x) => ship_object.controls.guns = x,
|
||||||
if player.input.pressed_land() {
|
PlayerDirective::Land => {
|
||||||
|
if let Some(target) = player.selection.get_planet() {
|
||||||
ship_object.data.set_autopilot(ShipAutoPilot::Landing {
|
ship_object.data.set_autopilot(ShipAutoPilot::Landing {
|
||||||
target: player.selection.get_planet().unwrap(),
|
target: target.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_ => {
|
||||||
|
error!("Got an invalid directive {directive:?} in shipstate `Flying`");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
ShipState::Flying { .. } => {
|
ShipState::Flying { .. } => match directive {
|
||||||
// Any input automatically releases autopilot
|
PlayerDirective::None => {}
|
||||||
if player.input.pressed_left()
|
PlayerDirective::Engine(_)
|
||||||
|| player.input.pressed_right()
|
| PlayerDirective::TurnLeft(_)
|
||||||
|| player.input.pressed_thrust()
|
| PlayerDirective::TurnRight(_)
|
||||||
|| player.input.pressed_guns()
|
| PlayerDirective::Land
|
||||||
{
|
| PlayerDirective::Guns(_) => {
|
||||||
|
// Disable autopilot and apply action
|
||||||
ship_object.data.set_autopilot(ShipAutoPilot::None);
|
ship_object.data.set_autopilot(ShipAutoPilot::None);
|
||||||
|
self.apply_directive(directive, player);
|
||||||
}
|
}
|
||||||
|
_ => {
|
||||||
|
error!("Got an invalid directive {directive:?} in shipstate `Flying`");
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
ShipState::Landed { .. } => {
|
ShipState::Landed { .. } => match directive {
|
||||||
if player.input.pressed_land() {
|
PlayerDirective::None => {}
|
||||||
self.start_unland_ship(ct, player.ship.unwrap());
|
PlayerDirective::UnLand => {
|
||||||
|
self.start_unland_ship(player.ship.unwrap());
|
||||||
}
|
}
|
||||||
|
_ => {
|
||||||
|
error!("Got an invalid directive {directive:?} in shipstate `Landed`");
|
||||||
}
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -271,7 +280,7 @@ impl PhysSim {
|
||||||
if p.is_none() || s.is_none() {
|
if p.is_none() || s.is_none() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
self.collide_projectile_ship(&mut res, a, b);
|
self.collide_projectile_ship(a, b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.timing.mark_physics_step();
|
res.timing.mark_physics_step();
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
/// An action the player wants to take in the game.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum PlayerDirective {
|
||||||
|
/// Do nothing
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// Set main engine state
|
||||||
|
Engine(bool),
|
||||||
|
|
||||||
|
/// Set left turn thruster state
|
||||||
|
TurnLeft(bool),
|
||||||
|
|
||||||
|
/// Set right turn thruster state
|
||||||
|
TurnRight(bool),
|
||||||
|
|
||||||
|
/// Set main gun state
|
||||||
|
Guns(bool),
|
||||||
|
|
||||||
|
/// Land on the currently selected planet
|
||||||
|
Land,
|
||||||
|
|
||||||
|
/// Take off from the planet we're landed on
|
||||||
|
UnLand,
|
||||||
|
}
|
Loading…
Reference in New Issue