You've already forked factorio-blueprint-editor
mirror of
https://github.com/teoxoy/factorio-blueprint-editor.git
synced 2025-11-23 22:15:01 +02:00
first commit
This commit is contained in:
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
.wwp-cache
|
||||
dist
|
||||
*.log
|
||||
parser/temp
|
||||
parser/temp.*
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Tanasoaia Teodor Andrei
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
26
README.md
Normal file
26
README.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# factorio-blueprint-editor
|
||||
|
||||
A [Factorio](https://www.factorio.com/) blueprint editor and renderer webapp
|
||||
|
||||
# TODO:
|
||||
- ctrl + click to add modules
|
||||
- implement more entity settings (filters, conditions)
|
||||
- edit bp label and icons
|
||||
- put entityInfo icon backgrounds on another layer
|
||||
- pipe window
|
||||
- implement the other cursorBoxes
|
||||
- overlay for turrets
|
||||
- show electricity-icon-unplugged for entities that are not connected to a power pole
|
||||
- bp manager (manage bps and books in an editor + placement of new blueprint in an allready loaded bp)
|
||||
- show bp inputs (show icons for belts)
|
||||
- throughput calculator/bp analyzer/bottleneck detector
|
||||
- highlight lone underground pipes/belts
|
||||
- train-stop station name
|
||||
- tiles support
|
||||
- poles range, wires and rotations
|
||||
- rotate bp
|
||||
- implement circuit_wire_max_distance with visualization ((x - center_x)^2 + (y - center_y)^2 <= radius^2)
|
||||
- rail endings
|
||||
- rail custom bounding box
|
||||
- rail rotations
|
||||
- belt endings
|
||||
14985
package-lock.json
generated
Normal file
14985
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
53
package.json
Normal file
53
package.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "factorio-blueprint-editor",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "webpack-dev-server --config webpack.dev.js --host 0.0.0.0 --open --useLocalIp",
|
||||
"build": "webpack --config webpack.prod.js"
|
||||
},
|
||||
"author": "Teoxoy",
|
||||
"license": "MIT",
|
||||
"browserslist": [
|
||||
"> 5%",
|
||||
"last 3 versions"
|
||||
],
|
||||
"dependencies": {
|
||||
"@pixi/filter-adjustment": "^2.5.0",
|
||||
"ajv": "^6.4.0",
|
||||
"gown": "^0.1.6",
|
||||
"immutable": "^3.8.2",
|
||||
"keyboardjs": "^2.4.1",
|
||||
"normalize.css": "^8.0.0",
|
||||
"pixi.js": "^4.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.0.0-beta.42",
|
||||
"@babel/preset-env": "^7.0.0-beta.42",
|
||||
"@types/keyboardjs": "^2.2.31",
|
||||
"@types/pixi.js": "^4.7.2",
|
||||
"babel-loader": "^8.0.0-beta.2",
|
||||
"clean-webpack-plugin": "^0.1.19",
|
||||
"copy-webpack-plugin": "^4.5.1",
|
||||
"css-loader": "^0.28.11",
|
||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||
"fork-ts-checker-webpack-plugin": "^0.4.1",
|
||||
"fs-extra": "^5.0.0",
|
||||
"html-webpack-plugin": "3.1.0",
|
||||
"jimp": "^0.2.28",
|
||||
"node-sprite-generator": "^0.10.2",
|
||||
"optimize-css-assets-webpack-plugin": "^4.0.0",
|
||||
"ts-loader": "^4.1.0",
|
||||
"tslint": "^5.9.1",
|
||||
"typescript": "^2.8.1",
|
||||
"webapp-webpack-plugin": "^1.3.1",
|
||||
"webpack": "^4.3.0",
|
||||
"webpack-bundle-analyzer": "^2.11.1",
|
||||
"webpack-cli": "^2.0.13",
|
||||
"webpack-closure-compiler": "^2.1.6",
|
||||
"webpack-dev-server": "^3.1.1",
|
||||
"webpack-merge": "^4.1.2",
|
||||
"webpack-visualizer-plugin": "^0.1.11"
|
||||
}
|
||||
}
|
||||
598
parser/defines.lua
Normal file
598
parser/defines.lua
Normal file
@@ -0,0 +1,598 @@
|
||||
defines = {
|
||||
alert_type = {
|
||||
custom = 6,
|
||||
entity_destroyed = 0,
|
||||
entity_under_attack = 1,
|
||||
no_material_for_construction = 3,
|
||||
no_storage = 7,
|
||||
not_enough_construction_robots = 2,
|
||||
not_enough_repair_packs = 4,
|
||||
turret_fire = 5
|
||||
},
|
||||
chain_signal_state = {
|
||||
all_open = 1,
|
||||
none = 0,
|
||||
none_open = 3,
|
||||
partially_open = 2
|
||||
},
|
||||
chunk_generated_status = {
|
||||
basic_tiles = 20,
|
||||
corrected_tiles = 30,
|
||||
custom_tiles = 10,
|
||||
entities = 50,
|
||||
nothing = 0,
|
||||
tiles = 40
|
||||
},
|
||||
circuit_condition_index = {
|
||||
arithmetic_combinator = 1,
|
||||
constant_combinator = 1,
|
||||
decider_combinator = 1,
|
||||
inserter_circuit = 1,
|
||||
inserter_logistic = 2,
|
||||
lamp = 1,
|
||||
offshore_pump = 1,
|
||||
pump = 1
|
||||
},
|
||||
circuit_connector_id = {
|
||||
accumulator = 1,
|
||||
combinator_input = 1,
|
||||
combinator_output = 2,
|
||||
constant_combinator = 1,
|
||||
container = 1,
|
||||
electric_pole = 1,
|
||||
inserter = 1,
|
||||
lamp = 1,
|
||||
offshore_pump = 1,
|
||||
programmable_speaker = 1,
|
||||
pump = 1,
|
||||
rail_chain_signal = 1,
|
||||
rail_signal = 1,
|
||||
roboport = 1,
|
||||
storage_tank = 1,
|
||||
wall = 1
|
||||
},
|
||||
command = {
|
||||
attack = 1,
|
||||
attack_area = 5,
|
||||
build_base = 7,
|
||||
compound = 3,
|
||||
flee = 8,
|
||||
go_to_location = 2,
|
||||
group = 4,
|
||||
wander = 6
|
||||
},
|
||||
compound_command = {
|
||||
logical_and = 0,
|
||||
logical_or = 1,
|
||||
return_last = 2
|
||||
},
|
||||
control_behavior = {
|
||||
inserter = {
|
||||
circuit_mode_of_operation = {
|
||||
enable_disable = 0,
|
||||
none = 3,
|
||||
read_hand_contents = 2,
|
||||
set_filters = 1,
|
||||
set_stack_size = 4
|
||||
},
|
||||
hand_read_mode = {
|
||||
hold = 1,
|
||||
pulse = 0
|
||||
}
|
||||
},
|
||||
lamp = {
|
||||
circuit_mode_of_operation = {
|
||||
use_colors = 0
|
||||
}
|
||||
},
|
||||
logistic_container = {
|
||||
circuit_mode_of_operation = {
|
||||
send_contents = 0,
|
||||
set_requests = 1
|
||||
}
|
||||
},
|
||||
mining_drill = {
|
||||
resource_read_mode = {
|
||||
entire_patch = 1,
|
||||
this_miner = 0
|
||||
}
|
||||
},
|
||||
roboport = {
|
||||
circuit_mode_of_operation = {
|
||||
read_logistics = 0,
|
||||
read_robot_stats = 1
|
||||
}
|
||||
},
|
||||
train_stop = {
|
||||
circuit_mode_of_operation = {
|
||||
enable_disable = 0,
|
||||
read_from_train = 2,
|
||||
read_stopped_train = 3,
|
||||
send_to_train = 1
|
||||
}
|
||||
},
|
||||
transport_belt = {
|
||||
content_read_mode = {
|
||||
hold = 1,
|
||||
pulse = 0
|
||||
}
|
||||
},
|
||||
type = {
|
||||
accumulator = 13,
|
||||
arithmetic_combinator = 10,
|
||||
constant_combinator = 11,
|
||||
container = 1,
|
||||
decider_combinator = 9,
|
||||
generic_on_off = 2,
|
||||
inserter = 3,
|
||||
lamp = 4,
|
||||
logistic_container = 5,
|
||||
mining_drill = 16,
|
||||
programmable_speaker = 17,
|
||||
rail_chain_signal = 18,
|
||||
rail_signal = 14,
|
||||
roboport = 6,
|
||||
storage_tank = 7,
|
||||
train_stop = 8,
|
||||
transport_belt = 12,
|
||||
wall = 15
|
||||
}
|
||||
},
|
||||
controllers = {
|
||||
character = 1,
|
||||
ghost = 0,
|
||||
god = 2
|
||||
},
|
||||
deconstruction_item = {
|
||||
entity_filter_mode = {
|
||||
blacklist = 1,
|
||||
whitelist = 0
|
||||
},
|
||||
tile_filter_mode = {
|
||||
blacklist = 1,
|
||||
whitelist = 0
|
||||
},
|
||||
tile_selection_mode = {
|
||||
always = 1,
|
||||
never = 2,
|
||||
normal = 0,
|
||||
only = 3
|
||||
}
|
||||
},
|
||||
difficulty = {
|
||||
easy = 0,
|
||||
hard = 2,
|
||||
normal = 1
|
||||
},
|
||||
difficulty_settings = {
|
||||
recipe_difficulty = {
|
||||
expensive = 1,
|
||||
normal = 0
|
||||
},
|
||||
technology_difficulty = {
|
||||
expensive = 1,
|
||||
normal = 0
|
||||
}
|
||||
},
|
||||
direction = {
|
||||
east = 2,
|
||||
north = 0,
|
||||
northeast = 1,
|
||||
northwest = 7,
|
||||
south = 4,
|
||||
southeast = 3,
|
||||
southwest = 5,
|
||||
west = 6
|
||||
},
|
||||
distraction = {
|
||||
by_anything = 3,
|
||||
by_damage = 4,
|
||||
by_enemy = 1,
|
||||
none = 0
|
||||
},
|
||||
events = {
|
||||
on_biter_base_built = 55,
|
||||
on_built_entity = 6,
|
||||
on_canceled_deconstruction = 21,
|
||||
on_character_corpse_expired = 90,
|
||||
on_chunk_charted = 98,
|
||||
on_chunk_generated = 12,
|
||||
on_combat_robot_expired = 80,
|
||||
on_console_chat = 71,
|
||||
on_console_command = 72,
|
||||
on_difficulty_settings_changed = 60,
|
||||
on_entity_damaged = 97,
|
||||
on_entity_died = 4,
|
||||
on_entity_renamed = 57,
|
||||
on_entity_settings_pasted = 31,
|
||||
on_force_created = 27,
|
||||
on_forces_merging = 28,
|
||||
on_gui_checked_state_changed = 3,
|
||||
on_gui_click = 1,
|
||||
on_gui_closed = 84,
|
||||
on_gui_elem_changed = 67,
|
||||
on_gui_opened = 83,
|
||||
on_gui_selection_state_changed = 58,
|
||||
on_gui_text_changed = 2,
|
||||
on_gui_value_changed = 85,
|
||||
on_marked_for_deconstruction = 20,
|
||||
on_market_item_purchased = 53,
|
||||
on_mod_item_opened = 82,
|
||||
on_picked_up_item = 5,
|
||||
on_player_alt_selected_area = 50,
|
||||
on_player_ammo_inventory_changed = 36,
|
||||
on_player_armor_inventory_changed = 35,
|
||||
on_player_built_tile = 45,
|
||||
on_player_cancelled_crafting = 96,
|
||||
on_player_changed_force = 56,
|
||||
on_player_changed_position = 81,
|
||||
on_player_changed_surface = 51,
|
||||
on_player_cheat_mode_disabled = 89,
|
||||
on_player_cheat_mode_enabled = 88,
|
||||
on_player_configured_blueprint = 70,
|
||||
on_player_crafted_item = 13,
|
||||
on_player_created = 24,
|
||||
on_player_cursor_stack_changed = 29,
|
||||
on_player_deconstructed_area = 69,
|
||||
on_player_demoted = 76,
|
||||
on_player_died = 41,
|
||||
on_player_display_resolution_changed = 93,
|
||||
on_player_display_scale_changed = 94,
|
||||
on_player_driving_changed_state = 26,
|
||||
on_player_dropped_item = 54,
|
||||
on_player_gun_inventory_changed = 37,
|
||||
on_player_joined_game = 43,
|
||||
on_player_left_game = 44,
|
||||
on_player_main_inventory_changed = 32,
|
||||
on_player_mined_entity = 65,
|
||||
on_player_mined_item = 8,
|
||||
on_player_mined_tile = 46,
|
||||
on_player_muted = 86,
|
||||
on_player_pipette = 92,
|
||||
on_player_placed_equipment = 38,
|
||||
on_player_promoted = 75,
|
||||
on_player_quickbar_inventory_changed = 33,
|
||||
on_player_removed = 73,
|
||||
on_player_removed_equipment = 39,
|
||||
on_player_respawned = 42,
|
||||
on_player_rotated_entity = 19,
|
||||
on_player_selected_area = 49,
|
||||
on_player_setup_blueprint = 68,
|
||||
on_player_tool_inventory_changed = 34,
|
||||
on_player_unmuted = 87,
|
||||
on_player_used_capsule = 74,
|
||||
on_pre_entity_settings_pasted = 30,
|
||||
on_pre_ghost_deconstructed = 91,
|
||||
on_pre_player_crafted_item = 95,
|
||||
on_pre_player_died = 40,
|
||||
on_pre_player_mined_item = 11,
|
||||
on_pre_surface_deleted = 63,
|
||||
on_put_item = 9,
|
||||
on_research_finished = 18,
|
||||
on_research_started = 17,
|
||||
on_resource_depleted = 25,
|
||||
on_robot_built_entity = 14,
|
||||
on_robot_built_tile = 47,
|
||||
on_robot_mined = 16,
|
||||
on_robot_mined_entity = 64,
|
||||
on_robot_mined_tile = 48,
|
||||
on_robot_pre_mined = 15,
|
||||
on_rocket_launched = 10,
|
||||
on_runtime_mod_setting_changed = 59,
|
||||
on_sector_scanned = 7,
|
||||
on_selected_entity_changed = 52,
|
||||
on_surface_created = 61,
|
||||
on_surface_deleted = 62,
|
||||
on_tick = 0,
|
||||
on_train_changed_state = 23,
|
||||
on_train_created = 66,
|
||||
on_trigger_created_entity = 22,
|
||||
script_raised_built = 77,
|
||||
script_raised_destroy = 78,
|
||||
script_raised_revive = 79
|
||||
},
|
||||
group_state = {
|
||||
attacking_distraction = 2,
|
||||
attacking_target = 3,
|
||||
finished = 4,
|
||||
gathering = 0,
|
||||
moving = 1
|
||||
},
|
||||
gui_type = {
|
||||
achievement = 8,
|
||||
blueprint_library = 9,
|
||||
bonus = 6,
|
||||
controller = 3,
|
||||
custom = 16,
|
||||
entity = 1,
|
||||
equipment = 10,
|
||||
item = 5,
|
||||
kills = 13,
|
||||
logistic = 11,
|
||||
none = 0,
|
||||
other_player = 12,
|
||||
permissions = 14,
|
||||
production = 4,
|
||||
research = 2,
|
||||
trains = 7,
|
||||
tutorials = 15
|
||||
},
|
||||
input_action = {
|
||||
add_permission_group = 177,
|
||||
alt_select_area = 129,
|
||||
alt_select_blueprint_entities = 94,
|
||||
begin_mining = 2,
|
||||
begin_mining_terrain = 46,
|
||||
build_item = 44,
|
||||
build_rail = 125,
|
||||
build_terrain = 120,
|
||||
cancel_craft = 64,
|
||||
cancel_deconstruct = 114,
|
||||
cancel_new_blueprint = 22,
|
||||
cancel_research = 126,
|
||||
change_active_item_group_for_crafting = 78,
|
||||
change_active_item_group_for_filters = 89,
|
||||
change_active_quick_bar = 17,
|
||||
change_arithmetic_combinator_parameters = 115,
|
||||
change_blueprint_book_record_label = 109,
|
||||
change_decider_combinator_parameters = 116,
|
||||
change_item_label = 124,
|
||||
change_picking_state = 150,
|
||||
change_programmable_speaker_alert_parameters = 118,
|
||||
change_programmable_speaker_circuit_parameters = 119,
|
||||
change_programmable_speaker_parameters = 117,
|
||||
change_riding_state = 47,
|
||||
change_shooting_state = 58,
|
||||
change_single_blueprint_record_label = 100,
|
||||
change_train_stop_station = 77,
|
||||
change_train_wait_condition = 121,
|
||||
change_train_wait_condition_data = 122,
|
||||
clean_cursor_stack = 13,
|
||||
clear_selected_blueprint = 131,
|
||||
clear_selected_deconstruction_item = 132,
|
||||
connect_rolling_stock = 10,
|
||||
copy_entity_settings = 24,
|
||||
craft = 56,
|
||||
create_blueprint_like = 104,
|
||||
cursor_split = 52,
|
||||
cursor_transfer = 51,
|
||||
custom_input = 123,
|
||||
cycle_blueprint_book_backwards = 36,
|
||||
cycle_blueprint_book_forwards = 35,
|
||||
deconstruct = 92,
|
||||
delete_blueprint_record = 103,
|
||||
delete_custom_tag = 175,
|
||||
delete_permission_group = 176,
|
||||
destroy_opened_item = 26,
|
||||
disconnect_rolling_stock = 11,
|
||||
drag_train_schedule = 142,
|
||||
drag_train_wait_condition = 143,
|
||||
drop_blueprint_record = 102,
|
||||
drop_item = 43,
|
||||
drop_to_blueprint_book = 174,
|
||||
edit_custom_tag = 137,
|
||||
edit_permission_group = 138,
|
||||
edit_train_schedule = 76,
|
||||
export_blueprint = 111,
|
||||
fast_entity_split = 164,
|
||||
fast_entity_transfer = 163,
|
||||
grab_blueprint_record = 101,
|
||||
gui_checked_state_changed = 80,
|
||||
gui_click = 73,
|
||||
gui_elem_changed = 140,
|
||||
gui_selection_state_changed = 81,
|
||||
gui_text_changed = 79,
|
||||
gui_value_changed = 82,
|
||||
import_blueprint = 112,
|
||||
import_blueprint_string = 139,
|
||||
inventory_split = 63,
|
||||
inventory_transfer = 54,
|
||||
launch_rocket = 16,
|
||||
market_offer = 75,
|
||||
mod_settings_changed = 135,
|
||||
open_achievements_gui = 33,
|
||||
open_blueprint_library_gui = 18,
|
||||
open_blueprint_record = 98,
|
||||
open_bonus_gui = 31,
|
||||
open_character_gui = 9,
|
||||
open_equipment = 50,
|
||||
open_gui = 7,
|
||||
open_item = 48,
|
||||
open_kills_gui = 20,
|
||||
open_logistic_gui = 41,
|
||||
open_mod_item = 49,
|
||||
open_production_gui = 19,
|
||||
open_technology_gui = 15,
|
||||
open_train_gui = 170,
|
||||
open_train_station_gui = 127,
|
||||
open_trains_gui = 32,
|
||||
open_tutorials_gui = 34,
|
||||
paste_entity_settings = 25,
|
||||
place_equipment = 83,
|
||||
remove_cables = 110,
|
||||
reset_assembling_machine = 14,
|
||||
reverse_rotate_entity = 5,
|
||||
rotate_entity = 4,
|
||||
select_area = 128,
|
||||
select_blueprint_entities = 93,
|
||||
select_entity_slot = 145,
|
||||
select_gun = 156,
|
||||
select_item = 144,
|
||||
select_tile_slot = 146,
|
||||
set_auto_launch_rocket = 158,
|
||||
set_autosort_inventory = 157,
|
||||
set_behavior_mode = 162,
|
||||
set_car_weapons_control = 179,
|
||||
set_circuit_condition = 67,
|
||||
set_circuit_mode_of_operation = 72,
|
||||
set_deconstruction_item_tile_selection_mode = 173,
|
||||
set_deconstruction_item_trees_and_rocks_only = 172,
|
||||
set_entity_color = 171,
|
||||
set_entity_energy_property = 136,
|
||||
set_filter = 65,
|
||||
set_infinity_container_filter_item = 134,
|
||||
set_infinity_container_remove_unfiltered_items = 178,
|
||||
set_inserter_max_stack_size = 169,
|
||||
set_inventory_bar = 88,
|
||||
set_logistic_filter_item = 70,
|
||||
set_logistic_filter_signal = 71,
|
||||
set_logistic_trash_filter_item = 133,
|
||||
set_request_from_buffers = 180,
|
||||
set_research_finished_stops_game = 168,
|
||||
set_signal = 68,
|
||||
set_single_blueprint_record_icon = 97,
|
||||
set_splitter_priority = 149,
|
||||
set_train_stopped = 165,
|
||||
setup_assembling_machine = 59,
|
||||
setup_blueprint = 95,
|
||||
setup_single_blueprint_record = 96,
|
||||
shortcut_quick_bar_transfer = 155,
|
||||
smart_pipette = 61,
|
||||
stack_split = 62,
|
||||
stack_transfer = 53,
|
||||
start_repair = 91,
|
||||
start_research = 69,
|
||||
start_walking = 45,
|
||||
switch_connect_to_logistic_network = 161,
|
||||
switch_constant_combinator_state = 159,
|
||||
switch_power_switch_state = 160,
|
||||
switch_to_rename_stop_gui = 30,
|
||||
take_equipment = 84,
|
||||
toggle_deconstruction_item_entity_filter_mode = 39,
|
||||
toggle_deconstruction_item_tile_filter_mode = 40,
|
||||
toggle_driving = 6,
|
||||
toggle_enable_vehicle_logistics_while_moving = 38,
|
||||
toggle_show_entity_info = 27,
|
||||
use_ability = 85,
|
||||
use_artillery_remote = 87,
|
||||
use_item = 86,
|
||||
wire_dragging = 57,
|
||||
write_to_console = 74
|
||||
},
|
||||
inventory = {
|
||||
assembling_machine_input = 2,
|
||||
assembling_machine_modules = 4,
|
||||
assembling_machine_output = 3,
|
||||
beacon_modules = 1,
|
||||
burnt_result = 6,
|
||||
car_ammo = 3,
|
||||
car_trunk = 2,
|
||||
cargo_wagon = 1,
|
||||
chest = 1,
|
||||
fuel = 1,
|
||||
furnace_modules = 4,
|
||||
furnace_result = 3,
|
||||
furnace_source = 2,
|
||||
god_main = 2,
|
||||
god_quickbar = 1,
|
||||
item_main = 1,
|
||||
lab_input = 2,
|
||||
lab_modules = 3,
|
||||
mining_drill_modules = 2,
|
||||
player_ammo = 4,
|
||||
player_armor = 5,
|
||||
player_guns = 3,
|
||||
player_main = 1,
|
||||
player_quickbar = 2,
|
||||
player_tools = 6,
|
||||
player_trash = 8,
|
||||
player_vehicle = 7,
|
||||
roboport_material = 2,
|
||||
roboport_robot = 1,
|
||||
robot_cargo = 1,
|
||||
robot_repair = 2,
|
||||
rocket_silo_result = 6,
|
||||
rocket_silo_rocket = 5,
|
||||
turret_ammo = 1
|
||||
},
|
||||
logistic_member_index = {
|
||||
character_provider = 2,
|
||||
character_requester = 0,
|
||||
character_storage = 1,
|
||||
generic_on_off_behavior = 0,
|
||||
logistic_container = 0,
|
||||
vehicle_storage = 1
|
||||
},
|
||||
logistic_mode = {
|
||||
active_provider = 1,
|
||||
buffer = 5,
|
||||
none = 0,
|
||||
passive_provider = 4,
|
||||
requester = 3,
|
||||
storage = 2
|
||||
},
|
||||
mouse_button_type = {
|
||||
left = 2,
|
||||
middle = 8,
|
||||
none = 1,
|
||||
right = 4
|
||||
},
|
||||
rail_connection_direction = {
|
||||
left = 0,
|
||||
none = 3,
|
||||
right = 2,
|
||||
straight = 1
|
||||
},
|
||||
rail_direction = {
|
||||
back = 1,
|
||||
front = 0
|
||||
},
|
||||
riding = {
|
||||
acceleration = {
|
||||
accelerating = 1,
|
||||
braking = 2,
|
||||
nothing = 0,
|
||||
reversing = 3
|
||||
},
|
||||
direction = {
|
||||
left = 0,
|
||||
right = 2,
|
||||
straight = 1
|
||||
}
|
||||
},
|
||||
shooting = {
|
||||
not_shooting = 0,
|
||||
shooting_enemies = 1,
|
||||
shooting_selected = 2
|
||||
},
|
||||
signal_state = {
|
||||
closed = 1,
|
||||
open = 0,
|
||||
reserved = 2,
|
||||
reserved_by_circuit_network = 3
|
||||
},
|
||||
train_state = {
|
||||
arrive_signal = 4,
|
||||
arrive_station = 6,
|
||||
manual_control = 9,
|
||||
manual_control_stop = 8,
|
||||
no_path = 3,
|
||||
no_schedule = 2,
|
||||
on_the_path = 0,
|
||||
path_lost = 1,
|
||||
wait_signal = 5,
|
||||
wait_station = 7
|
||||
},
|
||||
transport_line = {
|
||||
left_line = 1,
|
||||
left_split_line = 5,
|
||||
left_underground_line = 3,
|
||||
right_line = 2,
|
||||
right_split_line = 6,
|
||||
right_underground_line = 4,
|
||||
secondary_left_line = 3,
|
||||
secondary_left_split_line = 7,
|
||||
secondary_right_line = 4,
|
||||
secondary_right_split_line = 8
|
||||
},
|
||||
wire_connection_id = {
|
||||
electric_pole = 0,
|
||||
power_switch_left = 0,
|
||||
power_switch_right = 1
|
||||
},
|
||||
wire_type = {
|
||||
copper = 1,
|
||||
green = 3,
|
||||
red = 2
|
||||
}
|
||||
}
|
||||
109
parser/exportRawData.js
Normal file
109
parser/exportRawData.js
Normal file
@@ -0,0 +1,109 @@
|
||||
const fse = require('fs-extra')
|
||||
const lua_parser = require('./luajs/lua_parser_umd').parser
|
||||
const execSync = require('child_process').execSync
|
||||
//const factorioDirectory = 'C:/SteamLibrary/steamapps/common/Factorio/data/'
|
||||
const factorioDirectory = 'C:/_Programs/Steam/steamapps/common/Factorio/data/'
|
||||
|
||||
//run /c game.write_file("defines.lua", serpent.block(_G.defines, {comments=false}))
|
||||
//_tree_data_320 -> _tree_data_1
|
||||
|
||||
// Load Order:
|
||||
// data.lua
|
||||
// data-updates.lua
|
||||
// data-final-fixes.lua
|
||||
|
||||
const reqLualibRegex = /.*?require\s*\(*['"]([^.]+?)['"]\)*/g
|
||||
const reqRegex = /require\s*\(*['"](.+?)['"]\)*/g
|
||||
|
||||
let loadedModules = []
|
||||
|
||||
function searchLoadRemoveDependencies(contents, regex, baseFolder) {
|
||||
let newModules = []
|
||||
let match = regex.exec(contents)
|
||||
while (match !== null) {
|
||||
let dep = match[1]
|
||||
if (!loadedModules.includes(dep)) {
|
||||
//load module
|
||||
loadedModules.push(dep)
|
||||
newModules.push({
|
||||
index: match.index,
|
||||
name: dep
|
||||
})
|
||||
}
|
||||
match = regex.exec(contents)
|
||||
}
|
||||
|
||||
let offset = 0
|
||||
for (let i = 0; i < newModules.length; i++) {
|
||||
let startPart = contents.slice(0, newModules[i].index + offset)
|
||||
let endPart = contents.slice(newModules[i].index + offset)
|
||||
let depData = readRequireOfFile(baseFolder, newModules[i].name.replace(/\./g, '/') + '.lua') + '\n'
|
||||
contents = startPart + depData + endPart
|
||||
offset += depData.length
|
||||
}
|
||||
|
||||
// remove all requires
|
||||
contents = contents.replace(regex, '')
|
||||
|
||||
return contents
|
||||
}
|
||||
|
||||
function readRequireOfFile(baseFolder, pathCon) {
|
||||
let contents = fse.readFileSync(factorioDirectory + baseFolder + pathCon).toString()
|
||||
|
||||
contents = searchLoadRemoveDependencies(contents, reqLualibRegex, 'core/lualib/')
|
||||
contents = searchLoadRemoveDependencies(contents, reqRegex, baseFolder)
|
||||
|
||||
// remove last return
|
||||
contents = contents.replace(/return\s*\b.+?\b\s*$/g, '')
|
||||
|
||||
// if a return is an obj, convert the return with the filename
|
||||
contents = contents.replace(/return\s(\{(.|\n)+?\})\s*$/g, function(match, capture){
|
||||
let split = pathCon.split('/')
|
||||
return split[split.length - 1].replace('.lua', '') + ' = ' + capture
|
||||
})
|
||||
|
||||
if (pathCon.includes('autoplace_utils')) {
|
||||
contents = contents.replace(/M/g, 'autoplace_utils')
|
||||
}
|
||||
|
||||
return contents
|
||||
}
|
||||
|
||||
const fileOrder = [
|
||||
'core/lualib/dataloader.lua',
|
||||
'core/data.lua',
|
||||
'base/data.lua',
|
||||
'base/data-updates.lua'
|
||||
]
|
||||
|
||||
let mainFileData = ''
|
||||
|
||||
for (let i = 0; i < fileOrder.length; i++) {
|
||||
let splitPath = fileOrder[i].split('/')
|
||||
let data = readRequireOfFile(splitPath[0] + '/', splitPath.slice(1).join('/'))
|
||||
mainFileData += data + '\n'
|
||||
}
|
||||
|
||||
mainFileData = mainFileData
|
||||
// var = require(...) results in var = var = {}
|
||||
.replace(/\b[a-zA-Z_-]+?\b\s*(=\s*\b[a-zA-Z_-]+?\b\s*)=\s*\{/g, function(match, capture){
|
||||
return match.replace(capture, '')
|
||||
})
|
||||
|
||||
mainFileData = fse.readFileSync('./defines.lua').toString() + mainFileData
|
||||
|
||||
fse.writeFileSync('./temp.lua', mainFileData)
|
||||
|
||||
let parsedData = lua_parser.parse(mainFileData).replace(/_tree_data_320/g, '_tree_data_1')
|
||||
|
||||
let script = "var fs = require('fs');\n" +
|
||||
fse.readFileSync('./luajs/lua.js').toString() + "\n" +
|
||||
"var lua_script = (function() {\n" +
|
||||
" " + parsedData.split("\n").join("\n ") + "\n" +
|
||||
"})()[0];\n" +
|
||||
"fs.writeFileSync('./temp.json', JSON.stringify(lua_tabletoJson(lua_tableget(lua_tableget(lua_script, 'data'), 'raw')), null, 2))"
|
||||
|
||||
fse.writeFileSync('./temp.js', script)
|
||||
|
||||
execSync('node temp.js')
|
||||
11
parser/json-entity.tpl
Normal file
11
parser/json-entity.tpl
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"frames": {
|
||||
<% layout.images.forEach(function (image, idx)
|
||||
{ %>"<%= image.className %>": {
|
||||
"frame": { "x": <%= image.x %>, "y": <%= image.y %>, "w": <%= image.width %>, "h": <%= image.height %> },
|
||||
"sourceSize": { "w": <%= image.width %>, "h": <%= image.height %> }
|
||||
}<% if (idx !== layout.images.length - 1) { %>,<% } %>
|
||||
<% }); %>
|
||||
},
|
||||
"meta": { "image": "entitySprites.png" }
|
||||
}
|
||||
11
parser/json-icon.tpl
Normal file
11
parser/json-icon.tpl
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"frames": {
|
||||
<% layout.images.forEach(function (image, idx)
|
||||
{ %>"<%= image.className %>": {
|
||||
"frame": { "x": <%= image.x %>, "y": <%= image.y %>, "w": <%= image.width %>, "h": <%= image.height %> },
|
||||
"sourceSize": { "w": <%= image.width %>, "h": <%= image.height %> }
|
||||
}<% if (idx !== layout.images.length - 1) { %>,<% } %>
|
||||
<% }); %>
|
||||
},
|
||||
"meta": { "image": "iconSprites.png" }
|
||||
}
|
||||
1800
parser/luajs/lua.js
Normal file
1800
parser/luajs/lua.js
Normal file
File diff suppressed because it is too large
Load Diff
1441
parser/luajs/lua_parser_umd.js
Normal file
1441
parser/luajs/lua_parser_umd.js
Normal file
File diff suppressed because one or more lines are too long
466
parser/processRawData.js
Normal file
466
parser/processRawData.js
Normal file
@@ -0,0 +1,466 @@
|
||||
const fse = require('fs-extra')
|
||||
const nsg = require('node-sprite-generator')
|
||||
const Jimp = require('jimp')
|
||||
const util = require('util')
|
||||
//const factorioDirectory = 'C:/SteamLibrary/steamapps/common/Factorio/data/'
|
||||
const factorioDirectory = 'C:/_Programs/Steam/steamapps/common/Factorio/data/'
|
||||
const outDir = '../src/bundles/'
|
||||
const spritesheetsOutDir = '../src/spritesheets/'
|
||||
|
||||
function nameMapping(imagePath) {
|
||||
const sP = imagePath.split('/')
|
||||
return sP.splice(sP.length - 2).join('/').split('.')[0]
|
||||
}
|
||||
|
||||
let rawData = JSON.parse(fse.readFileSync('./temp.json').toString()
|
||||
.replace(/"(up|down|left|right|north|south|west|east)"/g, function(match, capture) {
|
||||
if (capture === 'north' || capture === 'up') return '"0"'
|
||||
if (capture === 'east' || capture === 'left') return '"2"'
|
||||
if (capture === 'south' || capture === 'down') return '"4"'
|
||||
if (capture === 'west' || capture === 'right') return '"6"'
|
||||
}))
|
||||
|
||||
let tiles = {}
|
||||
for (const k in rawData.tile) {
|
||||
if (rawData.tile[k].minable) tiles[k] = rawData.tile[k]
|
||||
}
|
||||
console.log('Tiles: ' + Object.keys(tiles).length)
|
||||
fse.writeFileSync(outDir + 'tileBundle.json', JSON.stringify(tiles, null, 2).replace(/__base__|__core__/g, 'factorio-data'))
|
||||
|
||||
console.log('Recipes: ' + Object.keys(rawData.recipe).length)
|
||||
fse.writeFileSync(outDir + 'recipeBundle.json', JSON.stringify(rawData.recipe, null, 2).replace(/__base__|__core__/g, 'factorio-data'))
|
||||
|
||||
let inventory = []
|
||||
let items = {}
|
||||
let placeableEntities = ['curved-rail']
|
||||
|
||||
const blacklistedGroups = [
|
||||
'environment',
|
||||
'enemies',
|
||||
'other'
|
||||
]
|
||||
|
||||
for (const k in rawData['item-group']) {
|
||||
const group = rawData['item-group'][k]
|
||||
if (!blacklistedGroups.includes(group.name)) {
|
||||
group.subgroups = []
|
||||
inventory.push(group)
|
||||
}
|
||||
}
|
||||
|
||||
for (const k in rawData['item-subgroup']) {
|
||||
const subgroup = rawData['item-subgroup'][k]
|
||||
subgroup.items = []
|
||||
for (const group of inventory) {
|
||||
if (group.name === subgroup.group) {
|
||||
group.subgroups.push(subgroup)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findAllItems(data) {
|
||||
if (data.constructor === Object) {
|
||||
if (data.hasOwnProperty('subgroup')) {
|
||||
addItem(data)
|
||||
} else {
|
||||
for (const k in data) {
|
||||
if (data.hasOwnProperty(k)) {
|
||||
findAllItems(data[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
findAllItems(rawData)
|
||||
|
||||
for (const k in rawData['fluid']) {
|
||||
const fluid = rawData['fluid'][k]
|
||||
fluid.subgroup = 'fluid'
|
||||
addItem(fluid)
|
||||
}
|
||||
|
||||
function addItem(item) {
|
||||
if ((item.flags && item.flags.includes('hidden')) || !(item.icon || item.icons) || !item.order || item.collision_box) return
|
||||
for (let j = 0; j < inventory.length; j++) {
|
||||
for (let k = 0; k < inventory[j].subgroups.length; k++) {
|
||||
if (inventory[j].subgroups[k].name === item.subgroup) {
|
||||
inventory[j].subgroups[k].items.push(item)
|
||||
if (item.place_result) placeableEntities.push(item.place_result)
|
||||
items[item.name] = item
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Items: ' + Object.keys(items).length)
|
||||
fse.writeFileSync(outDir + 'itemBundle.json', JSON.stringify(items, null, 2).replace(/"((__base__|__core__)\/.+?)"/g, function(match, capture) {
|
||||
return '"icon:' + nameMapping(capture) + '"'
|
||||
}))
|
||||
|
||||
// sort and remove extra info from inventoryBundle
|
||||
inventory.sort(sortByOrder)
|
||||
for (let i = 0; i < inventory.length; i++) {
|
||||
inventory[i].subgroups.sort(sortByOrder)
|
||||
for (let j = 0; j < inventory[i].subgroups.length; j++) {
|
||||
inventory[i].subgroups[j].items.sort(sortByOrder)
|
||||
for (let k = 0; k < inventory[i].subgroups[j].items.length; k++) {
|
||||
removeExtraInfo(inventory[i].subgroups[j].items[k])
|
||||
}
|
||||
removeExtraInfo(inventory[i].subgroups[j])
|
||||
}
|
||||
removeExtraInfo(inventory[i])
|
||||
}
|
||||
|
||||
function sortByOrder(a, b) {
|
||||
// https://forums.factorio.com/viewtopic.php?f=25&t=3236#p23818
|
||||
// https://forums.factorio.com/viewtopic.php?f=25&t=24163#p152955
|
||||
if (a.order < b.order) return -1
|
||||
if (a.order > b.order) return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
function removeExtraInfo(obj) {
|
||||
for (const k of Object.keys(obj)) {
|
||||
if (!['subgroups', 'items', 'name', 'icon', 'icons'].includes(k)) delete obj[k]
|
||||
}
|
||||
}
|
||||
|
||||
fse.writeFileSync(outDir + 'inventoryBundle.json', JSON.stringify(inventory, null, 2).replace(/"((__base__|__core__)\/.+?)"/g, function(match, capture) {
|
||||
return '"icon:' + nameMapping(capture) + '"'
|
||||
}))
|
||||
|
||||
let paths = []
|
||||
for (let i = 0, l = inventory.length; i < l; i++) {
|
||||
paths.push(factorioDirectory + inventory[i].icon.replace(/__base__/g, 'base').replace(/__core__/g, 'core'))
|
||||
for (let j = 0, l2 = inventory[i].subgroups.length; j < l2; j++) {
|
||||
for (let k = 0, l3 = inventory[i].subgroups[j].items.length; k < l3; k++) {
|
||||
const item = inventory[i].subgroups[j].items[k]
|
||||
if (item.icon) {
|
||||
paths.push(factorioDirectory + item.icon.replace(/__base__/g, 'base').replace(/__core__/g, 'core'))
|
||||
} else {
|
||||
for (let l = 0; l < item.icons.length; l++) {
|
||||
paths.push(factorioDirectory + item.icons[l].icon.replace(/__base__/g, 'base').replace(/__core__/g, 'core'))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
paths = Array.from(new Set(paths).values())
|
||||
|
||||
console.log('Icon sprites: ' + paths.length)
|
||||
nsg({
|
||||
src: paths,
|
||||
spritePath: spritesheetsOutDir + 'iconSpritesheet.png',
|
||||
stylesheet: './json-icon.tpl',
|
||||
stylesheetPath: spritesheetsOutDir + 'iconSpritesheet.json',
|
||||
stylesheetOptions: {
|
||||
prefix: 'icon:',
|
||||
nameMapping: nameMapping
|
||||
},
|
||||
compositor: 'jimp',
|
||||
layout: 'packed',
|
||||
layoutOptions: {
|
||||
padding: 2
|
||||
}
|
||||
}, function(err) {
|
||||
if (err)
|
||||
console.log(err)
|
||||
else
|
||||
console.log('Icon sprite atlas generated!')
|
||||
})
|
||||
|
||||
let entities = {}
|
||||
function findAllEntities(data) {
|
||||
if (data.constructor === Object) {
|
||||
if (placeableEntities.includes(data.name) && data.hasOwnProperty('collision_box') && (!data.flags.includes('placeable-off-grid') || data.name === 'land-mine')) {
|
||||
entities[data.name] = data
|
||||
} else {
|
||||
for (let k in data) {
|
||||
if (data.hasOwnProperty(k)) {
|
||||
findAllEntities(data[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
findAllEntities(rawData)
|
||||
|
||||
const regexNameMatches = [
|
||||
'combinator',
|
||||
'underground-belt',
|
||||
'transport-belt',
|
||||
'splitter',
|
||||
'inserter',
|
||||
'turret',
|
||||
'mining-drill',
|
||||
'pump'
|
||||
]
|
||||
|
||||
let nameMatches = [
|
||||
'assembling-machine-2',
|
||||
'assembling-machine-3',
|
||||
'pipe-to-ground',
|
||||
'oil-refinery',
|
||||
'chemical-plant',
|
||||
'heat-exchanger',
|
||||
'boiler',
|
||||
'train-stop'
|
||||
]
|
||||
|
||||
for (let k in entities) {
|
||||
// Size
|
||||
const box = entities[k].selection_box
|
||||
entities[k].size = {
|
||||
width: Math.ceil(Math.abs(box[0][0]) + Math.abs(box[1][0])),
|
||||
height: Math.ceil(Math.abs(box[0][1]) + Math.abs(box[1][1]))
|
||||
}
|
||||
// Move out splitters and underground-belts from transport-belt fast_replaceable_group
|
||||
if (k.search('splitter') !== -1) {
|
||||
entities[k].fast_replaceable_group = 'splitter'
|
||||
}
|
||||
if (k.search('underground-belt') !== -1) {
|
||||
entities[k].fast_replaceable_group = 'underground-belt'
|
||||
}
|
||||
// Possible Rotations
|
||||
for (let j = 0; j < regexNameMatches.length; j++) {
|
||||
if (k.includes(regexNameMatches[j])) {
|
||||
nameMatches.push(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Actual land size of the offshore pump
|
||||
entities['offshore-pump'].size = { width: 1, height: 1 }
|
||||
|
||||
for (let i = 0; i < nameMatches.length; i++) {
|
||||
entities[nameMatches[i]].possible_rotations = [0, 2, 4, 6]
|
||||
}
|
||||
entities['storage-tank'].possible_rotations = [0, 2]
|
||||
entities['gate'].possible_rotations = [0, 2]
|
||||
entities['steam-engine'].possible_rotations = [0, 2]
|
||||
entities['steam-turbine'].possible_rotations = [0, 2]
|
||||
entities['straight-rail'].possible_rotations = [0, 2]
|
||||
entities['rail-signal'].possible_rotations = [0, 1, 2, 3, 4, 5, 6, 7]
|
||||
entities['rail-chain-signal'].possible_rotations = [0, 1, 2, 3, 4, 5, 6, 7]
|
||||
// End Possible Rotations
|
||||
|
||||
// switch dir 2 and 6 for pipe-to-ground
|
||||
let dir2 = Object.assign({}, entities['pipe-to-ground'].pictures['2'])
|
||||
entities['pipe-to-ground'].pictures['2'] = entities['pipe-to-ground'].pictures['6']
|
||||
entities['pipe-to-ground'].pictures['6'] = dir2
|
||||
// shift.y-1 for dir 4 wall patch of gate
|
||||
let wp4 = entities['gate'].wall_patch['4'].layers[0]
|
||||
wp4.shift = [wp4.shift[0], wp4.shift[1] - 1]
|
||||
if (wp4.hr_version) {
|
||||
wp4.hr_version.shift = [wp4.hr_version.shift[0], wp4.hr_version.shift[1] - 1]
|
||||
}
|
||||
// fix shifts
|
||||
entities['storage-tank'].pictures.window_background.shift = [0, 1]
|
||||
entities['storage-tank'].pictures.window_background.hr_version.shift = [0, 1]
|
||||
|
||||
add_to_shift([0, -0.6875], entities['artillery-turret'].base_picture.layers[0])
|
||||
add_to_shift([0, -0.6875], entities['artillery-turret'].cannon_barrel_pictures.layers[0])
|
||||
add_to_shift([0, -0.6875], entities['artillery-turret'].cannon_base_pictures.layers[0])
|
||||
|
||||
function add_to_shift(shift, tab) {
|
||||
if (tab.shift) {
|
||||
tab.shift = [shift[0] + tab.shift[0], shift[1] + tab.shift[1]]
|
||||
} else {
|
||||
tab.shift = shift
|
||||
}
|
||||
if (tab.hr_version) {
|
||||
if (tab.hr_version.shift) {
|
||||
tab.hr_version.shift = [shift[0] + tab.hr_version.shift[0], shift[1] + tab.hr_version.shift[1]]
|
||||
} else {
|
||||
tab.hr_version.shift = shift
|
||||
}
|
||||
}
|
||||
return tab
|
||||
}
|
||||
|
||||
console.log('Entities: ' + Object.keys(entities).length)
|
||||
fse.writeFileSync(outDir + 'entityBundle.json', JSON.stringify(entities, null, 2).replace(/"((__base__|__core__)\/.+?)"/g, function(match, capture) {
|
||||
return '"entity:' + nameMapping(capture) + '"'
|
||||
}))
|
||||
|
||||
graphicsBundle()
|
||||
|
||||
async function graphicsBundle() {
|
||||
let paths = []
|
||||
let hrPaths = []
|
||||
let re = /"filename":\s*"([^.]+?\.png)"/g
|
||||
let str = JSON.stringify(entities)
|
||||
let match
|
||||
|
||||
const excludeKeywords = [
|
||||
'explosion',
|
||||
'cloud',
|
||||
'smoke',
|
||||
'fire',
|
||||
'muzzle-flash',
|
||||
'-light\.padding',
|
||||
'steam\.png',
|
||||
'-shadow\.png',
|
||||
'-shadow-',
|
||||
'load-standup',
|
||||
'flamethrower-turret-gun(-[^e]|[^-])',
|
||||
'pump-[a-z]+?-liquid',
|
||||
'pump-[a-z]+?-glass',
|
||||
'accumulator-[a-z]+?-animation',
|
||||
'connector\/(hr-)?.-.-',
|
||||
'heated',
|
||||
'gun-turret-gun-[m12]',
|
||||
'roboport-recharging',
|
||||
'segment-visualisation',
|
||||
'graphics\/[^/]*$',
|
||||
'-light\.png',
|
||||
'-lights-color',
|
||||
'boiling-green',
|
||||
'power-switch-electricity',
|
||||
'electric-furnace-heater',
|
||||
'integration',
|
||||
'arrows',
|
||||
'hole',
|
||||
'rocket-over',
|
||||
'working',
|
||||
'hand-closed'
|
||||
]
|
||||
const excludeKeywordsRegex = new RegExp(excludeKeywords.join('|'), 'g')
|
||||
|
||||
while ((match = re.exec(str)) !== null) {
|
||||
let path = match[1].replace(/__base__/g, 'base').replace(/__core__/g, 'core')
|
||||
if (match[1].search(excludeKeywordsRegex) === -1) {
|
||||
if (match[1].search(/\/hr-/g) === -1) {
|
||||
if (!paths.includes(path)) {
|
||||
paths.push(path)
|
||||
}
|
||||
} else {
|
||||
if (!hrPaths.includes(path)) {
|
||||
hrPaths.push(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
paths.push('base/graphics/entity/artillery-wagon/artillery-wagon-cannon-barrel-1.png')
|
||||
paths.push('base/graphics/entity/artillery-wagon/artillery-wagon-cannon-barrel-5.png')
|
||||
paths.push('base/graphics/entity/artillery-wagon/artillery-wagon-cannon-barrel-9.png')
|
||||
paths.push('base/graphics/entity/artillery-wagon/artillery-wagon-cannon-barrel-13.png')
|
||||
|
||||
hrPaths.push('base/graphics/entity/artillery-wagon/hr-artillery-wagon-cannon-barrel-1.png')
|
||||
hrPaths.push('base/graphics/entity/artillery-wagon/hr-artillery-wagon-cannon-barrel-5.png')
|
||||
hrPaths.push('base/graphics/entity/artillery-wagon/hr-artillery-wagon-cannon-barrel-9.png')
|
||||
hrPaths.push('base/graphics/entity/artillery-wagon/hr-artillery-wagon-cannon-barrel-13.png')
|
||||
|
||||
paths.push('base/graphics/entity/artillery-wagon/artillery-wagon-cannon-base-1.png')
|
||||
paths.push('base/graphics/entity/artillery-wagon/artillery-wagon-cannon-base-5.png')
|
||||
paths.push('base/graphics/entity/artillery-wagon/artillery-wagon-cannon-base-9.png')
|
||||
paths.push('base/graphics/entity/artillery-wagon/artillery-wagon-cannon-base-13.png')
|
||||
|
||||
hrPaths.push('base/graphics/entity/artillery-wagon/hr-artillery-wagon-cannon-base-1.png')
|
||||
hrPaths.push('base/graphics/entity/artillery-wagon/hr-artillery-wagon-cannon-base-5.png')
|
||||
hrPaths.push('base/graphics/entity/artillery-wagon/hr-artillery-wagon-cannon-base-9.png')
|
||||
hrPaths.push('base/graphics/entity/artillery-wagon/hr-artillery-wagon-cannon-base-13.png')
|
||||
|
||||
console.log('Entity images: ' + paths.length)
|
||||
console.log('Entity HR images: ' + hrPaths.length)
|
||||
|
||||
let cropImages = [
|
||||
['artillery-wagon-cannon', 4, 4],
|
||||
['flamethrower-turret-gun-extension', 5, 1],
|
||||
['gun-turret-gun-extension', 5, 1],
|
||||
['laser-turret-gun-start', 15, 1],
|
||||
['burner-mining-drill', 4, 8],
|
||||
['electric-mining-drill', 8, 8],
|
||||
['pumpjack-horsehead', 8, 5],
|
||||
['assembling-machine-[1-3]\.png', 8, 4],
|
||||
['centrifuge', 8, 8],
|
||||
['lab.png', 11, 3],
|
||||
['[^e]-pump-', 8, 4],
|
||||
['splitter', 8, 4],
|
||||
['radar', 8, 8],
|
||||
['steam-engine', 8, 4],
|
||||
['steam-turbine', 4, 2],
|
||||
['transport-belt', 16, 1],
|
||||
['laser-turret-gun', 8, 1],
|
||||
['beacon-antenna', 8, 4],
|
||||
['roboport-door-', 16, 1],
|
||||
['gate(-rail(-base)?)?-[a-z]+?(-(left|right))?\.png', 8, 2],
|
||||
['arm', 4, 3],
|
||||
['rail-signal\.png', 3, 1],
|
||||
['rail-chain-signal\.png', 4, 1],
|
||||
['power-switch', 2, 3]
|
||||
]
|
||||
|
||||
let addedHrPaths = []
|
||||
let imagesToCrop = []
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
let pArr = paths[i].split('/')
|
||||
if (pArr[pArr.length - 1] === 'electric-furnace-base.png') {
|
||||
pArr[pArr.length - 1] = 'electric-furnace.png'
|
||||
}
|
||||
pArr[pArr.length - 1] = 'hr-' + pArr[pArr.length - 1]
|
||||
let hrVersion = pArr.join('/')
|
||||
if (hrPaths.includes(hrVersion)) {
|
||||
paths[i] = hrVersion
|
||||
addedHrPaths.push(hrVersion)
|
||||
}
|
||||
paths[i] = factorioDirectory + paths[i]
|
||||
// Crop spritesheet
|
||||
for (let j = 0, len2 = cropImages.length; j < len2; j++) {
|
||||
if (paths[i].search(new RegExp(cropImages[j][0], 'g')) !== -1) {
|
||||
let p = './temp/' + nameMapping(paths[i]) + '.png'
|
||||
imagesToCrop.push({
|
||||
path: paths[i],
|
||||
outPath: p,
|
||||
cropImgIndex: j
|
||||
})
|
||||
paths[i] = p
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < hrPaths.length; i++) {
|
||||
if (!addedHrPaths.includes(hrPaths[i])) {
|
||||
paths.push(factorioDirectory + hrPaths[i])
|
||||
}
|
||||
}
|
||||
|
||||
//Jison.write doesn't return a Promise
|
||||
//https://github.com/oliver-moran/jimp/issues/90
|
||||
Jimp.prototype.writeAsync = util.promisify(Jimp.prototype.write)
|
||||
|
||||
let res = Promise.all(imagesToCrop.map(data => Jimp.read(data.path).then(img =>
|
||||
img
|
||||
.crop(0, 0, img.bitmap.width / cropImages[data.cropImgIndex][1], img.bitmap.height / cropImages[data.cropImgIndex][2])
|
||||
.writeAsync(data.outPath)
|
||||
)))
|
||||
|
||||
res.then(() => {
|
||||
console.log('Final entity images: ' + paths.length)
|
||||
nsg({
|
||||
src: paths,
|
||||
spritePath: spritesheetsOutDir + 'entitySpritesheet.png',
|
||||
stylesheet: './json-entity.tpl',
|
||||
stylesheetPath: spritesheetsOutDir + 'entitySpritesheet.json',
|
||||
stylesheetOptions: {
|
||||
prefix: 'entity:',
|
||||
nameMapping: nameMapping
|
||||
},
|
||||
compositor: 'jimp',
|
||||
layout: 'packed',
|
||||
layoutOptions: {
|
||||
padding: 2
|
||||
}
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
} else {
|
||||
fse.remove('./temp')
|
||||
console.log('Entity sprite atlas generated!')
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
289
src/app.ts
Normal file
289
src/app.ts
Normal file
@@ -0,0 +1,289 @@
|
||||
// tslint:disable:no-import-side-effect
|
||||
import 'normalize.css'
|
||||
|
||||
import * as PIXI from 'pixi.js'
|
||||
import keyboardJS from 'keyboardjs'
|
||||
|
||||
import { Book } from './factorio-data/book'
|
||||
import BPString from './factorio-data/BPString'
|
||||
import sampleBP from './sample-blueprint'
|
||||
|
||||
import util from './util'
|
||||
import { InventoryContainer } from './containers/inventory'
|
||||
import G from './globals'
|
||||
import { EntityContainer } from './containers/entity'
|
||||
import { PaintContainer } from './containers/paint'
|
||||
import { BlueprintContainer } from './containers/blueprint'
|
||||
import { ToolbarContainer } from './containers/toolbar'
|
||||
import { isNumber } from 'util'
|
||||
import { Blueprint } from './factorio-data/blueprint'
|
||||
import { EditEntityContainer } from './containers/editEntity'
|
||||
import { InfoContainer } from './containers/info'
|
||||
|
||||
G.renderOnly = window.location.search.slice(1).split('&').includes('renderOnly')
|
||||
|
||||
G.app = new PIXI.Application({
|
||||
autoStart: false,
|
||||
antialias: true,
|
||||
resolution: window.devicePixelRatio
|
||||
// roundPixels: true
|
||||
})
|
||||
|
||||
// https://github.com/pixijs/pixi.js/issues/3928
|
||||
G.app.renderer.plugins.interaction.moveWhenInside = true
|
||||
|
||||
G.app.renderer.view.style.position = 'absolute'
|
||||
G.app.renderer.view.style.display = 'none'
|
||||
G.app.renderer.autoResize = true
|
||||
G.app.renderer.resize(window.innerWidth, window.innerHeight)
|
||||
window.addEventListener('resize', () => {
|
||||
G.app.renderer.resize(window.innerWidth, window.innerHeight)
|
||||
G.BPC.zoomPan.setViewPortSize(G.app.renderer.width, G.app.renderer.height)
|
||||
G.BPC.zoomPan.updateTransform()
|
||||
G.BPC.updateViewportCulling()
|
||||
}, false)
|
||||
document.body.appendChild(G.app.view)
|
||||
|
||||
// PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST
|
||||
// PIXI.settings.GC_MODE = PIXI.GC_MODES.MANUAL
|
||||
|
||||
G.BPC = new BlueprintContainer()
|
||||
G.app.stage.addChild(G.BPC)
|
||||
|
||||
G.editEntityContainer = new EditEntityContainer()
|
||||
G.app.stage.addChild(G.editEntityContainer)
|
||||
|
||||
G.inventoryContainer = new InventoryContainer()
|
||||
G.app.stage.addChild(G.inventoryContainer)
|
||||
|
||||
G.toolbarContainer = new ToolbarContainer()
|
||||
G.app.stage.addChild(G.toolbarContainer)
|
||||
|
||||
const infoContainer = new InfoContainer()
|
||||
G.app.stage.addChild(infoContainer)
|
||||
|
||||
PIXI.loader
|
||||
.add([
|
||||
{ name: 'extra_iconSprites', url: 'spritesheets/extra_iconSpritesheet.json' },
|
||||
{ name: 'iconSprites', url: 'spritesheets/iconSpritesheet.json' },
|
||||
{ name: 'entitySprites', url: 'spritesheets/entitySpritesheet.json' }
|
||||
])
|
||||
.load((_: any, resources: any) => {
|
||||
G.app.renderer.plugins.prepare
|
||||
.add(resources.extra_iconSprites.spritesheet.baseTexture)
|
||||
.add(resources.iconSprites.spritesheet.baseTexture)
|
||||
.add(resources.entitySprites.spritesheet.baseTexture)
|
||||
.upload(setup)
|
||||
})
|
||||
|
||||
function setup() {
|
||||
let initialSource: string
|
||||
for (const a of window.location.search.slice(1).split('&')) {
|
||||
if (a.includes('source')) {
|
||||
initialSource = a.split('=')[1]
|
||||
break
|
||||
}
|
||||
}
|
||||
loadBpFromSource(initialSource ? initialSource : sampleBP).then(() => {
|
||||
|
||||
if (!G.bp) G.bp = new Blueprint()
|
||||
G.BPC.centerViewport()
|
||||
G.BPC.updateCursorPosition({
|
||||
x: G.app.renderer.width / 2,
|
||||
y: G.app.renderer.height / 2
|
||||
})
|
||||
|
||||
G.app.start()
|
||||
G.app.renderer.view.style.display = 'block'
|
||||
})
|
||||
}
|
||||
|
||||
function loadBpFromSource(source: string) {
|
||||
return util.findBPString(source).then(loadBp).catch(error => {
|
||||
console.error(error)
|
||||
})
|
||||
|
||||
function loadBp(bpString: string) {
|
||||
const res = BPString.decode(bpString)
|
||||
// TODO: Handle decode errors
|
||||
if ((res as {error: any}).error) throw (res as {error: any}).error
|
||||
G.bp = res instanceof Book ? res.getBlueprint() : res
|
||||
|
||||
G.BPC.clearData()
|
||||
G.BPC.initBP()
|
||||
console.log('Loaded BP String')
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('copy', e => {
|
||||
e.preventDefault()
|
||||
|
||||
e.clipboardData.setData('text/plain', BPString.encode(G.bp))
|
||||
|
||||
console.log('Copied BP String')
|
||||
})
|
||||
|
||||
window.addEventListener('paste', e => {
|
||||
e.preventDefault()
|
||||
|
||||
G.app.renderer.view.style.display = 'none'
|
||||
loadBpFromSource(e.clipboardData.getData('text')).then(() => G.app.renderer.view.style.display = 'block')
|
||||
})
|
||||
|
||||
keyboardJS.bind('shift + n', () => {
|
||||
G.BPC.clearData()
|
||||
G.bp = new Blueprint()
|
||||
G.BPC.initBP()
|
||||
})
|
||||
|
||||
keyboardJS.bind('modifier + s', e => {
|
||||
e.preventDefault()
|
||||
|
||||
G.BPC.centerViewport()
|
||||
if (G.renderOnly) G.BPC.cacheAsBitmap = false
|
||||
const t = G.app.renderer.generateTexture(G.BPC)
|
||||
if (G.renderOnly) G.BPC.cacheAsBitmap = true
|
||||
t.frame = G.BPC.entitySprites.getLocalBounds()
|
||||
t._updateUvs()
|
||||
const s = new PIXI.Sprite(t)
|
||||
const image = G.app.renderer.plugins.extract.image(s)
|
||||
const w = window.open()
|
||||
w.focus()
|
||||
w.document.write(image.outerHTML)
|
||||
|
||||
console.log('Saved BP Image')
|
||||
})
|
||||
|
||||
keyboardJS.bind('shift', () => G.keyboard.shift = true, () => G.keyboard.shift = false)
|
||||
|
||||
keyboardJS.bind('alt', e => {
|
||||
e.preventDefault()
|
||||
G.BPC.overlayContainer.overlay.visible = !G.BPC.overlayContainer.overlay.visible
|
||||
})
|
||||
|
||||
keyboardJS.bind('i', () => infoContainer.toggle())
|
||||
|
||||
keyboardJS.bind('esc', () => { if (G.openedGUIWindow) G.openedGUIWindow.close() })
|
||||
|
||||
keyboardJS.bind('e', () => {
|
||||
if (G.currentMouseState !== G.mouseStates.MOVING && G.currentMouseState !== G.mouseStates.PAINTING && !G.renderOnly) {
|
||||
if (G.openedGUIWindow) {
|
||||
G.openedGUIWindow.close()
|
||||
} else {
|
||||
G.inventoryContainer.toggle()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
keyboardJS.bind('f', () => G.BPC.centerViewport())
|
||||
|
||||
keyboardJS.bind('r', () => {
|
||||
if (G.BPC.hoverContainer &&
|
||||
(G.currentMouseState === G.mouseStates.NONE || G.currentMouseState === G.mouseStates.MOVING)
|
||||
) {
|
||||
G.BPC.hoverContainer.rotate()
|
||||
} else if (G.currentMouseState === G.mouseStates.PAINTING) {
|
||||
G.BPC.paintContainer.rotate()
|
||||
}
|
||||
})
|
||||
|
||||
keyboardJS.bind('q', () => {
|
||||
if (G.BPC.hoverContainer && G.currentMouseState === G.mouseStates.NONE) {
|
||||
G.currentMouseState = G.mouseStates.PAINTING
|
||||
|
||||
const hoverContainer = G.BPC.hoverContainer
|
||||
G.BPC.hoverContainer.pointerOutEventHandler()
|
||||
const entity = G.bp.entity(hoverContainer.entity_number)
|
||||
G.BPC.paintContainer = new PaintContainer(entity.name,
|
||||
entity.directionType === 'output' ? (entity.direction + 4) % 8 : entity.direction,
|
||||
hoverContainer.position)
|
||||
G.BPC.paintContainer.moveTo({
|
||||
x: G.gridCoordsOfCursor.x * 32,
|
||||
y: G.gridCoordsOfCursor.y * 32
|
||||
})
|
||||
G.BPC.addChild(G.BPC.paintContainer)
|
||||
} else if (G.currentMouseState === G.mouseStates.PAINTING) {
|
||||
G.BPC.paintContainer.destroy()
|
||||
G.BPC.paintContainer = undefined
|
||||
|
||||
G.currentMouseState = G.mouseStates.NONE
|
||||
}
|
||||
})
|
||||
|
||||
keyboardJS.bind('modifier + z', () => {
|
||||
G.bp.undo(
|
||||
hist => pre(hist, 'add'),
|
||||
hist => post(hist, 'del')
|
||||
)
|
||||
})
|
||||
|
||||
keyboardJS.bind('modifier + y', () => {
|
||||
G.bp.redo(
|
||||
hist => pre(hist, 'del'),
|
||||
hist => post(hist, 'add')
|
||||
)
|
||||
})
|
||||
|
||||
function pre(hist: any, addDel: string) {
|
||||
switch (hist.type) {
|
||||
case 'mov':
|
||||
case addDel:
|
||||
const e = EntityContainer.mappings.get(hist.entity_number)
|
||||
e.redrawSurroundingEntities()
|
||||
if (hist.type === addDel) {
|
||||
G.BPC.wiresContainer.remove(hist.entity_number)
|
||||
e.destroy()
|
||||
}
|
||||
if (hist.type === 'mov') G.BPC.wiresContainer.update(hist.entity_number)
|
||||
}
|
||||
}
|
||||
|
||||
function post(hist: any, addDel: string) {
|
||||
function redrawEntityAndSurroundingEntities(entnr: number) {
|
||||
const e = EntityContainer.mappings.get(entnr)
|
||||
e.redraw()
|
||||
e.redrawSurroundingEntities()
|
||||
}
|
||||
switch (hist.type) {
|
||||
case 'mov':
|
||||
redrawEntityAndSurroundingEntities(hist.entity_number)
|
||||
const entity = G.bp.entity(hist.entity_number)
|
||||
const e = EntityContainer.mappings.get(hist.entity_number)
|
||||
e.position.set(
|
||||
entity.position.x * 32,
|
||||
entity.position.y * 32
|
||||
)
|
||||
e.updateVisualStuff()
|
||||
break
|
||||
case 'upd':
|
||||
if (isNumber(hist.entity_number)) {
|
||||
const e = EntityContainer.mappings.get(hist.entity_number)
|
||||
e.redrawEntityInfo()
|
||||
redrawEntityAndSurroundingEntities(hist.entity_number)
|
||||
G.BPC.wiresContainer.update(hist.entity_number)
|
||||
if (G.editEntityContainer.visible) {
|
||||
if (G.inventoryContainer.visible) G.inventoryContainer.close()
|
||||
G.editEntityContainer.create(hist.entity_number)
|
||||
}
|
||||
} else {
|
||||
for (const entnr of hist.entity_number) {
|
||||
redrawEntityAndSurroundingEntities(entnr)
|
||||
}
|
||||
}
|
||||
break
|
||||
case addDel:
|
||||
const ec = new EntityContainer(hist.entity_number)
|
||||
G.BPC.entities.addChild(ec)
|
||||
ec.redrawSurroundingEntities()
|
||||
G.BPC.wiresContainer.update(hist.entity_number)
|
||||
}
|
||||
|
||||
console.log(`${addDel === 'del' ? 'Undo' : 'Redo'} ${hist.entity_number} ${hist.annotation}`)
|
||||
G.BPC.updateOverlay()
|
||||
G.BPC.updateViewportCulling()
|
||||
}
|
||||
|
||||
keyboardJS.bind('w', () => G.keyboard.w = true, () => G.keyboard.w = false)
|
||||
keyboardJS.bind('a', () => G.keyboard.a = true, () => G.keyboard.a = false)
|
||||
keyboardJS.bind('s', () => G.keyboard.s = true, () => G.keyboard.s = false)
|
||||
keyboardJS.bind('d', () => G.keyboard.d = true, () => G.keyboard.d = false)
|
||||
475
src/blueprintSchema.json
Normal file
475
src/blueprintSchema.json
Normal file
@@ -0,0 +1,475 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "blueprintSchema.json",
|
||||
"type": "object",
|
||||
"oneOf": [
|
||||
{
|
||||
"required": [ "blueprint" ],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"blueprint": { "$ref": "blueprintSchema.json#/definitions/blueprint" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"required": [ "blueprint_book" ],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"blueprint_book": {
|
||||
"type": "object",
|
||||
"required": ["version", "item", "active_index", "blueprints"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"version": { "type": "integer" },
|
||||
"item": {
|
||||
"type": "string",
|
||||
"const": "blueprint-book"
|
||||
},
|
||||
"label": { "type": "string" },
|
||||
"active_index": { "type": "integer" },
|
||||
"blueprints": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"maxITems": 1000,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["index", "blueprint"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"index": { "type": "integer" },
|
||||
"blueprint": { "$ref": "blueprintSchema.json#/definitions/blueprint" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"definitions": {
|
||||
"position": {
|
||||
"type": "object",
|
||||
"required": ["x", "y"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"x": { "type": "number" },
|
||||
"y": { "type": "number" }
|
||||
}
|
||||
},
|
||||
"signal": {
|
||||
"type": "object",
|
||||
"required": ["name", "type"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"itemName": true
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["item", "virtual", "fluid"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"wireColor": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["entity_id"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"entity_id": { "type": "integer" },
|
||||
"circuit_id": { "type": "integer" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"side": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"red": { "$ref": "blueprintSchema.json#/definitions/wireColor" },
|
||||
"green": { "$ref": "blueprintSchema.json#/definitions/wireColor" }
|
||||
}
|
||||
},
|
||||
"blueprint": {
|
||||
"type": "object",
|
||||
"required": ["version", "item", "icons"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"version": { "type": "integer" },
|
||||
"item": {
|
||||
"type": "string",
|
||||
"const": "blueprint"
|
||||
},
|
||||
"label": { "type": "string" },
|
||||
"icons": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"maxItems": 4,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["index", "signal"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"index": {
|
||||
"type": "integer",
|
||||
"enum": [1, 2, 3, 4]
|
||||
},
|
||||
"signal": { "$ref": "blueprintSchema.json#/definitions/signal" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"entities": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["entity_number", "name", "position"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"entity_number": { "type": "integer" },
|
||||
"name": {
|
||||
"type": "string",
|
||||
"entityName": true
|
||||
},
|
||||
"position": { "$ref": "blueprintSchema.json#/definitions/position" },
|
||||
"direction": {
|
||||
"type": "integer",
|
||||
"enum": [0, 1, 2, 3, 4, 5, 6, 7],
|
||||
"$comment": "direction, can be ommited if 0"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["input", "output"],
|
||||
"$comment": "direction type, only present if entity is of type underground-belt"
|
||||
},
|
||||
"recipe": {
|
||||
"type": "string",
|
||||
"recipeName": true,
|
||||
"$comment": "recipe name, only present if entity is of type assembling-machine or has fixed_recipe"
|
||||
},
|
||||
"bar": {
|
||||
"type": "integer",
|
||||
"$comment": "inventory size limitation, only present if entity has inventory_size"
|
||||
},
|
||||
"items": {
|
||||
"type": "object",
|
||||
"objectWithItemNames": true,
|
||||
"$comment": "object, keys are module names and value nr of modules, only present if entity has module_specification"
|
||||
},
|
||||
|
||||
"input_priority": {
|
||||
"type": "string",
|
||||
"enum": ["left", "right"],
|
||||
"$comment": "splitter input priority, only present if entity is of type splitter"
|
||||
},
|
||||
"output_priority": {
|
||||
"type": "string",
|
||||
"enum": ["left", "right"],
|
||||
"$comment": "splitter output priority, only present if entity is of type splitter"
|
||||
},
|
||||
"filter": {
|
||||
"type": "string",
|
||||
"itemName": true,
|
||||
"$comment": "splitter filter for output priority, only present if entity is of type splitter"
|
||||
},
|
||||
|
||||
"station": {
|
||||
"type": "string",
|
||||
"$comment": "train stop station name, only present if entity is train-stop"
|
||||
},
|
||||
"color": {
|
||||
"type": "object",
|
||||
"required": ["r", "g", "b", "a"],
|
||||
"additionalProperties": false,
|
||||
"$comment": "train stop color, only present if entity is train-stop",
|
||||
"properties": {
|
||||
"r": { "type": "number" },
|
||||
"g": { "type": "number" },
|
||||
"b": { "type": "number" },
|
||||
"a": { "type": "number" }
|
||||
}
|
||||
},
|
||||
|
||||
"auto_launch": {
|
||||
"type": "boolean",
|
||||
"$comment": "auto launch, only present if entity is rocket-silo"
|
||||
},
|
||||
"override_stack_size": {
|
||||
"type": "integer",
|
||||
"$comment": "override stack size, only present if entity is of type inserter"
|
||||
},
|
||||
"request_from_buffers": {
|
||||
"type": "boolean",
|
||||
"$comment": "only present if entity is logistic-chest-requester"
|
||||
},
|
||||
"filters": {
|
||||
"type": "array",
|
||||
"$comment": "only present if entity is filter-inserter or stack-filter-inserter",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["index", "name"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"index": { "type": "integer" },
|
||||
"name": {
|
||||
"type": "string",
|
||||
"itemName": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"request_filters": {
|
||||
"type": "array",
|
||||
"$comment": "only present if entity is logistic-chest-storage, logistic-chest-buffer or logistic-chest-requester",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["index", "name", "count"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"index": { "type": "integer" },
|
||||
"name": {
|
||||
"type": "string",
|
||||
"itemName": true
|
||||
},
|
||||
"count": { "type": "integer" }
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"alert_parameters": {
|
||||
"type": "object",
|
||||
"$comment": "only present if entity is programmable-speaker",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"alert_message": { "type": "string" },
|
||||
"icon_signal_id": { "$ref": "blueprintSchema.json#/definitions/signal" },
|
||||
"show_alert": { "type": "boolean" },
|
||||
"show_on_map": { "type": "boolean" }
|
||||
}
|
||||
},
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"$comment": "only present if entity is programmable-speaker",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"playback_volume": { "type": "number" },
|
||||
"playback_globally": { "type": "boolean" },
|
||||
"allow_polyphony": { "type": "boolean" }
|
||||
}
|
||||
},
|
||||
|
||||
"connections": {
|
||||
"type": "object",
|
||||
"$comment": "wire connections",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"1": { "$ref": "blueprintSchema.json#/definitions/side" },
|
||||
"2": { "$ref": "blueprintSchema.json#/definitions/side" }
|
||||
}
|
||||
},
|
||||
"control_behavior": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"is_on": {
|
||||
"type": "boolean",
|
||||
"enum": [false],
|
||||
"$comment": "only present if entity is constant-combinator"
|
||||
},
|
||||
"filters": {
|
||||
"type": "array",
|
||||
"$comment": "only present if entity is constant-combinator",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["index", "count", "signal"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"index": { "type": "integer" },
|
||||
"count": { "type": "integer" },
|
||||
"signal": { "$ref": "blueprintSchema.json#/definitions/signal" }
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"use_colors": {
|
||||
"type": "boolean",
|
||||
"enum": [true],
|
||||
"$comment": "only present if entity is small-lamp"
|
||||
},
|
||||
"circuit_enable_disable": {
|
||||
"type": "boolean",
|
||||
"$comment": "only present if entity is of type mining-drill or transport-belt or train-stop"
|
||||
},
|
||||
|
||||
"circuit_read_hand_contents": {
|
||||
"type": "boolean",
|
||||
"$comment": "only present if entity is of type inserter or transport-belt"
|
||||
},
|
||||
"circuit_hand_read_mode": {
|
||||
"type": "integer",
|
||||
"enum": [1],
|
||||
"$comment": "0 = pulse, 1 = hold, only present if entity is of type inserter and circuit_read_hand_contents is true"
|
||||
},
|
||||
"circuit_set_stack_size": {
|
||||
"type": "boolean",
|
||||
"enum": [true],
|
||||
"$comment": "only present if entity is of type inserter and override_stack_size is not set"
|
||||
},
|
||||
"stack_control_input_signal": { "$ref": "blueprintSchema.json#/definitions/signal" },
|
||||
"circuit_contents_read_mode": {
|
||||
"type": "integer",
|
||||
"enum": [0, 1],
|
||||
"$comment": "0 = pulse, 1 = hold, only present if entity is of type transport-belt and circuit_read_hand_contents is true"
|
||||
},
|
||||
|
||||
"circuit_mode_of_operation": {
|
||||
"type": "integer",
|
||||
"$comment": "only present if entity is roboport or logistic-chest-buffer or logistic-chest-requester or of type inserter(3)????????????????"
|
||||
},
|
||||
|
||||
"available_logistic_output_signal": { "$ref": "blueprintSchema.json#/definitions/signal" },
|
||||
"total_logistic_output_signal": { "$ref": "blueprintSchema.json#/definitions/signal" },
|
||||
"available_construction_output_signal": { "$ref": "blueprintSchema.json#/definitions/signal" },
|
||||
"total_construction_output_signal": { "$ref": "blueprintSchema.json#/definitions/signal" },
|
||||
|
||||
"circuit_read_resources": {
|
||||
"type": "boolean",
|
||||
"$comment": "only present if entity is of type mining-drill"
|
||||
},
|
||||
"circuit_resource_read_mode": {
|
||||
"type": "integer",
|
||||
"enum": [0,1],
|
||||
"$comment": "only present if entity is burner-mining-drill or electric-mining-drill and circuit_read_resources is true"
|
||||
},
|
||||
|
||||
"circuit_open_gate": {
|
||||
"type": "boolean",
|
||||
"$comment": "only present if entity is stone-wall"
|
||||
},
|
||||
"circuit_read_sensor": {
|
||||
"type": "boolean",
|
||||
"$comment": "only present if entity is stone-wall"
|
||||
},
|
||||
|
||||
"send_to_train": {
|
||||
"type": "boolean",
|
||||
"enum": [false],
|
||||
"$comment": "only present if entity is train-stop"
|
||||
},
|
||||
"read_from_train": {
|
||||
"type": "boolean",
|
||||
"enum": [true],
|
||||
"$comment": "only present if entity is train-stop"
|
||||
},
|
||||
"read_stopped_train": {
|
||||
"type": "boolean",
|
||||
"enum": [true],
|
||||
"$comment": "only present if entity is train-stop"
|
||||
},
|
||||
"train_stopped_signal": { "$ref": "blueprintSchema.json#/definitions/signal" },
|
||||
|
||||
"circuit_close_signal": {
|
||||
"type": "boolean",
|
||||
"$comment": "only present if entity is rail-signal"
|
||||
},
|
||||
"circuit_read_signal": {
|
||||
"type": "boolean",
|
||||
"$comment": "only present if entity is rail-signal, for chain signals: you have the same signals"
|
||||
},
|
||||
"red_output_signal": { "$ref": "blueprintSchema.json#/definitions/signal" },
|
||||
"orange_output_signal": { "$ref": "blueprintSchema.json#/definitions/signal" },
|
||||
"green_output_signal": { "$ref": "blueprintSchema.json#/definitions/signal" },
|
||||
"blue_output_signal": { "$ref": "blueprintSchema.json#/definitions/signal" },
|
||||
|
||||
"output_signal": {
|
||||
"$comment": "only present if entity is stone-wall or accumulator",
|
||||
"$ref": "blueprintSchema.json#/definitions/signal"
|
||||
},
|
||||
|
||||
"circuit_parameters": {
|
||||
"type": "object",
|
||||
"$comment": "only present if entity is programmable-speaker",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"instrument_id": { "type": "integer" },
|
||||
"note_id": { "type": "integer" },
|
||||
"signal_value_is_pitch": { "type": "boolean" }
|
||||
}
|
||||
},
|
||||
"decider_conditions": {
|
||||
"type": "object",
|
||||
"$comment": "only present if entity is decider-combinator",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"comparator": { "type": "string" },
|
||||
"constant": { "type": "integer" },
|
||||
"copy_count_from_input": { "type": "boolean" },
|
||||
"first_signal": { "$ref": "blueprintSchema.json#/definitions/signal" },
|
||||
"second_signal": { "$ref": "blueprintSchema.json#/definitions/signal" },
|
||||
"output_signal": { "$ref": "blueprintSchema.json#/definitions/signal" }
|
||||
}
|
||||
},
|
||||
"arithmetic_conditions": {
|
||||
"type": "object",
|
||||
"$comment": "only present if entity is arithmetic-combinator",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"operation": { "type": "string" },
|
||||
"constant": { "type": "integer" },
|
||||
"first_constant": { "type": "integer" },
|
||||
"second_constant": { "type": "integer" },
|
||||
"first_signal": { "$ref": "blueprintSchema.json#/definitions/signal" },
|
||||
"second_signal": { "$ref": "blueprintSchema.json#/definitions/signal" },
|
||||
"output_signal": { "$ref": "blueprintSchema.json#/definitions/signal" }
|
||||
}
|
||||
},
|
||||
"circuit_condition": {
|
||||
"type": "object",
|
||||
"$comment": "only present if entity is pump, offshore-pump, rail-signal, train-stop, small-lamp, power-switch, stone-wall, programmable-speaker or of type: inserter, transport-belt or mining-drill",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"comparator": { "type": "string" },
|
||||
"constant": { "type": "integer" },
|
||||
"first_signal": { "$ref": "blueprintSchema.json#/definitions/signal" },
|
||||
"second_signal": { "$ref": "blueprintSchema.json#/definitions/signal" }
|
||||
}
|
||||
},
|
||||
"connect_to_logistic_network": {
|
||||
"type": "boolean",
|
||||
"enum": [true],
|
||||
"$comment": "only present if entity is pump, offshore-pump, train-stop, small-lamp, power-switch or of type: inserter, transport-belt or mining-drill"
|
||||
},
|
||||
"logistic_condition": {
|
||||
"type": "object",
|
||||
"$comment": "only present if entity is pump, offshore-pump, train-stop, small-lamp, power-switch or of type: inserter, transport-belt or mining-drill",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"comparator": { "type": "string" },
|
||||
"constant": { "type": "integer" },
|
||||
"first_signal": { "$ref": "blueprintSchema.json#/definitions/signal" },
|
||||
"second_signal": { "$ref": "blueprintSchema.json#/definitions/signal" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tiles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["name", "position"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"tileName": false
|
||||
},
|
||||
"position": { "$ref": "blueprintSchema.json#/definitions/position" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
62266
src/bundles/entityBundle.json
Normal file
62266
src/bundles/entityBundle.json
Normal file
File diff suppressed because it is too large
Load Diff
1908
src/bundles/inventoryBundle.json
Normal file
1908
src/bundles/inventoryBundle.json
Normal file
File diff suppressed because it is too large
Load Diff
5976
src/bundles/itemBundle.json
Normal file
5976
src/bundles/itemBundle.json
Normal file
File diff suppressed because it is too large
Load Diff
5615
src/bundles/recipeBundle.json
Normal file
5615
src/bundles/recipeBundle.json
Normal file
File diff suppressed because it is too large
Load Diff
2287
src/bundles/tileBundle.json
Normal file
2287
src/bundles/tileBundle.json
Normal file
File diff suppressed because it is too large
Load Diff
342
src/containers/blueprint.ts
Normal file
342
src/containers/blueprint.ts
Normal file
@@ -0,0 +1,342 @@
|
||||
import G from '../globals'
|
||||
import { ZoomPan } from '../zoomPan'
|
||||
import { WiresContainer } from './wires'
|
||||
import { UnderlayContainer } from './underlay'
|
||||
import { EntitySprite } from '../entitySprite'
|
||||
import { AdjustmentFilter } from '@pixi/filter-adjustment'
|
||||
import { EntityContainer } from './entity'
|
||||
import { OverlayContainer } from './overlay'
|
||||
import { PaintContainer } from './paint'
|
||||
|
||||
export class BlueprintContainer extends PIXI.Container {
|
||||
|
||||
holdingLeftClick: boolean
|
||||
grid: PIXI.Sprite
|
||||
wiresContainer: WiresContainer
|
||||
overlayContainer: OverlayContainer
|
||||
underlayContainer: UnderlayContainer
|
||||
entities: PIXI.Container
|
||||
movingEntityFilter: AdjustmentFilter
|
||||
entitySprites: PIXI.Container
|
||||
movementSpeed: number
|
||||
zoomPan: ZoomPan
|
||||
holdingRightClick: boolean
|
||||
lastCursorPos: IPoint
|
||||
pgOverlay: PIXI.Graphics
|
||||
hoverContainer: undefined | EntityContainer
|
||||
movingContainer: undefined | EntityContainer
|
||||
paintContainer: undefined | PaintContainer
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.interactive = true
|
||||
|
||||
this.holdingLeftClick = false
|
||||
this.holdingRightClick = false
|
||||
this.lastCursorPos = { x: 0, y: 0 }
|
||||
|
||||
this.movementSpeed = 10
|
||||
|
||||
this.zoomPan = new ZoomPan(this, G.sizeBPContainer, G.positionBPContainer, {
|
||||
width: G.app.renderer.width,
|
||||
height: G.app.renderer.height
|
||||
}, 10)
|
||||
|
||||
this.movingEntityFilter = new AdjustmentFilter({ red: 0.4, blue: 0.4, green: 1 })
|
||||
|
||||
const ggrid = new PIXI.Graphics()
|
||||
for (let i = 0, l = G.sizeBPContainer.width; i < l; i += G.cellSize) {
|
||||
for (let j = 0, l2 = G.sizeBPContainer.height; j < l2; j += G.cellSize) {
|
||||
if ((i + j) / G.cellSize % 2) {
|
||||
ggrid.beginFill(0x303030)
|
||||
} else {
|
||||
ggrid.beginFill(0x181818)
|
||||
}
|
||||
ggrid.drawRect(i, j, G.cellSize, G.cellSize)
|
||||
ggrid.endFill()
|
||||
}
|
||||
}
|
||||
this.grid = new PIXI.Sprite(G.app.renderer.generateTexture(ggrid))
|
||||
this.grid.interactive = false
|
||||
this.addChild(this.grid)
|
||||
|
||||
this.pgOverlay = new PIXI.Graphics()
|
||||
this.pgOverlay.alpha = 0.2
|
||||
// this.addChild(this.pgOverlay)
|
||||
|
||||
this.underlayContainer = new UnderlayContainer()
|
||||
this.addChild(this.underlayContainer)
|
||||
|
||||
this.entitySprites = new PIXI.Container()
|
||||
this.entitySprites.interactive = false
|
||||
this.entitySprites.interactiveChildren = false
|
||||
this.addChild(this.entitySprites)
|
||||
|
||||
this.entities = new PIXI.Container()
|
||||
this.entities.interactive = false
|
||||
this.entities.interactiveChildren = true
|
||||
this.addChild(this.entities)
|
||||
|
||||
this.wiresContainer = new WiresContainer()
|
||||
this.addChild(this.wiresContainer)
|
||||
|
||||
this.overlayContainer = new OverlayContainer()
|
||||
this.addChild(this.overlayContainer)
|
||||
|
||||
this.on('pointerdown', this.pointerDownEventHandler)
|
||||
this.on('pointerup', this.pointerUpEventHandler)
|
||||
this.on('pointerupoutside', this.pointerUpEventHandler)
|
||||
this.on('pointermove', this.pointerMoveEventHandler)
|
||||
|
||||
document.addEventListener('wheel', e => {
|
||||
e.preventDefault()
|
||||
this.zoomPan.setScaleCenter(G.gridCoordsOfCursor.x * 32, G.gridCoordsOfCursor.y * 32)
|
||||
const z = Math.sign(-e.deltaY) * 0.1
|
||||
this.zoomPan.zoomBy(z, z)
|
||||
this.zoomPan.updateTransform()
|
||||
this.updateViewportCulling()
|
||||
}, false)
|
||||
|
||||
G.app.ticker.add(() => {
|
||||
const WSXOR = G.keyboard.w !== G.keyboard.s
|
||||
const ADXOR = G.keyboard.a !== G.keyboard.d
|
||||
if (WSXOR || ADXOR) {
|
||||
this.zoomPan.translateBy(
|
||||
ADXOR ? (G.keyboard.a ? this.movementSpeed : -this.movementSpeed) : 0,
|
||||
WSXOR ? (G.keyboard.w ? this.movementSpeed : -this.movementSpeed) : 0
|
||||
)
|
||||
this.zoomPan.updateTransform()
|
||||
|
||||
if (this.updateCursorPosition() && (this.movingContainer || this.paintContainer)) {
|
||||
(this.movingContainer || this.paintContainer).moveTo({
|
||||
x: G.gridCoordsOfCursor.x * 32,
|
||||
y: G.gridCoordsOfCursor.y * 32
|
||||
})
|
||||
}
|
||||
|
||||
this.updateViewportCulling()
|
||||
}
|
||||
})
|
||||
|
||||
if (G.renderOnly) {
|
||||
this.interactiveChildren = false
|
||||
}
|
||||
}
|
||||
|
||||
initBP() {
|
||||
// TODO: maybe check for curved rails as well
|
||||
for (const entity_number of G.bp.rawEntities.keys()) {
|
||||
const entity = G.bp.entity(entity_number)
|
||||
if (entity.name === 'straight-rail') {
|
||||
const x = Math.abs(entity.position.x)
|
||||
const y = Math.abs(entity.position.y)
|
||||
G.railMoveOffset = {
|
||||
x: x % 2 + 1,
|
||||
y: y % 2 + 1
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Render Bp
|
||||
for (const entity_number of G.bp.rawEntities.keys()) {
|
||||
this.entities.addChild(new EntityContainer(entity_number, false))
|
||||
}
|
||||
|
||||
this.sortEntities()
|
||||
this.wiresContainer.drawWires()
|
||||
this.updateOverlay()
|
||||
this.centerViewport()
|
||||
|
||||
if (G.renderOnly) {
|
||||
this.cacheAsBitmap = false
|
||||
this.cacheAsBitmap = true
|
||||
}
|
||||
}
|
||||
|
||||
clearData() {
|
||||
const opt = { children: true }
|
||||
this.underlayContainer.destroy(opt)
|
||||
this.entitySprites.destroy(opt)
|
||||
this.entities.destroy(opt)
|
||||
this.wiresContainer.destroy(opt)
|
||||
this.overlayContainer.destroy(opt)
|
||||
|
||||
this.removeChildren()
|
||||
|
||||
this.holdingLeftClick = false
|
||||
this.holdingRightClick = false
|
||||
this.hoverContainer = undefined
|
||||
this.movingContainer = undefined
|
||||
this.paintContainer = undefined
|
||||
|
||||
this.underlayContainer = new UnderlayContainer()
|
||||
|
||||
this.entitySprites = new PIXI.Container()
|
||||
this.entitySprites.interactive = false
|
||||
this.entitySprites.interactiveChildren = false
|
||||
|
||||
this.entities = new PIXI.Container()
|
||||
this.entities.interactive = false
|
||||
this.entities.interactiveChildren = true
|
||||
|
||||
this.wiresContainer = new WiresContainer()
|
||||
|
||||
this.overlayContainer = new OverlayContainer()
|
||||
|
||||
this.addChild(this.grid, this.underlayContainer, this.entitySprites, this.entities, this.wiresContainer, this.overlayContainer)
|
||||
|
||||
G.currentMouseState = G.mouseStates.NONE
|
||||
}
|
||||
|
||||
sortEntities() {
|
||||
(this.entities.children as EntityContainer[]).sort((a, b) =>
|
||||
((b.hitArea as PIXI.Rectangle).height - (a.hitArea as PIXI.Rectangle).height)
|
||||
);
|
||||
|
||||
(this.entitySprites.children as EntitySprite[]).sort((a, b) => {
|
||||
if (a.isMoving && !b.isMoving) return 1
|
||||
if (b.isMoving && !a.isMoving) return -1
|
||||
const dZ = a.zIndex - b.zIndex
|
||||
if (dZ !== 0) return dZ
|
||||
const dY = (a.y - a.shift.y) - (b.y - b.shift.y)
|
||||
if (dY !== 0) return dY
|
||||
const dO = a.zOrder - b.zOrder
|
||||
if (dO !== 0) return dO
|
||||
const dX = (a.x - a.shift.x) - (b.x - b.shift.x)
|
||||
if (dX !== 0) return dX
|
||||
return a.id - b.id
|
||||
})
|
||||
}
|
||||
|
||||
updateCursorPosition(mousePosition?: IPoint) {
|
||||
const mousePositionInBP = {
|
||||
x: Math.abs(this.position.x - (mousePosition ? mousePosition.x : G.app.renderer.plugins.interaction.mouse.global.x))
|
||||
/ this.zoomPan.getCurrentScale(),
|
||||
y: Math.abs(this.position.y - (mousePosition ? mousePosition.y : G.app.renderer.plugins.interaction.mouse.global.y))
|
||||
/ this.zoomPan.getCurrentScale()
|
||||
}
|
||||
const newGridCoordsOfCursor = {
|
||||
x: (mousePositionInBP.x - mousePositionInBP.x % 32) / 32,
|
||||
y: (mousePositionInBP.y - mousePositionInBP.y % 32) / 32
|
||||
}
|
||||
if (newGridCoordsOfCursor.x !== G.gridCoordsOfCursor.x || newGridCoordsOfCursor.y !== G.gridCoordsOfCursor.y) {
|
||||
this.lastCursorPos = { ...(mousePosition ? mousePosition : G.app.renderer.plugins.interaction.mouse.global) }
|
||||
G.gridCoordsOfCursor = newGridCoordsOfCursor
|
||||
G.toolbarContainer.updateGridPos(G.gridCoordsOfCursor)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
updateOverlay() {
|
||||
return
|
||||
const TEMP = G.bp.entityPositionGrid.getAllPositions()
|
||||
this.pgOverlay.clear()
|
||||
for (const t of TEMP) {
|
||||
this.pgOverlay.beginFill(0x0080FF)
|
||||
this.pgOverlay.drawRect(t.x * 32, t.y * 32, G.cellSize, G.cellSize)
|
||||
this.pgOverlay.endFill()
|
||||
}
|
||||
}
|
||||
|
||||
centerViewport() {
|
||||
if (G.bp.rawEntities.size === 0) {
|
||||
this.zoomPan.setPosition(-G.sizeBPContainer.width / 2, -G.sizeBPContainer.height / 2)
|
||||
this.zoomPan.updateTransform()
|
||||
return
|
||||
}
|
||||
|
||||
const TL = G.bp.topLeft()
|
||||
const TR = G.bp.topRight()
|
||||
const BL = G.bp.bottomLeft()
|
||||
|
||||
const W = G.bpArea.width / 2
|
||||
const H = G.bpArea.height / 2
|
||||
|
||||
const hor1 = Math.abs(TL.x - W)
|
||||
const hor2 = TR.x - W
|
||||
|
||||
const ver1 = Math.abs(TL.y - H)
|
||||
const ver2 = BL.y - H
|
||||
|
||||
this.zoomPan.centerViewPort({
|
||||
x: (hor1 + hor2) * 32,
|
||||
y: (ver1 + ver2) * 32
|
||||
}, {
|
||||
x: (hor1 - hor2) * 16,
|
||||
y: (ver1 - ver2) * 16
|
||||
})
|
||||
this.updateViewportCulling()
|
||||
}
|
||||
|
||||
updateViewportCulling() {
|
||||
cullChildren(this.entitySprites.children)
|
||||
cullChildren(this.overlayContainer.overlay.children)
|
||||
|
||||
function cullChildren(children: PIXI.DisplayObject[]) {
|
||||
for (const c of children) {
|
||||
const b = c.getBounds()
|
||||
c.renderable =
|
||||
b.x + b.width > G.positionBPContainer.x &&
|
||||
b.y + b.height > G.positionBPContainer.y &&
|
||||
b.x < G.app.renderer.width &&
|
||||
b.y < G.app.renderer.height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pointerMoveEventHandler(e: PIXI.interaction.InteractionEvent) {
|
||||
// Update the position here to avoid calling all pointermove eventHandlers with
|
||||
// G.app.renderer.plugins.interaction.moveWhenInside set to false
|
||||
const newCursorPos = e.data.getLocalPosition(e.currentTarget)
|
||||
if (this.movingContainer || this.paintContainer) {
|
||||
(this.movingContainer || this.paintContainer).moveTo(newCursorPos)
|
||||
}
|
||||
|
||||
if (G.keyboard.w !== G.keyboard.s || G.keyboard.a !== G.keyboard.d) return
|
||||
const newGridCoordsOfCursor = {
|
||||
x: (newCursorPos.x - newCursorPos.x % 32) / 32,
|
||||
y: (newCursorPos.y - newCursorPos.y % 32) / 32
|
||||
}
|
||||
if (newGridCoordsOfCursor.x !== G.gridCoordsOfCursor.x || newGridCoordsOfCursor.y !== G.gridCoordsOfCursor.y) {
|
||||
if (this.hoverContainer) {
|
||||
if (this.holdingRightClick) this.hoverContainer.removeContainer()
|
||||
if (this.holdingLeftClick && G.keyboard.shift) this.hoverContainer.pasteRecipe()
|
||||
}
|
||||
G.gridCoordsOfCursor = newGridCoordsOfCursor
|
||||
G.toolbarContainer.updateGridPos(G.gridCoordsOfCursor)
|
||||
}
|
||||
if (G.currentMouseState === G.mouseStates.PANNING) {
|
||||
const dX = G.app.renderer.plugins.interaction.mouse.global.x - this.lastCursorPos.x
|
||||
const dY = G.app.renderer.plugins.interaction.mouse.global.y - this.lastCursorPos.y
|
||||
this.zoomPan.translateBy(dX, dY)
|
||||
this.zoomPan.updateTransform()
|
||||
this.updateViewportCulling()
|
||||
}
|
||||
this.lastCursorPos = { ...G.app.renderer.plugins.interaction.mouse.global }
|
||||
}
|
||||
|
||||
pointerDownEventHandler(e: PIXI.interaction.InteractionEvent) {
|
||||
if (G.currentMouseState === G.mouseStates.NONE) {
|
||||
if (e.data.button === 0) {
|
||||
if (!G.openedGUIWindow && !G.keyboard.shift) {
|
||||
G.currentMouseState = G.mouseStates.PANNING
|
||||
}
|
||||
this.holdingLeftClick = true
|
||||
} else if (e.data.button === 2) {
|
||||
this.holdingRightClick = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pointerUpEventHandler(e: PIXI.interaction.InteractionEvent) {
|
||||
if (e.data.button === 0) {
|
||||
if (G.currentMouseState === G.mouseStates.PANNING) {
|
||||
G.currentMouseState = G.mouseStates.NONE
|
||||
}
|
||||
this.holdingLeftClick = false
|
||||
} else if (e.data.button === 2) {
|
||||
this.holdingRightClick = false
|
||||
}
|
||||
}
|
||||
}
|
||||
167
src/containers/editEntity.ts
Normal file
167
src/containers/editEntity.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
import factorioData from '../factorio-data/factorioData'
|
||||
import G from '../globals'
|
||||
import { InventoryContainer } from './inventory'
|
||||
import { EntityContainer } from './entity'
|
||||
|
||||
export class EditEntityContainer extends PIXI.Container {
|
||||
|
||||
content: PIXI.Container
|
||||
itemTooltip: PIXI.Text
|
||||
iconGutter = 32
|
||||
inventoryActiveGroup: PIXI.Sprite
|
||||
inventoryGroup: Map<PIXI.Sprite, PIXI.Container> = new Map()
|
||||
iWidth = 32 * 12
|
||||
iHeight = 32 * 13
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
this.visible = false
|
||||
this.interactive = true
|
||||
|
||||
this.setPosition()
|
||||
window.addEventListener('resize', () => this.setPosition(), false)
|
||||
|
||||
const background = new PIXI.Sprite(PIXI.Texture.WHITE)
|
||||
background.width = this.iWidth
|
||||
background.height = this.iHeight
|
||||
background.tint = 0x3A3A3A
|
||||
background.alpha = 0.9
|
||||
this.addChild(background)
|
||||
|
||||
this.content = new PIXI.Container()
|
||||
this.addChild(this.content)
|
||||
}
|
||||
|
||||
setPosition() {
|
||||
this.position.set(
|
||||
G.app.renderer.width / 2 - this.iWidth / 2,
|
||||
G.app.renderer.height / 2 - this.iHeight / 2
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: Refactor, optimize and make a layout system for this
|
||||
create(entity_number: number) {
|
||||
this.content.removeChildren()
|
||||
const entity = G.bp.entity(entity_number)
|
||||
|
||||
const cc = entity.entityData.crafting_categories
|
||||
if (cc && !cc.includes('rocket-building') && !cc.includes('smelting')) {
|
||||
const recipeContainer = new PIXI.Container()
|
||||
const background = new PIXI.Sprite(PIXI.Texture.WHITE)
|
||||
background.anchor.set(0.5, 0.5)
|
||||
background.width = 32
|
||||
background.height = 32
|
||||
background.tint = 0x9E9E9E
|
||||
recipeContainer.addChild(background)
|
||||
if (entity.recipe) recipeContainer.addChild(InventoryContainer.createIcon(factorioData.getItem(entity.recipe)))
|
||||
recipeContainer.position.set(
|
||||
this.iWidth / 2 + 16,
|
||||
this.iHeight / 2 - 18
|
||||
)
|
||||
recipeContainer.interactive = true
|
||||
recipeContainer.buttonMode = true
|
||||
|
||||
recipeContainer.on('pointerdown', (e: PIXI.interaction.InteractionEvent) => {
|
||||
e.stopPropagation()
|
||||
if (e.data.button === 0) {
|
||||
G.inventoryContainer.toggle(entity.acceptedRecipes, name => {
|
||||
G.openedGUIWindow = this
|
||||
if (entity.recipe !== name) {
|
||||
EntityContainer.mappings.get(entity_number).changeRecipe(name)
|
||||
this.create(entity_number)
|
||||
}
|
||||
})
|
||||
} else if (e.data.button === 2) {
|
||||
if (entity.recipe) {
|
||||
EntityContainer.mappings.get(entity_number).changeRecipe(undefined)
|
||||
this.create(entity_number)
|
||||
}
|
||||
}
|
||||
})
|
||||
this.content.addChild(recipeContainer)
|
||||
|
||||
const recipeText = new PIXI.Text('Recipe ')
|
||||
recipeText.anchor.set(1, 0.5)
|
||||
recipeText.position.set(
|
||||
this.iWidth / 2,
|
||||
this.iHeight / 2 - 18
|
||||
)
|
||||
recipeText.style.fill = 0xFFFFFF
|
||||
this.content.addChild(recipeText)
|
||||
}
|
||||
|
||||
if (entity.entityData.module_specification) {
|
||||
const moduleContainer = new PIXI.Container()
|
||||
moduleContainer.position.set(
|
||||
this.iWidth / 2 + 16,
|
||||
this.iHeight / 2 + 18
|
||||
)
|
||||
const slots = entity.entityData.module_specification.module_slots
|
||||
const modules = entity.modulesList
|
||||
for (let i = 0; i < slots; i++) {
|
||||
const slot = new PIXI.Container()
|
||||
slot.position.set(i * 36, 0)
|
||||
slot.interactive = true
|
||||
slot.buttonMode = true
|
||||
slot.on('pointerdown', (e: PIXI.interaction.InteractionEvent) => {
|
||||
e.stopPropagation()
|
||||
if (e.data.button === 0) {
|
||||
G.inventoryContainer.toggle(entity.acceptedModules, name => {
|
||||
G.openedGUIWindow = this
|
||||
if (modules && modules[i] !== name) {
|
||||
modules[modules.length] = name
|
||||
entity.modulesList = modules
|
||||
} else {
|
||||
entity.modulesList = [name]
|
||||
}
|
||||
EntityContainer.mappings.get(entity_number).redrawEntityInfo()
|
||||
this.create(entity_number)
|
||||
})
|
||||
} else if (e.data.button === 2) {
|
||||
if (modules && modules[i]) {
|
||||
modules.splice(i, 1)
|
||||
entity.modulesList = modules
|
||||
EntityContainer.mappings.get(entity_number).redrawEntityInfo()
|
||||
this.create(entity_number)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const background = new PIXI.Sprite(PIXI.Texture.WHITE)
|
||||
background.anchor.set(0.5, 0.5)
|
||||
background.width = 32
|
||||
background.height = 32
|
||||
background.tint = 0x9E9E9E
|
||||
slot.addChild(background)
|
||||
|
||||
if (modules && modules[i]) slot.addChild(InventoryContainer.createIcon(factorioData.getItem(modules[i])))
|
||||
|
||||
moduleContainer.addChild(slot)
|
||||
}
|
||||
this.content.addChild(moduleContainer)
|
||||
|
||||
const recipeText = new PIXI.Text('Modules ')
|
||||
recipeText.anchor.set(1, 0.5)
|
||||
recipeText.position.set(
|
||||
this.iWidth / 2,
|
||||
this.iHeight / 2 + 18
|
||||
)
|
||||
recipeText.style.fill = 0xFFFFFF
|
||||
this.content.addChild(recipeText)
|
||||
}
|
||||
|
||||
if (this.content.children.length !== 0) {
|
||||
this.visible = true
|
||||
G.openedGUIWindow = this
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
if (this.visible && G.openedGUIWindow !== this) {
|
||||
G.openedGUIWindow.close()
|
||||
}
|
||||
this.visible = false
|
||||
G.openedGUIWindow = undefined
|
||||
}
|
||||
}
|
||||
427
src/containers/entity.ts
Normal file
427
src/containers/entity.ts
Normal file
@@ -0,0 +1,427 @@
|
||||
import G from '../globals'
|
||||
import factorioData from '../factorio-data/factorioData'
|
||||
import { updateGroups } from '../updateGroups'
|
||||
import { isNumber } from 'util'
|
||||
import { EntitySprite } from '../entitySprite'
|
||||
import { UnderlayContainer } from './underlay'
|
||||
|
||||
export class EntityContainer extends PIXI.Container {
|
||||
static mappings: Map<number, EntityContainer> = new Map()
|
||||
|
||||
static getGridPosition(containerPosition: IPoint) {
|
||||
return {
|
||||
x: Math.round(containerPosition.x / 32 * 10) / 10,
|
||||
y: Math.round(containerPosition.y / 32 * 10) / 10
|
||||
}
|
||||
}
|
||||
|
||||
static getPositionFromData(currentPos: IPoint, size: IPoint) {
|
||||
const res = { x: 0, y: 0 }
|
||||
if (size.x % 2 === 0) {
|
||||
const npx = currentPos.x - currentPos.x % 16
|
||||
res.x = npx + (npx % 32 === 0 ? 0 : 16)
|
||||
} else {
|
||||
res.x = currentPos.x - currentPos.x % 32 + 16
|
||||
}
|
||||
if (size.y % 2 === 0) {
|
||||
const npy = currentPos.y - currentPos.y % 16
|
||||
res.y = npy + (npy % 32 === 0 ? 0 : 16)
|
||||
} else {
|
||||
res.y = currentPos.y - currentPos.y % 32 + 16
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
static isContainerOutOfBpArea(newPos: IPoint, size: IPoint) {
|
||||
return newPos.x - size.x / 2 < 0 ||
|
||||
newPos.y - size.y / 2 < 0 ||
|
||||
newPos.x + size.x / 2 > G.bpArea.width ||
|
||||
newPos.y + size.y / 2 > G.bpArea.height
|
||||
}
|
||||
|
||||
static getParts(entity: any, hr: boolean, ignore_connections?: boolean): EntitySprite[] {
|
||||
const anims = factorioData.getSpriteData(entity, hr, ignore_connections ? undefined : G.bp)
|
||||
|
||||
// const icon = new PIXI.Sprite(G.iconSprites['icon:' + factorioData.getEntity(entity.name).icon.split(':')[1]])
|
||||
// icon.x -= 16
|
||||
// icon.y -= 16
|
||||
// return [icon]
|
||||
|
||||
const parts: EntitySprite[] = []
|
||||
for (let i = 0, l = anims.length; i < l; i++) {
|
||||
const img = new EntitySprite(anims[i])
|
||||
|
||||
if (entity.name === 'straight-rail' || entity.name === 'curved-rail') {
|
||||
if (i < 2) {
|
||||
img.zIndex = -10
|
||||
} else if (i < 4) {
|
||||
img.zIndex = -9
|
||||
} else {
|
||||
img.zIndex = -8
|
||||
}
|
||||
} else if (entity.type === 'transport-belt' || entity.name === 'heat-pipe') {
|
||||
img.zIndex = i === 0 ? -7 : -6
|
||||
} else {
|
||||
img.zIndex = 0
|
||||
}
|
||||
img.zOrder = i
|
||||
|
||||
parts.push(img)
|
||||
}
|
||||
|
||||
return parts
|
||||
}
|
||||
|
||||
entity_number: number
|
||||
areaVisualization: PIXI.Sprite | PIXI.Sprite[] | undefined
|
||||
entityInfo: PIXI.Container
|
||||
entitySprites: EntitySprite[]
|
||||
|
||||
constructor(entity_number: number, sort = true) {
|
||||
super()
|
||||
this.entity_number = entity_number
|
||||
|
||||
EntityContainer.mappings.set(entity_number, this)
|
||||
|
||||
const entity = G.bp.entity(entity_number)
|
||||
this.position.set(
|
||||
entity.position.x * 32,
|
||||
entity.position.y * 32
|
||||
)
|
||||
|
||||
this.interactive = true
|
||||
this.interactiveChildren = false
|
||||
this.buttonMode = true
|
||||
|
||||
this.entitySprites = []
|
||||
|
||||
this.areaVisualization = G.BPC.underlayContainer.createNewArea(entity.name, this.position)
|
||||
this.entityInfo = G.BPC.overlayContainer.createEntityInfo(this.entity_number, this.position)
|
||||
|
||||
this.on('pointerdown', this.pointerDownEventHandler)
|
||||
// this.on('pointermove', this.pointerMoveEventHandler)
|
||||
this.on('pointerover', this.pointerOverEventHandler)
|
||||
this.on('pointerout', this.pointerOutEventHandler)
|
||||
|
||||
this.redraw(false, sort)
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (G.editEntityContainer.visible) G.editEntityContainer.close()
|
||||
|
||||
for (const s of this.entitySprites) s.destroy()
|
||||
|
||||
super.destroy()
|
||||
EntityContainer.mappings.delete(this.entity_number)
|
||||
|
||||
UnderlayContainer.modifyVisualizationArea(this.areaVisualization, s => s.destroy())
|
||||
G.BPC.overlayContainer.hideCursorBox()
|
||||
G.BPC.overlayContainer.hideUndergroundLines()
|
||||
|
||||
if (this.entityInfo) this.entityInfo.destroy()
|
||||
}
|
||||
|
||||
checkBuildable() {
|
||||
const position = EntityContainer.getGridPosition(this.position)
|
||||
const entity = G.bp.entity(this.entity_number)
|
||||
if (!EntityContainer.isContainerOutOfBpArea(position, entity.size) &&
|
||||
G.bp.entityPositionGrid.checkNoOverlap(entity.name, entity.direction, position)
|
||||
) {
|
||||
G.BPC.movingEntityFilter.red = 0.4
|
||||
G.BPC.movingEntityFilter.green = 1
|
||||
} else {
|
||||
G.BPC.movingEntityFilter.red = 1
|
||||
G.BPC.movingEntityFilter.green = 0.4
|
||||
}
|
||||
}
|
||||
|
||||
rotate() {
|
||||
const offset = {
|
||||
x: (this.x / 16 - G.gridCoords16.x) === 0 ? 0.5 : -0.5,
|
||||
y: (this.y / 16 - G.gridCoords16.y) === 0 ? 0.5 : -0.5
|
||||
}
|
||||
const entity = G.bp.entity(this.entity_number)
|
||||
let otherEntity
|
||||
if (G.currentMouseState === G.mouseStates.NONE && entity.type === 'underground-belt') {
|
||||
otherEntity = G.bp.entityPositionGrid.findEntityWithSameNameAndDirection(
|
||||
entity.name, entity.direction, entity.position,
|
||||
entity.directionType === 'input' ? entity.direction : (entity.direction + 4) % 8,
|
||||
entity.entityData.max_distance
|
||||
)
|
||||
if (isNumber(otherEntity)) {
|
||||
const oe = G.bp.entity(otherEntity)
|
||||
if (oe.directionType === entity.directionType) {
|
||||
otherEntity = undefined
|
||||
} else {
|
||||
oe.rotate(G.currentMouseState === G.mouseStates.NONE, { x: 0, y: 0 }, false)
|
||||
EntityContainer.mappings.get(otherEntity).redraw()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (G.bp.entity(this.entity_number).rotate(G.currentMouseState === G.mouseStates.NONE, offset, true, otherEntity)) {
|
||||
const entity = G.bp.entity(this.entity_number)
|
||||
if (G.currentMouseState === G.mouseStates.MOVING && entity.size.x !== entity.size.y) {
|
||||
this.x += offset.x * 32
|
||||
this.y += offset.y * 32
|
||||
const pos = EntityContainer.getPositionFromData(this.position, entity.size)
|
||||
this.position.set(pos.x, pos.y)
|
||||
|
||||
G.BPC.overlayContainer.updateCursorBoxPosition(this.position)
|
||||
}
|
||||
|
||||
this.redraw(G.currentMouseState === G.mouseStates.MOVING)
|
||||
if (G.currentMouseState === G.mouseStates.NONE) this.redrawSurroundingEntities()
|
||||
|
||||
G.BPC.overlayContainer.updateCursorBoxSize(entity.size.x, entity.size.y)
|
||||
this.updateUndergroundLines()
|
||||
|
||||
if (G.BPC.movingContainer === this) this.checkBuildable()
|
||||
|
||||
this.redrawEntityInfo()
|
||||
G.BPC.wiresContainer.update(this.entity_number)
|
||||
}
|
||||
}
|
||||
|
||||
updateUndergroundLines() {
|
||||
const entity = G.bp.entity(this.entity_number)
|
||||
G.BPC.overlayContainer.updateUndergroundLines(
|
||||
entity.name,
|
||||
{ x: this.position.x / 32, y: this.position.y / 32 },
|
||||
entity.direction,
|
||||
entity.directionType === 'output' || entity.name === 'pipe-to-ground' ? (entity.direction + 4) % 8 : entity.direction
|
||||
)
|
||||
}
|
||||
|
||||
pointerDownEventHandler(e: PIXI.interaction.InteractionEvent) {
|
||||
console.log(G.bp.entity(this.entity_number).toJS())
|
||||
if (e.data.button === 0) {
|
||||
if (G.currentMouseState === G.mouseStates.NONE && !G.openedGUIWindow && !G.keyboard.shift) {
|
||||
G.editEntityContainer.create(this.entity_number)
|
||||
}
|
||||
if (G.keyboard.shift) this.pasteRecipe()
|
||||
} else if (e.data.button === 1) {
|
||||
if (this !== G.BPC.movingContainer && G.currentMouseState === G.mouseStates.NONE) {
|
||||
G.bp.entityPositionGrid.removeTileData(this.entity_number, false)
|
||||
this.redraw(true)
|
||||
this.redrawSurroundingEntities()
|
||||
G.BPC.movingContainer = this
|
||||
G.currentMouseState = G.mouseStates.MOVING
|
||||
|
||||
// Move container to cursor
|
||||
const newPosition = e.data.getLocalPosition(this.parent)
|
||||
const pos = EntityContainer.getPositionFromData(newPosition, G.bp.entity(this.entity_number).size)
|
||||
|
||||
if (this.position.x !== pos.x || this.position.y !== pos.y) {
|
||||
this.position.set(pos.x, pos.y)
|
||||
this.updateVisualStuff()
|
||||
}
|
||||
|
||||
G.gridCoords16 = {
|
||||
x: (newPosition.x - newPosition.x % 16) / 16,
|
||||
y: (newPosition.y - newPosition.y % 16) / 16
|
||||
}
|
||||
|
||||
for (const s of this.entitySprites) s.moving = true
|
||||
G.BPC.sortEntities()
|
||||
G.BPC.underlayContainer.activateRelatedAreas(G.bp.entity(this.entity_number).name)
|
||||
|
||||
G.BPC.updateOverlay()
|
||||
return
|
||||
}
|
||||
if (this === G.BPC.movingContainer && G.currentMouseState === G.mouseStates.MOVING) {
|
||||
this.placeEntityContainerDown()
|
||||
}
|
||||
} else if (e.data.button === 2 && G.currentMouseState === G.mouseStates.NONE) {
|
||||
if (G.keyboard.shift) {
|
||||
G.copyData.recipe = G.bp.entity(this.entity_number).recipe
|
||||
} else {
|
||||
G.BPC.holdingRightClick = true
|
||||
this.removeContainer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changeRecipe(recipeName: string) {
|
||||
const entity = G.bp.entity(this.entity_number)
|
||||
entity.recipe = recipeName
|
||||
this.redrawEntityInfo()
|
||||
if (entity.name === 'chemical-plant' || entity.assemblerCraftsWithFluid || G.bp.entity(this.entity_number).assemblerCraftsWithFluid) {
|
||||
this.redraw()
|
||||
this.redrawSurroundingEntities()
|
||||
}
|
||||
}
|
||||
|
||||
pasteRecipe() {
|
||||
const entity = G.bp.entity(this.entity_number)
|
||||
if (!entity.entityData.crafting_categories) return
|
||||
const RECIPE = G.copyData.recipe && entity.acceptedRecipes.includes(G.copyData.recipe) ? G.copyData.recipe : undefined
|
||||
if (entity.recipe !== RECIPE) this.changeRecipe(RECIPE)
|
||||
}
|
||||
|
||||
redrawEntityInfo() {
|
||||
const entity = G.bp.entity(this.entity_number)
|
||||
if (entity.entityData.module_specification || entity.type === 'splitter' ||
|
||||
entity.entityData.crafting_categories || entity.type === 'mining-drill' ||
|
||||
entity.type === 'boiler' || entity.type === 'generator' ||
|
||||
entity.name === 'pump' || entity.name === 'offshore-pump' ||
|
||||
entity.name === 'arithmetic-combinator' || entity.name === 'decider-combinator'
|
||||
) {
|
||||
if (this.entityInfo) this.entityInfo.destroy()
|
||||
this.entityInfo = G.BPC.overlayContainer.createEntityInfo(this.entity_number, this.position)
|
||||
}
|
||||
}
|
||||
|
||||
updateVisualStuff() {
|
||||
for (const s of this.entitySprites) s.setPosition(this.position)
|
||||
|
||||
UnderlayContainer.modifyVisualizationArea(this.areaVisualization, s => s.position.copy(this.position))
|
||||
|
||||
if (this.entityInfo) this.entityInfo.position = this.position
|
||||
|
||||
G.BPC.overlayContainer.updateCursorBoxPosition(this.position)
|
||||
G.BPC.overlayContainer.updateUndergroundLinesPosition(this.position)
|
||||
this.updateUndergroundLines()
|
||||
|
||||
G.BPC.wiresContainer.update(this.entity_number)
|
||||
|
||||
this.checkBuildable()
|
||||
}
|
||||
|
||||
removeContainer() {
|
||||
G.BPC.wiresContainer.remove(this.entity_number)
|
||||
G.bp.entityPositionGrid.removeTileData(this.entity_number, false)
|
||||
this.redrawSurroundingEntities()
|
||||
G.bp.removeEntity(this.entity_number,
|
||||
entity_number => EntityContainer.mappings.get(entity_number).redraw()
|
||||
)
|
||||
G.BPC.hoverContainer = undefined
|
||||
|
||||
G.BPC.updateOverlay()
|
||||
this.destroy()
|
||||
}
|
||||
|
||||
// pointerMoveEventHandler(e: PIXI.interaction.InteractionEvent) {
|
||||
// this.moveTo(e.data.getLocalPosition(this.parent))
|
||||
// }
|
||||
|
||||
moveTo(newPosition: IPoint) {
|
||||
if (G.BPC.movingContainer === this && G.currentMouseState === G.mouseStates.MOVING) {
|
||||
const newCursorPos = {
|
||||
x: (newPosition.x - newPosition.x % 16) / 16,
|
||||
y: (newPosition.y - newPosition.y % 16) / 16
|
||||
}
|
||||
if (newCursorPos.x !== G.gridCoords16.x || newCursorPos.y !== G.gridCoords16.y) {
|
||||
const entity = G.bp.entity(this.entity_number)
|
||||
switch (entity.name) {
|
||||
case 'straight-rail':
|
||||
case 'curved-rail':
|
||||
case 'train-stop':
|
||||
this.x = newPosition.x - (newPosition.x + G.railMoveOffset.x * 32) % 64 + 32
|
||||
this.y = newPosition.y - (newPosition.y + G.railMoveOffset.y * 32) % 64 + 32
|
||||
break
|
||||
default:
|
||||
const pos = EntityContainer.getPositionFromData(newPosition, entity.size)
|
||||
this.position.set(pos.x, pos.y)
|
||||
}
|
||||
|
||||
this.updateVisualStuff()
|
||||
|
||||
G.gridCoords16 = newCursorPos
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pointerOverEventHandler() {
|
||||
// Pointer over is sometimes getting called before pointer out
|
||||
if (G.BPC.hoverContainer && G.BPC.hoverContainer !== this) G.BPC.hoverContainer.pointerOutEventHandler()
|
||||
if (!G.BPC.movingContainer && !G.BPC.paintContainer) {
|
||||
G.BPC.hoverContainer = this
|
||||
|
||||
const entity = G.bp.entity(this.entity_number)
|
||||
G.BPC.overlayContainer.updateCursorBoxSize(entity.size.x, entity.size.y)
|
||||
G.BPC.overlayContainer.updateCursorBoxPosition(this.position)
|
||||
G.BPC.overlayContainer.showCursorBox()
|
||||
G.BPC.overlayContainer.updateUndergroundLinesPosition(this.position)
|
||||
this.updateUndergroundLines()
|
||||
|
||||
UnderlayContainer.modifyVisualizationArea(this.areaVisualization, s => s.visible = true)
|
||||
}
|
||||
}
|
||||
|
||||
pointerOutEventHandler() {
|
||||
if (!G.BPC.movingContainer && !G.BPC.paintContainer && G.BPC.hoverContainer === this) {
|
||||
G.BPC.hoverContainer = undefined
|
||||
G.BPC.overlayContainer.hideCursorBox()
|
||||
G.BPC.overlayContainer.hideUndergroundLines()
|
||||
UnderlayContainer.modifyVisualizationArea(this.areaVisualization, s => s.visible = false)
|
||||
}
|
||||
}
|
||||
|
||||
placeEntityContainerDown() {
|
||||
const entity = G.bp.entity(this.entity_number)
|
||||
const position = EntityContainer.getGridPosition(this.position)
|
||||
if (EntityContainer.isContainerOutOfBpArea(position, entity.size)) return
|
||||
if (G.currentMouseState === G.mouseStates.MOVING && entity.move(position)) {
|
||||
G.BPC.movingContainer = undefined
|
||||
G.currentMouseState = G.mouseStates.NONE
|
||||
|
||||
for (const s of this.entitySprites) s.moving = false
|
||||
|
||||
this.redraw(false)
|
||||
this.redrawSurroundingEntities()
|
||||
|
||||
G.BPC.underlayContainer.deactivateActiveAreas()
|
||||
|
||||
G.BPC.updateOverlay()
|
||||
}
|
||||
}
|
||||
|
||||
redrawSurroundingEntities() {
|
||||
const entity = G.bp.entity(this.entity_number)
|
||||
const redrawnEntities: number[] = []
|
||||
for (const updateGroup of updateGroups) {
|
||||
const j = updateGroup.is.indexOf(entity.name)
|
||||
if (j !== -1) {
|
||||
if (entity.name === 'straight-rail') {
|
||||
G.bp.entityPositionGrid.foreachOverlap(entity.getArea(), (entnr: number) => {
|
||||
const ent = G.bp.entity(entnr)
|
||||
if (ent.name === 'gate' && !redrawnEntities.includes(entnr)) {
|
||||
EntityContainer.mappings.get(ent.entity_number).redraw()
|
||||
redrawnEntities.push(entnr)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
G.bp.entityPositionGrid.getSurroundingEntities(entity.getArea(), (entnr: number) => {
|
||||
const ent = G.bp.entity(entnr)
|
||||
if (updateGroup.updates.includes(ent.name) && !redrawnEntities.includes(entnr)) {
|
||||
EntityContainer.mappings.get(ent.entity_number).redraw()
|
||||
redrawnEntities.push(entnr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
redraw(ignore_connections?: boolean, sort = true) {
|
||||
const entity = G.bp.entity(this.entity_number)
|
||||
|
||||
for (const s of this.entitySprites) s.destroy()
|
||||
this.entitySprites = []
|
||||
for (const s of EntityContainer.getParts(entity, true, ignore_connections)) {
|
||||
if (G.BPC.movingContainer === this) s.moving = true
|
||||
s.setPosition(this.position)
|
||||
this.entitySprites.push(s)
|
||||
G.BPC.entitySprites.addChild(s)
|
||||
}
|
||||
if (sort) G.BPC.sortEntities()
|
||||
|
||||
this.hitArea = new PIXI.Rectangle(
|
||||
-entity.size.x * 16,
|
||||
-entity.size.y * 16,
|
||||
entity.size.x * 32,
|
||||
entity.size.y * 32
|
||||
)
|
||||
}
|
||||
}
|
||||
135
src/containers/info.ts
Normal file
135
src/containers/info.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import G from '../globals'
|
||||
|
||||
export class InfoContainer extends PIXI.Container {
|
||||
|
||||
iWidth = 32 * 18
|
||||
iHeight = 32 * 24
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
this.visible = false
|
||||
this.interactive = false
|
||||
this.interactiveChildren = false
|
||||
|
||||
this.setPosition()
|
||||
window.addEventListener('resize', () => this.setPosition(), false)
|
||||
|
||||
const background = new PIXI.Sprite(PIXI.Texture.WHITE)
|
||||
background.width = this.iWidth
|
||||
background.height = this.iHeight
|
||||
background.tint = 0x3A3A3A
|
||||
background.alpha = 0.9
|
||||
this.addChild(background)
|
||||
|
||||
const text = new PIXI.Text('KEYBINDS')
|
||||
text.position.set(this.iWidth / 2, 4)
|
||||
text.style.fontSize = 24
|
||||
text.style.fontWeight = 'bold'
|
||||
text.style.fill = 0xFFFFFF
|
||||
text.anchor.set(0.5, 0)
|
||||
this.addChild(text)
|
||||
|
||||
this.writeColumn([
|
||||
'While hovering over an entity',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'In editor window',
|
||||
'',
|
||||
'',
|
||||
'Others'
|
||||
], { x: this.iWidth / 2, y: 40 }, 0.5, true)
|
||||
|
||||
this.writeColumn([
|
||||
'',
|
||||
'left click',
|
||||
'middle click',
|
||||
'right click',
|
||||
'R',
|
||||
'Q',
|
||||
'',
|
||||
'left click recipe/module',
|
||||
'right click recipe/module',
|
||||
'',
|
||||
'ctrl + Z/Y',
|
||||
'ctrl + C/V',
|
||||
'ctrl + S',
|
||||
'shift + N',
|
||||
'shift + right/left click',
|
||||
'alt',
|
||||
'esc',
|
||||
'E',
|
||||
'F',
|
||||
'W/A/S/D',
|
||||
'click + drag in blueprint area',
|
||||
'mouse wheel'
|
||||
], { x: this.iWidth / 2 - 4, y: 40 }, 1)
|
||||
|
||||
this.writeColumn([
|
||||
'',
|
||||
'open editor window',
|
||||
'move',
|
||||
'remove',
|
||||
'rotate',
|
||||
'pippete tool/clear cursor',
|
||||
'',
|
||||
'choose',
|
||||
'remove',
|
||||
'',
|
||||
'undo/redo changes',
|
||||
'copy/paste bpstring',
|
||||
'generate bp picture',
|
||||
'clear bp',
|
||||
'copy/paste recipe',
|
||||
'toggle overlay',
|
||||
'close active window',
|
||||
'open inventory or close active window',
|
||||
'focuses viewport on blueprint',
|
||||
'move',
|
||||
'move',
|
||||
'zoom in/out'
|
||||
], { x: this.iWidth / 2 + 4, y: 40 })
|
||||
|
||||
this.writeColumn([
|
||||
'You can load a blueprint from a bp string, pastebin, hastebin, gist, gitlab,',
|
||||
' factorioprints, google docs or text webpages.',
|
||||
'You can also add ?source=<BPSTRING_OR_URL_TO_BPSTRING> to the url',
|
||||
' to make sharing easier.',
|
||||
'Adding renderOnly as an url query parameter will only render the bp.',
|
||||
'I don\'t show network or parsing errors in the app yet, you can open the console',
|
||||
' (F12) to check if something is wrong.',
|
||||
'Entities with placeable-off-grid flag will not be added to the positionGrid',
|
||||
' (ex. landmine).',
|
||||
'',
|
||||
'Factorio assets come directly from the Factorio game files, and are subject to',
|
||||
' all copyright policies associated with the game.'
|
||||
], { x: 4, y: 500 })
|
||||
}
|
||||
|
||||
writeColumn(data: string[], offset: IPoint, anchorX = 0, bold = false) {
|
||||
let nextY = 0
|
||||
for (const str of data) {
|
||||
const text = new PIXI.Text(str)
|
||||
text.position.set(offset.x, nextY++ * 20 + offset.y)
|
||||
text.style.fontSize = 16
|
||||
if (bold) text.style.fontWeight = 'bold'
|
||||
text.style.fill = 0xFFFFFF
|
||||
text.anchor.set(anchorX, 0)
|
||||
this.addChild(text)
|
||||
}
|
||||
}
|
||||
|
||||
setPosition() {
|
||||
this.position.set(
|
||||
G.app.renderer.width / 2 - this.iWidth / 2,
|
||||
G.app.renderer.height / 2 - this.iHeight / 2
|
||||
)
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this.visible = !this.visible
|
||||
}
|
||||
}
|
||||
248
src/containers/inventory.ts
Normal file
248
src/containers/inventory.ts
Normal file
@@ -0,0 +1,248 @@
|
||||
import inventoryBundle from '../bundles/inventoryBundle.json'
|
||||
import factorioData from '../factorio-data/factorioData'
|
||||
import { AdjustmentFilter } from '@pixi/filter-adjustment'
|
||||
import util from '../util'
|
||||
import G from '../globals'
|
||||
import { PaintContainer } from './paint'
|
||||
import { isArray } from 'util'
|
||||
|
||||
export class InventoryContainer extends PIXI.Container {
|
||||
|
||||
static createIcon(item: any) {
|
||||
if (item.icon) {
|
||||
const icon = PIXI.Sprite.fromFrame(item.icon)
|
||||
icon.anchor.set(0.5, 0.5)
|
||||
return icon
|
||||
}
|
||||
if (item.icons) {
|
||||
const img = new PIXI.Container()
|
||||
for (const icon of item.icons) {
|
||||
const sprite = PIXI.Sprite.fromFrame(icon.icon)
|
||||
if (icon.scale) sprite.scale.set(icon.scale, icon.scale)
|
||||
if (icon.shift) sprite.position.set(icon.shift[0], icon.shift[1])
|
||||
if (icon.tint) {
|
||||
const t = icon.tint
|
||||
sprite.filters = [new AdjustmentFilter({
|
||||
red: t.r,
|
||||
green: t.g,
|
||||
blue: t.b,
|
||||
alpha: t.a
|
||||
})]
|
||||
}
|
||||
sprite.anchor.set(0.5, 0.5)
|
||||
img.addChild(sprite)
|
||||
}
|
||||
return img
|
||||
}
|
||||
}
|
||||
|
||||
recipeVisualization: PIXI.Container
|
||||
inventoryContents: PIXI.Container
|
||||
itemTooltip: PIXI.Text
|
||||
iconGutter = 36
|
||||
inventoryActiveGroup: PIXI.Sprite
|
||||
inventoryGroup: Map<PIXI.Sprite, PIXI.Container> = new Map()
|
||||
iWidth = 32 * 12
|
||||
iHeight = 32 * 13
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
this.visible = false
|
||||
this.interactive = true
|
||||
|
||||
this.setPosition()
|
||||
window.addEventListener('resize', () => this.setPosition(), false)
|
||||
|
||||
const background = new PIXI.Sprite(PIXI.Texture.WHITE)
|
||||
background.width = this.iWidth
|
||||
background.height = this.iHeight
|
||||
background.tint = 0x3A3A3A
|
||||
background.alpha = 0.9
|
||||
this.addChild(background)
|
||||
|
||||
this.inventoryContents = new PIXI.Container()
|
||||
this.addChild(this.inventoryContents)
|
||||
|
||||
this.itemTooltip = new PIXI.Text('')
|
||||
this.itemTooltip.style.fill = 0xFFFFFF
|
||||
this.itemTooltip.y = 352
|
||||
this.addChild(this.itemTooltip)
|
||||
|
||||
this.recipeVisualization = new PIXI.Container()
|
||||
this.recipeVisualization.position.set(16, 384 + 16)
|
||||
this.addChild(this.recipeVisualization)
|
||||
}
|
||||
|
||||
setPosition() {
|
||||
this.position.set(
|
||||
G.app.renderer.width / 2 - this.iWidth / 2,
|
||||
G.app.renderer.height / 2 - this.iHeight / 2
|
||||
)
|
||||
}
|
||||
|
||||
create(filteredItems?: string[], cb?: (name: string) => void) {
|
||||
this.itemTooltip.text = ''
|
||||
this.recipeVisualization.visible = false
|
||||
this.inventoryContents.removeChildren()
|
||||
|
||||
let nextI = 0
|
||||
let groupHasItem = false
|
||||
for (let i = 0, l = inventoryBundle.length; i < l; i++) {
|
||||
|
||||
const grObj = new PIXI.Container()
|
||||
let nextK = 0
|
||||
let nextJ = 0
|
||||
let subgroupHasItem = false
|
||||
for (const subgroup of inventoryBundle[i].subgroups) {
|
||||
for (const item of subgroup.items) {
|
||||
const placeResult = factorioData.getItem(item.name).place_result
|
||||
if ((!filteredItems && placeResult && factorioData.getEntity(placeResult)) ||
|
||||
filteredItems && filteredItems.includes(item.name)
|
||||
) {
|
||||
const img = InventoryContainer.createIcon(item)
|
||||
|
||||
if (nextK > 9) {
|
||||
nextJ++
|
||||
nextK = 0
|
||||
}
|
||||
|
||||
img.x = nextK * this.iconGutter + 16
|
||||
img.y = 64 + nextJ * this.iconGutter + 16
|
||||
img.interactive = true
|
||||
img.buttonMode = true
|
||||
|
||||
if (filteredItems && filteredItems.includes(item.name)) {
|
||||
img.on('pointerdown', (e: PIXI.interaction.InteractionEvent) => {
|
||||
if (e.data.button === 0) {
|
||||
cb(item.name)
|
||||
this.visible = false
|
||||
}
|
||||
})
|
||||
} else {
|
||||
img.on('pointerdown', (e: PIXI.interaction.InteractionEvent) => {
|
||||
if (e.data.button === 0) {
|
||||
G.currentMouseState = G.mouseStates.PAINTING
|
||||
|
||||
const newPosition = e.data.getLocalPosition(G.BPC)
|
||||
const size = util.switchSizeBasedOnDirection(factorioData.getEntity(placeResult).size, 0)
|
||||
G.BPC.paintContainer = new PaintContainer(placeResult, 0, {
|
||||
x: newPosition.x - newPosition.x % 32 + (size.x % 2 * 16),
|
||||
y: newPosition.y - newPosition.y % 32 + (size.y % 2 * 16)
|
||||
})
|
||||
G.BPC.addChild(G.BPC.paintContainer)
|
||||
this.visible = false
|
||||
}
|
||||
})
|
||||
}
|
||||
img.on('pointerover', () => {
|
||||
this.itemTooltip.text = item.name.split('-').map((s: any) => s.charAt(0).toUpperCase() + s.slice(1)).join(' ')
|
||||
this.createRecipeVisualization(item.name)
|
||||
})
|
||||
img.on('pointerout', () => {
|
||||
this.itemTooltip.text = ''
|
||||
this.recipeVisualization.visible = false
|
||||
})
|
||||
|
||||
grObj.addChild(img)
|
||||
groupHasItem = true
|
||||
subgroupHasItem = true
|
||||
nextK++
|
||||
}
|
||||
}
|
||||
if (subgroupHasItem) nextJ++
|
||||
subgroupHasItem = false
|
||||
nextK = 0
|
||||
}
|
||||
|
||||
if (groupHasItem) {
|
||||
const img = PIXI.Sprite.fromFrame(inventoryBundle[i].icon)
|
||||
img.x = nextI * 64
|
||||
img.y = 0
|
||||
img.interactive = true
|
||||
img.buttonMode = true
|
||||
img.on('pointerdown', (e: PIXI.interaction.InteractionEvent) => {
|
||||
if (e.data.button === 0) {
|
||||
if (img !== this.inventoryActiveGroup) {
|
||||
this.inventoryGroup.get(this.inventoryActiveGroup).visible = false
|
||||
this.inventoryActiveGroup = img
|
||||
this.inventoryGroup.get(img).visible = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (nextI === 0) this.inventoryActiveGroup = img
|
||||
else grObj.visible = false
|
||||
|
||||
this.inventoryGroup.set(img, grObj)
|
||||
this.inventoryContents.addChild(img, grObj)
|
||||
|
||||
nextI++
|
||||
groupHasItem = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toggle(filteredItems?: string[], cb?: (name: string) => void) {
|
||||
if (!this.visible) {
|
||||
this.create(filteredItems, cb)
|
||||
this.visible = true
|
||||
G.openedGUIWindow = this
|
||||
} else {
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
this.visible = false
|
||||
G.openedGUIWindow = G.editEntityContainer.visible ? G.editEntityContainer : undefined
|
||||
}
|
||||
|
||||
createRecipeVisualization(recipeName: string) {
|
||||
const RECIPE = factorioData.getRecipe(recipeName)
|
||||
if (!RECIPE) return
|
||||
this.recipeVisualization.removeChildren()
|
||||
|
||||
const recipe = RECIPE.normal ? RECIPE.normal : RECIPE
|
||||
// TODO: maybe normalize the recipeBundle trough script and not here at runtime
|
||||
const time = (recipe.energy_required !== undefined ? recipe.energy_required : RECIPE.energy_required) || 0.5
|
||||
const ingredients = recipe.ingredients.map((o: any) => isArray(o) ? o : [o.name, o.amount])
|
||||
const results = recipe.result ? [[recipe.result, recipe.result_count || 1]] :
|
||||
recipe.results.map((o: any) => [o.name, o.probability ? o.probability * o.amount : o.amount])
|
||||
|
||||
let nextX = 0
|
||||
for (const i of ingredients) {
|
||||
const s = InventoryContainer.createIcon(factorioData.getItem(i[0]))
|
||||
s.x = nextX * 36
|
||||
this.recipeVisualization.addChild(s, createAmountText(i[1]))
|
||||
nextX++
|
||||
}
|
||||
|
||||
const text = new PIXI.Text(`=${time}s>`)
|
||||
text.style.fontSize = 13
|
||||
text.style.fontWeight = 'bold'
|
||||
text.style.fill = 0xFFFFFF
|
||||
text.anchor.set(0.5, 0.5)
|
||||
text.x = nextX++ * 36
|
||||
this.recipeVisualization.addChild(text)
|
||||
|
||||
for (const r of results) {
|
||||
const s = InventoryContainer.createIcon(factorioData.getItem(r[0]))
|
||||
s.x = nextX * 36
|
||||
this.recipeVisualization.addChild(s, createAmountText(r[1]))
|
||||
nextX++
|
||||
}
|
||||
|
||||
function createAmountText(amount: string) {
|
||||
const text = new PIXI.Text(amount)
|
||||
text.style.fontSize = 13
|
||||
text.style.fontWeight = 'bold'
|
||||
text.style.fill = 0xFFFFFF
|
||||
text.anchor.set(1, 1)
|
||||
text.position.set(nextX * 36 + 16, 16)
|
||||
return text
|
||||
}
|
||||
|
||||
this.recipeVisualization.visible = true
|
||||
}
|
||||
}
|
||||
387
src/containers/overlay.ts
Normal file
387
src/containers/overlay.ts
Normal file
@@ -0,0 +1,387 @@
|
||||
import factorioData from '../factorio-data/factorioData'
|
||||
import { InventoryContainer } from './inventory'
|
||||
import G from '../globals'
|
||||
import util from '../util'
|
||||
|
||||
export class OverlayContainer extends PIXI.Container {
|
||||
|
||||
undergroundLines: PIXI.Container
|
||||
cursorBox: PIXI.Container
|
||||
overlay: PIXI.Container
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
this.interactive = false
|
||||
this.interactiveChildren = false
|
||||
|
||||
this.overlay = new PIXI.Container()
|
||||
|
||||
this.cursorBox = new PIXI.Container()
|
||||
this.cursorBox.scale.set(0.5, 0.5)
|
||||
this.cursorBox.visible = false
|
||||
|
||||
this.undergroundLines = new PIXI.Container()
|
||||
|
||||
this.addChild(this.overlay, this.cursorBox, this.undergroundLines)
|
||||
}
|
||||
|
||||
createEntityInfo(entity_number: number, position: IPoint) {
|
||||
const entity = G.bp.entity(entity_number)
|
||||
const entityInfo = new PIXI.Container()
|
||||
|
||||
if (entity.recipe && entity.recipe !== 'rocket-part') {
|
||||
const recipeInfo = new PIXI.Container()
|
||||
createIconWithBackground(recipeInfo, entity.recipe)
|
||||
const S = entity.name === 'oil-refinery' ? 1.5 : 0.9
|
||||
recipeInfo.scale.set(S, S)
|
||||
recipeInfo.position.set(0, -10)
|
||||
entityInfo.addChild(recipeInfo)
|
||||
|
||||
const fluidIcons = new PIXI.Container()
|
||||
const recipeData = factorioData.getRecipe(entity.recipe)
|
||||
const rD = recipeData.normal ? recipeData.normal : recipeData
|
||||
switch (recipeData.category) {
|
||||
case 'oil-processing':
|
||||
case 'chemistry':
|
||||
const inputPositions: IPoint[] = []
|
||||
const outputPositions: IPoint[] = []
|
||||
for (const fb of entity.entityData.fluid_boxes) {
|
||||
(fb.production_type === 'input' ? inputPositions : outputPositions).push({
|
||||
x: fb.pipe_connections[0].position[0],
|
||||
y: fb.pipe_connections[0].position[1]
|
||||
})
|
||||
}
|
||||
function createIconsForType(type: string) {
|
||||
const iconNames: string[] = []
|
||||
for (const io of type === 'input' ? rD.ingredients : rD.results) {
|
||||
if (io.type === 'fluid') {
|
||||
iconNames.push(io.name)
|
||||
}
|
||||
}
|
||||
if (iconNames.length !== 0) {
|
||||
const positions = type === 'input' ? inputPositions : outputPositions
|
||||
for (let i = 0; i < positions.length; i++) {
|
||||
const position = util.transformConnectionPosition(positions[i], entity.direction)
|
||||
createIconWithBackground(
|
||||
fluidIcons,
|
||||
i > iconNames.length - 1 ? iconNames[0] : iconNames[i],
|
||||
{ x: position.x * 64, y: position.y * 64 }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
createIconsForType('input')
|
||||
if (rD.results) createIconsForType('output')
|
||||
break
|
||||
case 'crafting-with-fluid':
|
||||
function createIconForType(type: string) {
|
||||
for (const io of type === 'input' ? rD.ingredients : rD.results) {
|
||||
if (io.type === 'fluid') {
|
||||
const position = util.rotatePointBasedOnDir(entity.entityData.fluid_boxes.find(
|
||||
(fb: any) => fb.production_type === type).pipe_connections[0].position,
|
||||
entity.direction
|
||||
)
|
||||
createIconWithBackground(
|
||||
fluidIcons,
|
||||
io.name,
|
||||
{ x: position.x * 32, y: position.y * 32 }
|
||||
)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
createIconForType(entity.assemblerPipeDirection)
|
||||
}
|
||||
fluidIcons.scale.set(0.5, 0.5)
|
||||
if (fluidIcons.children.length !== 0) entityInfo.addChild(fluidIcons)
|
||||
}
|
||||
|
||||
if (entity.modules) {
|
||||
const moduleInfo = new PIXI.Container()
|
||||
const shift = entity.entityData.module_specification.module_info_icon_shift
|
||||
const mL = entity.modulesList
|
||||
for (let i = 0; i < mL.length; i++) {
|
||||
createIconWithBackground(moduleInfo, mL[i], { x: i * 32, y: 0 })
|
||||
}
|
||||
moduleInfo.scale.set(0.5, 0.5)
|
||||
moduleInfo.position.set((shift ? shift[0] : 0) * 32 - mL.length * 8 + 8, (shift ? shift[1] : 0.75) * 32)
|
||||
entityInfo.addChild(moduleInfo)
|
||||
}
|
||||
|
||||
const filters = entity.inserterFilters || entity.logisticChestFilters || entity.constantCombinatorFilters
|
||||
if (filters) {
|
||||
const filterInfo = new PIXI.Container()
|
||||
for (let i = 0; i < filters.length; i++) {
|
||||
if (i === 4) break
|
||||
createIconWithBackground(
|
||||
filterInfo,
|
||||
filters[i].name || filters[i].signal.name,
|
||||
{ x: i % 2 * 32 - (filters.length !== 1 ? 16 : 0), y: filters.length < 3 ? 0 : (i < 2 ? -16 : 16)}
|
||||
)
|
||||
}
|
||||
let S = 0.5
|
||||
if (entity.inserterFilters && filters.length !== 1) S = 0.4
|
||||
if (entity.logisticChestFilters && filters.length === 1) S = 0.6
|
||||
filterInfo.scale.set(S, S)
|
||||
entityInfo.addChild(filterInfo)
|
||||
}
|
||||
|
||||
const combinatorConditions = entity.deciderCombinatorConditions || entity.arithmeticCombinatorConditions
|
||||
if (combinatorConditions) {
|
||||
const filterInfo = new PIXI.Container()
|
||||
const cFS = combinatorConditions.first_signal
|
||||
const cSS = combinatorConditions.second_signal
|
||||
const cOS = combinatorConditions.output_signal
|
||||
if (cFS) createIconWithBackground(filterInfo, cFS.name, { x: cSS ? -16 : 0, y: -16 })
|
||||
if (cSS) createIconWithBackground(filterInfo, cSS.name, { x: 16, y: -16 })
|
||||
if (cOS) createIconWithBackground(filterInfo, cOS.name, { x: 0, y: 16 })
|
||||
filterInfo.scale.set(0.5, 0.5)
|
||||
if (filterInfo.children.length !== 0) entityInfo.addChild(filterInfo)
|
||||
}
|
||||
|
||||
if (entity.type === 'boiler' || entity.type === 'generator') {
|
||||
const filteredFluidInputs = new PIXI.Container()
|
||||
function generateIconsForFluidBox(fluidBox: any) {
|
||||
for (const c of fluidBox.pipe_connections) {
|
||||
const position = util.transformConnectionPosition({ x: c.position[0], y: c.position[1] }, entity.direction)
|
||||
createIconWithBackground(
|
||||
filteredFluidInputs,
|
||||
fluidBox.filter,
|
||||
{ x: position.x * 64, y: position.y * 64 }
|
||||
)
|
||||
}
|
||||
}
|
||||
generateIconsForFluidBox(entity.entityData.fluid_box)
|
||||
if (entity.entityData.output_fluid_box) generateIconsForFluidBox(entity.entityData.output_fluid_box)
|
||||
filteredFluidInputs.scale.set(0.5, 0.5)
|
||||
entityInfo.addChild(filteredFluidInputs)
|
||||
}
|
||||
|
||||
if (entity.splitterInputPriority || entity.splitterOutputPriority) {
|
||||
const filterInfo = new PIXI.Container()
|
||||
if (entity.splitterFilter) {
|
||||
createIconWithBackground(
|
||||
filterInfo,
|
||||
entity.splitterFilter,
|
||||
util.rotatePointBasedOnDir({ x: entity.splitterOutputPriority === 'right' ? 32 : -32, y: 0 }, entity.direction)
|
||||
)
|
||||
} else if (entity.splitterOutputPriority) createArrowForDirection(entity.splitterOutputPriority, -16)
|
||||
if (entity.splitterInputPriority) createArrowForDirection(entity.splitterInputPriority, 16)
|
||||
|
||||
function createArrowForDirection(direction: string, offsetY: number) {
|
||||
const arrow = createArrow(util.rotatePointBasedOnDir({ x: direction === 'right' ? 32 : -32, y: offsetY }, entity.direction))
|
||||
arrow.scale.set(0.75, 0.75)
|
||||
arrow.rotation = entity.direction * Math.PI * 0.25
|
||||
filterInfo.addChild(arrow)
|
||||
}
|
||||
filterInfo.scale.set(0.5, 0.5)
|
||||
entityInfo.addChild(filterInfo)
|
||||
}
|
||||
|
||||
if (entity.name === 'arithmetic-combinator' || entity.name === 'decider-combinator') {
|
||||
const arrows = new PIXI.Container()
|
||||
arrows.addChild(createArrow({ x: 0, y: -48 }), createArrow({ x: 0, y: 48 }))
|
||||
arrows.rotation = entity.direction * Math.PI * 0.25
|
||||
arrows.scale.set(0.5, 0.5)
|
||||
entityInfo.addChild(arrows)
|
||||
}
|
||||
|
||||
if (entity.type === 'mining-drill' && entity.name !== 'pumpjack') {
|
||||
const arrows = new PIXI.Container()
|
||||
arrows.addChild(createArrow({
|
||||
x: entity.entityData.vector_to_place_result[0] * 64,
|
||||
y: entity.entityData.vector_to_place_result[1] * 64 + 18
|
||||
}))
|
||||
arrows.rotation = entity.direction * Math.PI * 0.25
|
||||
arrows.scale.set(0.5, 0.5)
|
||||
entityInfo.addChild(arrows)
|
||||
}
|
||||
|
||||
if (entity.name === 'pumpjack' || entity.name === 'pump' || entity.name === 'offshore-pump' || entity.type === 'boiler' ||
|
||||
entity.type === 'generator' || entity.name === 'oil-refinery' || entity.name === 'chemical-plant' || entity.assemblerCraftsWithFluid
|
||||
) {
|
||||
const arrows = new PIXI.Container()
|
||||
if (entity.entityData.fluid_boxes) {
|
||||
if (entity.assemblerCraftsWithFluid) {
|
||||
const c = entity.entityData.fluid_boxes[entity.assemblerPipeDirection === 'input' ? 1 : 0]
|
||||
f({
|
||||
x: c.pipe_connections[0].position[0],
|
||||
y: c.pipe_connections[0].position[1]
|
||||
})
|
||||
} else {
|
||||
const dontConnectOutput = entity.name === 'chemical-plant' && entity.chemicalPlantDontConnectOutput
|
||||
for (const c of entity.entityData.fluid_boxes) {
|
||||
// fluid_boxes are reversed
|
||||
if (!(c.production_type === 'input' && dontConnectOutput)) {
|
||||
f({
|
||||
x: c.pipe_connections[0].position[0],
|
||||
y: c.pipe_connections[0].position[1]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (entity.entityData.fluid_box) {
|
||||
for (const p of entity.entityData.fluid_box.pipe_connections) {
|
||||
if (entity.name === 'pump' && p === entity.entityData.fluid_box.pipe_connections[1]) break
|
||||
f({
|
||||
x: p.position[0],
|
||||
y: p.position[1]
|
||||
}, entity.entityData.fluid_box.production_type === 'input-output' ? 2 : 1)
|
||||
}
|
||||
}
|
||||
if (entity.entityData.output_fluid_box) {
|
||||
for (const p of entity.entityData.output_fluid_box.pipe_connections) {
|
||||
f({
|
||||
x: p.position ? p.position[0] : p.positions[entity.direction / 2][0],
|
||||
y: p.position ? p.position[1] : p.positions[entity.direction / 2][1]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
function f(position: IPoint, type = 1) {
|
||||
const offset = 0.5
|
||||
if (entity.name === 'offshore-pump') position.y -= 2
|
||||
const dir = Math.abs(position.x) > Math.abs(position.y) ?
|
||||
(Math.sign(position.x) === 1 ? 2 : 6) :
|
||||
(Math.sign(position.y) === 1 ? 4 : 0)
|
||||
switch (dir) {
|
||||
case 0: position.y += offset; break
|
||||
case 2: position.x -= offset; break
|
||||
case 4: position.y -= offset; break
|
||||
case 6: position.x += offset
|
||||
}
|
||||
const arrow = createArrow({
|
||||
x: position.x * 64,
|
||||
y: position.y * 64
|
||||
}, type)
|
||||
if (entity.type === 'boiler' && type === 2) arrow.rotation = 0.5 * Math.PI
|
||||
if (entity.name === 'pumpjack') arrow.rotation = entity.direction * Math.PI * 0.25
|
||||
arrows.addChild(arrow)
|
||||
}
|
||||
if (entity.name !== 'pumpjack') {
|
||||
arrows.rotation = (entity.name === 'oil-refinery' || entity.name === 'pump' || entity.type === 'boiler' ?
|
||||
entity.direction : (entity.direction + 4) % 8) * Math.PI * 0.25
|
||||
}
|
||||
arrows.scale.set(0.5, 0.5)
|
||||
entityInfo.addChild(arrows)
|
||||
}
|
||||
|
||||
if (entityInfo.children.length !== 0) {
|
||||
entityInfo.position.set(position.x, position.y)
|
||||
this.overlay.addChild(entityInfo)
|
||||
return entityInfo
|
||||
}
|
||||
|
||||
function createIconWithBackground(container: PIXI.Container, itemName: string, position?: IPoint) {
|
||||
const icon = InventoryContainer.createIcon(factorioData.getItem(itemName))
|
||||
const background = PIXI.Sprite.fromFrame('extra-icon:entity-info-dark-background')
|
||||
background.anchor.set(0.5, 0.5)
|
||||
if (position) {
|
||||
icon.position.set(position.x , position.y)
|
||||
background.position.set(position.x , position.y)
|
||||
}
|
||||
const lastLength = container.children.length
|
||||
container.addChild(background, icon)
|
||||
if (lastLength !== 0) {
|
||||
container.swapChildren(container.getChildAt(lastLength / 2), container.getChildAt(lastLength))
|
||||
}
|
||||
}
|
||||
|
||||
function createArrow(position: IPoint, type = 0) {
|
||||
const arrow = PIXI.Sprite.fromFrame('extra-icon:' +
|
||||
(type === 0 ? 'indication-arrow' : (type === 1 ? 'fluid-indication-arrow' : 'fluid-indication-arrow-both-ways'))
|
||||
)
|
||||
arrow.anchor.set(0.5, 0.5)
|
||||
arrow.position.set(position.x , position.y)
|
||||
return arrow
|
||||
}
|
||||
}
|
||||
|
||||
showCursorBox() {
|
||||
this.cursorBox.visible = true
|
||||
}
|
||||
|
||||
updateCursorBoxSize(width: number, height: number) {
|
||||
this.cursorBox.removeChildren()
|
||||
if (width === 1 && height === 1) {
|
||||
const s = PIXI.Sprite.fromFrame('extra-icon:cursor-boxes-32x32-0')
|
||||
s.anchor.set(0.5, 0.5)
|
||||
this.cursorBox.addChild(s)
|
||||
} else {
|
||||
this.cursorBox.addChild(...createCorners(
|
||||
'extra-icon:cursor-boxes-' + mapMinLengthToSpriteIndex(Math.min(width, height))
|
||||
))
|
||||
}
|
||||
function mapMinLengthToSpriteIndex(minLength: number) {
|
||||
if (minLength < 0.4) return '4'
|
||||
if (minLength < 0.7) return '3'
|
||||
if (minLength < 1.05) return '2'
|
||||
if (minLength < 3.5) return '1'
|
||||
return '0'
|
||||
}
|
||||
function createCorners(spriteName: string) {
|
||||
const c0 = PIXI.Sprite.fromFrame(spriteName)
|
||||
const c1 = PIXI.Sprite.fromFrame(spriteName)
|
||||
const c2 = PIXI.Sprite.fromFrame(spriteName)
|
||||
const c3 = PIXI.Sprite.fromFrame(spriteName)
|
||||
c0.position.set(-width * 32, -height * 32)
|
||||
c1.position.set(width * 32, -height * 32)
|
||||
c2.position.set(-width * 32, height * 32)
|
||||
c3.position.set(width * 32, height * 32)
|
||||
c1.rotation = Math.PI * 0.5
|
||||
c2.rotation = Math.PI * 1.5
|
||||
c3.rotation = Math.PI
|
||||
return [c0, c1, c2, c3]
|
||||
}
|
||||
}
|
||||
|
||||
updateCursorBoxPosition(position: IPoint) {
|
||||
this.cursorBox.position.set(position.x, position.y)
|
||||
}
|
||||
|
||||
hideCursorBox() {
|
||||
this.cursorBox.visible = false
|
||||
}
|
||||
|
||||
updateUndergroundLines(name: string, position: IPoint, direction: number, searchDirection: number) {
|
||||
const fd = factorioData.getEntity(name)
|
||||
if (fd.type === 'underground-belt' || name === 'pipe-to-ground') {
|
||||
this.undergroundLines.removeChildren()
|
||||
const otherEntity = G.bp.entityPositionGrid.findEntityWithSameNameAndDirection(
|
||||
name,
|
||||
name === 'pipe-to-ground' ? searchDirection : direction,
|
||||
position,
|
||||
searchDirection,
|
||||
fd.max_distance || 10
|
||||
)
|
||||
if (otherEntity) {
|
||||
const oE = G.bp.entity(otherEntity)
|
||||
// Return if directionTypes are the same
|
||||
if (fd.type === 'underground-belt' &&
|
||||
(oE.directionType === 'input' ? oE.direction : (oE.direction + 4 % 8)) === searchDirection) return
|
||||
const distance = searchDirection % 4 === 0 ? Math.abs(oE.position.y - position.y) :
|
||||
Math.abs(oE.position.x - position.x)
|
||||
const sign = searchDirection === 0 || searchDirection === 6 ? -1 : 1
|
||||
for (let i = 1; i < distance; i++) {
|
||||
const s = PIXI.Sprite.fromFrame('extra-icon:underground-lines-' + (name === 'pipe-to-ground' ? '0' : '1'))
|
||||
s.rotation = direction * Math.PI * 0.25
|
||||
s.scale.set(0.5, 0.5)
|
||||
s.anchor.set(0.5, 0.5)
|
||||
if (searchDirection % 4 === 0) s.position.y += sign * i * 32
|
||||
else s.position.x += sign * i * 32
|
||||
this.undergroundLines.addChild(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateUndergroundLinesPosition(position: IPoint) {
|
||||
this.undergroundLines.position.set(position.x, position.y)
|
||||
}
|
||||
|
||||
hideUndergroundLines() {
|
||||
this.undergroundLines.removeChildren()
|
||||
}
|
||||
}
|
||||
257
src/containers/paint.ts
Normal file
257
src/containers/paint.ts
Normal file
@@ -0,0 +1,257 @@
|
||||
import G from '../globals'
|
||||
import util from '../util'
|
||||
import factorioData from '../factorio-data/factorioData'
|
||||
import { EntityContainer } from './entity'
|
||||
import { AdjustmentFilter } from '@pixi/filter-adjustment'
|
||||
import { UnderlayContainer } from './underlay'
|
||||
|
||||
export class PaintContainer extends PIXI.Container {
|
||||
areaVisualization: PIXI.Sprite | PIXI.Sprite[] | undefined
|
||||
holdingRightClick: boolean
|
||||
directionType: string
|
||||
direction: number
|
||||
holdingLeftClick: boolean
|
||||
filter: AdjustmentFilter
|
||||
|
||||
constructor(name: string, direction: number, position: IPoint) {
|
||||
super()
|
||||
this.name = name
|
||||
this.direction = direction
|
||||
this.directionType = 'input'
|
||||
this.position.set(position.x, position.y)
|
||||
this.filter = new AdjustmentFilter({ blue: 0.4 })
|
||||
this.filters = [this.filter]
|
||||
this.checkBuildable()
|
||||
|
||||
this.interactive = true
|
||||
this.interactiveChildren = false
|
||||
this.buttonMode = true
|
||||
|
||||
this.holdingLeftClick = false
|
||||
|
||||
this.areaVisualization = G.BPC.underlayContainer.createNewArea(this.name)
|
||||
UnderlayContainer.modifyVisualizationArea(this.areaVisualization, s => {
|
||||
s.alpha += 0.25
|
||||
s.visible = true
|
||||
})
|
||||
G.BPC.underlayContainer.activateRelatedAreas(this.name)
|
||||
|
||||
this.on('pointerdown', this.pointerDownEventHandler)
|
||||
this.on('pointerup', this.pointerUpEventHandler)
|
||||
this.on('pointerupoutside', this.pointerUpEventHandler)
|
||||
// this.on('pointermove', this.pointerMoveEventHandler)
|
||||
|
||||
this.redraw()
|
||||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy()
|
||||
UnderlayContainer.modifyVisualizationArea(this.areaVisualization, s => s.destroy())
|
||||
G.BPC.underlayContainer.deactivateActiveAreas()
|
||||
G.BPC.overlayContainer.hideUndergroundLines()
|
||||
}
|
||||
|
||||
checkBuildable() {
|
||||
const position = EntityContainer.getGridPosition(this.position)
|
||||
const size = util.switchSizeBasedOnDirection(factorioData.getEntity(this.name).size, this.direction)
|
||||
if (!EntityContainer.isContainerOutOfBpArea(position, size) &&
|
||||
(G.bp.entityPositionGrid.checkFastReplaceableGroup(this.name, this.direction, position) ||
|
||||
G.bp.entityPositionGrid.checkSameEntityAndDifferentDirection(this.name, this.direction, position) ||
|
||||
G.bp.entityPositionGrid.checkNoOverlap(this.name, this.direction, position))
|
||||
) {
|
||||
this.filter.red = 0.4
|
||||
this.filter.green = 1
|
||||
} else {
|
||||
this.filter.red = 1
|
||||
this.filter.green = 0.4
|
||||
}
|
||||
}
|
||||
|
||||
updateUndergroundBeltRotation() {
|
||||
const fd = factorioData.getEntity(this.name)
|
||||
if (fd.type === 'underground-belt') {
|
||||
const otherEntity = G.bp.entityPositionGrid.findEntityWithSameNameAndDirection(
|
||||
this.name, (this.direction + 4) % 8, {
|
||||
x: this.x / 32,
|
||||
y: this.y / 32
|
||||
},
|
||||
this.direction,
|
||||
fd.max_distance
|
||||
)
|
||||
if (otherEntity) {
|
||||
const oe = G.bp.entity(otherEntity)
|
||||
this.directionType = oe.directionType === 'input' ? 'output' : 'input'
|
||||
} else {
|
||||
if (this.directionType === 'output') this.directionType = 'input'
|
||||
}
|
||||
this.redraw()
|
||||
}
|
||||
}
|
||||
|
||||
updateUndergroundLines() {
|
||||
G.BPC.overlayContainer.updateUndergroundLines(
|
||||
this.name,
|
||||
{ x: this.position.x / 32, y: this.position.y / 32 },
|
||||
this.directionType === 'input' ? this.direction : (this.direction + 4) % 8,
|
||||
this.name === 'pipe-to-ground' ? (this.direction + 4) % 8 : this.direction
|
||||
)
|
||||
}
|
||||
|
||||
rotate() {
|
||||
const pr = factorioData.getEntity(this.name).possible_rotations
|
||||
if (!pr) return
|
||||
this.direction = pr[ (pr.indexOf(this.direction) + 1) % pr.length ]
|
||||
this.redraw()
|
||||
const size = util.switchSizeBasedOnDirection(factorioData.getEntity(this.name).size, this.direction)
|
||||
if (size.x !== size.y) {
|
||||
this.position.set(
|
||||
this.x + ((this.x / 16 - G.gridCoords16.x) === 0 ? 0.5 : -0.5) * 32,
|
||||
this.y + ((this.y / 16 - G.gridCoords16.y) === 0 ? 0.5 : -0.5) * 32
|
||||
)
|
||||
|
||||
const pos = EntityContainer.getPositionFromData(this.position, size)
|
||||
this.position.set(pos.x, pos.y)
|
||||
}
|
||||
this.checkBuildable()
|
||||
this.updateUndergroundBeltRotation()
|
||||
this.updateUndergroundLines()
|
||||
}
|
||||
|
||||
redraw() {
|
||||
this.removeChildren()
|
||||
this.addChild(...EntityContainer.getParts({
|
||||
name: this.name,
|
||||
direction: this.directionType === 'output' ? (this.direction + 4) % 8 : this.direction,
|
||||
directionType: this.directionType
|
||||
}, true, true))
|
||||
const size = util.switchSizeBasedOnDirection(factorioData.getEntity(this.name).size, this.direction)
|
||||
this.hitArea = new PIXI.Rectangle(
|
||||
-size.x * 16,
|
||||
-size.y * 16,
|
||||
size.x * 32,
|
||||
size.y * 32
|
||||
)
|
||||
}
|
||||
|
||||
pointerDownEventHandler(e: PIXI.interaction.InteractionEvent) {
|
||||
if (e.data.button === 0) {
|
||||
this.holdingLeftClick = true
|
||||
this.placeEntityContainer()
|
||||
} else if (e.data.button === 2) {
|
||||
this.holdingRightClick = true
|
||||
this.removeContainer()
|
||||
}
|
||||
}
|
||||
|
||||
pointerUpEventHandler(e: PIXI.interaction.InteractionEvent) {
|
||||
if (e.data.button === 0) {
|
||||
this.holdingLeftClick = false
|
||||
} else if (e.data.button === 2) {
|
||||
this.holdingRightClick = false
|
||||
}
|
||||
}
|
||||
|
||||
// pointerMoveEventHandler(e: PIXI.interaction.InteractionEvent) {
|
||||
// this.moveTo(e.data.getLocalPosition(this.parent))
|
||||
// }
|
||||
|
||||
moveTo(newPosition: IPoint) {
|
||||
const newCursorPos = {
|
||||
x: (newPosition.x - newPosition.x % 16) / 16,
|
||||
y: (newPosition.y - newPosition.y % 16) / 16
|
||||
}
|
||||
if (newCursorPos.x !== G.gridCoords16.x || newCursorPos.y !== G.gridCoords16.y) {
|
||||
if (this.holdingRightClick) this.removeContainer()
|
||||
|
||||
switch (this.name) {
|
||||
case 'straight-rail':
|
||||
case 'curved-rail':
|
||||
case 'train-stop':
|
||||
this.position.set(
|
||||
newPosition.x - (newPosition.x + G.railMoveOffset.x * 32) % 64 + 32,
|
||||
newPosition.y - (newPosition.y + G.railMoveOffset.y * 32) % 64 + 32
|
||||
)
|
||||
break
|
||||
default:
|
||||
const size = util.switchSizeBasedOnDirection(factorioData.getEntity(this.name).size, this.direction)
|
||||
const pos = EntityContainer.getPositionFromData(newPosition, size)
|
||||
this.position.set(pos.x, pos.y)
|
||||
}
|
||||
|
||||
this.updateUndergroundBeltRotation()
|
||||
G.BPC.overlayContainer.updateUndergroundLinesPosition(this.position)
|
||||
this.updateUndergroundLines()
|
||||
|
||||
UnderlayContainer.modifyVisualizationArea(this.areaVisualization, s => s.position.copy(this.position))
|
||||
|
||||
if (this.holdingLeftClick) this.placeEntityContainer()
|
||||
G.gridCoords16 = newCursorPos
|
||||
|
||||
this.checkBuildable()
|
||||
}
|
||||
}
|
||||
|
||||
removeContainer() {
|
||||
const position = EntityContainer.getGridPosition(this.position)
|
||||
const c = EntityContainer.mappings.get(G.bp.entityPositionGrid.getCellAtPosition(position))
|
||||
if (c) {
|
||||
c.removeContainer()
|
||||
this.checkBuildable()
|
||||
}
|
||||
}
|
||||
|
||||
placeEntityContainer() {
|
||||
const fd = factorioData.getEntity(this.name)
|
||||
const position = EntityContainer.getGridPosition(this.position)
|
||||
const size = util.switchSizeBasedOnDirection(fd.size, this.direction)
|
||||
if (EntityContainer.isContainerOutOfBpArea(position, size)) return
|
||||
|
||||
const frgEntNr = G.bp.entityPositionGrid.checkFastReplaceableGroup(this.name, this.direction, position)
|
||||
if (frgEntNr) {
|
||||
const frgEnt = G.bp.entity(frgEntNr)
|
||||
frgEnt.change(this.name, this.direction)
|
||||
const c = EntityContainer.mappings.get(frgEntNr)
|
||||
c.redraw()
|
||||
c.redrawSurroundingEntities()
|
||||
return
|
||||
}
|
||||
const snEntNr = G.bp.entityPositionGrid.checkSameEntityAndDifferentDirection(
|
||||
this.name, this.direction, position
|
||||
)
|
||||
if (snEntNr) {
|
||||
G.bp.entity(snEntNr).direction = this.direction
|
||||
const c = EntityContainer.mappings.get(snEntNr)
|
||||
c.redraw()
|
||||
c.redrawSurroundingEntities()
|
||||
return
|
||||
}
|
||||
|
||||
const isUB = fd.type === 'underground-belt'
|
||||
const res = G.bp.createEntity(this.name, position,
|
||||
isUB && this.directionType === 'output' ? (this.direction + 4) % 8 : this.direction,
|
||||
isUB ? this.directionType : undefined
|
||||
)
|
||||
if (res) {
|
||||
const ec = new EntityContainer(res)
|
||||
if (ec.areaVisualization) {
|
||||
if (ec.areaVisualization instanceof PIXI.Sprite) {
|
||||
ec.areaVisualization.visible = true
|
||||
} else {
|
||||
for (const s of ec.areaVisualization) s.visible = true
|
||||
}
|
||||
}
|
||||
G.BPC.entities.addChild(ec)
|
||||
ec.redrawSurroundingEntities()
|
||||
|
||||
if (isUB || this.name === 'pipe-to-ground') {
|
||||
this.direction = (this.direction + 4) % 8
|
||||
this.redraw()
|
||||
G.BPC.overlayContainer.hideUndergroundLines()
|
||||
}
|
||||
|
||||
G.BPC.updateOverlay()
|
||||
}
|
||||
|
||||
this.checkBuildable()
|
||||
}
|
||||
}
|
||||
59
src/containers/toolbar.ts
Normal file
59
src/containers/toolbar.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import G from '../globals'
|
||||
|
||||
export class ToolbarContainer extends PIXI.Container {
|
||||
|
||||
info: PIXI.Text
|
||||
logo: PIXI.Text
|
||||
fpsGUIText: PIXI.Text
|
||||
gridposGUIText: PIXI.Text
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
this.interactive = false
|
||||
this.interactiveChildren = false
|
||||
|
||||
const background = new PIXI.Sprite(PIXI.Texture.WHITE)
|
||||
background.width = G.app.renderer.width
|
||||
window.addEventListener('resize', () => {
|
||||
background.width = G.app.renderer.width
|
||||
this.fpsGUIText.position.set(G.app.renderer.width, background.height / 2)
|
||||
this.logo.position.set(G.app.renderer.width / 2, background.height / 2)
|
||||
this.info.position.set(G.app.renderer.width - 100, background.height)
|
||||
}, false)
|
||||
background.height = 32
|
||||
background.tint = 0x303030
|
||||
this.addChild(background)
|
||||
|
||||
this.gridposGUIText = new PIXI.Text('')
|
||||
this.gridposGUIText.anchor.set(0, 0.5)
|
||||
this.gridposGUIText.position.set(0, background.height / 2)
|
||||
this.gridposGUIText.style.fill = 0xFFFFFF
|
||||
this.addChild(this.gridposGUIText)
|
||||
|
||||
this.fpsGUIText = new PIXI.Text('')
|
||||
this.fpsGUIText.anchor.set(1, 0.5)
|
||||
this.fpsGUIText.style.fill = 0xFFFFFF
|
||||
this.fpsGUIText.position.set(G.app.renderer.width, background.height / 2)
|
||||
this.addChild(this.fpsGUIText)
|
||||
|
||||
this.logo = new PIXI.Text('Factorio Blueprint Editor')
|
||||
this.logo.anchor.set(0.5, 0.5)
|
||||
this.logo.style.fill = 0xFFFFFF
|
||||
this.logo.position.set(G.app.renderer.width / 2, background.height / 2)
|
||||
this.addChild(this.logo)
|
||||
|
||||
this.info = new PIXI.Text('Press I for info')
|
||||
this.info.anchor.set(1, 1)
|
||||
this.info.style.fill = 0xFFFFFF
|
||||
this.info.style.fontSize = 13
|
||||
this.info.position.set(G.app.renderer.width - 100, background.height)
|
||||
this.addChild(this.info)
|
||||
|
||||
G.app.ticker.add(() => this.fpsGUIText.text = String(Math.round(G.app.ticker.FPS)) + ' FPS')
|
||||
}
|
||||
|
||||
updateGridPos(coords: IPoint) {
|
||||
this.gridposGUIText.text = `X ${coords.x} Y ${coords.y}`
|
||||
}
|
||||
}
|
||||
136
src/containers/underlay.ts
Normal file
136
src/containers/underlay.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import factorioData from '../factorio-data/factorioData'
|
||||
import { isArray } from 'util'
|
||||
import { AdjustmentFilter } from '@pixi/filter-adjustment'
|
||||
|
||||
export class UnderlayContainer extends PIXI.Container {
|
||||
|
||||
static getDataForVisualizationArea(name: string) {
|
||||
const type = factorioData.getEntity(name).type
|
||||
function undoBlendModeColorShift(color0: number, color1: number, alpha: number) {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/blendFunc
|
||||
// array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]
|
||||
return color1 - color0 * (1 - alpha)
|
||||
}
|
||||
if (name === 'roboport') {
|
||||
return {
|
||||
type: ['logistics0', 'logistics1'],
|
||||
rKey: ['construction_radius', 'logistics_radius'],
|
||||
color: [0x83D937, undoBlendModeColorShift(0xFF8800, 0x83D937, 0.25)]
|
||||
}
|
||||
}
|
||||
if (type === 'electric-pole') {
|
||||
return {
|
||||
type: 'poles',
|
||||
rKey: 'supply_area_distance',
|
||||
color: 0x33755D9
|
||||
}
|
||||
}
|
||||
if (name === 'beacon') {
|
||||
return {
|
||||
type: 'beacons',
|
||||
rKey: 'supply_area_distance',
|
||||
color: 0xD9C037
|
||||
}
|
||||
}
|
||||
if (name === 'electric-mining-drill') {
|
||||
return {
|
||||
type: 'drills',
|
||||
rKey: 'resource_searching_radius',
|
||||
color: 0x4EAD9F
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static modifyVisualizationArea(area: PIXI.Sprite | PIXI.Sprite[] | undefined, fn: (s: PIXI.Sprite) => void) {
|
||||
if (area) {
|
||||
if (area instanceof PIXI.Sprite) fn(area)
|
||||
else for (const s of area) fn(s)
|
||||
}
|
||||
}
|
||||
|
||||
active: string[]
|
||||
logistics0: PIXI.Container
|
||||
logistics1: PIXI.Container
|
||||
poles: PIXI.Container
|
||||
beacons: PIXI.Container
|
||||
drills: PIXI.Container
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
this.interactive = false
|
||||
this.interactiveChildren = false
|
||||
|
||||
this.active = []
|
||||
this.logistics0 = new PIXI.Container()
|
||||
this.logistics1 = new PIXI.Container()
|
||||
this.poles = new PIXI.Container()
|
||||
this.beacons = new PIXI.Container()
|
||||
this.drills = new PIXI.Container()
|
||||
|
||||
const filter = new AdjustmentFilter({ alpha: 0.25 })
|
||||
this.logistics0.filters = [filter]
|
||||
this.logistics1.filters = [filter]
|
||||
|
||||
this.addChild(this.logistics0, this.logistics1, this.poles, this.beacons, this.drills)
|
||||
}
|
||||
|
||||
activateRelatedAreas(entityName: string) {
|
||||
const ed = factorioData.getEntity(entityName)
|
||||
const data = UnderlayContainer.getDataForVisualizationArea(entityName)
|
||||
if (data) if (isArray(data.type)) this.active.push(...data.type); else this.active.push(data.type)
|
||||
if (ed.type === 'logistic-container') this.active.push('logistics0', 'logistics1')
|
||||
if (ed.energy_source && ed.energy_source.type === 'electric') this.active.push('poles')
|
||||
if (ed.module_specification) this.active.push('beacons')
|
||||
|
||||
for (const type of this.active) {
|
||||
for (const s of this[type].children) {
|
||||
s.visible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deactivateActiveAreas() {
|
||||
for (const type of this.active) {
|
||||
for (const s of this[type].children) {
|
||||
s.visible = false
|
||||
}
|
||||
}
|
||||
this.active = []
|
||||
}
|
||||
|
||||
createNewArea(entityName: string, position?: PIXI.Point | PIXI.ObservablePoint) {
|
||||
const aVData = UnderlayContainer.getDataForVisualizationArea(entityName)
|
||||
if (aVData) {
|
||||
const ed = factorioData.getEntity(entityName)
|
||||
if (isArray(aVData.type)) {
|
||||
const aVs = []
|
||||
for (let i = 0; i < aVData.type.length; i++) {
|
||||
const areaVisualization = createVisualizationArea(ed[aVData.rKey[i]], aVData.color[i], position, 1)
|
||||
this[aVData.type[i]].addChild(areaVisualization)
|
||||
aVs.push(areaVisualization)
|
||||
}
|
||||
return aVs
|
||||
} else {
|
||||
const areaVisualization = createVisualizationArea(ed[aVData.rKey], aVData.color, position)
|
||||
this[aVData.type].addChild(areaVisualization)
|
||||
return areaVisualization
|
||||
}
|
||||
}
|
||||
|
||||
function createVisualizationArea(radius: number, color: number, position?: PIXI.Point | PIXI.ObservablePoint, alpha = 0.25) {
|
||||
const aV = new PIXI.Sprite(PIXI.Texture.WHITE)
|
||||
const S = radius * 64
|
||||
aV.width = S
|
||||
aV.height = S
|
||||
aV.tint = color
|
||||
aV.anchor.set(0.5, 0.5)
|
||||
aV.alpha = alpha
|
||||
if (position) {
|
||||
aV.visible = false
|
||||
aV.position.copy(position)
|
||||
}
|
||||
return aV
|
||||
}
|
||||
}
|
||||
}
|
||||
111
src/containers/wires.ts
Normal file
111
src/containers/wires.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { EntityContainer } from './entity'
|
||||
import G from '../globals'
|
||||
|
||||
export class WiresContainer extends PIXI.Container {
|
||||
|
||||
static createWire(p1: IPoint, p2: IPoint, color: string) {
|
||||
const wire = new PIXI.Graphics()
|
||||
if (color === 'red') {
|
||||
wire.lineStyle(1.3, 0xC83718)
|
||||
} else {
|
||||
wire.lineStyle(1.3, 0x588C38)
|
||||
}
|
||||
|
||||
const force = 0.25
|
||||
const minX = Math.min(p1.x, p2.x)
|
||||
const minY = Math.min(p1.y, p2.y)
|
||||
const dX = Math.max(p1.x, p2.x) - minX
|
||||
const dY = Math.max(p1.y, p2.y) - minY
|
||||
const X = minX + dX / 2
|
||||
const Y = (dY / dX) * (X - minX) + minY + force * dX
|
||||
|
||||
wire.moveTo(p1.x, p1.y)
|
||||
// TODO: make wires smoother, use 2 points instead of 1
|
||||
if (p1.x === p2.x) {
|
||||
wire.lineTo(p2.x, p2.y)
|
||||
} else {
|
||||
wire.bezierCurveTo(X, Y, X, Y, p2.x, p2.y)
|
||||
}
|
||||
return wire
|
||||
}
|
||||
|
||||
static getFinalPos(entity_number: number, color: string, side: number) {
|
||||
const point = G.bp.entity(entity_number).getWireConnectionPoint(color, side)
|
||||
return {
|
||||
x: EntityContainer.mappings.get(entity_number).position.x + point[0] * 32,
|
||||
y: EntityContainer.mappings.get(entity_number).position.y + point[1] * 32
|
||||
}
|
||||
}
|
||||
|
||||
entityWiresMapping: Map<string, PIXI.Graphics[]>
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
this.interactive = false
|
||||
this.interactiveChildren = false
|
||||
|
||||
this.entityWiresMapping = new Map()
|
||||
}
|
||||
|
||||
remove(entity_number: number) {
|
||||
this.entityWiresMapping.forEach((v, k) => {
|
||||
const first = Number(k.split('-')[0])
|
||||
const second = Number(k.split('-')[1])
|
||||
if (first === entity_number || second === entity_number) {
|
||||
for (const g of v) g.destroy()
|
||||
this.entityWiresMapping.delete(k)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
update(entity_number: number) {
|
||||
if (!G.bp.entity(entity_number).hasConnections) return
|
||||
this.remove(entity_number)
|
||||
G.bp.connections.connections.forEach((v, k) => {
|
||||
const first = Number(k.split('-')[0])
|
||||
const second = Number(k.split('-')[1])
|
||||
if (first === entity_number || second === entity_number) {
|
||||
if (first === entity_number) EntityContainer.mappings.get(second).redraw()
|
||||
else if (second === entity_number) EntityContainer.mappings.get(first).redraw()
|
||||
|
||||
const paths: PIXI.Graphics[] = []
|
||||
v.forEach(c => {
|
||||
paths.push(WiresContainer.createWire(
|
||||
WiresContainer.getFinalPos(c.get('entity_number_1') as number, c.get('color') as string, c.get('entity_side_1') as number),
|
||||
WiresContainer.getFinalPos(c.get('entity_number_2') as number, c.get('color') as string, c.get('entity_side_2') as number),
|
||||
c.get('color') as string
|
||||
))
|
||||
})
|
||||
for (const p of paths) {
|
||||
this.addChild(p)
|
||||
}
|
||||
this.entityWiresMapping.set(k, paths)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
drawWires() {
|
||||
G.bp.connections.connections.forEach((v, k) => {
|
||||
if (this.entityWiresMapping.has(k)) {
|
||||
for (const p of this.entityWiresMapping.get(k)) {
|
||||
this.removeChild(p)
|
||||
}
|
||||
}
|
||||
const paths: PIXI.Graphics[] = []
|
||||
|
||||
v.forEach(c => {
|
||||
paths.push(WiresContainer.createWire(
|
||||
WiresContainer.getFinalPos(c.get('entity_number_1') as number, c.get('color') as string, c.get('entity_side_1') as number),
|
||||
WiresContainer.getFinalPos(c.get('entity_number_2') as number, c.get('color') as string, c.get('entity_side_2') as number),
|
||||
c.get('color') as string
|
||||
))
|
||||
})
|
||||
|
||||
for (const p of paths) {
|
||||
this.addChild(p)
|
||||
}
|
||||
this.entityWiresMapping.set(k, paths)
|
||||
})
|
||||
}
|
||||
}
|
||||
83
src/entitySprite.ts
Normal file
83
src/entitySprite.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import G from './globals'
|
||||
import { AdjustmentFilter } from '@pixi/filter-adjustment'
|
||||
|
||||
export class EntitySprite extends PIXI.Sprite {
|
||||
static nextID = 0
|
||||
|
||||
id: number
|
||||
isMoving: boolean
|
||||
shift: IPoint
|
||||
zIndex: number
|
||||
zOrder: number
|
||||
|
||||
constructor(data: any) {
|
||||
if (!data.shift) data.shift = [0, 0]
|
||||
if (!data.x) data.x = 0
|
||||
if (!data.y) data.y = 0
|
||||
if (!data.divW) data.divW = 1
|
||||
if (!data.divH) data.divH = 1
|
||||
|
||||
const spriteData = PIXI.utils.TextureCache[data.filename]
|
||||
// TODO: Cache the texture
|
||||
super(new PIXI.Texture(spriteData.baseTexture, new PIXI.Rectangle(
|
||||
spriteData.frame.x + data.x,
|
||||
spriteData.frame.y + data.y,
|
||||
data.width / data.divW,
|
||||
data.height / data.divH
|
||||
)))
|
||||
|
||||
this.interactive = false
|
||||
this.id = EntitySprite.nextID++
|
||||
this.isMoving = false
|
||||
|
||||
this.shift = {
|
||||
x: data.shift[0] * 32,
|
||||
y: data.shift[1] * 32
|
||||
}
|
||||
|
||||
this.position.set(this.shift.x, this.shift.y)
|
||||
|
||||
if (data.scale) this.scale.set(data.scale, data.scale)
|
||||
this.anchor.set(0.5, 0.5)
|
||||
|
||||
if (data.flipX) this.scale.x *= -1
|
||||
if (data.flipY) this.scale.y *= -1
|
||||
|
||||
if (data.height_divider) this.height /= data.height_divider
|
||||
|
||||
if (data.rot) this.rotation = data.rot * Math.PI * 0.5
|
||||
|
||||
if (data.color) {
|
||||
this.filters = [new AdjustmentFilter({
|
||||
gamma: 1.4,
|
||||
contrast: 1.4,
|
||||
brightness: 1.2,
|
||||
red: data.color.r,
|
||||
green: data.color.g,
|
||||
blue: data.color.b,
|
||||
alpha: data.color.a
|
||||
})]
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
set moving(moving: boolean) {
|
||||
this.isMoving = moving
|
||||
if (moving) {
|
||||
if (this.filters !== null) this.filters.push(G.BPC.movingEntityFilter)
|
||||
else this.filters = [G.BPC.movingEntityFilter]
|
||||
} else {
|
||||
// tslint:disable-next-line:no-null-keyword
|
||||
if (this.filters.length === 1) this.filters = null
|
||||
else this.filters.pop()
|
||||
}
|
||||
}
|
||||
|
||||
setPosition(position: PIXI.Point | PIXI.ObservablePoint) {
|
||||
this.position.set(
|
||||
position.x + this.shift.x,
|
||||
position.y + this.shift.y
|
||||
)
|
||||
}
|
||||
}
|
||||
67
src/factorio-data/BPString.ts
Normal file
67
src/factorio-data/BPString.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import zlib from 'zlib'
|
||||
import { Buffer } from 'buffer'
|
||||
import Ajv from 'ajv'
|
||||
import blueprintSchema from '../blueprintSchema.json'
|
||||
import factorioData from './factorioData'
|
||||
import { Blueprint } from './blueprint'
|
||||
import { Book } from './book'
|
||||
|
||||
const ajv = new Ajv()
|
||||
ajv.addKeyword('entityName', {
|
||||
validate: (data: string) => factorioData.checkEntityName(data),
|
||||
errors: false,
|
||||
schema: false
|
||||
})
|
||||
ajv.addKeyword('itemName', {
|
||||
validate: (data: string) => factorioData.checkItemName(data),
|
||||
errors: false,
|
||||
schema: false
|
||||
})
|
||||
ajv.addKeyword('objectWithItemNames', {
|
||||
validate: (data: object) => {
|
||||
for (const k in data) {
|
||||
if (!factorioData.checkItemName(k)) return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
errors: false,
|
||||
schema: false
|
||||
})
|
||||
ajv.addKeyword('recipeName', {
|
||||
validate: (data: string) => factorioData.checkRecipeName(data),
|
||||
errors: false,
|
||||
schema: false
|
||||
})
|
||||
ajv.addKeyword('tileName', {
|
||||
validate: (data: string) => factorioData.checkTileName(data),
|
||||
errors: false,
|
||||
schema: false
|
||||
})
|
||||
const validate = ajv.compile(blueprintSchema)
|
||||
|
||||
export default {
|
||||
decode: (str: string) => {
|
||||
let data
|
||||
try {
|
||||
data = JSON.parse(zlib.inflateSync(Buffer.from(str.slice(1), 'base64')).toString('utf8'))
|
||||
} catch (e) {
|
||||
return { error: e }
|
||||
}
|
||||
|
||||
console.log(data)
|
||||
|
||||
// data.blueprint.entities.forEach(e => {
|
||||
// // if (e.control_behavior) {
|
||||
// // let d = e.control_behavior.circuit_condition
|
||||
// // if (d !== undefined) console.log(e.name + '\t\t\t\t' + d)
|
||||
// // }
|
||||
// if (e.filters !== undefined) console.log(e.name + '\t\t\t\t' + e.filters)
|
||||
// });
|
||||
|
||||
if (!validate(data)) return { error: validate.errors }
|
||||
|
||||
if (data.blueprint_book === undefined) return new Blueprint(data.blueprint)
|
||||
return new Book(data)
|
||||
},
|
||||
encode: (bPOrBook: any) => ('0' + zlib.deflateSync(JSON.stringify(bPOrBook.toObject())).toString('base64'))
|
||||
}
|
||||
403
src/factorio-data/blueprint.ts
Normal file
403
src/factorio-data/blueprint.ts
Normal file
@@ -0,0 +1,403 @@
|
||||
import factorioData from './factorioData'
|
||||
import { Tile } from './tile'
|
||||
import { PositionGrid } from './positionGrid'
|
||||
import Immutable from 'immutable'
|
||||
import initEntity from './entity'
|
||||
import G from '../globals'
|
||||
import { ConnectionsManager } from './connectionsManager'
|
||||
import { EntityContainer } from '../containers/entity'
|
||||
|
||||
export class Blueprint {
|
||||
|
||||
name: string
|
||||
icons: any[]
|
||||
tiles: Tile[]
|
||||
tilePositionGrid: any
|
||||
version: number
|
||||
connections: ConnectionsManager
|
||||
next_entity_number: number
|
||||
historyIndex: number
|
||||
history: Array<{
|
||||
entity_number: number | number[];
|
||||
type: 'init' | 'add' | 'del' | 'mov' | 'upd';
|
||||
annotation: string;
|
||||
rawEntities: Immutable.Map<number, any>;
|
||||
}>
|
||||
bp: Blueprint
|
||||
entityPositionGrid: PositionGrid
|
||||
rawEntities: Immutable.Map<number, any>
|
||||
|
||||
constructor(data?: any) {
|
||||
initEntity(this)
|
||||
this.name = 'Blueprint'
|
||||
this.icons = []
|
||||
this.rawEntities = Immutable.Map()
|
||||
this.tiles = []
|
||||
this.tilePositionGrid = {}
|
||||
this.version = undefined
|
||||
this.next_entity_number = 1
|
||||
|
||||
if (data) {
|
||||
if (!data.tiles) data.tiles = []
|
||||
if (!data.icons) data.icons = []
|
||||
this.name = data.label
|
||||
this.version = data.version
|
||||
|
||||
this.next_entity_number += data.entities.length
|
||||
this.rawEntities = this.rawEntities.withMutations(map => {
|
||||
for (const entity of data.entities) {
|
||||
map.set(entity.entity_number, Immutable.fromJS(entity))
|
||||
// this.entityPositionGrid.setTileData(entity.entity_number)
|
||||
}
|
||||
})
|
||||
|
||||
data.tiles.forEach((tile: any) => {
|
||||
this.createTile(tile.name, tile.position)
|
||||
})
|
||||
|
||||
this.icons = []
|
||||
data.icons.forEach((icon: any) => {
|
||||
this.icons[icon.index - 1] = icon.signal.name
|
||||
})
|
||||
|
||||
this.setTileIds()
|
||||
|
||||
// TODO: if entity has placeable-off-grid flag then take the next one
|
||||
const firstEntityTopLeft = this.entity(1).topLeft()
|
||||
|
||||
const offsetX = G.sizeBPContainer.width / 64 - (firstEntityTopLeft.x % 1 !== 0 ? -0.5 : 0)
|
||||
const offsetY = G.sizeBPContainer.height / 64 - (firstEntityTopLeft.y % 1 !== 0 ? -0.5 : 0)
|
||||
|
||||
this.rawEntities = this.rawEntities.withMutations(map => {
|
||||
map.keySeq().forEach(k => {
|
||||
// tslint:disable-next-line:no-parameter-reassignment
|
||||
map.updateIn([k, 'position', 'x'], (x: number) => x += offsetX)
|
||||
// tslint:disable-next-line:no-parameter-reassignment
|
||||
map.updateIn([k, 'position', 'y'], (y: number) => y += offsetY)
|
||||
})
|
||||
})
|
||||
|
||||
// tslint:disable-next-line:no-dynamic-delete
|
||||
this.tiles.forEach(tile => delete this.tilePositionGrid[`${tile.position.x},${tile.position.y}`])
|
||||
this.tiles.forEach(tile => {
|
||||
tile.position.x += offsetX
|
||||
tile.position.y += offsetY
|
||||
this.tilePositionGrid[`${tile.position.x},${tile.position.y}`] = tile
|
||||
})
|
||||
}
|
||||
|
||||
this.entityPositionGrid = new PositionGrid(this, [...this.rawEntities.keys()])
|
||||
this.connections = new ConnectionsManager(this, [...this.rawEntities.keys()])
|
||||
|
||||
this.historyIndex = 0
|
||||
this.history = [{
|
||||
entity_number: 0,
|
||||
type: 'init',
|
||||
annotation: '',
|
||||
rawEntities: this.rawEntities
|
||||
}]
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
entity(entity_number: number) {
|
||||
const e = this.rawEntities.get(entity_number)
|
||||
if (!e) return undefined
|
||||
return e.entity()
|
||||
}
|
||||
|
||||
undo(
|
||||
pre: (hist: any) => void,
|
||||
post: (hist: any) => void
|
||||
) {
|
||||
if (this.historyIndex === 0) return
|
||||
const hist = this.history[this.historyIndex--]
|
||||
|
||||
switch (hist.type) {
|
||||
case 'add':
|
||||
case 'del':
|
||||
case 'mov':
|
||||
this.entityPositionGrid.undo()
|
||||
}
|
||||
|
||||
pre(hist)
|
||||
this.rawEntities = this.history[this.historyIndex].rawEntities
|
||||
switch (hist.type) {
|
||||
case 'del':
|
||||
if (this.entity(hist.entity_number as number).hasConnections) this.connections.undo()
|
||||
}
|
||||
post(hist)
|
||||
}
|
||||
|
||||
redo(
|
||||
pre: (hist: any) => void,
|
||||
post: (hist: any) => void
|
||||
) {
|
||||
if (this.historyIndex === this.history.length - 1) return
|
||||
const hist = this.history[++this.historyIndex]
|
||||
|
||||
switch (hist.type) {
|
||||
case 'add':
|
||||
case 'del':
|
||||
case 'mov':
|
||||
this.entityPositionGrid.redo()
|
||||
}
|
||||
|
||||
pre(hist)
|
||||
|
||||
const entity = this.entity(hist.entity_number as number)
|
||||
switch (hist.type) {
|
||||
case 'del':
|
||||
if (entity.hasConnections) this.connections.redo()
|
||||
}
|
||||
|
||||
this.rawEntities = hist.rawEntities
|
||||
|
||||
// TODO: Refactor this somehow
|
||||
if (hist.type === 'del' && entity.hasConnections && entity.connectedEntities) {
|
||||
for (const entNr of entity.connectedEntities) {
|
||||
EntityContainer.mappings.get(entNr).redraw()
|
||||
}
|
||||
}
|
||||
|
||||
post(hist)
|
||||
}
|
||||
|
||||
operation(
|
||||
entity_number: number | number[],
|
||||
annotation: string,
|
||||
fn: (entities: Immutable.Map<number, any>) => Immutable.Map<any, any>,
|
||||
type: 'add' | 'del' | 'mov' | 'upd' = 'upd',
|
||||
pushToHistory = true
|
||||
) {
|
||||
console.log(`${entity_number} - ${annotation}`)
|
||||
this.rawEntities = fn(this.rawEntities)
|
||||
|
||||
if (pushToHistory) {
|
||||
if (this.historyIndex < this.history.length) {
|
||||
this.history = this.history.slice(0, this.historyIndex + 1)
|
||||
}
|
||||
this.history.push({
|
||||
entity_number,
|
||||
type,
|
||||
annotation,
|
||||
rawEntities: this.rawEntities
|
||||
})
|
||||
this.historyIndex++
|
||||
}
|
||||
}
|
||||
|
||||
createEntity(name: string, position: IPoint, direction: number, directionType?: string) {
|
||||
if (!this.entityPositionGrid.checkNoOverlap(name, direction, position)) return false
|
||||
const entity_number = this.next_entity_number++
|
||||
const data = {
|
||||
entity_number,
|
||||
name,
|
||||
position,
|
||||
direction
|
||||
}
|
||||
if (directionType) data.type = directionType
|
||||
this.operation(entity_number, 'Added new entity',
|
||||
entities => entities.set(entity_number, Immutable.fromJS(data)),
|
||||
'add'
|
||||
)
|
||||
|
||||
this.entityPositionGrid.setTileData(entity_number)
|
||||
|
||||
return data.entity_number
|
||||
}
|
||||
|
||||
removeEntity(entity_number: number, redrawCb?: (entity_number: number) => void) {
|
||||
this.entityPositionGrid.removeTileData(entity_number)
|
||||
let entitiesToModify: any[] = []
|
||||
if (this.entity(entity_number).hasConnections) {
|
||||
entitiesToModify = this.connections.removeConnectionData(entity_number)
|
||||
}
|
||||
this.operation(entity_number, 'Deleted entity',
|
||||
entities => entities.withMutations(map => {
|
||||
|
||||
for (const i in entitiesToModify) {
|
||||
const entity_number = entitiesToModify[i].entity_number
|
||||
const side = entitiesToModify[i].side
|
||||
const color = entitiesToModify[i].color
|
||||
const index = entitiesToModify[i].index
|
||||
|
||||
const a = map.getIn([entity_number, 'connections']).size === 1
|
||||
const b = map.getIn([entity_number, 'connections', side]).size === 1
|
||||
const c = map.getIn([entity_number, 'connections', side, color]).size === 1
|
||||
if (a && b && c) {
|
||||
map.removeIn([entity_number, 'connections'])
|
||||
} else if (b && c) {
|
||||
map.removeIn([entity_number, 'connections', side])
|
||||
} else if (c) {
|
||||
map.removeIn([entity_number, 'connections', side, color])
|
||||
} else {
|
||||
map.removeIn([entity_number, 'connections', side, color, index])
|
||||
}
|
||||
}
|
||||
|
||||
map.delete(entity_number)
|
||||
}),
|
||||
'del'
|
||||
)
|
||||
for (const i in entitiesToModify) {
|
||||
redrawCb(entitiesToModify[i].entity_number)
|
||||
}
|
||||
}
|
||||
|
||||
getFirstRail() {
|
||||
const fR = this.rawEntities.find(v => v.get('name') === 'straight-rail' || v.get('name') === 'curved-rail')
|
||||
return fR ? fR.toJS() : undefined
|
||||
}
|
||||
|
||||
// placeBlueprint(bp, position, direction = 0, allowOverlap) { // direction is 0, 1, 2, or 3
|
||||
// const entitiesCreated = []
|
||||
// bp.entities.forEach(ent => {
|
||||
// const data = ent.getData()
|
||||
|
||||
// data.direction += direction * 2
|
||||
// data.direction %= 8
|
||||
|
||||
// if (direction === 3) data.position = { x: data.position.y, y: -data.position.x }
|
||||
// else if (direction === 2) data.position = { x: -data.position.x, y: -data.position.y }
|
||||
// else if (direction === 1) data.position = { x: -data.position.y, y: data.position.x }
|
||||
|
||||
// data.position.x += position.x
|
||||
// data.position.y += position.y
|
||||
|
||||
// entitiesCreated.push(this.createEntityWithData(data, allowOverlap, true, true))
|
||||
// })
|
||||
|
||||
// entitiesCreated.forEach(e => {
|
||||
// e.place(this.entitiesCreated)
|
||||
// })
|
||||
|
||||
// bp.tiles.forEach(tile => {
|
||||
// const data = tile.getData()
|
||||
|
||||
// if (direction === 3) data.position = { x: data.position.y, y: -data.position.x }
|
||||
// else if (direction === 2) data.position = { x: -data.position.x, y: -data.position.y }
|
||||
// else if (direction === 1) data.position = { x: -data.position.y, y: data.position.x }
|
||||
|
||||
// data.position.x += position.x
|
||||
// data.position.y += position.y
|
||||
|
||||
// this.createTileWithData(data)
|
||||
// })
|
||||
|
||||
// return this
|
||||
// }
|
||||
|
||||
// createEntityWithData(data: any, allowOverlap: boolean, noPlace: boolean) {
|
||||
// const ent = new Entity(data, this)
|
||||
// if (allowOverlap || this.entityPositionGrid.checkNoOverlap(ent)) {
|
||||
// if (!noPlace) ent.place(this.entities)
|
||||
// this.entities.push(ent)
|
||||
// return ent
|
||||
// } else {
|
||||
// // const otherEnt = ent.getOverlap(this.entityPositionGrid)
|
||||
// // throw new Error('Entity ' + data.name + ' overlaps ' + otherEnt.name +
|
||||
// // ' entity (' + data.position.x + ', ' + data.position.y + ')')
|
||||
// }
|
||||
// }
|
||||
|
||||
createTile(name: string, position: IPoint) {
|
||||
return this.createTileWithData({ name, position })
|
||||
}
|
||||
|
||||
createTileWithData(data: any) {
|
||||
const tile = new Tile(data, this)
|
||||
const key = `${data.position.x},${data.position.y}`
|
||||
if (this.tilePositionGrid[key]) this.removeTile(this.tilePositionGrid[key])
|
||||
|
||||
this.tilePositionGrid[key] = tile
|
||||
this.tiles.push(tile)
|
||||
return tile
|
||||
}
|
||||
|
||||
removeTile(tile: Tile) {
|
||||
if (!tile) return false
|
||||
else {
|
||||
const index = this.tiles.indexOf(tile)
|
||||
if (index === -1) return tile
|
||||
this.tiles.splice(index, 1)
|
||||
return tile
|
||||
}
|
||||
}
|
||||
|
||||
setTileIds() {
|
||||
this.tiles.forEach((tile, i) => {
|
||||
tile.id = i + 1
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
// Get corner/center positions
|
||||
getPosition(f: string, xcomp: any, ycomp: any) {
|
||||
if (!this.rawEntities.size) return { x: 0, y: 0 }
|
||||
return {
|
||||
x: [...this.rawEntities.keys()].reduce(
|
||||
(best: number, ent: any) => xcomp(best, this.entity(ent)[f]().x),
|
||||
this.entity(1)[f]().x
|
||||
),
|
||||
y: [...this.rawEntities.keys()].reduce(
|
||||
(best: number, ent: any) => ycomp(best, this.entity(ent)[f]().y),
|
||||
this.entity(1)[f]().y
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
center() {
|
||||
return {
|
||||
x: (this.topLeft().x + this.topRight().x) / 2,
|
||||
y: (this.topLeft().y + this.bottomLeft().y) / 2
|
||||
}
|
||||
}
|
||||
topLeft() { return this.getPosition('topLeft', Math.min, Math.min) }
|
||||
topRight() { return this.getPosition('topRight', Math.max, Math.min) }
|
||||
bottomLeft() { return this.getPosition('bottomLeft', Math.min, Math.max) }
|
||||
bottomRight() { return this.getPosition('bottomRight', Math.max, Math.max) }
|
||||
|
||||
generateIcons(num = 2) {
|
||||
// TODO: make this behave more like in factorio 2 icons default, the 2 most used items in the bp
|
||||
const NUM = Math.min(this.rawEntities.size, Math.min(Math.max(num, 1), 4))
|
||||
for (let i = 0; i < NUM; i++) {
|
||||
this.icons[i] = this.rawEntities[i].name
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
toObject() {
|
||||
this.setTileIds()
|
||||
// if (!this.icons.length) this.generateIcons()
|
||||
const entityInfo = this.rawEntities.valueSeq().toJS()
|
||||
const center = this.center()
|
||||
const fR = this.getFirstRail()
|
||||
if (fR) {
|
||||
center.x += (fR.position.x - center.x) % 2
|
||||
center.y += (fR.position.y - center.y) % 2
|
||||
}
|
||||
for (const e of entityInfo) {
|
||||
e.position.x -= center.x
|
||||
e.position.y -= center.y
|
||||
}
|
||||
const tileInfo = this.tiles.map(tile => tile.getData())
|
||||
for (const t of tileInfo) {
|
||||
t.position.x -= center.x
|
||||
t.position.y -= center.y
|
||||
}
|
||||
const iconData = this.icons.map((icon, i) => (
|
||||
{ signal: { type: factorioData.getItemTypeForBp(icon), name: icon }, index: i + 1 }
|
||||
))
|
||||
return {
|
||||
blueprint: {
|
||||
icons: iconData,
|
||||
entities: this.rawEntities.size ? entityInfo : undefined,
|
||||
tiles: this.tiles.length ? tileInfo : undefined,
|
||||
item: 'blueprint',
|
||||
version: this.version || 0,
|
||||
label: this.name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
53
src/factorio-data/book.ts
Normal file
53
src/factorio-data/book.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { Blueprint } from './blueprint'
|
||||
|
||||
export class Book {
|
||||
|
||||
active_index: number
|
||||
blueprints: any[]
|
||||
|
||||
constructor(data: any) {
|
||||
if (data) {
|
||||
this.active_index = data.blueprint_book.active_index
|
||||
this.blueprints = data.blueprint_book.blueprints
|
||||
} else {
|
||||
this.active_index = 0
|
||||
this.blueprints = []
|
||||
}
|
||||
}
|
||||
|
||||
addBlueprint(blueprint: Blueprint) {
|
||||
this.blueprints.push(blueprint)
|
||||
}
|
||||
|
||||
getBlueprint(index?: number) {
|
||||
const INDEX = index ? index : this.active_index
|
||||
this.active_index = INDEX
|
||||
|
||||
if (this.blueprints[INDEX].loaded) return this.blueprints[INDEX].loaded
|
||||
|
||||
const bp = new Blueprint(this.blueprints[INDEX].blueprint)
|
||||
this.blueprints[INDEX].loaded = bp
|
||||
return bp
|
||||
}
|
||||
|
||||
toObject() {
|
||||
const blueprints = []
|
||||
for (let i = 0; i < this.blueprints.length; i++) {
|
||||
blueprints.push({
|
||||
index: i,
|
||||
// TODO: modified instead of loaded
|
||||
blueprint: this.blueprints[i].loaded ?
|
||||
this.blueprints[i].loaded.toObject() :
|
||||
this.blueprints[i].blueprint
|
||||
})
|
||||
}
|
||||
return {
|
||||
blueprint_book: {
|
||||
blueprints,
|
||||
item: 'blueprint-book',
|
||||
active_index: this.active_index,
|
||||
version: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
132
src/factorio-data/connectionsManager.ts
Normal file
132
src/factorio-data/connectionsManager.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { Blueprint } from './blueprint'
|
||||
import Immutable from 'immutable'
|
||||
|
||||
export class ConnectionsManager {
|
||||
|
||||
bp: Blueprint
|
||||
connections: Immutable.Map<string, Immutable.List<Immutable.Map<
|
||||
'color' | 'circuit_id' | 'entity_number_1' | 'entity_number_2' | 'entity_side_1' | 'entity_side_2'
|
||||
, string | number | undefined>>>
|
||||
historyIndex: number
|
||||
history: Array<Immutable.Map<string, Immutable.List<Immutable.Map<
|
||||
'color' | 'circuit_id' | 'entity_number_1' | 'entity_number_2' | 'entity_side_1' | 'entity_side_2'
|
||||
, string | number | undefined>>>>
|
||||
|
||||
constructor(bp: Blueprint, entity_numbers?: number[]) {
|
||||
this.bp = bp
|
||||
this.connections = Immutable.Map()
|
||||
|
||||
// Set Bulk
|
||||
if (entity_numbers) {
|
||||
this.connections = this.connections.withMutations(map => {
|
||||
const connections = new Map()
|
||||
for (const entity_number of entity_numbers) {
|
||||
const entity = this.bp.entity(entity_number)
|
||||
if (entity.hasConnections) connections.set(entity_number, entity.connections)
|
||||
}
|
||||
connections.forEach((conn, k) => {
|
||||
for (const side in conn) {
|
||||
for (const color in conn[side]) {
|
||||
for (const c of conn[side][color]) {
|
||||
if (!map.has(`${(c as any).entity_id}-${k}`)) {
|
||||
let side2
|
||||
const conn2 = connections.get((c as any).entity_id)
|
||||
let found = false
|
||||
for (side2 in conn2) {
|
||||
for (const color2 in conn2[side2]) {
|
||||
for (const c2 of conn2[side2][color2]) {
|
||||
if (color === color2 && (c2 as any).entity_id === k) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (found) break
|
||||
}
|
||||
if (found) break
|
||||
}
|
||||
const key = `${k}-${(c as any).entity_id}`
|
||||
if (!map.has(key)) map.set(key, Immutable.List())
|
||||
map.set(key, map.get(key).push(Immutable.fromJS({
|
||||
color,
|
||||
circuit_id: (c as any).circuit_id,
|
||||
entity_number_1: k,
|
||||
entity_number_2: (c as any).entity_id,
|
||||
entity_side_1: Number(side),
|
||||
entity_side_2: Number(side2)
|
||||
})))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
this.history = [this.connections]
|
||||
this.historyIndex = 0
|
||||
}
|
||||
|
||||
undo() {
|
||||
if (this.historyIndex === 0) return
|
||||
this.connections = this.history[--this.historyIndex]
|
||||
}
|
||||
|
||||
redo() {
|
||||
if (this.historyIndex === this.history.length - 1) return
|
||||
this.connections = this.history[++this.historyIndex]
|
||||
}
|
||||
|
||||
operation(
|
||||
fn: (connections: Immutable.Map<string, Immutable.List<Immutable.Map<
|
||||
'color' | 'circuit_id' | 'entity_number_1' | 'entity_number_2' | 'entity_side_1' | 'entity_side_2'
|
||||
, string | number | undefined>>>) => Immutable.Map<any, any>
|
||||
) {
|
||||
this.connections = fn(this.connections)
|
||||
if (this.historyIndex < this.history.length) {
|
||||
this.history = this.history.slice(0, this.historyIndex + 1)
|
||||
}
|
||||
this.history.push(this.connections)
|
||||
this.historyIndex++
|
||||
}
|
||||
|
||||
removeConnectionData(entity_number: number) {
|
||||
const entitiesToModify: Array<{
|
||||
entity_number: number;
|
||||
side: string;
|
||||
color: string;
|
||||
index: number;
|
||||
}> = []
|
||||
this.operation(connections => connections.withMutations(map => {
|
||||
map.forEach((v, k) => {
|
||||
const isE1 = Number(k.split('-')[0]) === entity_number
|
||||
const isE2 = Number(k.split('-')[1]) === entity_number
|
||||
if (isE1 || isE2) {
|
||||
v.forEach(conn => {
|
||||
const entNr2 = (isE1 ? conn.get('entity_number_2') : conn.get('entity_number_1')) as number
|
||||
const conn2 = this.bp.entity(entNr2).connections
|
||||
for (const side in conn2) {
|
||||
for (const color in conn2[side]) {
|
||||
for (const i in conn2[side][color]) {
|
||||
if (entity_number === conn2[side][color][i].entity_id &&
|
||||
Number(side) === (isE1 ? conn.get('entity_side_2') : conn.get('entity_side_1')) &&
|
||||
color === conn.get('color')
|
||||
) {
|
||||
entitiesToModify.push({
|
||||
entity_number: entNr2,
|
||||
side,
|
||||
color,
|
||||
index: Number(i)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
map.deleteIn([k, map.get(k).indexOf(conn)])
|
||||
})
|
||||
}
|
||||
if (map.get(k).size === 0) map.delete(k)
|
||||
})
|
||||
}))
|
||||
return entitiesToModify
|
||||
}
|
||||
}
|
||||
324
src/factorio-data/entity.ts
Normal file
324
src/factorio-data/entity.ts
Normal file
@@ -0,0 +1,324 @@
|
||||
import { Blueprint } from './blueprint'
|
||||
import Immutable from 'immutable'
|
||||
import factorioData from './factorioData'
|
||||
import util from '../util'
|
||||
import { Area } from './positionGrid'
|
||||
|
||||
export default (BP: Blueprint) => {
|
||||
Immutable.Map.prototype.entity = function() {
|
||||
// tslint:disable-next-line:no-this-assignment
|
||||
const rawEntity = this
|
||||
return {
|
||||
get entity_number() { return rawEntity.get('entity_number') },
|
||||
get name() { return rawEntity.get('name') },
|
||||
|
||||
get type() { return factorioData.getEntity(this.name).type },
|
||||
get entityData() { return factorioData.getEntity(this.name) },
|
||||
get recipeData() { return factorioData.getRecipe(this.name) },
|
||||
get itemData() { return factorioData.getItem(this.name) },
|
||||
get size() { return util.switchSizeBasedOnDirection(this.entityData.size, this.direction) },
|
||||
|
||||
get position() { return rawEntity.get('position').toJS() },
|
||||
get direction() { return rawEntity.get('direction') || 0 },
|
||||
get directionType() { return rawEntity.get('type') },
|
||||
get recipe() { return rawEntity.get('recipe') },
|
||||
|
||||
set recipe(recipeName: string) {
|
||||
BP.operation(this.entity_number, 'Changed recipe', entities => (
|
||||
entities.withMutations(map => {
|
||||
map.setIn([this.entity_number, 'recipe'], recipeName)
|
||||
|
||||
const modules = this.modules
|
||||
if (modules && recipeName && !factorioData.getItem('productivity-module').limitation.includes(recipeName)) {
|
||||
for (const k in modules) {
|
||||
if (k.includes('productivity-module')) delete modules[k]
|
||||
}
|
||||
map.setIn([this.entity_number, 'items'], Object.keys(modules).length ? Immutable.fromJS(modules) : undefined)
|
||||
}
|
||||
})
|
||||
))
|
||||
},
|
||||
|
||||
get acceptedRecipes() {
|
||||
const acceptedRecipes: string[] = []
|
||||
const recipes = factorioData.getRecipes()
|
||||
const cc = this.entityData.crafting_categories
|
||||
for (const k in recipes) {
|
||||
if (cc.includes(recipes[k].category) || (cc.includes('crafting') && !recipes[k].category)) {
|
||||
const recipe = (recipes[k].normal ? recipes[k].normal : recipes[k])
|
||||
if (!((this.name === 'assembling-machine-1' && recipe.ingredients.length > 2) ||
|
||||
(this.name === 'assembling-machine-2' && recipe.ingredients.length > 4))
|
||||
) {
|
||||
acceptedRecipes.push(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
return acceptedRecipes
|
||||
},
|
||||
|
||||
get acceptedModules() {
|
||||
const ommitProductivityModules = this.name === 'beacon' ||
|
||||
(this.recipe && !factorioData.getItem('productivity-module').limitation.includes(this.recipe))
|
||||
const items = factorioData.getItems()
|
||||
const acceptedModules: string[] = []
|
||||
for (const k in items) {
|
||||
if (items[k].type === 'module' && !(k.includes('productivity-module') && ommitProductivityModules)) acceptedModules.push(k)
|
||||
}
|
||||
return acceptedModules
|
||||
},
|
||||
|
||||
set direction(direction: number) {
|
||||
BP.operation(this.entity_number, 'Set entity direction to ' + direction,
|
||||
entities => entities.setIn([this.entity_number, 'direction'], direction)
|
||||
)
|
||||
},
|
||||
|
||||
get modules() {
|
||||
const i = rawEntity.get('items')
|
||||
return i ? i.toJS() : undefined
|
||||
},
|
||||
|
||||
get modulesList() {
|
||||
const i = rawEntity.get('items')
|
||||
if (!i) return
|
||||
const modules = i.toJS()
|
||||
const moduleList = []
|
||||
for (const n in modules) {
|
||||
for (let i = 0; i < modules[n]; i++) {
|
||||
moduleList.push(n)
|
||||
}
|
||||
}
|
||||
return moduleList
|
||||
},
|
||||
|
||||
set modulesList(list: any) {
|
||||
const modules = {}
|
||||
for (const m of list) {
|
||||
if (Object.keys(modules).includes(m)) {
|
||||
modules[m]++
|
||||
} else {
|
||||
modules[m] = 1
|
||||
}
|
||||
}
|
||||
BP.operation(this.entity_number, 'Changed modules',
|
||||
entities => entities.setIn([this.entity_number, 'items'], Immutable.fromJS(modules))
|
||||
)
|
||||
},
|
||||
|
||||
get splitterInputPriority() {
|
||||
return rawEntity.get('input_priority')
|
||||
},
|
||||
|
||||
get splitterOutputPriority() {
|
||||
return rawEntity.get('output_priority')
|
||||
},
|
||||
|
||||
get splitterFilter() {
|
||||
return rawEntity.get('filter')
|
||||
},
|
||||
|
||||
get inserterFilters() {
|
||||
const f = rawEntity.get('filters')
|
||||
return f ? f.toJS() : undefined
|
||||
},
|
||||
|
||||
get constantCombinatorFilters() {
|
||||
const f = rawEntity.getIn(['control_behavior', 'filters'])
|
||||
return f ? f.toJS() : undefined
|
||||
},
|
||||
|
||||
get logisticChestFilters() {
|
||||
const f = rawEntity.get('request_filters')
|
||||
return f ? f.toJS() : undefined
|
||||
},
|
||||
|
||||
get deciderCombinatorConditions() {
|
||||
const c = rawEntity.getIn(['control_behavior', 'decider_conditions'])
|
||||
return c ? c.toJS() : undefined
|
||||
},
|
||||
|
||||
get arithmeticCombinatorConditions() {
|
||||
const c = rawEntity.getIn(['control_behavior', 'arithmetic_conditions'])
|
||||
return c ? c.toJS() : undefined
|
||||
},
|
||||
|
||||
get hasConnections() {
|
||||
return rawEntity.get('connections') !== undefined
|
||||
},
|
||||
|
||||
get connections() {
|
||||
const c = rawEntity.get('connections')
|
||||
return c ? c.toJS() : undefined
|
||||
},
|
||||
|
||||
get connectedEntities() {
|
||||
const c = rawEntity.get('connections')
|
||||
if (!c) return
|
||||
const connections = c.toJS()
|
||||
const entities = []
|
||||
for (const side in connections) {
|
||||
for (const color in connections[side]) {
|
||||
for (const c of connections[side][color]) {
|
||||
entities.push(c.entity_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
return entities
|
||||
},
|
||||
|
||||
get chemicalPlantDontConnectOutput() {
|
||||
const r = this.recipe
|
||||
if (!r) return false
|
||||
const rData = factorioData.getRecipe(r)
|
||||
const recipe = (rData.normal ? rData.normal : rData)
|
||||
if (recipe.result || recipe.results[0].type === 'item') return true
|
||||
return false
|
||||
},
|
||||
|
||||
get trainStopColor() {
|
||||
const c = rawEntity.get('color')
|
||||
return c ? c.toJS() : undefined
|
||||
},
|
||||
|
||||
get operator() {
|
||||
if (this.name === 'decider-combinator') {
|
||||
const cb = rawEntity.get('control_behavior')
|
||||
if (cb) return cb.getIn(['decider_conditions', 'comparator'])
|
||||
}
|
||||
if (this.name === 'arithmetic-combinator') {
|
||||
const cb = rawEntity.get('control_behavior')
|
||||
if (cb) return cb.getIn(['arithmetic_conditions', 'operation'])
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
|
||||
getArea(pos?: IPoint) {
|
||||
return new Area({
|
||||
x: pos ? pos.x : this.position.x,
|
||||
y: pos ? pos.y : this.position.y,
|
||||
width: this.size.x,
|
||||
height: this.size.y
|
||||
}, true)
|
||||
},
|
||||
|
||||
change(name: string, direction: number) {
|
||||
BP.operation(this.entity_number, 'Changed Entity', entities => (
|
||||
entities.withMutations(map => {
|
||||
map.setIn([this.entity_number, 'name'], name)
|
||||
map.setIn([this.entity_number, 'direction'], direction)
|
||||
})
|
||||
))
|
||||
},
|
||||
|
||||
move(pos: IPoint) {
|
||||
const entity = BP.entity(this.entity_number)
|
||||
if (!BP.entityPositionGrid.checkNoOverlap(entity.name, entity.direction, pos)) return false
|
||||
BP.operation(this.entity_number, 'Moved entity',
|
||||
entities => entities.setIn([this.entity_number, 'position'], Immutable.fromJS(pos)),
|
||||
'mov'
|
||||
)
|
||||
BP.entityPositionGrid.setTileData(this.entity_number)
|
||||
return true
|
||||
},
|
||||
|
||||
rotate(notMoving: boolean, offset?: IPoint, pushToHistory = true, otherEntity?: number) {
|
||||
if (!this.assemblerCraftsWithFluid &&
|
||||
(this.name === 'assembling-machine-2' || this.name === 'assembling-machine-3')) return false
|
||||
if (notMoving && BP.entityPositionGrid.sharesCell(this.getArea())) return false
|
||||
const pr = this.entityData.possible_rotations
|
||||
if (!pr) return false
|
||||
const newDir = pr[(pr.indexOf(this.direction) +
|
||||
(notMoving && (this.size.x !== this.size.y || this.type === 'underground-belt') ? 2 : 1)
|
||||
) % pr.length
|
||||
]
|
||||
if (newDir === this.direction) return false
|
||||
BP.operation(otherEntity ? [this.entity_number, otherEntity] : this.entity_number, 'Rotated entity',
|
||||
entities => entities.withMutations(map => {
|
||||
map.setIn([this.entity_number, 'direction'], newDir)
|
||||
if (notMoving && this.type === 'underground-belt') {
|
||||
map.updateIn([this.entity_number, 'type'], directionType =>
|
||||
directionType === 'input' ? 'output' : 'input'
|
||||
)
|
||||
}
|
||||
if (!notMoving && this.size.x !== this.size.y) {
|
||||
// tslint:disable-next-line:no-parameter-reassignment
|
||||
map.updateIn([this.entity_number, 'position', 'x'], x => x += offset.x)
|
||||
// tslint:disable-next-line:no-parameter-reassignment
|
||||
map.updateIn([this.entity_number, 'position', 'y'], y => y += offset.y)
|
||||
}
|
||||
}),
|
||||
'upd',
|
||||
notMoving && pushToHistory
|
||||
)
|
||||
return true
|
||||
},
|
||||
|
||||
topLeft() {
|
||||
return { x: this.position.x - (this.size.x / 2), y: this.position.y - (this.size.y / 2) }
|
||||
},
|
||||
topRight() {
|
||||
return { x: this.position.x + (this.size.x / 2), y: this.position.y - (this.size.y / 2) }
|
||||
},
|
||||
bottomLeft() {
|
||||
return { x: this.position.x - (this.size.x / 2), y: this.position.y + (this.size.y / 2) }
|
||||
},
|
||||
bottomRight() {
|
||||
return { x: this.position.x + (this.size.x / 2), y: this.position.y + (this.size.y / 2) }
|
||||
},
|
||||
|
||||
get assemblerCraftsWithFluid() {
|
||||
return this.recipe &&
|
||||
factorioData.getRecipe(this.recipe).category === 'crafting-with-fluid' &&
|
||||
this.entityData.crafting_categories &&
|
||||
this.entityData.crafting_categories.includes('crafting-with-fluid')
|
||||
},
|
||||
|
||||
get assemblerPipeDirection() {
|
||||
if (!this.recipe) return
|
||||
const recipeData = factorioData.getRecipe(this.recipe)
|
||||
const rD = recipeData.normal ? recipeData.normal : recipeData
|
||||
for (const io of rD.ingredients) {
|
||||
if (io.type === 'fluid') {
|
||||
return 'input'
|
||||
}
|
||||
}
|
||||
if (rD.results) {
|
||||
for (const io of rD.results) {
|
||||
if (io.type === 'fluid') {
|
||||
return 'output'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getWireConnectionPoint(color: string, side: number) {
|
||||
const e = this.entityData
|
||||
// poles
|
||||
if (e.connection_points) return e.connection_points[this.direction / 2].wire[color]
|
||||
// combinators
|
||||
if (e.input_connection_points) {
|
||||
if (side === 1) return e.input_connection_points[this.direction / 2].wire[color]
|
||||
return e.output_connection_points[this.direction / 2].wire[color]
|
||||
}
|
||||
if (e.circuit_wire_connection_point) return e.circuit_wire_connection_point.wire[color]
|
||||
|
||||
if (this.type === 'transport-belt') {
|
||||
return e.circuit_wire_connection_points[
|
||||
factorioData.getBeltConnections2(BP, this.position, this.direction) * 4
|
||||
].wire[color]
|
||||
}
|
||||
if (e.circuit_wire_connection_points.length === 8) {
|
||||
return e.circuit_wire_connection_points[7 - this.direction].wire[color]
|
||||
}
|
||||
if (this.name === 'constant-combinator') {
|
||||
return e.circuit_wire_connection_points[this.direction / 2].wire[color]
|
||||
}
|
||||
return e.circuit_wire_connection_points[3 - this.direction / 2].wire[color]
|
||||
},
|
||||
|
||||
toJS() {
|
||||
return rawEntity.toJS()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1048
src/factorio-data/factorioData.ts
Normal file
1048
src/factorio-data/factorioData.ts
Normal file
File diff suppressed because it is too large
Load Diff
361
src/factorio-data/positionGrid.ts
Normal file
361
src/factorio-data/positionGrid.ts
Normal file
@@ -0,0 +1,361 @@
|
||||
import { Blueprint } from './blueprint'
|
||||
import util from '../util'
|
||||
import factorioData from './factorioData'
|
||||
import Immutable from 'immutable'
|
||||
import { isNumber } from 'util'
|
||||
|
||||
export class Area {
|
||||
y: number
|
||||
x: number
|
||||
height: number
|
||||
width: number
|
||||
|
||||
constructor(data: any, posIsCenter?: boolean) {
|
||||
this.width = data.width || 1
|
||||
this.height = data.height || 1
|
||||
if (posIsCenter) {
|
||||
this.x = Math.round(data.x - this.width / 2)
|
||||
this.y = Math.round(data.y - this.height / 2)
|
||||
} else {
|
||||
this.x = Math.floor(data.x)
|
||||
this.y = Math.floor(data.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class PositionGrid {
|
||||
|
||||
static tileDataAction(
|
||||
grid: Immutable.Map<string, number | Immutable.List<number>>,
|
||||
area: Area,
|
||||
fn: (key: string, cell: number | Immutable.List<number>) => boolean | void,
|
||||
returnEmptyCells = false
|
||||
) {
|
||||
let stop = false
|
||||
for (let x = area.x, maxX = area.x + area.width; x < maxX; x++) {
|
||||
for (let y = area.y, maxY = area.y + area.height; y < maxY; y++) {
|
||||
const key = `${x},${y}`
|
||||
const cell = grid.get(key)
|
||||
if (cell || returnEmptyCells) stop = !!fn(key, cell)
|
||||
if (stop) break
|
||||
}
|
||||
if (stop) break
|
||||
}
|
||||
}
|
||||
|
||||
bp: Blueprint
|
||||
grid: Immutable.Map<string, number | Immutable.List<number>>
|
||||
historyIndex: number
|
||||
history: Array<Immutable.Map<string, number | Immutable.List<number>>>
|
||||
|
||||
constructor(bp: Blueprint, entity_numbers?: number[]) {
|
||||
this.bp = bp
|
||||
this.grid = Immutable.Map()
|
||||
|
||||
// Set Bulk
|
||||
if (entity_numbers) {
|
||||
this.grid = this.grid.withMutations(map => {
|
||||
for (const entity_number of entity_numbers) {
|
||||
const entity = this.bp.entity(entity_number)
|
||||
if (!entity.entityData.flags.includes('placeable-off-grid')) {
|
||||
PositionGrid.tileDataAction(map, entity.getArea(), (key, cell) => {
|
||||
if (cell) {
|
||||
if (isNumber(cell)) {
|
||||
map.set(key, Immutable.List([
|
||||
cell,
|
||||
entity_number
|
||||
]))
|
||||
} else {
|
||||
map.setIn([key, cell.size], entity_number)
|
||||
}
|
||||
} else {
|
||||
map.set(key, entity_number)
|
||||
}
|
||||
}, true)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.history = [this.grid]
|
||||
this.historyIndex = 0
|
||||
}
|
||||
|
||||
undo() {
|
||||
if (this.historyIndex === 0) return
|
||||
this.grid = this.history[--this.historyIndex]
|
||||
}
|
||||
|
||||
redo() {
|
||||
if (this.historyIndex === this.history.length - 1) return
|
||||
this.grid = this.history[++this.historyIndex]
|
||||
}
|
||||
|
||||
operation(fn: (grid: Immutable.Map<string, number | Immutable.List<number>>) => Immutable.Map<any, any>, pushToHistory = true) {
|
||||
this.grid = fn(this.grid)
|
||||
if (pushToHistory) {
|
||||
if (this.historyIndex < this.history.length) {
|
||||
this.history = this.history.slice(0, this.historyIndex + 1)
|
||||
}
|
||||
this.history.push(this.grid)
|
||||
this.historyIndex++
|
||||
}
|
||||
}
|
||||
|
||||
getAllPositions() {
|
||||
return [...this.grid.keys()].map(p => {
|
||||
const pS = p.split(',')
|
||||
return {x: Number(pS[0]), y: Number(pS[1])}
|
||||
})
|
||||
}
|
||||
|
||||
getCellAtPosition(position: any): number {
|
||||
const POS = position instanceof Array ? {x: position[0], y: position[1]} : position
|
||||
const cell = this.grid.get(`${Math.floor(POS.x)},${Math.floor(POS.y)}`)
|
||||
if (cell) {
|
||||
if (isNumber(cell)) return cell
|
||||
else return cell.first()
|
||||
}
|
||||
}
|
||||
|
||||
setTileData(entity_number: number) {
|
||||
const entity = this.bp.entity(entity_number)
|
||||
if (entity.entityData.flags.includes('placeable-off-grid')) return
|
||||
this.operation(grid => grid.withMutations(map => {
|
||||
PositionGrid.tileDataAction(map, entity.getArea(), (key, cell) => {
|
||||
if (cell) {
|
||||
if (isNumber(cell)) {
|
||||
map.set(key, Immutable.List([
|
||||
cell,
|
||||
entity_number
|
||||
]))
|
||||
} else {
|
||||
map.setIn([key, cell.size], entity_number)
|
||||
}
|
||||
} else {
|
||||
map.set(key, entity_number)
|
||||
}
|
||||
}, true)
|
||||
}))
|
||||
}
|
||||
|
||||
removeTileData(entity_number: number, pushToHistory?: boolean) {
|
||||
this.operation(grid => grid.withMutations(map => {
|
||||
PositionGrid.tileDataAction(map, this.bp.entity(entity_number).getArea(), (key, cell) => {
|
||||
if (isNumber(cell)) {
|
||||
if (cell === entity_number) map.delete(key)
|
||||
} else {
|
||||
const res = cell.findIndex(v => {
|
||||
if (v === entity_number) return true
|
||||
})
|
||||
if (res !== -1) {
|
||||
if (map.get(key).count() === 1) {
|
||||
map.delete(key)
|
||||
} else {
|
||||
map.deleteIn([key, res])
|
||||
if (map.get(key).count() === 1) map.set(key, map.get(key).first())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}), pushToHistory)
|
||||
}
|
||||
|
||||
checkNoOverlap(name: string, direction: number, pos: IPoint) {
|
||||
const fd = factorioData.getEntity(name)
|
||||
const size = util.switchSizeBasedOnDirection(fd.size, direction)
|
||||
const area = new Area({
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
width: size.x,
|
||||
height: size.y
|
||||
}, true)
|
||||
|
||||
const allStrRailEnt: number[] = []
|
||||
let gateEnt: number
|
||||
let strRailEnt: number
|
||||
let curRailEnt: number
|
||||
let otherEntities = false
|
||||
|
||||
if (!this.foreachOverlap(area, cell => {
|
||||
switch (this.bp.entity(cell).name) {
|
||||
case 'gate': gateEnt = cell; break
|
||||
case 'curved-rail': curRailEnt = cell; break
|
||||
case 'straight-rail': allStrRailEnt.push(cell); strRailEnt = cell; break
|
||||
default: otherEntities = true
|
||||
}
|
||||
})) return true
|
||||
|
||||
let sameDirStrRails = false
|
||||
for (const k of allStrRailEnt) {
|
||||
if (this.bp.entity(k).direction === direction) {
|
||||
sameDirStrRails = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(name === 'gate' && strRailEnt && allStrRailEnt.length === 1 && this.bp.entity(strRailEnt).direction !== direction && !gateEnt) ||
|
||||
(name === 'straight-rail' && gateEnt && !strRailEnt && this.bp.entity(gateEnt).direction !== direction && !otherEntities) ||
|
||||
(name === 'straight-rail' && strRailEnt && !sameDirStrRails && !gateEnt) ||
|
||||
(name === 'curved-rail' && strRailEnt && !gateEnt) ||
|
||||
(name === 'straight-rail' && curRailEnt) ||
|
||||
(name === 'curved-rail' && curRailEnt && this.bp.entity(curRailEnt).direction !== direction)
|
||||
) return true
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
checkFastReplaceableGroup(name: string, direction: number, pos: IPoint) {
|
||||
const fd = factorioData.getEntity(name)
|
||||
const size = util.switchSizeBasedOnDirection(fd.size, direction)
|
||||
const area = new Area({
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
width: size.x,
|
||||
height: size.y
|
||||
}, true)
|
||||
|
||||
if (this.sharesCell(area)) return false
|
||||
const ent = this.getFirstFromArea(area, cell => {
|
||||
const ent = this.bp.entity(cell)
|
||||
if (ent.name !== name &&
|
||||
ent.entityData.fast_replaceable_group &&
|
||||
fd.fast_replaceable_group &&
|
||||
ent.entityData.fast_replaceable_group ===
|
||||
fd.fast_replaceable_group
|
||||
) return cell
|
||||
})
|
||||
if (!ent || pos.x !== this.bp.entity(ent).position.x ||
|
||||
pos.y !== this.bp.entity(ent).position.y) return false
|
||||
return ent
|
||||
}
|
||||
|
||||
checkSameEntityAndDifferentDirection(name: string, direction: number, pos: IPoint) {
|
||||
if (name === 'straight-rail') return false
|
||||
const fd = factorioData.getEntity(name)
|
||||
const size = util.switchSizeBasedOnDirection(fd.size, direction)
|
||||
const area = new Area({
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
width: size.x,
|
||||
height: size.y
|
||||
}, true)
|
||||
|
||||
if (this.sharesCell(area)) return false
|
||||
const ent = this.getFirstFromArea(area, cell => {
|
||||
if (this.bp.entity(cell).name === name) return cell
|
||||
})
|
||||
|
||||
if (!ent) return false
|
||||
const e = this.bp.entity(ent)
|
||||
if (pos.x !== e.position.x || pos.y !== e.position.y || e.direction === direction) return false
|
||||
return ent
|
||||
}
|
||||
|
||||
findEntityWithSameNameAndDirection(name: string, direction: number, pos: IPoint, searchDirection: number, maxDistance: number) {
|
||||
const position = {
|
||||
x: Math.floor(pos.x),
|
||||
y: Math.floor(pos.y)
|
||||
}
|
||||
const horizontal = searchDirection % 4 !== 0
|
||||
const sign = searchDirection === 0 || searchDirection === 6 ? -1 : 1
|
||||
|
||||
for (let i = 1; i <= maxDistance; i++) {
|
||||
const cell = this.grid.get(
|
||||
`${position.x + (horizontal ? i * sign : 0)},${position.y + (!horizontal ? i * sign : 0)}`
|
||||
)
|
||||
if (isNumber(cell)) {
|
||||
const entity = this.bp.entity(cell)
|
||||
if (entity.name === name) {
|
||||
if (entity.direction === direction) return cell
|
||||
if ((entity.direction + 4) % 8 === direction) return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
sharesCell(area: Area) {
|
||||
let output = false
|
||||
PositionGrid.tileDataAction(this.grid, area, (_, cell) => {
|
||||
if (Immutable.List.isList(cell)) {
|
||||
output = true
|
||||
return true
|
||||
}
|
||||
})
|
||||
return output
|
||||
}
|
||||
|
||||
getFirstFromArea(area: Area, fn: (cell: number) => number): false | number {
|
||||
let output: boolean | number = false
|
||||
PositionGrid.tileDataAction(this.grid, area, (_, cell) => {
|
||||
if (isNumber(cell)) {
|
||||
output = fn(cell)
|
||||
if (output) return true
|
||||
} else {
|
||||
for (const v of cell.values()) {
|
||||
output = fn(v)
|
||||
if (output) return true
|
||||
}
|
||||
}
|
||||
})
|
||||
return output
|
||||
}
|
||||
|
||||
foreachOverlap(area: Area, fn: (cell: number) => any, returnEmptyCells?: boolean) {
|
||||
const output: boolean[] = []
|
||||
PositionGrid.tileDataAction(this.grid, area, (_, cell) => {
|
||||
let out = false
|
||||
if (Immutable.List.isList(cell)) {
|
||||
for (const v of cell.values()) {
|
||||
const o = fn(v)
|
||||
if (o !== undefined) out = o
|
||||
}
|
||||
} else {
|
||||
const o = fn(cell as number)
|
||||
if (o !== undefined) out = o
|
||||
}
|
||||
output.push(out)
|
||||
}, returnEmptyCells)
|
||||
return output.length === 0 ? false : output
|
||||
}
|
||||
|
||||
getSurroundingEntities(
|
||||
area: Area,
|
||||
fn: (cell: number, relDir: number, x: number, y: number) => any,
|
||||
direction?: number
|
||||
) {
|
||||
const coordinates = []
|
||||
|
||||
for (let i = 0; i < area.width; i++) {
|
||||
coordinates.push([0, area.x + i, area.y - 1])
|
||||
coordinates.push([4, area.x + i, area.y + area.height])
|
||||
}
|
||||
for (let i = 0; i < area.height; i++) {
|
||||
coordinates.push([2, area.x + area.width, area.y + i])
|
||||
coordinates.push([6, area.x - 1, area.y + i])
|
||||
}
|
||||
|
||||
let output: any[] = [false, false, false, false]
|
||||
for (const coordinate of coordinates) {
|
||||
const cell = this.grid.get(`${coordinate[1]},${coordinate[2]}`)
|
||||
const relDir = coordinate[0] / 2
|
||||
if (cell) {
|
||||
if (isNumber(cell)) {
|
||||
const o = fn(cell as number, coordinate[0], coordinate[1], coordinate[2])
|
||||
if (o !== undefined) output[relDir] = o
|
||||
} else {
|
||||
for (const v of cell.values()) {
|
||||
const o = fn(v, coordinate[0], coordinate[1], coordinate[2])
|
||||
if (o !== undefined) output[relDir] = o
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (direction) output = [...output, ...output].splice(direction / 2, 4)
|
||||
return output
|
||||
}
|
||||
}
|
||||
30
src/factorio-data/tile.ts
Normal file
30
src/factorio-data/tile.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Blueprint } from './blueprint'
|
||||
|
||||
export class Tile {
|
||||
|
||||
id: number
|
||||
bp: any
|
||||
name: any
|
||||
position: any
|
||||
|
||||
constructor(data: any, bp: Blueprint) {
|
||||
this.id = -1
|
||||
this.bp = bp
|
||||
this.name = data.name
|
||||
if (!data.position || data.position.x === undefined || data.position.y === undefined) {
|
||||
throw new Error(`Invalid position provided: ${data.position}`)
|
||||
}
|
||||
this.position = data.position
|
||||
}
|
||||
|
||||
remove() {
|
||||
return this.bp.removeTile(this)
|
||||
}
|
||||
|
||||
getData() {
|
||||
return {
|
||||
name: this.name,
|
||||
position: this.position
|
||||
}
|
||||
}
|
||||
}
|
||||
84
src/globals.ts
Normal file
84
src/globals.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { Blueprint } from './factorio-data/blueprint'
|
||||
import { ToolbarContainer } from './containers/toolbar'
|
||||
import { BlueprintContainer } from './containers/blueprint'
|
||||
import { EditEntityContainer } from './containers/editEntity'
|
||||
import { InventoryContainer } from './containers/inventory'
|
||||
|
||||
// tslint:disable:prefer-const
|
||||
|
||||
let app: PIXI.Application
|
||||
|
||||
let toolbarContainer: ToolbarContainer
|
||||
let editEntityContainer: EditEntityContainer
|
||||
let inventoryContainer: InventoryContainer
|
||||
let BPC: BlueprintContainer
|
||||
|
||||
let gridCoordsOfCursor: IPoint = { x: 0, y: 0 }
|
||||
let gridCoords16: IPoint = { x: 0, y: 0 }
|
||||
let railMoveOffset: IPoint = { x: 0, y: 0 }
|
||||
|
||||
let openedGUIWindow: InventoryContainer | EditEntityContainer | undefined
|
||||
|
||||
const cellSize = 32
|
||||
|
||||
const positionBPContainer = {
|
||||
x: 0,
|
||||
y: 32
|
||||
}
|
||||
|
||||
const bpArea = {
|
||||
width: 400,
|
||||
height: 400
|
||||
}
|
||||
|
||||
const sizeBPContainer = {
|
||||
width: bpArea.width * 32,
|
||||
height: bpArea.height * 32
|
||||
}
|
||||
|
||||
let bp: Blueprint
|
||||
|
||||
const mouseStates = {
|
||||
NONE: 0,
|
||||
MOVING: 1,
|
||||
PAINTING: 2,
|
||||
PANNING: 3
|
||||
}
|
||||
|
||||
const keyboard = {
|
||||
w: false,
|
||||
a: false,
|
||||
s: false,
|
||||
d: false,
|
||||
shift: false
|
||||
}
|
||||
|
||||
let currentMouseState = mouseStates.NONE
|
||||
|
||||
const copyData = {
|
||||
recipe: ''
|
||||
}
|
||||
|
||||
let renderOnly = false
|
||||
|
||||
export default {
|
||||
renderOnly,
|
||||
copyData,
|
||||
openedGUIWindow,
|
||||
inventoryContainer,
|
||||
editEntityContainer,
|
||||
BPC,
|
||||
app,
|
||||
keyboard,
|
||||
toolbarContainer,
|
||||
cellSize,
|
||||
bpArea,
|
||||
positionBPContainer,
|
||||
sizeBPContainer,
|
||||
gridCoordsOfCursor,
|
||||
railMoveOffset,
|
||||
gridCoords16,
|
||||
bp,
|
||||
mouseStates,
|
||||
currentMouseState
|
||||
}
|
||||
25
src/index.d.ts
vendored
Normal file
25
src/index.d.ts
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
declare module '*.json' {
|
||||
const content: any
|
||||
export default content
|
||||
}
|
||||
|
||||
interface IPoint {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
interface IEntityData {
|
||||
hr: boolean
|
||||
dir: number
|
||||
|
||||
bp: Blueprint
|
||||
position: IPoint
|
||||
hasConnections: boolean
|
||||
|
||||
assemblerPipeDirection: string
|
||||
dirType: string
|
||||
operator: string
|
||||
assemblerCraftsWithFluid: boolean
|
||||
trainStopColor: { r: number; g: number; b: number; a: number}
|
||||
chemicalPlantDontConnectOutput: boolean
|
||||
}
|
||||
13
src/index.html
Normal file
13
src/index.html
Normal file
File diff suppressed because one or more lines are too long
BIN
src/logo.png
Normal file
BIN
src/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
2
src/sample-blueprint.ts
Normal file
2
src/sample-blueprint.ts
Normal file
File diff suppressed because one or more lines are too long
1730
src/spritesheets/entitySpritesheet.json
Normal file
1730
src/spritesheets/entitySpritesheet.json
Normal file
File diff suppressed because it is too large
Load Diff
BIN
src/spritesheets/entitySpritesheet.png
Normal file
BIN
src/spritesheets/entitySpritesheet.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 MiB |
65
src/spritesheets/extra_iconSpritesheet.json
Normal file
65
src/spritesheets/extra_iconSpritesheet.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"frames": {
|
||||
"extra-icon:cursor-boxes-0": {
|
||||
"frame": { "x": 0, "y": 182, "w": 63, "h": 63 },
|
||||
"sourceSize": { "w": 64, "h": 64 }
|
||||
},
|
||||
"extra-icon:cursor-boxes-1": {
|
||||
"frame": { "x": 64, "y": 182, "w": 63, "h": 63 },
|
||||
"sourceSize": { "w": 64, "h": 64 }
|
||||
},
|
||||
"extra-icon:cursor-boxes-2": {
|
||||
"frame": { "x": 128, "y": 182, "w": 63, "h": 63 },
|
||||
"sourceSize": { "w": 64, "h": 64 }
|
||||
},
|
||||
"extra-icon:cursor-boxes-3": {
|
||||
"frame": { "x": 192, "y": 182, "w": 63, "h": 63 },
|
||||
"sourceSize": { "w": 64, "h": 64 }
|
||||
},
|
||||
"extra-icon:cursor-boxes-4": {
|
||||
"frame": { "x": 256, "y": 182, "w": 63, "h": 63 },
|
||||
"sourceSize": { "w": 64, "h": 64 }
|
||||
},
|
||||
"extra-icon:cursor-boxes": {
|
||||
"frame": { "x": 0, "y": 182, "w": 320, "h": 320 },
|
||||
"sourceSize": { "w": 320, "h": 320 }
|
||||
},
|
||||
"extra-icon:cursor-boxes-32x32-0": {
|
||||
"frame": { "x": 0, "y": 0, "w": 63, "h": 63 },
|
||||
"sourceSize": { "w": 64, "h": 64 }
|
||||
},
|
||||
"extra-icon:cursor-boxes-32x32": {
|
||||
"frame": { "x": 0, "y": 0, "w": 320, "h": 64 },
|
||||
"sourceSize": { "w": 320, "h": 64 }
|
||||
},
|
||||
"extra-icon:electricity-icon-unplugged": {
|
||||
"frame": { "x": 66, "y": 116, "w": 64, "h": 64 },
|
||||
"sourceSize": { "w": 64, "h": 64 }
|
||||
},
|
||||
"extra-icon:entity-info-dark-background": {
|
||||
"frame": { "x": 262, "y": 116, "w": 53, "h": 53 },
|
||||
"sourceSize": { "w": 53, "h": 53 }
|
||||
},
|
||||
"extra-icon:fluid-indication-arrow": {
|
||||
"frame": { "x": 0, "y": 66, "w": 48, "h": 48 },
|
||||
"sourceSize": { "w": 48, "h": 48 }
|
||||
},
|
||||
"extra-icon:fluid-indication-arrow-both-ways": {
|
||||
"frame": { "x": 50, "y": 66, "w": 48, "h": 48 },
|
||||
"sourceSize": { "w": 48, "h": 48 }
|
||||
},
|
||||
"extra-icon:indication-arrow": {
|
||||
"frame": { "x": 0, "y": 116, "w": 64, "h": 64 },
|
||||
"sourceSize": { "w": 64, "h": 64 }
|
||||
},
|
||||
"extra-icon:underground-lines-0": {
|
||||
"frame": { "x": 132, "y": 116, "w": 64, "h": 64 },
|
||||
"sourceSize": { "w": 64, "h": 64 }
|
||||
},
|
||||
"extra-icon:underground-lines-1": {
|
||||
"frame": { "x": 196, "y": 116, "w": 64, "h": 64 },
|
||||
"sourceSize": { "w": 64, "h": 64 }
|
||||
}
|
||||
},
|
||||
"meta": { "image": "extra_iconSprites.png" }
|
||||
}
|
||||
BIN
src/spritesheets/extra_iconSpritesheet.png
Normal file
BIN
src/spritesheets/extra_iconSpritesheet.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
1110
src/spritesheets/iconSpritesheet.json
Normal file
1110
src/spritesheets/iconSpritesheet.json
Normal file
File diff suppressed because it is too large
Load Diff
BIN
src/spritesheets/iconSpritesheet.png
Normal file
BIN
src/spritesheets/iconSpritesheet.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 519 KiB |
86
src/updateGroups.ts
Normal file
86
src/updateGroups.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import factorioData from './factorio-data/factorioData'
|
||||
|
||||
const updateGroups = [
|
||||
{
|
||||
is: [
|
||||
'transport-belt',
|
||||
'fast-transport-belt',
|
||||
'express-transport-belt',
|
||||
'splitter',
|
||||
'fast-splitter',
|
||||
'express-splitter',
|
||||
'underground-belt',
|
||||
'fast-underground-belt',
|
||||
'express-underground-belt'
|
||||
],
|
||||
updates: [
|
||||
'transport-belt',
|
||||
'fast-transport-belt',
|
||||
'express-transport-belt'
|
||||
]
|
||||
},
|
||||
{
|
||||
is: [
|
||||
'heat-pipe',
|
||||
'nuclear-reactor',
|
||||
'heat-exchanger'
|
||||
],
|
||||
updates: [
|
||||
'heat-pipe',
|
||||
'nuclear-reactor',
|
||||
'heat-exchanger'
|
||||
]
|
||||
},
|
||||
{
|
||||
has: [
|
||||
'fluid_box',
|
||||
'output_fluid_box',
|
||||
'fluid_boxes'
|
||||
],
|
||||
updates: [
|
||||
'fluid_box',
|
||||
'output_fluid_box',
|
||||
'fluid_boxes'
|
||||
]
|
||||
},
|
||||
{
|
||||
is: [
|
||||
'stone-wall',
|
||||
'gate',
|
||||
'straight-rail'
|
||||
],
|
||||
updates: [
|
||||
'stone-wall',
|
||||
'gate',
|
||||
'straight-rail'
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
for (const updateGroup of updateGroups) {
|
||||
if (updateGroup.has) {
|
||||
const is = []
|
||||
const updates = []
|
||||
for (let j = 0; j < updateGroup.has.length; j++) {
|
||||
const ed = factorioData.getEntities()
|
||||
for (const k in ed) {
|
||||
if (ed[k][updateGroup.has[j]]) {
|
||||
is.push(k)
|
||||
}
|
||||
}
|
||||
for (const k in ed) {
|
||||
if (ed[k][updateGroup.updates[j]]) {
|
||||
updates.push(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete updateGroup.has
|
||||
updateGroup.is = is
|
||||
updateGroup.updates = updates
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
updateGroups
|
||||
}
|
||||
157
src/util.ts
Normal file
157
src/util.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
function duplicate(obj: any) {
|
||||
return JSON.parse(JSON.stringify(obj))
|
||||
}
|
||||
|
||||
function set_shift(shift: any, tab: any) {
|
||||
tab.shift = shift
|
||||
if (tab.hr_version) {
|
||||
tab.hr_version.shift = shift
|
||||
}
|
||||
return tab
|
||||
}
|
||||
|
||||
function add_to_shift(shift: any, tab: any) {
|
||||
const SHIFT = shift.constructor === Object ? [shift.x, shift.y] : shift
|
||||
|
||||
tab.shift = tab.shift ? [SHIFT[0] + tab.shift[0], SHIFT[1] + tab.shift[1]] : SHIFT
|
||||
if (tab.hr_version) {
|
||||
tab.hr_version.shift = tab.hr_version.shift ?
|
||||
[SHIFT[0] + tab.hr_version.shift[0], SHIFT[1] + tab.hr_version.shift[1]] :
|
||||
SHIFT
|
||||
}
|
||||
return tab
|
||||
}
|
||||
|
||||
function set_property(img: any, key: string, val: any) {
|
||||
img[key] = val
|
||||
if (img.hr_version) {
|
||||
img.hr_version[key] = val
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
function set_property_using(img: any, key: any, key2: any, mult = 1) {
|
||||
if (key2) {
|
||||
img[key] = img[key2] * mult
|
||||
if (img.hr_version) {
|
||||
img.hr_version[key] = img.hr_version[key2] * mult
|
||||
}
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
function duplicateAndSetPropertyUsing(img: any, key: any, key2: any, mult: number) {
|
||||
return set_property_using(this.duplicate(img), key, key2, mult)
|
||||
}
|
||||
|
||||
function getRandomInt(min: number, max: number) {
|
||||
const MIN = Math.ceil(min)
|
||||
const MAX = Math.floor(max)
|
||||
return Math.floor(Math.random() * (MAX - MIN)) + MIN
|
||||
}
|
||||
|
||||
function rotatePointBasedOnDir(p: any, dir: number) {
|
||||
const point: IPoint = {x: 0, y: 0}
|
||||
const nP = p instanceof Array ? { x: p[0], y: p[1] } : { ...p }
|
||||
switch (dir) {
|
||||
case 0:
|
||||
// x y
|
||||
point.x = nP.x
|
||||
point.y = nP.y
|
||||
break
|
||||
case 2:
|
||||
// -y x
|
||||
point.x = nP.y * -1
|
||||
point.y = nP.x
|
||||
break
|
||||
case 4:
|
||||
// -x -y
|
||||
point.x = nP.x * -1
|
||||
point.y = nP.y * -1
|
||||
break
|
||||
case 6:
|
||||
// y -x
|
||||
point.x = nP.y
|
||||
point.y = nP.x * -1
|
||||
}
|
||||
|
||||
// if (retArray) return [point.x, point.y]
|
||||
return point
|
||||
}
|
||||
|
||||
function transformConnectionPosition(position: IPoint, direction: number) {
|
||||
const dir = Math.abs(position.x) > Math.abs(position.y) ?
|
||||
(Math.sign(position.x) === 1 ? 2 : 6) :
|
||||
(Math.sign(position.y) === 1 ? 4 : 0)
|
||||
switch (dir) {
|
||||
case 0: position.y += 1; break
|
||||
case 2: position.x -= 1; break
|
||||
case 4: position.y -= 1; break
|
||||
case 6: position.x += 1
|
||||
}
|
||||
return rotatePointBasedOnDir(position, direction)
|
||||
}
|
||||
|
||||
function switchSizeBasedOnDirection(defaultSize: { width: any; height: any }, direction: number) {
|
||||
if (defaultSize.width !== defaultSize.height && (direction === 2 || direction === 6)) {
|
||||
return { x: defaultSize.height, y: defaultSize.width }
|
||||
}
|
||||
return { x: defaultSize.width, y: defaultSize.height }
|
||||
}
|
||||
|
||||
function findBPString(data: string) {
|
||||
const DATA = data.replace(/\s/g, '')
|
||||
|
||||
if (DATA[0] === '0') return new Promise(resolve => resolve(DATA))
|
||||
|
||||
// function isUrl(url: string) {
|
||||
// try { return Boolean(new URL(url)) }
|
||||
// catch (e) { return false }
|
||||
// }
|
||||
|
||||
return new Promise(resolve => resolve(new URL(DATA))).then((url: URL) => {
|
||||
console.log(`Loading data from: ${url}`)
|
||||
const pathParts = url.pathname.slice(1).split('/')
|
||||
switch (url.hostname.split('.')[0]) {
|
||||
case 'pastebin':
|
||||
case 'hastebin':
|
||||
return fetchData(`${url.hostname}/raw/${pathParts[0]}`).then(r => r.text())
|
||||
case 'gist':
|
||||
return fetchData(`api.github.com/gists/${pathParts[1]}`).then(r =>
|
||||
r.json().then(data => data.files[Object.keys(data.files)[0]].content)
|
||||
)
|
||||
case 'gitlab':
|
||||
return fetchData(`${url}/raw`).then(r => r.text())
|
||||
case 'factorioprints':
|
||||
return fetchData(`facorio-blueprints.firebaseio.com/blueprints/${pathParts[1]}.json`).then(r =>
|
||||
r.json().then(data => data.blueprintString)
|
||||
)
|
||||
case 'docs':
|
||||
return fetchData(`${url.toString().replace('/edit', '')}/export?format=txt`).then(r => r.text())
|
||||
default:
|
||||
return fetchData(url.toString()).then(r => r.text())
|
||||
}
|
||||
// TODO: maybe add dropbox support https://www.dropbox.com/s/ID?raw=1
|
||||
})
|
||||
|
||||
function fetchData(url: string) {
|
||||
return fetch('https://allorigins.me/get?method=raw&url=' + url).then(response => {
|
||||
if (response.ok) return response
|
||||
throw new Error('Network response was not ok.')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
findBPString,
|
||||
duplicate,
|
||||
set_shift,
|
||||
set_property,
|
||||
set_property_using,
|
||||
add_to_shift,
|
||||
getRandomInt,
|
||||
duplicateAndSetPropertyUsing,
|
||||
rotatePointBasedOnDir,
|
||||
transformConnectionPosition,
|
||||
switchSizeBasedOnDirection
|
||||
}
|
||||
189
src/zoomPan.ts
Normal file
189
src/zoomPan.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import * as PIXI from 'pixi.js'
|
||||
|
||||
export class ZoomPan {
|
||||
|
||||
private container: PIXI.Container
|
||||
private size: any
|
||||
private viewPortPosition: any
|
||||
private viewPortSize: any
|
||||
private maxZoom: number
|
||||
private dirty: boolean
|
||||
private positionX: number
|
||||
private positionY: number
|
||||
private scaleX: number
|
||||
private scaleY: number
|
||||
private scaleCenterX: number
|
||||
private scaleCenterY: number
|
||||
private origTransform: PIXI.Matrix
|
||||
private transform: PIXI.Matrix
|
||||
|
||||
constructor(container: PIXI.Container, size: any, viewPortPosition: any, viewPortSize: any, maxZoom: number) {
|
||||
this.container = container
|
||||
|
||||
this.size = size
|
||||
this.viewPortPosition = viewPortPosition
|
||||
this.viewPortSize = viewPortSize
|
||||
this.maxZoom = maxZoom
|
||||
|
||||
this.dirty = true
|
||||
|
||||
this.positionX = 0
|
||||
this.positionY = 0
|
||||
|
||||
this.scaleX = 1
|
||||
this.scaleY = 1
|
||||
|
||||
this.scaleCenterX = 0
|
||||
this.scaleCenterY = 0
|
||||
|
||||
this.origTransform = new PIXI.Matrix()
|
||||
|
||||
this.transform = new PIXI.Matrix()
|
||||
}
|
||||
|
||||
updateTransform() {
|
||||
const t = this.getTransform()
|
||||
this.container.setTransform(t.tx, t.ty, t.a, t.d)
|
||||
}
|
||||
|
||||
_updateMatrix() {
|
||||
// Accumulate zoom transformations.
|
||||
// origTransform is an intermediate accumulative matrix used for tracking the current zoom target.
|
||||
this.origTransform.append(new PIXI.Matrix(1, 0, 0, 1, this.scaleCenterX, this.scaleCenterY))
|
||||
this.origTransform.append(new PIXI.Matrix(this.scaleX, 0, 0, this.scaleY, 0, 0))
|
||||
this.origTransform.append(new PIXI.Matrix(1, 0, 0, 1, -this.scaleCenterX, -this.scaleCenterY))
|
||||
|
||||
// We reset Scale because origTransform is accumulative and has "captured" the information.
|
||||
this.scaleX = 1
|
||||
this.scaleY = 1
|
||||
|
||||
// Tack on translation. Note: we don't append it, but concat it into a separate matrix.
|
||||
// We want to leave origTransform solely responsible for zooming.
|
||||
// "transform" is the final matrix.
|
||||
this.transform = this.origTransform.clone()
|
||||
|
||||
// UpperLeft Corner constraints
|
||||
const minX = this.viewPortPosition.x - this.transform.tx
|
||||
const minY = this.viewPortPosition.y - this.transform.ty
|
||||
// LowerRight Corner constraints
|
||||
const maxX = -(this.size.width * this.transform.a - this.viewPortSize.width) - this.transform.tx
|
||||
const maxY = -(this.size.height * this.transform.a - this.viewPortSize.height) - this.transform.ty
|
||||
|
||||
// Check if viewport area is bigger than the container
|
||||
if (maxX - minX > 0 || maxY - minY > 0) {
|
||||
this.origTransform = new PIXI.Matrix()
|
||||
|
||||
this.scaleCenterX = this.size.width / 2
|
||||
this.scaleCenterY = this.size.height / 2
|
||||
|
||||
const maxZoom = Math.max(
|
||||
this.viewPortSize.width / (this.size.width * this.transform.a),
|
||||
this.viewPortSize.height / (this.size.height * this.transform.a)
|
||||
) * this.transform.a
|
||||
this.scaleX = maxZoom
|
||||
this.scaleY = maxZoom
|
||||
|
||||
this._updateMatrix()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (this.positionX > minX) this.positionX = minX
|
||||
if (this.positionY > minY) this.positionY = minY
|
||||
if (this.positionX < maxX) this.positionX = maxX
|
||||
if (this.positionY < maxY) this.positionY = maxY
|
||||
|
||||
this.transform.translate(this.positionX, this.positionY)
|
||||
}
|
||||
|
||||
centerViewPort(focusObjectSize: IPoint, offset: IPoint) {
|
||||
this.origTransform = new PIXI.Matrix()
|
||||
|
||||
this.positionX = this.viewPortPosition.x - (this.size.width / 2) +
|
||||
(this.viewPortSize.width - this.viewPortPosition.x) / 2 + offset.x
|
||||
this.positionY = this.viewPortPosition.y - (this.size.height / 2) +
|
||||
(this.viewPortSize.height - this.viewPortPosition.y) / 2 + offset.y
|
||||
|
||||
this.scaleCenterX = this.size.width / 2 + -offset.x
|
||||
this.scaleCenterY = this.size.height / 2 + -offset.y
|
||||
|
||||
const zoom = Math.min(
|
||||
(this.viewPortSize.width - this.viewPortPosition.x) / focusObjectSize.x,
|
||||
(this.viewPortSize.height - this.viewPortPosition.y) / focusObjectSize.y
|
||||
)
|
||||
this.scaleX = zoom
|
||||
this.scaleY = zoom
|
||||
|
||||
this.dirty = true
|
||||
this.updateTransform()
|
||||
}
|
||||
|
||||
getTransform() {
|
||||
if (this.dirty) {
|
||||
this._updateMatrix()
|
||||
this.dirty = false
|
||||
}
|
||||
return this.transform
|
||||
}
|
||||
|
||||
setViewPortSize(width: number, height: number) {
|
||||
this.viewPortSize.width = width
|
||||
this.viewPortSize.height = height
|
||||
this.dirty = true
|
||||
}
|
||||
|
||||
setPosition(posX: number, posY: number) {
|
||||
this.positionX = posX
|
||||
this.positionY = posY
|
||||
this.dirty = true
|
||||
}
|
||||
|
||||
getPositionX() {
|
||||
return this.positionX
|
||||
}
|
||||
|
||||
getPositionY() {
|
||||
return this.positionY
|
||||
}
|
||||
|
||||
zoomBy(deltaX: number, deltaY: number) {
|
||||
if (Math.sign(deltaX) === 1 && this.origTransform.a > this.maxZoom) return
|
||||
this.scaleX += deltaX
|
||||
this.scaleY += deltaY
|
||||
this.dirty = true
|
||||
}
|
||||
|
||||
translateBy(deltaX: number, deltaY: number) {
|
||||
this.positionX += deltaX
|
||||
this.positionY += deltaY
|
||||
this.dirty = true
|
||||
}
|
||||
|
||||
setCurrentScale(newScale: number) {
|
||||
if (this.dirty) {
|
||||
this._updateMatrix()
|
||||
}
|
||||
|
||||
// We use dimensional analysis to set the scale. Remember we can't
|
||||
// just set the scale absolutely because origTransform is an accumulating matrix.
|
||||
// We have to take its current value and compute a new value based
|
||||
// on the passed in value.
|
||||
|
||||
const scaleFactor = newScale / this.origTransform.a
|
||||
|
||||
this.scaleX = scaleFactor
|
||||
this.scaleY = scaleFactor
|
||||
|
||||
this.dirty = true
|
||||
}
|
||||
|
||||
getCurrentScale() {
|
||||
return this.origTransform.a
|
||||
}
|
||||
|
||||
setScaleCenter(posX: number, posY: number) {
|
||||
this.scaleCenterX = posX
|
||||
this.scaleCenterY = posY
|
||||
this.dirty = true
|
||||
}
|
||||
}
|
||||
24
tsconfig.json
Normal file
24
tsconfig.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015",
|
||||
"es2016",
|
||||
"es6"
|
||||
],
|
||||
"target": "es6",
|
||||
"module": "es2015",
|
||||
"moduleResolution": "node",
|
||||
"noImplicitAny": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"removeComments": false,
|
||||
"preserveConstEnums": true,
|
||||
"sourceMap": true,
|
||||
"skipLibCheck": true,
|
||||
"declaration": true,
|
||||
"alwaysStrict": true
|
||||
}
|
||||
}
|
||||
122
tslint.json
Normal file
122
tslint.json
Normal file
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"extends": [
|
||||
"tslint:all"
|
||||
],
|
||||
"rules": {
|
||||
"no-any": false,
|
||||
"typedef": [
|
||||
false,
|
||||
"call-signature",
|
||||
"arrow-call-signature",
|
||||
"parameter",
|
||||
"arrow-parameter",
|
||||
"property-declaration",
|
||||
"member-variable-declaration"
|
||||
],
|
||||
"no-shadowed-variable": [
|
||||
false,
|
||||
{
|
||||
"class": true,
|
||||
"enum": true,
|
||||
"function": true,
|
||||
"interface": true,
|
||||
"namespace": true,
|
||||
"typeAlias": false,
|
||||
"typeParameter": false
|
||||
}
|
||||
],
|
||||
|
||||
"align": [
|
||||
true,
|
||||
"parameters",
|
||||
"statements",
|
||||
"elements",
|
||||
"members"
|
||||
],
|
||||
"no-default-export": false,
|
||||
"cyclomatic-complexity": false,
|
||||
"binary-expression-operand-order": false,
|
||||
"one-variable-per-declaration": [true, "ignore-for-loop"],
|
||||
"newline-per-chained-call": false,
|
||||
"prefer-template": [true, "allow-single-concat"],
|
||||
"newline-before-return": false,
|
||||
"no-magic-numbers": false,
|
||||
"member-ordering": [true, { "order": "statics-first" }],
|
||||
"arrow-parens": [true, "ban-single-arg-parens"],
|
||||
"class-name": true,
|
||||
"comment-format": [true, "check-space"],
|
||||
"indent": [true, "spaces", 4],
|
||||
"interface-name": [true, "always-prefix"],
|
||||
"max-classes-per-file": false,
|
||||
"max-line-length": [true, 150],
|
||||
"member-access": false,
|
||||
"no-duplicate-variable": true,
|
||||
"no-empty": true,
|
||||
"no-eval": true,
|
||||
"no-internal-module": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"no-var-keyword": true,
|
||||
"object-literal-sort-keys": false,
|
||||
"one-line": [
|
||||
true,
|
||||
"check-else",
|
||||
"check-open-brace",
|
||||
"check-whitespace"
|
||||
],
|
||||
"ordered-imports": false,
|
||||
"radix": false,
|
||||
"quotemark": [
|
||||
true,
|
||||
"single",
|
||||
"avoid-escape"
|
||||
],
|
||||
"semicolon": [true, "never"],
|
||||
"no-implicit-dependencies": [true, "dev"],
|
||||
"triple-equals": true,
|
||||
"prefer-for-of": true,
|
||||
"prefer-const": true,
|
||||
"curly": [true, "ignore-same-line"],
|
||||
"only-arrow-functions": [true, "allow-named-functions"],
|
||||
"no-console": false,
|
||||
"no-string-literal": false,
|
||||
"forin": false,
|
||||
"switch-default": false,
|
||||
"trailing-comma": [
|
||||
true,
|
||||
{
|
||||
"multiline": "never",
|
||||
"singleline": "never",
|
||||
"esSpecCompliant": true
|
||||
}
|
||||
],
|
||||
"typedef-whitespace": [
|
||||
true,
|
||||
{
|
||||
"call-signature": "nospace",
|
||||
"index-signature": "nospace",
|
||||
"parameter": "nospace",
|
||||
"property-declaration": "nospace",
|
||||
"variable-declaration": "nospace"
|
||||
}
|
||||
],
|
||||
"variable-name": [
|
||||
true,
|
||||
"ban-keywords",
|
||||
"check-format",
|
||||
"allow-snake-case"
|
||||
],
|
||||
"whitespace": [
|
||||
true,
|
||||
"check-branch",
|
||||
"check-decl",
|
||||
"check-operator",
|
||||
"check-module",
|
||||
"check-separator",
|
||||
"check-rest-spread",
|
||||
"check-type",
|
||||
"check-typecast",
|
||||
"check-type-operator",
|
||||
"check-preblock"
|
||||
]
|
||||
}
|
||||
}
|
||||
83
webpack.common.js
Normal file
83
webpack.common.js
Normal file
@@ -0,0 +1,83 @@
|
||||
'use strict'
|
||||
// https://medium.com/webpack/webpack-4-mode-and-optimization-5423a6bc597a
|
||||
const path = require('path')
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const CleanWebpackPlugin = require('clean-webpack-plugin')
|
||||
const FaviconsWebpackPlugin = require('webapp-webpack-plugin') // favicons-webpack-plugin
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
|
||||
|
||||
const extractPlugin = new ExtractTextPlugin({
|
||||
filename: '[name].css',
|
||||
allChunks: true
|
||||
})
|
||||
|
||||
const babelLoader = {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
cacheDirectory: true,
|
||||
presets: [['@babel/preset-env', { useBuiltIns: 'entry' }]]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
target: 'web',
|
||||
entry: {
|
||||
main: './src/app'
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, './dist'),
|
||||
filename: '[name].js'
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.json', '.ts']
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
babelLoader
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.ts?$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
babelLoader,
|
||||
{
|
||||
loader: 'ts-loader',
|
||||
options: { transpileOnly: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /normalize.css/,
|
||||
use: extractPlugin.extract({
|
||||
use: ['css-loader']
|
||||
})
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
template: 'src/index.html',
|
||||
hash: true
|
||||
}),
|
||||
extractPlugin,
|
||||
new FaviconsWebpackPlugin({
|
||||
logo: './src/logo.png'
|
||||
}),
|
||||
// https://github.com/ProvidenceGeeks/website-frontend/pull/142
|
||||
new CopyWebpackPlugin([
|
||||
{ from: 'src/spritesheets', to: 'spritesheets'/*'factorio-data/bundles/[name].[hash].[ext]'*/ }
|
||||
]),
|
||||
new CleanWebpackPlugin(['dist']),
|
||||
new ForkTsCheckerWebpackPlugin({
|
||||
tslint: true,
|
||||
watch: ['./src']
|
||||
})
|
||||
]
|
||||
}
|
||||
9
webpack.dev.js
Normal file
9
webpack.dev.js
Normal file
@@ -0,0 +1,9 @@
|
||||
'use strict'
|
||||
|
||||
const merge = require('webpack-merge')
|
||||
const common = require('./webpack.common.js')
|
||||
|
||||
module.exports = merge(common, {
|
||||
mode: 'development'
|
||||
// devtool: 'inline-source-map'
|
||||
})
|
||||
38
webpack.prod.js
Normal file
38
webpack.prod.js
Normal file
@@ -0,0 +1,38 @@
|
||||
'use strict'
|
||||
|
||||
const merge = require('webpack-merge')
|
||||
const common = require('./webpack.common.js')
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
|
||||
const Visualizer = require('webpack-visualizer-plugin')
|
||||
//const ClosureCompilerPlugin = require('webpack-closure-compiler')
|
||||
|
||||
// https://webpack.github.io/analyse/
|
||||
|
||||
module.exports = merge(common, {
|
||||
mode: 'production',
|
||||
profile: true,
|
||||
plugins: [
|
||||
new OptimizeCssAssetsPlugin({
|
||||
cssProcessorOptions: { discardComments: { removeAll: true } }
|
||||
}),
|
||||
new BundleAnalyzerPlugin({
|
||||
reportFilename: './stats/report.html',
|
||||
analyzerMode: 'static',
|
||||
openAnalyzer: false,
|
||||
generateStatsFile: true,
|
||||
statsFilename: './stats/stats.json'
|
||||
}),
|
||||
new Visualizer({
|
||||
filename: './stats/statistics.html'
|
||||
})
|
||||
// new ClosureCompilerPlugin({
|
||||
// compiler: {
|
||||
// language_in: 'ECMASCRIPT6',
|
||||
// language_out: 'ECMASCRIPT5',
|
||||
// compilation_level: 'ADVANCED'
|
||||
// },
|
||||
// jsCompiler: true,
|
||||
// concurrency: 3,
|
||||
// })
|
||||
]
|
||||
})
|
||||
Reference in New Issue
Block a user