mirror of
https://github.com/TimeCrafters/timecrafters_configuration_tool_desktop.git
synced 2025-12-16 13:52:34 +00:00
Compare commits
69 Commits
v0.4.1_bet
...
f60aadac72
| Author | SHA1 | Date | |
|---|---|---|---|
| f60aadac72 | |||
| 628212b5e3 | |||
| 86c1f24337 | |||
| 3aa63b4ad1 | |||
| abdb86440f | |||
| b8cb9b9ab8 | |||
| c4622fa563 | |||
| d49938855d | |||
| 6554140acf | |||
| 43510faaa2 | |||
| 3350f4f063 | |||
| 49133bb49d | |||
| 8aaed6bc8d | |||
| 4bdc12ce46 | |||
| f98edc1744 | |||
| 9940837065 | |||
| a8ebaebe4c | |||
| 08bbd2c638 | |||
| 08fdd1de1b | |||
| ffe946ae15 | |||
| d94490110e | |||
| 1145ac646d | |||
| be1c1f4989 | |||
| caa2000e91 | |||
| 7bcc32097f | |||
| 2d2a4e0733 | |||
| d61785c98a | |||
| 07fdf0055e | |||
| 2a8a2d1bbf | |||
| 48b4d6ddcf | |||
| 0cd4475a8f | |||
| acc0792d17 | |||
| 6f211adf28 | |||
| 3d29ff46e6 | |||
| 27d0b7314f | |||
| 308575dc63 | |||
| 94cd822b0c | |||
| c312f4839d | |||
| 3a8f4e5860 | |||
| 7e09031257 | |||
| 905ced174b | |||
| 510a24644b | |||
| 570d965669 | |||
| 60356fc7fa | |||
| 41f5710b4a | |||
| 1e21c64a18 | |||
| 451568003a | |||
| cce1c2c341 | |||
| d5cf1cb6a2 | |||
| a96cc7c604 | |||
| c2e527653d | |||
| 66b0eb3d1e | |||
| 655b418d70 | |||
| a8fc2dccde | |||
| 0d6fb8a657 | |||
| 15d8e2ff62 | |||
| 9b505b8201 | |||
| c609734357 | |||
| fd6eb64232 | |||
| 7bfc404413 | |||
| 870a3e4e8b | |||
| aa6d53dd5e | |||
| 0f0009bcf0 | |||
| 7b7efabaf6 | |||
| 2948b02f12 | |||
| 44523b0bf2 | |||
| 7d2d44c52f | |||
| a34d4bbeb2 | |||
| e915cbb72f |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -2,4 +2,10 @@ pkg/*
|
||||
data/**/*.json
|
||||
data/settings.json
|
||||
data/simulator.rb
|
||||
data/*.csv
|
||||
data/*.csv
|
||||
media/sounds/*
|
||||
!media/sounds/.gitkeep
|
||||
media/particles/*
|
||||
!media/particles/.gitkeep
|
||||
media/music/*
|
||||
!media/music/.gitkeep
|
||||
8
Gemfile
8
Gemfile
@@ -1,10 +1,8 @@
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "cyberarm_engine"
|
||||
gem "gosu_notifications"
|
||||
gem "ffi"
|
||||
gem "clipboard"
|
||||
|
||||
group :packaging do
|
||||
gem "ocra"
|
||||
end
|
||||
gem "ocran"
|
||||
# gem "releasy", path: "../releasy"
|
||||
end
|
||||
|
||||
18
Gemfile.lock
Normal file
18
Gemfile.lock
Normal file
@@ -0,0 +1,18 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
cyberarm_engine (0.24.4)
|
||||
gosu (~> 1.1)
|
||||
gosu (1.4.6)
|
||||
ocran (1.3.15)
|
||||
|
||||
PLATFORMS
|
||||
x64-mingw-ucrt
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
cyberarm_engine
|
||||
ocran
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.14
|
||||
41
README.md
41
README.md
@@ -1 +1,42 @@
|
||||
# TimeCrafters Configuration Tool for Desktop
|
||||
A desktop app for editing, either locally or remotely, JSON configuration files on the Robot Controller/Rev Control Hub.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
* TACNET - **T**imeCrafters **A**uxiliary **C**onfiguration **NET**work.
|
||||
* Enables syncing configurations between devices.
|
||||
|
||||
* Multiple Configurations
|
||||
* Create multiple configurations for specific robots/projects.
|
||||
|
||||
* Presets
|
||||
* Save Groups or Actions as Presets to quickly add pre-configurated Groups and Actions.
|
||||
|
||||
* Search
|
||||
* Search through the active configurations Groups, Actions, Variables and Presets.
|
||||
|
||||
* Built-in practice clock
|
||||
* Supports multiple screens or network remote control.
|
||||
* Supports official clock sounds (must be added manually.)
|
||||
* Built-in jukebox.
|
||||
|
||||
* Simulator
|
||||
* Create a simple 2D simulation of your robot's path.
|
||||
|
||||
* Field Planner
|
||||
* Estimate distances on the field using imperial or metric units.
|
||||
|
||||
## Usage
|
||||
* Download from [Releases](https://github.com/TimeCrafters/timecrafters_configuration_tool_desktop/releases/latest)
|
||||
|
||||
## Developing
|
||||
* Install [Ruby](https://ruby-lang.org)
|
||||
* Run `bundle install`
|
||||
* Run `bundle exec ruby timecrafters_configuration_tool.rb`
|
||||
|
||||
## Contributing
|
||||
* Clone this repo and create a new branch for your feature/patch.
|
||||
* Author your changes
|
||||
* Commit your changes and push to your fork
|
||||
* Open a pull request
|
||||
|
||||
16
Rakefile
16
Rakefile
@@ -7,13 +7,23 @@ Releasy::Project.new do
|
||||
version TAC::VERSION
|
||||
|
||||
executable "timecrafters_configuration_tool.rb"
|
||||
files ["lib/**/*.*", "media/**/*.*", "data"]
|
||||
exclude_encoding # Applications that don't use advanced encoding (e.g. Japanese characters) can save build size with this.
|
||||
files [
|
||||
"lib/**/*.*",
|
||||
"data/.gitkeep",
|
||||
"data/configs/.gitkeep",
|
||||
"media/*.*",
|
||||
"media/icons/*.*",
|
||||
"media/fonts/*.*",
|
||||
"media/sounds/.gitkeep",
|
||||
"media/music/.gitkeep",
|
||||
"media/particles/.gitkeep"
|
||||
]
|
||||
# exclude_encoding # Applications that don't use advanced encoding (e.g. Japanese characters) can save build size with this.
|
||||
verbose
|
||||
|
||||
add_build :windows_folder do
|
||||
icon "media/icon.ico"
|
||||
executable_type :console # Assuming you don't want it to run with a console window.
|
||||
executable_type :windows # Assuming you don't want it to run with a console window.
|
||||
add_package :exe # Windows self-extracting archive.
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,6 +2,8 @@ module TAC
|
||||
class Backend
|
||||
attr_reader :config, :settings, :tacnet
|
||||
def initialize
|
||||
create_directories
|
||||
|
||||
load_settings
|
||||
load_config(@settings.config) if @settings.config && File.exist?("#{TAC::CONFIGS_PATH}/#{@settings.config}.json")
|
||||
@tacnet = TACNET.new
|
||||
@@ -42,13 +44,13 @@ module TAC
|
||||
end
|
||||
|
||||
def move_config(old_name, new_name)
|
||||
if not File.exists?("#{TAC::CONFIGS_PATH}/#{old_name}.json") or
|
||||
if not File.exist?("#{TAC::CONFIGS_PATH}/#{old_name}.json") or
|
||||
File.directory?("#{TAC::CONFIGS_PATH}/#{old_name}.json")
|
||||
# move_config: Can not move config file "#{old_name}" does not exist!
|
||||
return false
|
||||
end
|
||||
|
||||
if File.exists?("#{TAC::CONFIGS_PATH}/#{new_name}.json") &&
|
||||
if File.exist?("#{TAC::CONFIGS_PATH}/#{new_name}.json") &&
|
||||
!File.directory?("#{TAC::CONFIGS_PATH}/#{old_name}.json")
|
||||
# move_config: Config file "#{new_name}" already exist!
|
||||
return false
|
||||
@@ -61,7 +63,7 @@ module TAC
|
||||
end
|
||||
|
||||
def delete_config(config_name)
|
||||
FileUtils.rm("#{TAC::CONFIGS_PATH}/#{config_name}.json") if File.exists?("#{TAC::CONFIGS_PATH}/#{config_name}.json")
|
||||
FileUtils.rm("#{TAC::CONFIGS_PATH}/#{config_name}.json") if File.exist?("#{TAC::CONFIGS_PATH}/#{config_name}.json")
|
||||
end
|
||||
|
||||
|
||||
@@ -101,7 +103,7 @@ module TAC
|
||||
end
|
||||
|
||||
def refresh_tacnet_status
|
||||
$window.current_state.refresh_tacnet_status
|
||||
CyberarmEngine::Window.instance.current_state.refresh_tacnet_status
|
||||
end
|
||||
|
||||
|
||||
@@ -113,6 +115,12 @@ module TAC
|
||||
@settings_changed
|
||||
end
|
||||
|
||||
def create_directories
|
||||
FileUtils.mkdir_p(TAC::ROOT_PATH) unless File.exist?(TAC::ROOT_PATH)
|
||||
FileUtils.mkdir_p(TAC::CONFIGS_PATH) unless File.exist?(TAC::CONFIGS_PATH)
|
||||
# FileUtils.mkdir_p(TAC::SETTINGS_PATH) unless File.exist?(TAC::SETTINGS_PATH)
|
||||
end
|
||||
|
||||
def load_settings
|
||||
if File.exist?(TAC::SETTINGS_PATH)
|
||||
@settings = TAC::Settings.new
|
||||
|
||||
@@ -5,51 +5,32 @@ module TAC
|
||||
background Gosu::Color.new(0xaa_000000)
|
||||
|
||||
@title = @options[:title] ? @options[:title] : "#{self.class}"
|
||||
@window_width, @window_height = window.width, window.height
|
||||
@previous_state = window.previous_state
|
||||
|
||||
@dialog_root = stack width: 0.25, border_thickness: 2, border_color: [TAC::Palette::TIMECRAFTERS_PRIMARY, TAC::Palette::TIMECRAFTERS_SECONDARY] do
|
||||
@dialog_root = stack(width: 0.25, h_align: :center, v_align: :center, border_thickness: 2, border_color: [TAC::Palette::TIMECRAFTERS_PRIMARY, TAC::Palette::TIMECRAFTERS_SECONDARY]) do
|
||||
# Title bar
|
||||
@titlebar = flow width: 1.0 do
|
||||
@titlebar = flow(width: 1.0, height: 36) do
|
||||
background [TAC::Palette::TIMECRAFTERS_PRIMARY, TAC::Palette::TIMECRAFTERS_SECONDARY]
|
||||
|
||||
# title
|
||||
flow width: 0.9 do
|
||||
label @title, text_size: THEME_SUBHEADING_TEXT_SIZE, width: 1.0, text_align: :center, text_border: true, text_border_color: 0xff_222222, text_border_size: 1
|
||||
end
|
||||
para @title, text_size: THEME_SUBHEADING_TEXT_SIZE, font: TAC::THEME_BOLD_FONT, fill: true, text_align: :center, text_border: true, text_border_color: 0xaa_222222, text_border_size: 1
|
||||
|
||||
# Buttons
|
||||
flow width: 0.0999 do
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/cross.png"), image_width: 1.0, **THEME_DANGER_BUTTON do
|
||||
close
|
||||
end
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/cross.png"), image_height: 1.0, **THEME_DANGER_BUTTON do
|
||||
close
|
||||
end
|
||||
end
|
||||
|
||||
# Dialog body
|
||||
@dialog_content = stack width: 1.0 do
|
||||
@dialog_content = stack(width: 1.0, scroll: true) do
|
||||
end
|
||||
end
|
||||
|
||||
@dialog_content.clear do
|
||||
build
|
||||
end
|
||||
|
||||
@root_container.recalculate
|
||||
@root_container.recalculate
|
||||
@root_container.recalculate
|
||||
|
||||
center_dialog
|
||||
end
|
||||
|
||||
def build
|
||||
end
|
||||
|
||||
def center_dialog
|
||||
@dialog_root.style.x = window.width / 2 - @dialog_root.width / 2
|
||||
@dialog_root.style.y = window.height / 2 - @dialog_root.height / 2
|
||||
end
|
||||
|
||||
def name_filter(text)
|
||||
text.match(/[A-Za-z0-9._\- ]/) ? text : ""
|
||||
end
|
||||
@@ -76,11 +57,12 @@ module TAC
|
||||
|
||||
def _deep_dive_interactive_elements(element, list)
|
||||
element.children.each do |child|
|
||||
if child.visible? && child.is_a?(CyberarmEngine::Element::EditLine) ||
|
||||
child.is_a?(CyberarmEngine::Element::EditBox) ||
|
||||
child.is_a?(CyberarmEngine::Element::CheckBox) ||
|
||||
child.is_a?(CyberarmEngine::Element::ToggleButton) ||
|
||||
child.is_a?(CyberarmEngine::Element::ListBox)
|
||||
if child.visible? && child.enabled? &&
|
||||
child.is_a?(CyberarmEngine::Element::EditLine) ||
|
||||
child.is_a?(CyberarmEngine::Element::EditBox) ||
|
||||
child.is_a?(CyberarmEngine::Element::CheckBox) ||
|
||||
child.is_a?(CyberarmEngine::Element::ToggleButton) ||
|
||||
child.is_a?(CyberarmEngine::Element::ListBox)
|
||||
|
||||
list << child
|
||||
elsif child.visible? && child.is_a?(CyberarmEngine::Element::Container)
|
||||
@@ -90,24 +72,12 @@ module TAC
|
||||
end
|
||||
|
||||
def draw
|
||||
@previous_state.draw
|
||||
previous_state&.draw
|
||||
Gosu.flush
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def update
|
||||
super
|
||||
|
||||
if window.width != @window_width or window.height != @window_height
|
||||
request_recalculate
|
||||
|
||||
@window_width, @window_height = window.width, window.height
|
||||
end
|
||||
|
||||
center_dialog
|
||||
end
|
||||
|
||||
def button_down(id)
|
||||
super
|
||||
|
||||
@@ -122,7 +92,7 @@ module TAC
|
||||
end
|
||||
|
||||
def close
|
||||
$window.pop_state
|
||||
pop_state
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,15 +4,15 @@ module TAC
|
||||
def build
|
||||
background Gosu::Color::GRAY
|
||||
|
||||
label "Name", width: 1.0, text_align: :center
|
||||
@name_error = label "Error", color: TAC::Palette::TACNET_CONNECTION_ERROR
|
||||
para "Name", width: 1.0, text_align: :center
|
||||
@name_error = para "Error", color: TAC::Palette::TACNET_CONNECTION_ERROR
|
||||
@name_error.hide
|
||||
@name = edit_line @options[:action] ? @options[:action].name : "", filter: method(:name_filter), width: 1.0, autofocus: true
|
||||
@name = edit_line @options[:action] ? @options[:action].name : "", filter: method(:name_filter), width: 1.0, autofocus: true, focus: true
|
||||
@name.subscribe(:changed) do |sender, value|
|
||||
valid?
|
||||
end
|
||||
|
||||
label "Comment", width: 1.0, text_align: :center
|
||||
para "Comment", width: 1.0, text_align: :center
|
||||
@comment = edit_line @options[:action] ? @options[:action].comment : "", width: 1.0
|
||||
|
||||
flow width: 1.0, margin_top: THEME_DIALOG_BUTTON_PADDING do
|
||||
|
||||
@@ -3,7 +3,7 @@ module TAC
|
||||
class AlertDialog < Dialog
|
||||
def build
|
||||
background Gosu::Color::GRAY
|
||||
label @options[:message]
|
||||
para @options[:message]
|
||||
|
||||
button "Close", width: 1.0, margin_top: THEME_DIALOG_BUTTON_PADDING do
|
||||
try_commit
|
||||
|
||||
@@ -7,12 +7,11 @@ module TAC
|
||||
|
||||
color = @dangerous ? Palette::DANGEROUS : Palette::ALERT
|
||||
|
||||
|
||||
@dialog_root.style.border_color = [ color, darken(color, 50) ]
|
||||
@titlebar.style.background = [ color, darken(color, 50) ]
|
||||
@dialog_root.style.default[:border_color] = [ color, darken(color, 50) ]
|
||||
@titlebar.style.default[:background] = [ color, darken(color, 50) ]
|
||||
|
||||
background Gosu::Color::GRAY
|
||||
label @options[:message]
|
||||
para @options[:message]
|
||||
|
||||
flow width: 1.0, margin_top: THEME_DIALOG_BUTTON_PADDING do
|
||||
button "Cancel", width: 0.5 do
|
||||
@@ -23,17 +22,17 @@ module TAC
|
||||
try_commit(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def try_commit(force = false)
|
||||
if !@dangerous
|
||||
close
|
||||
def try_commit(force = false)
|
||||
if !@dangerous
|
||||
close
|
||||
|
||||
@options[:callback_method].call
|
||||
elsif @dangerous && force
|
||||
close
|
||||
@options[:callback_method].call
|
||||
elsif @dangerous && force
|
||||
close
|
||||
|
||||
@options[:callback_method].call
|
||||
end
|
||||
@options[:callback_method].call
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,10 +5,10 @@ module TAC
|
||||
|
||||
def build
|
||||
background Gosu::Color::GRAY
|
||||
label "Name", width: 1.0, text_align: :center
|
||||
@name_error = label "", color: TAC::Palette::TACNET_CONNECTION_ERROR
|
||||
para "Name", width: 1.0, text_align: :center
|
||||
@name_error = para "", color: TAC::Palette::TACNET_CONNECTION_ERROR
|
||||
@name_error.hide
|
||||
@name = edit_line @options[:renaming] ? @options[:renaming].name : "", filter: method(:name_filter), width: 1.0, autofocus: true
|
||||
@name = edit_line @options[:renaming] ? @options[:renaming].name : "", filter: method(:name_filter), width: 1.0, autofocus: true, focus: true
|
||||
|
||||
@name.subscribe(:changed) do |sender, value|
|
||||
valid?
|
||||
|
||||
@@ -2,13 +2,13 @@ module TAC
|
||||
class Dialog
|
||||
class TACNETDialog < Dialog
|
||||
def build
|
||||
@dialog_root.style.border_color = [ Palette::TACNET_PRIMARY, Palette::TACNET_SECONDARY ]
|
||||
@titlebar.style.background = [ Palette::TACNET_PRIMARY, Palette::TACNET_SECONDARY ]
|
||||
@dialog_root.style.default[:border_color] = [ Palette::TACNET_PRIMARY, Palette::TACNET_SECONDARY ]
|
||||
@titlebar.style.default[:background] = [ Palette::TACNET_PRIMARY, Palette::TACNET_SECONDARY ]
|
||||
|
||||
background Gosu::Color::GRAY
|
||||
label @options[:message], width: 1.0
|
||||
para @options[:message], width: 1.0
|
||||
|
||||
@sound = Gosu::Sample.new("#{TAC::ROOT_PATH}/media/error_alarm.ogg").play(1, 1, true)
|
||||
@sound = Gosu::Sample.new("#{TAC::MEDIA_PATH}/error_alarm.ogg").play(1, 1, true)
|
||||
|
||||
button "Close", width: 1.0, margin_top: THEME_DIALOG_BUTTON_PADDING do
|
||||
try_commit
|
||||
|
||||
@@ -3,14 +3,14 @@ module TAC
|
||||
class TACNETStatusDialog < Dialog
|
||||
def build
|
||||
background Gosu::Color::GRAY
|
||||
@message_label = label $window.backend.tacnet.full_status
|
||||
@message_label = para CyberarmEngine::Window.instance.backend.tacnet.full_status
|
||||
|
||||
button "Close", width: 1.0, margin_top: THEME_DIALOG_BUTTON_PADDING do
|
||||
try_commit
|
||||
end
|
||||
|
||||
@timer = CyberarmEngine::Timer.new(1000.0) do
|
||||
@message_label.value = $window.backend.tacnet.full_status
|
||||
@message_label.value = CyberarmEngine::Window.instance.backend.tacnet.full_status
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -6,20 +6,20 @@ module TAC
|
||||
|
||||
@type = @options[:variable].type if @options[:variable]
|
||||
|
||||
label "Name", width: 1.0, text_align: :center
|
||||
@name_error = label "Error", color: TAC::Palette::TACNET_CONNECTION_ERROR
|
||||
para "Name", width: 1.0, text_align: :center
|
||||
@name_error = para "Error", color: TAC::Palette::TACNET_CONNECTION_ERROR
|
||||
@name_error.hide
|
||||
@name = edit_line @options[:variable] ? @options[:variable].name : "", filter: method(:name_filter), width: 1.0, autofocus: true
|
||||
@name = edit_line @options[:variable] ? @options[:variable].name : "", filter: method(:name_filter), width: 1.0, autofocus: true, focus: true
|
||||
@name.subscribe(:changed) do |sender, value|
|
||||
valid?
|
||||
end
|
||||
|
||||
label "Type", width: 1.0, text_align: :center
|
||||
@type_error = label "Error", color: TAC::Palette::TACNET_CONNECTION_ERROR
|
||||
para "Type", width: 1.0, text_align: :center
|
||||
@type_error = para "Error", color: TAC::Palette::TACNET_CONNECTION_ERROR
|
||||
@type_error.hide
|
||||
|
||||
@var_type = list_box items: [:float, :double, :integer, :long, :string, :boolean], choose: @type ? @type : :double, width: 1.0 do |item|
|
||||
@type = item.value.to_sym
|
||||
@type = item.to_sym
|
||||
|
||||
if @type == :boolean
|
||||
@value.hide
|
||||
@@ -35,8 +35,8 @@ module TAC
|
||||
@type ||= @var_type.value.to_sym
|
||||
|
||||
@value_container = stack width: 1.0 do
|
||||
label "Value", width: 1.0, text_align: :center
|
||||
@value_error = label "Error", color: TAC::Palette::TACNET_CONNECTION_ERROR
|
||||
para "Value", width: 1.0, text_align: :center
|
||||
@value_error = para "Error", color: TAC::Palette::TACNET_CONNECTION_ERROR
|
||||
@value_error.hide
|
||||
@value = edit_line @options[:variable] ? @options[:variable].value : "", width: 1.0
|
||||
@value_boolean = check_box "Boolean", checked: @options[:variable] ? @options[:variable].value == "true" : false
|
||||
@@ -79,23 +79,28 @@ module TAC
|
||||
|
||||
def valid?
|
||||
valid = true
|
||||
name = @name.value.strip
|
||||
|
||||
if @name.value.strip.empty?
|
||||
if name.empty?
|
||||
@name_error.value = "Error: Name cannot be blank or only whitespace."
|
||||
@name_error.show
|
||||
valid = false
|
||||
else
|
||||
|
||||
### Don't error if renaming variable to itself
|
||||
elsif @options[:variable] && @options[:variable].name == name
|
||||
@name_error.value = ""
|
||||
@name_error.hide
|
||||
|
||||
elsif @options[:list].find { |variable| variable.name == name }
|
||||
@name_error.value = "Error: Name is not unique!"
|
||||
@name_error.show
|
||||
valid = false
|
||||
end
|
||||
|
||||
if not @type
|
||||
@type_error.value = "Error: Type not set."
|
||||
@type_error.show
|
||||
valid = false
|
||||
else
|
||||
@type_error.value = ""
|
||||
@type_error.hide
|
||||
end
|
||||
|
||||
if [:integer, :float, :double, :long].include?(@type)
|
||||
@@ -121,14 +126,11 @@ module TAC
|
||||
@value_error.show
|
||||
valid = false
|
||||
end
|
||||
else
|
||||
@value_error.value = ""
|
||||
@value_error.hide
|
||||
end
|
||||
|
||||
elsif @type == :string
|
||||
if @value.value.strip.empty?
|
||||
@value_error.value = "Error: Value cannot be blank or only whitespace."
|
||||
@value_error.value = "Error: Value cannot be blank or only whitespace."
|
||||
@value_error.show
|
||||
valid = false
|
||||
end
|
||||
@@ -141,6 +143,13 @@ module TAC
|
||||
valid = false
|
||||
end
|
||||
|
||||
if valid
|
||||
@value_error.value = ""
|
||||
@value_error.hide
|
||||
@type_error.value = ""
|
||||
@type_error.hide
|
||||
end
|
||||
|
||||
return valid
|
||||
end
|
||||
end
|
||||
|
||||
69
lib/game_clock/clock.rb
Normal file
69
lib/game_clock/clock.rb
Normal file
@@ -0,0 +1,69 @@
|
||||
module TAC
|
||||
class PracticeGameClock
|
||||
class Clock
|
||||
CLOCK_SIZE = Gosu.screen_height
|
||||
TITLE_SIZE = 128
|
||||
|
||||
attr_reader :title, :controller
|
||||
|
||||
def initialize
|
||||
@title = CyberarmEngine::Text.new("FIRST TECH CHALLENGE", size: TITLE_SIZE, text_shadow: true, y: 10, color: Gosu::Color::GRAY)
|
||||
@title.x = CyberarmEngine::Window.instance.width / 2 - @title.width / 2
|
||||
|
||||
@text = CyberarmEngine::Text.new(":1234567890", size: CLOCK_SIZE, text_border: true, border_size: 2, border_color: Gosu::Color::GRAY)
|
||||
@text.width # trigger font-eager loading
|
||||
|
||||
@title.z, @text.z = -1, -1
|
||||
|
||||
@controller = nil
|
||||
end
|
||||
|
||||
def controller=(controller)
|
||||
@controller = controller
|
||||
end
|
||||
|
||||
def draw
|
||||
@title.draw
|
||||
@text.draw
|
||||
end
|
||||
|
||||
def update
|
||||
@title.x = CyberarmEngine::Window.instance.width / 2 - @title.width / 2
|
||||
|
||||
if @controller
|
||||
@text.color = @controller.display_color
|
||||
@text.text = clock_time(@controller.time_left)
|
||||
else
|
||||
@text.color = Gosu::Color::WHITE
|
||||
@text.text = "0:00"
|
||||
end
|
||||
|
||||
@text.x = CyberarmEngine::Window.instance.width / 2 - @text.textobject.text_width("0:00") / 2
|
||||
@text.y = CyberarmEngine::Window.instance.height / 2 - @text.height / 2
|
||||
|
||||
@controller&.update
|
||||
end
|
||||
|
||||
def active?
|
||||
if @controller
|
||||
@controller.clock? || @controller.countdown?
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def value
|
||||
@text.text
|
||||
end
|
||||
|
||||
def clock_time(time_left)
|
||||
minutes = ((time_left + 1.0) / 60.0).floor
|
||||
|
||||
seconds = format("%02d", time_left.ceil % 60)
|
||||
|
||||
return "#{minutes}:#{seconds}" if time_left.ceil.even?
|
||||
return "#{minutes}<c=999999>:</c>#{seconds}" if time_left.ceil.odd?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
136
lib/game_clock/clock_controller.rb
Normal file
136
lib/game_clock/clock_controller.rb
Normal file
@@ -0,0 +1,136 @@
|
||||
module TAC
|
||||
class PracticeGameClock
|
||||
class ClockController
|
||||
Event = Struct.new(:event, :trigger_after, :arguments)
|
||||
|
||||
include EventHandlers
|
||||
|
||||
def self.create_event(event, trigger_after, arguments = nil)
|
||||
arguments = [arguments] unless arguments.is_a?(Array) || arguments == nil
|
||||
|
||||
Event.new(event, trigger_after, arguments)
|
||||
end
|
||||
|
||||
AUTONOMOUS = [
|
||||
create_event(:change_clock, 0.0, "2:30"),
|
||||
create_event(:change_countdown, 0.0, "0:03"),
|
||||
create_event(:change_color, 0.0, :red),
|
||||
create_event(:change_display, 0.0, :countdown),
|
||||
create_event(:start_countdown, 0.0),
|
||||
create_event(:play_sound, 0.0, :autonomous_countdown),
|
||||
create_event(:change_color, 3.0, :white),
|
||||
create_event(:change_display, 3.0, :clock),
|
||||
create_event(:play_sound, 3.0, :autonomous_start),
|
||||
create_event(:change_display, 3.0, :clock),
|
||||
create_event(:stop_countdown, 3.0),
|
||||
create_event(:start_clock, 3.0),
|
||||
create_event(:play_sound, 33.0, :autonomous_ended),
|
||||
create_event(:stop_clock, 33.0),
|
||||
].freeze
|
||||
|
||||
PRE_TELEOP = [
|
||||
create_event(:change_color, 33.0, :orange),
|
||||
create_event(:change_countdown, 33.0, "0:08"),
|
||||
create_event(:change_display, 33.0, :countdown),
|
||||
create_event(:start_countdown, 33.0),
|
||||
create_event(:play_sound, 35.0, :teleop_pickup_controllers),
|
||||
create_event(:change_color, 38.0, :red),
|
||||
create_event(:play_sound, 38.0, :teleop_countdown),
|
||||
create_event(:stop_countdown, 41.0),
|
||||
].freeze
|
||||
|
||||
TELEOP_ENDGAME = [
|
||||
create_event(:change_color, 131.0, :white),
|
||||
create_event(:change_clock, 131.0, "0:30"),
|
||||
create_event(:start_clock, 131.0),
|
||||
create_event(:play_sound, 131.0, :end_game),
|
||||
# create_event(:play_sound, 158.0, :autonomous_countdown), # Not played here anymore
|
||||
create_event(:play_sound, 161.0, :end_match),
|
||||
create_event(:stop_clock, 161.0),
|
||||
].freeze
|
||||
|
||||
TELEOP = [
|
||||
create_event(:change_color, 41.0, :white),
|
||||
create_event(:change_clock, 41.0, "2:00"),
|
||||
create_event(:play_sound, 41.0, :teleop_started),
|
||||
create_event(:change_display, 41.0, :clock),
|
||||
create_event(:start_clock, 41.0),
|
||||
TELEOP_ENDGAME
|
||||
].flatten.freeze
|
||||
|
||||
FULL_TELEOP = [
|
||||
PRE_TELEOP,
|
||||
TELEOP,
|
||||
TELEOP_ENDGAME,
|
||||
].flatten.freeze
|
||||
|
||||
FULL_MATCH = [
|
||||
# Autonomous
|
||||
AUTONOMOUS,
|
||||
FULL_TELEOP
|
||||
].flatten.freeze
|
||||
|
||||
attr_reader :display_color
|
||||
def initialize(elapsed_time = 0, events = [])
|
||||
@events = events.dup
|
||||
@last_update = Gosu.milliseconds
|
||||
|
||||
@elapsed_time = elapsed_time
|
||||
@display = :clock
|
||||
|
||||
@clock_time = 0.0
|
||||
@countdown_time = 0.0
|
||||
|
||||
@clock_running = false
|
||||
@countdown_running = false
|
||||
|
||||
@display_color = Gosu::Color::WHITE
|
||||
end
|
||||
|
||||
def update
|
||||
dt = (Gosu.milliseconds - @last_update) / 1000.0
|
||||
update_active_timer(dt)
|
||||
|
||||
@events.select { |event| event.trigger_after <= @elapsed_time }.each do |event|
|
||||
@events.delete(event)
|
||||
|
||||
if event.arguments
|
||||
self.send(event.event, *event.arguments)
|
||||
else
|
||||
self.send(event.event)
|
||||
end
|
||||
end
|
||||
|
||||
@last_update = Gosu.milliseconds
|
||||
end
|
||||
|
||||
def update_active_timer(dt)
|
||||
if @clock_running
|
||||
@clock_time -= dt
|
||||
elsif @countdown_running
|
||||
@countdown_time -= dt
|
||||
end
|
||||
|
||||
@elapsed_time += dt
|
||||
end
|
||||
|
||||
def clock?
|
||||
@clock_running
|
||||
end
|
||||
|
||||
def countdown?
|
||||
@countdown_running
|
||||
end
|
||||
|
||||
def time_left
|
||||
if @clock_running
|
||||
return @clock_time
|
||||
elsif @countdown_running
|
||||
return @countdown_time
|
||||
else
|
||||
return 0 # Clock defaults to showing "0:00" if there is no clock or countdown
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
107
lib/game_clock/clock_proxy.rb
Normal file
107
lib/game_clock/clock_proxy.rb
Normal file
@@ -0,0 +1,107 @@
|
||||
module TAC
|
||||
class PracticeGameClock
|
||||
class ClockProxy
|
||||
attr_reader :queue, :clock
|
||||
|
||||
def initialize(clock, jukebox)
|
||||
@clock = clock
|
||||
@jukebox = jukebox
|
||||
|
||||
@queue = []
|
||||
@callbacks = {}
|
||||
end
|
||||
|
||||
def enqueue(&block)
|
||||
@queue << block
|
||||
end
|
||||
|
||||
def register(callback, method)
|
||||
@callbacks[callback] = method
|
||||
end
|
||||
|
||||
def start_clock(mode)
|
||||
return if @clock.active? || CyberarmEngine::Window.instance.current_state.is_a?(Randomizer)
|
||||
|
||||
@clock.controller = case mode
|
||||
when :full_match
|
||||
ClockController.new(0, ClockController::FULL_MATCH)
|
||||
when :autonomous
|
||||
ClockController.new(0, ClockController::AUTONOMOUS)
|
||||
when :full_teleop
|
||||
ClockController.new(33.0, ClockController::FULL_TELEOP)
|
||||
when :teleop_only
|
||||
ClockController.new(41.0, ClockController::TELEOP)
|
||||
when :endgame_only
|
||||
ClockController.new(131.0, ClockController::TELEOP_ENDGAME)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def abort_clock
|
||||
@clock.controller&.play_sound(:abort_match) if @clock.active?
|
||||
@clock.controller = nil
|
||||
end
|
||||
|
||||
def set_clock_title(string)
|
||||
@clock.title.text = string.to_s
|
||||
@clock.title.x = CyberarmEngine::Window.instance.width / 2 - @clock.title.width / 2
|
||||
end
|
||||
|
||||
def get_clock_title(string)
|
||||
@clock.title
|
||||
end
|
||||
|
||||
def jukebox_previous_track
|
||||
@jukebox.previous_track
|
||||
end
|
||||
|
||||
def jukebox_next_track
|
||||
@jukebox.next_track
|
||||
end
|
||||
|
||||
def jukebox_stop
|
||||
@jukebox.stop
|
||||
end
|
||||
|
||||
def jukebox_play
|
||||
@jukebox.play
|
||||
end
|
||||
|
||||
def jukebox_pause
|
||||
@jukebox.pause
|
||||
end
|
||||
|
||||
def jukebox_set_volume(float)
|
||||
@jukebox.set_volume(float)
|
||||
end
|
||||
|
||||
def jukebox_volume
|
||||
@jukebox.volume
|
||||
end
|
||||
|
||||
def jukebox_current_track
|
||||
@jukebox.now_playing
|
||||
end
|
||||
|
||||
def jukebox_set_sound_effects(boolean)
|
||||
@jukebox.set_sfx(boolean)
|
||||
end
|
||||
|
||||
def jukebox_volume_changed(float)
|
||||
@callbacks[:volume_changed]&.call(float)
|
||||
end
|
||||
|
||||
def jukebox_track_changed(name)
|
||||
@callbacks[:track_changed]&.call(name)
|
||||
end
|
||||
|
||||
def randomizer_changed(boolean)
|
||||
@callbacks[:randomizer_changed]&.call(boolean)
|
||||
end
|
||||
|
||||
def shutdown!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
95
lib/game_clock/event_handlers.rb
Normal file
95
lib/game_clock/event_handlers.rb
Normal file
@@ -0,0 +1,95 @@
|
||||
module TAC
|
||||
class PracticeGameClock
|
||||
module EventHandlers
|
||||
### Clock ###
|
||||
def start_clock
|
||||
@clock_running = true
|
||||
end
|
||||
|
||||
def stop_clock
|
||||
@clock_running = false
|
||||
end
|
||||
|
||||
def change_clock(value)
|
||||
@clock_time = time_from_string(value)
|
||||
end
|
||||
|
||||
### Countdown ###
|
||||
def start_countdown
|
||||
@countdown_running = true
|
||||
end
|
||||
|
||||
def stop_countdown
|
||||
@countdown_running = false
|
||||
end
|
||||
|
||||
|
||||
def change_countdown(value)
|
||||
@countdown_time = time_from_string(value)
|
||||
end
|
||||
|
||||
def change_display(display)
|
||||
end
|
||||
|
||||
def change_color(color)
|
||||
out = case color
|
||||
when :white
|
||||
Gosu::Color::WHITE
|
||||
when :orange
|
||||
Gosu::Color.rgb(150, 75, 0)
|
||||
when :red
|
||||
Gosu::Color.rgb(150, 0, 0)
|
||||
end
|
||||
|
||||
### --- ###
|
||||
# OVERRIDE: offical CenterStage game clock no longer has colors
|
||||
### --- ###
|
||||
|
||||
out = Gosu::Color::WHITE
|
||||
|
||||
@display_color = out
|
||||
end
|
||||
|
||||
private def time_from_string(string)
|
||||
split = string.split(":")
|
||||
minutes = (split.first.to_i) * 60
|
||||
seconds = (split.last.to_i)
|
||||
|
||||
return minutes + seconds
|
||||
end
|
||||
|
||||
def play_sound(sound)
|
||||
path = nil
|
||||
case sound
|
||||
when :autonomous_countdown
|
||||
path = "sounds/3-2-1.wav"
|
||||
when :autonomous_start
|
||||
path = "sounds/charge.wav"
|
||||
when :autonomous_ended
|
||||
path = "sounds/endauto.wav"
|
||||
when :teleop_pickup_controllers
|
||||
path = "sounds/Pick_Up_Controllers.wav"
|
||||
when :abort_match
|
||||
path = "sounds/fogblast.wav"
|
||||
when :teleop_countdown
|
||||
path = "sounds/3-2-1.wav"
|
||||
when :teleop_started
|
||||
path = "sounds/firebell.wav"
|
||||
when :end_game
|
||||
path = "sounds/factwhistle.wav"
|
||||
when :end_match
|
||||
path = "sounds/endmatch.wav"
|
||||
end
|
||||
|
||||
path = "#{MEDIA_PATH}/#{path}"
|
||||
|
||||
if path && File.exist?(path) && !File.directory?(path)
|
||||
Jukebox::SAMPLES[path] = Gosu::Sample.new(path) unless Jukebox::SAMPLES[path].is_a?(Gosu::Sample)
|
||||
Jukebox::SAMPLES[path].play
|
||||
else
|
||||
warn "WARNING: Sample for #{sound.inspect} could not be found at '#{path}'"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
144
lib/game_clock/jukebox.rb
Normal file
144
lib/game_clock/jukebox.rb
Normal file
@@ -0,0 +1,144 @@
|
||||
module TAC
|
||||
class PracticeGameClock
|
||||
class Jukebox
|
||||
MUSIC = Dir.glob(MEDIA_PATH + "/music/*.*").freeze
|
||||
SAMPLES = {}
|
||||
|
||||
if File.exist?(MEDIA_PATH + "/sounds/skystone")
|
||||
BEEPS_AND_BOOPS = Dir.glob(MEDIA_PATH + "/sounds/skystone/*.*").freeze
|
||||
end
|
||||
|
||||
attr_reader :volume, :now_playing
|
||||
|
||||
def initialize(clock)
|
||||
@clock = clock
|
||||
|
||||
@order = MUSIC.shuffle
|
||||
@now_playing = ""
|
||||
@playing = false
|
||||
@song = nil
|
||||
@volume = 1.0
|
||||
@last_check_time = Gosu.milliseconds
|
||||
|
||||
@last_sfx_time = Gosu.milliseconds
|
||||
@sfx_random_interval = generate_sfx_period
|
||||
@play_sfx = true
|
||||
|
||||
if defined?(BEEPS_AND_BOOPS)
|
||||
BEEPS_AND_BOOPS.each do |beep|
|
||||
SAMPLES[beep] = Gosu::Sample.new(beep)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
return unless Gosu.milliseconds - @last_check_time >= 2000.0
|
||||
|
||||
@last_check_time = Gosu.milliseconds
|
||||
|
||||
if @song && !@song.playing? && !@song.paused?
|
||||
next_track if @playing
|
||||
end
|
||||
|
||||
if @play_sfx && defined?(BEEPS_AND_BOOPS)
|
||||
play_sfx
|
||||
end
|
||||
end
|
||||
|
||||
def play_sfx
|
||||
if Gosu.milliseconds - @last_sfx_time >= @sfx_random_interval
|
||||
@last_sfx_time = Gosu.milliseconds
|
||||
@sfx_random_interval = generate_sfx_period
|
||||
|
||||
pan = rand(0.49999..0.50001)
|
||||
volume = rand(0.75..1.0)
|
||||
speed = rand(0.5..1.25)
|
||||
SAMPLES[BEEPS_AND_BOOPS.sample].play_pan(pan, volume, speed) unless @clock.active?
|
||||
end
|
||||
end
|
||||
|
||||
def generate_sfx_period
|
||||
# rand(15..120) * 1000.0
|
||||
rand(5..10) * 1000.0
|
||||
end
|
||||
|
||||
def set_sfx(boolean)
|
||||
@play_sfx = boolean
|
||||
end
|
||||
|
||||
def play_sfx?
|
||||
@play_sfx
|
||||
end
|
||||
|
||||
def play
|
||||
if @song && @song.paused?
|
||||
@song.play
|
||||
else
|
||||
return false unless @order.size > 0
|
||||
|
||||
@current_song = @order.first
|
||||
@song = Gosu::Song.new(@current_song)
|
||||
@song.volume = @volume
|
||||
@song.play
|
||||
@now_playing = File.basename(@current_song)
|
||||
@order.rotate!(1)
|
||||
end
|
||||
|
||||
@playing = true
|
||||
end
|
||||
|
||||
def pause
|
||||
@playing = false
|
||||
@song.pause if @song
|
||||
end
|
||||
|
||||
def song
|
||||
@song
|
||||
end
|
||||
|
||||
def stop
|
||||
@song.stop if @song
|
||||
@playing = false
|
||||
@now_playing = ""
|
||||
end
|
||||
|
||||
def previous_track
|
||||
return false unless @order.size > 0
|
||||
|
||||
@song.stop if @song
|
||||
@order.rotate!(-1)
|
||||
@current_song = @order.first
|
||||
@song = Gosu::Song.new(@current_song)
|
||||
@song.volume = @volume
|
||||
@song.play
|
||||
|
||||
@playing = true
|
||||
@now_playing = File.basename(@current_song)
|
||||
end
|
||||
|
||||
def next_track
|
||||
return false unless @order.size > 0
|
||||
|
||||
@song.stop if @song
|
||||
@current_song = @order.first
|
||||
@song = Gosu::Song.new(@current_song)
|
||||
@song.volume = @volume
|
||||
@song.play
|
||||
@order.rotate!(1)
|
||||
|
||||
@playing = true
|
||||
@now_playing = File.basename(@current_song)
|
||||
end
|
||||
|
||||
def current_track
|
||||
@current_song
|
||||
end
|
||||
|
||||
def set_volume(float)
|
||||
@volume = float
|
||||
@volume = @volume.clamp(0.1, 1.0)
|
||||
@song.volume = @volume if @song
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
28
lib/game_clock/logger.rb
Normal file
28
lib/game_clock/logger.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
module TAC
|
||||
class PracticeGameClock
|
||||
class ClockNet
|
||||
class Logger
|
||||
def printer(message)
|
||||
# return
|
||||
puts "#{Time.now.strftime("%Y-%m-%d %H:%M:%S %Z")} #{message}"
|
||||
end
|
||||
|
||||
def i(tag, message)
|
||||
printer("INFO #{tag}: #{message}")
|
||||
end
|
||||
|
||||
def d(tag, message)
|
||||
printer("DEBUG #{tag}: #{message}")
|
||||
end
|
||||
|
||||
def e(tag, message)
|
||||
printer("ERROR #{tag}: #{message}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def log
|
||||
@logger ||= ClockNet::Logger.new
|
||||
end
|
||||
end
|
||||
end
|
||||
162
lib/game_clock/net/client.rb
Normal file
162
lib/game_clock/net/client.rb
Normal file
@@ -0,0 +1,162 @@
|
||||
require "securerandom"
|
||||
|
||||
module TAC
|
||||
class PracticeGameClock
|
||||
class ClockNet
|
||||
class Client
|
||||
TAG = "ClockNet|Client"
|
||||
CHUNK_SIZE = 4096
|
||||
PACKET_TAIL = "\r\n\n"
|
||||
|
||||
attr_reader :uuid, :read_queue, :write_queue, :socket,
|
||||
:packets_sent, :packets_received,
|
||||
:data_sent, :data_received
|
||||
attr_accessor :sync_interval, :last_socket_error, :socket_error
|
||||
def initialize
|
||||
@uuid = SecureRandom.uuid
|
||||
@read_queue = []
|
||||
@write_queue = []
|
||||
|
||||
@sync_interval = 100
|
||||
|
||||
@last_socket_error = nil
|
||||
@socket_error = false
|
||||
@bound = false
|
||||
|
||||
@packets_sent, @packets_received = 0, 0
|
||||
@data_sent, @data_received = 0, 0
|
||||
end
|
||||
|
||||
def uuid=(id)
|
||||
@uuid = id
|
||||
end
|
||||
|
||||
def socket=(socket)
|
||||
@socket = socket
|
||||
@bound = true
|
||||
|
||||
listen
|
||||
end
|
||||
|
||||
def listen
|
||||
Thread.new do
|
||||
while connected?
|
||||
# Read from socket
|
||||
while message_in = read
|
||||
if message_in.empty?
|
||||
break
|
||||
else
|
||||
log.i(TAG, "Read: " + message_in)
|
||||
|
||||
@read_queue << message_in
|
||||
|
||||
@packets_received += 1
|
||||
@data_received += message_in.length
|
||||
end
|
||||
end
|
||||
|
||||
sleep @sync_interval / 1000.0
|
||||
end
|
||||
end
|
||||
|
||||
Thread.new do
|
||||
while connected?
|
||||
# Write to socket
|
||||
while message_out = @write_queue.shift
|
||||
write(message_out)
|
||||
|
||||
@packets_sent += 1
|
||||
@data_sent += message_out.to_s.length
|
||||
log.i(TAG, "Write: " + message_out.to_s)
|
||||
end
|
||||
|
||||
sleep @sync_interval / 1000.0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def sync(block)
|
||||
block.call
|
||||
end
|
||||
|
||||
def handle_read_queue
|
||||
message = gets
|
||||
|
||||
while message
|
||||
puts(message)
|
||||
|
||||
log.i(TAG, "Writing to Queue: " + message)
|
||||
|
||||
message = gets
|
||||
end
|
||||
end
|
||||
|
||||
def socket_error?
|
||||
@socket_error
|
||||
end
|
||||
|
||||
def connected?
|
||||
if closed? == true || closed? == nil
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def closed?
|
||||
@socket.closed? if @socket
|
||||
end
|
||||
|
||||
def write(message)
|
||||
begin
|
||||
@socket.puts("#{message}#{PACKET_TAIL}")
|
||||
rescue => error
|
||||
@last_socket_error = error
|
||||
@socket_error = true
|
||||
log.e(TAG, error.message)
|
||||
close
|
||||
end
|
||||
end
|
||||
|
||||
def read
|
||||
begin
|
||||
message = @socket.gets
|
||||
rescue => error
|
||||
@last_socket_error = error
|
||||
@socket_error = true
|
||||
|
||||
message = ""
|
||||
end
|
||||
|
||||
|
||||
return message.to_s.strip
|
||||
end
|
||||
|
||||
def puts(message)
|
||||
@write_queue << message
|
||||
end
|
||||
|
||||
def gets
|
||||
@read_queue.shift
|
||||
end
|
||||
|
||||
def encode(message)
|
||||
return message
|
||||
end
|
||||
|
||||
def decode(blob)
|
||||
return blob
|
||||
end
|
||||
|
||||
def flush
|
||||
@socket.flush if socket
|
||||
end
|
||||
|
||||
def close(reason = nil)
|
||||
write(reason) if reason
|
||||
@socket.close if @socket
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
97
lib/game_clock/net/connection.rb
Normal file
97
lib/game_clock/net/connection.rb
Normal file
@@ -0,0 +1,97 @@
|
||||
module TAC
|
||||
class PracticeGameClock
|
||||
class ClockNet
|
||||
class Connection
|
||||
TAG = "ClockNet|Connection"
|
||||
attr_reader :hostname, :port, :client, :proxy_object
|
||||
|
||||
def initialize(hostname: "localhost", port: 4567, proxy_object:)
|
||||
@hostname = hostname
|
||||
@port = port
|
||||
@proxy_object = proxy_object
|
||||
|
||||
@client = nil
|
||||
|
||||
@last_sync_time = Gosu.milliseconds
|
||||
@sync_interval = SYNC_INTERVAL
|
||||
|
||||
@last_heartbeat_sent = Gosu.milliseconds
|
||||
@heartbeat_interval = HEARTBEAT_INTERVAL
|
||||
|
||||
@connection_handler = proc do
|
||||
handle_connection
|
||||
end
|
||||
|
||||
@packet_handler = PacketHandler.new(host_is_a_connection: true, proxy_object: @proxy_object)
|
||||
end
|
||||
|
||||
def connect
|
||||
return if @client
|
||||
|
||||
@client = Client.new
|
||||
|
||||
Thread.new do
|
||||
begin
|
||||
@client.socket = Socket.tcp(@hostname, @port, connect_timeout: 5)
|
||||
@client.socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
||||
log.i(TAG, "Connected to: #{@hostname}:#{@port}")
|
||||
|
||||
while @client && @client.connected?
|
||||
if Gosu.milliseconds > @last_sync_time + @sync_interval
|
||||
@last_sync_time = Gosu.milliseconds
|
||||
|
||||
@client.sync(@connection_handler)
|
||||
end
|
||||
end
|
||||
|
||||
rescue => error
|
||||
log.e(TAG, error)
|
||||
|
||||
if @client
|
||||
@client.close
|
||||
@client.last_socket_error = error
|
||||
@client.socket_error = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def handle_connection
|
||||
if @client && @client.connected?
|
||||
message = @client.gets
|
||||
|
||||
@packet_handler.handle(message) if message
|
||||
|
||||
if Gosu.milliseconds > @last_heartbeat_sent + @heartbeat_interval
|
||||
@last_heartbeat_sent = Gosu.milliseconds
|
||||
|
||||
@client.puts(PacketHandler.packet_heartbeat)
|
||||
end
|
||||
|
||||
sleep @sync_interval / 1000.0
|
||||
end
|
||||
end
|
||||
|
||||
def puts(packet)
|
||||
@client.puts(packet)
|
||||
end
|
||||
|
||||
def gets
|
||||
@client.gets
|
||||
end
|
||||
|
||||
def connected?
|
||||
@client.connected? if @client
|
||||
end
|
||||
|
||||
def closed?
|
||||
@client.closed? if @client
|
||||
end
|
||||
|
||||
def close
|
||||
@client.close if @client
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
109
lib/game_clock/net/packet.rb
Normal file
109
lib/game_clock/net/packet.rb
Normal file
@@ -0,0 +1,109 @@
|
||||
module TAC
|
||||
class PracticeGameClock
|
||||
class ClockNet
|
||||
SYNC_INTERVAL = 250
|
||||
HEARTBEAT_INTERVAL = 1_500
|
||||
|
||||
class Packet
|
||||
PROTOCOL_VERSION = 1
|
||||
PROTOCOL_SEPERATOR = "|"
|
||||
PROTOCOL_HEARTBEAT = "heartbeat"
|
||||
|
||||
PACKET_TYPES = {
|
||||
handshake: 0,
|
||||
heartbeat: 1,
|
||||
error: 2,
|
||||
shutdown: 3,
|
||||
|
||||
start_clock: 10,
|
||||
abort_clock: 11,
|
||||
|
||||
set_clock_title: 20,
|
||||
get_clock_title: 21,
|
||||
clock_title: 22,
|
||||
clock_time: 23,
|
||||
|
||||
randomizer_visible: 27,
|
||||
|
||||
jukebox_previous_track: 30,
|
||||
jukebox_next_track: 31,
|
||||
jukebox_stop: 32,
|
||||
jukebox_play: 33,
|
||||
jukebox_pause: 34,
|
||||
jukebox_set_volume: 35,
|
||||
jukebox_get_volume: 36,
|
||||
jukebox_volume: 37,
|
||||
jukebox_get_current_track: 38,
|
||||
jukebox_current_track: 39,
|
||||
jukebox_get_sound_effects: 40,
|
||||
jukebox_set_sound_effects: 41,
|
||||
jukebox_sound_effects: 42,
|
||||
}
|
||||
|
||||
def self.from_stream(message)
|
||||
slice = message.split("|", 4)
|
||||
|
||||
if slice.size < 4
|
||||
warn "Failed to split packet along first 4 " + PROTOCOL_SEPERATOR + ". Raw return: " + slice.to_s
|
||||
return nil
|
||||
end
|
||||
|
||||
if slice.first != PROTOCOL_VERSION.to_s
|
||||
warn "Incompatible protocol version received, expected: " + PROTOCOL_VERSION.to_s + " got: " + slice.first
|
||||
return nil
|
||||
end
|
||||
|
||||
unless valid_packet_type?(Integer(slice[1]))
|
||||
warn "Unknown packet type detected: #{slice[1]}"
|
||||
return nil
|
||||
end
|
||||
|
||||
protocol_version = Integer(slice[0])
|
||||
type = PACKET_TYPES.key(Integer(slice[1]))
|
||||
content_length = Integer(slice[2])
|
||||
body = slice[3]
|
||||
|
||||
raise "Type is #{type.inspect} [#{type.class}]" unless type.is_a?(Symbol)
|
||||
|
||||
return Packet.new(protocol_version, type, content_length, body)
|
||||
end
|
||||
|
||||
def self.create(packet_type, body)
|
||||
Packet.new(PROTOCOL_VERSION, PACKET_TYPES.key(packet_type), body.length, body)
|
||||
end
|
||||
|
||||
def self.valid_packet_type?(packet_type)
|
||||
PACKET_TYPES.values.find { |t| t == packet_type }
|
||||
end
|
||||
|
||||
attr_reader :protocol_version, :type, :content_length, :body
|
||||
def initialize(protocol_version, type, content_length, body)
|
||||
@protocol_version = protocol_version
|
||||
@type = type
|
||||
@content_length = content_length
|
||||
@body = body
|
||||
end
|
||||
|
||||
def encode_header
|
||||
string = ""
|
||||
string += protocol_version.to_s
|
||||
string += PROTOCOL_SEPERATOR
|
||||
string += PACKET_TYPES[type].to_s
|
||||
string += PROTOCOL_SEPERATOR
|
||||
string += content_length.to_s
|
||||
string += PROTOCOL_SEPERATOR
|
||||
|
||||
return string
|
||||
end
|
||||
|
||||
def valid?
|
||||
true
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{encode_header}#{body}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
320
lib/game_clock/net/packet_handler.rb
Normal file
320
lib/game_clock/net/packet_handler.rb
Normal file
@@ -0,0 +1,320 @@
|
||||
module TAC
|
||||
class PracticeGameClock
|
||||
class ClockNet
|
||||
class PacketHandler
|
||||
TAG = "ClockNet|PacketHandler"
|
||||
def initialize(host_is_a_connection: false, proxy_object:)
|
||||
@host_is_a_connection = host_is_a_connection
|
||||
@proxy_object = proxy_object
|
||||
end
|
||||
|
||||
def handle(message)
|
||||
packet = Packet.from_stream(message)
|
||||
|
||||
if packet
|
||||
log.i(TAG, "Received packet of type: #{packet.type}")
|
||||
hand_off(packet)
|
||||
else
|
||||
log.d(TAG, "Rejected raw packet: #{message}")
|
||||
end
|
||||
end
|
||||
|
||||
def hand_off(packet)
|
||||
case packet.type
|
||||
when :handshake
|
||||
handle_handshake(packet)
|
||||
when :heartbeat
|
||||
handle_heartbeat(packet)
|
||||
when :error
|
||||
handle_error(packet)
|
||||
|
||||
when :start_clock
|
||||
handle_start_clock(packet)
|
||||
when :abort_clock
|
||||
handle_abort_clock(packet)
|
||||
when :get_clock_title
|
||||
handle_get_clock_title(packet)
|
||||
when :set_clock_title
|
||||
handle_set_clock_title(packet)
|
||||
when :clock_title
|
||||
handle_clock_title(packet)
|
||||
when :jukebox_previous_track
|
||||
handle_jukebox_previous_track(packet)
|
||||
when :jukebox_next_track
|
||||
handle_jukebox_next_track(packet)
|
||||
when :jukebox_play
|
||||
handle_jukebox_play(packet)
|
||||
when :jukebox_pause
|
||||
handle_jukebox_pause(packet)
|
||||
when :jukebox_stop
|
||||
handle_jukebox_stop(packet)
|
||||
when :jukebox_set_volume
|
||||
handle_jukebox_set_volume(packet)
|
||||
when :jukebox_volume
|
||||
handle_jukebox_volume(packet)
|
||||
when :jukebox_set_sound_effects
|
||||
handle_jukebox_set_sound_effects(packet)
|
||||
when :jukebox_current_track
|
||||
handle_jukebox_current_track(packet)
|
||||
when :clock_time
|
||||
handle_clock_time(packet)
|
||||
when :randomizer_visible
|
||||
handle_randomizer_visible(packet)
|
||||
when :shutdown
|
||||
handle_shutdown(packet)
|
||||
else
|
||||
log.d(TAG, "No hand off available for packet type: #{packet.type}")
|
||||
end
|
||||
end
|
||||
|
||||
def handle_handshake(packet)
|
||||
if @host_is_a_connection
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Reset socket timeout
|
||||
def handle_heartbeat(packet)
|
||||
end
|
||||
|
||||
# TODO: Handle errors
|
||||
def handle_error(packet)
|
||||
title, message = packet.body.split(Packet::PROTOCOL_SEPERATOR, 2)
|
||||
log.e(TAG, "Remote error: #{title}: #{message}")
|
||||
end
|
||||
|
||||
def handle_start_clock(packet)
|
||||
return if @host_is_a_connection
|
||||
|
||||
@proxy_object.enqueue do
|
||||
@proxy_object.start_clock(packet.body.to_sym)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_abort_clock(packet)
|
||||
return if @host_is_a_connection
|
||||
|
||||
@proxy_object.abort_clock
|
||||
end
|
||||
|
||||
def handle_set_clock_title(packet)
|
||||
return if @host_is_a_connection
|
||||
|
||||
title = packet.body
|
||||
|
||||
@proxy_object.enqueue do
|
||||
@proxy_object.set_clock_title(title)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_get_clock_title(packet)
|
||||
return if @host_is_a_connection
|
||||
|
||||
RemoteControl.server.active_client.puts(Packet.clock_title(@proxy_object.clock.title))
|
||||
end
|
||||
|
||||
def handle_jukebox_previous_track(packet)
|
||||
return if @host_is_a_connection
|
||||
|
||||
@proxy_object.jukebox_previous_track
|
||||
|
||||
RemoteControl.server.active_client.puts(PacketHandler.packet_jukebox_current_track(@proxy_object.jukebox_current_track))
|
||||
end
|
||||
|
||||
def handle_jukebox_next_track(packet)
|
||||
return if @host_is_a_connection
|
||||
|
||||
@proxy_object.jukebox_next_track
|
||||
|
||||
RemoteControl.server.active_client.puts(PacketHandler.packet_jukebox_current_track(@proxy_object.jukebox_current_track))
|
||||
end
|
||||
|
||||
def handle_jukebox_play(packet)
|
||||
return if @host_is_a_connection
|
||||
|
||||
@proxy_object.jukebox_play
|
||||
|
||||
RemoteControl.server.active_client.puts(PacketHandler.packet_jukebox_current_track(@proxy_object.jukebox_current_track))
|
||||
end
|
||||
|
||||
def handle_jukebox_pause(packet)
|
||||
return if @host_is_a_connection
|
||||
|
||||
@proxy_object.jukebox_pause
|
||||
|
||||
RemoteControl.server.active_client.puts(PacketHandler.packet_jukebox_current_track(@proxy_object.jukebox_current_track))
|
||||
end
|
||||
|
||||
def handle_jukebox_stop(packet)
|
||||
return if @host_is_a_connection
|
||||
|
||||
@proxy_object.jukebox_stop
|
||||
|
||||
RemoteControl.server.active_client.puts(PacketHandler.packet_jukebox_current_track(@proxy_object.jukebox_current_track))
|
||||
end
|
||||
|
||||
def handle_jukebox_set_volume(packet)
|
||||
return if @host_is_a_connection
|
||||
|
||||
float = packet.body.to_f
|
||||
float = float.clamp(0.0, 1.0)
|
||||
|
||||
@proxy_object.jukebox_set_volume(float)
|
||||
|
||||
float = @proxy_object.jukebox_volume
|
||||
RemoteControl.server.active_client.puts(PacketHandler.packet_jukebox_volume(float))
|
||||
end
|
||||
|
||||
def handle_jukebox_get_volume(packet)
|
||||
return if @host_is_a_connection
|
||||
|
||||
float = @proxy_object.jukebox_volume
|
||||
|
||||
RemoteControl.server.active_client.puts(PacketHandler.packet_jukebox_volume(float))
|
||||
end
|
||||
|
||||
def handle_jukebox_volume(packet)
|
||||
return unless @host_is_a_connection
|
||||
|
||||
float = packet.body.to_f
|
||||
|
||||
@proxy_object.enqueue do
|
||||
@proxy_object.volume_changed(float)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_jukebox_set_sound_effects(packet)
|
||||
return if @host_is_a_connection
|
||||
|
||||
boolean = packet.body == "true"
|
||||
|
||||
@proxy_object.enqueue do
|
||||
@proxy_object.jukebox_set_sound_effects(boolean)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_jukebox_current_track(packet)
|
||||
return unless @host_is_a_connection
|
||||
|
||||
@proxy_object.enqueue do
|
||||
@proxy_object.track_changed(packet.body)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_clock_time(packet)
|
||||
return unless @host_is_a_connection
|
||||
|
||||
@proxy_object.enqueue do
|
||||
@proxy_object.clock_changed(packet.body)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_randomizer_visible(packet)
|
||||
boolean = packet.body == "true"
|
||||
boolean = false if @proxy_object.is_a?(ClockProxy) && @proxy_object.clock.active?
|
||||
|
||||
@proxy_object.enqueue do
|
||||
@proxy_object.randomizer_changed(boolean)
|
||||
end
|
||||
|
||||
return if @host_is_a_connection
|
||||
|
||||
# Send confirmation to client
|
||||
RemoteControl.server.active_client.puts(PacketHandler.packet_randomizer_visible(boolean))
|
||||
end
|
||||
|
||||
def handle_shutdown(packet)
|
||||
unless @host_is_a_connection
|
||||
# RemoteControl.server.close
|
||||
# CyberarmEngine::Window.instance.close
|
||||
Gosu::Song.current_song&.stop
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
def self.packet_handshake(client_uuid)
|
||||
Packet.create(Packet::PACKET_TYPES[:handshake], client_uuid)
|
||||
end
|
||||
|
||||
def self.packet_heartbeat
|
||||
Packet.create(Packet::PACKET_TYPES[:heartbeat], Packet::PROTOCOL_HEARTBEAT)
|
||||
end
|
||||
|
||||
def self.packet_error(error_code, message)
|
||||
Packet.create(Packet::PACKET_TYPES[:error], error_code.to_s, message.to_s)
|
||||
end
|
||||
|
||||
def self.packet_start_clock(mode)
|
||||
Packet.create(Packet::PACKET_TYPES[:start_clock], mode.to_s)
|
||||
end
|
||||
|
||||
def self.packet_abort_clock
|
||||
Packet.create(Packet::PACKET_TYPES[:abort_clock], "")
|
||||
end
|
||||
|
||||
def self.packet_set_clock_title(string)
|
||||
Packet.create(Packet::PACKET_TYPES[:set_clock_title], string.to_s)
|
||||
end
|
||||
|
||||
def self.packet_get_clock_title
|
||||
Packet.create(Packet::PACKET_TYPES[:get_clock_title], "")
|
||||
end
|
||||
|
||||
def self.packet_clock_title(string)
|
||||
Packet.create(Packet::PACKET_TYPES[:clock_title], string.to_s)
|
||||
end
|
||||
|
||||
def self.packet_jukebox_previous_track
|
||||
Packet.create(Packet::PACKET_TYPES[:jukebox_previous_track], "")
|
||||
end
|
||||
|
||||
def self.packet_jukebox_next_track
|
||||
Packet.create(Packet::PACKET_TYPES[:jukebox_next_track], "")
|
||||
end
|
||||
|
||||
def self.packet_jukebox_play
|
||||
Packet.create(Packet::PACKET_TYPES[:jukebox_play], "")
|
||||
end
|
||||
|
||||
def self.packet_jukebox_pause
|
||||
Packet.create(Packet::PACKET_TYPES[:jukebox_pause], "")
|
||||
end
|
||||
|
||||
def self.packet_jukebox_stop
|
||||
Packet.create(Packet::PACKET_TYPES[:jukebox_stop], "")
|
||||
end
|
||||
|
||||
def self.packet_jukebox_set_volume(float)
|
||||
Packet.create(Packet::PACKET_TYPES[:jukebox_set_volume], float.to_s)
|
||||
end
|
||||
|
||||
def self.packet_jukebox_get_volume
|
||||
Packet.create(Packet::PACKET_TYPES[:jukebox_get_volume], "")
|
||||
end
|
||||
|
||||
def self.packet_jukebox_volume(float)
|
||||
Packet.create(Packet::PACKET_TYPES[:jukebox_volume], float.to_s)
|
||||
end
|
||||
|
||||
def self.packet_jukebox_set_sound_effects(boolean)
|
||||
Packet.create(Packet::PACKET_TYPES[:jukebox_set_sound_effects], boolean.to_s)
|
||||
end
|
||||
|
||||
def self.packet_jukebox_current_track(name)
|
||||
Packet.create(Packet::PACKET_TYPES[:jukebox_current_track], name)
|
||||
end
|
||||
|
||||
def self.packet_clock_time(string)
|
||||
Packet.create(Packet::PACKET_TYPES[:clock_time], string)
|
||||
end
|
||||
|
||||
def self.packet_randomizer_visible(boolean)
|
||||
Packet.create(Packet::PACKET_TYPES[:randomizer_visible], boolean.to_s)
|
||||
end
|
||||
|
||||
def self.packet_shutdown
|
||||
Packet.create(Packet::PACKET_TYPES[:shutdown], "")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
147
lib/game_clock/net/server.rb
Normal file
147
lib/game_clock/net/server.rb
Normal file
@@ -0,0 +1,147 @@
|
||||
module TAC
|
||||
class PracticeGameClock
|
||||
class ClockNet
|
||||
class Server
|
||||
TAG = "ClockNet|Server"
|
||||
attr_reader :active_client,
|
||||
:packets_sent, :packets_received, :data_sent, :data_received,
|
||||
:client_last_packets_sent, :client_last_packets_received, :client_last_data_sent, :client_last_data_received,
|
||||
:proxy_object
|
||||
def initialize(hostname: "localhost", port: 4567, proxy_object: )
|
||||
$server = self
|
||||
|
||||
@hostname = hostname
|
||||
@port = port
|
||||
@proxy_object = proxy_object
|
||||
|
||||
@socket = nil
|
||||
@active_client = nil
|
||||
@connection_attempts = 0
|
||||
@max_connection_attempts = 10
|
||||
|
||||
@packets_sent, @packets_received, @client_last_packets_sent, @client_last_packets_received = 0, 0, 0, 0
|
||||
@data_sent, @data_received, @client_last_data_sent, @client_last_data_received = 0, 0, 0, 0
|
||||
|
||||
@last_sync_time = Gosu.milliseconds
|
||||
@sync_interval = SYNC_INTERVAL
|
||||
|
||||
@last_heartbeat_sent = Gosu.milliseconds
|
||||
@heartbeat_interval = HEARTBEAT_INTERVAL
|
||||
|
||||
@client_handler_proc = proc do
|
||||
handle_client
|
||||
end
|
||||
|
||||
@packet_handler = PacketHandler.new(proxy_object: @proxy_object)
|
||||
end
|
||||
|
||||
def start(run_on_main_thread: false)
|
||||
thread = Thread.new do
|
||||
while (!@socket && @connection_attempts < @max_connection_attempts)
|
||||
begin
|
||||
log.i(TAG, "Starting server...")
|
||||
@socket = TCPServer.new(@port)
|
||||
@socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
||||
rescue IOError => error
|
||||
log.e(TAG, error)
|
||||
|
||||
@connection_attempts += 1
|
||||
retry if @connection_attempts < @max_connection_attempts
|
||||
end
|
||||
end
|
||||
|
||||
while @socket && !@socket.closed?
|
||||
begin
|
||||
run_server
|
||||
rescue IOError => error
|
||||
log.e(TAG, error)
|
||||
@socket.close if @socket
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
thread.join if run_on_main_thread
|
||||
end
|
||||
|
||||
def run_server
|
||||
while !@socket.closed?
|
||||
client = Client.new
|
||||
client.sync_interval = @sync_interval
|
||||
client.socket = @socket.accept
|
||||
|
||||
if @active_client && @active_client.connected?
|
||||
log.i(TAG, "Too many clients, already have one connected!")
|
||||
client.close("Too many clients!")
|
||||
else
|
||||
@active_client = client
|
||||
# TODO: Backup local config
|
||||
# SEND CONFIG
|
||||
|
||||
@active_client.puts(PacketHandler.packet_handshake(@active_client.uuid))
|
||||
|
||||
log.i(TAG, "Client connected!")
|
||||
|
||||
Thread.new do
|
||||
while @active_client && @active_client.connected?
|
||||
if Gosu.milliseconds > @last_sync_time + @sync_interval
|
||||
@last_sync_time = Gosu.milliseconds
|
||||
|
||||
@active_client.sync(@client_handler_proc)
|
||||
update_stats
|
||||
end
|
||||
end
|
||||
|
||||
update_stats
|
||||
@active_client = nil
|
||||
|
||||
@client_last_packets_sent = 0
|
||||
@client_last_packets_received = 0
|
||||
@client_last_data_sent = 0
|
||||
@client_last_data_received = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def handle_client
|
||||
if @active_client && @active_client.connected?
|
||||
message = @active_client.gets
|
||||
|
||||
if message && !message.empty?
|
||||
@packet_handler.handle(message)
|
||||
end
|
||||
|
||||
if Gosu.milliseconds > @last_heartbeat_sent + @heartbeat_interval
|
||||
@last_heartbeat_sent = Gosu.milliseconds
|
||||
|
||||
@active_client.puts(PacketHandler.packet_heartbeat)
|
||||
end
|
||||
|
||||
sleep @sync_interval / 1000.0
|
||||
end
|
||||
end
|
||||
|
||||
def close
|
||||
@socket.close
|
||||
end
|
||||
|
||||
private def update_stats
|
||||
if @active_client
|
||||
# NOTE: Sent and Received are reversed for Server stats
|
||||
|
||||
@packets_sent += @active_client.packets_received - @client_last_packets_received
|
||||
@packets_received += @active_client.packets_sent - @client_last_packets_sent
|
||||
|
||||
@data_sent += @active_client.data_received - @client_last_data_received
|
||||
@data_received += @active_client.data_sent - @client_last_data_sent
|
||||
|
||||
@client_last_packets_sent = @active_client.packets_sent
|
||||
@client_last_packets_received = @active_client.packets_received
|
||||
@client_last_data_sent = @active_client.data_sent
|
||||
@client_last_data_received = @active_client.data_received
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
139
lib/game_clock/particle_emitter.rb
Normal file
139
lib/game_clock/particle_emitter.rb
Normal file
@@ -0,0 +1,139 @@
|
||||
module TAC
|
||||
class PracticeGameClock
|
||||
class ParticleEmitter
|
||||
def initialize(max_particles: 50, time_to_live: 30_000, interval: 1_500, z: -2)
|
||||
@max_particles = max_particles
|
||||
@time_to_live = time_to_live
|
||||
@interval = interval
|
||||
@z = -2
|
||||
|
||||
@particles = []
|
||||
@image_options = Dir.glob("#{MEDIA_PATH}/particles/*.*")
|
||||
@last_spawned = 0
|
||||
@clock_active = false
|
||||
end
|
||||
|
||||
def draw
|
||||
@particles.each(&:draw)
|
||||
end
|
||||
|
||||
def update
|
||||
@particles.each { |part| part.update(CyberarmEngine::Window.instance.dt) }
|
||||
@particles.delete_if { |part| part.die? }
|
||||
|
||||
spawn_particles
|
||||
end
|
||||
|
||||
def spawn_particles
|
||||
# !clock_active? &&
|
||||
if @particles.count < @max_particles && Gosu.milliseconds - @last_spawned >= @interval
|
||||
screen_midpoint = CyberarmEngine::Vector.new(CyberarmEngine::Window.instance.width / 2, CyberarmEngine::Window.instance.height / 2)
|
||||
scale = rand(0.25..1.0)
|
||||
image_name = @image_options.sample
|
||||
|
||||
return unless image_name
|
||||
|
||||
image = CyberarmEngine::Window.instance.current_state.get_image(image_name)
|
||||
position = CyberarmEngine::Vector.new(0, 0)
|
||||
|
||||
r = rand
|
||||
if r < 0.25 # LEFT
|
||||
position.x = -image.width * scale
|
||||
position.y = rand(0..(CyberarmEngine::Window.instance.height - image.height * scale))
|
||||
elsif r < 0.5 # RIGHT
|
||||
position.x = CyberarmEngine::Window.instance.width + (image.width * scale)
|
||||
position.y = rand(0..(CyberarmEngine::Window.instance.height - image.height * scale))
|
||||
elsif r < 0.75 # TOP
|
||||
position.x = rand(0..(CyberarmEngine::Window.instance.width - image.width * scale))
|
||||
position.y = -image.height * scale
|
||||
else #BOTTOM
|
||||
position.x = rand(0..(CyberarmEngine::Window.instance.width - image.width * scale))
|
||||
position.y = CyberarmEngine::Window.instance.height + image.height * scale
|
||||
end
|
||||
|
||||
position.x ||= 0
|
||||
position.y ||= 0
|
||||
|
||||
velocity = (screen_midpoint - position)
|
||||
|
||||
@particles << Particle.new(
|
||||
image: image,
|
||||
position: position,
|
||||
velocity: velocity,
|
||||
time_to_live: @time_to_live,
|
||||
speed: rand(24..128),
|
||||
scale: scale,
|
||||
clock_active: @clock_active,
|
||||
z: @z
|
||||
)
|
||||
|
||||
@last_spawned = Gosu.milliseconds
|
||||
end
|
||||
end
|
||||
|
||||
def particle_count
|
||||
@particles.size
|
||||
end
|
||||
|
||||
def clock_active!
|
||||
@clock_active = true
|
||||
@particles.each(&:clock_active!)
|
||||
end
|
||||
|
||||
def clock_inactive!
|
||||
@clock_active = false
|
||||
@particles.each(&:clock_inactive!)
|
||||
end
|
||||
|
||||
def clock_active?
|
||||
@clock_active
|
||||
end
|
||||
|
||||
class Particle
|
||||
def initialize(image:, position:, velocity:, time_to_live:, speed:, z:, scale: 1.0, clock_active: false)
|
||||
@image = image
|
||||
@position = position
|
||||
@velocity = velocity.normalized
|
||||
@time_to_live = time_to_live
|
||||
@speed = speed
|
||||
@z = z
|
||||
@scale = scale
|
||||
|
||||
@born_at = Gosu.milliseconds
|
||||
@born_time_to_live = time_to_live
|
||||
@color = Gosu::Color.new(0xff_ffffff)
|
||||
@clock_active = clock_active
|
||||
end
|
||||
|
||||
def draw
|
||||
@image.draw(@position.x, @position.y, @z, @scale, @scale, @color)
|
||||
end
|
||||
|
||||
def update(dt)
|
||||
@position += @velocity * @speed * dt
|
||||
|
||||
@color.alpha = (255.0 * ratio).to_i.clamp(0, 255)
|
||||
end
|
||||
|
||||
def ratio
|
||||
r = 1.0 - ((Gosu.milliseconds - @born_at) / @time_to_live.to_f)
|
||||
@clock_active ? r.clamp(0.0, 0.5) : r
|
||||
end
|
||||
|
||||
def die?
|
||||
ratio <= 0
|
||||
end
|
||||
|
||||
def clock_active!
|
||||
@clock_active = true
|
||||
# @time_to_live = (Gosu.milliseconds - @born_at) + 1_000
|
||||
end
|
||||
|
||||
def clock_inactive!
|
||||
@clock_active = false
|
||||
# @time_to_live = @born_time_to_live unless Gosu.milliseconds - @born_at < @time_to_live
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
184
lib/game_clock/randomizer.rb
Normal file
184
lib/game_clock/randomizer.rb
Normal file
@@ -0,0 +1,184 @@
|
||||
require "securerandom"
|
||||
|
||||
module TAC
|
||||
class PracticeGameClock
|
||||
class Randomizer < CyberarmEngine::GameState
|
||||
def setup
|
||||
@roll = SecureRandom.random_number(1..6)
|
||||
|
||||
@dimple_color = 0xff_008000
|
||||
@dimple_res = 36
|
||||
|
||||
@size = [window.width, window.height].min / 2.0
|
||||
|
||||
@ducks = []
|
||||
|
||||
case @roll
|
||||
when 1, 4
|
||||
#Blue and Red: Left
|
||||
|
||||
@ducks << Ducky.new(window: window, alliance: :blue, slot: 1, speed: 1010, die_size: @size, label: "Left")
|
||||
@ducks << Ducky.new(window: window, alliance: :red, slot: 1, speed: 1010, die_size: @size, label: "Left")
|
||||
when 2, 5
|
||||
#Blue and Red: Center
|
||||
|
||||
@ducks << Ducky.new(window: window, alliance: :blue, slot: 2, speed: 1010, die_size: @size, label: "Center")
|
||||
@ducks << Ducky.new(window: window, alliance: :red, slot: 2, speed: 1010, die_size: @size, label: "Center")
|
||||
when 3, 6
|
||||
#Blue and Red: Right
|
||||
|
||||
@ducks << Ducky.new(window: window, alliance: :blue, slot: 3, speed: 1010, die_size: @size, label: "Right")
|
||||
@ducks << Ducky.new(window: window, alliance: :red, slot: 3, speed: 1010, die_size: @size, label: "Right")
|
||||
end
|
||||
end
|
||||
|
||||
def draw
|
||||
previous_state.draw
|
||||
|
||||
Gosu.flush
|
||||
|
||||
fill(0xdd_202020)
|
||||
|
||||
Gosu.translate(window.width * 0.5 - @size * 0.5, 24) do
|
||||
Gosu.draw_rect(0, 0, @size, @size, Gosu::Color::BLACK)
|
||||
Gosu.draw_rect(12, 12, @size - 24, @size - 24, Gosu::Color::GRAY)
|
||||
|
||||
self.send(:"dice_#{@roll}", @size)
|
||||
end
|
||||
|
||||
@ducks.each { |o| o.draw(@size) }
|
||||
end
|
||||
|
||||
def dimple(size)
|
||||
size / 9.0
|
||||
end
|
||||
|
||||
def update
|
||||
previous_state&.update_non_gui
|
||||
|
||||
@ducks.each { |o| o.update(@size) }
|
||||
|
||||
@size = [window.width, window.height].min / 2.0
|
||||
end
|
||||
|
||||
def button_down(id)
|
||||
case id
|
||||
when Gosu::MS_LEFT, Gosu::KB_ESCAPE, Gosu::KB_SPACE
|
||||
pop_state
|
||||
end
|
||||
end
|
||||
|
||||
def dice_1(size)
|
||||
Gosu.draw_circle(size / 2, size / 2, dimple(size), @dimple_res, @dimple_color)
|
||||
end
|
||||
|
||||
def dice_2(size)
|
||||
Gosu.draw_circle(size * 0.25, size * 0.25, dimple(size), @dimple_res, @dimple_color)
|
||||
Gosu.draw_circle(size * 0.75, size * 0.75, dimple(size), @dimple_res, @dimple_color)
|
||||
end
|
||||
|
||||
def dice_3(size)
|
||||
Gosu.draw_circle(size * 0.25, size * 0.25, dimple(size), @dimple_res, @dimple_color)
|
||||
Gosu.draw_circle(size * 0.50, size * 0.50, dimple(size), @dimple_res, @dimple_color)
|
||||
Gosu.draw_circle(size * 0.75, size * 0.75, dimple(size), @dimple_res, @dimple_color)
|
||||
end
|
||||
|
||||
def dice_4(size)
|
||||
Gosu.draw_circle(size * 0.25, size * 0.25, dimple(size), @dimple_res, @dimple_color)
|
||||
Gosu.draw_circle(size * 0.75, size * 0.25, dimple(size), @dimple_res, @dimple_color)
|
||||
Gosu.draw_circle(size * 0.25, size * 0.75, dimple(size), @dimple_res, @dimple_color)
|
||||
Gosu.draw_circle(size * 0.75, size * 0.75, dimple(size), @dimple_res, @dimple_color)
|
||||
end
|
||||
|
||||
def dice_5(size)
|
||||
Gosu.draw_circle(size * 0.50, size * 0.50, dimple(size), @dimple_res, @dimple_color)
|
||||
Gosu.draw_circle(size * 0.25, size * 0.25, dimple(size), @dimple_res, @dimple_color)
|
||||
Gosu.draw_circle(size * 0.75, size * 0.25, dimple(size), @dimple_res, @dimple_color)
|
||||
Gosu.draw_circle(size * 0.25, size * 0.75, dimple(size), @dimple_res, @dimple_color)
|
||||
Gosu.draw_circle(size * 0.75, size * 0.75, dimple(size), @dimple_res, @dimple_color)
|
||||
end
|
||||
|
||||
def dice_6(size)
|
||||
Gosu.draw_circle(size * 0.25, size * 0.20, dimple(size), @dimple_res, @dimple_color)
|
||||
Gosu.draw_circle(size * 0.75, size * 0.20, dimple(size), @dimple_res, @dimple_color)
|
||||
Gosu.draw_circle(size * 0.25, size * 0.50, dimple(size), @dimple_res, @dimple_color)
|
||||
Gosu.draw_circle(size * 0.75, size * 0.50, dimple(size), @dimple_res, @dimple_color)
|
||||
Gosu.draw_circle(size * 0.25, size * 0.80, dimple(size), @dimple_res, @dimple_color)
|
||||
Gosu.draw_circle(size * 0.75, size * 0.80, dimple(size), @dimple_res, @dimple_color)
|
||||
end
|
||||
|
||||
class Ducky
|
||||
SIZE = 0.20
|
||||
HALF_SIZE = SIZE * 0.5
|
||||
|
||||
def initialize(window:, alliance:, slot:, speed:, die_size:, label:)
|
||||
@window = window
|
||||
@alliance = alliance
|
||||
@slot = slot
|
||||
@speed = speed
|
||||
@die_size = die_size
|
||||
@label = label
|
||||
|
||||
@image = @window.get_image("#{MEDIA_PATH}/openclipart_ducky.png")
|
||||
@debug_text = Gosu::Font.new(28)
|
||||
@label_text = CyberarmEngine::Text.new(@label, static: true, size: 28, alignment: :center)
|
||||
|
||||
if @alliance == :blue
|
||||
@position = CyberarmEngine::Vector.new(@window.width, die_size)
|
||||
else
|
||||
@position = CyberarmEngine::Vector.new(-die_size, die_size + die_size * 0.40)
|
||||
end
|
||||
end
|
||||
|
||||
def draw(size)
|
||||
Gosu.translate(@position.x, @position.y) do
|
||||
Gosu.draw_rect(0, size * SIZE, size * SIZE, size * SIZE, alliance_color)
|
||||
Gosu.draw_rect(size * 0.5 - size * HALF_SIZE, size * SIZE, size * SIZE, size * SIZE, alliance_color)
|
||||
Gosu.draw_rect(size * (1.0 - SIZE), size * SIZE, size * SIZE, size * SIZE, alliance_color)
|
||||
|
||||
duck_scale = (size * (SIZE + HALF_SIZE)) / @image.width
|
||||
duck_scale_x = @alliance == :blue ? -duck_scale : duck_scale
|
||||
@image.draw_rot(slot_position(size), size * SIZE + float_y(size), 1, 0, 0.5, 0.5, duck_scale_x, duck_scale)
|
||||
|
||||
@label_text.x = slot_position(size) + (@alliance == :blue ? -170 : 110)
|
||||
@label_text.y = float_y(size) + 52
|
||||
@label_text.draw
|
||||
end
|
||||
end
|
||||
|
||||
def update(size)
|
||||
center = @window.width * 0.5 - size * 0.5
|
||||
|
||||
if @position.x > center
|
||||
@position.x -= @speed * @window.dt
|
||||
@position.x = center if @position.x < center
|
||||
elsif @position.x < center
|
||||
@position.x += @speed * @window.dt
|
||||
@position.x = center if @position.x > center
|
||||
end
|
||||
end
|
||||
|
||||
def alliance_color
|
||||
@alliance == :blue ? 0xff_000080 : 0xff_800000
|
||||
end
|
||||
|
||||
def slot_position(size)
|
||||
case @slot
|
||||
when 1
|
||||
size * HALF_SIZE
|
||||
when 2
|
||||
size * 0.5
|
||||
when 3
|
||||
size * (1.0 - HALF_SIZE)
|
||||
else
|
||||
raise "Slot value of: #{@slot.inspect} is invalid!"
|
||||
end
|
||||
end
|
||||
|
||||
def float_y(size)
|
||||
Math.sin(Gosu.milliseconds / 100.0) * (size * 0.01)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
312
lib/game_clock/remote_control.rb
Normal file
312
lib/game_clock/remote_control.rb
Normal file
@@ -0,0 +1,312 @@
|
||||
module TAC
|
||||
class PracticeGameClock
|
||||
class RemoteControl
|
||||
@@connection = nil
|
||||
@@server = nil
|
||||
|
||||
def self.connection
|
||||
@@connection
|
||||
end
|
||||
|
||||
def self.connection=(connection)
|
||||
@@connection = connection
|
||||
end
|
||||
|
||||
def self.server
|
||||
@@server
|
||||
end
|
||||
|
||||
def self.server=(server)
|
||||
@@server = server
|
||||
end
|
||||
|
||||
class NetConnect < CyberarmEngine::GuiState
|
||||
def setup
|
||||
theme(THEME)
|
||||
|
||||
background Palette::TACNET_NOT_CONNECTED
|
||||
|
||||
banner "ClockNet Remote Control", text_align: :center, width: 1.0
|
||||
flow(width: 1.0) do
|
||||
stack(width: 0.25) {}
|
||||
stack(width: 0.5) do
|
||||
title "Hostname"
|
||||
@hostname = edit_line "localhost", width: 1.0
|
||||
title "Port"
|
||||
@port = edit_line "4567", width: 1.0
|
||||
|
||||
flow(width: 1.0, margin_top: 20) do
|
||||
@back_button = button "Back", width: 0.5 do
|
||||
window.pop_state
|
||||
end
|
||||
|
||||
@connect_button = button "Connect", width: 0.5 do
|
||||
begin
|
||||
@connect_button.enabled = false
|
||||
@back_button.enabled = false
|
||||
|
||||
@connection = ClockNet::Connection.new(hostname: @hostname.value, port: Integer(@port.value), proxy_object: RemoteProxy.new(window))
|
||||
|
||||
@connection.connect
|
||||
RemoteControl.connection = @connection
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
super
|
||||
|
||||
if RemoteControl.connection
|
||||
push_state(Controller) if RemoteControl.connection.connected?
|
||||
|
||||
RemoteControl.connection = nil if RemoteControl.connection.client.socket_error?
|
||||
else
|
||||
@back_button.enabled = true
|
||||
@connect_button.enabled = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Controller < CyberarmEngine::GuiState
|
||||
def setup
|
||||
theme(THEME)
|
||||
|
||||
at_exit do
|
||||
@connection&.close
|
||||
end
|
||||
|
||||
@jukebox_volume = 1.0
|
||||
@jukebox_sound_effects = true
|
||||
@locked_buttons_randomizer_visible = []
|
||||
@locked_buttons_clock_active = []
|
||||
@randomizer_visible = false
|
||||
@clock_updated_at = -1000
|
||||
|
||||
RemoteControl.connection.proxy_object.register(:track_changed, method(:track_changed))
|
||||
RemoteControl.connection.proxy_object.register(:volume_changed, method(:volume_changed))
|
||||
RemoteControl.connection.proxy_object.register(:clock_changed, method(:clock_changed))
|
||||
RemoteControl.connection.proxy_object.register(:randomizer_changed, method(:randomizer_changed))
|
||||
|
||||
background Palette::TACNET_NOT_CONNECTED
|
||||
|
||||
banner "ClockNet Remote Control", text_align: :center, width: 1.0
|
||||
|
||||
flow width: 1.0, height: 1.0 do
|
||||
stack width: 0.5 do
|
||||
title "Match", width: 1.0, text_align: :center
|
||||
@start_match_btn = button "Start Match", width: 1.0, text_size: 48, margin_bottom: 50 do
|
||||
start_clock(:full_match)
|
||||
end
|
||||
|
||||
title "Practice", width: 1.0, text_align: :center
|
||||
@autonomous_btn = button "Autonomous", width: 1.0 do
|
||||
start_clock(:autonomous)
|
||||
end
|
||||
@teleop_with_countdown_btn = button "TeleOp with Countdown", width: 1.0 do
|
||||
start_clock(:full_teleop)
|
||||
end
|
||||
@teleop_btn = button "TeleOp", width: 1.0 do
|
||||
start_clock(:teleop_only)
|
||||
end
|
||||
@teleop_endgame_btn = button "TeleOp Endgame", width: 1.0, margin_bottom: 50 do
|
||||
start_clock(:endgame_only)
|
||||
end
|
||||
|
||||
@abort_match_btn = button "Abort Match", width: 1.0 do
|
||||
RemoteControl.connection.puts(ClockNet::PacketHandler.packet_abort_clock)
|
||||
end
|
||||
|
||||
button "Shutdown", width: 1.0, **TAC::THEME_DANGER_BUTTON do
|
||||
RemoteControl.connection.puts(ClockNet::PacketHandler.packet_shutdown)
|
||||
sleep 1 # let packet escape before closing
|
||||
end
|
||||
end
|
||||
|
||||
stack width: 0.495 do
|
||||
title "Clock Title", width: 1.0, text_align: :center
|
||||
|
||||
stack width: 0.9, margin_left: 50 do
|
||||
@title = edit_line "FIRST TECH CHALLENGE", width: 1.0
|
||||
|
||||
button "Update", width: 1.0, margin_bottom: 50 do
|
||||
RemoteControl.connection.puts(ClockNet::PacketHandler.packet_set_clock_title(@title.value.strip))
|
||||
end
|
||||
end
|
||||
|
||||
title "JukeBox", width: 1.0, text_align: :center
|
||||
stack width: 0.9, margin_left: 50 do
|
||||
flow width: 1.0 do
|
||||
tagline "Now Playing: "
|
||||
@track_name = tagline ""
|
||||
end
|
||||
|
||||
flow width: 1.0 do
|
||||
tagline "Volume: "
|
||||
@volume = tagline "100%"
|
||||
end
|
||||
|
||||
flow width: 1.0 do
|
||||
button get_image("#{MEDIA_PATH}/icons/previous.png") do
|
||||
RemoteControl.connection.puts(ClockNet::PacketHandler.packet_jukebox_previous_track)
|
||||
end
|
||||
|
||||
button get_image("#{MEDIA_PATH}/icons/right.png") do |button|
|
||||
if @jukebox_playing
|
||||
RemoteControl.connection.puts(ClockNet::PacketHandler.packet_jukebox_pause)
|
||||
button.value = get_image("#{MEDIA_PATH}/icons/right.png")
|
||||
@jukebox_playing = false
|
||||
else
|
||||
RemoteControl.connection.puts(ClockNet::PacketHandler.packet_jukebox_play)
|
||||
button.value = get_image("#{MEDIA_PATH}/icons/pause.png")
|
||||
@jukebox_playing = true
|
||||
end
|
||||
end
|
||||
|
||||
button get_image("#{MEDIA_PATH}/icons/stop.png") do
|
||||
RemoteControl.connection.puts(ClockNet::PacketHandler.packet_jukebox_stop)
|
||||
end
|
||||
|
||||
button get_image("#{MEDIA_PATH}/icons/next.png") do
|
||||
RemoteControl.connection.puts(ClockNet::PacketHandler.packet_jukebox_next_track)
|
||||
end
|
||||
|
||||
button get_image("#{MEDIA_PATH}/icons/minus.png"), margin_left: 20 do
|
||||
@jukebox_volume -= 0.1
|
||||
@jukebox_volume = 0.1 if @jukebox_volume < 0.1
|
||||
RemoteControl.connection.puts(ClockNet::PacketHandler.packet_jukebox_set_volume(@jukebox_volume))
|
||||
end
|
||||
|
||||
button get_image("#{MEDIA_PATH}/icons/plus.png") do
|
||||
@jukebox_volume += 0.1
|
||||
@jukebox_volume = 1.0 if @jukebox_volume > 1.0
|
||||
RemoteControl.connection.puts(ClockNet::PacketHandler.packet_jukebox_set_volume(@jukebox_volume))
|
||||
end
|
||||
|
||||
button get_image("#{MEDIA_PATH}/icons/musicOn.png"), margin_left: 20, tip: "Toggle Sound Effects" do |button|
|
||||
if @jukebox_sound_effects
|
||||
button.value = get_image("#{MEDIA_PATH}/icons/musicOff.png")
|
||||
@jukebox_sound_effects = false
|
||||
else
|
||||
button.value = get_image("#{MEDIA_PATH}/icons/musicOn.png")
|
||||
@jukebox_sound_effects = true
|
||||
end
|
||||
|
||||
RemoteControl.connection.puts(ClockNet::PacketHandler.packet_jukebox_set_sound_effects(@jukebox_sound_effects))
|
||||
end
|
||||
end
|
||||
|
||||
button "Open Music Library", width: 1.0 do
|
||||
path = "#{MEDIA_PATH}/music"
|
||||
|
||||
if RUBY_PLATFORM.match(/ming|msys|cygwin/)
|
||||
system("explorer \"#{path.gsub("/", "\\")}\"")
|
||||
elsif RUBY_PLATFORM.match(/linux/)
|
||||
system("xdg-open \"#{MEDIA_PATH}/music\"")
|
||||
else
|
||||
# TODO.
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
stack width: 0.9, margin_left: 50, margin_top: 20 do
|
||||
flow width: 1.0 do
|
||||
title "Clock: "
|
||||
@clock_label = title "0:00"
|
||||
end
|
||||
|
||||
flow width: 1.0 do
|
||||
title "Randomizer: "
|
||||
@randomizer_label = title "Not Visible"
|
||||
end
|
||||
|
||||
@randomizer_btn = button "Randomizer", width: 1.0, **TAC::THEME_DANGER_BUTTON do
|
||||
@randomizer_visible = !@randomizer_visible
|
||||
|
||||
RemoteControl.connection.puts(ClockNet::PacketHandler.packet_randomizer_visible(@randomizer_visible))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@locked_buttons_clock_active.push(
|
||||
@start_match_btn,
|
||||
@autonomous_btn,
|
||||
@teleop_with_countdown_btn,
|
||||
@teleop_btn,
|
||||
@teleop_endgame_btn,
|
||||
@randomizer_btn
|
||||
)
|
||||
|
||||
@locked_buttons_randomizer_visible.push(
|
||||
@start_match_btn,
|
||||
@autonomous_btn,
|
||||
@teleop_with_countdown_btn,
|
||||
@teleop_btn,
|
||||
@teleop_endgame_btn,
|
||||
@abort_match_btn
|
||||
)
|
||||
end
|
||||
|
||||
def update
|
||||
super
|
||||
|
||||
while (o = RemoteControl.connection.proxy_object.queue.shift)
|
||||
o.call
|
||||
end
|
||||
|
||||
manage_button_enablement
|
||||
|
||||
return if RemoteControl.connection.connected?
|
||||
|
||||
# We've lost connection, unset window's connection object
|
||||
# and send user back to connect screen to to attempt to
|
||||
# reconnect
|
||||
RemoteControl.connection = nil
|
||||
pop_state
|
||||
end
|
||||
|
||||
def start_clock(mode)
|
||||
RemoteControl.connection.puts(ClockNet::PacketHandler.packet_start_clock(mode.to_s))
|
||||
end
|
||||
|
||||
def track_changed(name)
|
||||
@track_name.value = name
|
||||
end
|
||||
|
||||
def volume_changed(float)
|
||||
@volume.value = "#{(float.round(1) * 100.0).round}%"
|
||||
end
|
||||
|
||||
def clock_changed(string)
|
||||
@clock_updated_at = Gosu.milliseconds if @clock_label.value != string
|
||||
|
||||
@clock_label.value = string
|
||||
end
|
||||
|
||||
def randomizer_changed(boolean)
|
||||
@randomizer_label.value = "Visible" if boolean
|
||||
@randomizer_label.value = "Not Visible" unless boolean
|
||||
end
|
||||
|
||||
def manage_button_enablement
|
||||
if @randomizer_visible
|
||||
@locked_buttons_randomizer_visible.each do |btn|
|
||||
btn.enabled = false
|
||||
end
|
||||
elsif Gosu.milliseconds - @clock_updated_at <= 1_250
|
||||
@locked_buttons_clock_active.each do |btn|
|
||||
btn.enabled = false
|
||||
end
|
||||
else
|
||||
(@locked_buttons_clock_active + @locked_buttons_randomizer_visible).uniq.each do |btn|
|
||||
btn.enabled = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
71
lib/game_clock/remote_proxy.rb
Normal file
71
lib/game_clock/remote_proxy.rb
Normal file
@@ -0,0 +1,71 @@
|
||||
module TAC
|
||||
class PracticeGameClock
|
||||
class RemoteProxy
|
||||
attr_reader :queue
|
||||
|
||||
def initialize(window)
|
||||
@window = window
|
||||
|
||||
@queue = []
|
||||
@callbacks = {}
|
||||
end
|
||||
|
||||
def enqueue(&block)
|
||||
@queue << block
|
||||
end
|
||||
|
||||
def register(callback, method)
|
||||
@callbacks[callback] = method
|
||||
end
|
||||
|
||||
def start_clock(mode)
|
||||
end
|
||||
|
||||
def abort_clock
|
||||
end
|
||||
|
||||
def set_clock_title(string)
|
||||
end
|
||||
|
||||
def get_clock_title(string)
|
||||
end
|
||||
|
||||
def jukebox_previous_track
|
||||
end
|
||||
|
||||
def jukebox_next_track
|
||||
end
|
||||
|
||||
def jukebox_stop
|
||||
end
|
||||
|
||||
def jukebox_play
|
||||
end
|
||||
|
||||
def jukebox_pause
|
||||
end
|
||||
|
||||
def jukebox_sound_effects(boolean)
|
||||
end
|
||||
|
||||
def volume_changed(float)
|
||||
@callbacks[:volume_changed]&.call(float)
|
||||
end
|
||||
|
||||
def track_changed(name)
|
||||
@callbacks[:track_changed]&.call(name)
|
||||
end
|
||||
|
||||
def clock_changed(string)
|
||||
@callbacks[:clock_changed]&.call(string)
|
||||
end
|
||||
|
||||
def randomizer_changed(boolean)
|
||||
@callbacks[:randomizer_changed]&.call(boolean)
|
||||
end
|
||||
|
||||
def shutdown!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
30
lib/game_clock/theme.rb
Normal file
30
lib/game_clock/theme.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
module TAC
|
||||
class PracticeGameClock
|
||||
THEME = {
|
||||
TextBlock: {
|
||||
font: "NotoSans-Bold",
|
||||
color: Gosu::Color.new(0xee_ffffff)
|
||||
},
|
||||
Button: {
|
||||
image_width: 40,
|
||||
text_size: 40,
|
||||
background: Palette::TIMECRAFTERS_PRIMARY,
|
||||
border_thickness: 1,
|
||||
border_color: Gosu::Color.new(0xff_111111),
|
||||
hover: {
|
||||
background: Palette::TIMECRAFTERS_SECONDARY,
|
||||
},
|
||||
active: {
|
||||
background: Palette::TIMECRAFTERS_TERTIARY,
|
||||
}
|
||||
},
|
||||
EditLine: {
|
||||
caret_color: Gosu::Color.new(0xff_88ef90),
|
||||
},
|
||||
ToggleButton: {
|
||||
width: 18,
|
||||
checkmark_image: "#{MEDIA_PATH}/icons/checkmark.png",
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
373
lib/game_clock/view.rb
Normal file
373
lib/game_clock/view.rb
Normal file
@@ -0,0 +1,373 @@
|
||||
module TAC
|
||||
class PracticeGameClock
|
||||
class View < CyberarmEngine::GuiState
|
||||
|
||||
attr_reader :clock
|
||||
|
||||
def setup
|
||||
@remote_control_mode = @options[:remote_control_mode]
|
||||
window.show_cursor = !@remote_control_mode
|
||||
@escape_counter = 0
|
||||
|
||||
@background_image = get_image("#{MEDIA_PATH}/background.png")
|
||||
# Preload duck image since Gosu and Windows threads don't get along with OpenGL (image is blank if loaded in a threaded context)
|
||||
get_image("#{MEDIA_PATH}/openclipart_ducky.png")
|
||||
@menu_background = 0xaa004000
|
||||
@mouse = Mouse.new(window)
|
||||
@clock = Clock.new
|
||||
@clock.controller = nil
|
||||
@last_clock_display_value = @clock.value
|
||||
@last_clock_title_value = @clock.title.text
|
||||
|
||||
@particle_emitters = [
|
||||
PracticeGameClock::ParticleEmitter.new
|
||||
]
|
||||
|
||||
@last_clock_state = @clock.active?
|
||||
|
||||
@locked_buttons_randomizer_visible = []
|
||||
@locked_buttons_clock_active = []
|
||||
@randomizer_visible = false
|
||||
|
||||
theme(THEME)
|
||||
|
||||
@menu_container = flow width: 1.0 do
|
||||
stack(width: 0.35, padding: 5) do
|
||||
background @menu_background
|
||||
|
||||
title "Match", width: 1.0, text_align: :center
|
||||
@start_match_btn = button "Start Match", width: 1.0, margin_bottom: 50 do |btn|
|
||||
@locked_buttons_clock_active << btn
|
||||
@locked_buttons_randomizer_visible << btn
|
||||
|
||||
@clock_proxy.start_clock(:full_match)
|
||||
end
|
||||
|
||||
title "Practice", width: 1.0, text_align: :center
|
||||
@autonomous_btn = button "Autonomous", width: 1.0 do |btn|
|
||||
@locked_buttons_clock_active << btn
|
||||
@locked_buttons_randomizer_visible << btn
|
||||
|
||||
@clock_proxy.start_clock(:autonomous)
|
||||
end
|
||||
|
||||
@teleop_with_countdown_btn = button "TeleOp with Countdown", width: 1.0 do |btn|
|
||||
@locked_buttons_clock_active << btn
|
||||
@locked_buttons_randomizer_visible << btn
|
||||
|
||||
@clock_proxy.start_clock(:full_teleop)
|
||||
end
|
||||
|
||||
@teleop_btn = button "TeleOp", width: 1.0 do |btn|
|
||||
@locked_buttons_clock_active << btn
|
||||
@locked_buttons_randomizer_visible << btn
|
||||
|
||||
@clock_proxy.start_clock(:teleop_only)
|
||||
end
|
||||
|
||||
@teleop_endgame_btn = button "TeleOp Endgame", width: 1.0 do |btn|
|
||||
@locked_buttons_clock_active << btn
|
||||
@locked_buttons_randomizer_visible << btn
|
||||
|
||||
@clock_proxy.start_clock(:endgame_only)
|
||||
end
|
||||
|
||||
@abort_match_btn = button "Abort Match", width: 1.0, margin_top: 50 do |btn|
|
||||
@locked_buttons_randomizer_visible << btn
|
||||
|
||||
@clock_proxy.abort_clock
|
||||
end
|
||||
|
||||
button "Close", width: 1.0, **TAC::THEME_DANGER_BUTTON do
|
||||
if window.instance_variable_get(:"@states").size == 1
|
||||
window.close
|
||||
else
|
||||
@server&.close
|
||||
|
||||
@jukebox.stop
|
||||
window.fullscreen = false
|
||||
window.pop_state
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
stack width: 0.4, padding_left: 50 do
|
||||
background @menu_background
|
||||
|
||||
flow(width: 1.0) do
|
||||
para "♫ Now playing:"
|
||||
@current_song_label = para "♫ ♫ ♫"
|
||||
end
|
||||
|
||||
flow(width: 1.0) do
|
||||
para "Volume:"
|
||||
@current_volume_label = para "100%"
|
||||
end
|
||||
|
||||
flow(width: 1.0) do
|
||||
button get_image("#{MEDIA_PATH}/icons/previous.png") do
|
||||
@jukebox.previous_track
|
||||
end
|
||||
|
||||
button get_image("#{MEDIA_PATH}/icons/pause.png") do |button|
|
||||
if @jukebox.song && @jukebox.song.paused?
|
||||
button.value = get_image("#{MEDIA_PATH}/icons/right.png")
|
||||
@jukebox.play
|
||||
elsif !@jukebox.song
|
||||
button.value = get_image("#{MEDIA_PATH}/icons/right.png")
|
||||
@jukebox.play
|
||||
else
|
||||
button.value = get_image("#{MEDIA_PATH}/icons/pause.png")
|
||||
@jukebox.pause
|
||||
end
|
||||
end
|
||||
|
||||
button get_image("#{MEDIA_PATH}/icons/stop.png") do
|
||||
@jukebox.stop
|
||||
end
|
||||
|
||||
button get_image("#{MEDIA_PATH}/icons/next.png") do
|
||||
@jukebox.next_track
|
||||
end
|
||||
|
||||
button get_image("#{MEDIA_PATH}/icons/minus.png"), margin_left: 20 do
|
||||
@jukebox.set_volume(@jukebox.volume - 0.1)
|
||||
end
|
||||
|
||||
button get_image("#{MEDIA_PATH}/icons/plus.png") do
|
||||
@jukebox.set_volume(@jukebox.volume + 0.1)
|
||||
end
|
||||
|
||||
button "Open Music Library", margin_left: 50 do
|
||||
if RUBY_PLATFORM.match(/ming|msys|cygwin/)
|
||||
system("explorer #{MEDIA_PATH}/music")
|
||||
elsif RUBY_PLATFORM.match(/linux/)
|
||||
system("xdg-open #{MEDIA_PATH}/music")
|
||||
else
|
||||
# TODO.
|
||||
end
|
||||
end
|
||||
|
||||
button get_image("#{MEDIA_PATH}/icons/musicOn.png"), margin_left: 50, tip: "Toggle Sound Effects" do |button|
|
||||
boolean = @jukebox.set_sfx(!@jukebox.play_sfx?)
|
||||
|
||||
if boolean
|
||||
button.value = get_image("#{MEDIA_PATH}/icons/musicOn.png")
|
||||
else
|
||||
button.value = get_image("#{MEDIA_PATH}/icons/musicOff.png")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
stack(width: 1.0) do
|
||||
@randomizer_btn = button "Randomizer", width: 1.0, **TAC::THEME_DANGER_BUTTON do |btn|
|
||||
unless @clock.active?
|
||||
push_state(Randomizer)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@locked_buttons_clock_active.push(
|
||||
@start_match_btn,
|
||||
@autonomous_btn,
|
||||
@teleop_with_countdown_btn,
|
||||
@teleop_btn,
|
||||
@teleop_endgame_btn,
|
||||
@randomizer_btn
|
||||
)
|
||||
|
||||
@locked_buttons_randomizer_visible.push(
|
||||
@start_match_btn,
|
||||
@autonomous_btn,
|
||||
@teleop_with_countdown_btn,
|
||||
@teleop_btn,
|
||||
@teleop_endgame_btn,
|
||||
@abort_match_btn
|
||||
)
|
||||
|
||||
@jukebox = Jukebox.new(@clock)
|
||||
|
||||
@clock_proxy = ClockProxy.new(@clock, @jukebox)
|
||||
|
||||
if @remote_control_mode
|
||||
@server = ClockNet::Server.new(proxy_object: @clock_proxy)
|
||||
@server.start
|
||||
RemoteControl.server = @server
|
||||
|
||||
@clock_proxy.register(:randomizer_changed, method(:randomizer_changed))
|
||||
end
|
||||
end
|
||||
|
||||
def draw
|
||||
background_image_scale = [window.width.to_f / @background_image.width, window.height.to_f / @background_image.height].max
|
||||
|
||||
@background_image.draw(0, 0, -3, background_image_scale, background_image_scale)
|
||||
@particle_emitters.each(&:draw)
|
||||
@clock.draw
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def update
|
||||
super
|
||||
@clock.update
|
||||
@mouse.update
|
||||
update_non_gui
|
||||
|
||||
if @last_clock_state != @clock.active?
|
||||
@particle_emitters.each { |emitter| @clock.active? ? emitter.clock_active! : emitter.clock_inactive! }
|
||||
end
|
||||
|
||||
if @remote_control_mode
|
||||
@menu_container.hide
|
||||
else
|
||||
if @mouse.last_moved < 1.5
|
||||
@menu_container.show unless @menu_container.visible?
|
||||
window.show_cursor = true
|
||||
else
|
||||
@menu_container.hide if @menu_container.visible?
|
||||
window.show_cursor = false
|
||||
end
|
||||
|
||||
manage_button_enablement
|
||||
end
|
||||
|
||||
if @clock.value != @last_clock_display_value
|
||||
@last_clock_display_value = @clock.value
|
||||
|
||||
request_repaint
|
||||
|
||||
if @remote_control_mode && @server.active_client
|
||||
@server.active_client.puts(ClockNet::PacketHandler.packet_clock_time(@last_clock_display_value))
|
||||
end
|
||||
end
|
||||
|
||||
if @clock.title.text != @last_clock_title_value
|
||||
@last_clock_title_value = @clock.title.text
|
||||
|
||||
request_repaint
|
||||
end
|
||||
|
||||
if @last_track_name != @jukebox.current_track
|
||||
track_changed(@jukebox.current_track)
|
||||
end
|
||||
|
||||
if @last_volume != @jukebox.volume
|
||||
volume_changed(@jukebox.volume)
|
||||
end
|
||||
|
||||
@last_track_name = @jukebox.current_track
|
||||
@last_volume = @jukebox.volume
|
||||
@last_clock_state = @clock.active?
|
||||
end
|
||||
|
||||
def needs_repaint?
|
||||
if @particle_emitters && @particle_emitters.map(&:particle_count).sum.positive?
|
||||
@needs_repaint = true
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def update_non_gui
|
||||
if @remote_control_mode
|
||||
while (o = RemoteControl.server.proxy_object.queue.shift)
|
||||
o.call
|
||||
end
|
||||
end
|
||||
|
||||
@particle_emitters.each(&:update)
|
||||
@jukebox.update
|
||||
end
|
||||
|
||||
def manage_button_enablement
|
||||
if @randomizer_visible
|
||||
@locked_buttons_randomizer_visible.each do |btn|
|
||||
btn.enabled = false
|
||||
end
|
||||
elsif @clock.active?
|
||||
@locked_buttons_clock_active.each do |btn|
|
||||
btn.enabled = false
|
||||
end
|
||||
else
|
||||
(@locked_buttons_clock_active + @locked_buttons_randomizer_visible).uniq.each do |btn|
|
||||
btn.enabled = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def button_down(id)
|
||||
super
|
||||
|
||||
@mouse.button_down(id)
|
||||
|
||||
case id
|
||||
when Gosu::KB_ESCAPE
|
||||
@escape_counter += 1
|
||||
|
||||
if @escape_counter >= 3
|
||||
@server&.close
|
||||
|
||||
if window.instance_variable_get(:"@states").size == 1
|
||||
window.close
|
||||
else
|
||||
window.fullscreen = false
|
||||
window.pop_state
|
||||
end
|
||||
end
|
||||
else
|
||||
@escape_counter = 0
|
||||
end
|
||||
end
|
||||
|
||||
def track_changed(name)
|
||||
@current_song_label.value = File.basename(name)
|
||||
end
|
||||
|
||||
def volume_changed(float)
|
||||
@current_volume_label.value = "#{(float.round(1) * 100.0).round}%"
|
||||
end
|
||||
|
||||
def randomizer_changed(boolean)
|
||||
@randomizer_visible = boolean
|
||||
|
||||
if boolean
|
||||
push_state(Randomizer) unless @clock.active?
|
||||
else
|
||||
pop_state if current_state.is_a?(Randomizer)
|
||||
end
|
||||
end
|
||||
|
||||
class Mouse
|
||||
def initialize(window)
|
||||
@window = window
|
||||
@last_moved = 0
|
||||
|
||||
@last_position = CyberarmEngine::Vector.new(@window.mouse_x, @window.mouse_y)
|
||||
end
|
||||
|
||||
def update
|
||||
position = CyberarmEngine::Vector.new(@window.mouse_x, @window.mouse_y)
|
||||
|
||||
if @last_position != position
|
||||
@last_position = position
|
||||
@last_moved = Gosu.milliseconds
|
||||
end
|
||||
end
|
||||
|
||||
def button_down(id)
|
||||
case id
|
||||
when Gosu::MS_LEFT, Gosu::MS_MIDDLE, Gosu::MS_RIGHT
|
||||
@last_moved = Gosu.milliseconds
|
||||
end
|
||||
end
|
||||
|
||||
def last_moved
|
||||
(Gosu.milliseconds - @last_moved) / 1000.0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,7 +5,7 @@ module TAC
|
||||
header_bar("Manage Configurations")
|
||||
|
||||
menu_bar.clear do
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/plus.png"), image_height: 1.0, tip: "Add configuration" do
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/plus.png"), image_height: 1.0, tip: "Add configuration" do
|
||||
push_state(Dialog::NamePromptDialog, title: "Config Name", callback_method: proc { |name|
|
||||
window.backend.write_new_config(name)
|
||||
|
||||
@@ -13,15 +13,27 @@ module TAC
|
||||
populate_configs
|
||||
})
|
||||
end
|
||||
|
||||
button "Open Folder", tip: "Open folder containing configurations", height: 1.0 do
|
||||
if RUBY_PLATFORM =~ /mingw/
|
||||
system("explorer \"#{TAC::CONFIGS_PATH.gsub("/", "\\")}\"")
|
||||
elsif RUBY_PLATFORM =~ /darwin/
|
||||
system("open \"#{TAC::CONFIGS_PATH}\"")
|
||||
else
|
||||
system("xdg-open \"#{TAC::CONFIGS_PATH}\"")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
status_bar.clear do
|
||||
label "Current Configuration: "
|
||||
@config_label = label window.backend.settings.config
|
||||
flow(width: 1.0, max_width: 720, h_align: :center) do
|
||||
para "Current Configuration: "
|
||||
@config_label = para window.backend.settings.config
|
||||
end
|
||||
end
|
||||
|
||||
body.clear do
|
||||
@configs_list = stack width: 1.0, height: 1.0, scroll: true do
|
||||
@configs_list = stack width: 1.0, height: 1.0, margin_top: 36, max_width: 720, h_align: :center, scroll: true do
|
||||
end
|
||||
end
|
||||
|
||||
@@ -33,13 +45,14 @@ module TAC
|
||||
@config_files_list = @config_files.map { |file| Dialog::NamePromptDialog::NameStub.new(File.basename(file, ".json")) }
|
||||
|
||||
@configs_list.clear do
|
||||
@config_files.each_with_index do |config_file, i|
|
||||
flow width: 1.0, **THEME_ITEM_CONTAINER_PADDING do
|
||||
background i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
|
||||
|
||||
@config_files.sort_by { |f| [f.downcase] }.each_with_index do |config_file, i|
|
||||
flow width: 1.0, height: 36, **THEME_ITEM_CONTAINER_PADDING do
|
||||
name = File.basename(config_file, ".json")
|
||||
|
||||
button "#{name}", width: 0.94 do
|
||||
background i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR unless name == window.backend.settings.config
|
||||
background THEME_HIGHLIGHTED_COLOR if name == window.backend.settings.config
|
||||
|
||||
button "#{name}", fill: true, text_size: THEME_ICON_SIZE - 3 do
|
||||
change_config(name)
|
||||
|
||||
if window.backend.tacnet.connected?
|
||||
@@ -47,7 +60,7 @@ module TAC
|
||||
end
|
||||
end
|
||||
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/gear.png"), image_width: THEME_ICON_SIZE, tip: "Rename configuration" do
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/gear.png"), image_width: THEME_ICON_SIZE, tip: "Rename configuration" do
|
||||
push_state(Dialog::NamePromptDialog, title: "Rename Config", renaming: @config_files_list.find { |c| c.name == name }, list: @config_files_list, accept_label: "Update", callback_method: proc { |old_name, new_name|
|
||||
if not File.exist?("#{TAC::CONFIGS_PATH}/#{new_name}.json")
|
||||
FileUtils.mv(
|
||||
@@ -66,7 +79,7 @@ module TAC
|
||||
})
|
||||
end
|
||||
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/trashcan.png"), image_width: THEME_ICON_SIZE, **THEME_DANGER_BUTTON, tip: "Delete configuration" do
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/trashcan.png"), image_width: THEME_ICON_SIZE, **THEME_DANGER_BUTTON, tip: "Delete configuration" do
|
||||
push_state(Dialog::ConfirmDialog, title: "Delete Config?", dangerous: true, callback_method: proc {
|
||||
File.delete("#{TAC::CONFIGS_PATH}/#{name}.json")
|
||||
|
||||
@@ -92,6 +105,8 @@ module TAC
|
||||
window.backend.load_config(name)
|
||||
|
||||
@config_label.value = name.to_s
|
||||
|
||||
populate_configs
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,22 +7,25 @@ module TAC
|
||||
header_bar("Drive Team Rotation Generator")
|
||||
|
||||
@roster ||= [
|
||||
"Alexander",
|
||||
"Aubrey",
|
||||
"Cayden",
|
||||
"Connor",
|
||||
"Ben",
|
||||
"Dan",
|
||||
"Gabe",
|
||||
"Spencer",
|
||||
"Olivia"
|
||||
"Sodi"
|
||||
]
|
||||
|
||||
@roles ||= [
|
||||
"Coach",
|
||||
"Driver A",
|
||||
"Driver B"
|
||||
"Driver B",
|
||||
"Human"
|
||||
]
|
||||
|
||||
menu_bar.clear do
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/save.png"), image_height: 1.0, tip: "Export rotation as Comma-Seperated Values" do
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/save.png"), image_height: 1.0, tip: "Export rotation as Comma-Seperated Values" do
|
||||
export_rotation
|
||||
|
||||
@status_bar.clear do
|
||||
@@ -30,19 +33,19 @@ module TAC
|
||||
end
|
||||
end
|
||||
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/target.png"), margin_right: 10, image_height: 1.0, tip: "Generate rotation" do
|
||||
button "Generate", margin_right: 10, height: 1.0, tip: "Generate rotation" do
|
||||
populate_rotation
|
||||
end
|
||||
end
|
||||
|
||||
body.clear do
|
||||
flow(margin_left: 20, width: 1.0, height: 1.0) do
|
||||
stack(width: 0.25) do
|
||||
stack(width: 0.25, height: 1.0) do
|
||||
title "Roles", width: 1.0, margin_bottom: 4, text_align: :center
|
||||
|
||||
flow(width: 1.0, margin_bottom: 20) do
|
||||
@role_name = edit_line "", placeholder: "Add role", width: 0.9
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/plus.png"), image_width: 0.1 do
|
||||
flow(width: 1.0, height: 32, margin_bottom: 20) do
|
||||
@role_name = edit_line "", placeholder: "Add role", fill: true, height: 1.0
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/plus.png"), image_height: 1.0, tip: "Add role" do
|
||||
if @role_name.value.strip.length.positive?
|
||||
@roles.push(@role_name.value.strip)
|
||||
@role_name.value = ""
|
||||
@@ -52,16 +55,16 @@ module TAC
|
||||
end
|
||||
end
|
||||
|
||||
@roles_container = stack(width: 1.0, height: 0.835, scroll: true) do
|
||||
@roles_container = stack(width: 1.0, fill: true, scroll: true) do
|
||||
end
|
||||
end
|
||||
|
||||
stack(margin_left: 20, width: 0.25, height: 1.0) do
|
||||
title "Roster", width: 1.0, margin_bottom: 4, text_align: :center
|
||||
|
||||
flow(width: 1.0, margin_bottom: 20) do
|
||||
@roster_name = edit_line "", placeholder: "Add name", width: 0.9
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/plus.png"), image_width: 0.1 do
|
||||
flow(width: 1.0, height: 32, margin_bottom: 20) do
|
||||
@roster_name = edit_line "", placeholder: "Add name", height: 1.0, fill: true
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/plus.png"), image_height: 1.0, tip: "Add name" do
|
||||
if @roster_name.value.strip.length.positive?
|
||||
@roster.push(@roster_name.value.strip)
|
||||
@roster_name.value = ""
|
||||
@@ -71,14 +74,14 @@ module TAC
|
||||
end
|
||||
end
|
||||
|
||||
@roster_container = stack(width: 1.0, height: 0.835, scroll: true) do
|
||||
@roster_container = stack(width: 1.0, fill: true, scroll: true) do
|
||||
end
|
||||
end
|
||||
|
||||
stack(margin_left: 20, margin_right: 20, width: 0.5, height: 1.0) do
|
||||
stack(margin_left: 20, margin_right: 20, fill: true, height: 1.0) do
|
||||
title "Rotation", width: 1.0, margin_bottom: 4, text_align: :center
|
||||
|
||||
@rotation_container = stack(width: 1.0, height: 0.835, scroll: true) do
|
||||
@rotation_container = stack(width: 1.0, fill: true, scroll: true) do
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -92,11 +95,11 @@ module TAC
|
||||
def populate_roles
|
||||
@roles_container.clear do
|
||||
@roles.each_with_index do |name, i|
|
||||
flow(width: 1.0, padding: 2) do
|
||||
flow(width: 1.0, height: 32, padding: 2) do
|
||||
background i.even? ? 0xff_007000 : 0xff_006000
|
||||
|
||||
tagline name, width: 0.9
|
||||
button "<b>X</b>", width: 0.1, text_size: 18, **THEME_DANGER_BUTTON do
|
||||
tagline name, fill: true
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/trashcan.png"), image_height: 1.0, tip: "Remove role", **THEME_DANGER_BUTTON do
|
||||
@roles.delete(name)
|
||||
populate_roles
|
||||
end
|
||||
@@ -108,11 +111,11 @@ module TAC
|
||||
def populate_roster
|
||||
@roster_container.clear do
|
||||
@roster.each_with_index do |name, i|
|
||||
flow(width: 1.0, padding: 2) do
|
||||
flow(width: 1.0, height: 32, padding: 2) do
|
||||
background i.even? ? 0xff_007000 : 0xff_006000
|
||||
|
||||
tagline name, width: 0.9
|
||||
button "<b>X</b>", width: 0.1, text_size: 18, **THEME_DANGER_BUTTON do
|
||||
tagline name, fill: true
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/trashcan.png"), image_height: 1.0, tip: "Remove name", **THEME_DANGER_BUTTON do
|
||||
@roster.delete(name)
|
||||
populate_roster
|
||||
end
|
||||
@@ -125,13 +128,11 @@ module TAC
|
||||
@rotation = Generator.new(roster: @roster, team_size: @roles.count)
|
||||
|
||||
@rotation_container.clear do
|
||||
fraction = (1.0 / @rotation.team_size) - 0.02
|
||||
|
||||
flow(width: 1.0, padding: 2) do
|
||||
flow(width: 1.0, height: 32, padding: 2) do
|
||||
background Gosu::Color::BLACK
|
||||
|
||||
@roles.each do |role|
|
||||
tagline "<b>#{role}</b>", width: fraction
|
||||
tagline "<b>#{role}</b>", fill: true
|
||||
end
|
||||
end
|
||||
|
||||
@@ -139,11 +140,11 @@ module TAC
|
||||
teams = @rotation.teams.shuffle if @shuffle_teams&.value
|
||||
|
||||
teams.each_with_index do |team, i|
|
||||
flow(width: 1.0, padding: 2) do
|
||||
flow(width: 1.0, height: 32, padding: 2) do
|
||||
background i.even? ? 0xff_007000 : 0xff_006000
|
||||
|
||||
team.each do |player|
|
||||
tagline player, width: fraction
|
||||
tagline player, fill: true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -33,8 +33,15 @@ module TAC
|
||||
|
||||
button "Reset", text_size: THEME_HEADING_TEXT_SIZE, **THEME_DANGER_BUTTON do
|
||||
@nodes.clear
|
||||
measure_path
|
||||
|
||||
refresh_panel
|
||||
end
|
||||
|
||||
list_box items: ["CENTERSTAGE", "Power Play", "Freight Frenzy", "Ultimate Goal", "Skystone"], width: 200, height: 1.0 do |item|
|
||||
season = item.downcase.gsub(" ", "_").to_sym
|
||||
@field = TAC::Simulator::Field.new(container: @field_container, season: season, simulation: nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -43,7 +50,7 @@ module TAC
|
||||
tagline "Nodes:"
|
||||
@nodes_count_label = tagline "0"
|
||||
|
||||
tagline "Total Distance:"
|
||||
tagline "Total Distance:", margin_left: 20
|
||||
@total_distance_label = tagline "0"
|
||||
|
||||
@units_label = tagline "Inches"
|
||||
@@ -61,7 +68,7 @@ module TAC
|
||||
end
|
||||
end
|
||||
|
||||
@field = TAC::Simulator::Field.new(container: @field_container, season: :freight_frenzy, simulation: nil)
|
||||
@field = TAC::Simulator::Field.new(container: @field_container, season: :centerstage, simulation: nil)
|
||||
@nodes ||= []
|
||||
@unit = :inches
|
||||
@total_distance = 0
|
||||
@@ -72,6 +79,9 @@ module TAC
|
||||
@node_radius = 6
|
||||
@segment_thickness = 2
|
||||
|
||||
@font = CyberarmEngine::Text.new(font: THEME_BOLD_FONT, size: 18, border: true, static: true)
|
||||
@last_mouse_position = CyberarmEngine::Vector.new(window.mouse_x, window.mouse_y)
|
||||
|
||||
measure_path
|
||||
refresh_panel
|
||||
end
|
||||
@@ -82,11 +92,35 @@ module TAC
|
||||
@field.draw
|
||||
|
||||
display_path
|
||||
|
||||
if @field_container.hit?(window.mouse_x, window.mouse_y)
|
||||
x = (window.mouse_x - @field_container.x) / @field.scale - 72
|
||||
y = (window.mouse_y - @field_container.y) / @field.scale - 72
|
||||
|
||||
@font.text = "X: #{inches_to_unit(x).round(2)} Y: #{inches_to_unit(y).round(2)} (#{@unit.to_s})"
|
||||
@font.x = window.mouse_x + 6
|
||||
@font.y = window.mouse_y - (@font.height / 2.0 + 24)
|
||||
@font.z = 100_001
|
||||
|
||||
Gosu.draw_rect(
|
||||
window.mouse_x,
|
||||
@font.y - 6,
|
||||
@font.width + 12,
|
||||
@font.height + 12,
|
||||
0xaa_000000,
|
||||
100_000
|
||||
)
|
||||
|
||||
@font.draw
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
super
|
||||
|
||||
current_state.request_repaint if window.mouse_x != @last_mouse_position.x || window.mouse_y != @last_mouse_position.y
|
||||
@last_mouse_position = CyberarmEngine::Vector.new(window.mouse_x, window.mouse_y)
|
||||
|
||||
@field.update
|
||||
|
||||
measure_path
|
||||
@@ -133,7 +167,7 @@ module TAC
|
||||
Gosu.draw_circle(
|
||||
current_node.x * @field.scale + @field_container.x,
|
||||
current_node.y * @field.scale + @field_container.y,
|
||||
@node_radius, 7, mouse_near ? @node_hover_color : @node_color, 10
|
||||
@node_radius, 7, mouse_near ? @node_hover_color : @node_color, @field_container.z + 1
|
||||
)
|
||||
|
||||
next if i.zero?
|
||||
@@ -153,7 +187,8 @@ module TAC
|
||||
(@field_container.y + last_node.y * @field.scale) - distance,
|
||||
@segment_thickness,
|
||||
distance,
|
||||
@segment_color
|
||||
@segment_color,
|
||||
@field_container.z + 1
|
||||
)
|
||||
end
|
||||
|
||||
@@ -184,6 +219,10 @@ module TAC
|
||||
@total_distance_label.value = "#{inches_to_unit(@total_distance).round(2)}"
|
||||
@units_label.value = @unit.to_s.capitalize
|
||||
|
||||
status_bar.recalculate
|
||||
status_bar.recalculate
|
||||
status_bar.recalculate
|
||||
|
||||
# @points_container.clear do
|
||||
# v1 = @nodes.first
|
||||
# break unless v1
|
||||
@@ -214,11 +253,11 @@ module TAC
|
||||
when :feet
|
||||
inches / 12.0
|
||||
when :millimeters
|
||||
inches / 0.254
|
||||
inches * 25.4
|
||||
when :centimeters
|
||||
inches / 2.54
|
||||
inches * 2.54
|
||||
when :meters
|
||||
inches / 25.4
|
||||
inches * 0.0254
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
56
lib/pages/game_clock.rb
Normal file
56
lib/pages/game_clock.rb
Normal file
@@ -0,0 +1,56 @@
|
||||
module TAC
|
||||
class Pages
|
||||
class GameClock < Page
|
||||
def setup
|
||||
header_bar("Game Clock")
|
||||
|
||||
body.clear do
|
||||
flow(width: 1.0, height: 1.0) do
|
||||
@command_options = flow(width: 1.0) do
|
||||
stack(width: 0.3) do
|
||||
end
|
||||
|
||||
stack(width: 0.4) do
|
||||
banner "Choose Mode", width: 1.0, text_align: :center
|
||||
title "Local", width: 1.0, text_align: :center
|
||||
|
||||
button "Game Clock", width: 1.0 do
|
||||
push_state(PracticeGameClock::View)
|
||||
|
||||
window.fullscreen = true
|
||||
end
|
||||
|
||||
button "Dual Screen Game Clock", width: 1.0 do
|
||||
# Spawn game clock window
|
||||
$clock_pid = Process.spawn(
|
||||
RbConfig.ruby,
|
||||
"#{MEDIA_PATH}/../timecrafters_configuration_tool.rb",
|
||||
"--game-clock-remote-display"
|
||||
)
|
||||
|
||||
|
||||
# switch to remote control
|
||||
push_state(PracticeGameClock::RemoteControl::NetConnect)
|
||||
end
|
||||
|
||||
title "Remote", width: 1.0, text_align: :center, margin_top: 32
|
||||
button "Game Clock Display", width: 1.0 do
|
||||
push_state(PracticeGameClock::View, remote_control_mode: true)
|
||||
|
||||
window.fullscreen = true
|
||||
end
|
||||
|
||||
button "Game Clock Remote Control", width: 1.0 do
|
||||
push_state(PracticeGameClock::RemoteControl::NetConnect)
|
||||
end
|
||||
end
|
||||
|
||||
stack(width: 0.3) do
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -6,37 +6,71 @@ module TAC
|
||||
|
||||
body.clear do
|
||||
stack(width: 1.0, height: 1.0) do
|
||||
label TAC::NAME, width: 1.0, text_size: 48, text_align: :center
|
||||
para TAC::NAME, width: 1.0, text_size: 48, text_align: :center
|
||||
|
||||
stack(width: 1.0, height: 8) do
|
||||
background 0xff_006000
|
||||
end
|
||||
|
||||
if window.backend.settings.config.empty?
|
||||
label "TODO: Introduction"
|
||||
label "Get Started", text_size: 28
|
||||
para "TODO: Introduction"
|
||||
para "Get Started", text_size: 28
|
||||
button "1. Create a configuration" do
|
||||
page(TAC::Pages::Configurations)
|
||||
end
|
||||
label "2. Add a group"
|
||||
label "3. Add an action"
|
||||
label "4. Add a variable"
|
||||
label "5. Profit?"
|
||||
para "2. Add a group"
|
||||
para "3. Add an action"
|
||||
para "4. Add a variable"
|
||||
para "5. Profit?"
|
||||
else
|
||||
label "Display config stats or something?"
|
||||
para "Display config stats or something?"
|
||||
|
||||
config = window.backend.config
|
||||
groups = config.groups
|
||||
actions = config.groups.map { |g| g.actions }.flatten
|
||||
variables = actions.map { |a| a.variables }.flatten
|
||||
|
||||
label "Total groups: #{groups.size}"
|
||||
label "Total actions: #{actions.size}"
|
||||
label "Total variables: #{variables.size}"
|
||||
para "Total groups: #{groups.size}"
|
||||
para "Total actions: #{actions.size}"
|
||||
para "Total variables: #{variables.size}"
|
||||
end
|
||||
|
||||
stack(width: 1.0, fill: true, scroll: true, margin_top: 32) do
|
||||
heading, items = Gosu::LICENSES.split("\n\n")
|
||||
|
||||
title heading
|
||||
|
||||
items.split("\n").each do |item|
|
||||
name, website, license, license_website = item.split(",").map(&:strip)
|
||||
flow(width: 1.0, height: 28) do
|
||||
tagline "#{name} - "
|
||||
button "Website", height: 1.0, tip: website do
|
||||
open_url(website)
|
||||
end
|
||||
end
|
||||
|
||||
flow(width: 1.0, height: 22, margin_bottom: 20) do
|
||||
para "#{license} - "
|
||||
button "License Website", height: 1.0, text_size: 16, tip: license_website do
|
||||
open_url(license_website)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def open_url(url)
|
||||
case RUBY_PLATFORM
|
||||
when /mingw/ # windows
|
||||
system("start #{url}")
|
||||
when /linux/
|
||||
system("xdg-open #{url}")
|
||||
when /darwin/ # macos
|
||||
system("open #{url}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -11,90 +11,205 @@ module TAC
|
||||
|
||||
body.clear do
|
||||
flow(width: 1.0, height: 1.0) do
|
||||
@group_presets = stack(width: 0.49995, height: 1.0, scroll: true, border_thickness_right: 1, border_color: [0, Gosu::Color::BLACK, 0, 0]) do
|
||||
@group_presets = stack(fill: true, height: 1.0, scroll: true, padding_left: 2, padding_top: 2, padding_right: 2, border_thickness_right: 1, border_color: Gosu::Color::BLACK) do
|
||||
end
|
||||
|
||||
@action_presets = stack(width: 0.49995, height: 1.0, scroll: true) do
|
||||
@action_presets = stack(fill: true, height: 1.0, scroll: true, padding_left: 2, padding_top: 2, padding_right: 2) do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@scroll_into_view_list = []
|
||||
@highlight_item_container = nil
|
||||
@highlight_from_color = Gosu::Color.rgba(0, 0, 0, 0)
|
||||
@highlight_to_color = Gosu::Color.rgba(THEME_HIGHLIGHTED_COLOR.red, THEME_HIGHLIGHTED_COLOR.green, THEME_HIGHLIGHTED_COLOR.blue, 200)
|
||||
|
||||
@highlight_animator = CyberarmEngine::Animator.new(
|
||||
start_time: Gosu.milliseconds - 1,
|
||||
duration: 0,
|
||||
from: Gosu::Color.rgba(0, 0, 0, 0),
|
||||
to: THEME_HIGHLIGHTED_COLOR
|
||||
)
|
||||
|
||||
populate_group_presets
|
||||
populate_action_presets
|
||||
end
|
||||
|
||||
def draw
|
||||
super
|
||||
|
||||
unless @highlight_animator.complete?
|
||||
item = @highlight_item_container
|
||||
|
||||
Gosu.draw_rect(
|
||||
item.x, item.y,
|
||||
item.width, item.height,
|
||||
@highlight_animator.color_transition,
|
||||
item.z + 1
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
super
|
||||
|
||||
current_state.request_repaint unless @highlight_animator.complete?
|
||||
|
||||
while (hash = @scroll_into_view_list.shift)
|
||||
list_container = hash[:list]
|
||||
item_container = hash[:item]
|
||||
|
||||
return unless list_container
|
||||
return unless item_container
|
||||
|
||||
unless (item_container.y + item_container.height).between?(list_container.y, list_container.y + list_container.height)
|
||||
|
||||
list_container.scroll_top = (item_container.y + item_container.height) - (list_container.y + list_container.height)
|
||||
|
||||
list_container.recalculate
|
||||
end
|
||||
|
||||
@highlight_item_container = item_container
|
||||
@highlight_animator = CyberarmEngine::Animator.new(
|
||||
start_time: Gosu.milliseconds,
|
||||
duration: 750,
|
||||
from: @highlight_from_color,
|
||||
to: @highlight_to_color,
|
||||
tween: :ease_in_out_back
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def update_list_children(list)
|
||||
list.children.sort_by! { |i| i.style.tag.downcase }
|
||||
|
||||
list.children.each_with_index do |child, i|
|
||||
child.style.default[:background] = i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
|
||||
|
||||
child.root.gui_state.request_recalculate
|
||||
end
|
||||
end
|
||||
|
||||
def scroll_into_view(item)
|
||||
list_container = nil
|
||||
item_container = nil
|
||||
|
||||
if item.is_a?(TAC::Config::Group)
|
||||
list_container = @group_presets
|
||||
elsif item.is_a?(TAC::Config::Action)
|
||||
list_container = @action_presets
|
||||
else
|
||||
raise "Unsupported item type: #{item.class}"
|
||||
end
|
||||
|
||||
item_container = find_element_by_tag(list_container, item.name)
|
||||
|
||||
@scroll_into_view_list << { list: list_container, item: item_container }
|
||||
end
|
||||
|
||||
def add_group_container(group)
|
||||
index = window.backend.config.presets.groups.index(group)
|
||||
|
||||
flow width: 1.0, height: 36, **THEME_ITEM_CONTAINER_PADDING, tag: group.name do |container|
|
||||
background group == @active_group ? THEME_HIGHLIGHTED_COLOR : (index.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR)
|
||||
@active_group_container = container if group == @active_group
|
||||
|
||||
button group.name, fill: true, text_size: THEME_ICON_SIZE - 3, tag: "label" do
|
||||
page(TAC::Pages::Editor, { group: group, group_is_preset: true })
|
||||
end
|
||||
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/gear.png"), image_width: THEME_ICON_SIZE, tip: "Edit group" do
|
||||
push_state(
|
||||
Dialog::NamePromptDialog,
|
||||
title: "Rename Group Preset",
|
||||
renaming: group,
|
||||
list: window.backend.config.presets.groups,
|
||||
callback_method: method(:update_group_preset)
|
||||
)
|
||||
end
|
||||
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/trashcan.png"), image_width: THEME_ICON_SIZE, tip: "Delete group", **THEME_DANGER_BUTTON do
|
||||
push_state(
|
||||
Dialog::ConfirmDialog,
|
||||
title: "Are you sure?",
|
||||
message: "Delete group preset and all of its actions and variables?",
|
||||
callback_method: proc { delete_group_preset(group) }
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def add_action_container(action)
|
||||
index = window.backend.config.presets.actions.index(action)
|
||||
|
||||
stack width: 1.0, height: action.comment.empty? ? 36 : 72, **THEME_ITEM_CONTAINER_PADDING, tag: action.name do |container|
|
||||
background action == @active_action ? THEME_HIGHLIGHTED_COLOR : (index.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR)
|
||||
@active_action_container = container if action == @active_action
|
||||
|
||||
flow width: 1.0, height: 36 do
|
||||
button action.name, fill: true, text_size: THEME_ICON_SIZE - 3, tag: "label" do
|
||||
page(TAC::Pages::Editor, { action: action, action_is_preset: true })
|
||||
end
|
||||
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/gear.png"), image_width: THEME_ICON_SIZE, tip: "Edit action" do
|
||||
push_state(
|
||||
Dialog::ActionDialog,
|
||||
title: "Edit Action Preset",
|
||||
action: action,
|
||||
list: window.backend.config.presets.actions,
|
||||
callback_method: method(:update_action_preset)
|
||||
)
|
||||
end
|
||||
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/trashcan.png"), image_width: THEME_ICON_SIZE, tip: "Delete action", **THEME_DANGER_BUTTON do
|
||||
push_state(
|
||||
Dialog::ConfirmDialog,
|
||||
title: "Are you sure?",
|
||||
message: "Delete action preset and all of its actions and variables?",
|
||||
callback_method: proc { delete_action_preset(action) }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
stack(width: 1.0, fill: true, scroll: true, visible: !action.comment.empty?, tag: "comment_container") do
|
||||
caption action.comment.to_s, width: 1.0, text_wrap: :word_wrap, text_border: true, text_border_size: 1, text_border_color: 0xaa_000000, tag: "comment"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def populate_group_presets
|
||||
@group_presets.clear do
|
||||
window.backend.config.presets.groups.each_with_index do |group, i|
|
||||
flow(width: 1.0, **THEME_ITEM_CONTAINER_PADDING) do
|
||||
background i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
|
||||
|
||||
button group.name, width: 0.895 do
|
||||
page(TAC::Pages::Editor,{ group: group, group_is_preset: true })
|
||||
end
|
||||
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/gear.png"), image_width: THEME_ICON_SIZE, tip: "Edit group preset" do
|
||||
push_state(
|
||||
Dialog::NamePromptDialog,
|
||||
title: "Rename Group Preset",
|
||||
renaming: group,
|
||||
list: window.backend.config.presets.groups,
|
||||
callback_method: method(:update_group_preset)
|
||||
)
|
||||
end
|
||||
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/trashcan.png"), image_width: THEME_ICON_SIZE, tip: "Delete group preset", **THEME_DANGER_BUTTON do
|
||||
push_state(
|
||||
Dialog::ConfirmDialog,
|
||||
title: "Are you sure?",
|
||||
message: "Delete group preset and all of its actions and variables?",
|
||||
callback_method: proc { delete_group_preset(group) }
|
||||
)
|
||||
end
|
||||
end
|
||||
window.backend.config.presets.groups.each do |group|
|
||||
add_group_container(group)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def populate_action_presets
|
||||
@action_presets.clear do
|
||||
window.backend.config.presets.actions.each_with_index do |action, i|
|
||||
flow(width: 1.0, **THEME_ITEM_CONTAINER_PADDING) do
|
||||
background i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
|
||||
|
||||
button action.name, width: 0.895 do
|
||||
page(TAC::Pages::Editor,{ action: action, action_is_preset: true })
|
||||
end
|
||||
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/gear.png"), image_width: THEME_ICON_SIZE, tip: "Edit action preset" do
|
||||
push_state(
|
||||
Dialog::NamePromptDialog,
|
||||
title: "Rename Action Preset",
|
||||
renaming: action,
|
||||
list: window.backend.config.presets.actions,
|
||||
callback_method: method(:update_action_preset)
|
||||
)
|
||||
end
|
||||
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/trashcan.png"), image_width: THEME_ICON_SIZE, tip: "Delete action preset", **THEME_DANGER_BUTTON do
|
||||
push_state(
|
||||
Dialog::ConfirmDialog,
|
||||
title: "Are you sure?",
|
||||
message: "Delete action preset and all of its actions and variables?",
|
||||
callback_method: proc { delete_action_preset(action) }
|
||||
)
|
||||
end
|
||||
end
|
||||
window.backend.config.presets.actions.each do |action|
|
||||
add_action_container(action)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_group_preset(group, name)
|
||||
old_name = group.name
|
||||
|
||||
group.name = name
|
||||
window.backend.config.presets.groups.sort_by! { |g| g.name.downcase }
|
||||
window.backend.config_changed!
|
||||
|
||||
populate_group_presets
|
||||
group_container = find_element_by_tag(@group_presets, old_name)
|
||||
para = find_element_by_tag(group_container, "label")
|
||||
|
||||
label.value = name
|
||||
|
||||
group_container.style.tag = name
|
||||
|
||||
update_list_children(@group_presets)
|
||||
|
||||
scroll_into_view(group)
|
||||
end
|
||||
|
||||
def delete_group_preset(group)
|
||||
@@ -102,15 +217,42 @@ module TAC
|
||||
window.backend.config.presets.groups.sort_by! { |g| g.name.downcase }
|
||||
window.backend.config_changed!
|
||||
|
||||
populate_group_presets
|
||||
# Remove deleted action from list
|
||||
container = find_element_by_tag(@group_presets, group.name)
|
||||
@group_presets.remove(container)
|
||||
|
||||
update_list_children(@group_presets)
|
||||
end
|
||||
|
||||
def update_action_preset(action, name)
|
||||
def update_action_preset(action, name, comment)
|
||||
old_name = action.name
|
||||
|
||||
action.name = name
|
||||
action.comment = comment
|
||||
window.backend.config.presets.actions.sort_by! { |a| a.name.downcase }
|
||||
window.backend.config_changed!
|
||||
|
||||
populate_action_presets
|
||||
action_container = find_element_by_tag(@action_presets, old_name)
|
||||
para = find_element_by_tag(action_container, "label")
|
||||
comment_container = find_element_by_tag(action_container, "comment_container")
|
||||
comment_label = find_element_by_tag(action_container, "comment")
|
||||
|
||||
label.value = name
|
||||
if comment.empty?
|
||||
action_container.style.height = 36
|
||||
comment_container.hide
|
||||
comment_label.value = ""
|
||||
else
|
||||
action_container.style.height = 72
|
||||
comment_container.show
|
||||
comment_label.value = comment.to_s
|
||||
end
|
||||
|
||||
action_container.style.tag = name
|
||||
|
||||
update_list_children(@action_presets)
|
||||
|
||||
scroll_into_view(action)
|
||||
end
|
||||
|
||||
def delete_action_preset(action)
|
||||
@@ -118,8 +260,12 @@ module TAC
|
||||
window.backend.config.presets.actions.sort_by! { |a| a.name.downcase }
|
||||
window.backend.config_changed!
|
||||
|
||||
populate_action_presets
|
||||
# Remove deleted action from list
|
||||
container = find_element_by_tag(@action_presets, action.name)
|
||||
@action_presets.remove(container)
|
||||
|
||||
update_list_children(@action_presets)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,10 +5,10 @@ module TAC
|
||||
header_bar("Search")
|
||||
|
||||
menu_bar.clear do
|
||||
search = edit_line "", width: 0.9, height: 1.0
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/zoom.png"), image_height: 1.0 do
|
||||
search = edit_line "", fill: true, height: 1.0
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/zoom.png"), image_height: 1.0 do
|
||||
unless search.value.strip.empty?
|
||||
search_results = search_config(search.value.downcase.strip)
|
||||
search_results = search_config(search.value.strip)
|
||||
|
||||
status_bar.clear do
|
||||
if search_results.results.size.zero?
|
||||
@@ -22,7 +22,7 @@ module TAC
|
||||
shared_index = 0
|
||||
|
||||
flow(width: 1.0, height: 1.0) do
|
||||
stack(width: 0.495, height: 1.0, scroll: true) do
|
||||
stack(fill: true, height: 1.0, scroll: true, padding: 20, padding_right: 10) do
|
||||
if search_results.groups.size.positive?
|
||||
title "Groups"
|
||||
|
||||
@@ -44,7 +44,9 @@ module TAC
|
||||
search_results.actions.each do |result|
|
||||
stack(width: 1.0, **THEME_ITEM_CONTAINER_PADDING) do
|
||||
background shared_index.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
|
||||
button result.highlight(result.action.name), width: 1.0 do
|
||||
tip = "Group: #{result.group.name}"
|
||||
|
||||
button result.highlight(result.action.name), width: 1.0, tip: tip do
|
||||
page(TAC::Pages::Editor, { group: result.group, action: result.action, is_search: true })
|
||||
end
|
||||
|
||||
@@ -63,7 +65,9 @@ module TAC
|
||||
search_results.variables.each do |result|
|
||||
stack(width: 1.0, **THEME_ITEM_CONTAINER_PADDING) do
|
||||
background shared_index.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
|
||||
button "#{result.highlight(result.variable.name)} [#{result.highlight(result.variable.value)}]", width: 1.0 do
|
||||
tip = "Group: #{result.group.name}, Action: #{result.action.name}"
|
||||
|
||||
button "#{result.highlight(result.variable.name)} [#{result.variable.type}: #{result.highlight(result.variable.value)}]", width: 1.0, tip: tip do
|
||||
page(TAC::Pages::Editor, { group: result.group, action: result.action, variable: result.variable, is_search: true })
|
||||
end
|
||||
end
|
||||
@@ -73,13 +77,14 @@ module TAC
|
||||
end
|
||||
end
|
||||
|
||||
stack(width: 0.495, height: 1.0, scroll: true) do
|
||||
stack(fill: true, height: 1.0, scroll: true, padding: 20, padding_left: 10) do
|
||||
if search_results.group_presets.size.positive?
|
||||
title "Group Presets"
|
||||
|
||||
search_results.group_presets.each do |result|
|
||||
stack(width: 1.0, **THEME_ITEM_CONTAINER_PADDING) do
|
||||
background shared_index.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
|
||||
|
||||
button result.highlight(result.group.name), width: 1.0 do
|
||||
page(TAC::Pages::Editor, { group: result.group, group_is_preset: true, is_search: true })
|
||||
end
|
||||
@@ -89,14 +94,15 @@ module TAC
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if search_results.action_presets.size.positive?
|
||||
title "Action Presets"
|
||||
|
||||
search_results.action_presets.each do |result|
|
||||
stack(width: 1.0, **THEME_ITEM_CONTAINER_PADDING) do
|
||||
background shared_index.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
|
||||
button result.highlight(result.action.name), width: 1.0 do
|
||||
tip = result.group ? "Group: #{result.group.name}" : nil
|
||||
|
||||
button result.highlight(result.action.name), width: 1.0, tip: tip do
|
||||
if result.group.nil?
|
||||
page(TAC::Pages::Editor, { action: result.action, action_is_preset: true, is_search: true })
|
||||
else
|
||||
@@ -119,7 +125,9 @@ module TAC
|
||||
search_results.variables_from_presets.each do |result|
|
||||
stack(width: 1.0, **THEME_ITEM_CONTAINER_PADDING) do
|
||||
background shared_index.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
|
||||
button "#{result.highlight(result.variable.name)} [#{result.highlight(result.variable.value)}]", width: 1.0 do
|
||||
tip = result.group ? "Group: #{result.group.name}, Action: #{result.action.name}" : "Action: #{result.action.name}"
|
||||
|
||||
button "#{result.highlight(result.variable.name)} [#{result.variable.type}: #{result.highlight(result.variable.value)}]", width: 1.0, tip: tip do
|
||||
if result.group.nil?
|
||||
page(TAC::Pages::Editor, { action: result.action, variable: result.variable, action_is_preset: true, is_search: true })
|
||||
else
|
||||
@@ -152,7 +160,7 @@ module TAC
|
||||
|
||||
def search_groups(query, search_results)
|
||||
window.backend.config.groups.each do |group|
|
||||
if group.name.downcase.include?(query)
|
||||
if group.name.downcase.include?(query.downcase)
|
||||
result = SearchResult.new(group: group, query: query, is_group: true, is_from_name: true)
|
||||
search_results.results << result
|
||||
end
|
||||
@@ -162,12 +170,12 @@ module TAC
|
||||
def search_actions(query, search_results)
|
||||
window.backend.config.groups.each do |group|
|
||||
group.actions.each do |action|
|
||||
if action.name.downcase.include?(query)
|
||||
if action.name.downcase.include?(query.downcase)
|
||||
result = SearchResult.new(group: group, action: action, query: query, is_action: true, is_from_name: true)
|
||||
search_results.results << result
|
||||
end
|
||||
|
||||
if action.comment.downcase.include?(query)
|
||||
if action.comment.downcase.include?(query.downcase)
|
||||
result = SearchResult.new(group: group, action: action, query: query, is_action: true, is_from_comment: true)
|
||||
search_results.results << result
|
||||
end
|
||||
@@ -179,12 +187,12 @@ module TAC
|
||||
window.backend.config.groups.each do |group|
|
||||
group.actions.each do |action|
|
||||
action.variables.each do |variable|
|
||||
if variable.name.downcase.include?(query)
|
||||
if variable.name.downcase.include?(query.downcase)
|
||||
result = SearchResult.new(group: group, action: action, variable: variable, is_variable: true, query: query, is_from_name: true)
|
||||
search_results.results << result
|
||||
end
|
||||
|
||||
if variable.value.downcase.include?(query)
|
||||
if variable.value.downcase.include?(query.downcase)
|
||||
result = SearchResult.new(group: group, action: action, variable: variable, is_variable: true, query: query, is_from_value: true)
|
||||
search_results.results << result
|
||||
end
|
||||
@@ -195,29 +203,29 @@ module TAC
|
||||
|
||||
def search_presets(query, search_results)
|
||||
window.backend.config.presets.groups.each do |group|
|
||||
if group.name.downcase.include?(query)
|
||||
if group.name.downcase.include?(query.downcase)
|
||||
result = SearchResult.new(group: group, query: query, is_group: true, is_from_name: true, is_preset: true)
|
||||
search_results.results << result
|
||||
end
|
||||
|
||||
group.actions.each do |action|
|
||||
if action.name.downcase.include?(query)
|
||||
if action.name.downcase.include?(query.downcase)
|
||||
result = SearchResult.new(group: group, action: action, query: query, is_action: true, is_from_name: true, is_preset: true)
|
||||
search_results.results << result
|
||||
end
|
||||
|
||||
if action.comment.downcase.include?(query)
|
||||
if action.comment.downcase.include?(query.downcase)
|
||||
result = SearchResult.new(group: group, action: action, query: query, is_action: true, is_from_comment: true, is_preset: true)
|
||||
search_results.results << result
|
||||
end
|
||||
|
||||
action.variables.each do |variable|
|
||||
if variable.name.downcase.include?(query)
|
||||
if variable.name.downcase.include?(query.downcase)
|
||||
result = SearchResult.new(group: group, action: action, variable: variable, is_variable: true, query: query, is_from_name: true, is_preset: true)
|
||||
search_results.results << result
|
||||
end
|
||||
|
||||
if variable.value.downcase.include?(query)
|
||||
if variable.value.downcase.include?(query.downcase)
|
||||
result = SearchResult.new(group: group, action: action, variable: variable, is_variable: true, query: query, is_from_value: true, is_preset: true)
|
||||
search_results.results << result
|
||||
end
|
||||
@@ -226,27 +234,27 @@ module TAC
|
||||
end
|
||||
|
||||
window.backend.config.presets.actions.each do |action|
|
||||
if action.name.downcase.include?(query)
|
||||
result = SearchResult.new(group: nil, action: action, query: query, is_action: true, is_from_name: true, is_preset: true)
|
||||
if action.name.downcase.include?(query.downcase)
|
||||
result = SearchResult.new(group: nil, action: action, query: query, is_action: true, is_from_name: true, is_preset: true)
|
||||
search_results.results << result
|
||||
end
|
||||
|
||||
if action.comment.downcase.include?(query.downcase)
|
||||
result = SearchResult.new(group: nil, action: action, query: query, is_action: true, is_from_comment: true, is_preset: true)
|
||||
search_results.results << result
|
||||
end
|
||||
|
||||
action.variables.each do |variable|
|
||||
if variable.name.downcase.include?(query.downcase)
|
||||
result = SearchResult.new(group: nil, action: action, variable: variable, is_variable: true, query: query, is_from_name: true, is_preset: true)
|
||||
search_results.results << result
|
||||
end
|
||||
|
||||
if action.comment.downcase.include?(query)
|
||||
result = SearchResult.new(group: nil, action: action, query: query, is_action: true, is_from_comment: true, is_preset: true)
|
||||
if variable.value.downcase.include?(query.downcase)
|
||||
result = SearchResult.new(group: nil, action: action, variable: variable, is_variable: true, query: query, is_from_value: true, is_preset: true)
|
||||
search_results.results << result
|
||||
end
|
||||
|
||||
action.variables.each do |variable|
|
||||
if variable.name.downcase.include?(query)
|
||||
result = SearchResult.new(group: nil, action: action, variable: variable, is_variable: true, query: query, is_from_name: true, is_preset: true)
|
||||
search_results.results << result
|
||||
end
|
||||
|
||||
if variable.value.downcase.include?(query)
|
||||
result = SearchResult.new(group: nil, action: action, variable: variable, is_variable: true, query: query, is_from_value: true, is_preset: true)
|
||||
search_results.results << result
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -332,7 +340,8 @@ module TAC
|
||||
end
|
||||
|
||||
def highlight(string)
|
||||
string.gsub(/#{@query}/i, "<b><c=ff00ff>#{@query}</c></b>")
|
||||
match = string.match(/#{@query}/i)
|
||||
string.gsub(/#{@query}/i, "<b><c=ff00ff>#{match}</c></b>")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,30 +6,30 @@ module TAC
|
||||
header_bar("Simulator")
|
||||
|
||||
menu_bar.clear do
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/right.png"), tip: "Run Simulation", image_height: 1.0 do
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/right.png"), tip: "Run Simulation", image_height: 1.0 do
|
||||
save_source
|
||||
|
||||
begin
|
||||
@simulation = TAC::Simulator::Simulation.new(source_code: @source_code.value, field_container: @field_container)
|
||||
@simulation.start
|
||||
rescue SyntaxError, NameError, NoMethodError, TypeError, ArgumentError => e
|
||||
rescue SyntaxError, NameError, NoMethodError, TypeError, ArgumentError, StandardError => e
|
||||
puts e.backtrace.reverse.join("\n")
|
||||
puts e
|
||||
push_state(Dialog::AlertDialog, title: "#{e.class}", message: e)
|
||||
end
|
||||
end
|
||||
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/stop.png"), tip: "Stop Simulation", image_height: 1.0 do
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/stop.png"), tip: "Stop Simulation", image_height: 1.0 do
|
||||
@simulation.robots.each { |robot| robot.queue.clear } if @simulation
|
||||
end
|
||||
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/save.png"), tip: "Save", image_height: 1.0 do
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/save.png"), tip: "Save", image_height: 1.0 do
|
||||
save_source
|
||||
end
|
||||
end
|
||||
|
||||
status_bar.clear do
|
||||
@simulation_status = label ""
|
||||
@simulation_status = para ""
|
||||
end
|
||||
|
||||
body.clear do
|
||||
@@ -49,7 +49,7 @@ robot.forward 100
|
||||
robot.turn -90
|
||||
robot.forward 100"
|
||||
|
||||
source_code = File.read(SOURCE_FILE_PATH) if File.exists?(SOURCE_FILE_PATH)
|
||||
source_code = File.read(SOURCE_FILE_PATH) if File.exist?(SOURCE_FILE_PATH)
|
||||
|
||||
@source_code = edit_box source_code, width: 1.0, height: 1.0
|
||||
end
|
||||
@@ -77,6 +77,7 @@ robot.forward 100"
|
||||
@simulation.update
|
||||
|
||||
unless @simulation.robots.all? { |robot| robot.queue.empty? } # Only update clock if simulation is running
|
||||
current_state.request_repaint
|
||||
@simulation_status.value = "Time: #{(@simulation.simulation_time).round(1)} seconds"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,9 +6,9 @@ module TAC
|
||||
|
||||
menu_bar.clear do
|
||||
@connect_menu = flow(width: 1.0, height: 1.0) do
|
||||
label "Hostname", text_size: 28
|
||||
para "Hostname", text_size: 28
|
||||
hostname = edit_line window.backend.settings.hostname, width: 0.33, height: 1.0, text_size: 28
|
||||
label "Port", text_size: 28
|
||||
para "Port", text_size: 28
|
||||
port = edit_line window.backend.settings.port, width: 0.33, height: 1.0, text_size: 28
|
||||
button "Connect", height: 1.0, text_size: 28 do
|
||||
if hostname.value != window.backend.settings.hostname || port.value.to_i != window.backend.settings.port
|
||||
@@ -30,12 +30,12 @@ module TAC
|
||||
end
|
||||
|
||||
status_bar.clear do
|
||||
@tacnet_icon = image "#{TAC::ROOT_PATH}/media/icons/signal3.png", height: 26
|
||||
@status_label = label "TACNET: Not Connected", text_size: 26
|
||||
@tacnet_icon = image "#{TAC::MEDIA_PATH}/icons/signal3.png", height: 26
|
||||
@status_label = para "TACNET: Not Connected", text_size: 26
|
||||
end
|
||||
|
||||
body.clear do
|
||||
@full_status_label = label ""
|
||||
@full_status_label = para ""
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -12,14 +12,14 @@ module TAC
|
||||
@scale = 1
|
||||
@size = 0
|
||||
@field_size = 144 # inches [1 pixel = 1 inch]
|
||||
@z = @container.z + 1
|
||||
|
||||
@blue = Gosu::Color.new(0xff_004080)
|
||||
@red = Gosu::Color.new(0xff_800000)
|
||||
@soft_orange = Gosu::Color.rgb(255, 175, 0)
|
||||
end
|
||||
|
||||
def draw
|
||||
Gosu.flush
|
||||
|
||||
Gosu.clip_to(@position.x, @position.y, @size, @size) do
|
||||
Gosu.translate(@position.x, @position.y) do
|
||||
draw_field
|
||||
@@ -34,88 +34,90 @@ module TAC
|
||||
end
|
||||
|
||||
def draw_field
|
||||
Gosu.draw_rect(0, 0, @field_size * @scale, @field_size * @scale, Gosu::Color::GRAY)
|
||||
Gosu.draw_rect(0, 0, @field_size * @scale, @field_size * @scale, Gosu::Color::GRAY, @z)
|
||||
6.times do |i| # Tile lines across
|
||||
next if i == 0
|
||||
Gosu.draw_rect((@field_size * @scale) / 6 * i, 0, 1, @field_size * @scale, Gosu::Color::BLACK)
|
||||
Gosu.draw_rect((@field_size * @scale) / 6 * i, 0, 1, @field_size * @scale, Gosu::Color::BLACK, @z)
|
||||
end
|
||||
6.times do |i| # Tile lines down
|
||||
next if i == 0
|
||||
Gosu.draw_rect(0, (@field_size * @scale) / 6 * i, @field_size * @scale, 1, Gosu::Color::BLACK)
|
||||
Gosu.draw_rect(0, (@field_size * @scale) / 6 * i, @field_size * @scale, 1, Gosu::Color::BLACK, @z)
|
||||
end
|
||||
end
|
||||
|
||||
def draw_field_skystone
|
||||
# blue bridge
|
||||
Gosu.draw_rect(0, @field_size / 2 - 2, 48, 1, @blue)
|
||||
Gosu.draw_rect(0, @field_size / 2 + 1, 48, 1, @blue)
|
||||
Gosu.draw_rect(0, @field_size / 2 - 2, 48, 1, @blue, @z)
|
||||
Gosu.draw_rect(0, @field_size / 2 + 1, 48, 1, @blue, @z)
|
||||
|
||||
# mid bridge
|
||||
Gosu.draw_rect(@field_size / 2 - 24, @field_size / 2 - 9.25, 48, 18.5, Gosu::Color.new(0xff_222222))
|
||||
Gosu.draw_rect(@field_size / 2 - 24, @field_size / 2 - 2, 48, 1, Gosu::Color::YELLOW)
|
||||
Gosu.draw_rect(@field_size / 2 - 24, @field_size / 2 + 1, 48, 1, Gosu::Color::YELLOW)
|
||||
Gosu.draw_rect(@field_size / 2 - 24, @field_size / 2 - 9.25, 48, 18.5, Gosu::Color.new(0xff_222222), @z)
|
||||
Gosu.draw_rect(@field_size / 2 - 24, @field_size / 2 - 2, 48, 1, @soft_orange, @z)
|
||||
Gosu.draw_rect(@field_size / 2 - 24, @field_size / 2 + 1, 48, 1, @soft_orange, @z)
|
||||
|
||||
# blue bridge
|
||||
Gosu.draw_rect(@field_size - 48, @field_size / 2 - 2, 48, 1, @red)
|
||||
Gosu.draw_rect(@field_size - 48, @field_size / 2 + 1, 48, 1, @red)
|
||||
Gosu.draw_rect(@field_size - 48, @field_size / 2 - 2, 48, 1, @red, @z)
|
||||
Gosu.draw_rect(@field_size - 48, @field_size / 2 + 1, 48, 1, @red, @z)
|
||||
|
||||
# blue build site
|
||||
Gosu.draw_quad(
|
||||
24 - 2, 0, @blue,
|
||||
24, 0, @blue,
|
||||
0, 24 - 2, @blue,
|
||||
0, 24, @blue
|
||||
)
|
||||
0, 24, @blue,
|
||||
@z
|
||||
)
|
||||
|
||||
# red build site
|
||||
Gosu.draw_quad(
|
||||
@field_size - (24 - 2), 0, @red,
|
||||
@field_size - (24 - 0), 0, @red,
|
||||
@field_size, 24 - 2, @red,
|
||||
@field_size, 24, @red
|
||||
)
|
||||
@field_size, 24, @red,
|
||||
@z
|
||||
)
|
||||
|
||||
# blue depot
|
||||
Gosu.draw_rect(@field_size - 24, @field_size - 24, 24, 2, @blue)
|
||||
Gosu.draw_rect(@field_size - 24, @field_size - 24, 2, 24, @blue)
|
||||
Gosu.draw_rect(@field_size - 24, @field_size - 24, 24, 2, @blue, @z)
|
||||
Gosu.draw_rect(@field_size - 24, @field_size - 24, 2, 24, @blue, @z)
|
||||
|
||||
# red depot
|
||||
Gosu.draw_rect(-1, @field_size - 24, 24, 2, @red)
|
||||
Gosu.draw_rect(22, @field_size - 24, 2, 24, @red)
|
||||
Gosu.draw_rect(-1, @field_size - 24, 24, 2, @red, @z)
|
||||
Gosu.draw_rect(22, @field_size - 24, 2, 24, @red, @z)
|
||||
|
||||
# blue foundation
|
||||
Gosu.draw_rect(48, 4, 18.5, 34.5, @blue)
|
||||
Gosu.draw_rect(48, 4, 18.5, 34.5, @blue, @z)
|
||||
|
||||
# red foundation
|
||||
Gosu.draw_rect(@field_size - (48 + 18.5), 4, 18.5, 34.5, @red)
|
||||
Gosu.draw_rect(@field_size - (48 + 18.5), 4, 18.5, 34.5, @red, @z)
|
||||
|
||||
# stones
|
||||
6.times do |i|
|
||||
Gosu.draw_rect(48, @field_size - 8 * i - 8, 4, 8, Gosu::Color::YELLOW)
|
||||
Gosu.draw_rect(48, @field_size - 8 * i - 8, 4, 8, @soft_orange, @z)
|
||||
end
|
||||
6.times do |i|
|
||||
Gosu.draw_rect(@field_size - (48 + 4), @field_size - 8 * i - 8, 4, 8, Gosu::Color::YELLOW)
|
||||
Gosu.draw_rect(@field_size - (48 + 4), @field_size - 8 * i - 8, 4, 8, @soft_orange, @z)
|
||||
end
|
||||
end
|
||||
|
||||
def draw_field_ultimate_goal
|
||||
# middle line
|
||||
Gosu.draw_rect(0, @field_size / 2 - 13, @field_size, 2, Gosu::Color::WHITE)
|
||||
Gosu.draw_rect(0, @field_size / 2 - 13, @field_size, 2, Gosu::Color::WHITE, @z)
|
||||
|
||||
# phantom center line to indict half field for remote season field
|
||||
Gosu.draw_rect(@field_size / 2 - (0.5 + 24), 0, 1, @field_size, 0x88_448844)
|
||||
Gosu.draw_rect(@field_size / 2 - (0.5 + 24), 0, 1, @field_size, 0x88_448844, @z)
|
||||
|
||||
|
||||
# blue starting lines
|
||||
Gosu.draw_rect(24 - 1, @field_size - 24, 2, 24, @blue)
|
||||
Gosu.draw_rect(48 - 1, @field_size - 24, 2, 24, @blue)
|
||||
Gosu.draw_rect(24 - 1, @field_size - 24, 2, 24, @blue, @z)
|
||||
Gosu.draw_rect(48 - 1, @field_size - 24, 2, 24, @blue, @z)
|
||||
|
||||
# blue wobbly wobs
|
||||
Gosu.draw_circle(24, @field_size - 24, 4, 32, @blue)
|
||||
Gosu.draw_circle(48, @field_size - 24, 4, 32, @blue)
|
||||
Gosu.draw_circle(24, @field_size - 24, 4, 32, @blue, @z)
|
||||
Gosu.draw_circle(48, @field_size - 24, 4, 32, @blue, @z)
|
||||
|
||||
# blue starter stack
|
||||
Gosu.draw_rect(36 - 1, @field_size - 50, 2, 2, @blue)
|
||||
Gosu.draw_rect(36 - 1, @field_size - 50, 2, 2, @blue, @z)
|
||||
|
||||
# blue target zones
|
||||
# A
|
||||
@@ -132,15 +134,15 @@ module TAC
|
||||
end
|
||||
|
||||
# red starting lines
|
||||
Gosu.draw_rect(@field_size - 24 - 1, @field_size - 24, 2, 24, @red)
|
||||
Gosu.draw_rect(@field_size - 48 - 1, @field_size - 24, 2, 24, @red)
|
||||
Gosu.draw_rect(@field_size - 24 - 1, @field_size - 24, 2, 24, @red, @z)
|
||||
Gosu.draw_rect(@field_size - 48 - 1, @field_size - 24, 2, 24, @red, @z)
|
||||
|
||||
# red wobbly wobs
|
||||
Gosu.draw_circle(@field_size - 24, @field_size - 24, 4, 32, @red)
|
||||
Gosu.draw_circle(@field_size - 48, @field_size - 24, 4, 32, @red)
|
||||
Gosu.draw_circle(@field_size - 24, @field_size - 24, 4, 32, @red, @z)
|
||||
Gosu.draw_circle(@field_size - 48, @field_size - 24, 4, 32, @red, @z)
|
||||
|
||||
# red starter stack
|
||||
Gosu.draw_rect(@field_size - 37, @field_size - 50, 2, 2, @red)
|
||||
Gosu.draw_rect(@field_size - 37, @field_size - 50, 2, 2, @red, @z)
|
||||
|
||||
# red target zones
|
||||
# A
|
||||
@@ -161,91 +163,88 @@ module TAC
|
||||
|
||||
def draw_field_freight_frenzy
|
||||
# blue ZONE
|
||||
Gosu.draw_rect(24, @field_size - 24, 2, 24, @blue)
|
||||
Gosu.draw_rect(24, @field_size - 24, 24, 2, @blue)
|
||||
Gosu.draw_rect(48 - 2, @field_size - 24, 2, 24, @blue)
|
||||
Gosu.draw_rect(24, @field_size - 24, 2, 24, @blue, @z)
|
||||
Gosu.draw_rect(24, @field_size - 24, 24, 2, @blue, @z)
|
||||
Gosu.draw_rect(48 - 2, @field_size - 24, 2, 24, @blue, @z)
|
||||
|
||||
# blue barcode 1
|
||||
Gosu.draw_rect(36 - 1, @field_size - 24 - 4, 2, 2, @blue)
|
||||
Gosu.draw_rect(36 - 1, @field_size - 36 - 1, 2, 2, @blue)
|
||||
Gosu.draw_rect(36 - 1, @field_size - 48 + 2, 2, 2, @blue)
|
||||
Gosu.draw_rect(36 - 1, @field_size - 24 - 4, 2, 2, @blue, @z)
|
||||
Gosu.draw_rect(36 - 1, @field_size - 36 - 1, 2, 2, @blue, @z)
|
||||
Gosu.draw_rect(36 - 1, @field_size - 48 + 2, 2, 2, @blue, @z)
|
||||
|
||||
# blue barcode 2
|
||||
Gosu.draw_rect(36 - 1, 48 + 2, 2, 2, @blue)
|
||||
Gosu.draw_rect(36 - 1, 60 - 1, 2, 2, @blue)
|
||||
Gosu.draw_rect(36 - 1, 72 - 4, 2, 2, @blue)
|
||||
Gosu.draw_rect(36 - 1, 48 + 2, 2, 2, @blue, @z)
|
||||
Gosu.draw_rect(36 - 1, 60 - 1, 2, 2, @blue, @z)
|
||||
Gosu.draw_rect(36 - 1, 72 - 4, 2, 2, @blue, @z)
|
||||
|
||||
# blue wobble goal
|
||||
Gosu.draw_circle(48, 84, 9, 32, @blue)
|
||||
Gosu.draw_circle(48, 84, 9, 32, @blue, @z)
|
||||
|
||||
# blue shared wobble goal
|
||||
Gosu.draw_circle(@field_size / 2, 24, 9, 32, @blue)
|
||||
Gosu.draw_circle(@field_size / 2, 24, 9, 32, @blue, @z)
|
||||
|
||||
# red ZONE
|
||||
Gosu.draw_rect(@field_size - 24 - 2, @field_size - 24, 2, 24, @red)
|
||||
Gosu.draw_rect(@field_size - 48, @field_size - 24, 24, 2, @red)
|
||||
Gosu.draw_rect(@field_size - 48, @field_size - 24, 2, 24, @red)
|
||||
Gosu.draw_rect(@field_size - 24 - 2, @field_size - 24, 2, 24, @red, @z)
|
||||
Gosu.draw_rect(@field_size - 48, @field_size - 24, 24, 2, @red, @z)
|
||||
Gosu.draw_rect(@field_size - 48, @field_size - 24, 2, 24, @red, @z)
|
||||
|
||||
# red barcode 1
|
||||
Gosu.draw_rect(@field_size - 36 - 1, @field_size - 24 - 4, 2, 2, @red)
|
||||
Gosu.draw_rect(@field_size - 36 - 1, @field_size - 36 - 1, 2, 2, @red)
|
||||
Gosu.draw_rect(@field_size - 36 - 1, @field_size - 48 + 2, 2, 2, @red)
|
||||
Gosu.draw_rect(@field_size - 36 - 1, @field_size - 24 - 4, 2, 2, @red, @z)
|
||||
Gosu.draw_rect(@field_size - 36 - 1, @field_size - 36 - 1, 2, 2, @red, @z)
|
||||
Gosu.draw_rect(@field_size - 36 - 1, @field_size - 48 + 2, 2, 2, @red, @z)
|
||||
|
||||
# red barcode 2
|
||||
Gosu.draw_rect(@field_size - 36 - 1, 48 + 2, 2, 2, @red)
|
||||
Gosu.draw_rect(@field_size - 36 - 1, 60 - 1, 2, 2, @red)
|
||||
Gosu.draw_rect(@field_size - 36 - 1, 72 - 4, 2, 2, @red)
|
||||
Gosu.draw_rect(@field_size - 36 - 1, 48 + 2, 2, 2, @red, @z)
|
||||
Gosu.draw_rect(@field_size - 36 - 1, 60 - 1, 2, 2, @red, @z)
|
||||
Gosu.draw_rect(@field_size - 36 - 1, 72 - 4, 2, 2, @red, @z)
|
||||
|
||||
# red wobble goal
|
||||
Gosu.draw_circle(@field_size - 48, 84, 9, 32, @red)
|
||||
Gosu.draw_circle(@field_size - 48, 84, 9, 32, @red, @z)
|
||||
|
||||
# red shared wobble goal
|
||||
Gosu.clip_to(@field_size / 2, 0, 10, 48) do
|
||||
Gosu.draw_circle(@field_size / 2, 24, 9, 32, @red)
|
||||
end
|
||||
# Gosu.clip_to(@field_size / 2, 0, 10, 48) do
|
||||
Gosu.draw_circle(@field_size / 2, 24, 9, 32, @red, @z)
|
||||
# end
|
||||
|
||||
# white corner left
|
||||
faint_white = Gosu::Color.rgb(240, 240, 240)
|
||||
|
||||
Gosu.draw_rect(0, 46 - 2, 46, 2, faint_white)
|
||||
Gosu.draw_rect(46 - 2, 0, 2, 46, faint_white)
|
||||
Gosu.draw_rect(0, 46 - 2, 46, 2, faint_white, @z)
|
||||
Gosu.draw_rect(46 - 2, 0, 2, 46, faint_white, @z)
|
||||
# white corner right
|
||||
Gosu.draw_rect(@field_size - 46, 46 - 2, 46, 2, faint_white)
|
||||
Gosu.draw_rect(@field_size - 46, 0, 2, 46, faint_white)
|
||||
Gosu.draw_rect(@field_size - 46, 46 - 2, 46, 2, faint_white, @z)
|
||||
Gosu.draw_rect(@field_size - 46, 0, 2, 46, faint_white, @z)
|
||||
|
||||
# cross bars
|
||||
bar_gray = Gosu::Color.rgb(50, 50, 50)
|
||||
# MAIN
|
||||
Gosu.draw_rect(13.75, 48 - 2, @field_size - 13.75 * 2, 1, bar_gray)
|
||||
Gosu.draw_rect(13.75, 48 + 1, @field_size - 13.75 * 2, 1, bar_gray)
|
||||
Gosu.draw_rect(13.75, 48 - 2, 1, 4, Gosu::Color::BLACK)
|
||||
Gosu.draw_rect(@field_size - 13.75 - 1, 48 - 2, 1, 4, Gosu::Color::BLACK)
|
||||
Gosu.draw_rect(13.75, 48 - 2, @field_size - 13.75 * 2, 1, bar_gray, @z)
|
||||
Gosu.draw_rect(13.75, 48 + 1, @field_size - 13.75 * 2, 1, bar_gray, @z)
|
||||
Gosu.draw_rect(13.75, 48 - 2, 1, 4, Gosu::Color::BLACK, @z)
|
||||
Gosu.draw_rect(@field_size - 13.75 - 1, 48 - 2, 1, 4, Gosu::Color::BLACK, @z)
|
||||
|
||||
# BLUE
|
||||
Gosu.draw_rect(48 - 2, 13.75, 1, 48 - 13.75 - 2, bar_gray)
|
||||
Gosu.draw_rect(48 + 1, 13.75, 1, 48 - 13.75 - 2, bar_gray)
|
||||
Gosu.draw_rect(48 - 2, 13.75, 4, 1, Gosu::Color::BLACK)
|
||||
Gosu.draw_rect(48 - 2, 48 - 3, 4, 1, Gosu::Color::BLACK)
|
||||
Gosu.draw_rect(48 - 2, 13.75, 1, 48 - 13.75 - 2, bar_gray, @z)
|
||||
Gosu.draw_rect(48 + 1, 13.75, 1, 48 - 13.75 - 2, bar_gray, @z)
|
||||
Gosu.draw_rect(48 - 2, 13.75, 4, 1, Gosu::Color::BLACK, @z)
|
||||
Gosu.draw_rect(48 - 2, 48 - 3, 4, 1, Gosu::Color::BLACK, @z)
|
||||
|
||||
# RED
|
||||
Gosu.draw_rect(@field_size - 48 - 2, 13.75, 1, 48 - 13.75 - 2, bar_gray)
|
||||
Gosu.draw_rect(@field_size - 48 + 1, 13.75, 1, 48 - 13.75 - 2, bar_gray)
|
||||
Gosu.draw_rect(@field_size - 48 - 2, 13.75, 4, 1, Gosu::Color::BLACK)
|
||||
Gosu.draw_rect(@field_size - 48 - 2, 48 - 3, 4, 1, Gosu::Color::BLACK)
|
||||
Gosu.draw_rect(@field_size - 48 - 2, 13.75, 1, 48 - 13.75 - 2, bar_gray, @z)
|
||||
Gosu.draw_rect(@field_size - 48 + 1, 13.75, 1, 48 - 13.75 - 2, bar_gray, @z)
|
||||
Gosu.draw_rect(@field_size - 48 - 2, 13.75, 4, 1, Gosu::Color::BLACK, @z)
|
||||
Gosu.draw_rect(@field_size - 48 - 2, 48 - 3, 4, 1, Gosu::Color::BLACK, @z)
|
||||
|
||||
# Duck Delivery
|
||||
Gosu.draw_circle(2, @field_size - 2, 9, 16, Gosu::Color.rgb(75, 75, 75))
|
||||
Gosu.draw_circle(@field_size - 2, @field_size - 2, 9, 16, Gosu::Color.rgb(75, 75, 75))
|
||||
|
||||
# packages
|
||||
soft_orange = Gosu::Color.rgb(255, 175, 0)
|
||||
|
||||
7.times do |y|
|
||||
7.times do |x|
|
||||
if x.even?
|
||||
Gosu.draw_rect(x * 3 + 1, y * 3 + 1, 2, 2, soft_orange)
|
||||
Gosu.draw_rect(x * 3 + 1, y * 3 + 1, 2, 2, @soft_orange, @z)
|
||||
else
|
||||
Gosu.draw_circle(x * 3 + 2, y * 3 + 2, 1, 16, faint_white)
|
||||
Gosu.draw_circle(x * 3 + 2, y * 3 + 2, 1, 16, faint_white, @z)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -253,24 +252,200 @@ module TAC
|
||||
7.times do |y|
|
||||
7.times do |x|
|
||||
if x.even?
|
||||
Gosu.draw_rect((@field_size - 4) - x * 3 + 1, y * 3 + 1, 2, 2, soft_orange)
|
||||
Gosu.draw_rect((@field_size - 4) - x * 3 + 1, y * 3 + 1, 2, 2, @soft_orange, @z)
|
||||
else
|
||||
Gosu.draw_circle((@field_size - 4) - x * 3 + 2, y * 3 + 2, 1, 16, faint_white)
|
||||
Gosu.draw_circle((@field_size - 4) - x * 3 + 2, y * 3 + 2, 1, 16, faint_white, @z)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Gosu.draw_rect(0, 60 - 1, 2, 2, soft_orange)
|
||||
Gosu.draw_rect(0, 108 - 1, 2, 2, soft_orange)
|
||||
Gosu.draw_rect(@field_size - 2, 60 - 1, 2, 2, soft_orange)
|
||||
Gosu.draw_rect(@field_size - 2, 108 - 1, 2, 2, soft_orange)
|
||||
Gosu.draw_rect(0, 60 - 1, 2, 2, @soft_orange, @z)
|
||||
Gosu.draw_rect(0, 108 - 1, 2, 2, @soft_orange, @z)
|
||||
Gosu.draw_rect(@field_size - 2, 60 - 1, 2, 2, @soft_orange, @z)
|
||||
Gosu.draw_rect(@field_size - 2, 108 - 1, 2, 2, @soft_orange, @z)
|
||||
end
|
||||
|
||||
def draw_field_power_play
|
||||
# pole junctions (Drawn before ground junctions to be lazy- ground junctions will cover non-existant poles)
|
||||
5.times do |y|
|
||||
5.times do |x|
|
||||
Gosu.draw_circle(24 + (x * 24), 24 + (y * 24), 0.5, 16, @soft_orange, @z)
|
||||
end
|
||||
end
|
||||
|
||||
# ground junction
|
||||
3.times do |y|
|
||||
3.times do |x|
|
||||
Gosu.draw_circle(24 + (x * 48), 24 + (y * 48), 3, 16, Gosu::Color::BLACK, @z)
|
||||
end
|
||||
end
|
||||
|
||||
# Field cones
|
||||
2.times do |y|
|
||||
2.times do |x|
|
||||
Gosu.draw_circle(36 + (x * 72), 36 + (y * 72), 2, 16, x.zero? ? @blue : @red, @z)
|
||||
end
|
||||
end
|
||||
|
||||
# alliance LINEs
|
||||
2.times do |y|
|
||||
2.times do |x|
|
||||
Gosu.draw_rect(59 + (x * 24), y * (144 - 23.5), 2, 23.5, x.zero? ? @blue : @red, @z)
|
||||
end
|
||||
end
|
||||
|
||||
# alliance LINE cones
|
||||
2.times do |y|
|
||||
2.times do |x|
|
||||
Gosu.draw_circle(60 + (x * 24), y * (144 - 4) + 2, 2, 16, x.zero? ? @blue : @red, @z)
|
||||
end
|
||||
end
|
||||
|
||||
# Corner TAPE
|
||||
4.times do |i|
|
||||
Gosu.rotate(i * 90.0, 72, 72) do
|
||||
Gosu.draw_quad(
|
||||
24 - 2, 0, i.even? ? @red : @blue,
|
||||
24, 0, i.even? ? @red : @blue,
|
||||
0, 24 - 2, i.even? ? @red : @blue,
|
||||
0, 24, i.even? ? @red : @blue,
|
||||
@z
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Triangle TAPE
|
||||
2.times do |i|
|
||||
Gosu.rotate(i * 180.0, 72, 72) do
|
||||
Gosu.draw_quad(
|
||||
0, 72 - 10.5, i.odd? ? @red : @blue,
|
||||
10.5, 72, i.odd? ? @red : @blue,
|
||||
8.5, 72, i.odd? ? @red : @blue,
|
||||
0, 72 - 8.5, i.odd? ? @red : @blue,
|
||||
@z
|
||||
)
|
||||
|
||||
Gosu.draw_quad(
|
||||
0, 72 + 10.5, i.odd? ? @red : @blue,
|
||||
10.5, 72, i.odd? ? @red : @blue,
|
||||
8.5, 72, i.odd? ? @red : @blue,
|
||||
0, 72 + 8.5, i.odd? ? @red : @blue,
|
||||
@z
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
###########################
|
||||
### --- CENTERSTAGE --- ###
|
||||
###########################
|
||||
def draw_field_centerstage
|
||||
# Corner TAPE
|
||||
2.times do |i|
|
||||
Gosu.rotate((i + 2) * 90.0, 72, 72) do
|
||||
Gosu.draw_quad(
|
||||
24 - 2, 0, i.odd? ? @red : @blue,
|
||||
24, 0, i.odd? ? @red : @blue,
|
||||
0, 24 - 2, i.odd? ? @red : @blue,
|
||||
0, 24, i.odd? ? @red : @blue,
|
||||
@z
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Backstage TAPE
|
||||
# BLUE
|
||||
Gosu.draw_rect(0, 22, 58.5, 2, @blue, @z)
|
||||
Gosu.draw_quad(
|
||||
72 - 2, 0, @blue,
|
||||
72, 0, @blue,
|
||||
58.5 - 2, 24, @blue,
|
||||
58.5, 24, @blue,
|
||||
@z
|
||||
)
|
||||
# RED
|
||||
Gosu.draw_rect(@field_size, 22, -58.5, 2, @red, @z)
|
||||
Gosu.draw_quad(
|
||||
@field_size - (72 - 2), 0, @red,
|
||||
@field_size - 72, 0, @red,
|
||||
@field_size - (58.5 - 2), 24, @red,
|
||||
@field_size - 58.5, 24, @red,
|
||||
@z
|
||||
)
|
||||
|
||||
# Backstage BACKDROP
|
||||
Gosu.draw_rect(24, 0, 24, 11.25, 0xff_252525, @z)
|
||||
Gosu.draw_rect(@field_size - 48, 0, 24, 11.25, 0xff_252525, @z)
|
||||
|
||||
# Pixel TAPE
|
||||
7.times do |i|
|
||||
next if i == 3 # skip 4th iteration; empty slot
|
||||
|
||||
# TAPE
|
||||
Gosu.draw_rect(24 + 11.5 + (12 * i), @field_size - 6, 1, 6, 0xff_dddddd, @z)
|
||||
# Pixel
|
||||
Gosu.rotate(30, 24 + 12 + (12 * i), @field_size - 1.75) do
|
||||
Gosu.draw_circle(24 + 12 + (12 * i), @field_size - 1.75, 2.25, 6, 0xff_ffffff, @z)
|
||||
end
|
||||
end
|
||||
|
||||
# Spike marks TAPE
|
||||
# BLUE
|
||||
2.times do |r|
|
||||
2.times do |i|
|
||||
Gosu.rotate(r * 180, @field_size / 2, @field_size / 2 + 12) do
|
||||
c = r.even? ? @blue : @red
|
||||
Gosu.translate(0, i * 48) do
|
||||
Gosu.draw_rect(35.5, @field_size / 2 - 1.5, 12, 1, c, @z)
|
||||
Gosu.draw_rect(47.5 - 1, @field_size / 2 - 18, 1, 12, c, @z)
|
||||
Gosu.draw_rect(35.5, @field_size / 2 - 23.5, 12, 1, c, @z)
|
||||
# --- mark
|
||||
Gosu.draw_rect(35.5 + 6, @field_size / 2 - 1.5, 0.125, 1, 0xff_000000, @z)
|
||||
Gosu.draw_rect(47.5 - 1, @field_size / 2 - 18 + 6, 1, 0.125, 0xff_000000, @z)
|
||||
Gosu.draw_rect(35.5 + 6, @field_size / 2 - 23.5, 0.125, 1, 0xff_000000, @z)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Trusses
|
||||
Gosu.draw_rect(0, @field_size / 2 - 2, 2, 28, 0xff_656565, @z)
|
||||
Gosu.draw_rect(23, @field_size / 2 - 2, 2, 28, 0xff_656565, @z)
|
||||
Gosu.draw_rect(47, @field_size / 2 - 2, 2, 28, 0xff_656565, @z)
|
||||
Gosu.draw_rect(@field_size / 2 + 24 + -1, @field_size / 2 - 2, 2, 28, 0xff_656565, @z)
|
||||
Gosu.draw_rect(@field_size / 2 + 24 + 23, @field_size / 2 - 2, 2, 28, 0xff_656565, @z)
|
||||
Gosu.draw_rect(@field_size / 2 + 24 + 46, @field_size / 2 - 2, 2, 28, 0xff_656565, @z)
|
||||
|
||||
# Crossbeams
|
||||
# BLUE
|
||||
Gosu.draw_rect(0, @field_size / 2 + 12 + 2, 26, 2, @blue, @z)
|
||||
Gosu.draw_rect(24, @field_size / 2 + 12 - 1, 24, 2, @blue, @z)
|
||||
# YELLOW
|
||||
# --- Blue
|
||||
Gosu.draw_rect(0, @field_size / 2 + 2, 25, 1, @soft_orange, @z)
|
||||
Gosu.draw_rect(23, @field_size / 2 + 21, 26, 1, @soft_orange, @z)
|
||||
# --- Middle
|
||||
Gosu.draw_rect(24 + 24, @field_size / 2 + 2, 48, 1, @soft_orange, @z)
|
||||
Gosu.draw_rect(24 + 24, @field_size / 2 + 12 - 0.5, 48, 1, @soft_orange, @z)
|
||||
Gosu.draw_rect(24 + 24, @field_size / 2 + 12 - 0.5, 48, 1, @soft_orange, @z)
|
||||
# --- --- parallel beams
|
||||
Gosu.draw_rect(24 + 28, @field_size / 2 + 2, 1, 10, @soft_orange, @z)
|
||||
Gosu.draw_rect(@field_size / 2, @field_size / 2 + 2, 1, 10, @soft_orange, @z)
|
||||
Gosu.draw_rect(24 + 72 - 4, @field_size / 2 + 2, 1, 10, @soft_orange, @z)
|
||||
# --- Red
|
||||
Gosu.draw_rect(47 + 72, @field_size / 2 + 2, 25, 1, @soft_orange, @z)
|
||||
Gosu.draw_rect(23 + 72, @field_size / 2 + 21, 26, 1, @soft_orange, @z)
|
||||
# RED
|
||||
Gosu.draw_rect(24 + 72, @field_size / 2 + 12 - 1, 24, 2, @red, @z)
|
||||
Gosu.draw_rect(46 + 72, @field_size / 2 + 12 + 2, 26, 2, @red, @z)
|
||||
|
||||
end
|
||||
|
||||
def draw_tile_box(color)
|
||||
Gosu.draw_rect(0, 0, 24, 2, color)
|
||||
Gosu.draw_rect(22, 2, 2, 22, color)
|
||||
Gosu.draw_rect(0, 22, 22, 2, color)
|
||||
Gosu.draw_rect(0, 2, 2, 22, color)
|
||||
Gosu.draw_rect(0, 0, 24, 2, color, @z)
|
||||
Gosu.draw_rect(22, 2, 2, 22, color, @z)
|
||||
Gosu.draw_rect(0, 22, 22, 2, color, @z)
|
||||
Gosu.draw_rect(0, 2, 2, 22, color, @z)
|
||||
end
|
||||
|
||||
def update
|
||||
|
||||
@@ -1,44 +1,54 @@
|
||||
module TAC
|
||||
class Simulator
|
||||
class Robot
|
||||
attr_accessor :position, :angle
|
||||
attr_reader :alliance, :width, :depth
|
||||
def initialize(alliance:, width:, depth:)
|
||||
FONT = Gosu::Font.new(11)
|
||||
|
||||
attr_accessor :position, :angle, :comment
|
||||
attr_reader :alliance, :width, :depth, :z
|
||||
def initialize(alliance:, width:, depth:, container:)
|
||||
@alliance = alliance
|
||||
@width, @depth = width, depth
|
||||
@width = width
|
||||
@depth = depth
|
||||
@container = container
|
||||
|
||||
@position = CyberarmEngine::Vector.new
|
||||
@angle = 0
|
||||
@z = @container.z + 1
|
||||
|
||||
@queue = []
|
||||
|
||||
@unit = :ticks
|
||||
@ticks_per_revolution = 240
|
||||
@gear_ratio = 1
|
||||
|
||||
@comment = ""
|
||||
end
|
||||
|
||||
def draw
|
||||
Gosu.translate(@width / 2, @depth / 2) do
|
||||
Gosu.rotate(@angle, @position.x, @position.y) do
|
||||
Gosu.draw_rect(@position.x - @width / 2, @position.y - @depth / 2, @width, @depth, Gosu::Color::BLACK)
|
||||
Gosu.draw_rect(@position.x - @width / 2 + 1, @position.y - @depth / 2 + 1, @width - 2, @depth - 2, Gosu::Color.new(0xff_808022))
|
||||
Gosu.draw_rect(@position.x - @width / 2, @position.y - @depth / 2, @width, @depth, Gosu::Color::BLACK, @z)
|
||||
Gosu.draw_rect(@position.x - @width / 2 + 1, @position.y - @depth / 2 + 1, @width - 2, @depth - 2, Gosu::Color.new(0xff_808022), @z)
|
||||
|
||||
if @alliance == :blue
|
||||
Gosu.draw_arc(@position.x, @position.y, 6, 1.0, 32, 2, TAC::Palette::BLUE_ALLIANCE)
|
||||
Gosu.draw_arc(@position.x, @position.y, 6, 1.0, 32, 2, TAC::Palette::BLUE_ALLIANCE, @z)
|
||||
elsif @alliance == :red
|
||||
Gosu.draw_arc(@position.x, @position.y, 6, 1.0, 32, 2, TAC::Palette::RED_ALLIANCE)
|
||||
Gosu.draw_arc(@position.x, @position.y, 6, 1.0, 32, 2, TAC::Palette::RED_ALLIANCE, @z)
|
||||
else
|
||||
Gosu.draw_arc(@position.x, @position.y, 6, 1.0, 32, 2, @alliance)
|
||||
Gosu.draw_arc(@position.x, @position.y, 6, 1.0, 32, 2, @alliance, @z)
|
||||
end
|
||||
Gosu.draw_circle(@position.x, @position.y - @depth * 0.25, 2, 3, TAC::Palette::TIMECRAFTERS_TERTIARY)
|
||||
Gosu.draw_circle(@position.x, @position.y - @depth * 0.25, 2, 3, TAC::Palette::TIMECRAFTERS_TERTIARY, @z)
|
||||
end
|
||||
|
||||
FONT.draw_text(@comment, 2.2, 2.2, @z, 1, 1, Gosu::Color::BLACK)
|
||||
FONT.draw_text(@comment, 2, 2, @z)
|
||||
end
|
||||
end
|
||||
|
||||
def update(dt)
|
||||
@angle %= 360.0
|
||||
|
||||
if state = @queue.first
|
||||
if (state = @queue.first)
|
||||
state.update(dt)
|
||||
|
||||
if state.complete?
|
||||
@@ -87,6 +97,14 @@ module TAC
|
||||
@queue << Turn.new(robot: self, relative_angle: relative_angle, power: power)
|
||||
end
|
||||
|
||||
def delay(time_in_seconds)
|
||||
@queue << Delay.new(robot: self, time_in_seconds: time_in_seconds)
|
||||
end
|
||||
|
||||
def comment(comment)
|
||||
@queue << Comment.new(robot: self, comment: comment)
|
||||
end
|
||||
|
||||
def speed
|
||||
@ticks_per_revolution / @gear_ratio
|
||||
end
|
||||
@@ -95,7 +113,7 @@ module TAC
|
||||
@queue
|
||||
end
|
||||
|
||||
class State
|
||||
class State
|
||||
def start
|
||||
end
|
||||
|
||||
@@ -130,9 +148,10 @@ class State
|
||||
def draw
|
||||
Gosu.draw_line(
|
||||
@robot.position.x + @robot.width / 2, @robot.position.y + @robot.depth / 2, TAC::Palette::TIMECRAFTERS_TERTIARY,
|
||||
@goal.x + @robot.width / 2, @goal.y + @robot.depth / 2, TAC::Palette::TIMECRAFTERS_TERTIARY
|
||||
@goal.x + @robot.width / 2, @goal.y + @robot.depth / 2, TAC::Palette::TIMECRAFTERS_TERTIARY,
|
||||
@robot.z
|
||||
)
|
||||
Gosu.draw_rect(@goal.x + (@robot.width / 2 - 1), @goal.y + (@robot.depth / 2 - 1), 2, 2, Gosu::Color::RED)
|
||||
Gosu.draw_rect(@goal.x + (@robot.width / 2 - 1), @goal.y + (@robot.depth / 2 - 1), 2, 2, Gosu::Color::RED, @robot.z)
|
||||
end
|
||||
|
||||
def update(dt)
|
||||
@@ -176,9 +195,10 @@ class State
|
||||
def draw
|
||||
Gosu.draw_line(
|
||||
@robot.position.x + @robot.width / 2, @robot.position.y + @robot.depth / 2, TAC::Palette::TIMECRAFTERS_TERTIARY,
|
||||
@goal.x + @robot.width / 2, @goal.y + @robot.depth / 2, TAC::Palette::TIMECRAFTERS_TERTIARY
|
||||
@goal.x + @robot.width / 2, @goal.y + @robot.depth / 2, TAC::Palette::TIMECRAFTERS_TERTIARY,
|
||||
@robot.z
|
||||
)
|
||||
Gosu.draw_rect(@goal.x + (@robot.width / 2 - 1), @goal.y + (@robot.depth / 2 - 1), 2, 2, Gosu::Color::RED)
|
||||
Gosu.draw_rect(@goal.x + (@robot.width / 2 - 1), @goal.y + (@robot.depth / 2 - 1), 2, 2, Gosu::Color::RED, @robot.z)
|
||||
end
|
||||
|
||||
def update(dt)
|
||||
@@ -230,7 +250,8 @@ class State
|
||||
fraction,
|
||||
360,
|
||||
1,
|
||||
TAC::Palette::TIMECRAFTERS_TERTIARY
|
||||
TAC::Palette::TIMECRAFTERS_TERTIARY,
|
||||
@robot.z
|
||||
)
|
||||
end
|
||||
|
||||
@@ -239,7 +260,8 @@ class State
|
||||
@robot.position.y + @robot.depth / 2 + Gosu.offset_y(@target_angle, @robot.width > @robot.depth ? @robot.width : @robot.depth),
|
||||
1,
|
||||
9,
|
||||
Gosu::Color::RED
|
||||
Gosu::Color::RED,
|
||||
@robot.z
|
||||
)
|
||||
# Gosu.draw_arc(@position.x, @position.y, 6, 1.0, 32, 2, @alliance)
|
||||
end
|
||||
@@ -259,6 +281,58 @@ class State
|
||||
@last_angle = @robot.angle
|
||||
end
|
||||
end
|
||||
|
||||
class Delay < State
|
||||
def initialize(robot:, time_in_seconds:)
|
||||
@robot = robot
|
||||
@time_in_seconds = time_in_seconds
|
||||
|
||||
@accumulator = 0.0
|
||||
end
|
||||
|
||||
def start
|
||||
@complete = false
|
||||
end
|
||||
|
||||
def draw
|
||||
fraction = @accumulator / @time_in_seconds.to_f
|
||||
|
||||
Gosu.draw_arc(
|
||||
@robot.position.x + @robot.width / 2,
|
||||
@robot.position.y + @robot.depth / 2,
|
||||
@robot.width > @robot.depth ? @robot.width : @robot.depth,
|
||||
1 - fraction,
|
||||
360,
|
||||
1,
|
||||
TAC::Palette::TIMECRAFTERS_TERTIARY,
|
||||
@robot.z
|
||||
)
|
||||
|
||||
@complete = fraction >= 1
|
||||
end
|
||||
|
||||
def update(dt)
|
||||
@accumulator += dt
|
||||
end
|
||||
end
|
||||
|
||||
class Comment < State
|
||||
def initialize(robot:, comment:)
|
||||
@robot = robot
|
||||
@comment = comment
|
||||
end
|
||||
|
||||
def start
|
||||
@robot.comment = @comment
|
||||
@complete = true
|
||||
end
|
||||
|
||||
def draw
|
||||
end
|
||||
|
||||
def update(dt)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -8,7 +8,7 @@ module TAC
|
||||
@field_container = field_container
|
||||
|
||||
@robots = []
|
||||
@field = Field.new(simulation: self, season: :freight_frenzy, container: @field_container)
|
||||
@field = Field.new(simulation: self, season: :centerstage, container: @field_container)
|
||||
@show_paths = false
|
||||
|
||||
@last_milliseconds = Gosu.milliseconds
|
||||
@@ -29,7 +29,7 @@ module TAC
|
||||
def update
|
||||
@accumulator += (Gosu.milliseconds - @last_milliseconds) / 1000.0
|
||||
|
||||
while(@accumulator > @simulation_step)
|
||||
while @accumulator > @simulation_step
|
||||
@field.update
|
||||
@robots.each { |robot| robot.update(@simulation_step) }
|
||||
|
||||
@@ -41,7 +41,7 @@ module TAC
|
||||
end
|
||||
|
||||
def create_robot(alliance:, width:, depth:)
|
||||
robot = Simulator::Robot.new(alliance: alliance, width: width, depth: depth)
|
||||
robot = Simulator::Robot.new(alliance: alliance, width: width, depth: depth, container: @field_container)
|
||||
@robots << robot
|
||||
|
||||
return robot
|
||||
|
||||
@@ -2,16 +2,18 @@ module TAC
|
||||
class States
|
||||
class Boot < CyberarmEngine::GuiState
|
||||
def setup
|
||||
window.show_cursor = true
|
||||
|
||||
stack width: 1.0, height: 1.0 do
|
||||
background [TAC::Palette::TIMECRAFTERS_PRIMARY, TAC::Palette::TIMECRAFTERS_SECONDARY, TAC::Palette::TIMECRAFTERS_TERTIARY, TAC::Palette::TIMECRAFTERS_PRIMARY]
|
||||
end
|
||||
|
||||
@title_font = CyberarmEngine::Text.new(TAC::NAME, z: 100, size: 72, border: true, border_size: 3, font: THEME[:Label][:font])
|
||||
@logo = Gosu::Image.new("#{TAC::ROOT_PATH}/media/logo.png")
|
||||
@title_font = CyberarmEngine::Text.new(TAC::NAME, z: 100, size: 72, border: true, border_size: 2, border_color: 0xff_000000, font: THEME[:TextBlock][:font], static: true)
|
||||
@logo = Gosu::Image.new("#{TAC::MEDIA_PATH}/logo.png")
|
||||
|
||||
@title_animator = CyberarmEngine::Animator.new(start_time: 0, duration: 750, from: 0.0, to: 1.0, tween: :swing_from_to)
|
||||
@logo_animator = CyberarmEngine::Animator.new(start_time: 750, duration: 1_000, from: 0.0, to: 1.0, tween: :swing_to)
|
||||
@transition_animator = CyberarmEngine::Animator.new(start_time: 2_250, duration: 750, from: 0, to: 255, tween: :ease_out)
|
||||
@title_animator = CyberarmEngine::Animator.new(start_time: Gosu.milliseconds + 0, duration: 750, from: 0.0, to: 1.0, tween: :swing_from_to)
|
||||
@logo_animator = CyberarmEngine::Animator.new(start_time: Gosu.milliseconds + 750, duration: 1_000, from: 0.0, to: 1.0, tween: :swing_to)
|
||||
@transition_animator = CyberarmEngine::Animator.new(start_time: Gosu.milliseconds + 2_250, duration: 750, from: 0, to: 255, tween: :ease_out)
|
||||
@transition_color = Gosu::Color.new(0x00_111111)
|
||||
|
||||
@next_state = Editor
|
||||
@@ -28,17 +30,23 @@ module TAC
|
||||
def update
|
||||
super
|
||||
|
||||
request_repaint
|
||||
|
||||
@title_font.x = window.width / 2 - @title_font.width / 2
|
||||
@title_font.y = (window.height / 2 - (@logo.height / 2 + @title_font.height)) * @title_animator.transition
|
||||
|
||||
@transition_color.alpha = @transition_animator.transition
|
||||
|
||||
push_state(@next_state) if @transition_color.alpha >= 255
|
||||
if @transition_color.alpha >= 255
|
||||
pop_state
|
||||
push_state(@next_state)
|
||||
end
|
||||
end
|
||||
|
||||
def button_up(id)
|
||||
def button_down(id)
|
||||
super
|
||||
|
||||
pop_state
|
||||
push_state(@next_state)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,9 +3,13 @@ class Editor < CyberarmEngine::GuiState
|
||||
attr_reader :header_bar, :header_bar_label, :navigation, :content, :menu_bar, :status_bar, :body
|
||||
|
||||
def setup
|
||||
window.show_cursor = true
|
||||
|
||||
@window_width = 0
|
||||
@window_height = 0
|
||||
|
||||
@last_tacnet_status = nil
|
||||
|
||||
@pages = {}
|
||||
@page = nil
|
||||
|
||||
@@ -32,18 +36,18 @@ class Editor < CyberarmEngine::GuiState
|
||||
@header_bar = flow(width: 1.0, height: 36) do
|
||||
background 0xff_006000
|
||||
|
||||
@header_bar_label = label TAC::NAME, width: 1.0, text_align: :center, text_size: 32
|
||||
@header_bar_label = para TAC::NAME, fill: true, text_align: :center, text_size: 32, font: TAC::THEME_BOLD_FONT, margin_left: BORDERLESS ? 36 * 3 : 0
|
||||
|
||||
@window_controls = flow(x: window.width - 36 * 2, y: 0, height: 1.0) do
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/minus.png"), tip: "Minimize", image_height: 1.0 do
|
||||
@window_controls = flow(width: 36 * 3, height: 1.0) do
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/minus.png"), tip: "Minimize", image_height: 1.0 do
|
||||
window.minimize if window.respond_to?(:minimize)
|
||||
end
|
||||
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/larger.png"), tip: "Maximize", image_height: 1.0 do |btn|
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/larger.png"), tip: "Maximize", image_height: 1.0 do |btn|
|
||||
window.maximize if window.respond_to?(:maximize)
|
||||
end
|
||||
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/cross.png"), tip: "Exit", image_height: 1.0, **TAC::THEME_DANGER_BUTTON do
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/cross.png"), tip: "Exit", image_height: 1.0, **TAC::THEME_DANGER_BUTTON do
|
||||
window.close
|
||||
end
|
||||
end
|
||||
@@ -53,44 +57,57 @@ class Editor < CyberarmEngine::GuiState
|
||||
@navigation = stack(width: 64, height: 1.0, scroll: true) do
|
||||
background 0xff_333333
|
||||
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/home.png"), margin: 4, tip: "Home", image_width: 1.0 do
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/home.png"), margin: 4, tip: "Home", image_width: 1.0 do
|
||||
page(TAC::Pages::Home)
|
||||
end
|
||||
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/menuList.png"), margin: 4, tip: "Editor", image_width: 1.0 do
|
||||
page(TAC::Pages::Editor)
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/menuList.png"), margin: 4, tip: "Editor", image_width: 1.0 do
|
||||
page(TAC::Pages::EditorV3)
|
||||
end
|
||||
|
||||
@tacnet_button = button get_image("#{TAC::ROOT_PATH}/media/icons/signal3.png"), margin: 4, tip: "TACNET", image_width: 1.0 do
|
||||
@tacnet_button = button get_image("#{TAC::MEDIA_PATH}/icons/signal3.png"), margin: 4, tip: "TACNET", image_width: 1.0 do
|
||||
page(TAC::Pages::TACNET)
|
||||
end
|
||||
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/right.png"), margin: 4, tip: "Simulator", image_width: 1.0 do
|
||||
page(TAC::Pages::Simulator)
|
||||
end
|
||||
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/gear.png"), margin: 4, tip: "Configurations", image_width: 1.0 do
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/gear.png"), margin: 4, tip: "Configurations", image_width: 1.0 do
|
||||
page(TAC::Pages::Configurations)
|
||||
end
|
||||
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/menuGrid.png"), margin: 4, tip: "Presets", image_width: 1.0 do
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/menuGrid.png"), margin: 4, tip: "Presets", image_width: 1.0 do
|
||||
page(TAC::Pages::Presets)
|
||||
end
|
||||
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/zoom.png"), margin: 4, tip: "Search", image_width: 1.0 do
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/zoom.png"), margin: 4, tip: "Search", image_width: 1.0 do
|
||||
page(TAC::Pages::Search)
|
||||
end
|
||||
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/joystickLeft.png"), margin: 4, tip: "Field Planner", image_width: 1.0 do
|
||||
stack(margin_left: 4, width: 1.0, margin_right: 4) do
|
||||
background 0xff_444444
|
||||
para "Tools", width: 1.0, text_align: :center
|
||||
end
|
||||
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/right.png"), margin: 4, tip: "Simulator", image_width: 1.0 do
|
||||
page(TAC::Pages::Simulator)
|
||||
end
|
||||
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/joystickLeft.png"), margin: 4, tip: "Field Planner", image_width: 1.0 do
|
||||
page(TAC::Pages::FieldPlanner)
|
||||
end
|
||||
|
||||
button get_image("#{TAC::ROOT_PATH}/media/icons/massiveMultiplayer.png"), margin: 4, tip: "Drive Team Rotation Generator", image_width: 1.0 do
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/massiveMultiplayer.png"), margin: 4, tip: "Drive Team Rotation Generator", image_width: 1.0 do
|
||||
page(TAC::Pages::DriveTeamRotationGenerator)
|
||||
end
|
||||
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/custom_stopWatch.png"), margin: 4, tip: "Game Clock", image_width: 1.0 do
|
||||
page(TAC::Pages::GameClock)
|
||||
end
|
||||
|
||||
button get_image("#{TAC::MEDIA_PATH}/icons/power.png"), margin: 4, tip: "Exit", image_width: 1.0, **TAC::THEME_DANGER_BUTTON do
|
||||
window.close
|
||||
end
|
||||
end
|
||||
|
||||
@content = stack(width: window.width - @navigation.style.width, height: 1.0) do
|
||||
@content = stack(fill: true, height: 1.0) do
|
||||
@chrome = stack(width: 1.0, height: 96) do
|
||||
@menu_bar = flow(width: 1.0, height: 48, padding: 8) do
|
||||
background 0xff_008000
|
||||
@@ -113,9 +130,9 @@ class Editor < CyberarmEngine::GuiState
|
||||
end
|
||||
|
||||
def draw
|
||||
super
|
||||
@page&.draw
|
||||
|
||||
@page.draw if @page
|
||||
super
|
||||
end
|
||||
|
||||
def update
|
||||
@@ -123,15 +140,35 @@ class Editor < CyberarmEngine::GuiState
|
||||
|
||||
@page.update if @page
|
||||
|
||||
case window.backend.tacnet.status
|
||||
when :not_connected
|
||||
@tacnet_button.style.color = Gosu::Color::WHITE
|
||||
when :connecting
|
||||
@tacnet_button.style.color = TAC::Palette::TACNET_CONNECTING
|
||||
when :connected
|
||||
@tacnet_button.style.color = TAC::Palette::TACNET_CONNECTED
|
||||
when :connection_error
|
||||
@tacnet_button.style.color = TAC::Palette::TACNET_CONNECTION_ERROR
|
||||
if @last_tacnet_status != window.backend.tacnet.status
|
||||
@last_tacnet_status = window.backend.tacnet.status
|
||||
|
||||
case window.backend.tacnet.status
|
||||
when :not_connected
|
||||
@tacnet_button.style.color = Gosu::Color::WHITE
|
||||
@header_bar.style.background = 0xff_006000
|
||||
when :connected
|
||||
@tacnet_button.style.color = Gosu::Color::WHITE
|
||||
@header_bar.style.background = TAC::Palette::TACNET_PRIMARY
|
||||
when :connecting
|
||||
@tacnet_button.style.color = TAC::Palette::TACNET_CONNECTING
|
||||
@header_bar.style.background = TAC::Palette::TACNET_CONNECTING
|
||||
when :connection_error
|
||||
@tacnet_button.style.color = TAC::Palette::TACNET_CONNECTION_ERROR
|
||||
@header_bar.style.background = TAC::Palette::TACNET_CONNECTION_ERROR
|
||||
|
||||
unless @page.is_a?(TAC::Pages::TACNET)
|
||||
push_state(TAC::Dialog::TACNETDialog, title: "TACNET Connection Error", message: window.backend.tacnet.full_status)
|
||||
end
|
||||
end
|
||||
|
||||
@tacnet_button.style.default[:color] = @tacnet_button.style.color
|
||||
@header_bar.style.default[:background] = @header_bar.style.background
|
||||
|
||||
@tacnet_button.recalculate
|
||||
@header_bar.recalculate
|
||||
|
||||
request_repaint
|
||||
end
|
||||
|
||||
window.width = Gosu.available_width / 2 if window.width < Gosu.available_width / 2
|
||||
@@ -196,4 +233,4 @@ class Editor < CyberarmEngine::GuiState
|
||||
TAC::Pages::Search
|
||||
].include?(klass)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
module TAC
|
||||
ROOT_PATH = File.expand_path("../..", __FILE__)
|
||||
if ARGV.join.include?("--dev")
|
||||
ROOT_PATH = File.expand_path("../..", __FILE__)
|
||||
else
|
||||
ROOT_PATH = "#{Dir.home}/TimeCrafters_Configuration_Tool"
|
||||
end
|
||||
CONFIGS_PATH = "#{ROOT_PATH}/data/configs"
|
||||
SETTINGS_PATH = "#{ROOT_PATH}/data/settings.json"
|
||||
|
||||
MEDIA_PATH = "#{File.expand_path("../..", __FILE__)}/media"
|
||||
|
||||
CONFIG_SPEC_VERSION = 2
|
||||
end
|
||||
@@ -1,6 +1,6 @@
|
||||
module TAC
|
||||
class TACNET
|
||||
DEFAULT_HOSTNAME = "192.168.49.1"
|
||||
DEFAULT_HOSTNAME = "192.168.49.1".freeze
|
||||
DEFAULT_PORT = 8962
|
||||
|
||||
SYNC_INTERVAL = 250 # ms
|
||||
@@ -30,7 +30,7 @@ module TAC
|
||||
end
|
||||
|
||||
def full_status
|
||||
_status = status.to_s.split("_").map { |c| c.capitalize }.join(" ")
|
||||
_status = status.to_s.split("_").map(&:capitalize).join(" ")
|
||||
|
||||
if connected?
|
||||
net_stats = ""
|
||||
@@ -42,7 +42,7 @@ module TAC
|
||||
|
||||
"<b>Status:</b> #{_status}\n\n#{net_stats}"
|
||||
elsif @connection&.client && @connection.client.socket_error?
|
||||
"<b>Status:</b> #{_status}\n\n#{@connection.client.last_socket_error.to_s}"
|
||||
"<b>Status:</b> #{_status}\n\n#{@connection.client.last_socket_error}"
|
||||
else
|
||||
"<b>Status:</b> #{_status}"
|
||||
end
|
||||
@@ -53,10 +53,10 @@ module TAC
|
||||
end
|
||||
|
||||
def close
|
||||
if connected?
|
||||
@connection.close
|
||||
@connection = nil
|
||||
end
|
||||
return unless connected?
|
||||
|
||||
@connection.close
|
||||
@connection = nil
|
||||
end
|
||||
|
||||
def client
|
||||
@@ -75,4 +75,4 @@ module TAC
|
||||
Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,7 +8,9 @@ module TAC
|
||||
attr_reader :uuid, :read_queue, :write_queue, :socket,
|
||||
:packets_sent, :packets_received,
|
||||
:data_sent, :data_received
|
||||
|
||||
attr_accessor :sync_interval, :last_socket_error, :socket_error
|
||||
|
||||
def initialize
|
||||
@uuid = SecureRandom.uuid
|
||||
@read_queue = []
|
||||
@@ -20,8 +22,11 @@ module TAC
|
||||
@socket_error = false
|
||||
@bound = false
|
||||
|
||||
@packets_sent, @packets_received = 0, 0
|
||||
@data_sent, @data_received = 0, 0
|
||||
@packets_sent = 0
|
||||
@packets_received = 0
|
||||
|
||||
@data_sent = 0
|
||||
@data_received = 0
|
||||
end
|
||||
|
||||
def uuid=(id)
|
||||
@@ -39,17 +44,15 @@ module TAC
|
||||
Thread.new do
|
||||
while connected?
|
||||
# Read from socket
|
||||
while message_in = read
|
||||
if message_in.empty?
|
||||
break
|
||||
else
|
||||
log.i(TAG, "Read: " + message_in)
|
||||
while (message_in = read)
|
||||
break if message_in.empty?
|
||||
|
||||
@read_queue << message_in
|
||||
log.i(TAG, "Read: #{message_in}")
|
||||
|
||||
@packets_received += 1
|
||||
@data_received += message_in.length
|
||||
end
|
||||
@read_queue << message_in
|
||||
|
||||
@packets_received += 1
|
||||
@data_received += message_in.length
|
||||
end
|
||||
|
||||
sleep @sync_interval / 1000.0
|
||||
@@ -59,12 +62,12 @@ module TAC
|
||||
Thread.new do
|
||||
while connected?
|
||||
# Write to socket
|
||||
while message_out = @write_queue.shift
|
||||
while (message_out = @write_queue.shift)
|
||||
write(message_out)
|
||||
|
||||
@packets_sent += 1
|
||||
@data_sent += message_out.to_s.length
|
||||
log.i(TAG, "Write: " + message_out.to_s)
|
||||
log.i(TAG, "Write: #{message_out}")
|
||||
end
|
||||
|
||||
sleep @sync_interval / 1000.0
|
||||
@@ -82,7 +85,7 @@ module TAC
|
||||
while message
|
||||
puts(message)
|
||||
|
||||
log.i(TAG, "Writing to Queue: " + message)
|
||||
log.i(TAG, "Writing to Queue: #{message}")
|
||||
|
||||
message = gets
|
||||
end
|
||||
@@ -101,32 +104,29 @@ module TAC
|
||||
end
|
||||
|
||||
def closed?
|
||||
@socket.closed? if @socket
|
||||
@socket&.closed?
|
||||
end
|
||||
|
||||
def write(message)
|
||||
begin
|
||||
@socket.puts("#{message}#{PACKET_TAIL}")
|
||||
rescue => error
|
||||
@last_socket_error = error
|
||||
@socket_error = true
|
||||
log.e(TAG, error.message)
|
||||
close
|
||||
end
|
||||
@socket.puts("#{message}#{PACKET_TAIL}") if connected?
|
||||
rescue => error
|
||||
@last_socket_error = error
|
||||
@socket_error = true
|
||||
|
||||
log.e(TAG, error.message)
|
||||
|
||||
close
|
||||
end
|
||||
|
||||
def read
|
||||
begin
|
||||
message = @socket.gets
|
||||
rescue => error
|
||||
@last_socket_error = error
|
||||
@socket_error = true
|
||||
@socket&.gets&.strip if connected?
|
||||
rescue => error
|
||||
@last_socket_error = error
|
||||
@socket_error = true
|
||||
|
||||
message = ""
|
||||
end
|
||||
log.e(TAG, error.message)
|
||||
|
||||
|
||||
return message.strip
|
||||
close
|
||||
end
|
||||
|
||||
def puts(message)
|
||||
@@ -138,11 +138,11 @@ module TAC
|
||||
end
|
||||
|
||||
def encode(message)
|
||||
return message
|
||||
message
|
||||
end
|
||||
|
||||
def decode(blob)
|
||||
return blob
|
||||
blob
|
||||
end
|
||||
|
||||
def flush
|
||||
@@ -151,8 +151,9 @@ module TAC
|
||||
|
||||
def close(reason = nil)
|
||||
write(reason) if reason
|
||||
@socket.close if @socket
|
||||
|
||||
@socket&.close
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -47,7 +47,7 @@ module TAC
|
||||
|
||||
def handle_handshake(packet)
|
||||
if @host_is_a_connection
|
||||
$window.backend.tacnet.client.uuid = packet.body
|
||||
CyberarmEngine::Window.instance.backend.tacnet.client.uuid = packet.body
|
||||
end
|
||||
end
|
||||
|
||||
@@ -59,7 +59,7 @@ module TAC
|
||||
def handle_error(packet)
|
||||
if @host_is_a_connection
|
||||
title, message = packet.body.split(Packet::PROTOCOL_SEPERATOR, 2)
|
||||
$window.push_state(TAC::Dialog::TACNETDialog, title: title, message: message)
|
||||
CyberarmEngine::Window.instance.push_state(TAC::Dialog::TACNETDialog, title: title, message: message)
|
||||
else
|
||||
log.e(TAG, "Remote error: #{title}: #{message}")
|
||||
end
|
||||
@@ -73,11 +73,11 @@ module TAC
|
||||
if data.is_a?(Hash) && data.dig(:config, :spec_version) == TAC::CONFIG_SPEC_VERSION
|
||||
File.open("#{TAC::CONFIGS_PATH}/#{config_name}.json", "w") { |f| f.write json }
|
||||
|
||||
if $window.backend.config&.name == config_name
|
||||
$window.backend.load_config(config_name)
|
||||
if CyberarmEngine::Window.instance.backend.config&.name == config_name
|
||||
CyberarmEngine::Window.instance.backend.load_config(config_name)
|
||||
end
|
||||
else
|
||||
$window.push_state(TAC::Dialog::AlertDialog, title: "Invalid Config", message: "Supported config spec: v#{TAC::CONFIG_SPEC_VERSION} got v#{data.dig(:config, :spec_version)}")
|
||||
CyberarmEngine::Window.instance.push_state(TAC::Dialog::AlertDialog, title: "Invalid Config", message: "Supported config spec: v#{TAC::CONFIG_SPEC_VERSION} got v#{data.dig(:config, :spec_version)}")
|
||||
end
|
||||
|
||||
rescue JSON::ParserError => e
|
||||
@@ -97,7 +97,7 @@ module TAC
|
||||
end
|
||||
|
||||
if @host_is_a_connection
|
||||
$window.backend.tacnet.puts(pkt)
|
||||
CyberarmEngine::Window.instance.backend.tacnet.puts(pkt)
|
||||
else
|
||||
$server.active_client.puts(pkt)
|
||||
end
|
||||
@@ -119,20 +119,20 @@ module TAC
|
||||
config = Config.new(name)
|
||||
|
||||
if config.configuration.revision < revision
|
||||
$window.backend.tacnet.puts( PacketHandler.packet_download_config(name) )
|
||||
CyberarmEngine::Window.instance.backend.tacnet.puts( PacketHandler.packet_download_config(name) )
|
||||
elsif config.configuration.revision > revision
|
||||
$window.backend.tacnet.puts( PacketHandler.packet_upload_config(name, JSON.dump( config )) )
|
||||
CyberarmEngine::Window.instance.backend.tacnet.puts( PacketHandler.packet_upload_config(name, JSON.dump( config )) )
|
||||
end
|
||||
|
||||
else
|
||||
$window.backend.tacnet.puts( PacketHandler.packet_download_config(name) )
|
||||
CyberarmEngine::Window.instance.backend.tacnet.puts( PacketHandler.packet_download_config(name) )
|
||||
end
|
||||
end
|
||||
|
||||
_diff.each do |name|
|
||||
config = Config.new(name)
|
||||
|
||||
$window.backend.tacnet.puts( PacketHandler.packet_upload_config(name, JSON.dump( config )) )
|
||||
CyberarmEngine::Window.instance.backend.tacnet.puts( PacketHandler.packet_upload_config(name, JSON.dump( config )) )
|
||||
end
|
||||
else
|
||||
if $server.active_client && $server.active_client.connected?
|
||||
@@ -144,43 +144,43 @@ module TAC
|
||||
def handle_select_config(packet)
|
||||
config_name = packet.body
|
||||
|
||||
$window.backend.settings.config = config_name
|
||||
$window.backend.save_settings
|
||||
$window.backend.load_config(config_name)
|
||||
CyberarmEngine::Window.instance.backend.settings.config = config_name
|
||||
CyberarmEngine::Window.instance.backend.save_settings
|
||||
CyberarmEngine::Window.instance.backend.load_config(config_name)
|
||||
end
|
||||
|
||||
def handle_add_config(packet)
|
||||
config_name = packet.body
|
||||
|
||||
if $window.backend.configs_list.include?(config_name)
|
||||
if CyberarmEngine::Window.instance.backend.configs_list.include?(config_name)
|
||||
unless @host_is_a_connection
|
||||
if $server.active_client&.connected?
|
||||
$server.active_client.puts(PacketHandler.packet_error("Config already exists!", "A config with the name #{config_name} already exists over here."))
|
||||
end
|
||||
end
|
||||
else
|
||||
$window.backend.write_new_config(config_name)
|
||||
CyberarmEngine::Window.instance.backend.write_new_config(config_name)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_update_config(packet)
|
||||
old_config_name, new_config_name = packet.body.split(PROTOCOL_SEPERATOR, 2)
|
||||
|
||||
if $window.backend.configs_list.include?(config_name)
|
||||
if CyberarmEngine::Window.instance.backend.configs_list.include?(config_name)
|
||||
unless @host_is_a_connection
|
||||
if $server.active_client&.connected?
|
||||
$server.active_client.puts(PacketHandler.packet_error("Config already exists!", "A config with the name #{config_name} already exists over here."))
|
||||
end
|
||||
end
|
||||
else
|
||||
$window.backend.move_config(old_config_name, new_config_name)
|
||||
CyberarmEngine::Window.instance.backend.move_config(old_config_name, new_config_name)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_delete_config(packet)
|
||||
config_name = packet.body
|
||||
|
||||
$window.backend.delete_config(config_name)
|
||||
CyberarmEngine::Window.instance.backend.delete_config(config_name)
|
||||
end
|
||||
|
||||
def self.packet_handshake(client_uuid)
|
||||
|
||||
17
lib/theme.rb
17
lib/theme.rb
@@ -1,12 +1,15 @@
|
||||
module TAC
|
||||
THEME_FONT = "#{TAC::ROOT_PATH}/media/fonts/DejaVuSansCondensed.ttf"
|
||||
THEME_FONT = "#{TAC::MEDIA_PATH}/fonts/NotoSans-Bold.ttf"
|
||||
THEME_BOLD_FONT = "#{TAC::MEDIA_PATH}/fonts/NotoSans-Black.ttf"
|
||||
THEME = {
|
||||
Label: {
|
||||
TextBlock: {
|
||||
text_static: true,
|
||||
font: THEME_FONT,
|
||||
text_size: 22,
|
||||
color: Gosu::Color.new(0xee_ffffff),
|
||||
},
|
||||
Button: {
|
||||
font: THEME_BOLD_FONT,
|
||||
text_size: 22,
|
||||
background: TAC::Palette::TIMECRAFTERS_PRIMARY,
|
||||
border_thickness: 1,
|
||||
@@ -20,10 +23,11 @@ module TAC
|
||||
},
|
||||
EditLine: {
|
||||
caret_color: Gosu::Color.new(0xff_88ef90),
|
||||
font: THEME_FONT
|
||||
},
|
||||
ToggleButton: {
|
||||
width: 18,
|
||||
checkmark_image: "#{TAC::ROOT_PATH}/media/icons/checkmark.png",
|
||||
checkmark_image: "#{TAC::MEDIA_PATH}/icons/checkmark.png",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -31,10 +35,10 @@ module TAC
|
||||
color: Gosu::Color.new(0xff_ffffff),
|
||||
background: Gosu::Color.new(0xff_800000),
|
||||
hover: {
|
||||
background: Gosu::Color.new(0xff_600000),
|
||||
background: Gosu::Color.new(0xff_c00000),
|
||||
},
|
||||
active: {
|
||||
background: Gosu::Color.new(0xff_c00000),
|
||||
background: Gosu::Color.new(0xff_600000),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +53,7 @@ module TAC
|
||||
padding_top: THEME_ITEM_PADDING,
|
||||
padding_bottom: THEME_ITEM_PADDING
|
||||
}
|
||||
THEME_HIGHLIGHTED_COLOR = Gosu::Color.rgb(255, 175, 0) # Gosu::Color.new(0xff_f080f0)
|
||||
THEME_EVEN_COLOR = Gosu::Color.new(0xff_202020)
|
||||
THEME_ODD_COLOR = Gosu::Color.new(0xff_606060)
|
||||
THEME_CONTENT_BACKGROUND = Gosu::Color.new(0x88_007f3f)
|
||||
@@ -60,4 +65,4 @@ module TAC
|
||||
THEME_NOTIFICATION_BACKGROUND = Gosu::Color.new(0xff_102010)
|
||||
THEME_NOTIFICATION_TITLE_COLOR = Gosu::Color::WHITE
|
||||
THEME_NOTIFICATION_TAGLINE_COLOR = Gosu::Color::WHITE
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
module TAC
|
||||
NAME = "TimeCrafters Configuration Tool"
|
||||
VERSION = "0.4.1"
|
||||
RELEASE_NAME = "Beta"
|
||||
end
|
||||
RELEASE_DATE = "2024-01-19" # ISO 8601 Date of version release
|
||||
VERSION = "0.8.0"
|
||||
RELEASE_NAME = "BETA"
|
||||
end
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
module TAC
|
||||
class Window < CyberarmEngine::Window
|
||||
attr_reader :backend, :notification_manager
|
||||
|
||||
def initialize(**args)
|
||||
super(**args)
|
||||
|
||||
self.caption = "#{TAC::NAME} v#{TAC::VERSION} (#{TAC::RELEASE_NAME})"
|
||||
self.caption = "#{TAC::NAME} v#{TAC::VERSION} (#{TAC::RELEASE_NAME}) [#{TAC::RELEASE_DATE}]"
|
||||
@backend = Backend.new
|
||||
@notification_manager = GosuNotifications::NotificationManager.new(window: self, edge: :bottom)
|
||||
@notification_manager = CyberarmEngine::NotificationManager.new(window: self, edge: :bottom)
|
||||
|
||||
push_state(TAC::States::Boot)
|
||||
if ARGV.join.include?("--game-clock-remote-display")
|
||||
push_state(PracticeGameClock::View, remote_control_mode: true)
|
||||
elsif ARGV.join.include?("--intro")
|
||||
push_state(CyberarmEngine::IntroState, forward: TAC::States::Boot)
|
||||
else
|
||||
push_state(TAC::States::Boot)
|
||||
end
|
||||
end
|
||||
|
||||
def draw
|
||||
@@ -19,18 +26,18 @@ module TAC
|
||||
end
|
||||
|
||||
def update
|
||||
super
|
||||
|
||||
@notification_manager.update
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def needs_cursor?
|
||||
true
|
||||
def needs_redraw?
|
||||
states.any?(&:needs_repaint?) || @notification_manager.instance_variable_get(:@drivers).size.positive?
|
||||
end
|
||||
|
||||
def toast(title, message = nil)
|
||||
@notification_manager.create_notification(
|
||||
priority: GosuNotifications::Notification::PRIORITY_HIGH,
|
||||
priority: CyberarmEngine::Notification::PRIORITY_HIGH,
|
||||
title: title,
|
||||
|
||||
tagline: message ? message : "",
|
||||
|
||||
BIN
media/background.png
Normal file
BIN
media/background.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 134 KiB |
6244
media/background.svg
Normal file
6244
media/background.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 231 KiB |
@@ -1,187 +0,0 @@
|
||||
Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
|
||||
Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
|
||||
|
||||
|
||||
Bitstream Vera Fonts Copyright
|
||||
------------------------------
|
||||
|
||||
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
|
||||
a trademark of Bitstream, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of the fonts accompanying this license ("Fonts") and associated
|
||||
documentation files (the "Font Software"), to reproduce and distribute the
|
||||
Font Software, including without limitation the rights to use, copy, merge,
|
||||
publish, distribute, and/or sell copies of the Font Software, and to permit
|
||||
persons to whom the Font Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright and trademark notices and this permission notice shall
|
||||
be included in all copies of one or more of the Font Software typefaces.
|
||||
|
||||
The Font Software may be modified, altered, or added to, and in particular
|
||||
the designs of glyphs or characters in the Fonts may be modified and
|
||||
additional glyphs or characters may be added to the Fonts, only if the fonts
|
||||
are renamed to names not containing either the words "Bitstream" or the word
|
||||
"Vera".
|
||||
|
||||
This License becomes null and void to the extent applicable to Fonts or Font
|
||||
Software that has been modified and is distributed under the "Bitstream
|
||||
Vera" names.
|
||||
|
||||
The Font Software may be sold as part of a larger software package but no
|
||||
copy of one or more of the Font Software typefaces may be sold by itself.
|
||||
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
|
||||
TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
|
||||
FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
|
||||
ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
||||
THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
|
||||
FONT SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the names of Gnome, the Gnome
|
||||
Foundation, and Bitstream Inc., shall not be used in advertising or
|
||||
otherwise to promote the sale, use or other dealings in this Font Software
|
||||
without prior written authorization from the Gnome Foundation or Bitstream
|
||||
Inc., respectively. For further information, contact: fonts at gnome dot
|
||||
org.
|
||||
|
||||
Arev Fonts Copyright
|
||||
------------------------------
|
||||
|
||||
Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the fonts accompanying this license ("Fonts") and
|
||||
associated documentation files (the "Font Software"), to reproduce
|
||||
and distribute the modifications to the Bitstream Vera Font Software,
|
||||
including without limitation the rights to use, copy, merge, publish,
|
||||
distribute, and/or sell copies of the Font Software, and to permit
|
||||
persons to whom the Font Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright and trademark notices and this permission notice
|
||||
shall be included in all copies of one or more of the Font Software
|
||||
typefaces.
|
||||
|
||||
The Font Software may be modified, altered, or added to, and in
|
||||
particular the designs of glyphs or characters in the Fonts may be
|
||||
modified and additional glyphs or characters may be added to the
|
||||
Fonts, only if the fonts are renamed to names not containing either
|
||||
the words "Tavmjong Bah" or the word "Arev".
|
||||
|
||||
This License becomes null and void to the extent applicable to Fonts
|
||||
or Font Software that has been modified and is distributed under the
|
||||
"Tavmjong Bah Arev" names.
|
||||
|
||||
The Font Software may be sold as part of a larger software package but
|
||||
no copy of one or more of the Font Software typefaces may be sold by
|
||||
itself.
|
||||
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
|
||||
TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the name of Tavmjong Bah shall not
|
||||
be used in advertising or otherwise to promote the sale, use or other
|
||||
dealings in this Font Software without prior written authorization
|
||||
from Tavmjong Bah. For further information, contact: tavmjong @ free
|
||||
. fr.
|
||||
|
||||
TeX Gyre DJV Math
|
||||
-----------------
|
||||
Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
|
||||
|
||||
Math extensions done by B. Jackowski, P. Strzelczyk and P. Pianowski
|
||||
(on behalf of TeX users groups) are in public domain.
|
||||
|
||||
Letters imported from Euler Fraktur from AMSfonts are (c) American
|
||||
Mathematical Society (see below).
|
||||
Bitstream Vera Fonts Copyright
|
||||
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera
|
||||
is a trademark of Bitstream, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of the fonts accompanying this license (“Fonts”) and associated
|
||||
documentation
|
||||
files (the “Font Software”), to reproduce and distribute the Font Software,
|
||||
including without limitation the rights to use, copy, merge, publish,
|
||||
distribute,
|
||||
and/or sell copies of the Font Software, and to permit persons to whom
|
||||
the Font Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright and trademark notices and this permission notice
|
||||
shall be
|
||||
included in all copies of one or more of the Font Software typefaces.
|
||||
|
||||
The Font Software may be modified, altered, or added to, and in particular
|
||||
the designs of glyphs or characters in the Fonts may be modified and
|
||||
additional
|
||||
glyphs or characters may be added to the Fonts, only if the fonts are
|
||||
renamed
|
||||
to names not containing either the words “Bitstream” or the word “Vera”.
|
||||
|
||||
This License becomes null and void to the extent applicable to Fonts or
|
||||
Font Software
|
||||
that has been modified and is distributed under the “Bitstream Vera”
|
||||
names.
|
||||
|
||||
The Font Software may be sold as part of a larger software package but
|
||||
no copy
|
||||
of one or more of the Font Software typefaces may be sold by itself.
|
||||
|
||||
THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
|
||||
TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
|
||||
FOUNDATION
|
||||
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL,
|
||||
SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN
|
||||
ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR
|
||||
INABILITY TO USE
|
||||
THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Except as contained in this notice, the names of GNOME, the GNOME
|
||||
Foundation,
|
||||
and Bitstream Inc., shall not be used in advertising or otherwise to promote
|
||||
the sale, use or other dealings in this Font Software without prior written
|
||||
authorization from the GNOME Foundation or Bitstream Inc., respectively.
|
||||
For further information, contact: fonts at gnome dot org.
|
||||
|
||||
AMSFonts (v. 2.2) copyright
|
||||
|
||||
The PostScript Type 1 implementation of the AMSFonts produced by and
|
||||
previously distributed by Blue Sky Research and Y&Y, Inc. are now freely
|
||||
available for general use. This has been accomplished through the
|
||||
cooperation
|
||||
of a consortium of scientific publishers with Blue Sky Research and Y&Y.
|
||||
Members of this consortium include:
|
||||
|
||||
Elsevier Science IBM Corporation Society for Industrial and Applied
|
||||
Mathematics (SIAM) Springer-Verlag American Mathematical Society (AMS)
|
||||
|
||||
In order to assure the authenticity of these fonts, copyright will be
|
||||
held by
|
||||
the American Mathematical Society. This is not meant to restrict in any way
|
||||
the legitimate use of the fonts, such as (but not limited to) electronic
|
||||
distribution of documents containing these fonts, inclusion of these fonts
|
||||
into other public domain or commercial font collections or computer
|
||||
applications, use of the outline data to create derivative fonts and/or
|
||||
faces, etc. However, the AMS does require that the AMS copyright notice be
|
||||
removed from any derivative versions of the fonts which have been altered in
|
||||
any way. In addition, to ensure the fidelity of TeX documents using Computer
|
||||
Modern fonts, Professor Donald Knuth, creator of the Computer Modern faces,
|
||||
has requested that any alterations which yield different font metrics be
|
||||
given a different name.
|
||||
|
||||
$Id$
|
||||
Binary file not shown.
BIN
media/fonts/NotoSans-Black.ttf
Normal file
BIN
media/fonts/NotoSans-Black.ttf
Normal file
Binary file not shown.
BIN
media/fonts/NotoSans-Bold.ttf
Normal file
BIN
media/fonts/NotoSans-Bold.ttf
Normal file
Binary file not shown.
BIN
media/fonts/NotoSans-Medium.ttf
Normal file
BIN
media/fonts/NotoSans-Medium.ttf
Normal file
Binary file not shown.
BIN
media/fonts/NotoSans-Regular.ttf
Normal file
BIN
media/fonts/NotoSans-Regular.ttf
Normal file
Binary file not shown.
93
media/fonts/OFL.txt
Normal file
93
media/fonts/OFL.txt
Normal file
@@ -0,0 +1,93 @@
|
||||
Copyright 2015-2021 Google LLC. All Rights Reserved.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
BIN
media/icons/custom_stopWatch.png
Normal file
BIN
media/icons/custom_stopWatch.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
117
media/icons/custom_stopWatch.svg
Normal file
117
media/icons/custom_stopWatch.svg
Normal file
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="100"
|
||||
height="100"
|
||||
viewBox="0 0 100 100"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:export-filename="/home/cyberarm/Code/timecrafters_configuration_tool_desktop/media/icons/custom_stopWatch.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||
sodipodi:docname="custom_stopWatch.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#dcdcdc"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="px"
|
||||
showgrid="true"
|
||||
inkscape:showpageshadow="false"
|
||||
borderlayer="true"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:snap-bbox-midpoints="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:zoom="4"
|
||||
inkscape:cx="-12.625"
|
||||
inkscape:cy="52.625"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1011"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid824" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:0.98396;stroke:none;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="rect3345"
|
||||
width="20"
|
||||
height="6"
|
||||
x="40"
|
||||
y="7"
|
||||
ry="1" />
|
||||
<path
|
||||
id="path848"
|
||||
style="fill:#ffffff;fill-opacity:0.98396;stroke-width:2"
|
||||
d="M 50 15 A 35 35 0 0 0 15 50 A 35 35 0 0 0 50 85 A 35 35 0 0 0 85 50 A 35 35 0 0 0 50 15 z M 50 25 A 25.000003 25.000003 0 0 1 75 50 A 25.000003 25.000003 0 0 1 50 75 A 25.000003 25.000003 0 0 1 25 50 A 25.000003 25.000003 0 0 1 50 25 z " />
|
||||
<circle
|
||||
style="fill:#ffffff;fill-opacity:0.98396;stroke-width:2"
|
||||
id="path1762"
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="2" />
|
||||
<path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
d="M 50,50 60,30"
|
||||
id="path2248"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 50,26 v 9"
|
||||
id="path2778"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 50,65 v 9"
|
||||
id="path2778-0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 74,50 H 65"
|
||||
id="path2778-9"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 35,50 H 26"
|
||||
id="path2778-0-3"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 66.970563,33.029437 -6.363961,6.363961"
|
||||
id="path2778-6"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 39.393398,60.606602 -6.363961,6.363961"
|
||||
id="path2778-0-0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 66.970563,66.970563 60.606602,60.606602"
|
||||
id="path2778-9-6"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 39.393398,39.393398 33.029437,33.029437"
|
||||
id="path2778-0-3-2"
|
||||
sodipodi:nodetypes="cc" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
0
media/music/.gitkeep
Normal file
0
media/music/.gitkeep
Normal file
BIN
media/openclipart_ducky.png
Normal file
BIN
media/openclipart_ducky.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
0
media/particles/.gitkeep
Normal file
0
media/particles/.gitkeep
Normal file
BIN
media/screenshots/screenshot_editor.png
Normal file
BIN
media/screenshots/screenshot_editor.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 280 KiB |
0
media/sounds/.gitkeep
Normal file
0
media/sounds/.gitkeep
Normal file
@@ -1,10 +1,9 @@
|
||||
begin
|
||||
raise LoadError if defined?(Ocra)
|
||||
raise LoadError if defined?(Ocra) || defined?(Ocran)
|
||||
require_relative "../cyberarm_engine/lib/cyberarm_engine"
|
||||
rescue LoadError
|
||||
require "cyberarm_engine"
|
||||
end
|
||||
require "gosu_notifications"
|
||||
require "socket"
|
||||
require "securerandom"
|
||||
require "json"
|
||||
@@ -29,6 +28,7 @@ require_relative "lib/pages/presets"
|
||||
require_relative "lib/pages/search"
|
||||
require_relative "lib/pages/field_planner"
|
||||
require_relative "lib/pages/drive_team_rotation_generator"
|
||||
require_relative "lib/pages/game_clock"
|
||||
require_relative "lib/simulator/robot"
|
||||
require_relative "lib/simulator/field"
|
||||
require_relative "lib/simulator/simulation"
|
||||
@@ -50,11 +50,31 @@ require_relative "lib/tacnet/client"
|
||||
require_relative "lib/tacnet/connection"
|
||||
require_relative "lib/tacnet/server"
|
||||
|
||||
require_relative "lib/game_clock/view"
|
||||
require_relative "lib/game_clock/clock"
|
||||
require_relative "lib/game_clock/event_handlers"
|
||||
require_relative "lib/game_clock/clock_controller"
|
||||
require_relative "lib/game_clock/jukebox"
|
||||
require_relative "lib/game_clock/theme"
|
||||
require_relative "lib/game_clock/clock_proxy"
|
||||
require_relative "lib/game_clock/logger"
|
||||
require_relative "lib/game_clock/particle_emitter"
|
||||
require_relative "lib/game_clock/randomizer"
|
||||
require_relative "lib/game_clock/remote_control"
|
||||
require_relative "lib/game_clock/remote_proxy"
|
||||
require_relative "lib/game_clock/net/client"
|
||||
require_relative "lib/game_clock/net/server"
|
||||
require_relative "lib/game_clock/net/connection"
|
||||
require_relative "lib/game_clock/net/packet_handler"
|
||||
require_relative "lib/game_clock/net/packet"
|
||||
|
||||
# Thread.abort_on_exception = true
|
||||
|
||||
USE_REDESIGN = ARGV.include?("--redesign")
|
||||
BORDERLESS = ARGV.include?("--borderless")
|
||||
|
||||
if not defined?(Ocra)
|
||||
unless defined?(Ocra) || defined?(Ocran)
|
||||
TAC::Window.new(width: (Gosu.screen_width * 0.8).round, height: (Gosu.screen_height * 0.8).round, resizable: true, borderless: BORDERLESS).show
|
||||
end
|
||||
|
||||
Process.wait($clock_pid) if $clock_pid
|
||||
|
||||
Reference in New Issue
Block a user