33 Commits

Author SHA1 Message Date
27d0b7314f WIP: Refactoring Editor to no longer rebuild lists from scratch when changing most anything 2023-01-31 10:14:24 -06:00
308575dc63 Refactor TACNET Client a bit 2023-01-30 16:16:02 -06:00
94cd822b0c Fix up game clock a bit more so that it correctly requests repaints when remotely controlled, fixed layout issues with Drive Team Rotation Generator, update README 2023-01-30 15:52:21 -06:00
c312f4839d Fixed indentation, fixed calling .strip on nil in TACNET Client#read 2023-01-30 08:42:46 -06:00
3a8f4e5860 Added used software section to home page 2023-01-30 08:38:26 -06:00
7e09031257 Fixed Boot animation not requesting repaint every frame 2023-01-30 08:37:56 -06:00
905ced174b Fixed when adding action from preset: editing comment before adding was not preserved, and not defaulting to enabled 2023-01-30 08:37:30 -06:00
510a24644b Make titlebar TACNET_PRIMARY (blue) when connected, log tacnet read error. 2023-01-26 10:25:42 -06:00
570d965669 VariableDialog now reports an error if attempting to name a variable to one that already exists 2023-01-22 09:53:03 -06:00
60356fc7fa Added 'Open Folder' button on configurations page, replaced target icon with 'Generate' text for generate roster button on team rotation generator page, make group/action get highlighted correctly when selected from search. 2023-01-22 09:39:27 -06:00
41f5710b4a FieldPlanner: Fixed wrong inch to metric maths, fixed node counter and total distance labels overlapping, added toolip when hovering over the field showing the X/Y coordinate in 'unit' with the origin at field center. 2023-01-21 22:37:19 -06:00
1e21c64a18 Fixed using File.exists? instead of File.exist?, updated Randomizer to PowerPlay (with ducks), misc. tweaking. 2023-01-19 12:33:51 -06:00
451568003a Remove no longer needed clipboard and ffi gems, use new CyberarmEngine::GuiState#needs_repaint? method to only paint when needed- should avoid wasting CPU/GPU time (gain a bit more battery life?), make TACNET connection error visually change UI and also trigger a TACNET dialog when not on the TACNET page 2023-01-05 08:42:29 -06:00
cce1c2c341 Bump version 2022-10-23 18:42:46 -05:00
d5cf1cb6a2 Slight formatting change 2022-10-23 18:42:25 -05:00
a96cc7c604 Fixed actions not getting sorted when an action is updated 2022-10-23 17:44:28 -05:00
c2e527653d Added Alt+G/A/V shortcuts to add groups, actions, and variables
respectively.
2022-10-23 17:36:32 -05:00
66b0eb3d1e Updated Field/Robot/Simulator to use parent container's z index to remove need for Gosu.flush and causing Field to be drawn on top of tooltip's and menus, Added Power Play field, replaced font, enabled static option for text to improve rendering appearance, misc. bug fixes and tweaks. 2022-10-23 17:30:51 -05:00
655b418d70 Updates to support latest version of CyberarmEngine 2022-09-20 17:17:57 -05:00
a8fc2dccde A few tweaks 2022-04-27 14:31:01 -05:00
0d6fb8a657 Added comment 'state' for show a comment above the robot (TODO: make it non-scaled and actually hover above robot) 2021-10-23 12:40:13 -05:00
15d8e2ff62 Added delay state to simulator 2021-10-23 12:13:23 -05:00
9b505b8201 Bump version 2021-09-30 11:07:01 -05:00
c609734357 Fixed confirm and tacnet dialogs not using correct titlebar background and border colors 2021-09-30 11:06:28 -05:00
fd6eb64232 Fixed some issues related to threading in game clock by using a queue 2021-09-30 10:51:49 -05:00
7bfc404413 Fixed mouse cursor always visible, even on game clock where it is preferred to be invisible while idle 2021-09-30 08:46:44 -05:00
870a3e4e8b Bump version 2021-09-29 16:26:43 -05:00
aa6d53dd5e Added rubber ducky, updated game clock randomizer 2021-09-29 16:26:24 -05:00
0f0009bcf0 Fixed crash due to renaming variable 2021-09-29 15:04:10 -05:00
7b7efabaf6 Added icon for game clock, updated game clock background image, made game clock background image auto-scale to fit window 2021-09-29 14:21:51 -05:00
2948b02f12 Dual screen game clock now working 2021-09-29 13:41:44 -05:00
44523b0bf2 Imported FTC Clock 2021-09-29 12:41:56 -05:00
7d2d44c52f Stubbed game clock page 2021-09-29 09:08:15 -05:00
67 changed files with 9852 additions and 605 deletions

6
.gitignore vendored
View File

@@ -3,3 +3,9 @@ data/**/*.json
data/settings.json data/settings.json
data/simulator.rb data/simulator.rb
data/*.csv data/*.csv
media/sounds/*
!media/sounds/.gitkeep
media/particles/*
!media/particles/.gitkeep
media/music/*
!media/music/.gitkeep

View File

@@ -2,9 +2,8 @@ source "https://rubygems.org"
gem "cyberarm_engine" gem "cyberarm_engine"
gem "gosu_notifications" gem "gosu_notifications"
gem "ffi"
gem "clipboard"
group :packaging do group :packaging do
gem "ocra" gem "ocra"
gem "releasy"
end end

37
Gemfile.lock Normal file
View File

@@ -0,0 +1,37 @@
GEM
remote: https://rubygems.org/
specs:
clipboard (1.3.6)
cri (2.1.0)
cyberarm_engine (0.19.1)
clipboard (~> 1.3.5)
excon (~> 0.78.0)
gosu (~> 1.1)
gosu_more_drawables (~> 0.3)
excon (0.78.1)
ffi (1.15.4-x64-mingw32)
gosu (1.2.0)
gosu_more_drawables (0.3.1)
gosu_notifications (0.1.0)
ocra (1.3.11)
rake (13.0.6)
releasy (0.2.3)
bundler (>= 1.2.1)
cri (~> 2.1.0)
ocra (~> 1.3.0)
rake (>= 0.9.2.2)
PLATFORMS
x64-mingw32
x86_64-linux
DEPENDENCIES
clipboard
cyberarm_engine
ffi
gosu_notifications
ocra
releasy
BUNDLED WITH
2.2.28

View File

@@ -1 +1,42 @@
# TimeCrafters Configuration Tool for Desktop # TimeCrafters Configuration Tool for Desktop
A desktop app for editing, either locally or remotely, JSON configuration files on the Robot Controller/Rev Control Hub.
![Screenshot of Editor](https://raw.githubusercontent.com/TimeCrafters/timecrafters_configuration_tool_desktop/master/media/screenshots/screenshot_editor.png)
## 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

View File

@@ -7,7 +7,17 @@ Releasy::Project.new do
version TAC::VERSION version TAC::VERSION
executable "timecrafters_configuration_tool.rb" executable "timecrafters_configuration_tool.rb"
files ["lib/**/*.*", "media/**/*.*", "data"] 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. exclude_encoding # Applications that don't use advanced encoding (e.g. Japanese characters) can save build size with this.
verbose verbose

View File

@@ -42,13 +42,13 @@ module TAC
end end
def move_config(old_name, new_name) 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") File.directory?("#{TAC::CONFIGS_PATH}/#{old_name}.json")
# move_config: Can not move config file "#{old_name}" does not exist! # move_config: Can not move config file "#{old_name}" does not exist!
return false return false
end 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") !File.directory?("#{TAC::CONFIGS_PATH}/#{old_name}.json")
# move_config: Config file "#{new_name}" already exist! # move_config: Config file "#{new_name}" already exist!
return false return false
@@ -61,7 +61,7 @@ module TAC
end end
def delete_config(config_name) 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 end
@@ -101,7 +101,7 @@ module TAC
end end
def refresh_tacnet_status def refresh_tacnet_status
$window.current_state.refresh_tacnet_status CyberarmEngine::Window.instance.current_state.refresh_tacnet_status
end end

View File

@@ -5,51 +5,32 @@ module TAC
background Gosu::Color.new(0xaa_000000) background Gosu::Color.new(0xaa_000000)
@title = @options[:title] ? @options[:title] : "#{self.class}" @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 # 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] background [TAC::Palette::TIMECRAFTERS_PRIMARY, TAC::Palette::TIMECRAFTERS_SECONDARY]
# title label @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
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
# Buttons button get_image("#{TAC::ROOT_PATH}/media/icons/cross.png"), image_height: 1.0, **THEME_DANGER_BUTTON do
flow width: 0.0999 do
button get_image("#{TAC::ROOT_PATH}/media/icons/cross.png"), image_width: 1.0, **THEME_DANGER_BUTTON do
close close
end end
end end
end
# Dialog body # Dialog body
@dialog_content = stack width: 1.0 do @dialog_content = stack(width: 1.0, scroll: true) do
end end
end end
@dialog_content.clear do @dialog_content.clear do
build build
end end
@root_container.recalculate
@root_container.recalculate
@root_container.recalculate
center_dialog
end end
def build def build
end 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) def name_filter(text)
text.match(/[A-Za-z0-9._\- ]/) ? text : "" text.match(/[A-Za-z0-9._\- ]/) ? text : ""
end end
@@ -76,7 +57,8 @@ module TAC
def _deep_dive_interactive_elements(element, list) def _deep_dive_interactive_elements(element, list)
element.children.each do |child| element.children.each do |child|
if child.visible? && child.is_a?(CyberarmEngine::Element::EditLine) || if child.visible? && child.enabled? &&
child.is_a?(CyberarmEngine::Element::EditLine) ||
child.is_a?(CyberarmEngine::Element::EditBox) || child.is_a?(CyberarmEngine::Element::EditBox) ||
child.is_a?(CyberarmEngine::Element::CheckBox) || child.is_a?(CyberarmEngine::Element::CheckBox) ||
child.is_a?(CyberarmEngine::Element::ToggleButton) || child.is_a?(CyberarmEngine::Element::ToggleButton) ||
@@ -90,24 +72,12 @@ module TAC
end end
def draw def draw
@previous_state.draw previous_state&.draw
Gosu.flush Gosu.flush
super super
end 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) def button_down(id)
super super
@@ -122,7 +92,7 @@ module TAC
end end
def close def close
$window.pop_state pop_state
end end
end end
end end

View File

@@ -7,7 +7,7 @@ module TAC
label "Name", width: 1.0, text_align: :center label "Name", width: 1.0, text_align: :center
@name_error = label "Error", color: TAC::Palette::TACNET_CONNECTION_ERROR @name_error = label "Error", color: TAC::Palette::TACNET_CONNECTION_ERROR
@name_error.hide @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| @name.subscribe(:changed) do |sender, value|
valid? valid?
end end

View File

@@ -7,9 +7,8 @@ module TAC
color = @dangerous ? Palette::DANGEROUS : Palette::ALERT color = @dangerous ? Palette::DANGEROUS : Palette::ALERT
@dialog_root.style.default[:border_color] = [ color, darken(color, 50) ]
@dialog_root.style.border_color = [ color, darken(color, 50) ] @titlebar.style.default[:background] = [ color, darken(color, 50) ]
@titlebar.style.background = [ color, darken(color, 50) ]
background Gosu::Color::GRAY background Gosu::Color::GRAY
label @options[:message] label @options[:message]
@@ -23,6 +22,7 @@ module TAC
try_commit(true) try_commit(true)
end end
end end
end
def try_commit(force = false) def try_commit(force = false)
if !@dangerous if !@dangerous
@@ -38,4 +38,3 @@ module TAC
end end
end end
end end
end

View File

@@ -8,7 +8,7 @@ module TAC
label "Name", width: 1.0, text_align: :center label "Name", width: 1.0, text_align: :center
@name_error = label "", color: TAC::Palette::TACNET_CONNECTION_ERROR @name_error = label "", color: TAC::Palette::TACNET_CONNECTION_ERROR
@name_error.hide @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| @name.subscribe(:changed) do |sender, value|
valid? valid?

