253 lines
7.4 KiB
Crystal
253 lines
7.4 KiB
Crystal
require "./gravity"
|
|
|
|
class Vessel(N)
|
|
getter :name, :gravity_body, :gravity_field, :stations
|
|
getter :pilot, :navigation
|
|
|
|
def initialize(@name : String, @gravity_body : Gravity::MovingBody(N), @gravity_field : Gravity::Field(N))
|
|
@pilot = Pilot(N).new
|
|
@navigation = Navigation(N).new
|
|
@stations = {
|
|
pilot: @pilot,
|
|
navigation: @navigation,
|
|
}
|
|
# -- bottom after instances variables are set, we can reference self --
|
|
@stations.each { |sn, s| s.vessel = self }
|
|
end
|
|
|
|
def execute_orders!
|
|
@stations.each { |sn, s| s.execute_orders! }
|
|
end
|
|
|
|
def gravity : Vector(Float64, N)
|
|
@gravity_field.acceleration(@gravity_body.position)
|
|
end
|
|
|
|
abstract class Station(N)
|
|
getter orders
|
|
setter vessel
|
|
|
|
def initialize(@vessel : Vessel(N)? = nil)
|
|
@orders = Hash(Symbol, Array(Order(N))).new
|
|
end
|
|
|
|
def vessel?
|
|
@vessel
|
|
end
|
|
|
|
def vessel : Vessel(N)
|
|
vessel = @vessel
|
|
raise "@vessel cannot be Nil" if vessel.nil?
|
|
vessel
|
|
end
|
|
|
|
# like a orders[group] but will never throw and always
|
|
# have an initialized array.
|
|
# It is not a default value because it can modify the orders state.
|
|
def orders_at(group : Symbol)
|
|
@orders[group] ||= Array(Order(N)).new
|
|
@orders[group]
|
|
end
|
|
|
|
def orders_at(order : Order(N))
|
|
orders_at(order.group)
|
|
end
|
|
|
|
def <<(order : Order(N))
|
|
order.station = self
|
|
orders_at(order.group) << order
|
|
end
|
|
|
|
def order(priority : Int32 = 0, group : Symbol = :default, &block : Order(N) -> Nil)
|
|
self << Order(N).new(priority: priority, group: group, station: self, &block)
|
|
end
|
|
|
|
# Given one `Order`, this method remove all next orders in the list
|
|
# so we don't execute them next tick.
|
|
def clear_next!(order : Order(N))
|
|
orders_at(order.group).clear
|
|
end
|
|
|
|
def execute_orders! : Array(Order(N))
|
|
executed = Array(Order(N)).new
|
|
@orders.each do |group, orders|
|
|
executed_one = execute_orders_group!(orders)
|
|
executed << executed_one if !executed_one.nil?
|
|
end
|
|
executed
|
|
end
|
|
|
|
# Execute only the most prioritized order and remove it from the order list.
|
|
# This order may trigger a clear operation if it cancels non executed orders.
|
|
private def execute_orders_group!(orders : Array(Order(N))) : Order(N)?
|
|
return nil if orders.empty?
|
|
orders.sort!
|
|
order = orders.pop # remove last order and execute it
|
|
order.execute
|
|
# orders.unshift order if order.keep
|
|
order
|
|
end
|
|
end
|
|
|
|
|
|
# And Order is supposed to write something on the vessel state.
|
|
# It may be accelerate the vector, use communications antennas, sensors, etc.
|
|
# And order is executed when executed is called, which allows for the DLS code:
|
|
#
|
|
# value = 123.45
|
|
# vessel.pilot << Order(2).new(priority: 2, group: :acceleration) { |order|
|
|
# order.vessel.acceleration[0] = value
|
|
# order.station.clear_next!(order)
|
|
# }
|
|
#
|
|
class Order(N)
|
|
setter :station
|
|
getter :priority, :group
|
|
# property :keep
|
|
|
|
def initialize(@priority : Int32 = 0, @group : Symbol = :default, @station : Station(N)? = nil, @keep : Bool = false, &@block : Order(N) -> Nil)
|
|
end
|
|
|
|
def to_s(io : IO) : Nil
|
|
io << "Order[#{station.class}:#{group}:#{priority}]"
|
|
end
|
|
|
|
def pretty_print(pp)
|
|
pp.text(to_s)
|
|
end
|
|
|
|
# No order should have a nil station, but there is a very small window in the code where
|
|
# the station is nil during initialization before being attributed to the station via `Station#<<`.
|
|
def station : Station(N)
|
|
station = @station
|
|
raise "No station set, should not happen" if station.nil?
|
|
station
|
|
end
|
|
|
|
# shortcut
|
|
def vessel
|
|
station.vessel
|
|
end
|
|
|
|
def execute
|
|
@block.call(self)
|
|
end
|
|
|
|
# Compare priorities of 2 `Order`
|
|
def <=>(right)
|
|
return 0 if @group != right.group # should not happen... raise an exception ?
|
|
@priority <=> right.priority
|
|
end
|
|
end
|
|
|
|
class Pilot(N) < Station(N)
|
|
# these properties handle manual override of trust (either analogic or exact computed)
|
|
property low_thrust_save, low_thrust_pressed, computer_input_thrust
|
|
# on the interface, the pilot can select a body to create relative vectors
|
|
property current_selected_body : Gravity::Body(N)?
|
|
# this is a hard limit for thrust so the pilot can avoid killing all the people aboard
|
|
property max_thrust
|
|
# permanent mode is the default modes to apply at all time
|
|
property permanent_modes : Array(Mode)
|
|
# temporary mode must be set at each tick and will be reset each time it is applied
|
|
property temporary_modes : Array(Mode)
|
|
|
|
enum Mode
|
|
Constant
|
|
Pulse
|
|
AntiGrav
|
|
AntiSpeed
|
|
end
|
|
|
|
def initialize(*p, **o)
|
|
super
|
|
@permanent_modes = Array(Mode).new
|
|
@temporary_modes = Array(Mode).new
|
|
@low_thrust_save = 0.0
|
|
@low_thrust_pressed = false
|
|
@computer_input_thrust = Vector(Float64, N).zero
|
|
@max_thrust = 20.0
|
|
@current_selected_body = nil
|
|
end
|
|
|
|
# Modify immediatly one elemnt of the acceleration vector
|
|
def set_exact_acceleration_axis(index : Int32, value : Float64)
|
|
vessel.gravity_body.acceleration[index] = value
|
|
end
|
|
|
|
# Modify immediatly the acceleration vector
|
|
def set_exact_acceleration_axis(axis : Vector(Float64, N))
|
|
axis.each_with_index do |value, index|
|
|
set_exact_acceleration_axis(index, value)
|
|
end
|
|
end
|
|
|
|
# set_exact_acceleration_axis({0, 23.0}, {1, 11.2}) # to set x;y at once
|
|
def set_exact_acceleration_axis(*tuples : Array(Tuple(Int32, Float64)))
|
|
tuples.each do |tuple|
|
|
set_exact_acceleration_axis(*tuple)
|
|
end
|
|
end
|
|
|
|
def immediate_modes : Array(Mode)
|
|
if !@temporary_modes.empty?
|
|
@temporary_modes
|
|
else
|
|
@permanent_modes
|
|
end
|
|
end
|
|
|
|
def apply_current_mode
|
|
new_acceleration_axis = Vector(Float64, N).zero
|
|
must_update_acceleration_axis = false
|
|
if immediate_modes.includes?(Mode::AntiGrav)
|
|
must_update_acceleration_axis = true
|
|
new_acceleration_axis.add!(-vessel.gravity)
|
|
end
|
|
|
|
if immediate_modes.includes?(Mode::AntiSpeed)
|
|
must_update_acceleration_axis = true
|
|
reset_acceleration = true
|
|
if (current_selected_body = @current_selected_body)
|
|
relative_speed = vessel.gravity_body.speed - current_selected_body.speed
|
|
new_acceleration_axis.add!(-relative_speed)
|
|
end
|
|
end
|
|
|
|
if new_acceleration_axis.magnitude > @max_thrust
|
|
new_acceleration_axis.normalize.mult!(@max_thrust)
|
|
end
|
|
set_exact_acceleration_axis(new_acceleration_axis) if must_update_acceleration_axis
|
|
|
|
@temporary_modes.clear # each time we apply the temporary_mode, reset it
|
|
end
|
|
|
|
def accelerate_current_vector!(coef : Float64)
|
|
set_exact_acceleration_axis(vessel.gravity_body.speed * coef)
|
|
end
|
|
end
|
|
|
|
class Navigation(N) < Station(N)
|
|
class Cadran
|
|
property radius : Array(Float64)
|
|
def initialize(default_radius : Float64 = 1000.0)
|
|
@radius = [] of Float64
|
|
@radius << default_radius if !default_radius.nil?
|
|
end
|
|
end
|
|
|
|
property zoom_select : Int32
|
|
property zoom_position : Vector(Float64, N)
|
|
property cadran : Cadran
|
|
property zoom_ratio : Float64
|
|
|
|
def initialize(*p, **o)
|
|
super
|
|
@zoom_ratio = 1.0
|
|
@zoom_select = 0
|
|
@zoom_position = Vector(Float64, N).zero
|
|
@cadran = Cadran.new
|
|
end
|
|
end
|
|
end
|