View File

@@ -2,8 +2,8 @@ module TAC
class Dialog class Dialog
class TACNETDialog < Dialog class TACNETDialog < Dialog
def build def build
@dialog_root.style.border_color = [ Palette::TACNET_PRIMARY, Palette::TACNET_SECONDARY ] @dialog_root.style.default[:border_color] = [ Palette::TACNET_PRIMARY, Palette::TACNET_SECONDARY ]
@titlebar.style.background = [ Palette::TACNET_PRIMARY, Palette::TACNET_SECONDARY ] @titlebar.style.default[:background] = [ Palette::TACNET_PRIMARY, Palette::TACNET_SECONDARY ]
background Gosu::Color::GRAY background Gosu::Color::GRAY
label @options[:message], width: 1.0 label @options[:message], width: 1.0

View File

@@ -3,14 +3,14 @@ module TAC
class TACNETStatusDialog < Dialog class TACNETStatusDialog < Dialog
def build def build
background Gosu::Color::GRAY background Gosu::Color::GRAY
@message_label = label $window.backend.tacnet.full_status @message_label = label CyberarmEngine::Window.instance.backend.tacnet.full_status
button "Close", width: 1.0, margin_top: THEME_DIALOG_BUTTON_PADDING do button "Close", width: 1.0, margin_top: THEME_DIALOG_BUTTON_PADDING do
try_commit try_commit
end end
@timer = CyberarmEngine::Timer.new(1000.0) do @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
end end

View File

@@ -9,7 +9,7 @@ module TAC
label "Name", width: 1.0, text_align: :center label "Name", width: 1.0, text_align: :center
@name_error = label "Error", color: TAC::Palette::TACNET_CONNECTION_ERROR @name_error = label "Error", color: TAC::Palette::TACNET_CONNECTION_ERROR
@name_error.hide @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| @name.subscribe(:changed) do |sender, value|
valid? valid?
end end
@@ -79,11 +79,22 @@ module TAC
def valid? def valid?
valid = true 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.value = "Error: Name cannot be blank or only whitespace."
@name_error.show @name_error.show
valid = false valid = false
### 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 end
if not @type if not @type

70
lib/game_clock/clock.rb Normal file
View File

@@ -0,0 +1,70 @@
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 + 0.5) / 60.0).floor
seconds = time_left.round % 60
seconds = "0#{seconds}" if seconds < 10
return "#{minutes}:#{seconds}" if time_left.round.even?
return "#{minutes}<c=999999>:</c>#{seconds}" if time_left.round.odd?
end
end
end
end

View 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, 34.5, :teleop_pickup_controllers),
create_event(:change_color, 37.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),
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 60 * 2 + 30
end
end
end
end
end

View 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

View File

@@ -0,0 +1,89 @@
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
@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 = "media/sounds/3-2-1.wav"
when :autonomous_start
path = "media/sounds/charge.wav"
when :autonomous_ended
path = "media/sounds/endauto.wav"
when :teleop_pickup_controllers
path = "media/sounds/Pick_Up_Controllers.wav"
when :abort_match
path = "media/sounds/fogblast.wav"
when :teleop_countdown
path = "media/sounds/3-2-1.wav"
when :teleop_started
path = "media/sounds/firebell.wav"
when :end_game
path = "media/sounds/factwhistle.wav"
when :end_match
path = "media/sounds/endmatch.wav"
end
path = "#{ROOT_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
View File

@@ -0,0 +1,144 @@
module TAC
class PracticeGameClock
class Jukebox
MUSIC = Dir.glob(ROOT_PATH + "/media/music/*.*").freeze
SAMPLES = {}
if File.exist?(ROOT_PATH + "/media/sounds/skystone")
BEEPS_AND_BOOPS = Dir.glob(ROOT_PATH + "/media/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
View 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

View 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.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

View 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

View 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

View 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

View 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

View 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("#{ROOT_PATH}/media/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

View File

@@ -0,0 +1,187 @@
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: Right
# Red: Left
@ducks << Ducky.new(window: window, alliance: :blue, slot: 3, speed: 512, die_size: @size)
@ducks << Ducky.new(window: window, alliance: :red, slot: 1, speed: 512, die_size: @size)
when 2, 5
#Blue and Red: Center
@ducks << Ducky.new(window: window, alliance: :blue, slot: 1, speed: 512, die_size: @size)
@ducks << Ducky.new(window: window, alliance: :blue, slot: 3, speed: 512, die_size: @size)
@ducks << Ducky.new(window: window, alliance: :red, slot: 3, speed: 512, die_size: @size)
@ducks << Ducky.new(window: window, alliance: :red, slot: 1, speed: 512, die_size: @size)
when 3, 6
# Blue: Left
# Red: Right
@ducks << Ducky.new(window: window, alliance: :blue, slot: 1, speed: 512, die_size: @size)
@ducks << Ducky.new(window: window, alliance: :blue, slot: 2, speed: 512, die_size: @size)
@ducks << Ducky.new(window: window, alliance: :blue, slot: 3, speed: 512, die_size: @size)
@ducks << Ducky.new(window: window, alliance: :red, slot: 3, speed: 512, die_size: @size)
@ducks << Ducky.new(window: window, alliance: :red, slot: 2, speed: 512, die_size: @size)
@ducks << Ducky.new(window: window, alliance: :red, slot: 1, speed: 512, die_size: @size)
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:)
@window = window
@alliance = alliance
@slot = slot
@speed = speed
@image = @window.get_image("#{ROOT_PATH}/media/openclipart_ducky.png")
@debug_text = Gosu::Font.new(28)
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)
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

View File

@@ -0,0 +1,271 @@
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
@randomizer_visible = false
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
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
button "Autonomous", width: 1.0 do
start_clock(:autonomous)
end
button "TeleOp with Countdown", width: 1.0 do
start_clock(:full_teleop)
end
button "TeleOp", width: 1.0 do
start_clock(:teleop_only)
end
button "TeleOp Endgame", width: 1.0, margin_bottom: 50 do
start_clock(:endgame_only)
end
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("#{ROOT_PATH}/media/icons/previous.png") do
RemoteControl.connection.puts(ClockNet::PacketHandler.packet_jukebox_previous_track)
end
button get_image("#{ROOT_PATH}/media/icons/right.png") do |button|
if @jukebox_playing
RemoteControl.connection.puts(ClockNet::PacketHandler.packet_jukebox_pause)
button.value = get_image("#{ROOT_PATH}/media/icons/right.png")
@jukebox_playing = false
else
RemoteControl.connection.puts(ClockNet::PacketHandler.packet_jukebox_play)
button.value = get_image("#{ROOT_PATH}/media/icons/pause.png")
@jukebox_playing = true
end
end
button get_image("#{ROOT_PATH}/media/icons/stop.png") do
RemoteControl.connection.puts(ClockNet::PacketHandler.packet_jukebox_stop)
end
button get_image("#{ROOT_PATH}/media/icons/next.png") do
RemoteControl.connection.puts(ClockNet::PacketHandler.packet_jukebox_next_track)
end
button get_image("#{ROOT_PATH}/media/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("#{ROOT_PATH}/media/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("#{ROOT_PATH}/media/icons/musicOn.png"), margin_left: 20, tip: "Toggle Sound Effects" do |button|
if @jukebox_sound_effects
button.value = get_image("#{ROOT_PATH}/media/icons/musicOff.png")
@jukebox_sound_effects = false
else
button.value = get_image("#{ROOT_PATH}/media/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 = "#{ROOT_PATH}/media/music"
if RUBY_PLATFORM.match(/ming|msys|cygwin/)
system("explorer \"#{path.gsub("/", "\\")}\"")
elsif RUBY_PLATFORM.match(/linux/)
system("xdg-open \"#{ROOT_PATH}/media/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
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
end
def update
super
while (o = RemoteControl.connection.proxy_object.queue.shift)
o.call
end
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_label.value = string
end
def randomizer_changed(boolean)
@randomizer_label.value = "Visible" if boolean
@randomizer_label.value = "Not Visible" unless boolean
end
end
end
end
end

View 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
View File

@@ -0,0 +1,30 @@
module TAC
class PracticeGameClock
THEME = {
TextBlock: {
font: "Canterell",
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: "#{File.expand_path("..", __dir__)}/media/icons/checkmark.png",
}
}
end
end

314
lib/game_clock/view.rb Normal file
View File

@@ -0,0 +1,314 @@
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("#{ROOT_PATH}/media/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("#{ROOT_PATH}/media/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?
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
button "Start Match", width: 1.0, margin_bottom: 50 do
@clock_proxy.start_clock(:full_match)
end
title "Practice", width: 1.0, text_align: :center
button "Autonomous", width: 1.0 do
@clock_proxy.start_clock(:autonomous)
end
button "TeleOp with Countdown", width: 1.0 do
@clock_proxy.start_clock(:full_teleop)
end
button "TeleOp", width: 1.0 do
@clock_proxy.start_clock(:teleop_only)
end
button "TeleOp Endgame", width: 1.0 do
@clock_proxy.start_clock(:endgame_only)
end
button "Abort Match", width: 1.0, margin_top: 50 do
@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
label "♫ Now playing:"
@current_song_label = label "♫ ♫ ♫"
end
flow(width: 1.0) do
label "Volume:"
@current_volume_label = label "100%"
end
flow(width: 1.0) do
button get_image("#{ROOT_PATH}/media/icons/previous.png") do
@jukebox.previous_track
end
button get_image("#{ROOT_PATH}/media/icons/pause.png") do |button|
if @jukebox.song && @jukebox.song.paused?
button.value = get_image("#{ROOT_PATH}/media/icons/right.png")
@jukebox.play
elsif !@jukebox.song
button.value = get_image("#{ROOT_PATH}/media/icons/right.png")
@jukebox.play
else
button.value = get_image("#{ROOT_PATH}/media/icons/pause.png")
@jukebox.pause
end
end
button get_image("#{ROOT_PATH}/media/icons/stop.png") do
@jukebox.stop
end
button get_image("#{ROOT_PATH}/media/icons/next.png") do
@jukebox.next_track
end
button get_image("#{ROOT_PATH}/media/icons/minus.png"), margin_left: 20 do
@jukebox.set_volume(@jukebox.volume - 0.1)
end
button get_image("#{ROOT_PATH}/media/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 #{ROOT_PATH}/media/music")
elsif RUBY_PLATFORM.match(/linux/)
system("xdg-open #{ROOT_PATH}/media/music")
else
# TODO.
end
end
button get_image("#{ROOT_PATH}/media/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("#{ROOT_PATH}/media/icons/musicOn.png")
else
button.value = get_image("#{ROOT_PATH}/media/icons/musicOff.png")
end
end
end
stack(width: 1.0) do
button "Randomizer", width: 1.0, **TAC::THEME_DANGER_BUTTON do
unless @clock.active?
push_state(Randomizer)
end
end
end
end
end
@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
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 request_repaint
if @particle_emitters && @particle_emitters.map(&:particle_count).sum.positive?
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 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)
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

View File

@@ -13,15 +13,27 @@ module TAC
populate_configs populate_configs
}) })
end end
button "Open Folder", tip: "Open folder containing configurations", height: 1.0 do
if RUBY_PLATFORM =~ /mingw/
system("start \"#{TAC::CONFIGS_PATH}\"")
elsif RUBY_PLATFORM =~ /darwin/
system("open \"#{TAC::CONFIGS_PATH}\"")
else
system("xdg-open \"#{TAC::CONFIGS_PATH}\"")
end
end
end end
status_bar.clear do status_bar.clear do
flow(width: 1.0, max_width: 720, h_align: :center) do
label "Current Configuration: " label "Current Configuration: "
@config_label = label window.backend.settings.config @config_label = label window.backend.settings.config
end end
end
body.clear do 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
end end
@@ -34,12 +46,13 @@ module TAC
@configs_list.clear do @configs_list.clear do
@config_files.each_with_index do |config_file, i| @config_files.each_with_index do |config_file, i|
flow width: 1.0, **THEME_ITEM_CONTAINER_PADDING do flow width: 1.0, height: 36, **THEME_ITEM_CONTAINER_PADDING do
background i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
name = File.basename(config_file, ".json") 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) change_config(name)
if window.backend.tacnet.connected? if window.backend.tacnet.connected?
@@ -92,6 +105,8 @@ module TAC
window.backend.load_config(name) window.backend.load_config(name)
@config_label.value = name.to_s @config_label.value = name.to_s
populate_configs
end end
end end
end end

View File

@@ -7,18 +7,20 @@ module TAC
header_bar("Drive Team Rotation Generator") header_bar("Drive Team Rotation Generator")
@roster ||= [ @roster ||= [
"Alexander",
"Aubrey", "Aubrey",
"Cayden", "Cayden",
"Dan",
"Gabe", "Gabe",
"Spencer", "Spencer",
"Olivia" "Sodi",
"Trent"
] ]
@roles ||= [ @roles ||= [
"Coach", "Coach",
"Driver A", "Driver A",
"Driver B" "Driver B",
"Human"
] ]
menu_bar.clear do menu_bar.clear do
@@ -30,19 +32,19 @@ module TAC
end end
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 populate_rotation
end end
end end
body.clear do body.clear do
flow(margin_left: 20, width: 1.0, height: 1.0) 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 title "Roles", width: 1.0, margin_bottom: 4, text_align: :center
flow(width: 1.0, margin_bottom: 20) do flow(width: 1.0, height: 32, margin_bottom: 20) do
@role_name = edit_line "", placeholder: "Add role", width: 0.9 @role_name = edit_line "", placeholder: "Add role", fill: true, height: 1.0
button get_image("#{TAC::ROOT_PATH}/media/icons/plus.png"), image_width: 0.1 do button get_image("#{TAC::ROOT_PATH}/media/icons/plus.png"), image_height: 1.0, tip: "Add role" do
if @role_name.value.strip.length.positive? if @role_name.value.strip.length.positive?
@roles.push(@role_name.value.strip) @roles.push(@role_name.value.strip)
@role_name.value = "" @role_name.value = ""
@@ -52,16 +54,16 @@ module TAC
end end
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
end end
stack(margin_left: 20, width: 0.25, height: 1.0) do stack(margin_left: 20, width: 0.25, height: 1.0) do
title "Roster", width: 1.0, margin_bottom: 4, text_align: :center title "Roster", width: 1.0, margin_bottom: 4, text_align: :center
flow(width: 1.0, margin_bottom: 20) do flow(width: 1.0, height: 32, margin_bottom: 20) do
@roster_name = edit_line "", placeholder: "Add name", width: 0.9 @roster_name = edit_line "", placeholder: "Add name", height: 1.0, fill: true
button get_image("#{TAC::ROOT_PATH}/media/icons/plus.png"), image_width: 0.1 do button get_image("#{TAC::ROOT_PATH}/media/icons/plus.png"), image_height: 1.0, tip: "Add name" do
if @roster_name.value.strip.length.positive? if @roster_name.value.strip.length.positive?
@roster.push(@roster_name.value.strip) @roster.push(@roster_name.value.strip)
@roster_name.value = "" @roster_name.value = ""
@@ -71,14 +73,14 @@ module TAC
end end
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
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 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 end
end end
@@ -92,11 +94,11 @@ module TAC
def populate_roles def populate_roles
@roles_container.clear do @roles_container.clear do
@roles.each_with_index do |name, i| @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 background i.even? ? 0xff_007000 : 0xff_006000
tagline name, width: 0.9 tagline name, fill: true
button "<b>X</b>", width: 0.1, text_size: 18, **THEME_DANGER_BUTTON do button get_image("#{TAC::ROOT_PATH}/media/icons/trashcan.png"), image_height: 1.0, tip: "Remove role", **THEME_DANGER_BUTTON do
@roles.delete(name) @roles.delete(name)
populate_roles populate_roles
end end
@@ -108,11 +110,11 @@ module TAC
def populate_roster def populate_roster
@roster_container.clear do @roster_container.clear do
@roster.each_with_index do |name, i| @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 background i.even? ? 0xff_007000 : 0xff_006000
tagline name, width: 0.9 tagline name, fill: true
button "<b>X</b>", width: 0.1, text_size: 18, **THEME_DANGER_BUTTON do button get_image("#{TAC::ROOT_PATH}/media/icons/trashcan.png"), image_height: 1.0, tip: "Remove name", **THEME_DANGER_BUTTON do
@roster.delete(name) @roster.delete(name)
populate_roster populate_roster
end end
@@ -125,13 +127,11 @@ module TAC
@rotation = Generator.new(roster: @roster, team_size: @roles.count) @rotation = Generator.new(roster: @roster, team_size: @roles.count)
@rotation_container.clear do @rotation_container.clear do
fraction = (1.0 / @rotation.team_size) - 0.02 flow(width: 1.0, height: 32, padding: 2) do
flow(width: 1.0, padding: 2) do
background Gosu::Color::BLACK background Gosu::Color::BLACK
@roles.each do |role| @roles.each do |role|
tagline "<b>#{role}</b>", width: fraction tagline "<b>#{role}</b>", fill: true
end end
end end
@@ -139,11 +139,11 @@ module TAC
teams = @rotation.teams.shuffle if @shuffle_teams&.value teams = @rotation.teams.shuffle if @shuffle_teams&.value
teams.each_with_index do |team, i| 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 background i.even? ? 0xff_007000 : 0xff_006000
team.each do |player| team.each do |player|
tagline player, width: fraction tagline player, fill: true
end end
end end
end end

View File

@@ -30,9 +30,9 @@ module TAC
body.clear do body.clear do
flow(width: 1.0, height: 1.0) do flow(width: 1.0, height: 1.0) do
stack width: 0.33333, height: 1.0, border_thickness_right: 1, border_color: [0, Gosu::Color::BLACK, 0, 0] do stack fill: true, height: 1.0, padding_left: 2, padding_right: 2, border_thickness_right: 1, border_color: Gosu::Color::BLACK do
@groups_menu = flow(width: 1.0) do @groups_menu = flow(width: 1.0, height: 36) do
label "Groups", text_size: THEME_SUBHEADING_TEXT_SIZE label "Groups", text_size: THEME_SUBHEADING_TEXT_SIZE, fill: true, text_align: :center
button get_image("#{TAC::ROOT_PATH}/media/icons/plus.png"), image_width: THEME_ICON_SIZE, tip: "Add group" do button get_image("#{TAC::ROOT_PATH}/media/icons/plus.png"), image_width: THEME_ICON_SIZE, tip: "Add group" do
push_state(TAC::Dialog::NamePromptDialog, title: "Create Group", list: window.backend.config.groups, callback_method: method(:create_group)) push_state(TAC::Dialog::NamePromptDialog, title: "Create Group", list: window.backend.config.groups, callback_method: method(:create_group))
@@ -42,7 +42,8 @@ module TAC
if @active_group if @active_group
push_state(Dialog::NamePromptDialog, title: "Clone Group", renaming: @active_group, accept_label: "Clone", list: window.backend.config.groups, callback_method: proc { |group, name| push_state(Dialog::NamePromptDialog, title: "Clone Group", renaming: @active_group, accept_label: "Clone", list: window.backend.config.groups, callback_method: proc { |group, name|
clone = TAC::Config::Group.from_json( JSON.parse( @active_group.to_json, symbolize_names: true )) clone = TAC::Config::Group.from_json( JSON.parse( @active_group.to_json, symbolize_names: true ))
clone.name = "#{name}" clone.name = name.to_s
window.backend.config.groups << clone window.backend.config.groups << clone
window.backend.config_changed! window.backend.config_changed!
@@ -57,7 +58,8 @@ module TAC
if @active_group if @active_group
push_state(Dialog::NamePromptDialog, title: "Save Group Preset", renaming: @active_group, accept_label: "Save", list: window.backend.config.presets.groups, callback_method: proc { |group, name| push_state(Dialog::NamePromptDialog, title: "Save Group Preset", renaming: @active_group, accept_label: "Save", list: window.backend.config.presets.groups, callback_method: proc { |group, name|
clone = TAC::Config::Group.from_json( JSON.parse( @active_group.to_json, symbolize_names: true )) clone = TAC::Config::Group.from_json( JSON.parse( @active_group.to_json, symbolize_names: true ))
clone.name = "#{name}" clone.name = name.to_s
window.backend.config.presets.groups << clone window.backend.config.presets.groups << clone
window.backend.config.presets.groups.sort_by! { |g| g.name.downcase } window.backend.config.presets.groups.sort_by! { |g| g.name.downcase }
window.backend.config_changed! window.backend.config_changed!
@@ -73,7 +75,8 @@ module TAC
push_state(Dialog::PickPresetDialog, title: "Pick Group Preset", limit: :groups, callback_method: proc { |preset| push_state(Dialog::PickPresetDialog, title: "Pick Group Preset", limit: :groups, callback_method: proc { |preset|
push_state(Dialog::NamePromptDialog, title: "Name Group", renaming: preset, accept_label: "Add", list: window.backend.config.groups, callback_method: proc { |group, name| push_state(Dialog::NamePromptDialog, title: "Name Group", renaming: preset, accept_label: "Add", list: window.backend.config.groups, callback_method: proc { |group, name|
clone = TAC::Config::Group.from_json( JSON.parse( group.to_json, symbolize_names: true )) clone = TAC::Config::Group.from_json( JSON.parse( group.to_json, symbolize_names: true ))
clone.name = "#{name}" clone.name = name.to_s
window.backend.config.groups << clone window.backend.config.groups << clone
window.backend.config.groups.sort_by! { |g| g.name.downcase } window.backend.config.groups.sort_by! { |g| g.name.downcase }
window.backend.config_changed! window.backend.config_changed!
@@ -84,13 +87,13 @@ module TAC
end end
end end
@groups_list = stack width: 1.0, scroll: true do @groups_list = stack width: 1.0, fill: true, scroll: true do
end end
end end
stack width: 0.33333, height: 1.0, border_thickness_right: 1, border_color: [0, Gosu::Color::BLACK, 0, 0] do stack fill: true, height: 1.0, padding_left: 2, padding_right: 2, border_thickness_right: 1, border_color: Gosu::Color::BLACK do
@actions_menu = flow(width: 1.0) do @actions_menu = flow(width: 1.0, height: 36) do
label "Actions", text_size: THEME_SUBHEADING_TEXT_SIZE label "Actions", text_size: THEME_SUBHEADING_TEXT_SIZE, fill: true, text_align: :center
button get_image("#{TAC::ROOT_PATH}/media/icons/plus.png"), image_width: THEME_ICON_SIZE, tip: "Add action" do button get_image("#{TAC::ROOT_PATH}/media/icons/plus.png"), image_width: THEME_ICON_SIZE, tip: "Add action" do
if @active_group if @active_group
@@ -106,6 +109,7 @@ module TAC
clone = TAC::Config::Action.from_json( JSON.parse( @active_action.to_json, symbolize_names: true )) clone = TAC::Config::Action.from_json( JSON.parse( @active_action.to_json, symbolize_names: true ))
clone.name = name clone.name = name
clone.comment = comment clone.comment = comment
@active_group.actions << clone @active_group.actions << clone
window.backend.config_changed! window.backend.config_changed!
@@ -121,6 +125,7 @@ module TAC
push_state(Dialog::NamePromptDialog, title: "Save Action Preset", renaming: @active_action, accept_label: "Save", list: window.backend.config.presets.actions, callback_method: proc { |action, name| push_state(Dialog::NamePromptDialog, title: "Save Action Preset", renaming: @active_action, accept_label: "Save", list: window.backend.config.presets.actions, callback_method: proc { |action, name|
clone = TAC::Config::Action.from_json( JSON.parse( @active_action.to_json, symbolize_names: true )) clone = TAC::Config::Action.from_json( JSON.parse( @active_action.to_json, symbolize_names: true ))
clone.name = "#{name}" clone.name = "#{name}"
window.backend.config.presets.actions << clone window.backend.config.presets.actions << clone
window.backend.config.presets.actions.sort_by! { |a| a.name.downcase } window.backend.config.presets.actions.sort_by! { |a| a.name.downcase }
window.backend.config_changed! window.backend.config_changed!
@@ -135,9 +140,12 @@ module TAC
button get_image("#{TAC::ROOT_PATH}/media/icons/import.png"), image_width: THEME_ICON_SIZE, tip: "Import action from preset" do button get_image("#{TAC::ROOT_PATH}/media/icons/import.png"), image_width: THEME_ICON_SIZE, tip: "Import action from preset" do
if @active_group if @active_group
push_state(Dialog::PickPresetDialog, title: "Pick Action Preset", limit: :actions, callback_method: proc { |preset| push_state(Dialog::PickPresetDialog, title: "Pick Action Preset", limit: :actions, callback_method: proc { |preset|
push_state(Dialog::ActionDialog, title: "Name Action", action: preset, accept_label: "Add", list: @active_group.actions, callback_method: proc { |action, name| push_state(Dialog::ActionDialog, title: "Name Action", action: preset, accept_label: "Add", list: @active_group.actions, callback_method: proc { |action, name, comment|
clone = TAC::Config::Action.from_json( JSON.parse( action.to_json, symbolize_names: true )) clone = TAC::Config::Action.from_json( JSON.parse( action.to_json, symbolize_names: true ))
clone.name = "#{name}" clone.name = name.to_s
clone.comment = comment.to_s
clone.enabled = true
@active_group.actions << clone @active_group.actions << clone
@active_group.actions.sort_by! { |a| a.name.downcase } @active_group.actions.sort_by! { |a| a.name.downcase }
window.backend.config_changed! window.backend.config_changed!
@@ -151,33 +159,37 @@ module TAC
end end
end end
@actions_list = stack width: 1.0, scroll: true do @actions_list = stack width: 1.0, fill: true, scroll: true do
end end
end end
stack width: 0.331, height: 1.0 do stack fill: true, height: 1.0, padding_left: 2, padding_right: 2 do
@variables_menu = flow(width: 1.0) do @variables_menu = flow(width: 1.0, height: 36) do
label "Variables", text_size: THEME_SUBHEADING_TEXT_SIZE label "Variables", text_size: THEME_SUBHEADING_TEXT_SIZE, fill: true, text_align: :center
button get_image("#{TAC::ROOT_PATH}/media/icons/plus.png"), image_width: THEME_ICON_SIZE, tip: "Add variable" do button get_image("#{TAC::ROOT_PATH}/media/icons/plus.png"), image_width: THEME_ICON_SIZE, tip: "Add variable" do
if @active_action if @active_action
push_state(TAC::Dialog::VariableDialog, title: "Create Variable", callback_method: method(:create_variable)) push_state(TAC::Dialog::VariableDialog, title: "Create Variable", list: @active_action.variables, callback_method: method(:create_variable))
else else
push_state(TAC::Dialog::AlertDialog, title: "Error", message: "Unable to create variable, no action selected.") push_state(TAC::Dialog::AlertDialog, title: "Error", message: "Unable to create variable, no action selected.")
end end
end end
end end
@variables_list = stack width: 1.0, scroll: true do @variables_list = stack width: 1.0, fill: true, scroll: true do
end end
end end
end end
end end
populate_groups_list populated_groups_list = false
if @options[:group_is_preset] if @options[:group_is_preset]
populated_groups_list = true
@active_group = @options[:group] @active_group = @options[:group]
@active_group_label.value = @active_group.name @active_group_label.value = @active_group.name
populate_groups_list
populate_actions_list(@active_group) populate_actions_list(@active_group)
@groups_menu.hide @groups_menu.hide
@@ -193,15 +205,20 @@ module TAC
else else
if @options[:group] if @options[:group]
populated_groups_list = true
@active_group = @options[:group] @active_group = @options[:group]
@active_group_label.value = @active_group.name @active_group_label.value = @active_group.name
populate_actions_list(@active_group) populate_groups_list
populate_actions_list(@active_group) unless @options[:action]
if @options[:action] if @options[:action]
@active_action = @options[:action] @active_action = @options[:action]
@active_action_label.value = @active_action.name @active_action_label.value = @active_action.name
populate_actions_list(@active_group)
populate_variables_list(@active_action) populate_variables_list(@active_action)
if @options[:variable] if @options[:variable]
@@ -211,23 +228,19 @@ module TAC
end end
end end
body.root.subscribe(:window_size_changed) do populate_groups_list unless populated_groups_list
set_list_heights
end
end
def set_list_heights
@groups_list.style.height = body.height - @groups_menu.height
@actions_list.style.height = body.height - @actions_menu.height
@variables_list.style.height = body.height - @variables_menu.height
end end
def create_group(name) def create_group(name)
window.backend.config.groups << TAC::Config::Group.new(name: name, actions: []) group = TAC::Config::Group.new(name: name, actions: [])
window.backend.config.groups << group
window.backend.config.groups.sort_by! { |g| g.name.downcase } window.backend.config.groups.sort_by! { |g| g.name.downcase }
window.backend.config_changed! window.backend.config_changed!
populate_groups_list populate_groups_list
scroll_into_view(group)
end end
def update_group(group, name) def update_group(group, name)
@@ -236,6 +249,8 @@ module TAC
window.backend.config_changed! window.backend.config_changed!
populate_groups_list populate_groups_list
scroll_into_view(group)
end end
def delete_group(group) def delete_group(group)
@@ -244,8 +259,10 @@ module TAC
window.backend.config_changed! window.backend.config_changed!
@active_group = nil @active_group = nil
@active_group_container = nil
@active_group_label.value = "" @active_group_label.value = ""
@active_action = nil @active_action = nil
@active_active_container = nil
@active_action_label.value = "" @active_action_label.value = ""
@actions_list.clear @actions_list.clear
@variables_list.clear @variables_list.clear
@@ -254,19 +271,32 @@ module TAC
end end
def create_action(name, comment) def create_action(name, comment)
@active_group.actions << TAC::Config::Action.new(name: name, comment: comment, enabled: true, variables: []) action = TAC::Config::Action.new(name: name, comment: comment, enabled: true, variables: [])
@active_group.actions << action
@active_group.actions.sort_by! { |a| a.name.downcase } @active_group.actions.sort_by! { |a| a.name.downcase }
window.backend.config_changed! window.backend.config_changed!
populate_actions_list(@active_group) populate_actions_list(@active_group)
scroll_into_view(action)
end end
def update_action(action, name, comment) def update_action(action, name, comment)
old_name = action.name
action.name = name action.name = name
action.comment = comment action.comment = comment
@active_group.actions.sort_by! { |a| a.name.downcase }
window.backend.config_changed! window.backend.config_changed!
populate_actions_list(@active_group) a = @actions_list.children.find { |a| a.style.tag == old_name.downcase }
label = a.children.find { |a| a.style.tag == "label" }
# comment = a.children.find { |a| a.style.tag == "comment" }
label.value = name
update_list_children(@actions_list)
scroll_into_view(action)
end end
def delete_action(action) def delete_action(action)
@@ -278,15 +308,23 @@ module TAC
@active_action_label.value = "" @active_action_label.value = ""
@variables_list.clear @variables_list.clear
populate_actions_list(@active_group) # Remove deleted action from list
container = @actions_list.children.find { |a| a.style.tag == action.name.downcase }
@actions_list.remove(container)
update_list_children(@actions_list)
end end
def create_variable(name, type, value) def create_variable(name, type, value)
@active_action.variables << TAC::Config::Variable.new(name: name, type: type, value: value) variable = TAC::Config::Variable.new(name: name, type: type, value: value)
@active_action.variables << variable
@active_action.variables.sort_by! { |v| v.name.downcase } @active_action.variables.sort_by! { |v| v.name.downcase }
window.backend.config_changed! window.backend.config_changed!
populate_variables_list(@active_action) populate_variables_list(@active_action)
scroll_into_view(variable)
end end
def update_variable(variable, name, type, value) def update_variable(variable, name, type, value)
@@ -299,6 +337,8 @@ module TAC
window.backend.config_changed! window.backend.config_changed!
populate_variables_list(@active_action) populate_variables_list(@active_action)
scroll_into_view(variable)
end end
def delete_variable(variable) def delete_variable(variable)
@@ -309,6 +349,33 @@ module TAC
populate_variables_list(@active_action) populate_variables_list(@active_action)
end end
def update_list_children(list)
is_group = list == @groups_list
is_action = list == @actions_list
is_variable = list == @variables_list
list.children.sort_by! { |i| i.style.tag }
list.children.each_with_index do |child, i|
bg_color = i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
bg_color = THEME_HIGHLIGHTED_COLOR if is_group && @active_group&.name&.downcase == child.style.tag
bg_color = THEME_HIGHLIGHTED_COLOR if is_action && @active_action&.name&.downcase == child.style.tag
child.style.default[:background] = bg_color
child.root.gui_state.request_recalculate
end
end
def scroll_into_view(item)
if item.is_a?(TAC::Config::Group)
elsif item.is_a?(TAC::Config::Action)
elsif item.is_a?(TAC::Config::Variable)
else
raise "Unsupported item type: #{item.class}"
end
end
def populate_groups_list def populate_groups_list
@groups_list.scroll_top = 0 @groups_list.scroll_top = 0
@@ -320,13 +387,21 @@ module TAC
@groups_list.clear do @groups_list.clear do
groups.each_with_index do |group, i| groups.each_with_index do |group, i|
flow width: 1.0, **THEME_ITEM_CONTAINER_PADDING do flow width: 1.0, height: 36, **THEME_ITEM_CONTAINER_PADDING, tag: group.name.downcase do |container|
background i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR background group == @active_group ? THEME_HIGHLIGHTED_COLOR : (i.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
if (old_i = groups.index(@active_group))
@active_group_container.style.default[:background] = old_i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
end
button group.name, width: 0.8 do
@active_group = group @active_group = group
@active_group_container = container
@active_group_container.style.default[:background] = THEME_HIGHLIGHTED_COLOR
@active_group_label.value = group.name @active_group_label.value = group.name
@active_action = nil @active_action = nil
@active_action_container = nil
@active_action_label.value = "" @active_action_label.value = ""
populate_actions_list(group) populate_actions_list(group)
@@ -342,8 +417,6 @@ module TAC
end end
end end
end end
set_list_heights
end end
def populate_actions_list(group) def populate_actions_list(group)
@@ -353,12 +426,19 @@ module TAC
@actions_list.clear do @actions_list.clear do
actions.each_with_index do |action, i| actions.each_with_index do |action, i|
stack width: 1.0, **THEME_ITEM_CONTAINER_PADDING do stack width: 1.0, height: action.comment.empty? ? 36 : 72, **THEME_ITEM_CONTAINER_PADDING, tag: action.name.downcase do |container|
background i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR background action == @active_action ? THEME_HIGHLIGHTED_COLOR : (i.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
if (old_i = actions.index(@active_action))
@active_action_container.style.default[:background] = old_i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
end
flow width: 1.0 do
button action.name, width: 0.72 do
@active_action = action @active_action = action
@active_action_container = container
@active_action_container.style.default[:background] = THEME_HIGHLIGHTED_COLOR
@active_action_label.value = action.name @active_action_label.value = action.name
populate_variables_list(action) populate_variables_list(action)
@@ -371,7 +451,7 @@ module TAC
end end
button get_image("#{TAC::ROOT_PATH}/media/icons/gear.png"), image_width: THEME_ICON_SIZE, tip: "Edit action" do button get_image("#{TAC::ROOT_PATH}/media/icons/gear.png"), image_width: THEME_ICON_SIZE, tip: "Edit action" do
push_state(Dialog::ActionDialog, title: "Rename Action", action: action, list: @active_group.actions, callback_method: method(:update_action)) push_state(Dialog::ActionDialog, title: "Edit Action", action: action, list: @active_group.actions, callback_method: method(:update_action))
end end
button get_image("#{TAC::ROOT_PATH}/media/icons/trashcan.png"), image_width: THEME_ICON_SIZE, tip: "Delete action", **THEME_DANGER_BUTTON do button get_image("#{TAC::ROOT_PATH}/media/icons/trashcan.png"), image_width: THEME_ICON_SIZE, tip: "Delete action", **THEME_DANGER_BUTTON do
@@ -379,12 +459,14 @@ module TAC
end end
end end
caption "#{action.comment}", width: 1.0, text_wrap: :word_wrap unless action.comment.empty? unless action.comment.empty?
stack(width: 1.0, fill: true, scroll: true) 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 end
end end
end end
set_list_heights
end end
def populate_variables_list(action) def populate_variables_list(action)
@@ -394,12 +476,12 @@ module TAC
@variables_list.clear do @variables_list.clear do
variables.each_with_index do |variable, i| variables.each_with_index do |variable, i|
stack width: 1.0, **THEME_ITEM_CONTAINER_PADDING do stack width: 1.0, height: 96, **THEME_ITEM_CONTAINER_PADDING, tag: variable.name.downcase do
background i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR background i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
flow(width: 1.0) do flow(width: 1.0, fill: true) do
button "#{variable.name}", width: 0.89, tip: "Edit variable" do button "#{variable.name}", fill: true, text_size: THEME_ICON_SIZE - 3, tip: "Edit variable", tag: "label" do
push_state(Dialog::VariableDialog, title: "Edit Variable", variable: variable, callback_method: method(:update_variable)) push_state(Dialog::VariableDialog, title: "Edit Variable", variable: variable, list: @active_action.variables, callback_method: method(:update_variable))
end end
button get_image("#{TAC::ROOT_PATH}/media/icons/trashcan.png"), image_width: THEME_ICON_SIZE, tip: "Delete variable", **THEME_DANGER_BUTTON do button get_image("#{TAC::ROOT_PATH}/media/icons/trashcan.png"), image_width: THEME_ICON_SIZE, tip: "Delete variable", **THEME_DANGER_BUTTON do
@@ -407,13 +489,57 @@ module TAC
end end
end end
caption "Type: #{variable.type}" caption "Type: #{variable.type}", tag: "type", fill: true
caption "Value: #{variable.value}" caption "Value: #{variable.value}", tag: "value", fill: true
end
end end
end end
end end
set_list_heights def button_down(id)
super
return if control_down? || shift_down? || !alt_down?
case id
when Gosu::KB_G
push_state(
TAC::Dialog::NamePromptDialog,
title: "Create Group",
list: window.backend.config.groups,
callback_method: method(:create_group)
)
when Gosu::KB_A
if @active_group
push_state(
TAC::Dialog::ActionDialog,
title: "Create Action",
list: @active_group.actions,
callback_method: method(:create_action)
)
else
push_state(
TAC::Dialog::AlertDialog,
title: "Error",
message: "Unable to create action, no group selected."
)
end
when Gosu::KB_V
if @active_action
push_state(
TAC::Dialog::VariableDialog,
title: "Create Variable",
list: @active_action.variables,
callback_method: method(:create_variable)
)
else
push_state(
TAC::Dialog::AlertDialog,
title: "Error",
message: "Unable to create variable, no action selected."
)
end
end
end end
end end
end end

View File

@@ -33,8 +33,15 @@ module TAC
button "Reset", text_size: THEME_HEADING_TEXT_SIZE, **THEME_DANGER_BUTTON do button "Reset", text_size: THEME_HEADING_TEXT_SIZE, **THEME_DANGER_BUTTON do
@nodes.clear @nodes.clear
measure_path
refresh_panel refresh_panel
end end
list_box items: ["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
end end
@@ -43,7 +50,7 @@ module TAC
tagline "Nodes:" tagline "Nodes:"
@nodes_count_label = tagline "0" @nodes_count_label = tagline "0"
tagline "Total Distance:" tagline "Total Distance:", margin_left: 20
@total_distance_label = tagline "0" @total_distance_label = tagline "0"
@units_label = tagline "Inches" @units_label = tagline "Inches"
@@ -61,7 +68,7 @@ module TAC
end end
end end
@field = TAC::Simulator::Field.new(container: @field_container, season: :freight_frenzy, simulation: nil) @field = TAC::Simulator::Field.new(container: @field_container, season: :power_play, simulation: nil)
@nodes ||= [] @nodes ||= []
@unit = :inches @unit = :inches
@total_distance = 0 @total_distance = 0
@@ -72,21 +79,41 @@ module TAC
@node_radius = 6 @node_radius = 6
@segment_thickness = 2 @segment_thickness = 2
@font = CyberarmEngine::Text.new(font: THEME_BOLD_FONT, size: 18, border: true, static: true)
measure_path measure_path
refresh_panel refresh_panel
end end
def draw def draw
super
@field.draw @field.draw
display_path 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 end
def update def update
super
@field.update @field.update
measure_path measure_path
@@ -133,7 +160,7 @@ module TAC
Gosu.draw_circle( Gosu.draw_circle(
current_node.x * @field.scale + @field_container.x, current_node.x * @field.scale + @field_container.x,
current_node.y * @field.scale + @field_container.y, 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? next if i.zero?
@@ -153,7 +180,8 @@ module TAC
(@field_container.y + last_node.y * @field.scale) - distance, (@field_container.y + last_node.y * @field.scale) - distance,
@segment_thickness, @segment_thickness,
distance, distance,
@segment_color @segment_color,
@field_container.z + 1
) )
end end
@@ -184,6 +212,10 @@ module TAC
@total_distance_label.value = "#{inches_to_unit(@total_distance).round(2)}" @total_distance_label.value = "#{inches_to_unit(@total_distance).round(2)}"
@units_label.value = @unit.to_s.capitalize @units_label.value = @unit.to_s.capitalize
status_bar.recalculate
status_bar.recalculate
status_bar.recalculate
# @points_container.clear do # @points_container.clear do
# v1 = @nodes.first # v1 = @nodes.first
# break unless v1 # break unless v1
@@ -214,11 +246,11 @@ module TAC
when :feet when :feet
inches / 12.0 inches / 12.0
when :millimeters when :millimeters
inches / 0.254 inches * 25.4
when :centimeters when :centimeters
inches / 2.54 inches * 2.54
when :meters when :meters
inches / 25.4 inches * 0.0254
end end
end end
end end

56
lib/pages/game_clock.rb Normal file
View 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,
"#{ROOT_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

View File

@@ -34,9 +34,43 @@ module TAC
label "Total actions: #{actions.size}" label "Total actions: #{actions.size}"
label "Total variables: #{variables.size}" label "Total variables: #{variables.size}"
end 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 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

View File

@@ -11,10 +11,10 @@ module TAC
body.clear do body.clear do
flow(width: 1.0, height: 1.0) 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 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 end
end end
@@ -26,10 +26,10 @@ module TAC
def populate_group_presets def populate_group_presets
@group_presets.clear do @group_presets.clear do
window.backend.config.presets.groups.each_with_index do |group, i| window.backend.config.presets.groups.each_with_index do |group, i|
flow(width: 1.0, **THEME_ITEM_CONTAINER_PADDING) do flow(width: 1.0, height: 36, **THEME_ITEM_CONTAINER_PADDING) do
background i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR background i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
button group.name, width: 0.895 do button group.name, fill: true, text_size: THEME_ICON_SIZE - 3 do
page(TAC::Pages::Editor, { group: group, group_is_preset: true }) page(TAC::Pages::Editor, { group: group, group_is_preset: true })
end end
@@ -59,10 +59,10 @@ module TAC
def populate_action_presets def populate_action_presets
@action_presets.clear do @action_presets.clear do
window.backend.config.presets.actions.each_with_index do |action, i| window.backend.config.presets.actions.each_with_index do |action, i|
flow(width: 1.0, **THEME_ITEM_CONTAINER_PADDING) do flow(width: 1.0, height: 36, **THEME_ITEM_CONTAINER_PADDING) do
background i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR background i.even? ? THEME_EVEN_COLOR : THEME_ODD_COLOR
button action.name, width: 0.895 do button action.name, fill: true, text_size: THEME_ICON_SIZE - 3 do
page(TAC::Pages::Editor, { action: action, action_is_preset: true }) page(TAC::Pages::Editor, { action: action, action_is_preset: true })
end end

View File

@@ -5,10 +5,10 @@ module TAC
header_bar("Search") header_bar("Search")
menu_bar.clear do menu_bar.clear do
search = edit_line "", width: 0.9, height: 1.0 search = edit_line "", fill: true, height: 1.0
button get_image("#{TAC::ROOT_PATH}/media/icons/zoom.png"), image_height: 1.0 do button get_image("#{TAC::ROOT_PATH}/media/icons/zoom.png"), image_height: 1.0 do
unless search.value.strip.empty? unless search.value.strip.empty?
search_results = search_config(search.value.downcase.strip) search_results = search_config(search.value.strip)
status_bar.clear do status_bar.clear do
if search_results.results.size.zero? if search_results.results.size.zero?
@@ -89,7 +89,6 @@ module TAC
end end
end end
if search_results.action_presets.size.positive? if search_results.action_presets.size.positive?
title "Action Presets" title "Action Presets"
@@ -152,7 +151,7 @@ module TAC
def search_groups(query, search_results) def search_groups(query, search_results)
window.backend.config.groups.each do |group| 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) result = SearchResult.new(group: group, query: query, is_group: true, is_from_name: true)
search_results.results << result search_results.results << result
end end
@@ -162,12 +161,12 @@ module TAC
def search_actions(query, search_results) def search_actions(query, search_results)
window.backend.config.groups.each do |group| window.backend.config.groups.each do |group|
group.actions.each do |action| 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) result = SearchResult.new(group: group, action: action, query: query, is_action: true, is_from_name: true)
search_results.results << result search_results.results << result
end 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) result = SearchResult.new(group: group, action: action, query: query, is_action: true, is_from_comment: true)
search_results.results << result search_results.results << result
end end
@@ -179,12 +178,12 @@ module TAC
window.backend.config.groups.each do |group| window.backend.config.groups.each do |group|
group.actions.each do |action| group.actions.each do |action|
action.variables.each do |variable| 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) result = SearchResult.new(group: group, action: action, variable: variable, is_variable: true, query: query, is_from_name: true)
search_results.results << result search_results.results << result
end 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) result = SearchResult.new(group: group, action: action, variable: variable, is_variable: true, query: query, is_from_value: true)
search_results.results << result search_results.results << result
end end
@@ -195,29 +194,29 @@ module TAC
def search_presets(query, search_results) def search_presets(query, search_results)
window.backend.config.presets.groups.each do |group| 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) result = SearchResult.new(group: group, query: query, is_group: true, is_from_name: true, is_preset: true)
search_results.results << result search_results.results << result
end end
group.actions.each do |action| 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) result = SearchResult.new(group: group, action: action, query: query, is_action: true, is_from_name: true, is_preset: true)
search_results.results << result search_results.results << result
end 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) result = SearchResult.new(group: group, action: action, query: query, is_action: true, is_from_comment: true, is_preset: true)
search_results.results << result search_results.results << result
end end
action.variables.each do |variable| 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) 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 search_results.results << result
end 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) 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 search_results.results << result
end end
@@ -226,23 +225,23 @@ module TAC
end end
window.backend.config.presets.actions.each do |action| window.backend.config.presets.actions.each do |action|
if action.name.downcase.include?(query) 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) result = SearchResult.new(group: nil, action: action, query: query, is_action: true, is_from_name: true, is_preset: true)
search_results.results << result search_results.results << result
end end
if action.comment.downcase.include?(query) 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) result = SearchResult.new(group: nil, action: action, query: query, is_action: true, is_from_comment: true, is_preset: true)
search_results.results << result search_results.results << result
end end
action.variables.each do |variable| action.variables.each do |variable|
if variable.name.downcase.include?(query) 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) 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 search_results.results << result
end end
if variable.value.downcase.include?(query) 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) 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 search_results.results << result
end end
@@ -332,7 +331,8 @@ module TAC
end end
def highlight(string) 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 end
end end

View File

@@ -12,7 +12,7 @@ module TAC
begin begin
@simulation = TAC::Simulator::Simulation.new(source_code: @source_code.value, field_container: @field_container) @simulation = TAC::Simulator::Simulation.new(source_code: @source_code.value, field_container: @field_container)
@simulation.start @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.backtrace.reverse.join("\n")
puts e puts e
push_state(Dialog::AlertDialog, title: "#{e.class}", message: e) push_state(Dialog::AlertDialog, title: "#{e.class}", message: e)
@@ -49,7 +49,7 @@ robot.forward 100
robot.turn -90 robot.turn -90
robot.forward 100" 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 @source_code = edit_box source_code, width: 1.0, height: 1.0
end end
@@ -77,6 +77,7 @@ robot.forward 100"
@simulation.update @simulation.update
unless @simulation.robots.all? { |robot| robot.queue.empty? } # Only update clock if simulation is running 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" @simulation_status.value = "Time: #{(@simulation.simulation_time).round(1)} seconds"
end end
end end

View File

@@ -12,14 +12,14 @@ module TAC
@scale = 1 @scale = 1
@size = 0 @size = 0
@field_size = 144 # inches [1 pixel = 1 inch] @field_size = 144 # inches [1 pixel = 1 inch]
@z = @container.z + 1
@blue = Gosu::Color.new(0xff_004080) @blue = Gosu::Color.new(0xff_004080)
@red = Gosu::Color.new(0xff_800000) @red = Gosu::Color.new(0xff_800000)
@soft_orange = Gosu::Color.rgb(255, 175, 0)
end end
def draw def draw
Gosu.flush
Gosu.clip_to(@position.x, @position.y, @size, @size) do Gosu.clip_to(@position.x, @position.y, @size, @size) do
Gosu.translate(@position.x, @position.y) do Gosu.translate(@position.x, @position.y) do
draw_field draw_field
@@ -34,37 +34,38 @@ module TAC
end end
def draw_field 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 6.times do |i| # Tile lines across
next if i == 0 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 end
6.times do |i| # Tile lines down 6.times do |i| # Tile lines down
next if i == 0 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
end end
def draw_field_skystone def draw_field_skystone
# blue bridge # blue bridge
Gosu.draw_rect(0, @field_size / 2 - 2, 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) Gosu.draw_rect(0, @field_size / 2 + 1, 48, 1, @blue, @z)
# mid bridge # 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 - 9.25, 48, 18.5, Gosu::Color.new(0xff_222222), @z)
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 - 2, 48, 1, @soft_orange, @z)
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 + 1, 48, 1, @soft_orange, @z)
# blue bridge # blue bridge
Gosu.draw_rect(@field_size - 48, @field_size / 2 - 2, 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) Gosu.draw_rect(@field_size - 48, @field_size / 2 + 1, 48, 1, @red, @z)
# blue build site # blue build site
Gosu.draw_quad( Gosu.draw_quad(
24 - 2, 0, @blue, 24 - 2, 0, @blue,
24, 0, @blue, 24, 0, @blue,
0, 24 - 2, @blue, 0, 24 - 2, @blue,
0, 24, @blue 0, 24, @blue,
@z
) )
# red build site # red build site
@@ -72,50 +73,51 @@ module TAC
@field_size - (24 - 2), 0, @red, @field_size - (24 - 2), 0, @red,
@field_size - (24 - 0), 0, @red, @field_size - (24 - 0), 0, @red,
@field_size, 24 - 2, @red, @field_size, 24 - 2, @red,
@field_size, 24, @red @field_size, 24, @red,
@z
) )
# blue depot # blue depot
Gosu.draw_rect(@field_size - 24, @field_size - 24, 24, 2, @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) Gosu.draw_rect(@field_size - 24, @field_size - 24, 2, 24, @blue, @z)
# red depot # red depot
Gosu.draw_rect(-1, @field_size - 24, 24, 2, @red) Gosu.draw_rect(-1, @field_size - 24, 24, 2, @red, @z)
Gosu.draw_rect(22, @field_size - 24, 2, 24, @red) Gosu.draw_rect(22, @field_size - 24, 2, 24, @red, @z)
# blue foundation # 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 # 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 # stones
6.times do |i| 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 end
6.times do |i| 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
end end
def draw_field_ultimate_goal def draw_field_ultimate_goal
# middle line # 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 # 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 # blue starting lines
Gosu.draw_rect(24 - 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) Gosu.draw_rect(48 - 1, @field_size - 24, 2, 24, @blue, @z)
# blue wobbly wobs # blue wobbly wobs
Gosu.draw_circle(24, @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) Gosu.draw_circle(48, @field_size - 24, 4, 32, @blue, @z)
# blue starter stack # 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 # blue target zones
# A # A
@@ -132,15 +134,15 @@ module TAC
end end
# red starting lines # red starting lines
Gosu.draw_rect(@field_size - 24 - 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) Gosu.draw_rect(@field_size - 48 - 1, @field_size - 24, 2, 24, @red, @z)
# red wobbly wobs # red wobbly wobs
Gosu.draw_circle(@field_size - 24, @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) Gosu.draw_circle(@field_size - 48, @field_size - 24, 4, 32, @red, @z)
# red starter stack # 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 # red target zones
# A # A
@@ -161,91 +163,88 @@ module TAC
def draw_field_freight_frenzy def draw_field_freight_frenzy
# blue ZONE # blue ZONE
Gosu.draw_rect(24, @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) Gosu.draw_rect(24, @field_size - 24, 24, 2, @blue, @z)
Gosu.draw_rect(48 - 2, @field_size - 24, 2, 24, @blue) Gosu.draw_rect(48 - 2, @field_size - 24, 2, 24, @blue, @z)
# blue barcode 1 # blue barcode 1
Gosu.draw_rect(36 - 1, @field_size - 24 - 4, 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) 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) Gosu.draw_rect(36 - 1, @field_size - 48 + 2, 2, 2, @blue, @z)
# blue barcode 2 # blue barcode 2
Gosu.draw_rect(36 - 1, 48 + 2, 2, 2, @blue) Gosu.draw_rect(36 - 1, 48 + 2, 2, 2, @blue, @z)
Gosu.draw_rect(36 - 1, 60 - 1, 2, 2, @blue) Gosu.draw_rect(36 - 1, 60 - 1, 2, 2, @blue, @z)
Gosu.draw_rect(36 - 1, 72 - 4, 2, 2, @blue) Gosu.draw_rect(36 - 1, 72 - 4, 2, 2, @blue, @z)
# blue wobble goal # blue wobble goal
Gosu.draw_circle(48, 84, 9, 32, @blue) Gosu.draw_circle(48, 84, 9, 32, @blue, @z)
# blue shared wobble goal # 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 # red ZONE
Gosu.draw_rect(@field_size - 24 - 2, @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) 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) Gosu.draw_rect(@field_size - 48, @field_size - 24, 2, 24, @red, @z)
# red barcode 1 # 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 - 24 - 4, 2, 2, @red, @z)
Gosu.draw_rect(@field_size - 36 - 1, @field_size - 36 - 1, 2, 2, @red) 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) Gosu.draw_rect(@field_size - 36 - 1, @field_size - 48 + 2, 2, 2, @red, @z)
# red barcode 2 # red barcode 2
Gosu.draw_rect(@field_size - 36 - 1, 48 + 2, 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) 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) Gosu.draw_rect(@field_size - 36 - 1, 72 - 4, 2, 2, @red, @z)
# red wobble goal # 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 # red shared wobble goal
Gosu.clip_to(@field_size / 2, 0, 10, 48) do # Gosu.clip_to(@field_size / 2, 0, 10, 48) do
Gosu.draw_circle(@field_size / 2, 24, 9, 32, @red) Gosu.draw_circle(@field_size / 2, 24, 9, 32, @red, @z)
end # end
# white corner left # white corner left
faint_white = Gosu::Color.rgb(240, 240, 240) faint_white = Gosu::Color.rgb(240, 240, 240)
Gosu.draw_rect(0, 46 - 2, 46, 2, faint_white) Gosu.draw_rect(0, 46 - 2, 46, 2, faint_white, @z)
Gosu.draw_rect(46 - 2, 0, 2, 46, faint_white) Gosu.draw_rect(46 - 2, 0, 2, 46, faint_white, @z)
# white corner right # white corner right
Gosu.draw_rect(@field_size - 46, 46 - 2, 46, 2, 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) Gosu.draw_rect(@field_size - 46, 0, 2, 46, faint_white, @z)
# cross bars # cross bars
bar_gray = Gosu::Color.rgb(50, 50, 50) bar_gray = Gosu::Color.rgb(50, 50, 50)
# MAIN # MAIN
Gosu.draw_rect(13.75, 48 - 2, @field_size - 13.75 * 2, 1, bar_gray) 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) 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) 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) Gosu.draw_rect(@field_size - 13.75 - 1, 48 - 2, 1, 4, Gosu::Color::BLACK, @z)
# BLUE # BLUE
Gosu.draw_rect(48 - 2, 13.75, 1, 48 - 13.75 - 2, bar_gray) 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) 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) 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) Gosu.draw_rect(48 - 2, 48 - 3, 4, 1, Gosu::Color::BLACK, @z)
# RED # RED
Gosu.draw_rect(@field_size - 48 - 2, 13.75, 1, 48 - 13.75 - 2, bar_gray) 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) 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) 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) Gosu.draw_rect(@field_size - 48 - 2, 48 - 3, 4, 1, Gosu::Color::BLACK, @z)
# Duck Delivery # Duck Delivery
Gosu.draw_circle(2, @field_size - 2, 9, 16, Gosu::Color.rgb(75, 75, 75)) 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)) 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 |y|
7.times do |x| 7.times do |x|
if x.even? 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 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 end
end end
@@ -253,24 +252,95 @@ module TAC
7.times do |y| 7.times do |y|
7.times do |x| 7.times do |x|
if x.even? 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 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 end
end end
Gosu.draw_rect(0, 60 - 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) Gosu.draw_rect(0, 108 - 1, 2, 2, @soft_orange, @z)
Gosu.draw_rect(@field_size - 2, 60 - 1, 2, 2, soft_orange) 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) 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 end
def draw_tile_box(color) def draw_tile_box(color)
Gosu.draw_rect(0, 0, 24, 2, color) Gosu.draw_rect(0, 0, 24, 2, color, @z)
Gosu.draw_rect(22, 2, 2, 22, color) Gosu.draw_rect(22, 2, 2, 22, color, @z)
Gosu.draw_rect(0, 22, 22, 2, color) Gosu.draw_rect(0, 22, 22, 2, color, @z)
Gosu.draw_rect(0, 2, 2, 22, color) Gosu.draw_rect(0, 2, 2, 22, color, @z)
end end
def update def update

View File

@@ -1,44 +1,54 @@
module TAC module TAC
class Simulator class Simulator
class Robot class Robot
attr_accessor :position, :angle FONT = Gosu::Font.new(11)
attr_reader :alliance, :width, :depth
def initialize(alliance:, width:, depth:) attr_accessor :position, :angle, :comment
attr_reader :alliance, :width, :depth, :z
def initialize(alliance:, width:, depth:, container:)
@alliance = alliance @alliance = alliance
@width, @depth = width, depth @width = width
@depth = depth
@container = container
@position = CyberarmEngine::Vector.new @position = CyberarmEngine::Vector.new
@angle = 0 @angle = 0
@z = @container.z + 1
@queue = [] @queue = []
@unit = :ticks @unit = :ticks
@ticks_per_revolution = 240 @ticks_per_revolution = 240
@gear_ratio = 1 @gear_ratio = 1
@comment = ""
end end
def draw def draw
Gosu.translate(@width / 2, @depth / 2) do Gosu.translate(@width / 2, @depth / 2) do
Gosu.rotate(@angle, @position.x, @position.y) 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, @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)) 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 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 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 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 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 end
FONT.draw_text(@comment, 2.2, 2.2, @z, 1, 1, Gosu::Color::BLACK)
FONT.draw_text(@comment, 2, 2, @z)
end end
end end
def update(dt) def update(dt)
@angle %= 360.0 @angle %= 360.0
if state = @queue.first if (state = @queue.first)
state.update(dt) state.update(dt)
if state.complete? if state.complete?
@@ -87,6 +97,14 @@ module TAC
@queue << Turn.new(robot: self, relative_angle: relative_angle, power: power) @queue << Turn.new(robot: self, relative_angle: relative_angle, power: power)
end 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 def speed
@ticks_per_revolution / @gear_ratio @ticks_per_revolution / @gear_ratio
end end
@@ -130,9 +148,10 @@ class State
def draw def draw
Gosu.draw_line( Gosu.draw_line(
@robot.position.x + @robot.width / 2, @robot.position.y + @robot.depth / 2, TAC::Palette::TIMECRAFTERS_TERTIARY, @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 end
def update(dt) def update(dt)
@@ -176,9 +195,10 @@ class State
def draw def draw
Gosu.draw_line( Gosu.draw_line(
@robot.position.x + @robot.width / 2, @robot.position.y + @robot.depth / 2, TAC::Palette::TIMECRAFTERS_TERTIARY, @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 end
def update(dt) def update(dt)
@@ -230,7 +250,8 @@ class State
fraction, fraction,
360, 360,
1, 1,
TAC::Palette::TIMECRAFTERS_TERTIARY TAC::Palette::TIMECRAFTERS_TERTIARY,
@robot.z
) )
end 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), @robot.position.y + @robot.depth / 2 + Gosu.offset_y(@target_angle, @robot.width > @robot.depth ? @robot.width : @robot.depth),
1, 1,
9, 9,
Gosu::Color::RED Gosu::Color::RED,
@robot.z
) )
# 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)
end end
@@ -259,6 +281,58 @@ class State
@last_angle = @robot.angle @last_angle = @robot.angle
end end
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 end
end end

View File

@@ -8,7 +8,7 @@ module TAC
@field_container = field_container @field_container = field_container
@robots = [] @robots = []
@field = Field.new(simulation: self, season: :freight_frenzy, container: @field_container) @field = Field.new(simulation: self, season: :power_play, container: @field_container)
@show_paths = false @show_paths = false
@last_milliseconds = Gosu.milliseconds @last_milliseconds = Gosu.milliseconds
@@ -29,7 +29,7 @@ module TAC
def update def update
@accumulator += (Gosu.milliseconds - @last_milliseconds) / 1000.0 @accumulator += (Gosu.milliseconds - @last_milliseconds) / 1000.0
while(@accumulator > @simulation_step) while @accumulator > @simulation_step
@field.update @field.update
@robots.each { |robot| robot.update(@simulation_step) } @robots.each { |robot| robot.update(@simulation_step) }
@@ -41,7 +41,7 @@ module TAC
end end
def create_robot(alliance:, width:, depth:) 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 @robots << robot
return robot return robot

View File

@@ -2,16 +2,18 @@ module TAC
class States class States
class Boot < CyberarmEngine::GuiState class Boot < CyberarmEngine::GuiState
def setup def setup
window.show_cursor = true
stack width: 1.0, height: 1.0 do 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] background [TAC::Palette::TIMECRAFTERS_PRIMARY, TAC::Palette::TIMECRAFTERS_SECONDARY, TAC::Palette::TIMECRAFTERS_TERTIARY, TAC::Palette::TIMECRAFTERS_PRIMARY]
end end
@title_font = CyberarmEngine::Text.new(TAC::NAME, z: 100, size: 72, border: true, border_size: 3, font: THEME[:Label][:font]) @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::ROOT_PATH}/media/logo.png") @logo = Gosu::Image.new("#{TAC::ROOT_PATH}/media/logo.png")
@title_animator = CyberarmEngine::Animator.new(start_time: 0, duration: 750, from: 0.0, to: 1.0, tween: :swing_from_to) @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: 750, duration: 1_000, from: 0.0, to: 1.0, tween: :swing_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: 2_250, duration: 750, from: 0, to: 255, tween: :ease_out) @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) @transition_color = Gosu::Color.new(0x00_111111)
@next_state = Editor @next_state = Editor
@@ -28,15 +30,20 @@ module TAC
def update def update
super super
request_repaint
@title_font.x = window.width / 2 - @title_font.width / 2 @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 @title_font.y = (window.height / 2 - (@logo.height / 2 + @title_font.height)) * @title_animator.transition
@transition_color.alpha = @transition_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 end
def button_up(id) def button_down(id)
super super
push_state(@next_state) push_state(@next_state)

View File

@@ -3,9 +3,13 @@ class Editor < CyberarmEngine::GuiState
attr_reader :header_bar, :header_bar_label, :navigation, :content, :menu_bar, :status_bar, :body attr_reader :header_bar, :header_bar_label, :navigation, :content, :menu_bar, :status_bar, :body
def setup def setup
window.show_cursor = true
@window_width = 0 @window_width = 0
@window_height = 0 @window_height = 0
@last_tacnet_status = nil
@pages = {} @pages = {}
@page = nil @page = nil
@@ -32,9 +36,9 @@ class Editor < CyberarmEngine::GuiState
@header_bar = flow(width: 1.0, height: 36) do @header_bar = flow(width: 1.0, height: 36) do
background 0xff_006000 background 0xff_006000
@header_bar_label = label TAC::NAME, width: 1.0, text_align: :center, text_size: 32 @header_bar_label = label 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 @window_controls = flow(width: 36 * 3, height: 1.0) do
button get_image("#{TAC::ROOT_PATH}/media/icons/minus.png"), tip: "Minimize", image_height: 1.0 do button get_image("#{TAC::ROOT_PATH}/media/icons/minus.png"), tip: "Minimize", image_height: 1.0 do
window.minimize if window.respond_to?(:minimize) window.minimize if window.respond_to?(:minimize)
end end
@@ -65,10 +69,6 @@ class Editor < CyberarmEngine::GuiState
page(TAC::Pages::TACNET) page(TAC::Pages::TACNET)
end 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::ROOT_PATH}/media/icons/gear.png"), margin: 4, tip: "Configurations", image_width: 1.0 do
page(TAC::Pages::Configurations) page(TAC::Pages::Configurations)
end end
@@ -81,6 +81,15 @@ class Editor < CyberarmEngine::GuiState
page(TAC::Pages::Search) page(TAC::Pages::Search)
end end
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::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/joystickLeft.png"), margin: 4, tip: "Field Planner", image_width: 1.0 do button get_image("#{TAC::ROOT_PATH}/media/icons/joystickLeft.png"), margin: 4, tip: "Field Planner", image_width: 1.0 do
page(TAC::Pages::FieldPlanner) page(TAC::Pages::FieldPlanner)
end end
@@ -88,6 +97,14 @@ class Editor < CyberarmEngine::GuiState
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::ROOT_PATH}/media/icons/massiveMultiplayer.png"), margin: 4, tip: "Drive Team Rotation Generator", image_width: 1.0 do
page(TAC::Pages::DriveTeamRotationGenerator) page(TAC::Pages::DriveTeamRotationGenerator)
end end
button get_image("#{TAC::ROOT_PATH}/media/icons/custom_stopWatch.png"), margin: 4, tip: "Game Clock", image_width: 1.0 do
page(TAC::Pages::GameClock)
end
button get_image("#{TAC::ROOT_PATH}/media/icons/power.png"), margin: 4, tip: "Exit", image_width: 1.0, **TAC::THEME_DANGER_BUTTON do
window.close
end
end end
@content = stack(width: window.width - @navigation.style.width, height: 1.0) do @content = stack(width: window.width - @navigation.style.width, height: 1.0) do
@@ -113,9 +130,9 @@ class Editor < CyberarmEngine::GuiState
end end
def draw def draw
super @page&.draw
@page.draw if @page super
end end
def update def update
@@ -123,15 +140,35 @@ class Editor < CyberarmEngine::GuiState
@page.update if @page @page.update if @page
if @last_tacnet_status != window.backend.tacnet.status
@last_tacnet_status = window.backend.tacnet.status
case window.backend.tacnet.status case window.backend.tacnet.status
when :not_connected when :not_connected
@tacnet_button.style.color = Gosu::Color::WHITE @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 when :connecting
@tacnet_button.style.color = TAC::Palette::TACNET_CONNECTING @tacnet_button.style.color = TAC::Palette::TACNET_CONNECTING
when :connected @header_bar.style.background = TAC::Palette::TACNET_CONNECTING
@tacnet_button.style.color = TAC::Palette::TACNET_CONNECTED
when :connection_error when :connection_error
@tacnet_button.style.color = TAC::Palette::TACNET_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 end
window.width = Gosu.available_width / 2 if window.width < Gosu.available_width / 2 window.width = Gosu.available_width / 2 if window.width < Gosu.available_width / 2

View File

@@ -1,6 +1,6 @@
module TAC module TAC
class TACNET class TACNET
DEFAULT_HOSTNAME = "192.168.49.1" DEFAULT_HOSTNAME = "192.168.49.1".freeze
DEFAULT_PORT = 8962 DEFAULT_PORT = 8962
SYNC_INTERVAL = 250 # ms SYNC_INTERVAL = 250 # ms
@@ -30,7 +30,7 @@ module TAC
end end
def full_status def full_status
_status = status.to_s.split("_").map { |c| c.capitalize }.join(" ") _status = status.to_s.split("_").map(&:capitalize).join(" ")
if connected? if connected?
net_stats = "" net_stats = ""
@@ -42,7 +42,7 @@ module TAC
"<b>Status:</b> #{_status}\n\n#{net_stats}" "<b>Status:</b> #{_status}\n\n#{net_stats}"
elsif @connection&.client && @connection.client.socket_error? 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 else
"<b>Status:</b> #{_status}" "<b>Status:</b> #{_status}"
end end
@@ -53,11 +53,11 @@ module TAC
end end
def close def close
if connected? return unless connected?
@connection.close @connection.close
@connection = nil @connection = nil
end end
end
def client def client
@connection.client @connection.client

View File

@@ -8,7 +8,9 @@ module TAC
attr_reader :uuid, :read_queue, :write_queue, :socket, attr_reader :uuid, :read_queue, :write_queue, :socket,
:packets_sent, :packets_received, :packets_sent, :packets_received,
:data_sent, :data_received :data_sent, :data_received
attr_accessor :sync_interval, :last_socket_error, :socket_error attr_accessor :sync_interval, :last_socket_error, :socket_error
def initialize def initialize
@uuid = SecureRandom.uuid @uuid = SecureRandom.uuid
@read_queue = [] @read_queue = []
@@ -20,8 +22,11 @@ module TAC
@socket_error = false @socket_error = false
@bound = false @bound = false
@packets_sent, @packets_received = 0, 0 @packets_sent = 0
@data_sent, @data_received = 0, 0 @packets_received = 0
@data_sent = 0
@data_received = 0
end end
def uuid=(id) def uuid=(id)
@@ -39,18 +44,16 @@ module TAC
Thread.new do Thread.new do
while connected? while connected?
# Read from socket # Read from socket
while message_in = read while (message_in = read)
if message_in.empty? break if message_in.empty?
break
else log.i(TAG, "Read: #{message_in}")
log.i(TAG, "Read: " + message_in)
@read_queue << message_in @read_queue << message_in
@packets_received += 1 @packets_received += 1
@data_received += message_in.length @data_received += message_in.length
end end
end
sleep @sync_interval / 1000.0 sleep @sync_interval / 1000.0
end end
@@ -59,12 +62,12 @@ module TAC
Thread.new do Thread.new do
while connected? while connected?
# Write to socket # Write to socket
while message_out = @write_queue.shift while (message_out = @write_queue.shift)
write(message_out) write(message_out)
@packets_sent += 1 @packets_sent += 1
@data_sent += message_out.to_s.length @data_sent += message_out.to_s.length
log.i(TAG, "Write: " + message_out.to_s) log.i(TAG, "Write: #{message_out}")
end end
sleep @sync_interval / 1000.0 sleep @sync_interval / 1000.0
@@ -82,7 +85,7 @@ module TAC
while message while message
puts(message) puts(message)
log.i(TAG, "Writing to Queue: " + message) log.i(TAG, "Writing to Queue: #{message}")
message = gets message = gets
end end
@@ -101,32 +104,29 @@ module TAC
end end
def closed? def closed?
@socket.closed? if @socket @socket&.closed?
end end
def write(message) def write(message)
begin @socket.puts("#{message}#{PACKET_TAIL}") if connected?
@socket.puts("#{message}#{PACKET_TAIL}")
rescue => error rescue => error
@last_socket_error = error @last_socket_error = error
@socket_error = true @socket_error = true
log.e(TAG, error.message) log.e(TAG, error.message)
close close
end end
end
def read def read
begin @socket&.gets&.strip if connected?
message = @socket.gets
rescue => error rescue => error
@last_socket_error = error @last_socket_error = error
@socket_error = true @socket_error = true
message = "" log.e(TAG, error.message)
end
close
return message.strip
end end
def puts(message) def puts(message)
@@ -138,11 +138,11 @@ module TAC
end end
def encode(message) def encode(message)
return message message
end end
def decode(blob) def decode(blob)
return blob blob
end end
def flush def flush
@@ -151,7 +151,8 @@ module TAC
def close(reason = nil) def close(reason = nil)
write(reason) if reason write(reason) if reason
@socket.close if @socket
@socket&.close
end end
end end
end end

View File

@@ -47,7 +47,7 @@ module TAC
def handle_handshake(packet) def handle_handshake(packet)
if @host_is_a_connection if @host_is_a_connection
$window.backend.tacnet.client.uuid = packet.body CyberarmEngine::Window.instance.backend.tacnet.client.uuid = packet.body
end end
end end
@@ -59,7 +59,7 @@ module TAC
def handle_error(packet) def handle_error(packet)
if @host_is_a_connection if @host_is_a_connection
title, message = packet.body.split(Packet::PROTOCOL_SEPERATOR, 2) 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 else
log.e(TAG, "Remote error: #{title}: #{message}") log.e(TAG, "Remote error: #{title}: #{message}")
end end
@@ -73,11 +73,11 @@ module TAC
if data.is_a?(Hash) && data.dig(:config, :spec_version) == TAC::CONFIG_SPEC_VERSION 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 } File.open("#{TAC::CONFIGS_PATH}/#{config_name}.json", "w") { |f| f.write json }
if $window.backend.config&.name == config_name if CyberarmEngine::Window.instance.backend.config&.name == config_name
$window.backend.load_config(config_name) CyberarmEngine::Window.instance.backend.load_config(config_name)
end end
else 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 end
rescue JSON::ParserError => e rescue JSON::ParserError => e
@@ -97,7 +97,7 @@ module TAC
end end
if @host_is_a_connection if @host_is_a_connection
$window.backend.tacnet.puts(pkt) CyberarmEngine::Window.instance.backend.tacnet.puts(pkt)
else else
$server.active_client.puts(pkt) $server.active_client.puts(pkt)
end end
@@ -119,20 +119,20 @@ module TAC
config = Config.new(name) config = Config.new(name)
if config.configuration.revision < revision 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 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 end
else else
$window.backend.tacnet.puts( PacketHandler.packet_download_config(name) ) CyberarmEngine::Window.instance.backend.tacnet.puts( PacketHandler.packet_download_config(name) )
end end
end end
_diff.each do |name| _diff.each do |name|
config = Config.new(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 end
else else
if $server.active_client && $server.active_client.connected? if $server.active_client && $server.active_client.connected?
@@ -144,43 +144,43 @@ module TAC
def handle_select_config(packet) def handle_select_config(packet)
config_name = packet.body config_name = packet.body
$window.backend.settings.config = config_name CyberarmEngine::Window.instance.backend.settings.config = config_name
$window.backend.save_settings CyberarmEngine::Window.instance.backend.save_settings
$window.backend.load_config(config_name) CyberarmEngine::Window.instance.backend.load_config(config_name)
end end
def handle_add_config(packet) def handle_add_config(packet)
config_name = packet.body 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 unless @host_is_a_connection
if $server.active_client&.connected? 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.")) $server.active_client.puts(PacketHandler.packet_error("Config already exists!", "A config with the name #{config_name} already exists over here."))
end end
end end
else else
$window.backend.write_new_config(config_name) CyberarmEngine::Window.instance.backend.write_new_config(config_name)
end end
end end
def handle_update_config(packet) def handle_update_config(packet)
old_config_name, new_config_name = packet.body.split(PROTOCOL_SEPERATOR, 2) 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 unless @host_is_a_connection
if $server.active_client&.connected? 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.")) $server.active_client.puts(PacketHandler.packet_error("Config already exists!", "A config with the name #{config_name} already exists over here."))
end end
end end
else 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
end end
def handle_delete_config(packet) def handle_delete_config(packet)
config_name = packet.body config_name = packet.body
$window.backend.delete_config(config_name) CyberarmEngine::Window.instance.backend.delete_config(config_name)
end end
def self.packet_handshake(client_uuid) def self.packet_handshake(client_uuid)

View File

@@ -1,12 +1,15 @@
module TAC module TAC
THEME_FONT = "#{TAC::ROOT_PATH}/media/fonts/DejaVuSansCondensed.ttf" THEME_FONT = "#{TAC::ROOT_PATH}/media/fonts/NotoSans-Bold.ttf"
THEME_BOLD_FONT = "#{TAC::ROOT_PATH}/media/fonts/NotoSans-Black.ttf"
THEME = { THEME = {
Label: { TextBlock: {
text_static: true,
font: THEME_FONT, font: THEME_FONT,
text_size: 22, text_size: 22,
color: Gosu::Color.new(0xee_ffffff), color: Gosu::Color.new(0xee_ffffff),
}, },
Button: { Button: {
font: THEME_BOLD_FONT,
text_size: 22, text_size: 22,
background: TAC::Palette::TIMECRAFTERS_PRIMARY, background: TAC::Palette::TIMECRAFTERS_PRIMARY,
border_thickness: 1, border_thickness: 1,
@@ -20,6 +23,7 @@ module TAC
}, },
EditLine: { EditLine: {
caret_color: Gosu::Color.new(0xff_88ef90), caret_color: Gosu::Color.new(0xff_88ef90),
font: THEME_FONT
}, },
ToggleButton: { ToggleButton: {
width: 18, width: 18,
@@ -31,10 +35,10 @@ module TAC
color: Gosu::Color.new(0xff_ffffff), color: Gosu::Color.new(0xff_ffffff),
background: Gosu::Color.new(0xff_800000), background: Gosu::Color.new(0xff_800000),
hover: { hover: {
background: Gosu::Color.new(0xff_600000), background: Gosu::Color.new(0xff_c00000),
}, },
active: { 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_top: THEME_ITEM_PADDING,
padding_bottom: 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_EVEN_COLOR = Gosu::Color.new(0xff_202020)
THEME_ODD_COLOR = Gosu::Color.new(0xff_606060) THEME_ODD_COLOR = Gosu::Color.new(0xff_606060)
THEME_CONTENT_BACKGROUND = Gosu::Color.new(0x88_007f3f) THEME_CONTENT_BACKGROUND = Gosu::Color.new(0x88_007f3f)

View File

@@ -1,5 +1,5 @@
module TAC module TAC
NAME = "TimeCrafters Configuration Tool" NAME = "TimeCrafters Configuration Tool"
VERSION = "0.4.2" VERSION = "0.6.0"
RELEASE_NAME = "Beta" RELEASE_NAME = "Beta"
end end

View File

@@ -1,6 +1,7 @@
module TAC module TAC
class Window < CyberarmEngine::Window class Window < CyberarmEngine::Window
attr_reader :backend, :notification_manager attr_reader :backend, :notification_manager
def initialize(**args) def initialize(**args)
super(**args) super(**args)
@@ -8,8 +9,14 @@ module TAC
@backend = Backend.new @backend = Backend.new
@notification_manager = GosuNotifications::NotificationManager.new(window: self, edge: :bottom) @notification_manager = GosuNotifications::NotificationManager.new(window: self, edge: :bottom)
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) push_state(TAC::States::Boot)
end end
end
def draw def draw
super super
@@ -24,8 +31,8 @@ module TAC
@notification_manager.update @notification_manager.update
end end
def needs_cursor? def needs_redraw?
true states.any?(&:needs_repaint?) || @notification_manager.instance_variable_get(:@drivers).size.positive?
end end
def toast(title, message = nil) def toast(title, message = nil)

BIN
media/background.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

6244
media/background.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 231 KiB

View File

@@ -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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

93
media/fonts/OFL.txt Normal file
View 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View 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
View File

BIN
media/openclipart_ducky.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

0
media/particles/.gitkeep Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

0
media/sounds/.gitkeep Normal file
View File

View File

@@ -29,6 +29,7 @@ require_relative "lib/pages/presets"
require_relative "lib/pages/search" require_relative "lib/pages/search"
require_relative "lib/pages/field_planner" require_relative "lib/pages/field_planner"
require_relative "lib/pages/drive_team_rotation_generator" require_relative "lib/pages/drive_team_rotation_generator"
require_relative "lib/pages/game_clock"
require_relative "lib/simulator/robot" require_relative "lib/simulator/robot"
require_relative "lib/simulator/field" require_relative "lib/simulator/field"
require_relative "lib/simulator/simulation" require_relative "lib/simulator/simulation"
@@ -50,6 +51,24 @@ require_relative "lib/tacnet/client"
require_relative "lib/tacnet/connection" require_relative "lib/tacnet/connection"
require_relative "lib/tacnet/server" 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 # Thread.abort_on_exception = true
USE_REDESIGN = ARGV.include?("--redesign") USE_REDESIGN = ARGV.include?("--redesign")
@@ -58,3 +77,5 @@ BORDERLESS = ARGV.include?("--borderless")
if not defined?(Ocra) if not defined?(Ocra)
TAC::Window.new(width: (Gosu.screen_width * 0.8).round, height: (Gosu.screen_height * 0.8).round, resizable: true, borderless: BORDERLESS).show TAC::Window.new(width: (Gosu.screen_width * 0.8).round, height: (Gosu.screen_height * 0.8).round, resizable: true, borderless: BORDERLESS).show
end end
Process.wait($clock_pid) if $clock_pid