diff --git a/CHANGES.md b/CHANGES.md index 2026e0f7..d245d8ac 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,160 @@ +# Version 0.4.0 + +Updated in October of 2023. + +## New Features + +- General vehicle support + * Entering and exiting a vehicle will notify you about the vehicle name. + * Press K inside a vehicle to learn the heading and coordinates. + * Pressing L while inside a vehicle will provide additional information about it. For cars, the fuel stocks are stated. For trains, basic info is given while the train menu has detailed info. + * You can refuel a vehicle by dropping a fuel item stack in hand into it via CONTROL + LEFT BRACKET. + * You can check the fuel inventory contents by pressing RIGHT BRACKET. + +- Trains Implementation. Phases 0 through 3, out of 5, are complete. In general, we made it accessible to build rail lines, analyze rail segments, build train stations, build and examine trains, and drive trains manually or automatically between two stations. + * Added info support for rails. There is now distinction between end rails, station rails, etc. and the directions of rails are given. + * Added support for building and renaming train stops. + * Added train station rail information tool. When you look at a rail behind a train stop, you get information of which section of which rail vehicle would be positioned there when a train stops at the station. + * Rail appender tool: Adds a straight rail to extend any straight end rail near the cursor. + * Rail structure building menu: Allows building correctly oriented railway structures based on a selected end rail as the anchor point. Also can add signals to mid rails. + * Functions added to build 45 degree turns and 90 degree turns as structures at end rails. + * Added support for building and naming trains. + * Allowed reading the fuel amount in a locomotive: check its status via RIGHT BRACKET. + * The contents of cargo wagons and fluid wagons can be checked via RIGHT BRACKET. + * Information about a train can be read from its menu including name, length, vehicle counts, cargo item counts, etc. + * Rail analyzer tool: Press J inside a train to learn the nearest structure ahead on the rails and the distance to it. Press SHIFT + J to check the opposite direction. + * Rail analyzer tool works on foot as well. + * Subautomatic travel added: You can select an option from the train menu to make a train go by itself to a far away station and wait until all passengers get off. + * Automatic travel via instant train scheduler added. An instant automatic schedule rotates between every reachable train stop and waits at each for 5 minutes. + * Trains announce to passengers their next stations when departing or arriving. + * Trains announce to passengers when waiting at rail signals. + * Rail crossing alert tool added. Slow beep means a train moving somewhere within 200 tiles. Fast beep means an automatic train within 100 tiles is heading towards your general direction. Frantic fast alarm bells mean that you need to get off that rail ASAP! + * Rail chain signal placement designed to be same as in Vanilla. You need to craft any signals you place and they are refunded when mined. + * Other additions all across the code to support trains. + * Note 1: In terms of missing features, there is currently no support for rail forks, one-way rails, and fine control over train schedules. + * Note 2: Parallel rail lines should be at least a few tiles apart. This is partially enforced for the rail appender tool. + * Note 3: See Chapter 16 of the wiki for more info. + +- Improved electric pole support with new features. + * Selecting an electric pole now lists all the wire connection distances, and counts the energy consumers and producers within its supply area. + * The pole's electric network's demand satisfaction percentages are now reported, as well as the network flow rate and capacity. Check with RIGHT BRACKET. + * If an electric pole has no power flowing (as in 0 satisfaction), the nearest supplied electric pole is reported. + * When placing an electric pole, all connectible electric poles around it are listed. If there are none, the nearest electric pole is listed. This context info allows better understanding of electric networks. + +- Added building preview information for several structures. This information is based on the cursor location while an item to be built is held in hand. + * For small buildings, the preview can warn when building is not possible. + * For transport belt types, the belt junction type that would form by placing the belt there is stated. + * For underground belts and pipes to ground, whether and where the currently selected tile would connect is stated. + * For pipe units, the expected fluid pipe connections around are stated, and there is a warning about potentially mixing fluids. + * For electric powered machines, if power is connected, an electric pole that supplies it is listed. If not, the nearest electric pole is listed. Note that this feature is a little inaccurate around supply area boundaries except when in cursor mode. + * Other similar changes. + +- Added support for splitter priority settings. + * Press SHIFT + LEFT ARROW to set the INPUT priority to the left side, meaning that the splitter will take from the right only when it cannot from the left. + * Similarly press SHIFT + RIGHT ARROW to set the INPUT priority to the right side. + * To clear the input priority, try to set the same side again. + * Press CONTROL + LEFT ARROW to set the OUTPUT priority to the left side, meaning that the splitter will output to the right only when it cannot to the left. + * Similarly press CONTROL + RIGHT ARROW to set the OUTPUT priority to the right side. + * To clear the output priority, try to set the same side again. + +- Added support for splitter filter mode. + * With an item in hand, press CONTROL + LEFT BRACKET on a splitter to set its output filter to the item. When there is a filtered item, it goes out one side and every other item goes out the other side. + * By default the filter output goes left. To change the filter output side, press CONTROL + ARROW KEY accordingly. There is no equal distribution in filter mode. + * To clear the filter, with an empty hand press CONTROL + LEFT BRACKET on the splitter. + +- Added support for armor and armor equipment modules. + * NOTE: All the following controls apply when the inventory (alone) is open, meaning that no buildings are open. + * Press LEFT BRACKET to pick up the item and then press SHIFT + LEFT BRACKET to equip it. Armor equipment requires wearing armor that can support it. + * Press G to read armor name and equipment statistics, if any. "G" is for "Gear". + * Press SHIFT + G to read the list of equipment. + * Press CONTROL + SHIFT + G to remove all equipment and armor. + +- Added support for hand weapons / guns. + * Your weapons inventory can hold up to three weapons. You cycle between them by pressing TAB when not in a menu. You also have three corresponsing ammo slots. + * NOTE: All the following controls apply when the inventory (alone) is open, meaning that no buildings are open. + * Press LEFT BRACKET to pick up the weapon or ammo stack and then press SHIFT + LEFT BRACKET to equip it. + * Press R to read current weapons and ammunition counts. "R" is for "arms" or "reloading". + * Press SHIFT + R to reload all weapon ammunition slots from your inventory. Note that existing ammo stacks will be prefered over fuller ammo stacks of different types. + * Press CONTROL + SHIFT + R to remove all weapons and ammunition. + +- Added entity part identification: When you press K with the cursor hovering over an entity, its selected part will be reported, such as the southeast corner or the center. + +- Added build lock smart placement for medium electric poles. They are placed to allow maxiumum continuous area coverage rather than maximum wire reach. + +- Group mining added: If you press SHIFT + X on a tree or a rail, it will mine all of them immediately around you instead of only one. + +- Mine lock added: Press CONTROL + X to hold the cut-and-paste tool. Every building the cursor touches while holding this tool will be mined instantly if possible. To disable to tool, empty your hand with SHIFT + Q. + +- Added support for placing stone bricks and concrete varients as tiles, for making pathways or decoration. + +- Added support for placing landfill over water. Note: This is not reversible. + +- Added support for throwing capsule items, including cliff explosives, defender drones, and grenades. Note: Grenades will damage everything including you while cliff explosives affect only cliffs. + +- Added support for nuclear power buildings: Temperature readings and other relevant information is now provided. + +- Added support for rocket silos. They can now report rocket part counts and launch rockets when ready. + +## Changes + +- You are now able to walk over pipes and under small or medium electric poles. This change reflects both what popular mods support already. Also tip: In smooth walking mode, you can already squeeze around inserters and chests or in between most side-by-side buildings. + +- The intermediate walking mode has been removed so that you can easily switch between telestep and smooth walking. When in game mod settings are supported in the future, we plan to bring it back. + +- Transport belt junction types are now better identified. + * Note that a "pouring end" could either be sideloading onto a different belt, or continuing into a belt corner which preserves the lanes. + * Safe merging junctions (also called T-junctions) are identified as a belt unit that is double sideloaded but empty at the back so that the lanes will be entirely predictable. + +- Underground belts can now be identified as entrances or exits, explaining whether items are flowing into the ground or out from it. + +- Teleporting is disabled while riding a vehicle. + +- Reported local time has been shifted by twelve hours so that daytime is 6 to 18 and midnight is around 24. + +- Minor changes + * Changed the keybind for disconnecting rail vehicles from "V" to "SHIFT + G". + * Entity ghosts are now better identified. + * Items on the ground are now better identified. + * Boilers and heat exchangers now report their fluid contents. + * For all entities reporting fluid contents, any extra fluids are also reported. + * Beacon contents are now read. + * Containers now report their top 2 items at first look, instead of 1. + * If the player inventory being full prevents an item transfer, this should be announced correctly. + * Added new sound effects for the cursor reaching the borders of inventories. + + +## Bugfixes + +- Fixed a bug where the inventory is opened directly when a Factorio Access menu is closed. + +- Fixed a bug where entities that have not started using their fuel are reported as out of fuel. + +- Fixed various crashes related to reading invalid items. + +- Corrected the information error where items cannot be transferred to another building. It is not necessarily because it is full. + +## Code Changes + +- New helper functions (Todo: copy over the precise names)** + * get_direction_of_that_from_this - reports the direction of that position according to this position. Reports 8 main directions, with perfect alignment not being necessary to declare the four cardinal directions. + * direction_lookup - convert direction defines integer to a text. + * rotate_90 and rotate_180, for changing direcitons + * mine_trees_and_rocks + * get_entity_part_at_cursor + +- For several main functions, parts of them were taken out and made new functions that can be called from multiple places. + +- Added new file "rails-and-trains.lua" that houses most vehicle and train related content, as well as some universal helper functions. + +- Added several new controls to data.lua + +- Modified some vanilla prototypes collision masks to allow walking through pipes and poles. + +- Teleporting function can now also be called silently. + +- Future proofing: Renamed all directions in "rails-and-trains.lua" to use the defines instead of hardcoded integers + + # Version 0.3.1 Updated 10/26/2022 diff --git a/README.md b/README.md index c6152e87..e283a0dd 100644 --- a/README.md +++ b/README.md @@ -44,13 +44,25 @@ Note: If you have done a full installation using the .zip instructions, there i -# Mod controls +# Factorio Access Controls + +## General + +Time of day and current research: T + +Save game: F1 + +Recalibrate: CONTROL + END + +Pause or unpause the game with the visual pause menu: ESC + +Pause or unpause the game with no menu: SHIFT + SPACE ## Movement Movement: W A S D -Note: When you change direction, your character doesn't immediately move a tile in that direction. Think of it like turning your head before taking a step. +Note: When you change direction, for the first key press, your character turns but does not take a step in that direction. Change movement mode: CONTROL + W @@ -58,7 +70,7 @@ Note the 3 movement types are as follows: 1- Telestep: Press a direction to turn in that direction, then continue pressing in that direction to move. - 2- Step-By-Walk: This mode is similar to Telestep, however the player character will physically take steps in the direction chosen. The biggest difference is footsteps. + 2- Step-By-Walk: Temporarily removed. This mode is similar to Telestep, however the player character will physically take steps in the direction chosen. The biggest difference is footsteps. 3- Smooth-Walking: In this mode the character will move similarly to in a sighted game. The player will be notified if they run into something, but otherwise will not be notified of entities they are passing. Very fast, and great for getting around! @@ -66,39 +78,45 @@ Note the 3 movement types are as follows: Get entity description: L, for most entities such as buildings -Get building status: RIGHT BRACKET, for applicable buildings when your hand is empty +Read building status: RIGHT BRACKET, for applicable buildings when your hand is empty Open building's menu: LEFT BRACKET Mine or pick up: X +Mine or pick up a group: SHIFT + X. Applies only to some groups such as trees or rails. + +Grab in hand instant mining tool: CONTROL + X. Also known as the cut and paste tool, this will instantly mine almost anything touched by the cursor. Does not work for ores. + +Put away instant mining tool: SHIFT + Q + Open player inventory: E -Rotate: R +Rotate: R. -Note: If you have something in your hand, you will rotate that. Otherwise you will rotate the building your cursor is over. +Rotation Note 1: If you have something in your hand, you will rotate that. Otherwise you will rotate the building your cursor is over. -Additional Note: The first time you press the rotate key, it will simply say the direction a building is facing. Subsequent presses will actually rotate the building. +Rotation Note 2: The first time you press the rotate key, it will simply say the direction a building is facing. Subsequent presses will actually rotate the building. -Picker tool: SHIFT + Q, brings to hand more of the selected item if you have it in your inventory +Picker tool: For a detected entity, SHIFT + Q. This brings to hand more of the selected entity's item form, if you have it in your inventory. Nudge building by one tile: CONTROL + SHIFT + DIRECTION, where the direction is one of W A S D. -Pick up items on the ground or on top of nearby belts: F +Pick up nearby items on the ground or on top of nearby belts: Hold F -Copy building settings: SHIFT + RIGHT BRACKET on the building, with empty hand +Copy building settings: With empty hand, SHIFT + RIGHT BRACKET on the building -Paste building settings: SHIFT + LEFT BRACKET on the building, with empty hand +Paste building settings: With empty hand, SHIFT + LEFT BRACKET on the building -Quickly collect the entire output of a building: CONTROL + LEFT BRACKET on the building, with empty hand +Quickly collect the entire output of a building: With empty hand, CONTROL + LEFT BRACKET on the building -Quickly collect half of the entire output of a building: CONTROL + RIGHT BRACKET on the building, with empty hand +Quickly collect half of the entire output of a building: With empty hand, CONTROL + RIGHT BRACKET on the building ## Cursor -Speak cursor coordinates: K +Speak cursor coordinates: K. If the cursor is over an entity, its relative location ıupon the entity is read out, such as the Southwest corner. -Cursor mode: I +Enable or disable cursor mode: I Move cursor freely in cursor mode: W A S D Jump cursor to character: J @@ -137,25 +155,65 @@ Get info on item in hand: L Empty the hand to your inventory: SHIFT + Q -Picker tool: Grab in hand more of the item in front of you, if you have it: SHIFT+ Q +Picker tool: With an empty hand, SHIFT + Q. Grabs from the inventory to the hand, more of the item related to the entity in front of you. -Grab item in hand from the quickbar: NUMBER KEY, for set up quickbar slots +Pick from quickbar: NUMBER KEY + +Assign hand item to a quickbar number: CONTROL + NUMBER KEY Place building: LEFT BRACKET, for items that support it -Toggle build lock for continuous building: CONTROL + B, while switching cursor modes and emptying the hand also disables it. +Toggle build lock for continuous building: CONTROL + B. It is also turned off while switching cursor modes or emptying the hand. + +Rotate: R. + +Rotation Note 1: If you have something in your hand, you will rotate that. Otherwise you will rotate the building your cursor is over. + +Rotation Note 2: The first time you press the rotate key, it will simply say the direction a building is facing. Subsequent presses will actually rotate the building. + +Drop 1 unit: Z. Drops the item onto the ground or onto a belt or inside an applicable building. -Rotate: R +Insert 1 stack of the item in hand where applicable: CONTROL + LEFT BRACKET. Works for chests or for smartly feeding machines and vehicles. -Note: If you have something in your hand, you will rotate that. Otherwise you will rotate the building your cursor is over. +Insert half a stack of the item in hand where applicable: CONTROL + RIGHT BRACKET. Works for chests or for smartly feeding machines and vehicles. -Additional Note: The first time you press the rotate key, it will simply say the direction a building is facing. Subsequent presses will actually rotate the building. +## Floor Pavings and Thrown Items -Drop 1 unit of the item onto the ground or onto a belt or inside an applicable building: Z +Pave the floor with bricks or concrete: With the paving item in hand, LEFT BRACKET. This affects a 3 by 3 area with your character in the center. + +Pick up floor paving: With any bricks or concrete in hand: X. This will pick up a 2 by 2 area centered on the cursor. + +Place landfill over water: With landfill in hand, LEFT BRACKET. This affects any water in a 3 by 3 area with your character in the center. Note: This is not reversible! + +Throw a capsule item at the cursor within range: With the item in hand, LEFT BRACKET. Warning: Throwing grenades will hurt you unless the cursor is moved far enough away. -Insert 1 stack of the item in hand where applicable: CONTROL + LEFT BRACKET +## Guns and Armor Equipment -Insert half a stack of the item in hand where applicable: CONTROL + RIGHT BRACKET +Swap gun in hand: TAB + +Fire at the cursor: C. Warning: Friendly fire is allowed. + +Fire at enemies with aiming assistance: SPACEBAR. This only when an enemy is within range, and only for pistols / submachine guns / rocket launchers with regular rockets. + +Warning: There is not yet any audio guidance added about enemy presence. + +The rest of the controls in this section require you to have the inventory screen opened (but no buildings). + +Equip a gun or ammo stack: LEFT BRACKET to take it in hand and SHIFT + LEFT BRACKET to equip it. + +Read currently equipped guns (up to 3) and ammo: R + +Reload all ammo slots from the inventory: SHIFT + R + +Return all guns and ammo to inventory: CONTROL + SHIFT + R + +Equip an armor suit or armor equipment module: LEFT BRACKET to take it in hand and SHIFT + LEFT BRACKET to equip it. + +Read armor type and equipment stats: G + +Read armor equipment list: SHIFT + G + +Return all equipment and armor to inventory: CONTROL + SHIFT + G ## Scanner Tool @@ -213,18 +271,6 @@ Teleport cursor to Building with warning: LEFT BRACKET Close Warnings menu: E -## Others - -Time of day and current research: T - -Save game: F1 - -Set quickbar #: CONTROL + Any number - -Pick from quickbar: Any number - -Recalibrate: CONTROL + END - ## While in a menu Change tabs within a menu: TAB and SHIFT + TAB @@ -251,7 +297,7 @@ Modify chest inventory slot limits: PAGE UP or PAGE DOWN. Note: You can hold SHIFT to modify limits by increments of 5 instead of 1 and you can hold CONTROL to set the limit to maximum or zero. -### Crafting +## Crafting Menu Navigate recipe groups: W S @@ -267,7 +313,7 @@ Craft 5 items: RIGHT BRACKET Craft as many items as possible: SHIFT + LEFT BRACKET -### Crafting Queue +## Crafting Queue Menu Navigate queue: W A S D @@ -277,7 +323,7 @@ Unqueue 5 items: RIGHT BRACKET Unqueue all items: SHIFT + LEFT BRACKET -## In item selector (alternative) +## In item selector submenu (alternative) Select category: LEFT BRACKET or S @@ -285,33 +331,132 @@ Jump to previous category level: W Select category from currently selected tier: A and D +## Splitter Interactions +Set input priority side: SHIFT + LEFT ARROW, or SHIFT + RIGHT ARROW. Press the same side again to reset to equal priority. + +Set output priority side: CONTROL + LEFT ARROW, or CONTROL + RIGHT ARROW. Press the same side again to reset to equal priority. + +Set an item filter: With the item in hand, CONTROL + LEFT BRACKET + +Set item filter output side: CONTROL + LEFT ARROW, or CONTROL + RIGHT ARROW + +Set an item filter: With the item in hand, CONTROL + LEFT BRACKET + +Clear the item filter: With an empty hand, CONTROL + LEFT BRACKET + +Copy and paste splitter settings: SHIFT + RIGHT BRACKET and then SHIFT + LEFT BRACKET -# FAQ: +## Rail Building and Analyzing -Q: Does this mod work with the steam version? +Rail unrestricted placement: Press CONTROL + LEFT BRACKET with rails in hand to place down a single straight rail. -A: Not yet, however if you buy the game on Steam you can use your product key to redeem the standalone version on factorio.com +Rail appending: Press LEFT BRACKET with rails in hand to automatically extend the nearest end rail by one unit. Also accepts RIGHT BRACKET. -Q: Does this mod work with the demo? +Rail structure building menu: Press SHIFT + LEFT BRACKET on any rail, but end rails have the most options. Structures include turns, train stops, etc. -A: No, in fact no mods work with the demo. +Rail analyzer UP: Press J with empty hand on any rail to check which rail structure is UP along the selected rail. Note: This cannot detect trains! -Q: Can this mod run the tutorial? +Rail analyzer DOWN: Press SHIFT + J with empty hand on any rail to check which rail structure is DOWN along the selected rail. Note: This cannot detect trains! -A: Not yet. There are plans to create a custom tutorial, and to make the built in tutorial accessible, but these things are still weeks away. In the meantime, there is some amount of in-game help via item descriptions, while the wiki and this page are the main information sources. +Station rail analyzer: Select a rail behind a train stop to hear corresponding the station space. Note: Every rail vehicle is 6 tiles long and there is one tile of extra space between each vehicle on a train. -Q: How much of the game is accessible right now? -A: All basic interactions with buildings and items are supported. With the recently added support for fluid handling, you can now progress until the late game, which takes dozens of hours. Some iconic optional features such as trains, combat, and multiplayer, are still being worked on. Some unique features have been added to increase accessibility. More about this can be found on the wiki. +Note 1: When building parallel rail segments, it is recommended to have at least 4 tiles of space between them in order to leave space for infrastructure such as rail signals, connecting rails, or crossing buildings. -Q: My game crashed, what gives? +Note 2: In case of bugs, be sure to save regularly. There is a known bug related to extending rails after building a train stop on an end rail. -A: This mod is currently still in early access. Bugs are normal and expected. Please post about it in the issues channel of Discord. +Shortcut for building rail right turn 45 degrees: CONTROL + RIGHT ARROW on an end rail. + +Shortcut for building rail left turn 45 degrees: CONTROL + LEFT ARROW on an end rail. + +## Train Building and Examining + +Place rail vehicles: LEFT BRACKET on an empty rail with the vehicle in hand and facing a rail. Locomotives snap into place at train stops. Nearby vehicles connect automatically to each other upon placing. + +Manually connect rail vehicles: G near vehicles + +Manually disconnect rail vehicles: SHIFT + G near vehicles + +Flip direction of a rail vehicle: SHIFT + R on the vehicle, but it must be fully disconnected first. + +Open train menu: LEFT BRACKET on the train's locomotives + +Train vehicle quick info: L + +Examine locomotive fuel tank contents: RIGHT BRACKET. + +Examine cargo wagon or fluid wagon contents: RIGHT BRACKET. Note that items can for now be added or removed only via cursor shortcuts or inserters. + +Add fuel to a locomotive: With fuel items in hand, CONTROL + LEFT BRACKET on the locomotive + + +## Driving Ground Vehicles or Locomotives + +Read fuel inventory: RIGHT BRACKET + +Insert fuel: With the fuel stack in hand: CONTROL + LEFT BRACKET to insert all, or CONTROL + RIGHT BRACKET to insert half. + +Insert ammo for any vehicle weapons: With the appropriate ammo stack in hand: CONTROL + LEFT BRACKET to insert all, or CONTROL + RIGHT BRACKET to insert half. + +Enter or exit: ENTER + +The following controls are for when driving: + +Accelerate forward (or break): Hold W + +Accelerate backward (or break): Hold S + +Steer left or right: A or D. Not needed to make trains go around turns. + +Get heading and speed and coordinates: K + +Get some vehicle info: L + +Fire selected vehicle weapon: SPACEBAR + +For trains, analyze the first rail structure ahead: J + +For trains, analyze the first rail structure behind: SHIFT + J + +For trains, read precise distance to a nearby train stop for manual alignment: J + +For trains, open the train menu: LEFT BRACKET. Navigate with ARROW KEYS. + +## Using the Screen Reader + +The screen reader, such as for NVDA, can be used but it is generally not that helpful during gameplay because in-game menus heavily use visual icons and graphs instead of text. We are designing the mod to require the screen reader as little as possible. However, the screen reader is necessary in the following situtaions: When the game crashes, when your character dies, when you win a game, and optionally when you pause the game. + +# FAQ: + +Q: How much of the game is accessible right now? +A: All basic interactions with buildings and items are supported. As of Version 0.4, you can complete the main objectives of the game but not every main feature is fully supported. There is currently partial support for combat and trains, and no support for multiplayer and flying robots and circuit networks. We are gradually working on it and some features are a few bugfixes away. Some unique features have been added to increase accessibility. More about this can be found on the wiki. + +Q: What is the development timeline? +A: Development is slow overall due to the time constraints of our volunteers but we aim to have at least partial support for all main features before the middle of 2024. Q: Do I have to pay to use the mod? +A: The mod is and always will be free. The game itself costs $35 on [Factorio.com](www.factorio.com) and prices can vary per country on Steam. + +Q: Does this mod work with the steam version? +A: Not yet, however if you buy the game on Steam or another seller, you can use your product key to redeem the standalone version on factorio.com + +Q: Does this mod work with the demo? +A: No, in fact no mods work with the demo. + +Q: Where can I find information about the game? +A: There is in-game help via item descriptions, while this page, our own wiki, and the official wiki can provide information and guidance. + +Q: Can this mod run the game's tutorials? +A: No, but there is in-game help via item descriptions, while this page, our own wiki, and the official wiki can provide info and guidance. + +Q: My game crashed, what gives? +A: This mod is currently still in early access. Bugs are normal and expected. Please post about it in the issues channel of Discord. Autosave should generally restore your progress. -A: The mod is and always will be free. The game itself costs $30 on [Factorio.com](www.factorio.com) and prices can vary per country on Steam. +Q: Who is working on the mod? +A: We are a small group of volunteers who are not officially affiliated with the developers of Factorio. +Q: Can I contribute to this project? +A: Generally, playtesting and feedback from players is always welcome. You can also make suggestions for or assist in writing our wiki pages. Coding assitance from experienced modders would be appreciated. You can talk to us on Discord to discuss details. # Wiki @@ -338,6 +483,6 @@ An updated changelog can be found [here](https://github.com/Crimso777/Factorio-A # Donations -While this mod is completely free for all, I am a full time student working on this mod in my free time, thus any and all support is greatly appreciated. +While this mod is completely free for all, our main developer is a full time student working on this mod in their free time, thus any and all support is greatly appreciated. If you are so inclined, you can donate at my [Patreon](https://www.patreon.com/Crimso777). diff --git a/config/config.ini b/config/config.ini index 309b30ff..10745815 100644 --- a/config/config.ini +++ b/config/config.ini @@ -754,7 +754,7 @@ logistic-networks= ; connect-train-alternative= -; disconnect-train=V +disconnect-train= ; disconnect-train-alternative= @@ -1513,7 +1513,7 @@ music-volume=0.100000 ; 'low' and 'very-low' options are deprecated and will be migrated to 'normal' ; ; Options: high, normal, low, very-low -; graphics-quality=high +graphics-quality=normal ; brightness=0 diff --git a/mods/FactorioAccess/control.lua b/mods/FactorioAccess/control.lua index bf77e764..530cb554 100644 --- a/mods/FactorioAccess/control.lua +++ b/mods/FactorioAccess/control.lua @@ -1,4 +1,5 @@ require('zoom') +require('rails-and-trains') groups = {} entity_types = {} @@ -494,13 +495,21 @@ function ent_info(pindex, ent, description) if game.players[pindex].name == "Crimso" then result = result .. " " .. ent.type .. " " end + if game.get_player(pindex).driving then + return + end if ent.type == "resource" then result = result .. ", x " .. ent.amount end + if ent.name == "entity-ghost" then + result = result .. " for a " .. ent.ghost_name .. ", " + elseif ent.name == "straight-rail" or ent.name == "curved-rail" then + return rail_ent_info(pindex, ent, description) + end result = result .. (description or "") - --Explain the contents of a container/pipe/belt without pause and before the direction + --Explain the contents of a container if ent.type == "container" or ent.type == "logistic-container" then --Chests etc: Report the most common item and say "and other items" if there are other types. local itemset = ent.get_inventory(defines.inventory.chest).get_contents() local itemtable = {} @@ -513,15 +522,18 @@ function ent_info(pindex, ent, description) if #itemtable == 0 then result = result .. " containing nothing " else - result = result .. " containing " .. itemtable[1].name .. " times " .. itemtable[1].count .. " " + result = result .. " containing " .. itemtable[1].name .. " times " .. itemtable[1].count .. ", " if #itemtable > 1 then + result = result .. " and " .. itemtable[2].name .. " times " .. itemtable[2].count .. ", " + end + if #itemtable > 2 then result = result .. "and other items " end end end - - if ent.type == "pipe" or ent.type == "pipe-to-ground" or ent.type == "storage-tank" or ent.type == "pump" then + --Explain the contents of a pipe or storage tank or etc. + if ent.type == "pipe" or ent.type == "pipe-to-ground" or ent.type == "storage-tank" or ent.type == "pump" or ent.name == "boiler" or ent.name == "heat-exchanger" then local dict = ent.get_fluid_contents() local fluids = {} for name, count in pairs(dict) do @@ -532,11 +544,17 @@ function ent_info(pindex, ent, description) end) if #fluids > 0 then result = result .. " containing " .. fluids[1].name .. " " + if #fluids > 1 then + result = result .. " and " .. fluids[2].name .. ", " + end + if #fluids > 2 then + result = result .. ", and other fluids " + end else result = result .. " containing no fluid " end end - + --Explain the type and content of a transport belt if ent.type == "transport-belt" then --Check if corner or junction or end local sideload_count = 0 @@ -544,6 +562,8 @@ function ent_info(pindex, ent, description) local outload_count = 0 local inputs = ent.belt_neighbours["inputs"] local outputs = ent.belt_neighbours["outputs"] + local outload_dir = nil + local this_dir = ent.direction for i, belt in pairs(inputs) do if ent.direction ~= belt.direction then sideload_count = sideload_count + 1 @@ -553,22 +573,10 @@ function ent_info(pindex, ent, description) end for i, belt in pairs(outputs) do outload_count = outload_count + 1 + outload_dir = belt.direction--Note: there should be only one of these belts anyway. end - if sideload_count == 0 and backload_count == 1 and outload_count == 1 then - result = result --middle (no need to specify) - elseif sideload_count == 0 and backload_count == 0 and outload_count == 0 then - result = result .. " unit " - elseif sideload_count == 0 and backload_count == 0 and outload_count == 1 then - result = result .. " start " - elseif sideload_count == 0 and backload_count == 1 and outload_count == 0 then - result = result .. " end " - elseif sideload_count == 1 and backload_count == 0 and outload_count == 0 then - result = result .. " end corner " - elseif sideload_count == 1 and backload_count == 0 and outload_count == 1 then - result = result .. " corner " - elseif sideload_count + backload_count > 1 then - result = result .. " junction " --maybe different junction types will be worth specifying in the future - end + --Check what the neighbor info reveals about the belt + result = result .. transport_belt_junction_info(sideload_count, backload_count, outload_count, this_dir, outload_dir) --Check contents local left = ent.get_transport_line(1).get_contents() @@ -598,49 +606,100 @@ function ent_info(pindex, ent, description) end else - result = result .. " carrying Nothing" + result = result .. " carrying nothing" end end + --For underground belts, note whether entrance or Exited + if ent.type == "underground-belt" then + if ent.belt_to_ground_type == "input" then + result = result .. " entrance " + elseif ent.belt_to_ground_type == "output" then + result = result .. " exit " + end + end + --Explain the recipe of a machine without pause and before the direction pcall(function() if ent.get_recipe() ~= nil then result = result .. " producing " .. ent.get_recipe().name end end) + + --State the name of a train stop + if ent.name == "train-stop" then + result = result .. " " .. ent.backer_name .. " " + --State the ID number of a train + elseif ent.name == "locomotive" or ent.name == "cargo-wagon" or ent.name == "fluid-wagon" then + result = result .. " of train " .. get_train_name(ent.train) + end --Explain the entity facing direction if ent.prototype.is_building and ent.supports_direction then result = result .. ", Facing " if ent.direction == 0 then result = result .. "North " + elseif ent.direction == 1 then + result = result .. "Northeast " + elseif ent.direction == 2 then + result = result .. "East " + elseif ent.direction == 3 then + result = result .. "Southeast " elseif ent.direction == 4 then result = result .. "South " + elseif ent.direction == 5 then + result = result .. "Southwest " elseif ent.direction == 6 then result = result .. "West " - elseif ent.direction == 2 then - result = result .. "East " + elseif ent.direction == 7 then + result = result .. "Northwest " end + elseif ent.name == "locomotive" or ent.prototype.type == "car" then + result = result .. " facing " .. get_heading(ent) end if ent.prototype.type == "generator" then result = result .. ", " local power1 = ent.energy_generated_last_tick * 60 local power2 = ent.prototype.max_energy_production * 60 if power2 ~= nil then - result = result .. "Producing " .. get_power_string(power1) .. " / " .. get_power_string(power2) .. " " + result = result .. "Producing " .. get_power_string(power1) .. " out of " .. get_power_string(power2) .. " capacity, " else result = result .. "Producing " .. get_power_string(power1) .. " " end end - if ent.prototype.type == "underground-belt" and ent.neighbours ~= nil then - result = result .. ", Connected to " ..distance(ent.position, ent.neighbours.position) .. " " .. direction(ent.position, ent.neighbours.position) - elseif (ent.prototype.type == "pipe" or ent.prototype.type == "pipe-to-ground") and ent.neighbours ~= nil then + if ent.type == "underground-belt" then + if ent.neighbours ~= nil then + result = result .. ", Connected to " .. direction(ent.position, ent.neighbours.position) .. " via " .. math.floor(distance(ent.position, ent.neighbours.position)) - 1 .. " tiles underground, " + else + result = result .. ", not connected " + end + elseif (ent.name == "pipe") and ent.neighbours ~= nil then result = result .. ", connected to " for i, v in pairs(ent.neighbours) do for i1, v1 in pairs(v) do - result = result .. ", " .. distance(ent.position, v1.position) .. " " .. direction(ent.position, v1.position) + result = result .. ", " .. math.floor(distance(ent.position, v1.position)) .. " " .. direction(ent.position, v1.position) + end + end + elseif (ent.name == "pipe-to-ground") and ent.neighbours ~= nil then + result = result .. ", connected to " + local connections = ent.fluidbox.get_pipe_connections(1) + local at_least_one = false + for i,con in ipairs(connections) do + if con.target ~= nil then + local dist = math.ceil(util.distance(ent.position,con.target.get_pipe_connections(1)[1].position)) + result = result .. direction_lookup(get_direction_of_that_from_this(con.target_position,ent.position)) .. " " + if con.connection_type == "underground" then + result = result .. " via " .. dist - 1 .. " tiles underground, " + else + result = result .. " by " .. dist .. " tiles, " + end + result = result .. ", " + at_least_one = true end end + if not at_least_one then + result = result .. " nothing " + end elseif next(ent.prototype.fluidbox_prototypes) ~= nil then local relative_position = {x = players[pindex].cursor_pos.x - ent.position.x, y = players[pindex].cursor_pos.y - ent.position.y} local direction = ent.direction/2 @@ -669,11 +728,11 @@ function ent_info(pindex, ent, description) if ent.type == "assembling-machine" and ent.get_recipe() ~= nil then if ent.name == "oil-refinery" and ent.get_recipe().name == "basic-oil-processing" then if i == 2 then - result = result .. ", crude-oil Flow" .. pipe.type .. " 1 " .. adjusted.direction .. " " + result = result .. ", crude-oil Flow " .. pipe.type .. " 1 " .. adjusted.direction .. ", at " .. get_entity_part_at_cursor(pindex) elseif i == 5 then - result = result .. ", petroleum-gas Flow" .. pipe.type .. " 1 " .. adjusted.direction .. " " + result = result .. ", petroleum-gas Flow " .. pipe.type .. " 1 " .. adjusted.direction .. ", at " .. get_entity_part_at_cursor(pindex) else - result = result .. ", " .. "Unused" .. "Flow" .. pipe.type .. " 1 " .. adjusted.direction .. " " + result = result .. ", " .. "Unused" .. " Flow " .. pipe.type .. " 1 " .. adjusted.direction .. ", at " .. get_entity_part_at_cursor(pindex) end else if pipe.type == "input" then @@ -689,9 +748,9 @@ function ent_info(pindex, ent, description) i3 = #inputs end local filter = inputs[i3] - result = result .. ", " .. filter.name .. "Flow" .. pipe.type .. " 1 " .. adjusted.direction .. " " + result = result .. ", " .. filter.name .. " Flow " .. pipe.type .. " 1 " .. adjusted.direction .. ", at " .. get_entity_part_at_cursor(pindex) else - result = result .. ", " .. "Unused" .. "Flow" .. pipe.type .. " 1 " .. adjusted.direction .. " " + result = result .. ", " .. "Unused" .. " Flow " .. pipe.type .. " 1 " .. adjusted.direction .. ", at " .. get_entity_part_at_cursor(pindex) end else local outputs = ent.get_recipe().products @@ -706,9 +765,9 @@ function ent_info(pindex, ent, description) i3 = #outputs end local filter = outputs[i3] - result = result .. ", " .. filter.name .. "Flow" .. pipe.type .. " 1 " .. adjusted.direction .. " " + result = result .. ", " .. filter.name .. " Flow " .. pipe.type .. " 1 " .. adjusted.direction .. ", at " .. get_entity_part_at_cursor(pindex) else - result = result .. ", " .. "Unused" .. "Flow" .. pipe.type .. " 1 " .. adjusted.direction .. " " + result = result .. ", " .. "Unused" .. " Flow " .. pipe.type .. " 1 " .. adjusted.direction .. ", at " .. get_entity_part_at_cursor(pindex) end end @@ -716,33 +775,72 @@ function ent_info(pindex, ent, description) else local filter = box.filter or {name = ""} - result = result .. ", " .. filter.name .. "Flow" .. pipe.type .. " 1 " .. adjusted.direction .. " " + result = result .. ", " .. filter.name .. " Flow " .. pipe.type .. " 1 " .. adjusted.direction .. ", at " .. get_entity_part_at_cursor(pindex) end end end end end - - if ent.type == "electric-pole" then - result = result .. ", Connected to " .. #ent.neighbours.copper .. "buildings, Network currently producing " - local power = 0 - local capacity = 0 - for i, v in pairs(ent.electric_network_statistics.output_counts) do - power = power + (ent.electric_network_statistics.get_flow_count{name = i, input = false, precision_index = defines.flow_precision_index.five_seconds}) - local cap_add = 0 - for _, power_ent in pairs(ent.surface.find_entities_filtered{name=i,force = ent.force}) do - if power_ent.electric_network_id == ent.electric_network_id then - cap_add = cap_add + 1 - end + if ent.name == "cargo-wagon" then + --Explain contents + local itemset = ent.get_inventory(defines.inventory.cargo_wagon).get_contents() + local itemtable = {} + for name, count in pairs(itemset) do + table.insert(itemtable, {name = name, count = count}) + end + table.sort(itemtable, function(k1, k2) + return k1.count > k2.count + end) + if #itemtable == 0 then + result = result .. " containing nothing " + else + result = result .. " containing " .. itemtable[1].name .. " times " .. itemtable[1].count .. ", " + if #itemtable > 1 then + result = result .. " and " .. itemtable[2].name .. " times " .. itemtable[2].count .. ", " + end + if #itemtable > 2 then + result = result .. "and other items " end - cap_add = cap_add * game.entity_prototypes[i].max_energy_production - if game.entity_prototypes[i].type == "solar-panel" then - cap_add = cap_add * ent.surface.solar_power_multiplier * (1-ent.surface.darkness) + end + elseif ent.type == "electric-pole" then + --List connected electric poles + if #ent.neighbours.copper == 0 then + result = result .. " with no connections, " + else + result = result .. " connected to " + for i,pole in ipairs(ent.neighbours.copper) do + local dir = get_direction_of_that_from_this(pole.position,ent.position) + local dist = util.distance(pole.position,ent.position) + if i > 1 then + result = result .. " and " + end + result = result .. math.ceil(dist) .. " tiles " .. direction_lookup(dir) .. ", " end - capacity = capacity + cap_add end - result = result .. get_power_string(power*60) .. " / " .. get_power_string(capacity*60) .. " " + --Count number of entities being supplied within supply area. + local pos = ent.position + local sdist = ent.prototype.supply_area_distance + local supply_area = {{pos.x - sdist, pos.y - sdist}, {pos.x + sdist, pos.y + sdist}} + local supplied_ents = ent.surface.find_entities_filtered{area = supply_area} + local supplied_count = 0 + local producer_count = 0 + for i, ent2 in ipairs(supplied_ents) do + if ent2.prototype.max_energy_usage ~= nil and ent2.prototype.max_energy_usage > 0 then + supplied_count = supplied_count + 1 + elseif ent2.prototype.max_energy_production ~= nil and ent2.prototype.max_energy_production > 0 then + producer_count = producer_count + 1 + end + end + result = result .. " supplying " .. supplied_count .. " buildings, " + if producer_count > 0 then + result = result .. " drawing from " .. producer_count .. " buildings, " + end + result = result .. "Check status for power flow information. " + + elseif ent.name == "rail-signal" or ent.name == "rail-chain-signal" then + result = result .. ", " .. get_signal_state_info(ent) end + --Give drop position (like for inserters) if ent.drop_position ~= nil then local position = table.deepcopy(ent.drop_position) local direction = ent.direction /2 @@ -798,9 +896,7 @@ function ent_info(pindex, ent, description) end if ent.prototype.burner_prototype ~= nil then - if ent.energy == 0 then --- local inv = ent.get_fuel_inventory() --- if inv.is_empty() then + if ent.energy == 0 and fuel_inventory_info(ent) == "Contains no fuel." then result = result .. ", Out of Fuel " end end @@ -814,8 +910,7 @@ function ent_info(pindex, ent, description) local level = math.ceil(ent.energy / 50000) --In percentage local charge = math.ceil(ent.energy / 1000) --In kilojoules result = result .. ", " .. level .. " percent full, containing " .. charge .. " kilojoules. " - end - if ent.type == "solar-panel" then + elseif ent.type == "solar-panel" then local s_time = ent.surface.daytime*24 --We observed 18 = peak solar start, 6 = peak solar end, 11 = night start, 13 = night end local solar_status = "" if s_time > 13 and s_time <= 18 then @@ -828,9 +923,97 @@ function ent_info(pindex, ent, description) solar_status = ", zero production, night time. " end result = result .. solar_status + elseif ent.name == "rocket-silo" then + if ent.rocket_parts ~= nil and ent.rocket_parts < 100 then + result = result .. ", " .. ent.rocket_parts .. " finished out of 100. " + elseif ent.rocket_parts ~= nil then + result = result .. ", rocket ready, press SPACE to launch. " + end + elseif ent.name == "beacon" then + local modules = ent.get_module_inventory() + if modules.get_item_count() == 0 then + result = result .. " with no modules " + elseif modules.get_item_count() == 1 then + result = result .. " with " .. modules[1].name + elseif modules.get_item_count() == 2 then + result = result .. " with " .. modules[1].name .. " and " .. modules[2].name + elseif modules.get_item_count() > 2 then + result = result .. " with " .. modules[1].name .. " and " .. modules[2].name .. " and other modules " + end + elseif ent.name == "nuclear-reactor" or ent.name == "heat-pipe" or ent.name == "heat-exchanger" then + result = result .. ", temperature " .. math.floor(ent.temperature) .. " degrees C " + if ent.name == "nuclear-reactor" then + if ent.temperature > 900 then + result = result .. ", danger " + end + if ent.energy > 0 then + result = result .. ", consuming fuel cell " + end + result = result .. ", neighbour bonus " .. ent.neighbour_bonus * 100 .. " percent " + end + elseif ent.name == "item-on-ground" then + result = result .. ", " .. ent.stack.name + end + return result +end + +--Explain whether the belt is some type of corner or sideloading junction or etc. +function transport_belt_junction_info(sideload_count, backload_count, outload_count, this_dir, outload_dir, say_middle) + local say_middle = say_middle or false + local result = "" + if sideload_count == 0 and backload_count == 0 and outload_count == 0 then + result = result .. " unit " + elseif sideload_count == 0 and backload_count == 1 and outload_count == 0 then + result = result .. " stopping end " + elseif sideload_count == 1 and backload_count == 0 and outload_count == 0 then + result = result .. " stopping end corner " + elseif sideload_count == 1 and backload_count == 1 and outload_count == 0 then + result = result .. " sideloading stopping end " + elseif sideload_count == 2 and backload_count == 1 and outload_count == 0 then + result = result .. " double sideloading stopping end " + elseif sideload_count == 2 and backload_count == 0 and outload_count == 0 then + result = result .. " safe merging stopping end " + elseif sideload_count == 0 and backload_count == 0 and outload_count == 1 and this_dir == outload_dir then + result = result .. " start " + elseif sideload_count == 0 and backload_count == 1 and outload_count == 1 and this_dir == outload_dir then + if say_middle then + result = result .. " middle " + else + result = result .. " " + end + elseif sideload_count == 1 and backload_count == 0 and outload_count == 1 and this_dir == outload_dir then + result = result .. " corner " + elseif sideload_count == 1 and backload_count == 1 and outload_count == 1 and this_dir == outload_dir then + result = result .. " sideloading junction " + elseif sideload_count == 2 and backload_count == 1 and outload_count == 1 and this_dir == outload_dir then + result = result .. " double sideloading junction " + elseif sideload_count == 2 and backload_count == 0 and outload_count == 1 and this_dir == outload_dir then + result = result .. " safe merging junction " + elseif sideload_count == 0 and backload_count == 0 and outload_count == 1 and this_dir ~= outload_dir then + result = result .. " unit pouring end " + elseif sideload_count == 0 and backload_count == 1 and outload_count == 1 and this_dir ~= outload_dir then + result = result .. " pouring end " + elseif sideload_count == 1 and backload_count == 0 and outload_count == 1 and this_dir ~= outload_dir then + result = result .. " corner pouring end " + elseif sideload_count == 1 and backload_count == 1 and outload_count == 1 and this_dir ~= outload_dir then + result = result .. " sideloading pouring end " + elseif sideload_count == 2 and backload_count == 1 and outload_count == 1 and this_dir ~= outload_dir then + result = result .. " double sideloading pouring end " + elseif sideload_count == 2 and backload_count == 0 and outload_count == 1 and this_dir ~= outload_dir then + result = result .. " safe merging pouring end " + elseif sideload_count + backload_count > 1 and (outload_count == 0 or (outload_count == 1 and this_dir == outload_dir)) then + result = result .. " unidentified junction "--this should not be reachable any more + elseif sideload_count + backload_count > 1 and outload_count == 1 and this_dir ~= outload_dir then + result = result .. " unidentified pouring end " + elseif outload_count > 1 then + result = result .. " multiple outputs " --unexpected case + else + result = result .. " unknown state " --unexpected case end return result end +--Notes for the wiki: A pouring end either pours into a sideloading something, or into a corner. Lanes are preserved if corner. + function compile_building_network (ent, radius) local ents = ent.surface.find_entities_filtered{position = ent.position, radius = radius, type = building_types} local adj = {hor = {}, vert = {}} @@ -974,7 +1157,8 @@ function read_travel_slot(pindex) printout(entry.name .. " at " .. math.floor(entry.position.x) .. ", " .. math.floor(entry.position.y), pindex) end end -function teleport_to_closest(pindex, pos) +function teleport_to_closest(pindex, pos, muted) + local muted = muted or false local first_player = game.get_player(pindex) local surf = first_player.surface local radius = .5 @@ -983,16 +1167,25 @@ function teleport_to_closest(pindex, pos) radius = radius + 1 new_pos = surf.find_non_colliding_position("character", pos, radius, .1, true) end - + + if game.get_player(pindex).driving then + printout("Cannot teleport while in a vehicle.", pindex) + return + end + local can_port = first_player.surface.can_place_entity{name = "character", position = new_pos} if can_port then local teleported = first_player.teleport(new_pos) if teleported then players[pindex].position = table.deepcopy(new_pos) if new_pos.x ~= pos.x or new_pos.y ~= pos.y then - printout("Teleported " .. math.ceil(distance(pos,new_pos)) .. " " .. direction(pos, new_pos) .. " of target", pindex) + if not muted then + printout("Teleported " .. math.ceil(distance(pos,new_pos)) .. " " .. direction(pos, new_pos) .. " of target", pindex) + end else - printout("Teleported to target", pindex) + if not muted then + printout("Teleported to target", pindex) + end end else @@ -1833,18 +2026,18 @@ function get_power_string(power) result = "" if power > 1000000000000 then power = power/1000000000000 - result = result .. string.format(" %.3f Terawatts", power) + result = result .. string.format(" %.1f Terawatts", power) elseif power > 1000000000 then power = power / 1000000000 - result = result .. string.format(" %.3f Gigawatts", power) + result = result .. string.format(" %.1f Gigawatts", power) elseif power > 1000000 then power = power / 1000000 - result = result .. string.format(" %.3f Megawatts", power) + result = result .. string.format(" %.1f Megawatts", power) elseif power > 1000 then power = power / 1000 - result = result .. string.format(" %.3f Kilowatts", power) + result = result .. string.format(" %.1f Kilowatts", power) else - result = result .. string.format(" %.3f Watts", power) + result = result .. string.format(" %.1f Watts", power) end return result end @@ -2046,7 +2239,7 @@ function read_building_slot(pindex, start_phrase) if fluid ~= nil then amount = fluid.amount name = fluid.name - end + end --laterdo use fluidbox.get_locked_fluid(i) if needed. --Read the fluid ingredients & products --Note: We could have separated by input/output but right now the "type" is "input" for all fluids it seeems? @@ -2304,41 +2497,7 @@ function tile_cycle(pindex) if players[pindex].tile.ents[players[pindex].tile.index - 1].valid then result = "" local ent = players[pindex].tile.ents[players[pindex].tile.index - 1] - result = ent.name - result = result .. " " .. ent.type .. " " - if ent.type == "resource" then - result = result .. " x " .. ent.amount - end - if ent.prototype.is_building and ent.supports_direction then - result = result .. "Facing " - if ent.direction == 0 then - result = result .. "North " - elseif ent.direction == 4 then - result = result .. "South " - elseif ent.direction == 6 then - result = result .. "West " - elseif ent.direction == 2 then - result = result .. "East " - end - end - if ent.prototype.type == "generator" then - local power1 = ent.energy_generated_last_tick * 60 - local power2 = ent.prototype.max_energy_production * 60 - if power2 ~= nil then - result = result .. "Producing " .. get_power_string(power1) .. " / " .. get_power_string(power2) .. " " - else - result = result .. "Producing " .. get_power_string(power1) .. " " - end - end - if ent.prototype.type == "underground-belt" and ent.neighbours ~= nil then - result = result .. distance(ent.position, ent.neighbours.position) .. " " .. direction(ent.position, ent.neighbours.position) - elseif (ent.prototype.type == "pipe" or ent.prototype.type == "pipe-to-ground") and ent.neighbours ~= nil then - for i, v in pairs(ent.neighbours) do - for i1, v1 in pairs(v) do - result = result .. distance(ent.position, v1.position) .. " " .. direction(ent.position, v1.position) - end - end - end + result = ent_info(pindex, ent, "") printout(result, pindex) @@ -2366,6 +2525,9 @@ function printout(str, pindex) players[pindex].last = str end localised_print{"","out ",str} + if str ~= "" and pindex > 0 and (game.players[pindex].name == "SirFendi") then + --game.get_player(pindex).print(str)--**Print all to in game console + end end function repeat_last_spoken (pindex) @@ -2668,8 +2830,9 @@ function toggle_cursor(pindex) end function teleport_to_cursor(pindex) -teleport_to_closest(pindex, players[pindex].cursor_pos) + teleport_to_closest(pindex, players[pindex].cursor_pos) end + function jump_to_player(pindex) local first_player = game.get_player(pindex) players[pindex].cursor_pos.x = math.floor(first_player.position.x)+.5 @@ -2693,78 +2856,403 @@ function read_tile(pindex) players[pindex].tile.previous = nil result = players[pindex].tile.tile - else + else--laterdo tackle the issue here where entities such as tree stumps block preview info local ent = players[pindex].tile.ents[1] result = ent_info(pindex, ent) + --game.get_player(pindex).print(result)--*** players[pindex].tile.previous = players[pindex].tile.ents[#players[pindex].tile.ents] players[pindex].tile.index = 2 end if next(players[pindex].tile.ents) == nil or players[pindex].tile.ents[1].type == "resource" then local stack = game.get_player(pindex).cursor_stack - if stack.valid_for_read and stack.valid and stack.prototype.place_result ~= nil and stack.prototype.place_result.type == "electric-pole" then - local ent = stack.prototype.place_result - local position = table.deepcopy(players[pindex].cursor_pos) - local dict = game.get_filtered_entity_prototypes{{filter = "type", type = "electric-pole"}} - local poles = {} - for i, v in pairs(dict) do - table.insert(poles, v) + --Run build preview checks + if stack.valid_for_read and stack.valid and stack.prototype.place_result ~= nil then + result = result .. build_preview_checks_info(stack,pindex) + --game.get_player(pindex).print(result)--*** + end + + end + + --If the build lock is on and the player is holding a cut or copy tool, every entity being read gets mined as soon as you read a new tile. + local stack = game.get_player(pindex).cursor_stack + if stack.valid_for_read and stack.name == "cut-paste-tool" then + local ent = players[pindex].tile.ents[1] + local ent_name = "Ent" + if ent ~= nil and ent.valid then + ent_name = ent.name + end + game.get_player(pindex).play_sound{path = "Mine-Building"} + if try_to_mine_with_sound(ent,pindex) then + result = ent_name .. " mined." + end + return + end + + printout(result, pindex) +end + +--Cursor building preview checks. NOTE: Only 1 by 1 entities for now +function build_preview_checks_info(stack, pindex) + if stack == nil or not stack.valid_for_read or not stack.valid then + return "invalid stack" + end + local p = game.get_player(pindex) + local surf = game.get_player(pindex).surface + local pos = table.deepcopy(players[pindex].cursor_pos) + local result = "" + local build_dir = players[pindex].building_direction * 2--laterdo get building directions to match the official defines + local ent_p = stack.prototype.place_result --it is an entity prototype! + if ent_p == nil or not ent_p.valid then + return "invalid entity" + end + + --Notify before all else if surface/player cannot place this entity. **laterdo extend this check by copying over build offset stuff + if ent_p.tile_width <= 1 and ent_p.tile_height <= 1 and not surf.can_place_entity{name = stack.name, position = pos} then + return " cannot place this here " + end + + --For belt types, check if it would form a corner or junction here. Laterdo include underground exits. + if ent_p.type == "transport-belt" then + local ents_north = p.surface.find_entities_filtered{position = {x = pos.x+0 ,y = pos.y-1}, type = "transport-belt"} + local ents_south = p.surface.find_entities_filtered{position = {x = pos.x+0 ,y = pos.y+1}, type = "transport-belt"} + local ents_east = p.surface.find_entities_filtered{position = {x = pos.x+1 ,y = pos.y+0}, type = "transport-belt"} + local ents_west = p.surface.find_entities_filtered{position = {x = pos.x-1 ,y = pos.y+0}, type = "transport-belt"} + + rendering.draw_circle{color = {1, 0.0, 0.5},radius = 0.1,width = 2,target = {x = pos.x+0 ,y = pos.y-1}, surface = p.surface, time_to_live = 30} + rendering.draw_circle{color = {1, 0.0, 0.5},radius = 0.1,width = 2,target = {x = pos.x+0 ,y = pos.y+1}, surface = p.surface, time_to_live = 30} + rendering.draw_circle{color = {1, 0.0, 0.5},radius = 0.1,width = 2,target = {x = pos.x-1 ,y = pos.y-0}, surface = p.surface, time_to_live = 30} + rendering.draw_circle{color = {1, 0.0, 0.5},radius = 0.1,width = 2,target = {x = pos.x+1 ,y = pos.y-0}, surface = p.surface, time_to_live = 30} + + if #ents_north > 0 or #ents_south > 0 or #ents_east > 0 or #ents_west > 0 then + local sideload_count = 0 + local backload_count = 0 + local outload_count = 0 + local this_dir = build_dir + local outload_dir = nil + + --Find the outloading belt and its direction, if any + if this_dir == dirs.north and ents_north[1] ~= nil and ents_north[1].valid then + rendering.draw_circle{color = {0.5, 0.5, 1},radius = 0.3,width = 2,target = ents_north[1].position,surface = ents_north[1].surface,time_to_live = 30} + outload_dir = ents_north[1].direction + outload_count = 1 + elseif this_dir == dirs.east and ents_east[1] ~= nil and ents_east[1].valid then + rendering.draw_circle{color = {0.5, 0.5, 1},radius = 0.3,width = 2,target = ents_east[1].position,surface = ents_east[1].surface,time_to_live = 30} + outload_dir = ents_east[1].direction + outload_count = 1 + elseif this_dir == dirs.south and ents_south[1] ~= nil and ents_south[1].valid then + rendering.draw_circle{color = {0.5, 0.5, 1},radius = 0.3,width = 2,target = ents_south[1].position,surface = ents_south[1].surface,time_to_live = 30} + outload_dir = ents_south[1].direction + outload_count = 1 + elseif this_dir == dirs.west and ents_west[1] ~= nil and ents_west[1].valid then + rendering.draw_circle{color = {0.5, 0.5, 1},radius = 0.3,width = 2,target = ents_west[1].position,surface = ents_west[1].surface,time_to_live = 30} + outload_dir = ents_west[1].direction + outload_count = 1 end - table.sort(poles, function(k1, k2) return k1.max_wire_distance < k2.max_wire_distance end) - local check = false - for i, pole in ipairs(poles) do - names = {} - for i1 = i, #poles, 1 do - table.insert(names, poles[i1].name) + + --Find the backloading and sideloading belts, if any + if ents_north[1] ~= nil and ents_north[1].valid and ents_north[1].direction == dirs.south then + rendering.draw_circle{color = {1, 1, 0.2},radius = 0.2,width = 2,target = ents_north[1].position,surface = ents_north[1].surface,time_to_live = 30} + if this_dir == dirs.east or this_dir == dirs.west then + sideload_count = sideload_count + 1 + elseif this_dir == dirs.south then + backload_count = 1 end - local T = { - position = position, - radius = pole.max_wire_distance, - name = names - } - if #surf.find_entities_filtered(T) > 0 then - check = true - break + end + + if ents_south[1] ~= nil and ents_south[1].valid and ents_south[1].direction == dirs.north then + rendering.draw_circle{color = {1, 1, 0.4},radius = 0.2,width = 2,target = ents_south[1].position,surface = ents_south[1].surface,time_to_live = 30} + if this_dir == dirs.east or this_dir == dirs.west then + sideload_count = sideload_count + 1 + elseif this_dir == dirs.north then + backload_count = 1 end - if stack.name == pole.name then - break + end + + if ents_east[1] ~= nil and ents_east[1].valid and ents_east[1].direction == dirs.west then + rendering.draw_circle{color = {1, 1, 0.6},radius = 0.2,width = 2,target = ents_east[1].position,surface = ents_east[1].surface,time_to_live = 30} + if this_dir == dirs.north or this_dir == dirs.south then + sideload_count = sideload_count + 1 + elseif this_dir == dirs.west then + backload_count = 1 + end + end + + if ents_west[1] ~= nil and ents_west[1].valid and ents_west[1].direction == dirs.east then + rendering.draw_circle{color = {1, 1, 0.8},radius = 0.2,width = 2,target = ents_west[1].position,surface = ents_west[1].surface,time_to_live = 30} + if this_dir == dirs.north or this_dir == dirs.south then + sideload_count = sideload_count + 1 + elseif this_dir == dirs.east then + backload_count = 1 + end + end + + --Determine expected junction info + if sideload_count + backload_count + outload_count > 0 then--Skips "unit" because it is obvious + result = ", forms belt " .. transport_belt_junction_info(sideload_count, backload_count, outload_count, this_dir, outload_dir, true) end end - if check then - result = result .. " " .. "connected" - else - result = result .. "Not Connected" + end + + --For underground belts, state the potential neighbor: any neighborless matching underground of the same name and same/opposite direction, and along the correct axis + if ent_p.type == "underground-belt" then + local connected = false + local check_dist = 5 + if stack.name == "fast-underground-belt" then + check_dist = 7 + elseif stack.name == "express-underground-belt" then + check_dist = 9 + end + local candidates = game.get_player(pindex).surface.find_entities_filtered{ name = stack.name, position = pos, radius = check_dist, direction = rotate_180(build_dir) } + if #candidates > 0 then + for i,cand in ipairs(candidates) do + rendering.draw_circle{color = {1, 1, 0},radius = 0.5,width = 3,target = cand.position,surface = cand.surface,time_to_live = 60} + local dist_x = cand.position.x - pos.x + local dist_y = cand.position.y - pos.y + if cand.direction == rotate_180(build_dir) + and (get_direction_of_that_from_this(cand.position,pos) == build_dir) and (dist_x == 0 or dist_y == 0) then + rendering.draw_circle{color = {0, 1, 0},radius = 1.0,width = 3,target = cand.position,surface = cand.surface,time_to_live = 60} + result = result .. " connects " .. direction_lookup(build_dir) .. " with " .. math.floor(util.distance(cand.position,pos)) - 1 .. " tiles underground, " + connected = true + end + end + end + if not connected then + result = result .. " not connected " + end + end + + --For pipes to ground, state when connected + if stack.name == "pipe-to-ground" then + local connected = false + local check_dist = 10 + local candidates = game.get_player(pindex).surface.find_entities_filtered{ name = stack.name, position = pos, radius = check_dist, direction = rotate_180(build_dir) } + if #candidates > 0 then + for i,cand in ipairs(candidates) do + rendering.draw_circle{color = {1, 1, 0},radius = 0.5,width = 3,target = cand.position,surface = cand.surface,time_to_live = 60} + local dist_x = cand.position.x - pos.x + local dist_y = cand.position.y - pos.y + if cand.direction == rotate_180(build_dir) + and (get_direction_of_that_from_this(pos,cand.position) == build_dir) and (dist_x == 0 or dist_y == 0) then + rendering.draw_circle{color = {0, 1, 0},radius = 1.0,width = 3,target = cand.position,surface = cand.surface,time_to_live = 60} + result = result .. " connects " .. direction_lookup(rotate_180(build_dir)) .. " with " .. math.floor(util.distance(cand.position,pos)) - 1 .. " tiles underground, " + connected = true + end + end + end + if not connected then + result = result .. " not connected underground, " + end + end + + --For pipes, read the fluids in fluidboxes of surrounding entities, if any. Also warn if there are multiple fluids, hence a mixing error. + if stack.name == "pipe" then + rendering.draw_circle{color = {1, 0.0, 0.5},radius = 0.1,width = 2,target = {x = pos.x+0 ,y = pos.y-1}, surface = p.surface, time_to_live = 30} + rendering.draw_circle{color = {1, 0.0, 0.5},radius = 0.1,width = 2,target = {x = pos.x+0 ,y = pos.y+1}, surface = p.surface, time_to_live = 30} + rendering.draw_circle{color = {1, 0.0, 0.5},radius = 0.1,width = 2,target = {x = pos.x-1 ,y = pos.y-0}, surface = p.surface, time_to_live = 30} + rendering.draw_circle{color = {1, 0.0, 0.5},radius = 0.1,width = 2,target = {x = pos.x+1 ,y = pos.y-0}, surface = p.surface, time_to_live = 30} + local ents_north = p.surface.find_entities_filtered{position = {x = pos.x+0, y = pos.y-1} } + local ents_south = p.surface.find_entities_filtered{position = {x = pos.x+0, y = pos.y+1} } + local ents_east = p.surface.find_entities_filtered{position = {x = pos.x+1, y = pos.y+0} } + local ents_west = p.surface.find_entities_filtered{position = {x = pos.x-1, y = pos.y+0} } + local relevant_fluid_north = nil + local relevant_fluid_east = nil + local relevant_fluid_south = nil + local relevant_fluid_west = nil + + if ents_north[1] ~= nil and ents_north[1].valid and ents_north[1].fluidbox ~= nil then + rendering.draw_circle{color = {1, 1, 0},radius = 0.2,width = 2,target = ents_north[1].position, surface = p.surface, time_to_live = 30} + --Run checks to see if we have any fluidboxes that are relevant + for i = 1, #ents_north[1].fluidbox, 1 do + --p.print("box " .. i .. ": " .. ents_north[1].fluidbox[i].name) + for j, con in ipairs(ents_north[1].fluidbox.get_pipe_connections(i)) do + local target_pos = con.target_position + --p.print("new connection at: " .. target_pos.x .. "," .. target_pos.y) + rendering.draw_circle{color = {1, 0, 0},radius = 0.2,width = 2,target = target_pos, surface = p.surface, time_to_live = 30} + if util.distance(target_pos, pos) < 0.3 and not (ents_north[1].name == "pipe-to-ground" and ents_north[1].direction == dirs.north) then + rendering.draw_circle{color = {0, 1, 0},radius = 0.3,width = 2,target = target_pos, surface = p.surface, time_to_live = 30} + if ents_north[1].fluidbox[i] ~= nil then + relevant_fluid_north = ents_north[1].fluidbox[i].name + elseif ents_north[1].fluidbox.get_locked_fluid(i) ~= nil then + relevant_fluid_north = ents_north[1].fluidbox.get_locked_fluid(i) + else + relevant_fluid_north = "empty pipe" + end + end + end + end + end + + if ents_south[1] ~= nil and ents_south[1].valid and ents_south[1].fluidbox ~= nil then + rendering.draw_circle{color = {1, 1, 0},radius = 0.2,width = 2,target = ents_south[1].position, surface = p.surface, time_to_live = 30} + --Run checks to see if we have any fluidboxes that are relevant + for i = 1, #ents_south[1].fluidbox, 1 do + --p.print("box " .. i .. ": " .. ents_south[1].fluidbox[i].name) + for j, con in ipairs(ents_south[1].fluidbox.get_pipe_connections(i)) do + local target_pos = con.target_position + --p.print("new connection at: " .. target_pos.x .. "," .. target_pos.y) + rendering.draw_circle{color = {1, 0, 0},radius = 0.2,width = 2,target = target_pos, surface = p.surface, time_to_live = 30} + if util.distance(target_pos, pos) < 0.3 and not (ents_south[1].name == "pipe-to-ground" and ents_south[1].direction == dirs.south) then + rendering.draw_circle{color = {0, 1, 0},radius = 0.3,width = 2,target = target_pos, surface = p.surface, time_to_live = 30} + if ents_south[1].fluidbox[i] ~= nil then + relevant_fluid_south = ents_south[1].fluidbox[i].name + elseif ents_south[1].fluidbox.get_locked_fluid(i) ~= nil then + relevant_fluid_south = ents_south[1].fluidbox.get_locked_fluid(i) + else + relevant_fluid_south = "empty pipe" + end + end + end + end + end + + if ents_east[1] ~= nil and ents_east[1].valid and ents_east[1].fluidbox ~= nil then + rendering.draw_circle{color = {1, 1, 0},radius = 0.2,width = 2,target = ents_east[1].position, surface = p.surface, time_to_live = 30} + --Run checks to see if we have any fluidboxes that are relevant + for i = 1, #ents_east[1].fluidbox, 1 do + --p.print("box " .. i .. ": " .. ents_east[1].fluidbox[i].name) + for j, con in ipairs(ents_east[1].fluidbox.get_pipe_connections(i)) do + local target_pos = con.target_position + --p.print("new connection at: " .. target_pos.x .. "," .. target_pos.y) + rendering.draw_circle{color = {1, 0, 0},radius = 0.2,width = 2,target = target_pos, surface = p.surface, time_to_live = 30} + if util.distance(target_pos, pos) < 0.3 and not (ents_east[1].name == "pipe-to-ground" and ents_east[1].direction == dirs.east) then + rendering.draw_circle{color = {0, 1, 0},radius = 0.3,width = 2,target = target_pos, surface = p.surface, time_to_live = 30} + if ents_east[1].fluidbox[i] ~= nil then + relevant_fluid_east = ents_east[1].fluidbox[i].name + elseif ents_east[1].fluidbox.get_locked_fluid(i) ~= nil then + relevant_fluid_east = ents_east[1].fluidbox.get_locked_fluid(i) + else + relevant_fluid_east = "empty pipe" + end + end + end + end + end + + if ents_west[1] ~= nil and ents_west[1].valid and ents_west[1].fluidbox ~= nil then + rendering.draw_circle{color = {1, 1, 0},radius = 0.2,width = 2,target = ents_west[1].position, surface = p.surface, time_to_live = 30} + --Run checks to see if we have any fluidboxes that are relevant + for i = 1, #ents_west[1].fluidbox, 1 do + --p.print("box " .. i .. ": " .. ents_west[1].fluidbox[i].name) + for j, con in ipairs(ents_west[1].fluidbox.get_pipe_connections(i)) do + local target_pos = con.target_position + --p.print("new connection at: " .. target_pos.x .. "," .. target_pos.y) + rendering.draw_circle{color = {1, 0, 0},radius = 0.2,width = 2,target = target_pos, surface = p.surface, time_to_live = 30} + if util.distance(target_pos, pos) < 0.3 and not (ents_west[1].name == "pipe-to-ground" and ents_west[1].direction == dirs.west) then + rendering.draw_circle{color = {0, 1, 0},radius = 0.3,width = 2,target = target_pos, surface = p.surface, time_to_live = 30} + if ents_west[1].fluidbox[i] ~= nil then + relevant_fluid_west = ents_west[1].fluidbox[i].name + elseif ents_west[1].fluidbox.get_locked_fluid(i) ~= nil then + relevant_fluid_west = ents_west[1].fluidbox.get_locked_fluid(i) + else + relevant_fluid_west = "empty pipe" + end + end + end + end + end + + + --Assuming empty fluidboxes return nil, we need to check if all none-nil boxes are equal... + if relevant_fluid_north ~= nil or relevant_fluid_east ~= nil or relevant_fluid_south ~= nil or relevant_fluid_west ~= nil then + local count = 0 + result = result .. " pipe connects to " + + if relevant_fluid_north ~= nil then + result = result .. relevant_fluid_north .. " at north, " + count = count + 1 + end + if relevant_fluid_east ~= nil then + result = result .. relevant_fluid_east .. " at east, " + count = count + 1 end - elseif stack.valid_for_read and stack.valid and stack.prototype.place_result ~= nil and stack.prototype.place_result.electric_energy_source_prototype ~= nil then - local ent = stack.prototype.place_result - local position = table.deepcopy(players[pindex].cursor_pos) + if relevant_fluid_south ~= nil then + result = result .. relevant_fluid_south .. " at south, " + count = count + 1 + end + if relevant_fluid_west ~= nil then + result = result .. relevant_fluid_west .. " at west, " + count = count + 1 + end + + if relevant_fluid_north ~= nil and (relevant_fluid_north == relevant_fluid_south or relevant_fluid_north == relevant_fluid_east or relevant_fluid_north == relevant_fluid_west) then + count = count - 1 + end + if relevant_fluid_east ~= nil and (relevant_fluid_east == relevant_fluid_south or relevant_fluid_east == relevant_fluid_west) then + count = count - 1 + end + if relevant_fluid_south ~= nil and (relevant_fluid_south == relevant_fluid_west) then + count = count - 1 + end + + if count > 1 then + result = result .. " warning: there may be mixing fluids " + end + end + + end + + --For electric poles, report the directions of up to 5 wire-connectible electric poles that can connect + if ent_p.type == "electric-pole" then + local pole_dict = surf.find_entities_filtered{type = "electric-pole", position = pos, radius = ent_p.max_wire_distance} + local poles = {} + for i, v in pairs(pole_dict) do + if v.prototype.max_wire_distance ~= nil and v.prototype.max_wire_distance >= ent_p.max_wire_distance then --Select only the poles that can connect back + table.insert(poles, v) + end + end + if #poles > 0 then + --List the first 4 poles within range + result = result .. " connecting " + for i, pole in ipairs(poles) do + if i < 5 then + local dist = math.ceil(util.distance(pole.position,pos)) + local dir = get_direction_of_that_from_this(pole.position,pos) + result = result .. dist .. " tiles " .. direction_lookup(dir) .. ", " + end + end + else + --Notify if no connections and state nearest electric pole + result = result .. " not connected, " + local nearest_pole, min_dist = find_nearest_electric_pole(nil,false,50,surf,pos) + if min_dist == nil or min_dist >= 1000 then + result = result .. " no electric poles within 1000 tiles, " + else + local dir = get_direction_of_that_from_this(nearest_pole.position,pos) + result = result .. math.ceil(min_dist) .. " tiles " .. direction_lookup(dir) .. " to nearest electric pole, " + end + end + end + --For all electric powered entities, note whether powered, and from which direction. Otherwise report the nearest power pole. + if ent_p.electric_energy_source_prototype ~= nil then + local position = pos if players[pindex].cursor then - position.x = position.x + math.ceil(2*ent.selection_box.right_bottom.x)/2 - .5 - position.y = position.y + math.ceil(2*ent.selection_box.right_bottom.y)/2 - .5 + position.x = position.x + math.ceil(2*ent_p.selection_box.right_bottom.x)/2 - .5 + position.y = position.y + math.ceil(2*ent_p.selection_box.right_bottom.y)/2 - .5 elseif players[pindex].player_direction == defines.direction.north then if players[pindex].building_direction == 0 or players[pindex].building_direction == 2 then - position.y = position.y + math.ceil(2* ent.selection_box.left_top.y)/2 + .5 + position.y = position.y + math.ceil(2* ent_p.selection_box.left_top.y)/2 + .5 elseif players[pindex].building_direction == 1 or players[pindex].building_direction == 3 then - position.y = position.y + math.ceil(2* ent.selection_box.left_top.x)/2 + .5 + position.y = position.y + math.ceil(2* ent_p.selection_box.left_top.x)/2 + .5 end elseif players[pindex].player_direction == defines.direction.south then if players[pindex].building_direction == 0 or players[pindex].building_direction == 2 then - position.y = position.y + math.ceil(2* ent.selection_box.right_bottom.y)/2 - .5 + position.y = position.y + math.ceil(2* ent_p.selection_box.right_bottom.y)/2 - .5 elseif players[pindex].building_direction == 1 or players[pindex].building_direction == 3 then - position.y = position.y + math.ceil(2* ent.selection_box.right_bottom.x)/2 - .5 + position.y = position.y + math.ceil(2* ent_p.selection_box.right_bottom.x)/2 - .5 end elseif players[pindex].player_direction == defines.direction.west then if players[pindex].building_direction == 0 or players[pindex].building_direction == 2 then - position.x = position.x + math.ceil(2* ent.selection_box.left_top.x)/2 + .5 + position.x = position.x + math.ceil(2* ent_p.selection_box.left_top.x)/2 + .5 elseif players[pindex].building_direction == 1 or players[pindex].building_direction == 3 then - position.x = position.x + math.ceil(2* ent.selection_box.left_top.y)/2 + .5 + position.x = position.x + math.ceil(2* ent_p.selection_box.left_top.y)/2 + .5 end elseif players[pindex].player_direction == defines.direction.east then if players[pindex].building_direction == 0 or players[pindex].building_direction == 2 then - position.x = position.x + math.ceil(2* ent.selection_box.right_bottom.x)/2 - .5 + position.x = position.x + math.ceil(2* ent_p.selection_box.right_bottom.x)/2 - .5 elseif players[pindex].building_direction == 1 or players[pindex].building_direction == 3 then - position.x = position.x + math.ceil(2* ent.selection_box.right_bottom.y)/2 - .5 + position.x = position.x + math.ceil(2* ent_p.selection_box.right_bottom.y)/2 - .5 end end local dict = game.get_filtered_entity_prototypes{{filter = "type", type = "electric-pole"}} @@ -2774,35 +3262,68 @@ function read_tile(pindex) end table.sort(poles, function(k1, k2) return k1.supply_area_distance < k2.supply_area_distance end) local check = false + local found_pole = nil for i, pole in ipairs(poles) do local names = {} for i1 = i, #poles, 1 do table.insert(names, poles[i1].name) end + local supply_dist = pole.supply_area_distance + if supply_dist > 15 then + supply_dist = supply_dist - 2 + end local area = { - left_top = {(position.x + math.ceil(ent.selection_box.left_top.x) - pole.supply_area_distance), (position.y + math.ceil(ent.selection_box.left_top.y) - pole.supply_area_distance)}, - right_bottom = {position.x + math.floor(ent.selection_box.right_bottom.x) + pole.supply_area_distance, position.y + math.floor(ent.selection_box.right_bottom.y) + pole.supply_area_distance}, + left_top = {(position.x + math.ceil(ent_p.selection_box.left_top.x) - supply_dist), (position.y + math.ceil(ent_p.selection_box.left_top.y) - supply_dist)}, + right_bottom = {(position.x + math.floor(ent_p.selection_box.right_bottom.x) + supply_dist), (position.y + math.floor(ent_p.selection_box.right_bottom.y) + supply_dist)}, orientation = players[pindex].building_direction/4 - } + }--**todo "connected" check is a little buggy at the supply area edges, need to trim and tune, maybe re-enable direction based offset? The offset could be due to the pole width: 1 vs 2 local T = { area = area, name = names } - if #surf.find_entities_filtered(T) > 0 then + local supplier_poles = surf.find_entities_filtered(T) + if #supplier_poles > 0 then check = true + found_pole = supplier_poles[1] break end end if check then - result = result .. " " .. "connected" + result = result .. " Power connected " + if found_pole.valid then + local dist = math.ceil(util.distance(found_pole.position,pos)) + local dir = get_direction_of_that_from_this(found_pole.position,pos) + result = result .. " from " .. dist .. " tiles " .. direction_lookup(dir) .. ", " + end else - result = result .. "Not Connected" + result = result .. " Power Not Connected, " + --Notify if no connections and state nearest electric pole + local nearest_pole, min_dist = find_nearest_electric_pole(nil,false,50,surf,pos) + if min_dist == nil or min_dist >= 1000 then + result = result .. " no electric poles within 1000 tiles, " + else + local dir = get_direction_of_that_from_this(nearest_pole.position,pos) + result = result .. math.ceil(min_dist) .. " tiles " .. direction_lookup(dir) .. " to nearest electric pole, " + end end - end end - printout(result, pindex) + + return result end + +--Turns off the cut paste tool if already held +script.on_event("control-x", function(event) + local pindex = event.player_index + local stack = game.get_player(pindex).cursor_stack + if stack.valid_for_read and stack.name == "cut-paste-tool" then + --game.get_player(pindex).clear_cursor()--does not work + --game.get_player(pindex).cursor_stack.clear() + printout("To disable this tool empty the hand, by pressing SHIFT + Q",pindex) + end +end) + + --Read the current co-ordinates of the cursor on the map or in a menu. Provides extra information in some menus. function read_coords(pindex, start_phrase) start_phrase = start_phrase or "" @@ -2813,7 +3334,26 @@ function read_coords(pindex, start_phrase) offset = 1 end if not(players[pindex].in_menu) then - printout(result .. math.floor(players[pindex].cursor_pos.x) .. ", " .. math.floor(players[pindex].cursor_pos.y), pindex) + if game.get_player(pindex).driving then + local vehicle = game.get_player(pindex).vehicle + result = result .. " in " .. vehicle.name .. " " + if vehicle.speed > 0 then + result = result .. " heading " .. get_heading(vehicle) .. " at " .. math.floor(vehicle.speed) .. " kilometers per hour, past the location " + elseif vehicle.speed < 0 then + result = result .. " reversing while facing" .. get_heading(vehicle) .. " at " .. math.floor(-vehicle.speed) .. " kilometers per hour, past the location " + else + result = result .. " parked facing " .. get_heading(vehicle) .. " at location " + end + printout(result .. math.floor(vehicle.position.x) .. ", " .. math.floor(vehicle.position.y), pindex) + else + local location = get_entity_part_at_cursor(pindex) + if location == nil then + location = "point" + end + --Simply give coords + printout(result .. " " .. location .. ", at " .. math.floor(players[pindex].cursor_pos.x) .. ", " .. math.floor(players[pindex].cursor_pos.y), pindex) + --p.print(result .. " " .. location .. ", at " .. (players[pindex].cursor_pos.x) .. ", " .. (players[pindex].cursor_pos.y)) + end elseif players[pindex].menu == "inventory" or (players[pindex].menu == "building" and players[pindex].building.sector > offset + #players[pindex].building.sectors) then local x = players[pindex].inventory.index %10 local y = math.floor(players[pindex].inventory.index/10) + 1 @@ -3024,11 +3564,33 @@ function initialize(player) index = 0, direction = "none" } + + faplayer.rail_builder = faplayer.rail_builder or { + index = 0, + index_max = 1, + rail = nil, + rail_type = 0 + } + + faplayer.train_menu = faplayer.train_menu or { + index = 0, + renaming = false, + locomotive = nil + } + + faplayer.train_stop_menu = faplayer.train_stop_menu or { + index = 0, + renaming = false, + stop = nil + } + if table_size(faplayer.mapped) == 0 then player.force.rechart() end + end + script.on_event(defines.events.on_player_changed_position,function(event) local pindex = event.player_index if not check_for_player(pindex) then @@ -3059,7 +3621,7 @@ end) function menu_cursor_move(direction,pindex) - players[pindex].setting_inventory_wraps_around = true--**temporary line because I could not find where to initialize this properly + players[pindex].setting_inventory_wraps_around = true--laterdo make this a setting to toggle if direction == defines.direction.north then menu_cursor_up(pindex) elseif direction == defines.direction.south then @@ -3100,7 +3662,7 @@ function menu_cursor_up(pindex) read_inventory_slot(pindex) else --Border setting: Undo change and play error sound players[pindex].inventory.index = players[pindex].inventory.index +10 - game.get_player(pindex).play_sound{path = "Mine-Building"}--todo set error sound + game.get_player(pindex).play_sound{path = "Mine-Building"} printout("Border.", pindex) end else @@ -3254,6 +3816,10 @@ function menu_cursor_up(pindex) read_travel_slot(pindex) elseif players[pindex].menu == "structure-travel" then move_cursor_structure(pindex, 0) + elseif players[pindex].menu == "rail_builder" then + rail_builder_up(pindex) + elseif players[pindex].menu == "train_stop_menu" then + train_stop_menu_up(pindex) end end @@ -3285,7 +3851,7 @@ function menu_cursor_down(pindex) read_inventory_slot(pindex) else --Border setting: Undo change and play error sound players[pindex].inventory.index = players[pindex].inventory.index -10 - game.get_player(pindex).play_sound{path = "Mine-Building"}--todo set error sound + game.get_player(pindex).play_sound{path = "Mine-Building"} printout("Border.", pindex) end else @@ -3456,6 +4022,10 @@ function menu_cursor_down(pindex) read_travel_slot(pindex) elseif players[pindex].menu == "structure-travel" then move_cursor_structure(pindex, 4) + elseif players[pindex].menu == "rail_builder" then + rail_builder_down(pindex) + elseif players[pindex].menu == "train_stop_menu" then + train_stop_menu_down(pindex) end end @@ -3473,7 +4043,7 @@ function menu_cursor_left(pindex) read_inventory_slot(pindex) else --Border setting: Undo change and play error sound players[pindex].inventory.index = players[pindex].inventory.index +1 - game.get_player(pindex).play_sound{path = "Mine-Building"}--todo set error sound + game.get_player(pindex).play_sound{path = "Mine-Building"} printout("Border.", pindex) end else @@ -3600,7 +4170,7 @@ function menu_cursor_right(pindex) read_inventory_slot(pindex) else --Border setting: Undo change and play error sound players[pindex].inventory.index = players[pindex].inventory.index -1 - game.get_player(pindex).play_sound{path = "Mine-Building"}--todo set error sound + game.get_player(pindex).play_sound{path = "Mine-Building"} printout("Border.", pindex) end else @@ -3818,6 +4388,17 @@ function on_tick(event) global.scheduled_events[event.tick] = nil end move_characters(event) + + --Play train track warning sounds at appropriate frequencies + if event.tick % 15 == 0 then + play_train_track_alert_sounds(3) + if event.tick % 30 == 0 then + play_train_track_alert_sounds(2) + if event.tick % 60 == 0 then + play_train_track_alert_sounds(1) + end + end + end end script.on_event(defines.events.on_tick,on_initial_joining_tick) @@ -3889,6 +4470,8 @@ end function move(direction,pindex) if players[pindex].walk == 2 then return + elseif game.get_player(pindex).driving then + return end local first_player = game.get_player(pindex) local pos = players[pindex].position @@ -3969,7 +4552,31 @@ function move_key(direction,event) end end - +--Called when a player enters or exits a vehicle +script.on_event(defines.events.on_player_driving_changed_state, function(event) + pindex = event.player_index + if not check_for_player(pindex) then + return + end + if game.get_player(pindex).driving then + players[pindex].last_vehicle = game.get_player(pindex).vehicle + printout("Entered " .. game.get_player(pindex).vehicle.name ,pindex) + if players[pindex].last_vehicle.train ~= nil and players[pindex].last_vehicle.train.schedule == nil then + players[pindex].last_vehicle.train.manual_mode = true + end + elseif players[pindex].last_vehicle ~= nil then + printout("Exited " .. players[pindex].last_vehicle.name ,pindex) + if players[pindex].last_vehicle.train ~= nil and players[pindex].last_vehicle.train.schedule == nil then + players[pindex].last_vehicle.train.manual_mode = true + end + teleport_to_closest(pindex, players[pindex].last_vehicle.position, true) + if players[pindex].menu == "train_menu" then + train_menu_close(pindex, false) + end + else + printout("Driving state changed." ,pindex) + end +end) script.on_event("cursor-up", function(event) move_key(defines.direction.north,event) @@ -3996,24 +4603,57 @@ script.on_event("read-coords", function(event) read_coords(pindex) end ) + +--J Key script.on_event("jump-to-player", function(event) pindex = event.player_index if not check_for_player(pindex) then return end - if not (players[pindex].in_menu) then + local ent = players[pindex].tile.ents[1] + if game.get_player(pindex).driving and game.get_player(pindex).vehicle.train ~= nil then + train_read_next_rail_entity_ahead(pindex,false) + elseif ent ~= nil and ent.valid and (ent.name == "straight-rail" or ent.name == "curved-rail") then + --Report what is along the rail + rail_read_next_rail_entity_ahead(pindex, ent, true) + elseif not (players[pindex].in_menu) then if players[pindex].cursor then jump_to_player(pindex) end end end ) + + +--SHIFT + J Key +script.on_event("shift-j", function(event) + pindex = event.player_index + if not check_for_player(pindex) then + return + end + local ent = players[pindex].tile.ents[1] + if game.get_player(pindex).driving and game.get_player(pindex).vehicle.train ~= nil then + train_read_next_rail_entity_ahead(pindex,true) + elseif ent ~= nil and ent.valid and (ent.name == "straight-rail" or ent.name == "curved-rail") then + --Report what is along the rail + rail_read_next_rail_entity_ahead(pindex, ent, false) + end +end +) + + script.on_event("teleport-to-cursor", function(event) pindex = event.player_index if not check_for_player(pindex) then return end + if game.get_player(pindex).driving then + printout("Cannot teleport while in a vehicle.", pindex) + return + end if not (players[pindex].in_menu) then - teleport_to_cursor(pindex) + teleport_to_cursor(pindex) + else + printout("Cannot teleport while in a menu.", pindex) end end ) @@ -4373,7 +5013,14 @@ script.on_event("open-inventory", function(event) if players[pindex].menu == "structure-travel" then game.get_player(pindex).gui.screen["structure-travel"].destroy() end - + if players[pindex].menu == "rail_builer" then + rail_builder_close(pindex, false) + elseif players[pindex].menu == "train_menu" then + train_menu_close(pindex, false) + elseif players[pindex].menu == "train_stop_menu" then + train_stop_menu_close(pindex, false) + end + players[pindex].menu = "none" players[pindex].item_selection = false players[pindex].item_cache = {} @@ -4623,6 +5270,76 @@ script.on_event("switch-menu", function(event) end end + + --Gun related changes + local p = game.get_player(pindex) + local gun_index = p.character.selected_gun_index + local guns_inv = p.get_inventory(defines.inventory.character_guns) + local ammo_inv = game.get_player(pindex).get_inventory(defines.inventory.character_ammo) + local stack = guns_inv[gun_index] + local guns_count = #guns_inv - guns_inv.count_empty_stacks() + local ammos_count = #ammo_inv - ammo_inv.count_empty_stacks() + local result = "" + + if not players[pindex].in_menu then + --Increment the selected weapon + if gun_index >= guns_count then + gun_index = 1 + else + gun_index = gun_index + 1 + end + stack = guns_inv[gun_index] + + --Select weapon + if stack ~= nil and stack.valid_for_read and stack.valid then + result = stack.name + else + result = " weapon name error 1" + end + + --Check if need to skip due to no ammo + if guns_count ~= ammos_count then + --Cycle forward until the first filled ammo slot (as the game does) + result = " weapon name error 2 " + local ammo_stack = ammo_inv[gun_index] + local tries = 0 + while tries < 4 and not (ammo_stack ~= nil and ammo_stack.valid_for_read and ammo_stack.valid) do + if gun_index >= guns_count then + gun_index = 1 + else + gun_index = gun_index + 1 + end + tries = tries + 1 + stack = guns_inv[gun_index] + ammo_stack = ammo_inv[gun_index] + end + --Now try the name + if stack ~= nil and stack.valid_for_read and stack.valid then + result = stack.name + else + result = " weapon name error 3" + end + end + --Declare the selected weapon + stack = guns_inv[gun_index] + if stack ~= nil and stack.valid_for_read and stack.valid then + result = stack.name + if ammo_inv[gun_index] ~= nil and ammo_inv[gun_index].valid and ammo_inv[gun_index].valid_for_read then + result = result .. " with " .. ammo_inv[gun_index].count .. " " .. ammo_inv[gun_index].name .. "s " + end + else + result = " missing or unknown weapon " + end + --p.print(result) + printout(result,pindex) + else + --Re-apply the previous gun index so that weapons do not switch when in a menu + if p.character.selected_gun_index == 1 then + p.character.selected_gun_index = guns_count + else + p.character.selected_gun_index = p.character.selected_gun_index - 1 + end + end end) script.on_event("reverse-switch-menu", function(event) @@ -4751,9 +5468,44 @@ script.on_event("mine-access", function(event) end ) +--Mines groups of entities depending on the name or type. Includes trees and rocks, rails. +script.on_event("mine-group", function(event) + pindex = event.player_index + if not check_for_player(pindex) then + return + end + if not (players[pindex].in_menu) and #players[pindex].tile.ents > 0 then + local ent = players[pindex].tile.ents[1] + if ent == nil or not ent.valid then + return + end + local surf = ent.surface + local pos = ent.position + if ent ~= nil and ent.valid and ent.type == "tree" or ent.name == "rock-big" or ent.name == "rock-huge" or ent.name == "sand-rock-big" then + --Trees and rocks within 5 tiles + game.get_player(pindex).play_sound{path = "Mine-Building"} + game.get_player(pindex).play_sound{path = "Mine-Building"} + mine_trees_and_rocks_in_circle(pos, 5, pindex) + elseif ent ~= nil and ent.valid and ent.name == "straight-rail" then + --Rails within 3 tiles (and their signals) + local rails = surf.find_entities_filtered{position = pos, radius = 3, name = "straight-rail"} + for i,rail in ipairs(rails) do + mine_signals(rail,pindex) + game.get_player(pindex).play_sound{path = "entity-mined/straight-rail"} + game.get_player(pindex).mine_entity(rail,true) + end + elseif ent ~= nil and ent.valid and ent.prototype.is_building and (ent.prototype.mineable_properties.products == nil or ent.prototype.mineable_properties.products[1].name == ent.name) then + --All others are treated as single objects + game.get_player(pindex).play_sound{path = "Mine-Building"} + schedule(25, "play_mining_sound", pindex) + end + end +end +) + script.on_event("left-click", function(event) pindex = event.player_index - if not check_for_player(pindex) then + if not check_for_player(pindex) then return end if players[pindex].in_menu then @@ -4776,8 +5528,6 @@ script.on_event("left-click", function(event) printout("Not enough materials", pindex) end - - elseif players[pindex].menu == "crafting_queue" then load_crafting_queue(pindex) if players[pindex].crafting_queue.max >= 1 then @@ -4788,8 +5538,8 @@ script.on_event("left-click", function(event) game.get_player(pindex).cancel_crafting(T) load_crafting_queue(pindex) read_crafting_queue(pindex) - end + elseif players[pindex].menu == "building" then if players[pindex].building.sector <= #players[pindex].building.sectors and #players[pindex].building.sectors[players[pindex].building.sector].inventory > 0 then if players[pindex].building.sectors[players[pindex].building.sector].name == "Fluid" then @@ -4831,8 +5581,6 @@ script.on_event("left-click", function(event) printout("Filter set.", pindex) players[pindex].building.item_selection = false players[pindex].item_selection = false - - end else players[pindex].item_selector.group = 0 @@ -4843,7 +5591,6 @@ script.on_event("left-click", function(event) players[pindex].item_cache = get_iterable_array(game.item_group_prototypes) prune_item_groups(players[pindex].item_cache) read_item_selector_slot(pindex) - end return end @@ -4899,6 +5646,7 @@ script.on_event("left-click", function(event) end end + elseif players[pindex].menu == "technology" then local techs = {} if players[pindex].technology.category == 1 then @@ -4916,6 +5664,7 @@ script.on_event("left-click", function(event) printout("Research locked, first complete the prerequisites.", pindex) end end + elseif players[pindex].menu == "pump" then if players[pindex].pump.index == 0 then printout("Move up and down to select a location.", pindex) @@ -4926,6 +5675,7 @@ script.on_event("left-click", function(event) players[pindex].in_menu = false players[pindex].menu = "none" printout("Pump placed.", pindex) + elseif players[pindex].menu == "warnings" then local warnings = {} if players[pindex].warnings.sector == 1 then @@ -4993,6 +5743,7 @@ input.select(1, 0) input.focus() input.select(1, 0) end + elseif players[pindex].menu == "structure-travel" then local tar = nil local network = players[pindex].structure_travel.network @@ -5025,11 +5776,21 @@ input.select(1, 0) return end target(pindex) - + + elseif players[pindex].menu == "rail_builder" then + rail_builder(pindex, true) + rail_builder_close(pindex,false) + elseif players[pindex].menu == "train_menu" then + train_menu(players[pindex].train_menu.index, pindex, true) + elseif players[pindex].menu == "train_stop_menu" then + train_stop_menu(players[pindex].train_stop_menu.index, pindex, true) end + else + --Not in a menu local stack = game.get_player(pindex).cursor_stack - if stack.valid_for_read and stack.valid and stack.prototype.place_result ~= nil and stack.name ~= "offshore-pump" then + local ent = players[pindex].tile.ents[1] + if stack.valid_for_read and stack.valid and (stack.prototype.place_result ~= nil or stack.prototype.place_as_tile_result ~= nil) and stack.name ~= "offshore-pump" then local offset = 0 if not players[pindex].cursor then offset = 1 @@ -5037,9 +5798,20 @@ input.select(1, 0) build_item_in_hand(pindex, offset) elseif stack.valid and stack.valid_for_read and stack.name == "offshore-pump" then build_offshore_pump_in_hand(pindex) + elseif stack.valid and stack.valid_for_read then + local p = game.get_player(pindex) + p.use_from_cursor{p.position.x+1,p.position.y+1}--tolaterdo adjust it to use an item 3 tiles in front of the player instead. + --No more stack related checks after this point + elseif game.get_player(pindex).driving and game.get_player(pindex).vehicle.train ~= nil then + train_menu_open(pindex) elseif next(players[pindex].tile.ents) ~= nil and players[pindex].tile.index > 1 and players[pindex].tile.ents[1].valid then - local ent = players[pindex].tile.ents[1] - if ent.operable and ent.prototype.is_building then + local ent = players[pindex].tile.ents[1] + --Clicking on an entity in the world + if ent.name == "train-stop" then + train_stop_menu_open(pindex) + elseif ent.name == "locomotive" or ent.name == "cargo-wagon" or ent.name == "fluid-wagon" then + train_menu_open(pindex) + elseif ent.operable and ent.prototype.is_building then if ent.prototype.subgroup.name == "belt" then players[pindex].in_menu = true players[pindex].menu = "belt" @@ -5158,6 +5930,19 @@ input.select(1, 0) end ) +--Mines an entity with the right sound +function try_to_mine_with_sound(ent,pindex) + if ent ~= nil and ent.valid and ent.destructible and ent.type ~= "resource" then + local ent_name = ent.name + if game.get_player(pindex).mine_entity(ent,false) and game.is_valid_sound_path("entity-mined/" .. ent_name) then + game.get_player(pindex).play_sound{path = "entity-mined/" .. ent_name} + return true + else + return false + end + end +end + --[[Attempts to build the item in hand. * Does nothing if the hand is empty or the item is not a place-able entity. @@ -5168,9 +5953,33 @@ function build_item_in_hand(pindex, offset_val) local stack = game.get_player(pindex).cursor_stack local offset = offset_val or 0 - if stack.valid and stack.valid_for_read and stack.name == "offshore-pump" then + if not (stack.valid and stack.valid_for_read) then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + local message = "Invalid item in hand!" + if game.get_player(pindex).is_cursor_empty() then + local auto_cancel_when_empty = true --laterdo this check may become a toggle-able game setting + if players[pindex].build_lock == true and auto_cancel_when_empty then + players[pindex].build_lock = false + message = "Build lock disabled, empty hand." + end + end + printout(message,pindex) + return + end + + if stack.name == "offshore-pump" then build_offshore_pump_in_hand(pindex) return + elseif stack.name == "rail" then + if offset_val ~= 1.337 then --only when sentinel value, it allows free building rails + local pos = players[pindex].cursor_pos + append_rail(pos, pindex) + return + end + elseif stack.name == "rail-signal" or stack.name == "rail-chain-signal" then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout("You need to use the building menu of a rail.",pindex) + return end if stack.valid_for_read and stack.valid and stack.prototype.place_result ~= nil then @@ -5181,7 +5990,10 @@ function build_item_in_hand(pindex, offset_val) if not(players[pindex].cursor) then local old_pos = game.get_player(pindex).position local adjusted_offset = offset - if players[pindex].player_direction == 0 or players[pindex].player_direction == 4 then + if stack.name == "locomotive" or stack.name == "cargo-wagon" or stack.name == "fluid-wagon" or stack.name == "artillery-wagon" then + --Allow easy placement onto rails. + adjusted_offset = 2.5 + elseif players[pindex].player_direction == 0 or players[pindex].player_direction == 4 then adjusted_offset = adjusted_offset * (dimensions.y + .5)/2 else adjusted_offset = adjusted_offset * (dimensions.x+.5)/2 @@ -5216,7 +6028,25 @@ function build_item_in_hand(pindex, offset_val) game.get_player(pindex).play_sound{path = "Inventory-Move"} return end - end + elseif stack.name == "medium-electric-pole" and players[pindex].build_lock == true then + --Place a medium electric pole in this position only if it is within 6.5 to 7.5 tiles of another medium electric pole + local surf = game.get_player(pindex).surface + local med_poles = surf.find_entities_filtered{position = position, radius = 7.5, name = "medium-electric-pole"} + local all_beyond_6_5 = true + local any_connects = false + for i,pole in ipairs(med_poles) do + if util.distance(position, pole.position) < 6.5 then + all_beyond_6_5 = false + elseif util.distance(position, pole.position) >= 6.5 then + any_connects = true + end + end + if not (all_beyond_6_5 and any_connects) then + game.get_player(pindex).play_sound{path = "Inventory-Move"} + return + end + end + --Build it local building = { position = position, direction = players[pindex].building_direction * 2, @@ -5234,11 +6064,17 @@ function build_item_in_hand(pindex, offset_val) printout("Cannot place that there.", pindex) end end + elseif stack.valid_for_read and stack.valid and stack.prototype.place_as_tile_result ~= nil then + --Place tiles + local p = game.get_player(pindex) + local t_size = 3 --laterdo allow adjusting terrain_building_size + if p.can_build_from_cursor{position = p.position, terrain_building_size = t_size} then + p.build_from_cursor{position = p.position, terrain_building_size = t_size} + else + p.play_sound{path = "utility/cannot_build"} + end else - if players[pindex].build_lock == true and 1 == 1 then --This check may become a toggle-able game setting - players[pindex].build_lock = false - printout("Build lock disabled, empty hand.", pindex) - end + game.get_player(pindex).play_sound{path = "utility/cannot_build"} end end @@ -5282,7 +6118,7 @@ end script.on_event("shift-click", function(event) pindex = event.player_index - if not check_for_player(pindex) then + if not check_for_player(pindex) then return end if players[pindex].in_menu then @@ -5314,7 +6150,8 @@ script.on_event("shift-click", function(event) end elseif players[pindex].menu == "building" then if players[pindex].building.sector <= #players[pindex].building.sectors and #players[pindex].building.sectors[players[pindex].building.sector].inventory > 0 and players[pindex].building.sectors[players[pindex].building.sector].name ~= "Fluid" then - local stack = players[pindex].building.sectors[players[pindex].building.sector].inventory[players[pindex].building.index] + --Transfer stack from building to player inventory + local stack = players[pindex].building.sectors[players[pindex].building.sector].inventory[players[pindex].building.index] if stack.valid and stack.valid_for_read then if game.get_player(pindex).can_insert(stack) then game.get_player(pindex).play_sound{path = "utility/inventory_move"} @@ -5324,7 +6161,11 @@ script.on_event("shift-click", function(event) result = "Moved " .. inserted .. " " .. result .. " to player's inventory." printout(result, pindex) else - printout("Inventory full.", pindex) + local result = "Cannot insert " .. stack.name .. " to player's inventory, " + if game.get_player(pindex).get_main_inventory().count_empty_stacks() == 0 then + result = result .. "because it is full." + end + printout(result,pindex) end end else @@ -5333,6 +6174,7 @@ script.on_event("shift-click", function(event) offset = offset + 1 end if players[pindex].building.sector == #players[pindex].building.sectors + offset then + --Transfer stack from player inventory to buidling local stack = players[pindex].inventory.lua_inventory[players[pindex].inventory.index] if stack.valid and stack.valid_for_read then if players[pindex].building.ent.can_insert(stack) then @@ -5343,13 +6185,28 @@ script.on_event("shift-click", function(event) result = "Moved " .. inserted .. " " .. result .. " to " .. players[pindex].building.ent.name printout(result, pindex) else - printout("Inventory full.", pindex) + local result = "Cannot insert " .. stack.name .. " to " .. players[pindex].building.ent.name + printout(result,pindex) end end end end - - + elseif players[pindex].menu == "inventory" then + --Equip item grabbed in hand + local stack = game.get_player(pindex).cursor_stack + local result = equip_it(stack,pindex) + --game.get_player(pindex).print(result)-- + printout(result,pindex) + end + else + local ent = players[pindex].tile.ents[1] + if ent ~= nil and ent.valid then + if ent.name == "straight-rail" then + --Open rail builder + rail_builder_open(pindex, ent) + elseif ent.name == "curved-rail" then + printout("Rail builder menu cannot use curved rails.", pindex) + end end end end @@ -5370,6 +6227,24 @@ script.on_event("control-click", function(event) if players[pindex].menu == "building" then do_multi_stack_transfer(1,pindex) end + else + local stack = game.get_player(pindex).cursor_stack + local ent = players[pindex].tile.ents[1] + if stack == nil or not stack.valid_for_read or not stack.valid then + if ent ~= nil and ent.valid and ent.name == "splitter" then + --Clear the filter + local result = set_splitter_priority(ent, nil, nil, nil, true) + printout(result,pindex) + end + return + elseif stack.name == "rail" then + --Straight rail free placement + build_item_in_hand(pindex, 1.337)--Uses sentinel value + elseif ent ~= nil and ent.valid and ent.name == "splitter" then + --Set the filter + local result = set_splitter_priority(ent, nil, nil, stack) + printout(result,pindex) + end end end ) @@ -5380,6 +6255,7 @@ end ]] script.on_event("control-right-click", function(event) pindex = event.player_index + local ent = players[pindex].tile.ents[1] if not check_for_player(pindex) then return end @@ -5441,7 +6317,7 @@ function do_multi_stack_transfer(ratio,pindex) local moved, full = transfer_inventory{from=game.players[pindex].get_main_inventory(),to=players[pindex].building.ent,name=item_name,ratio=ratio} if full then - table.insert(result,"Inventory full. ") + table.insert(result,"Inventory full or not applicable. ") end if table_size(moved) == 0 then table.insert(result,{"access.placed-nothing"}) @@ -5495,9 +6371,10 @@ end script.on_event("right-click", function(event) pindex = event.player_index - if not check_for_player(pindex) then + if not check_for_player(pindex) then return end + local stack = game.get_player(pindex).cursor_stack if players[pindex].in_menu then if players[pindex].menu == "crafting" then local recipe = players[pindex].crafting.lua_recipes[players[pindex].crafting.category][players[pindex].crafting.index] @@ -5558,18 +6435,46 @@ script.on_event("right-click", function(event) end end + elseif stack.valid and stack.valid_for_read and stack.name == "rail" then + --Append rail + build_item_in_hand(pindex, 0) elseif next(players[pindex].tile.ents) ~= nil and players[pindex].tile.index > 1 and players[pindex].tile.ents[1].valid then --Print out the status of a machine, if it exists. + local result = "" local ent = players[pindex].tile.ents[1] local ent_status_id = ent.status local ent_status_text = "" local status_lookup = into_lookup(defines.entity_status) - if ent_status_id ~= nil then + if ent.name == "cargo-wagon" then + --Instead of status, read contents + result = " " .. cargo_wagon_top_contents_info(ent) + elseif ent.name == "fluid-wagon" then + --Instead of status, read contents + result = " " .. fluid_contents_info(ent) + elseif ent_status_id ~= nil then + --Print status if it exists ent_status_text = status_lookup[ent_status_id] - printout(" " .. ent_status_text ,pindex) - else - printout("No status." ,pindex) + result = " " .. ent_status_text + else--There is no status + --When there is no status, for entities with fuel inventories, read that out instead. This is typical for vehicles. + if ent.get_fuel_inventory() ~= nil then + printout(" " .. fuel_inventory_info(ent),pindex) + elseif ent.type == "electric-pole" then + --For electric poles with no power flow, report the nearest electric pole with a power flow. + if get_electricity_satisfaction(ent) > 0 then + result = get_electricity_satisfaction(ent) .. " percent network satisfaction, with " .. get_electricity_flow_info(ent) + else + result = "No power, " .. report_nearest_supplied_electric_pole(ent) + end + else + result = "No status." + end + end + if result == "" then + result = "result error" end + printout(result ,pindex) + --game.get_player(pindex).print(result)-- end end ) @@ -5641,25 +6546,35 @@ script.on_event("rotate-building", function(event) printout(ent.name .. " cannot be rotated.", pindex) end else - print("not a valid stack for rotating") + print("not a valid stack for rotating", pindex) end + elseif players[pindex].menu == "inventory" then + --Read Weapon data + local result = read_weapons_and_ammo(pindex) + --game.get_player(pindex).print(result)-- + printout(result,pindex) end end ) ---Reads the custom written description for an item +--Reads the custom written description for an item, called with L Key script.on_event("item-info", function(event) pindex = event.player_index - if not check_for_player(pindex) then + if not check_for_player(pindex) then return end + if game.get_player(pindex).driving then + printout(vehicle_info(pindex),pindex) + return + end + local offset = 0 if players[pindex].menu == "building" and players[pindex].building.recipe_list ~= nil then offset = 1 end if not players[pindex].in_menu then local ent = players[pindex].tile.ents[1] - if ent ~= nil then + if ent ~= nil and ent.valid then local str = ent.localised_description printout(str, pindex) end @@ -5753,20 +6668,22 @@ script.on_event("item-info", function(event) end ) +--Gives in-game time. The night darkness is from 11 to 13, and peak daylight hours are 18 to 6. +--For realism, if we adjust by 12 hours, we get 23 to 1 as midnight and 6 to 18 as peak solar. script.on_event("time", function(event) pindex = event.player_index if not check_for_player(pindex) then return end local surf = game.get_player(pindex).surface - local hour = math.floor(24*surf.daytime) - local minute = math.floor((24* surf.daytime - hour) * 60) + local hour = math.floor((24*surf.daytime + 12) % 24) + local minute = math.floor((24* surf.daytime - math.floor(24*surf.daytime)) * 60) local progress = math.floor(game.get_player(pindex).force.research_progress* 100) local tech = game.get_player(pindex).force.current_research if tech ~= nil then - printout("The time is " .. hour .. ":" .. string.format("%02d", minute) .. " Researching " .. game.get_player(pindex).force.current_research.name .. " " .. progress .. "%", pindex) + printout("The local time is " .. hour .. ":" .. string.format("%02d", minute) .. ", Researching " .. game.get_player(pindex).force.current_research.name .. " " .. progress .. "%", pindex) else - printout("The time is " .. hour .. ":" .. string.format("%02d", minute), pindex) + printout("The local time is " .. hour .. ":" .. string.format("%02d", minute), pindex) end end) @@ -5949,7 +6866,12 @@ script.on_event("toggle-walk",function(event) if not check_for_player(pindex) then return end - players[pindex].walk = (players[pindex].walk + 1) % 3 + if players[pindex].walk == 0 then --Mode 1 (walk-by-step) is temporarily disabled until it comes back as an in game setting. + players[pindex].walk = 2 + else + players[pindex].walk = 0 + end + --players[pindex].walk = (players[pindex].walk + 1) % 3 printout(walk_type_speech[players[pindex].walk +1], pindex) end) @@ -6005,7 +6927,7 @@ script.on_event("open-fast-travel", function(event) if not check_for_player(pindex) then return end - if players[pindex].in_menu == false then + if players[pindex].in_menu == false and game.get_player(pindex).driving == false then game.get_player(pindex).game_view_settings.update_entity_selection = false game.get_player(pindex).selected = nil @@ -6022,10 +6944,29 @@ script.on_event("open-fast-travel", function(event) game.get_player(pindex).opened = frame end + + --Report disconnect error because the V key normally disconnects rolling stock if driving. + local vehicle = nil + if game.get_player(pindex).vehicle ~= nil and game.get_player(pindex).vehicle.train ~= nil then + vehicle = game.get_player(pindex).vehicle + local connected = 0 + if vehicle.get_connected_rolling_stock(defines.rail_direction.front) ~= nil then + connected = connected + 1 + end + if vehicle.get_connected_rolling_stock(defines.rail_direction.back) ~= nil then + connected = connected + 1 + end + if connected == 0 then + printout("Warning, this vehicle was disconnected. Please review mod settings.", pindex) + --Attempt to reconnect (does not work) + --vehicle.connect_rolling_stock(defines.rail_direction.front) + --vehicle.connect_rolling_stock(defines.rail_direction.back) + end + end end) - +--GUI action confirmed, such as by pressing ENTER script.on_event(defines.events.on_gui_confirmed,function(event) local pindex = event.player_index if players[pindex].menu == "travel" then @@ -6043,6 +6984,18 @@ script.on_event(defines.events.on_gui_confirmed,function(event) end players[pindex].travel.index.x = 1 event.element.destroy() + elseif players[pindex].train_menu.renaming == true then + players[pindex].train_menu.renaming = false + set_train_name(global.players[pindex].train_menu.locomotive.train,event.element.text) + printout("Train renamed to " .. event.element.text .. ", menu closed.", pindex) + event.element.destroy() + train_menu_close(pindex, false) + elseif players[pindex].train_stop_menu.renaming == true then + players[pindex].train_stop_menu.renaming = false + global.players[pindex].train_stop_menu.stop.backer_name = event.element.text + printout("Train stop renamed to " .. event.element.text .. ", menu closed.", pindex) + event.element.destroy() + train_stop_menu_close(pindex, false) end end) @@ -6164,6 +7117,288 @@ script.on_event("scan-selection-down", function(event) end end) +--Mines all trees and rocks in a selected rectangular area. Useful when placing structures. Forces mining. +function mine_trees_and_rocks_in_circle(position, radius, pindex) + local surf = game.get_player(pindex).surface + local comment = "" + local outcome = true + local trees_cleared = 0 + local rocks_cleared = 0 + + --Find and mine trees + local trees = surf.find_entities_filtered{position = position, radius = radius, type = "tree"} + for i,tree_ent in ipairs(trees) do + rendering.draw_circle{color = {1, 0, 0},radius = 1,width = 1,target = tree_ent.position,surface = tree_ent.surface,time_to_live = 60} + game.get_player(pindex).mine_entity(tree_ent,true) + trees_cleared = trees_cleared + 1 + end + + --Find and mine rocks. Note that they are resource entities with specific names + local resources = surf.find_entities_filtered{position = position, radius = radius, name = {"rock-big","rock-huge","sand-rock-big"}} + for i,resource_ent in ipairs(resources) do + if resource_ent ~= nil and resource_ent.valid then + --game.get_player(pindex).mine_entity(resource_ent,true) --tolaterdo bug with rock group mining or all rock mining? + rendering.draw_circle{color = {1, 0, 0},radius = 2,width = 2,target = resource_ent.position,surface = resource_ent.surface,time_to_live = 60} + rocks_cleared = rocks_cleared + 1 + end + end + if trees_cleared + rocks_cleared > 0 then + comment = "cleared " .. trees_cleared .. " trees and " .. rocks_cleared .. " rocks. " + end + rendering.draw_circle{color = {0, 1, 0},radius = radius,width = radius,target = position,surface = surf,time_to_live = 60} + return outcome, comment +end + + +script.on_event("up-arrow", function(event) + local pindex = event.player_index + local ent = players[pindex].tile.ents[1] + if not check_for_player(pindex) then + return + end + if players[pindex].in_menu and players[pindex].menu == "train_menu" then + train_menu_up(pindex) + else + printout("Up arrow pressed",pindex) + end +end) + + +script.on_event("down-arrow", function(event) + local pindex = event.player_index + local ent = players[pindex].tile.ents[1] + if not check_for_player(pindex) then + return + end + if players[pindex].in_menu and players[pindex].menu == "train_menu" then + train_menu_down(pindex) + else + printout("Down arrow pressed",pindex) + end +end) + + +script.on_event("control-left", function(event) + local pindex = event.player_index + if not check_for_player(pindex) then + return + end + local ent = players[pindex].tile.ents[1] + if ent == nil or not ent.valid then + return + end + --Build left turns on end rails + if ent.name == "straight-rail" then + build_rail_turn_left_45_degrees(ent, pindex) + elseif ent.name == "splitter" then + local result = set_splitter_priority(ent, false, true, nil) + printout(result,pindex) + end +end) + + +script.on_event("control-right", function(event) + local pindex = event.player_index + if not check_for_player(pindex) then + return + end + local ent = players[pindex].tile.ents[1] + if ent == nil or not ent.valid then + return + end + --Build left turns on end rails + if ent.name == "straight-rail" then + build_rail_turn_right_45_degrees(ent, pindex) + elseif ent.name == "splitter" then + local result = set_splitter_priority(ent, false, false, nil) + printout(result,pindex) + end +end) + + +-- G is used to connect rolling stock +script.on_event("g-key", function(event) + local pindex = event.player_index + local ent = players[pindex].tile.ents[1] + local vehicle = nil + if not check_for_player(pindex) then + return + end + + if game.get_player(pindex).vehicle ~= nil and game.get_player(pindex).vehicle.train ~= nil then + vehicle = game.get_player(pindex).vehicle + elseif ent ~= nil and ent.valid and ent.train ~= nil then + vehicle = ent + end + + if vehicle ~= nil then + --Connect rolling stock (or check if the default key bindings make the connection) + local connected = 0 + if vehicle.connect_rolling_stock(defines.rail_direction.front) then + connected = connected + 1 + end + if vehicle.connect_rolling_stock(defines.rail_direction.back) then + connected = connected + 1 + end + if connected > 0 then + printout("Connected this vehicle.", pindex) + else + connected = 0 + if vehicle.get_connected_rolling_stock(defines.rail_direction.front) ~= nil then + connected = connected + 1 + end + if vehicle.get_connected_rolling_stock(defines.rail_direction.back) ~= nil then + connected = connected + 1 + end + if connected > 0 then + printout("Connected this vehicle.", pindex) + else + printout("Nothing was connected.", pindex) + end + end + end + + if players[pindex].in_menu and players[pindex].menu == "inventory" then + local result = read_armor_stats(pindex) + --game.get_player(pindex).print(result)-- + printout(result,pindex) + end +end) + + +--SHIFT + G is used to disconnect rolling stock +script.on_event("shift-g-key", function(event) + local pindex = event.player_index + local ent = players[pindex].tile.ents[1] + local vehicle = nil + if not check_for_player(pindex) then + return + end + + if game.get_player(pindex).vehicle ~= nil and game.get_player(pindex).vehicle.train ~= nil then + vehicle = game.get_player(pindex).vehicle + elseif ent ~= nil and ent.train ~= nil then + vehicle = ent + end + + if vehicle ~= nil then + --Disconnect rolling stock + local disconnected = 0 + if vehicle.disconnect_rolling_stock(defines.rail_direction.front) then + disconnected = disconnected + 1 + end + if vehicle.disconnect_rolling_stock(defines.rail_direction.back) then + disconnected = disconnected + 1 + end + if disconnected > 0 then + printout("Disconnected this vehicle.", pindex) + else + local connected = 0 + if vehicle.get_connected_rolling_stock(defines.rail_direction.front) ~= nil then + connected = connected + 1 + end + if vehicle.get_connected_rolling_stock(defines.rail_direction.back) ~= nil then + connected = connected + 1 + end + if connected > 0 then + printout("Disconnection error.", pindex) + else + printout("Disconnected this vehicle.", pindex) + end + end + end + + if players[pindex].in_menu and players[pindex].menu == "inventory" then + local result = read_equipment_list(pindex) + --game.get_player(pindex).print(result)-- + printout(result,pindex) + end + +end) + + +--**Use this unassigned key binding to test stuff +script.on_event("control-g-key", function(event) + local pindex = event.player_index + local p = game.get_player(pindex) + local ent = players[pindex].tile.ents[1] + if not check_for_player(pindex) then + return + end + local stack = game.get_player(pindex).cursor_stack + if stack.valid_for_read and stack.valid then + -- + end + if ent ~= nil and ent.valid and ent.fluidbox ~= nil then + -- p.print(#ent.fluidbox .. " fluids ") + -- for i = 1, #ent.fluidbox, 1 do + -- if ent.fluidbox[i] ~= nil then + -- p.print(ent.fluidbox[i].name) + -- p.print(#ent.fluidbox.get_pipe_connections(i)) + -- else + -- p.print("(nil) " .. i) + -- p.print(#ent.fluidbox.get_pipe_connections(i)) + -- end + -- end + --set_temporary_train_stop(ent.train,pindex) + --sub_automatic_travel_to_other_stop(ent.train) + --instant_schedule(ent.train) + end + + +end) + +-- +script.on_event("control-shift-g-key", function(event) + local pindex = event.player_index + local ent = players[pindex].tile.ents[1] + local vehicle = nil + if not check_for_player(pindex) then + return + end + + if players[pindex].in_menu and players[pindex].menu == "inventory" then + local result = remove_equipment_and_armor(pindex) + --game.get_player(pindex).print(result)-- + printout(result,pindex) + end + +end) + + +--Attempt to launch a rocket +script.on_event("prompt", function(event) + local pindex = event.player_index + local ent = players[pindex].tile.ents[1] + if not check_for_player(pindex) then + return + end + --For rocket entities, return the silo instead + if ent ~= nil and ent.valid and (ent.name == "rocket-silo-rocket-shadow" or ent.name == "rocket-silo-rocket") then + local ents = ent.surface.find_entities_filtered{position = ent.position, radius = 20, name = "rocket-silo"} + for i,silo in ipairs(ents) do + ent = silo + end + end + --Try to launch from the silo + if ent ~= nil and ent.valid and ent.name == "rocket-silo" then + local try_launch = ent.launch_rocket() + if try_launch then + printout("Launch successful!",pindex) + else + printout("Not ready to launch!",pindex) + end + end +end) + +--This event handler patches the unwanted opening of the inventory screen when closing a factorio access menu +script.on_event(defines.events.on_gui_opened, function(event) + if event.gui_type == defines.gui_type.controller and players[event.player_index].menu == "none" then + game.get_player(event.player_index).opened = nil + --printout("Banana",event.player_index) + end +end) + script.on_event(defines.events.on_chunk_charted,function(event) local pindex = 0 -- if table_size(event.force.players) > 0 then @@ -6449,3 +7684,722 @@ script.on_event(defines.events.on_entity_destroyed,function(event) end players[pindex].destroyed[event.registration_number] = nil end) + +--Scripts regarding train state changes +script.on_event(defines.events.on_train_changed_state,function(event) + if event.train.state == defines.train_state.no_schedule then + --Trains with no schedule are set back to manual mode + event.train.manual_mode = true + elseif event.train.state == defines.train_state.arrive_station then + --Announce station to players on the train + for i,player in ipairs(event.train.passengers) do + local stop = event.train.path_end_stop + if stop ~= nil then + str = " Arriving at station " .. stop.backer_name .. " " + players[player.index].last = str + localised_print{"","out ",str} + end + end + elseif event.train.state == defines.train_state.on_the_path then --laterdo make this announce only when near another trainstop. + --Announce station to players on the train + for i,player in ipairs(event.train.passengers) do + local stop = event.train.path_end_stop + if stop ~= nil then + str = " Heading to station " .. stop.backer_name .. " " + players[player.index].last = str + localised_print{"","out ",str} + end + end + elseif event.train.state == defines.train_state.wait_signal then + --Announce the wait to players on the train + for i,player in ipairs(event.train.passengers) do + local stop = event.train.path_end_stop + if stop ~= nil then + str = " Waiting at signal. " + players[player.index].last = str + localised_print{"","out ",str} + end + end + end +end) + +--Returns the direction of that entity from this entity based on the ratios of the x and y distances. Returns 1 of 8 main directions, with a bias towards the diagonals to make it easier to align with the cardinal directions. +function get_direction_of_that_from_this(pos_that,pos_this) + local diff_x = pos_that.x - pos_this.x + local diff_y = pos_that.y - pos_this.y + local dir = -1 + + if math.abs(diff_x) > 4 * math.abs(diff_y) then --along east-west + if diff_x > 0 then + dir = defines.direction.east + else + dir = defines.direction.west + end + elseif math.abs(diff_y) > 4 * math.abs(diff_x) then --along north-south + if diff_y > 0 then + dir = defines.direction.south + else + dir = defines.direction.north + end + else --along diagonals + if diff_x > 0 and diff_y > 0 then + dir = defines.direction.southeast + elseif diff_x > 0 and diff_y < 0 then + dir = defines.direction.northeast + elseif diff_x < 0 and diff_y > 0 then + dir = defines.direction.southwest + elseif diff_x < 0 and diff_y < 0 then + dir = defines.direction.northwest + elseif diff_x == 0 and diff_y == 0 then + dir = 99--case for "it is right here" + else + dir = -2 + end + end + return dir +end + +--Spawns a lamp at the electric pole and uses its energy level to approximate the network satisfaction percentage with high accuracy +function get_electricity_satisfaction(electric_pole) + local satisfaction = -1 + local test_lamp = electric_pole.surface.create_entity{name = "small-lamp", position = electric_pole.position, raise_built = false, force = electric_pole.force} + satisfaction = math.ceil(test_lamp.energy * 9/8)--Experimentally found coefficient + test_lamp.destroy{} + return satisfaction +end + +function get_electricity_flow_info(ent) + local result = "" + local power = 0 + local capacity = 0 + for i, v in pairs(ent.electric_network_statistics.output_counts) do + power = power + (ent.electric_network_statistics.get_flow_count{name = i, input = false, precision_index = defines.flow_precision_index.five_seconds}) + local cap_add = 0 + for _, power_ent in pairs(ent.surface.find_entities_filtered{name=i,force = ent.force}) do + if power_ent.electric_network_id == ent.electric_network_id then + cap_add = cap_add + 1 + end + end + cap_add = cap_add * game.entity_prototypes[i].max_energy_production + if game.entity_prototypes[i].type == "solar-panel" then + cap_add = cap_add * ent.surface.solar_power_multiplier * (1-ent.surface.darkness) + end + capacity = capacity + cap_add + end + power = power * 60 + capacity = capacity * 60 + result = result .. get_power_string(power) .. " being produced out of " .. get_power_string(capacity) .. " capacity, " + return result +end + +--Finds the neearest electric pole. Can be set to determine whether to check only for poles with electricity flow. Can call using only the first two parameters. +function find_nearest_electric_pole(ent, require_supplied, radius, alt_surface, alt_pos) + local nearest = nil + local retry = retry or 0 + local min_dist = 99999 + local poles = nil + local require_supplied = require_supplied or false + local radius = radius or 10 + local surface = nil + local pos = nil + if ent ~= nil and ent.valid then + surface = ent.surface + pos = ent.position + else + surface = alt_surface + pos = alt_pos + end + + --Scan nearby for electric poles, expand radius if not successful + local poles = surface.find_entities_filtered{ type = "electric-pole" , position = pos , radius = radius} + if poles == nil or #poles == 0 then + if radius < 100 then + radius = 100 + return find_nearest_electric_pole(ent, require_supplied, radius, alt_surface, alt_pos) + elseif radius < 1000 then + radius = 1000 + return find_nearest_electric_pole(ent, require_supplied, radius, alt_surface, alt_pos) + elseif radius < 10000 then + radius = 10000 + return find_nearest_electric_pole(ent, require_supplied, radius, alt_surface, alt_pos) + else + return nil, nil --Nothing within 10000 tiles! + end + end + + --Find the nearest among the poles with electric networks + for i,pole in ipairs(poles) do + --Check if the pole's network has power producers + local has_power = get_electricity_satisfaction(pole) > 0 + local dict = pole.electric_network_statistics.output_counts + local network_producers = {} + for name, count in pairs(dict) do + table.insert(network_producers, {name = name, count = count}) + end + local network_producer_count = #network_producers --laterdo test again if this is working, it should pick up even 0.001% satisfaction... + local dist = 0 + if has_power or network_producer_count > 0 or (not require_supplied) then + dist = math.ceil(util.distance(pos, pole.position)) + --Set as nearest if valid + if dist < min_dist then + min_dist = dist + nearest = pole + end + end + end + --Return the nearst found, possibly nil + if nearest == nil then + if radius < 100 then + radius = 100 + return find_nearest_electric_pole(ent, require_supplied, radius, alt_surface, alt_pos) + elseif radius < 1000 then + radius = 1000 + return find_nearest_electric_pole(ent, require_supplied, radius, alt_surface, alt_pos) + elseif radius < 10000 then + radius = 10000 + return find_nearest_electric_pole(ent, require_supplied, radius, alt_surface, alt_pos) + else + return nil, nil --Nothing within 10000 tiles! + end + end + rendering.draw_circle{color = {1, 1, 0}, radius = 2, width = 2, target = nearest.position, surface = nearest.surface, time_to_live = 60} + return nearest, min_dist +end + + +--Returns an info string on the nearest supplied electric pole for this entity. +function report_nearest_supplied_electric_pole(ent) + local result = "" + local pole, dist = find_nearest_electric_pole(ent, true) + local dir = -1 + if pole ~= nil then + dir = get_direction_of_that_from_this(pole.position,ent.position) + result = "The nearest powered electric pole is " .. dist .. " tiles to the " .. direction_lookup(dir) + else + result = "And there are no powered electric poles within ten thousand tiles. Generators may be out of energy." + end + return result +end + + +--Reports which part of the selected entity has the cursor. E.g. southwest corner, center... +function get_entity_part_at_cursor(pindex) + --First check if there is an entity at the cursor + local p = game.get_player(pindex) + local x = players[pindex].cursor_pos.x + local y = players[pindex].cursor_pos.y + local ents = p.surface.find_entities_filtered{position = {x = x,y = y}} + local north_same = false + local south_same = false + local east_same = false + local west_same = false + local location = nil + if #ents > 0 then + --Report which part of the entity the cursor covers. + rendering.draw_circle{color = {1, 0.0, 0.5},radius = 0.1,width = 2,target = {x = x+0 ,y = y-1}, surface = p.surface, time_to_live = 30} + rendering.draw_circle{color = {1, 0.0, 0.5},radius = 0.1,width = 2,target = {x = x+0 ,y = y+1}, surface = p.surface, time_to_live = 30} + rendering.draw_circle{color = {1, 0.0, 0.5},radius = 0.1,width = 2,target = {x = x-1 ,y = y-0}, surface = p.surface, time_to_live = 30} + rendering.draw_circle{color = {1, 0.0, 0.5},radius = 0.1,width = 2,target = {x = x+1 ,y = y-0}, surface = p.surface, time_to_live = 30} + + local ent_north = p.surface.find_entities_filtered{position = {x = x,y = y-1}} + if #ent_north > 0 and ent_north[1].unit_number == ents[1].unit_number then north_same = true end + local ent_south = p.surface.find_entities_filtered{position = {x = x,y = y+1}} + if #ent_south > 0 and ent_south[1].unit_number == ents[1].unit_number then south_same = true end + local ent_east = p.surface.find_entities_filtered{position = {x = x+1,y = y}} + if #ent_east > 0 and ent_east[1].unit_number == ents[1].unit_number then east_same = true end + local ent_west = p.surface.find_entities_filtered{position = {x = x-1,y = y}} + if #ent_west > 0 and ent_west[1].unit_number == ents[1].unit_number then west_same = true end + + if north_same and south_same then + if east_same and west_same then + location = "center" + elseif east_same and not west_same then + location = "west edge" + elseif not east_same and west_same then + location = "east edge" + elseif not east_same and not west_same then + location = "middle" + end + elseif north_same and not south_same then + if east_same and west_same then + location = "south edge" + elseif east_same and not west_same then + location = "southwest corner" + elseif not east_same and west_same then + location = "southeast corner" + elseif not east_same and not west_same then + location = "south tip" + end + elseif not north_same and south_same then + if east_same and west_same then + location = "north edge" + elseif east_same and not west_same then + location = "northwest corner" + elseif not east_same and west_same then + location = "northeast corner" + elseif not east_same and not west_same then + location = "north tip" + end + elseif not north_same and not south_same then + if east_same and west_same then + location = "middle" + elseif east_same and not west_same then + location = "west tip" + elseif not east_same and west_same then + location = "east tip" + elseif not east_same and not west_same then + location = "center" + end + end + end + return location +end + +--Tries to equip a stack. For now called only for a stack in hand when the only the inventory is open. +function equip_it(stack,pindex) + local message = "no message" + + if stack == nil or not stack.valid_for_read or not stack.valid then + return "Nothing in hand to equip." + end + + if stack.is_armor then + local armor = game.get_player(pindex).get_inventory(defines.inventory.character_armor) + if armor.is_empty() then + message = " Equipped " .. stack.name + else + message = " Equipped " .. stack.name .. " and took in hand " .. armor[1].name + end + stack.swap_stack(armor[1]) + elseif stack.type == "gun" then + --Equip gun ("arms") + local gun_inv = game.get_player(pindex).get_inventory(defines.inventory.character_guns) + if gun_inv.can_insert(stack) then + local inserted = gun_inv.insert(stack) + message = " Equipped " .. stack.name + stack.count = stack.count - inserted + else + if gun_inv.count_empty_stacks() == 0 then + message = "All gun slots full." + else + message = "Cannot insert " .. stack.name + end + end + elseif stack.type == "ammo" then + --Equip ammo + local ammo_inv = game.get_player(pindex).get_inventory(defines.inventory.character_ammo) + if ammo_inv.can_insert(stack) then + local inserted = ammo_inv.insert(stack) + message = "Reloaded with " .. stack.name + stack.count = stack.count - inserted + else + if ammo_inv.count_empty_stacks() == 0 then + message = "All gun slots full." + else + message = "Cannot insert " .. stack.name + end + end + elseif stack.prototype.place_as_equipment_result ~= nil then + --Equip equipment ("gear") + local armor_inv = game.get_player(pindex).get_inventory(defines.inventory.character_armor) + if armor_inv.is_empty() then + return "Equipment requires armor with an equipment grid." + end + if armor_inv[1].grid == nil or not armor_inv[1].grid.valid then + return "Equipment requires armor with an equipment grid." + end + local grid = armor_inv[1].grid + --Iterate across the whole grid, trying to place the item. + local placed = nil + for i = 0, grid.width-1, 1 do + for j = 0, grid.height-1, 1 do + placed = grid.put{name = stack.name, position = {i,j}, by_player = pindex} + if placed ~= nil then + break + end + end + if placed ~= nil then + break + end + end + local slots_left = count_empty_equipment_slots(grid) + if placed ~= nil then + message = "Equipped " .. stack.name .. ", " .. slots_left .. " empty slots remaining." + stack.count = stack.count - 1 + else + --Check if the grid is full + if slots_left == 0 then + message = "All armor equipment slots are full." + else + message = "This equipment does not fit in the remaining ".. slots_left .. " slots." + end + end + else + message = stack.name .. " cannot be equipped. " + end + + return message +end + + +function count_empty_equipment_slots(grid) + local slots_left = 0 + for i = 0, grid.width-1, 1 do + for j = 0, grid.height-1, 1 do + local check = grid.get({i,j}) + if check == nil then + slots_left = slots_left + 1 + end + end + end + return slots_left +end + +--Returns info on weapons and ammo +function read_weapons_and_ammo(pindex) + local guns_inv = game.get_player(pindex).get_inventory(defines.inventory.character_guns) + local ammo_inv = game.get_player(pindex).get_inventory(defines.inventory.character_ammo) + local guns_count = #guns_inv - guns_inv.count_empty_stacks() + local ammos_count = #ammo_inv - ammo_inv.count_empty_stacks() + local result = "Weapons, " + + for i = 1, guns_count, 1 do + if i > 1 then + result = result .. " and " + end + result = result .. guns_inv[i].name + if ammo_inv[i] ~= nil and ammo_inv[i].valid and ammo_inv[i].valid_for_read then + result = result .. " with " .. ammo_inv[i].count .. " " .. ammo_inv[i].name .. "s, " + else + result = result .. " with no ammunition, " + end + end + if guns_count == 0 then + result = " No weapons equipped." + end + + return result +end + +--Reload all ammo possible from the inventory. Existing stacks have priority over fuller stacks. +function reload_weapons(pindex) + local ammo_inv = game.get_player(pindex).get_inventory(defines.inventory.character_ammo) + local main_inv = game.get_player(pindex).get_inventory(defines.inventory.character_main) + --Apply an inventory transfer to the ammo inventory. + local res, full = transfer_inventory{from = main_inv, to = ammo_inv} + + local result = "Reloaded weapons with any available ammunition. "--laterdo fail condition message, and maybe add sound? + return result +end + +--Move all weapons and ammo back to inventory +function remove_weapons_and_ammo(pindex) + local guns_inv = game.get_player(pindex).get_inventory(defines.inventory.character_guns) + local ammo_inv = game.get_player(pindex).get_inventory(defines.inventory.character_ammo) + local main_inv = game.get_player(pindex).get_inventory(defines.inventory.character_main) + local guns_count = #guns_inv - guns_inv.count_empty_stacks() + local ammos_count = #ammo_inv - ammo_inv.count_empty_stacks() + local expected_remove_count = guns_count + ammos_count + local resulted_remove_count = 0 + local message = "" + + --Remove all ammo + for i = 1, ammos_count, 1 do + if main_inv.can_insert(ammo_inv[i]) then + local inserted = main_inv.insert(ammo_inv[i])--laterdo recover the uninserted if cannot insert (which is only when inv full) or maybe fails to remove anyway? + resulted_remove_count = resulted_remove_count + math.ceil(ammo_inv.remove(ammo_inv[i]) / 1000 )--we just want to count stacks + end + end + + --Remove all guns + for i = 1, guns_count, 1 do + if main_inv.can_insert(guns_inv[i]) then + local inserted = main_inv.insert(guns_inv[i])--laterdo recover the uninserted if cannot insert (which is only when inv full) or maybe fails to remove anyway? + resulted_remove_count = resulted_remove_count + math.ceil(guns_inv.remove(guns_inv[i]) / 1000)--we just want to count stacks + end + end + + message = "Collected " .. resulted_remove_count .. " of " .. expected_remove_count .. " item stacks," + if game.get_player(pindex).get_main_inventory().count_empty_stacks() == 0 then + message = message .. " Inventory full. " + end + + return message +end + +--Read armor stats such as type and bonuses +function read_armor_stats(pindex) + local armor_inv = game.get_player(pindex).get_inventory(defines.inventory.character_armor) + local result = "" + if armor_inv.is_empty() then + return "No armor equipped." + end + if armor_inv[1].grid == nil or not armor_inv[1].grid.valid then + return armor_inv[1].name .. " equipped, with no equipment grid." + end + --Armor with Equipment + result = armor_inv[1].name .. " equipped, " + local grid = armor_inv[1].grid + if grid.count() == 0 then + return result .. " no armor equipment installed. " + end + --Read shield level + if grid.max_shield > 0 then + if grid.shield == grid.max_shield then + result = " shields full, " + else + result = result .. " shields at " .. math.ceil(100 * grid.shield / grid.max_shield) .. " percent, " + end + end + --Read battery level + if grid.battery_capacity > 0 then + if grid.available_in_batteries == grid.battery_capacity then + result = result .. " batteries full, " + elseif grid.available_in_batteries == 0 then + result = result .. " batteries empty " + else + result = result .. " batteries at " .. math.ceil(100 * grid.available_in_batteries / grid.battery_capacity) .. " percent, " + end + else + result = result .. " no batteries, " + end + --Energy Producers + if grid.generator_energy > 0 or grid.max_solar_energy > 0 then + result = result .. " generating " + if grid.generator_energy > 0 then + result = result .. get_power_string(grid.generator_energy*60) .. " nonstop, " + end + if grid.max_solar_energy > 0 then + result = result .. get_power_string(grid.max_solar_energy*60) .. " at daytime, " + end + end + + --Movement bonus + if grid.count("exoskeleton-equipment") > 0 then + result = result .. " movement bonus " .. grid.count("exoskeleton-equipment") * 30 .. " percent for " .. get_power_string(grid.count("exoskeleton-equipment")*200000) + end + + return result +end + +--List armor equipment +function read_equipment_list(pindex) + local armor_inv = game.get_player(pindex).get_inventory(defines.inventory.character_armor) + local result = "" + if armor_inv.is_empty() then + return "No armor equipped." + end + if armor_inv[1].grid == nil or not armor_inv[1].grid.valid then + return "No equipment grid." + end + --Armor with Equipment + result = "Equipped, " + local grid = armor_inv[1].grid + if grid.equipment == nil or grid.equipment == {} then + return " No armor equipment installed. " + end + --Read Equipment List + result = "Equipped, " + local contents = grid.get_contents() + local itemtable = {} + for name, count in pairs(contents) do + table.insert(itemtable, {name = name, count = count}) + end + if #itemtable == 0 then + result = result .. " nothing, " + else + for i = 1, #itemtable, 1 do + result = result .. itemtable[i].count .. " " .. itemtable[i].name .. ", " + end + end + + result = result .. count_empty_equipment_slots(grid) .. " empty slots remaining " + + return result +end + +--Remove equipment and then armor. laterdo "inv full" checks +function remove_equipment_and_armor(pindex) + local armor_inv = game.get_player(pindex).get_inventory(defines.inventory.character_armor) + local result = "" + if armor_inv.is_empty() then + return "No armor." + end + + local grid = armor_inv[1].grid + if grid ~= nil and grid.valid then + local e_count = grid.count() + --Take all items + for i = 0, grid.width-1, 1 do + for j = 0, grid.height-1, 1 do + local check = grid.get({i,j}) + local inv = game.get_player(pindex).get_main_inventory() + if check ~= nil and inv.can_insert({name = check.name}) then + inv.insert({name = check.name}) + grid.take{position = {i,j}} + end + end + end + result = "Collected " .. e_count - grid.count() .. " of " .. e_count .. " items, " + end + + --Remove armor + if game.get_player(pindex).get_inventory(defines.inventory.character_main).count_empty_stacks() == 0 then + return "Inventory full." + else + result = result .. "removed " .. armor_inv[1].name + game.get_player(pindex).clear_cursor() + local stack2 = game.get_player(pindex).cursor_stack + stack2.swap_stack(armor_inv[1]) + game.get_player(pindex).clear_cursor() + return result + end + + return result +end + + + +script.on_event("shift-r", function(event) + pindex = event.player_index + if not check_for_player(pindex) then + return + end + if players[pindex].menu == "inventory" then + --Reload weapons + local result = reload_weapons(pindex) + --game.get_player(pindex).print(result) + printout(result,pindex) + end +end +) + +script.on_event("control-shift-r", function(event) + pindex = event.player_index + if not check_for_player(pindex) then + return + end + if players[pindex].menu == "inventory" then + local result = remove_weapons_and_ammo(pindex) + --game.get_player(pindex).print(result) + printout(result,pindex) + end +end +) + + +--Set the input priority or the output priority or filter for a splitter +function set_splitter_priority(splitter, is_input, is_left, filter_item_stack, clear) + local clear = clear or false + local result = "no message" + local filter = splitter.splitter_filter + + if clear then + splitter.splitter_filter = nil + filter = splitter.splitter_filter + result = "Cleared splitter filter" + splitter.splitter_output_priority = "none" + elseif filter_item_stack ~= nil and filter_item_stack.valid_for_read then + splitter.splitter_filter = filter_item_stack.prototype + filter = splitter.splitter_filter + result = "filter set to " .. filter_item_stack.name + if splitter.splitter_output_priority == "none" then + splitter.splitter_output_priority = "left" + result = result .. ", from the left" + end + elseif is_input and is_left then + if splitter.splitter_input_priority == "left" then + splitter.splitter_input_priority = "none" + result = "equal input priority" + else + splitter.splitter_input_priority = "left" + result = "left input priority" + end + elseif is_input and not is_left then + if splitter.splitter_input_priority == "right" then + splitter.splitter_input_priority = "none" + result = "equal input priority" + else + splitter.splitter_input_priority = "right" + result = "right input priority" + end + elseif not is_input and is_left then + if splitter.splitter_output_priority == "left" then + if filter == nil then + splitter.splitter_output_priority = "none" + result = "equal output priority" + else + result = "left filter output" + end + else + if filter == nil then + splitter.splitter_output_priority = "left" + result = "left output priority" + else + splitter.splitter_output_priority = "left" + result = "left filter output" + end + end + elseif not is_input and not is_left then + if splitter.splitter_output_priority == "right" then + if filter == nil then + splitter.splitter_output_priority = "none" + result = "equal output priority" + else + result = "right filter output" + end + else + if filter == nil then + splitter.splitter_output_priority = "right" + result = "right output priority" + else + splitter.splitter_output_priority = "right" + result = "right filter output" + end + end + else + result = "Splitter config error" + end + + return result +end + + +script.on_event("shift-left", function(event) + pindex = event.player_index + if not check_for_player(pindex) then + return + end + local ent = players[pindex].tile.ents[1] + if ent == nil or not ent.valid then + return + elseif ent.name == "splitter" then + local result = set_splitter_priority(ent, true, true, nil) + printout(result,pindex) + end +end +) + +script.on_event("shift-right", function(event) + pindex = event.player_index + if not check_for_player(pindex) then + return + end + local ent = players[pindex].tile.ents[1] + if ent == nil or not ent.valid then + return + elseif ent.name == "splitter" then + local result = set_splitter_priority(ent, true, false, nil) + printout(result,pindex) + end +end +) + +function rotate_90(dir) + return (dir + dirs.east) % (2 * dirs.south) +end + +function rotate_180(dir) + return (dir + dirs.south) % (2 * dirs.south) +end + diff --git a/mods/FactorioAccess/data.lua b/mods/FactorioAccess/data.lua index abd49c2f..8b6cbfb8 100644 --- a/mods/FactorioAccess/data.lua +++ b/mods/FactorioAccess/data.lua @@ -17,7 +17,19 @@ resource_map_node["picture"] = { direction_count = 1 } +--Changes to Vanilla Objects (Mostly removal of collisions with the player) +local pipe = data.raw.pipe["pipe"] +pipe.collision_mask = {"object-layer", "floor-layer", "water-tile"} +local pipe_to_ground = data.raw["pipe-to-ground"]["pipe-to-ground"] +pipe_to_ground.collision_mask = {"object-layer", "floor-layer", "water-tile"} + +local small_electric_pole = data.raw["electric-pole"]["small-electric-pole"] +small_electric_pole.collision_mask = {"object-layer", "floor-layer", "water-tile"} + + +local medium_electric_pole = data.raw["electric-pole"]["medium-electric-pole"] +medium_electric_pole.collision_mask = {"object-layer", "floor-layer", "water-tile"} data:extend({ @@ -142,6 +154,12 @@ data:extend({ key_sequence = "J", consuming = "none" }, +{ + type = "custom-input", + name = "shift-j", + key_sequence = "SHIFT + J", + consuming = "none" +}, { type = "custom-input", name = "teleport-to-cursor", @@ -173,7 +191,7 @@ data:extend({ type = "custom-input", name = "scan-up", key_sequence = "PAGEUP", - alternative_key_sequence = "UP", + --alternative_key_sequence = "UP", consuming = "none" }, @@ -181,7 +199,7 @@ data:extend({ type = "custom-input", name = "scan-down", key_sequence = "PAGEDOWN", - alternative_key_sequence = "DOWN", + --alternative_key_sequence = "DOWN", consuming = "none" }, @@ -230,6 +248,20 @@ data:extend({ consuming = "none" }, +{ + type = "custom-input", + name = "up-arrow", + key_sequence = "UP", + consuming = "none" +}, + +{ + type = "custom-input", + name = "down-arrow", + key_sequence = "DOWN", + consuming = "none" +}, + { type = "custom-input", name = "scan-category-up", @@ -434,6 +466,20 @@ data:extend({ consuming = "none" }, +{ + type = "custom-input", + name = "mine-group", + key_sequence = "SHIFT + X", + consuming = "none" +}, + +{ + type = "custom-input", + name = "control-x", + key_sequence = "CONTROL + X", + consuming = "none" +}, + { type = "custom-input", name = "switch-menu", @@ -508,6 +554,27 @@ data:extend({ consuming = "game-only" }, +{ + type = "custom-input", + name = "shift-r", + key_sequence = "SHIFT + R", + consuming = "none" +}, + +{ + type = "custom-input", + name = "control-r", + key_sequence = "CONTROL + R", + consuming = "none" +}, + +{ + type = "custom-input", + name = "control-shift-r", + key_sequence = "CONTROL + SHIFT + R", + consuming = "none" +}, + { type = "custom-input", name = "prompt", @@ -610,6 +677,62 @@ data:extend({ name = "open-structure-travel", key_sequence = "CONTROL + S", consuming = "none" +}, + +{ + type = "custom-input", + name = "g-key", + key_sequence = "G", + consuming = "none" +}, + +{ + type = "custom-input", + name = "shift-g-key", + key_sequence = "SHIFT + G", + consuming = "none" +}, + +{ + type = "custom-input", + name = "control-g-key", + key_sequence = "CONTROL + G", + consuming = "none" +}, + +{ + type = "custom-input", + name = "control-shift-g-key", + key_sequence = "CONTROL + SHIFT + G", + consuming = "none" +}, + +{ + type = "custom-input", + name = "control-left", + key_sequence = "CONTROL + LEFT", + consuming = "none" +}, + +{ + type = "custom-input", + name = "control-right", + key_sequence = "CONTROL + RIGHT", + consuming = "none" +}, + +{ + type = "custom-input", + name = "shift-left", + key_sequence = "SHIFT + LEFT", + consuming = "none" +}, + +{ + type = "custom-input", + name = "shift-right", + key_sequence = "SHIFT + RIGHT", + consuming = "none" } }) \ No newline at end of file diff --git a/mods/FactorioAccess/rails-and-trains.lua b/mods/FactorioAccess/rails-and-trains.lua new file mode 100644 index 00000000..497cecb0 --- /dev/null +++ b/mods/FactorioAccess/rails-and-trains.lua @@ -0,0 +1,3442 @@ +dirs = defines.direction + +--Key information about rail units. +function rail_ent_info(pindex, ent, description) + local result = "" + local is_end_rail = false + local is_horz_or_vert = false + + --Check if end rail: The rail is at the end of its segment and is also not connected to another rail + is_end_rail, end_rail_dir, build_comment = check_end_rail(ent,pindex) + if is_end_rail then + --Further check if it is a single rail + if build_comment == "single rail" then + result = result .. "Single " + end + result = result .. "End rail " + else + result = result .. "Rail " + end + + --Explain the rail facing direction + if ent.name == "straight-rail" and is_end_rail then + result = result .. " straight " + if end_rail_dir == dirs.north then + result = result .. " facing North " + elseif end_rail_dir == dirs.northeast then + result = result .. " facing Northeast " + elseif end_rail_dir == dirs.east then + result = result .. " facing East " + elseif end_rail_dir == dirs.southeast then + result = result .. " facing Southeast " + elseif end_rail_dir == dirs.south then + result = result .. " facing South " + elseif end_rail_dir == dirs.southwest then + result = result .. " facing Southwest " + elseif end_rail_dir == dirs.west then + result = result .. " facing West " + elseif end_rail_dir == dirs.northwest then + result = result .. " facing Northwest " + end + + elseif ent.name == "straight-rail" and is_end_rail == false then + if ent.direction == dirs.north or ent.direction == dirs.south then --always reports 0 it seems + result = result .. " vertical " + is_horz_or_vert = true + elseif ent.direction == dirs.east or ent.direction == dirs.west then --always reports 2 it seems + result = result .. " horizontal " + is_horz_or_vert = true + + elseif ent.direction == dirs.northeast then + result = result .. " on falling diagonal left " + elseif ent.direction == dirs.southwest then + result = result .. " on falling diagonal right " + elseif ent.direction == dirs.southeast then + result = result .. " on rising diagonal left " + elseif ent.direction == dirs.northwest then + result = result .. " on rising diagonal right " + end + + elseif ent.name == "curved-rail" and is_end_rail == true then + result = result .. " curved " + if end_rail_dir == dirs.north then + result = result .. " facing North " + elseif end_rail_dir == dirs.northeast then + result = result .. " facing Northeast " + elseif end_rail_dir == dirs.east then + result = result .. " facing East " + elseif end_rail_dir == dirs.southeast then + result = result .. " facing Southeast " + elseif end_rail_dir == dirs.south then + result = result .. " facing South " + elseif end_rail_dir == dirs.southwest then + result = result .. " facing Southwest " + elseif end_rail_dir == dirs.west then + result = result .. " facing West " + elseif end_rail_dir == dirs.northwest then + result = result .. " facing Northwest " + end + + elseif ent.name == "curved-rail" and is_end_rail == false then + result = result .. " curved in direction " + if ent.direction == dirs.north then + result = result .. "0 with ends facing south and falling diagonal " + elseif ent.direction == dirs.northeast then + result = result .. "1 with ends facing south and rising diagonal " + elseif ent.direction == dirs.east then + result = result .. "2 with ends facing west and rising diagonal " + elseif ent.direction == dirs.southeast then + result = result .. "3 with ends facing west and falling diagonal " + elseif ent.direction == dirs.south then + result = result .. "4 with ends facing north and falling diagonal " + elseif ent.direction == dirs.southwest then + result = result .. "5 with ends facing north and rising diagonal " + elseif ent.direction == dirs.west then + result = result .. "6 with ends facing east and rising diagonal " + elseif ent.direction == dirs.northwest then + result = result .. "7 with ends facing east and falling diagonal " + end + end + + --Check if at junction: The rail has at least 3 connections + local connection_count = count_rail_connections(ent) + if connection_count > 2 then + result = result .. ", junction, " + end + + --Check if it has rail signals + local chain_s_count = 0 + local rail_s_count = 0 + local signals = ent.surface.find_entities_filtered{position = ent.position, radius = 2, name = "rail-chain-signal"} + for i,s in ipairs(signals) do + chain_s_count = chain_s_count + 1 + end + + signals = ent.surface.find_entities_filtered{position = ent.position, radius = 2, name = "rail-signal"} + for i,s in ipairs(signals) do + rail_s_count = rail_s_count + 1 + end + + if chain_s_count + rail_s_count == 0 then + --(nothing) + elseif chain_s_count + rail_s_count == 1 then + result = result .. " with one signal, " + elseif chain_s_count + rail_s_count == 2 then + result = result .. " with a pair of signals, " + elseif chain_s_count + rail_s_count > 2 then + result = result .. " with many signals, " + end + + --Check if there is a train stop nearby, to announce station spaces + if is_horz_or_vert then + local stop = nil + local segment_ent_1 = ent.get_rail_segment_entity(defines.rail_direction.front, false) + local segment_ent_2 = ent.get_rail_segment_entity(defines.rail_direction.back, false) + if segment_ent_1 ~= nil and segment_ent_1.name == "train-stop" and util.distance(ent.position, segment_ent_1.position) < 45 then + stop = segment_ent_1 + elseif segment_ent_2 ~= nil and segment_ent_2.name == "train-stop" and util.distance(ent.position, segment_ent_2.position) < 45 then + stop = segment_ent_2 + end + if stop == nil then + return result + end + + --Check if this rail is in the correct direction of the train stop + local rail_dir_1 = segment_ent_1 == stop + local rail_dir_2 = segment_ent_2 == stop + local stop_dir = stop.connected_rail_direction + local pairing_correct = false + + if rail_dir_1 and stop_dir == defines.rail_direction.front then + --result = result .. ", pairing 1, " + pairing_correct = true + elseif rail_dir_1 and stop_dir == defines.rail_direction.back then + --result = result .. ", pairing 2, " + pairing_correct = false + elseif rail_dir_2 and stop_dir == defines.rail_direction.front then + --result = result .. ", pairing 3, " + pairing_correct = false + elseif rail_dir_2 and stop_dir == defines.rail_direction.back then + --result = result .. ", pairing 4, " + pairing_correct = true + else + result = result .. ", pairing error, " + pairing_correct = false + end + + if not pairing_correct then + return result + end + + --Count distance and determine railcar slot + local dist = util.distance(ent.position, stop.position) + --result = result .. " stop distance " .. dist + if dist < 2 then + result = result .. " station locomotive space front" + elseif dist < 3 then + result = result .. " station locomotive space middle" + elseif dist < 5 then + result = result .. " station locomotive space middle" + elseif dist < 7 then + result = result .. " station locomotive end and gap 1" + elseif dist < 9 then + result = result .. " station space 1 front" + elseif dist < 11 then + result = result .. " station space 1 middle" + elseif dist < 13 then + result = result .. " station space 1 end" + elseif dist < 15 then + result = result .. " station gap 2 and station space 2 front" + elseif dist < 17 then + result = result .. " station space 2 middle" + elseif dist < 19 then + result = result .. " station space 2 middle" + elseif dist < 21 then + result = result .. " station space 2 end and gap 3" + elseif dist < 23 then + result = result .. " station space 3 front" + elseif dist < 25 then + result = result .. " station space 3 middle" + elseif dist < 27 then + result = result .. " station space 3 end" + elseif dist < 29 then + result = result .. " station gap 4 and station space 4 front" + elseif dist < 31 then + result = result .. " station space 4 middle" + elseif dist < 33 then + result = result .. " station space 4 middle" + elseif dist < 35 then + result = result .. " station space 4 end and gap 5" + elseif dist < 37 then + result = result .. " station space 5 front" + elseif dist < 39 then + result = result .. " station space 5 middle" + elseif dist < 41 then + result = result .. " station space 5 end" + elseif dist < 43 then + result = result .. " station gap 6 and station space 6 front" + elseif dist < 45 then + result = result .. " station space 6 middle" + end + end + + if is_intersection_rail(ent, pindex) then + result = result .. ", intersection " + end + + return result +end + + +--Determines how many connections a rail has +function count_rail_connections(ent) + local front_left_rail,r_dir_back,c_dir_back = ent.get_connected_rail{ rail_direction = defines.rail_direction.front,rail_connection_direction = defines.rail_connection_direction.left} + local front_right_rail,r_dir_back,c_dir_back = ent.get_connected_rail{rail_direction = defines.rail_direction.front,rail_connection_direction = defines.rail_connection_direction.right} + local back_left_rail,r_dir_back,c_dir_back = ent.get_connected_rail{ rail_direction = defines.rail_direction.back,rail_connection_direction = defines.rail_connection_direction.left} + local back_right_rail,r_dir_back,c_dir_back = ent.get_connected_rail{rail_direction = defines.rail_direction.back,rail_connection_direction = defines.rail_connection_direction.right} + local next_rail,r_dir_back,c_dir_back = ent.get_connected_rail{rail_direction = defines.rail_direction.front, rail_connection_direction = defines.rail_connection_direction.straight} + local prev_rail,r_dir_back,c_dir_back = ent.get_connected_rail{rail_direction = defines.rail_direction.back, rail_connection_direction = defines.rail_connection_direction.straight} + + local connection_count = 0 + if next_rail ~= nil then + connection_count = connection_count + 1 + end + if prev_rail ~= nil then + connection_count = connection_count + 1 + end + if front_left_rail ~= nil then + connection_count = connection_count + 1 + end + if front_right_rail ~= nil then + connection_count = connection_count + 1 + end + if back_left_rail ~= nil then + connection_count = connection_count + 1 + end + if back_right_rail ~= nil then + connection_count = connection_count + 1 + end + return connection_count +end + + +--Determines if an entity is an end rail. Returns boolean is_end_rail, integer end rail direction, and string comment for errors. +function check_end_rail(check_rail, pindex) + local is_end_rail = false + local dir = -1 + local comment = "Check function error." + + --Check if the entity is a rail + if check_rail == nil then + is_end_rail = false + comment = "Nil." + return is_end_rail, -1, comment + end + if not check_rail.valid then + is_end_rail = false + comment = "Invalid." + return is_end_rail, -1, comment + end + if not (check_rail.name == "straight-rail" or check_rail.name == "curved-rail") then + is_end_rail = false + comment = "Not a rail." + return is_end_rail, -1, comment + end + + --Check if end rail: The rail is at the end of its segment and has only 1 connection. + end_rail_1, end_dir_1 = check_rail.get_rail_segment_end(defines.rail_direction.front) + end_rail_2, end_dir_2 = check_rail.get_rail_segment_end(defines.rail_direction.back) + local connection_count = count_rail_connections(check_rail) + if (check_rail.unit_number == end_rail_1.unit_number or check_rail.unit_number == end_rail_2.unit_number) and connection_count < 2 then + --End rail confirmed, get direction + is_end_rail = true + comment = "End rail confirmed." + if connection_count == 0 then + comment = "single rail" + end + if check_rail.name == "straight-rail" then + local next_rail_straight,temp1,temp2 = check_rail.get_connected_rail{rail_direction = defines.rail_direction.front, + rail_connection_direction = defines.rail_connection_direction.straight} + local next_rail_left,temp1,temp2 = check_rail.get_connected_rail{rail_direction = defines.rail_direction.front, + rail_connection_direction = defines.rail_connection_direction.left} + local next_rail_right,temp1,temp2 = check_rail.get_connected_rail{rail_direction = defines.rail_direction.front, + rail_connection_direction = defines.rail_connection_direction.right} + local next_rail = nil + if next_rail_straight ~= nil then + next_rail = next_rail_straight + elseif next_rail_left ~= nil then + next_rail = next_rail_left + elseif next_rail_right ~= nil then + next_rail = next_rail_right + end + local prev_rail_straight,temp1,temp2 = check_rail.get_connected_rail{rail_direction = defines.rail_direction.back, + rail_connection_direction = defines.rail_connection_direction.straight} + local prev_rail_left,temp1,temp2 = check_rail.get_connected_rail{rail_direction = defines.rail_direction.back, + rail_connection_direction = defines.rail_connection_direction.left} + local prev_rail_right,temp1,temp2 = check_rail.get_connected_rail{rail_direction = defines.rail_direction.back, + rail_connection_direction = defines.rail_connection_direction.right} + local prev_rail = nil + if prev_rail_straight ~= nil then + prev_rail = prev_rail_straight + elseif prev_rail_left ~= nil then + prev_rail = prev_rail_left + elseif prev_rail_right ~= nil then + prev_rail = prev_rail_right + end + if check_rail.direction == dirs.north and next_rail == nil then + dir = dirs.north + elseif check_rail.direction == dirs.north and prev_rail == nil then + dir = dirs.south + elseif check_rail.direction == dirs.northeast and next_rail == nil then + dir = dirs.northwest + elseif check_rail.direction == dirs.northeast and prev_rail == nil then + dir = dirs.southeast + elseif check_rail.direction == dirs.east and next_rail == nil then + dir = dirs.east + elseif check_rail.direction == dirs.east and prev_rail == nil then + dir = dirs.west + elseif check_rail.direction == dirs.southeast and next_rail == nil then + dir = dirs.northeast + elseif check_rail.direction == dirs.southeast and prev_rail == nil then + dir = dirs.southwest + elseif check_rail.direction == dirs.south and next_rail == nil then + dir = dirs.south + elseif check_rail.direction == dirs.south and prev_rail == nil then + dir = dirs.north + elseif check_rail.direction == dirs.southwest and next_rail == nil then + dir = dirs.southeast + elseif check_rail.direction == dirs.southwest and prev_rail == nil then + dir = dirs.northwest + elseif check_rail.direction == dirs.west and next_rail == nil then + dir = dirs.west + elseif check_rail.direction == dirs.west and prev_rail == nil then + dir = dirs.east + elseif check_rail.direction == dirs.northwest and next_rail == nil then + dir = dirs.southwest + elseif check_rail.direction == dirs.northwest and prev_rail == nil then + dir = dirs.northeast + else + --This line should not be reachable + is_end_rail = false + comment = "Rail direction error." + return is_end_rail, -3, comment + end + elseif check_rail.name == "curved-rail" then + local next_rail,r_dir_back,c_dir_back = check_rail.get_connected_rail{rail_direction = defines.rail_direction.front, + rail_connection_direction = defines.rail_connection_direction.straight} + local prev_rail,r_dir_back,c_dir_back = check_rail.get_connected_rail{rail_direction = defines.rail_direction.back, + rail_connection_direction = defines.rail_connection_direction.straight} + if check_rail.direction == dirs.north and next_rail == nil then + dir = dirs.south + elseif check_rail.direction == dirs.north and prev_rail == nil then + dir = dirs.northwest + elseif check_rail.direction == dirs.northeast and next_rail == nil then + dir = dirs.south + elseif check_rail.direction == dirs.northeast and prev_rail == nil then + dir = dirs.northeast + elseif check_rail.direction == dirs.east and next_rail == nil then + dir = dirs.west + elseif check_rail.direction == dirs.east and prev_rail == nil then + dir = dirs.northeast + elseif check_rail.direction == dirs.southeast and next_rail == nil then + dir = dirs.west + elseif check_rail.direction == dirs.southeast and prev_rail == nil then + dir = dirs.southeast + elseif check_rail.direction == dirs.south and next_rail == nil then + dir = dirs.north + elseif check_rail.direction == dirs.south and prev_rail == nil then + dir = dirs.southeast + elseif check_rail.direction == dirs.southwest and next_rail == nil then + dir = dirs.north + elseif check_rail.direction == dirs.southwest and prev_rail == nil then + dir = dirs.southwest + elseif check_rail.direction == dirs.west and next_rail == nil then + dir = dirs.east + elseif check_rail.direction == dirs.west and prev_rail == nil then + dir = dirs.southwest + elseif check_rail.direction == dirs.northwest and next_rail == nil then + dir = dirs.east + elseif check_rail.direction == dirs.northwest and prev_rail == nil then + dir = dirs.northwest + else + --This line should not be reachable + is_end_rail = false + comment = "Rail direction error." + return is_end_rail, -3, comment + end + end + else + --Not the end rail + is_end_rail = false + comment = "This rail is not the end rail." + return is_end_rail, -4, comment + end + + return is_end_rail, dir, comment +end + + +--Report more info about a vehicle. For trains, this would include the name, ID, and train state. +function vehicle_info(pindex) + local result = "" + if not game.get_player(pindex).driving then + return "Not in a vehicle." + end + + local vehicle = game.get_player(pindex).vehicle + local train = game.get_player(pindex).vehicle.train + if train == nil then + --This is a type of car or tank. + result = "Driving " .. vehicle.name .. ", " .. fuel_inventory_info(vehicle) + --laterdo**: car info: health, ammo contents, trunk contents + return result + else + --This is a type of locomotive or wagon. + + --Add the train name + result = "On board " .. vehicle.name .. " of train " .. get_train_name(train) .. ", " + + --Add the train state + result = result .. get_train_state_info(train) .. ", " + + --Declare destination if any. Note: Not tested yet. laterdo + if train.path_end_stop ~= nil then + result = result .. " heading to station " .. train.path_end_stop.backer_name .. ", " + -- result = result .. " traveled a distance of " .. train.path.travelled_distance .. " out of " train.path.total_distance " distance, " + end + + --Note that more info and options are found in the train menu + if vehicle.name == "locomotive" then + result = result .. " Press LEFT BRACKET to open the train menu. " + end + return result + end +end + +--Look up and translate the train state. -laterdo better explanations +function get_train_state_info(train) + local train_state_id = train.state + local train_state_text = "" + local state_lookup = into_lookup(defines.train_state) + if train_state_id ~= nil then + train_state_text = state_lookup[train_state_id] + else + train_state_text = "None" + end + return train_state_text +end + +--Look up and translate the signal state. +function get_signal_state_info(signal) + local state_id = 0 + local state_lookup = nil + local state_name = "" + local result = "" + if signal.name == "rail-signal" then + state_id = signal.signal_state + state_lookup = into_lookup(defines.signal_state) + state_name = state_lookup[state_id] + result = state_name + elseif signal.name == "rail-chain-signal" then + state_id = signal.chain_signal_state + state_lookup = into_lookup(defines.chain_signal_state) + state_name = state_lookup[state_id] + result = state_name + if state_name == "none_open" then result = "closed" end + end + return result +end + +--Gets a train's name. The idea is that every locomotive on a train has the same backer name and this is the train's name. If there are multiple names, a warning returned. +function get_train_name(train) + local locos = train.locomotives + local train_name = "" + local multiple_names = false + + if locos == nil then + return "without locomotives" + end + + for i,loco in ipairs(locos["front_movers"]) do + if train_name ~= "" and train_name ~= loco.backer_name then + multiple_names = true + end + train_name = loco.backer_name + end + for i,loco in ipairs(locos["back_movers"]) do + if train_name ~= "" and train_name ~= loco.backer_name then + multiple_names = true + end + train_name = loco.backer_name + end + + if train_name == "" then + return "without a name" + elseif multiple_names then + local oldest_name = resolve_train_name(train) + set_train_name(train,oldest_name) + return oldest_name + else + return train_name + end +end + + +--Sets a train's name. The idea is that every locomotive on a train has the same backer name and this is the train's name. +function set_train_name(train,new_name) + local locos = train.locomotives + if locos == nil then + return false + end + for i,loco in ipairs(locos["front_movers"]) do + loco.backer_name = new_name + end + for i,loco in ipairs(locos["back_movers"]) do + loco.backer_name = new_name + end + return true +end + +--Finds the oldest locomotive and applies its name across the train. Any new loco will be newwer and so the older names will be kept. +function resolve_train_name(train) + local locos = train.locomotives + local oldest_loco = nil + + if locos == nil then + return "without locomotives" + end + + for i,loco in ipairs(locos["front_movers"]) do + if oldest_loco == nil then + oldest_loco = loco + elseif oldest_loco.unit_number > loco.unit_number then + oldest_loco = loco + end + end + for i,loco in ipairs(locos["back_movers"]) do + if oldest_loco == nil then + oldest_loco = loco + elseif oldest_loco.unit_number > loco.unit_number then + oldest_loco = loco + end + end + + if oldest_loco ~= nil then + return oldest_loco.backer_name + else + return "error resolving train name" + end +end + + +--Returns the rail at the end of an input rail's segment. If the input rail is already one end of the segment then it returns the other end. NOT TESTED +function get_rail_segment_other_end(rail) + local end_rail_1, end_dir_1 = rail.get_rail_segment_end(defines.rail_direction.front) --Cannot be nil + local end_rail_2, end_dir_2 = rail.get_rail_segment_end(defines.rail_direction.back) --Cannot be nil + + if rail.unit_number == end_rail_1.unit_number and rail.unit_number ~= end_rail_2.unit_number then + return end_rail_2 + elseif rail.unit_number ~= end_rail_1.unit_number and rail.unit_number == end_rail_2.unit_number then + return end_rail_1 + else + --The other end is either both options or neither, so return any. + return end_rail_1 + end +end + + +--For a rail at the end of its segment, returns the neighboring rail segment's end rail. Respects dir in terms of left/right/straight if it is given, else returns the first found option. NOTE: Not tested individually but worked in combination with other functions. +function get_neighbor_rail_segment_end(rail, con_dir_in) + local dir = con_dir_in or nil + local requested_neighbor_rail_1 = nil + local requested_neighbor_rail_2 = nil + local neighbor_rail,r_dir_back,c_dir_back = nil, nil, nil + + if dir ~= nil then + --Check requested neighbor + requested_neighbor_rail_1, req_dir_1, req_con_dir_1 = rail.get_connected_rail{ rail_direction = defines.rail_direction.front,rail_connection_direction = dir} + requested_neighbor_rail_2, req_dir_2, req_con_dir_2 = rail.get_connected_rail{ rail_direction = defines.rail_direction.back ,rail_connection_direction = dir} + if requested_neighbor_rail_1 ~= nil and not rail.is_rail_in_same_rail_segment_as(requested_neighbor_rail_1) then + return requested_neighbor_rail_1, req_dir_1, req_con_dir_1 + elseif requested_neighbor_rail_2 ~= nil and not rail.is_rail_in_same_rail_segment_as(requested_neighbor_rail_2) then + return requested_neighbor_rail_2, req_dir_2, req_con_dir_2 + else + return nil, nil, nil + end + else + --Try all 6 options until you get any + neighbor_rail,r_dir_back,c_dir_back = rail.get_connected_rail{rail_direction = defines.rail_direction.front, rail_connection_direction = defines.rail_connection_direction.straight} + if neighbor_rail ~= nil and not neighbor_rail.is_rail_in_same_rail_segment_as(rail) then + return neighbor_rail,r_dir_back,c_dir_back + end + + neighbor_rail,r_dir_back,c_dir_back = rail.get_connected_rail{rail_direction = defines.rail_direction.back, rail_connection_direction = defines.rail_connection_direction.straight} + if neighbor_rail ~= nil and not neighbor_rail.is_rail_in_same_rail_segment_as(rail) then + return neighbor_rail,r_dir_back,c_dir_back + end + + neighbor_rail,r_dir_back,c_dir_back = rail.get_connected_rail{ rail_direction = defines.rail_direction.front,rail_connection_direction = defines.rail_connection_direction.left} + if neighbor_rail ~= nil and not neighbor_rail.is_rail_in_same_rail_segment_as(rail) then + return neighbor_rail,r_dir_back,c_dir_back + end + + neighbor_rail,r_dir_back,c_dir_back = rail.get_connected_rail{rail_direction = defines.rail_direction.front,rail_connection_direction = defines.rail_connection_direction.right} + if neighbor_rail ~= nil and not neighbor_rail.is_rail_in_same_rail_segment_as(rail) then + return neighbor_rail,r_dir_back,c_dir_back + end + + neighbor_rail,r_dir_back,c_dir_back = rail.get_connected_rail{ rail_direction = defines.rail_direction.back,rail_connection_direction = defines.rail_connection_direction.left} + if neighbor_rail ~= nil and not neighbor_rail.is_rail_in_same_rail_segment_as(rail) then + return neighbor_rail,r_dir_back,c_dir_back + end + + neighbor_rail,r_dir_back,c_dir_back = rail.get_connected_rail{rail_direction = defines.rail_direction.back,rail_connection_direction = defines.rail_connection_direction.right} + if neighbor_rail ~= nil and not neighbor_rail.is_rail_in_same_rail_segment_as(rail) then + return neighbor_rail,r_dir_back,c_dir_back + end + + return nil, nil, nil + end +end + + +--Reads all rail segment entities around a rail. +--Result 1: A rail or chain signal creates a new segment and is at the end of one of the two segments. +--Result 2: A train creates a new segment and is at the end of one of the two segments. It can be reported twice for FW1 and BACK2 or for FW2 and BACK1. +function read_all_rail_segment_entities(pindex, rail) + local message = "" + local ent_f1 = rail.get_rail_segment_entity(defines.rail_direction.front, true) + local ent_f2 = rail.get_rail_segment_entity(defines.rail_direction.front, false) + local ent_b1 = rail.get_rail_segment_entity(defines.rail_direction.back, true) + local ent_b2 = rail.get_rail_segment_entity(defines.rail_direction.back, false) + + if ent_f1 == nil then + message = message .. "forward 1 is nil, " + elseif ent_f1.name == "train-stop" then + message = message .. "forward 1 is train stop " .. ent_f1.backer_name .. ", " + elseif ent_f1.name == "rail-signal" then + message = message .. "forward 1 is rails signal with signal " .. get_signal_state_info(ent_f1) .. ", " + elseif ent_f1.name == "rail-chain-signal" then + message = message .. "forward 1 is chain signal with signal " .. get_signal_state_info(ent_f1) .. ", " + else + message = message .. "forward 1 is else, " .. ent_f1.name .. ", " + end + + if ent_f2 == nil then + message = message .. "forward 2 is nil, " + elseif ent_f2.name == "train-stop" then + message = message .. "forward 2 is train stop " .. ent_f2.backer_name .. ", " + elseif ent_f2.name == "rail-signal" then + message = message .. "forward 2 is rails signal with signal " .. get_signal_state_info(ent_f2) .. ", " + elseif ent_f2.name == "rail-chain-signal" then + message = message .. "forward 2 is chain signal with signal " .. get_signal_state_info(ent_f2) .. ", " + else + message = message .. "forward 2 is else, " .. ent_f2.name .. ", " + end + + if ent_b1 == nil then + message = message .. "back 1 is nil, " + elseif ent_b1.name == "train-stop" then + message = message .. "back 1 is train stop " .. ent_b1.backer_name .. ", " + elseif ent_b1.name == "rail-signal" then + message = message .. "back 1 is rails signal with signal " .. get_signal_state_info(ent_b1) .. ", " + elseif ent_b1.name == "rail-chain-signal" then + message = message .. "back 1 is chain signal with signal " .. get_signal_state_info(ent_b1) .. ", " + else + message = message .. "back 1 is else, " .. ent_b1.name .. ", " + end + + if ent_b2 == nil then + message = message .. "back 2 is nil, " + elseif ent_b2.name == "train-stop" then + message = message .. "back 2 is train stop " .. ent_b2.backer_name .. ", " + elseif ent_b2.name == "rail-signal" then + message = message .. "back 2 is rails signal with signal " .. get_signal_state_info(ent_b2) .. ", " + elseif ent_b2.name == "rail-chain-signal" then + message = message .. "back 2 is chain signal with signal " .. get_signal_state_info(ent_b2) .. ", " + else + message = message .. "back 2 is else, " .. ent_b2.name .. ", " + end + + printout(message,pindex) + return +end + + +--Gets opposite rail direction +function get_opposite_rail_direction(dir) + if dir == defines.rail_direction.front then + return defines.rail_direction.back + else + return defines.rail_direction.front + end +end + +--Checks if the train is all in one segment, which means the front and back rails are in the same segment. +function train_is_all_in_one_segment(train) + return train.front_rail.is_rail_in_same_rail_segment_as(train.back_rail) +end + + +--[[Returns the leading rail and the direction on it that is "ahead" and the leading stock. This is the direction that the currently boarded locomotive or wagon is facing. +--Checks whether the current locomotive is one of the front or back locomotives and gives leading rail and leading stock accordingly. +--If this is not a locomotive, takes the front as the leading side. +--Checks distances with respect to the front/back stocks of the train +--Does not require any specific position or rotation for any of the stock! +--For the leading rail, the connected rail that is farthest from the leading stock is in the "ahead" direction. +--]] +function get_leading_rail_and_dir_of_train_by_boarded_vehicle(pindex, train) + local leading_rail = nil + local trailing_rail = nil + local leading_stock = nil + local ahead_rail_dir = nil + + local vehicle = game.get_player(pindex).vehicle + local front_rail = train.front_rail + local back_rail = train.back_rail + local locos = train.locomotives + local vehicle_is_a_front_loco = nil + + --Find the leading rail. If any "front" locomotive velocity is positive, the front stock is the one going ahead and its rail is the leading rail. + if vehicle.name == "locomotive" then + --Leading direction is the one this loconotive faces + for i,loco in ipairs(locos["front_movers"]) do + if vehicle.unit_number == loco.unit_number then + vehicle_is_a_front_loco = true + end + end + if vehicle_is_a_front_loco == true then + leading_rail = front_rail + trailing_rail = back_rail + leading_stock = train.front_stock + else + for i,loco in ipairs(locos["back_movers"]) do + if vehicle.unit_number == loco.unit_number then + vehicle_is_a_front_loco = false + end + end + if vehicle_is_a_front_loco == false then + leading_rail = back_rail + trailing_rail = front_rail + leading_stock = train.back_stock + else + --Unexpected place + return nil, -1, nil + end + end + else + --Just assume the front stock is leading + leading_rail = front_rail + trailing_rail = back_rail + leading_stock = train.front_stock + end + + --Error check + if leading_rail == nil then + return nil, -2, nil + end + + --Find the ahead direction. For the leading rail, the connected rail that is farthest from the leading stock is in the "ahead" direction. + --Repurpose the variables named front_rail and back_rail + front_rail = leading_rail.get_connected_rail{ rail_direction = defines.rail_direction.front, rail_connection_direction = defines.rail_connection_direction.straight} + if front_rail == nil then + front_rail = leading_rail.get_connected_rail{ rail_direction = defines.rail_direction.front, rail_connection_direction = defines.rail_connection_direction.left} + end + if front_rail == nil then + front_rail = leading_rail.get_connected_rail{ rail_direction = defines.rail_direction.front, rail_connection_direction = defines.rail_connection_direction.right} + end + if front_rail == nil then + --The leading rail is an end rail at the front direction + return leading_rail, defines.rail_direction.front, leading_stock + end + + back_rail = leading_rail.get_connected_rail{ rail_direction = defines.rail_direction.back, rail_connection_direction = defines.rail_connection_direction.straight} + if back_rail == nil then + back_rail = leading_rail.get_connected_rail{ rail_direction = defines.rail_direction.back, rail_connection_direction = defines.rail_connection_direction.left} + end + if back_rail == nil then + back_rail = leading_rail.get_connected_rail{ rail_direction = defines.rail_direction.back, rail_connection_direction = defines.rail_connection_direction.right} + end + if back_rail == nil then + --The leading rail is an end rail at the back direction + return leading_rail, defines.rail_direction.back, leading_stock + end + + local front_dist = math.abs(util.distance(leading_stock.position, front_rail.position)) + local back_dist = math.abs(util.distance(leading_stock.position, back_rail.position)) + --The connected rail that is farther from the leading stock is in the ahead direction. + if front_dist > back_dist then + return leading_rail, defines.rail_direction.front, leading_stock + else + return leading_rail, defines.rail_direction.back, leading_stock + end +end +--[[ALT:To find the leading rail, checks the velocity sign of any "front-facing" locomotive. + --f any "front" locomotive velocity is positive, the front stock is the one going ahead and its rail is the leading rail. + --if front_facing_loco.speed >= 0 then + -- leading_rail = front_rail + -- leading_stock = train.front_stock + --else + -- leading_rail = back_rail + -- leading_stock = train.back_stock + --end +--]] + + +--Return what is ahead at the end of this rail's segment in this given direction. +--Return the entity, a label, an extra value sometimes, and whether the entity faces the forward direction +function identify_rail_segment_end_object(rail, dir_ahead, accept_only_forward, prefer_back) + local result_entity = nil + local result_entity_label = "" + local result_extra = nil + local result_is_forward = nil + + --Correction: Flip the correct direction ahead for mismatching diagonal rails + if rail.name == "straight-rail" and (rail.direction == dirs.southwest or rail.direction == dirs.northwest) + or rail.name == "curved-rail" and (rail.direction == dirs.north or rail.direction == dirs.northeast or rail.direction == dirs.east or rail.direction == dirs.southeast) then + dir_ahead = get_opposite_rail_direction(dir_ahead) + end + + local segment_last_rail = rail.get_rail_segment_end(dir_ahead) + local entity_ahead = nil + local entity_ahead_forward = rail.get_rail_segment_entity(dir_ahead,false) + local entity_ahead_reverse = rail.get_rail_segment_entity(dir_ahead,true) + + local segment_last_is_end_rail, end_rail_dir, comment = check_end_rail(segment_last_rail, pindex) + local segment_last_neighbor_count = count_rail_connections(segment_last_rail) + + if entity_ahead_forward ~= nil then + entity_ahead = entity_ahead_forward + result_is_forward = true + elseif entity_ahead_reverse ~= nil and accept_only_forward == false then + entity_ahead = entity_ahead_reverse + result_is_forward = false + end + + if prefer_back == true and entity_ahead_reverse ~= nil and accept_only_forward == false then + entity_ahead = entity_ahead_reverse + result_is_forward = false + end + + --When no entity ahead, check if the segment end is an end rail or fork rail? + if entity_ahead == nil then + if segment_last_is_end_rail then + --End rail + result_entity = segment_last_rail + result_entity_label = "end rail" + result_extra = end_rail_dir + return result_entity, result_entity_label, result_extra, result_is_forward + elseif segment_last_neighbor_count > 2 then + --Junction rail + result_entity = segment_last_rail + result_entity_label = "fork split" + result_extra = rail --A rail from the segment "entering" the junction + return result_entity, result_entity_label, result_extra, result_is_forward + else + --The neighbor of the segment end rail is either a fork or an end rail or has an entity instead + neighbor_rail, neighbor_r_dir, neighbor_c_dir = get_neighbor_rail_segment_end(segment_last_rail, nil) + if neighbor_rail == nil then + --This must be a closed loop? + result_entity = nil + result_entity_label = "loop" + result_extra = nil + return result_entity, result_entity_label, result_extra, result_is_forward + elseif count_rail_connections(neighbor_rail) > 2 then + --The neighbor is a forking rail + result_entity = neighbor_rail + result_entity_label = "fork merge" + result_extra = nil + return result_entity, result_entity_label, result_extra, result_is_forward + elseif count_rail_connections(neighbor_rail) == 1 then + --The neighbor is an end rail + local neighbor_is_end_rail, end_rail_dir, comment = check_end_rail(neighbor_rail, pindex) + result_entity = neighbor_rail + result_entity_label = "neighbor end" + result_extra = end_rail_dir + return result_entity, result_entity_label, result_extra, result_is_forward + else + --The neighbor rail should have an entity? + result_entity = segment_last_rail + result_entity_label = "other rail" + result_extra = nil + return result_entity, result_entity_label, result_extra, result_is_forward + end + end + --When entity ahead, check its type + else + if entity_ahead.name == "rail-signal" then + result_entity = entity_ahead + result_entity_label = "rail signal" + result_extra = get_signal_state_info(entity_ahead) + return result_entity, result_entity_label, result_extra, result_is_forward + elseif entity_ahead.name == "rail-chain-signal" then + result_entity = entity_ahead + result_entity_label = "chain signal" + result_extra = get_signal_state_info(entity_ahead) + return result_entity, result_entity_label, result_extra, result_is_forward + elseif entity_ahead.name == "train-stop" then + result_entity = entity_ahead + result_entity_label = "train stop" + result_extra = entity_ahead.backer_name + return result_entity, result_entity_label, result_extra, result_is_forward + else + --This is NOT expected. + result_entity = entity_ahead + result_entity_label = "other entity" + result_extra = "Unidentified " .. entity_ahead.name + return result_entity, result_entity_label, result_extra, result_is_forward + end + end +end + + +--Reads out the nearest railway object ahead with relevant details. Skips to the next segment if needed. +--The output could be an end rail, junction rail, rail signal, chain signal, or train stop. +function get_next_rail_entity_ahead(origin_rail, dir_ahead, only_this_segment) + local next_entity, next_entity_label, result_extra, next_is_forward = identify_rail_segment_end_object(origin_rail, dir_ahead, false, false) + local iteration_count = 1 + local segment_end_ahead, dir_se = origin_rail.get_rail_segment_end(dir_ahead) + local prev_rail = segment_end_ahead + local current_rail = origin_rail + local neighbor_r_dir = dir_ahead + local neighbor_c_dir = nil + + --First correction for the train stop exception + if next_entity_label == "train stop" and next_is_forward == false then + next_entity, next_entity_label, result_extra, next_is_forward = identify_rail_segment_end_object(current_rail, neighbor_r_dir, true, false) + end + + --Skip all "other rail" cases + while not only_this_segment and next_entity_label == "other rail" and iteration_count < 100 do + if iteration_count % 2 == 1 then + --Switch to neighboring segment + current_rail, neighbor_r_dir, neighbor_c_dir = get_neighbor_rail_segment_end(prev_rail, nil) + prev_rail = current_rail + next_entity, next_entity_label, result_extra, next_is_forward = identify_rail_segment_end_object(current_rail, neighbor_r_dir, false, true) + --Correction for the train stop exception + if next_entity_label == "train stop" and next_is_forward == false then + next_entity, next_entity_label, result_extra, next_is_forward = identify_rail_segment_end_object(current_rail, neighbor_r_dir, true, true) + end + --Correction for flipped direction + if next_is_forward ~= nil then + next_is_forward = not next_is_forward + end + iteration_count = iteration_count + 1 + else + --Check other end of the segment. NOTE: Never got more than 2 iterations in tests so far... + neighbor_r_dir = get_opposite_rail_direction(neighbor_r_dir) + next_entity, next_entity_label, result_extra, next_is_forward = identify_rail_segment_end_object(current_rail, neighbor_r_dir, false, false) + --Correction for the train stop exception + if next_entity_label == "train stop" and next_is_forward == false then + next_entity, next_entity_label, result_extra, next_is_forward = identify_rail_segment_end_object(current_rail, neighbor_r_dir, true, false) + end + iteration_count = iteration_count + 1 + end + end + + return next_entity, next_entity_label, result_extra, next_is_forward, iteration_count +end + + +--Takes all the output from the get_next_rail_entity_ahead and adds extra info before reading them out. Does NOT detect trains. +function train_read_next_rail_entity_ahead(pindex, invert) + local message = "Ahead, " + local train = game.get_player(pindex).vehicle.train + local leading_rail, dir_ahead, leading_stock = get_leading_rail_and_dir_of_train_by_boarded_vehicle(pindex,train) + if invert then + dir_ahead = get_opposite_rail_direction(dir_ahead) + message = "Behind, " + end + --Correction for trains: Flip the correct direction ahead for mismatching diagonal rails + if leading_rail.name == "straight-rail" and (leading_rail.direction == dirs.southwest or leading_rail.direction == dirs.northwest) then + dir_ahead = get_opposite_rail_direction(dir_ahead) + end + --Correction for trains: Curved rails report different directions based on where the train sits and so are unreliable. + if leading_rail.name == "curved-rail" then + printout("Curved rail analysis error, check from another rail.",pindex) + return + end + local next_entity, next_entity_label, result_extra, next_is_forward, iteration_count = get_next_rail_entity_ahead(leading_rail, dir_ahead, false) + if next_entity == nil then + printout("Analysis error, this rail might be looping.",pindex) + return + end + local distance = math.floor(util.distance(leading_stock.position, next_entity.position)) + + --Test message + --message = message .. iteration_count .. " iterations, " + + --Maybe check for trains here, but there is no point because the checks use signal blocks... + --local trains_in_origin_block = origin_rail.trains_in_block + --local trains_in_current_block = current_rail.trains_in_block + + --Report opposite direction entities. + if next_is_forward == false and (next_entity_label == "train stop" or next_entity_label == "rail signal" or next_entity_label == "chain signal") then + message = message .. " Opposite direction's " + end + + --Add more info depending on entity label + if next_entity_label == "end rail" then + message = message .. next_entity_label + + elseif next_entity_label == "fork split" then + local entering_segment_rail = result_extra + message = message .. "rail fork splitting " + --laterdo here, list available fork directions + + elseif next_entity_label == "fork merge" then + local entering_segment_rail = result_extra + message = message .. "rail fork merging " + + elseif next_entity_label == "neighbor end" then + local entering_segment_rail = result_extra + message = message .. "end rail " + + elseif next_entity_label == "rail signal" then + message = message .. "rail signal with state " .. get_signal_state_info(next_entity) .. " " + + elseif next_entity_label == "chain signal" then + message = message .. "chain signal with state " .. get_signal_state_info(next_entity) .. " " + + elseif next_entity_label == "train stop" then + local stop_name = next_entity.backer_name + --Add more specific distance info + if math.abs(distance) > 25 or next_is_forward == false then + message = message .. "Train stop " .. stop_name .. ", in " .. distance .. " meters. " + else + distance = util.distance(leading_stock.position, next_entity.position) - 3.6 + if math.abs(distance) <= 0.2 then + message = " Aligned with train stop " .. stop_name + elseif distance > 0.2 then + message = math.floor(distance * 10) / 10 .. " meters away from train stop " .. stop_name .. ", for the frontmost vehicle. " + elseif distance < 0.2 then + message = math.floor((-distance) * 10) / 10 .. " meters past train stop " .. stop_name .. ", for the frontmost vehicle. " + end + end + + elseif next_entity_label == "other rail" then + message = message .. "unspecified entity" + + elseif next_entity_label == "other entity" then + message = message .. next_entity.name + end + + --Add general distance info + if next_entity_label ~= "train stop" then + message = message .. " in " .. distance .. " meters. " + if next_entity_label == "end rail" then + message = message .. " facing " .. direction_lookup(result_extra) + end + end + --If a train stop is close behind, read that instead + if leading_stock.name == "locomotive" and next_entity_label ~= "train stop" then + local heading = get_heading(leading_stock) + local pos = leading_stock.position + local scan_area = nil + local passed_stop = nil + local first_reset = false + --Scan behind the leading stock for 15m for passed train stops + if heading == "North" then --scan the south + scan_area = {{pos.x-4,pos.y-4},{pos.x+4,pos.y+15}} + elseif heading == "South" then + scan_area = {{pos.x-4,pos.y-15},{pos.x+4,pos.y+4}} + elseif heading == "East" then --scan the west + scan_area = {{pos.x-15,pos.y-4},{pos.x+4,pos.y+4}} + elseif heading == "West" then + scan_area = {{pos.x-4,pos.y-4},{pos.x+15,pos.y+4}} + else + --message = " Rail object scan error " .. heading .. " " + scan_area = {{pos.x+4,pos.y+4},{pos.x+4,pos.y+4}} + end + local ents = game.get_player(pindex).surface.find_entities_filtered{area = scan_area, name = "train-stop"} + for i,passed_stop in ipairs(ents) do + distance = util.distance(leading_stock.position, passed_stop.position) - 0 + --message = message .. " found stop " + if distance < 12.5 and direction_lookup(passed_stop.direction) == get_heading(leading_stock) then + if not first_reset then + message = "" + first_reset = true + end + message = message .. math.floor(distance+0.5) .. " meters past train stop " .. passed_stop.backer_name .. ", " + end + end + if first_reset then + message = message .. " for the front vehicle. " + end + end + printout(message,pindex) + --Draw circles for visual debugging + rendering.draw_circle{color = {0, 1, 0},radius = 1,width = 10,target = next_entity,surface = next_entity.surface,time_to_live = 100} +end + + +function rail_read_next_rail_entity_ahead(pindex, rail, is_forward) + local message = "Up this rail, " + local origin_rail = rail + local dir_ahead = defines.rail_direction.front + if not is_forward then + dir_ahead = defines.rail_direction.back + message = "Down this rail, " + end + local next_entity, next_entity_label, result_extra, next_is_forward, iteration_count = get_next_rail_entity_ahead(origin_rail, dir_ahead, false) + if next_entity == nil then + printout("Analysis error. This rail might be looping.",pindex) + return + end + local distance = math.floor(util.distance(origin_rail.position, next_entity.position)) + + --Test message + --message = message .. iteration_count .. " iterations, " + + --Maybe check for trains here, but there is no point because the checks use signal blocks... + --local trains_in_origin_block = origin_rail.trains_in_block + --local trains_in_current_block = current_rail.trains_in_block + + --Report opposite direction entities. + if next_is_forward == false and (next_entity_label == "train stop" or next_entity_label == "rail signal" or next_entity_label == "chain signal") then + message = message .. " Opposite direction's " + end + + --Add more info depending on entity label + if next_entity_label == "end rail" then + message = message .. next_entity_label + + elseif next_entity_label == "fork split" then + local entering_segment_rail = result_extra + message = message .. "rail fork splitting " + --laterdo here, give rail fork directions + + elseif next_entity_label == "fork merge" then + local entering_segment_rail = result_extra + message = message .. "rail fork merging " + + elseif next_entity_label == "neighbor end" then + local entering_segment_rail = result_extra + message = message .. "end rail " + + elseif next_entity_label == "rail signal" then + message = message .. "rail signal with state " .. get_signal_state_info(next_entity) .. " " + + elseif next_entity_label == "chain signal" then + message = message .. "chain signal with state " .. get_signal_state_info(next_entity) .. " " + + elseif next_entity_label == "train stop" then + local stop_name = next_entity.backer_name + --Add more specific distance info + if math.abs(distance) > 25 or next_is_forward == false then + message = message .. "Train stop " .. stop_name .. ", in " .. distance .. " meters, " + else + distance = util.distance(origin_rail.position, next_entity.position) - 2.5 + if math.abs(distance) <= 0.2 then + message = " Aligned with train stop " .. stop_name + elseif distance > 0.2 then + message = math.floor(distance * 10) / 10 .. " meters away from train stop " .. stop_name .. ". " + elseif distance < 0.2 then + message = math.floor((-distance) * 10) / 10 .. " meters past train stop " .. stop_name .. ". " + end + end + + elseif next_entity_label == "other rail" then + message = message .. "unspecified entity" + + elseif next_entity_label == "other entity" then + message = message .. next_entity.name + end + + --Add general distance info + if next_entity_label ~= "train stop" then + message = message .. " in " .. distance .. " meters, " + if next_entity_label == "end rail" then + message = message .. " facing " .. direction_lookup(result_extra) + end + end + printout(message,pindex) + --Draw circles for visual debugging + rendering.draw_circle{color = {0, 1, 0},radius = 1,width = 10,target = next_entity,surface = next_entity.surface,time_to_live = 100} +end + + + +--laterdo here: Rail analyzer menu where you will use arrow keys to go forward/back and left/right along a rail. +function rail_analyzer_menu(pindex, origin_rail,is_called_from_train) + return +end + + +--Builds a 45 degree rail turn to the right from a horizontal or vertical end rail that is the anchor rail. +function build_rail_turn_right_45_degrees(anchor_rail, pindex) + local build_comment = "" + local surf = game.get_player(pindex).surface + local stack = game.get_player(pindex).cursor_stack + local stack2 = nil + local pos = nil + local dir = -1 + local build_area = nil + local can_place_all = true + local is_end_rail + local anchor_dir = anchor_rail.direction + + --1. Firstly, check if the player has enough rails to place this (3 units) + if not (stack.valid and stack.valid_for_read and stack.name == "rail" and stack.count >= 3) then + --Check if the inventory has enough + if players[pindex].inventory.lua_inventory.get_item_count("rail") < 3 then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout("You need at least 3 rails in your inventory to build this turn.", pindex) + return + else + --Take from the inventory. + stack2 = players[pindex].inventory.lua_inventory.find_item_stack("rail") + game.get_player(pindex).cursor_stack.swap_stack(stack2) + stack = game.get_player(pindex).cursor_stack + players[pindex].inventory.max = #players[pindex].inventory.lua_inventory + end + end + + --2. Secondly, verify the end rail and find its direction + is_end_rail, dir, build_comment = check_end_rail(anchor_rail,pindex) + if not is_end_rail then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout(build_comment, pindex) + game.get_player(pindex).clear_cursor() + return + end + pos = anchor_rail.position + + --3. Clear trees and rocks in the build area, can be tuned later... + -- if dir == dirs.north or dir == dirs.northeast then + -- build_area = {{pos.x-9, pos.y+9},{pos.x+16,pos.y-16}} + -- elseif dir == dirs.east or dir == dirs.southeast then + -- build_area = {{pos.x-9, pos.y-9},{pos.x+16,pos.y+16}} + -- elseif dir == dirs.south or dir == dirs.southwest then + -- build_area = {{pos.x+9, pos.y-9},{pos.x-16,pos.y+16}} + -- elseif dir == dirs.west or dir == dirs.northwest then + -- build_area = {{pos.x+9, pos.y+9},{pos.x-16,pos.y-16}} + -- end + temp1, build_comment = mine_trees_and_rocks_in_circle(pos,12, pindex) + + --4. Check if every object can be placed + if dir == dirs.north then + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x+2, pos.y-4}, direction = dirs.northeast, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+4, pos.y-8}, direction = dirs.northwest, force = game.forces.player} + elseif dir == dirs.east then + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x+6, pos.y+2}, direction = dirs.southeast, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+8, pos.y+4}, direction = dirs.northeast, force = game.forces.player} + elseif dir == dirs.south then + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x+0, pos.y+6}, direction = dirs.southwest, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-4, pos.y+8}, direction = dirs.southeast, force = game.forces.player} + elseif dir == dirs.west then + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x-4, pos.y+0}, direction = dirs.northwest, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-8, pos.y-4}, direction = dirs.southwest, force = game.forces.player} + elseif dir == dirs.northeast then + if anchor_dir == dirs.northwest then + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x+4, pos.y-2}, direction = dirs.west, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+8, pos.y-4}, direction = dirs.east, force = game.forces.player} + elseif anchor_dir == dirs.southeast then + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+2, pos.y-0}, direction = dirs.northwest, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x+6, pos.y-2}, direction = dirs.west, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+10, pos.y-4}, direction = dirs.east, force = game.forces.player} + end + elseif dir == dirs.southwest then + if anchor_dir == dirs.southeast then--2 + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x-2, pos.y+4}, direction = dirs.east, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-8, pos.y+4}, direction = dirs.east, force = game.forces.player} + elseif anchor_dir == dirs.northwest then--3 + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-2, pos.y+0}, direction = dirs.southeast, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x-4, pos.y+4}, direction = dirs.east, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-10, pos.y+4}, direction = dirs.east, force = game.forces.player} + end + elseif dir == dirs.southeast then + if anchor_dir == dirs.northeast then--2 + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x+4, pos.y+4}, direction = dirs.north, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+4, pos.y+8}, direction = dirs.north, force = game.forces.player} + elseif anchor_dir == dirs.southwest then--3 + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-0, pos.y+2}, direction = dirs.northeast, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x+4, pos.y+6}, direction = dirs.north, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+4, pos.y+10}, direction = dirs.north, force = game.forces.player} + end + elseif dir == dirs.northwest then + if anchor_dir == dirs.southwest then--2 + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x-2, pos.y-2}, direction = dirs.south, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-4, pos.y-8}, direction = dirs.north, force = game.forces.player} + elseif anchor_dir == dirs.northeast then--3 + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-0, pos.y-2}, direction = dirs.southwest, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x-2, pos.y-4}, direction = dirs.south, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-4, pos.y-10}, direction = dirs.north, force = game.forces.player} + end + end + + if not can_place_all then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout("Building area occupied.", pindex) + game.get_player(pindex).clear_cursor() + return + end + + --5. Build the rail entities to create the turn + if dir == dirs.north then + surf.create_entity{name = "curved-rail", position = {pos.x+2, pos.y-4}, direction = dirs.northeast, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+4, pos.y-8}, direction = dirs.northwest, force = game.forces.player} + elseif dir == dirs.east then + surf.create_entity{name = "curved-rail", position = {pos.x+6, pos.y+2}, direction = dirs.southeast, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+8, pos.y+4}, direction = dirs.northeast, force = game.forces.player} + elseif dir == dirs.south then + surf.create_entity{name = "curved-rail", position = {pos.x+0, pos.y+6}, direction = dirs.southwest, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-4, pos.y+8}, direction = dirs.southeast, force = game.forces.player} + elseif dir == dirs.west then + surf.create_entity{name = "curved-rail", position = {pos.x-4, pos.y+0}, direction = dirs.northwest, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-8, pos.y-4}, direction = dirs.southwest, force = game.forces.player} + elseif dir == dirs.northeast then + if anchor_dir == dirs.northwest then + surf.create_entity{name = "curved-rail", position = {pos.x+4, pos.y-2}, direction = dirs.west, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+8, pos.y-4}, direction = dirs.east, force = game.forces.player} + elseif anchor_dir == dirs.southeast then + surf.create_entity{name = "straight-rail", position = {pos.x+2, pos.y-0}, direction = dirs.northwest, force = game.forces.player} + surf.create_entity{name = "curved-rail", position = {pos.x+6, pos.y-2}, direction = dirs.west, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+10, pos.y-4}, direction = dirs.east, force = game.forces.player} + end + elseif dir == dirs.southwest then + if anchor_dir == dirs.southeast then--2 + surf.create_entity{name = "curved-rail", position = {pos.x-2, pos.y+4}, direction = dirs.east, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-8, pos.y+4}, direction = dirs.east, force = game.forces.player} + elseif anchor_dir == dirs.northwest then--3 + surf.create_entity{name = "straight-rail", position = {pos.x-2, pos.y+0}, direction = dirs.southeast, force = game.forces.player} + surf.create_entity{name = "curved-rail", position = {pos.x-4, pos.y+4}, direction = dirs.east, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-10, pos.y+4}, direction = dirs.east, force = game.forces.player} + end + elseif dir == dirs.southeast then + if anchor_dir == dirs.northeast then--2 + surf.create_entity{name = "curved-rail", position = {pos.x+4, pos.y+4}, direction = dirs.north, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+4, pos.y+8}, direction = dirs.north, force = game.forces.player} + elseif anchor_dir == dirs.southwest then--3 + surf.create_entity{name = "straight-rail", position = {pos.x-0, pos.y+2}, direction = dirs.northeast, force = game.forces.player} + surf.create_entity{name = "curved-rail", position = {pos.x+4, pos.y+6}, direction = dirs.north, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+4, pos.y+10}, direction = dirs.north, force = game.forces.player} + end + elseif dir == dirs.northwest then + if anchor_dir == dirs.southwest then--2 + surf.create_entity{name = "curved-rail", position = {pos.x-2, pos.y-2}, direction = dirs.south, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-4, pos.y-8}, direction = dirs.north, force = game.forces.player} + elseif anchor_dir == dirs.northeast then--3 + surf.create_entity{name = "straight-rail", position = {pos.x-0, pos.y-2}, direction = dirs.southwest, force = game.forces.player} + surf.create_entity{name = "curved-rail", position = {pos.x-2, pos.y-4}, direction = dirs.south, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-4, pos.y-10}, direction = dirs.north, force = game.forces.player} + end + end + + + --6 Remove rail units from the player's hand + game.get_player(pindex).cursor_stack.count = game.get_player(pindex).cursor_stack.count - 2 + if (dir == dirs.northeast and anchor_dir == dirs.southeast) or (dir == dirs.southwest and anchor_dir == dirs.northwest) or (dir == dirs.southeast and anchor_dir == dirs.southwest) or (dir == dirs.northwest and anchor_dir == dirs.northeast) then + game.get_player(pindex).cursor_stack.count = game.get_player(pindex).cursor_stack.count - 1 + end + game.get_player(pindex).clear_cursor() + + --7. Sounds and results + game.get_player(pindex).play_sound{path = "entity-build/straight-rail"} + game.get_player(pindex).play_sound{path = "entity-build/curved-rail"} + printout("Rail turn built 45 degrees right, " .. build_comment, pindex) + return + +end + + +--Builds a 90 degree rail turn to the right from a horizontal or vertical end rail that is the anchor rail. +function build_rail_turn_right_90_degrees(anchor_rail, pindex) + local build_comment = "" + local surf = game.get_player(pindex).surface + local stack = game.get_player(pindex).cursor_stack + local stack2 = nil + local pos = nil + local dir = -1 + local build_area = nil + local can_place_all = true + local is_end_rail + + --1. Firstly, check if the player has enough rails to place this (10 units) + if not (stack.valid and stack.valid_for_read and stack.name == "rail" and stack.count >= 10) then + --Check if the inventory has enough + if players[pindex].inventory.lua_inventory.get_item_count("rail") < 10 then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout("You need at least 10 rails in your inventory to build this turn.", pindex) + return + else + --Take from the inventory. + stack2 = players[pindex].inventory.lua_inventory.find_item_stack("rail") + game.get_player(pindex).cursor_stack.swap_stack(stack2) + stack = game.get_player(pindex).cursor_stack + players[pindex].inventory.max = #players[pindex].inventory.lua_inventory + end + end + + --2. Secondly, verify the end rail and find its direction + is_end_rail, dir, build_comment = check_end_rail(anchor_rail,pindex) + if not is_end_rail then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout(build_comment, pindex) + game.get_player(pindex).clear_cursor() + return + end + pos = anchor_rail.position + if dir == dirs.northeast or dir == dirs.southeast or dir == dirs.southwest or dir == dirs.northwest then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout("This structure is for horizontal or vertical end rails only.", pindex) + game.get_player(pindex).clear_cursor() + return + end + + --3. Clear trees and rocks in the build area + -- if dir == dirs.north then + -- build_area = {{pos.x-2, pos.y+2},{pos.x+16,pos.y-16}} + -- elseif dir == dirs.east then + -- build_area = {{pos.x-2, pos.y-2},{pos.x+16,pos.y+16}} + -- elseif dir == dirs.south then + -- build_area = {{pos.x+2, pos.y-2},{pos.x-16,pos.y+16}} + -- elseif dir == dirs.west then + -- build_area = {{pos.x+2, pos.y+2},{pos.x-16,pos.y-16}} + -- end + temp1, build_comment = mine_trees_and_rocks_in_circle(pos,18, pindex) + + --4. Check if every object can be placed + if dir == dirs.north then + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x+2, pos.y-4}, direction = dirs.northeast, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+4, pos.y-8}, direction = dirs.northwest, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x+8, pos.y-10}, direction = dirs.west, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+12, pos.y-12}, direction = dirs.east, force = game.forces.player} + elseif dir == dirs.east then + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x+6, pos.y+2}, direction = dirs.southeast, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+8, pos.y+4}, direction = dirs.northeast, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x+12, pos.y+8}, direction = dirs.north, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+12, pos.y+12}, direction = dirs.south, force = game.forces.player} + elseif dir == dirs.south then + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x+0, pos.y+6}, direction = dirs.southwest, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-4, pos.y+8}, direction = dirs.southeast, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x-6, pos.y+12}, direction = dirs.east, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-12, pos.y+12}, direction = dirs.west, force = game.forces.player} + elseif dir == dirs.west then + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x-4, pos.y+0}, direction = dirs.northwest, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-8, pos.y-4}, direction = dirs.southwest, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x-10, pos.y-6}, direction = dirs.south, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-12, pos.y-12}, direction = dirs.north, force = game.forces.player} + end + + if not can_place_all then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout("Building area occupied.", pindex) + game.get_player(pindex).clear_cursor() + return + end + + --5. Build the five rail entities to create the turn + if dir == dirs.north then + surf.create_entity{name = "curved-rail", position = {pos.x+2, pos.y-4}, direction = dirs.northeast, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+4, pos.y-8}, direction = dirs.northwest, force = game.forces.player} + surf.create_entity{name = "curved-rail", position = {pos.x+8, pos.y-10}, direction = dirs.west, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+12, pos.y-12}, direction = dirs.east, force = game.forces.player} + elseif dir == dirs.east then + surf.create_entity{name = "curved-rail", position = {pos.x+6, pos.y+2}, direction = dirs.southeast, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+8, pos.y+4}, direction = dirs.northeast, force = game.forces.player} + surf.create_entity{name = "curved-rail", position = {pos.x+12, pos.y+8}, direction = dirs.north, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+12, pos.y+12}, direction = dirs.south, force = game.forces.player} + elseif dir == dirs.south then + surf.create_entity{name = "curved-rail", position = {pos.x+0, pos.y+6}, direction = dirs.southwest, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-4, pos.y+8}, direction = dirs.southeast, force = game.forces.player} + surf.create_entity{name = "curved-rail", position = {pos.x-6, pos.y+12}, direction = dirs.east, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-12, pos.y+12}, direction = dirs.west, force = game.forces.player} + elseif dir == dirs.west then + surf.create_entity{name = "curved-rail", position = {pos.x-4, pos.y+0}, direction = dirs.northwest, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-8, pos.y-4}, direction = dirs.southwest, force = game.forces.player} + surf.create_entity{name = "curved-rail", position = {pos.x-10, pos.y-6}, direction = dirs.south, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-12, pos.y-12}, direction = dirs.north, force = game.forces.player} + end + + --6 Remove 10 rail units from the player's hand + game.get_player(pindex).cursor_stack.count = game.get_player(pindex).cursor_stack.count - 10 + game.get_player(pindex).clear_cursor() + + --7. Sounds and results + game.get_player(pindex).play_sound{path = "entity-build/straight-rail"} + game.get_player(pindex).play_sound{path = "entity-build/curved-rail"} + printout("Rail turn built 90 degrees right, " .. build_comment, pindex) + return + +end + + +--Builds a 45 degree rail turn to the left from a horizontal or vertical end rail that is the anchor rail. +function build_rail_turn_left_45_degrees(anchor_rail, pindex) + local build_comment = "" + local surf = game.get_player(pindex).surface + local stack = game.get_player(pindex).cursor_stack + local stack2 = nil + local pos = nil + local dir = -1 + local build_area = nil + local can_place_all = true + local is_end_rail + local anchor_dir = anchor_rail.direction + + --1. Firstly, check if the player has enough rails to place this (3 units) + if not (stack.valid and stack.valid_for_read and stack.name == "rail" and stack.count >= 3) then + --Check if the inventory has enough + if players[pindex].inventory.lua_inventory.get_item_count("rail") < 3 then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout("You need at least 3 rails in your inventory to build this turn.", pindex) + return + else + --Take from the inventory. + stack2 = players[pindex].inventory.lua_inventory.find_item_stack("rail") + game.get_player(pindex).cursor_stack.swap_stack(stack2) + stack = game.get_player(pindex).cursor_stack + players[pindex].inventory.max = #players[pindex].inventory.lua_inventory + end + end + + --2. Secondly, verify the end rail and find its direction + is_end_rail, dir, build_comment = check_end_rail(anchor_rail,pindex) + if not is_end_rail then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout(build_comment, pindex) + game.get_player(pindex).clear_cursor() + return + end + pos = anchor_rail.position + + --3. Clear trees and rocks in the build area, can be tuned later... + -- if dir == dirs.north or dir == dirs.northeast then + -- build_area = {{pos.x+9, pos.y+9},{pos.x-16,pos.y-16}} + -- elseif dir == dirs.east or dir == dirs.southeast then + -- build_area = {{pos.x-9, pos.y+9},{pos.x+16,pos.y-16}} + -- elseif dir == dirs.south or dir == dirs.southwest then + -- build_area = {{pos.x-9, pos.y-9},{pos.x+16,pos.y+16}} + -- elseif dir == dirs.west or dir == dirs.northwest then + -- build_area = {{pos.x+9, pos.y-9},{pos.x-16,pos.y+16}} + -- end + temp1, build_comment = mine_trees_and_rocks_in_circle(pos,12, pindex) + + --4. Check if every object can be placed + if dir == dirs.north then + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x+0, pos.y-4}, direction = dirs.north, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-4, pos.y-8}, direction = dirs.northeast, force = game.forces.player} + elseif dir == dirs.east then + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x+6, pos.y+0}, direction = dirs.east, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+8, pos.y-4}, direction = dirs.southeast, force = game.forces.player} + elseif dir == dirs.south then + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x+2, pos.y+6}, direction = dirs.south, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+4, pos.y+8}, direction = dirs.southwest, force = game.forces.player} + elseif dir == dirs.west then + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x-4, pos.y+2}, direction = dirs.west, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-8, pos.y+4}, direction = dirs.northwest, force = game.forces.player} + elseif dir == dirs.northeast then + if anchor_dir == dirs.southeast then--2 + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x+4, pos.y-2}, direction = dirs.southwest, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+4, pos.y-8}, direction = dirs.north, force = game.forces.player} + elseif anchor_dir == dirs.northwest then--3 + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+0, pos.y-2}, direction = dirs.southeast, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x+4, pos.y-4}, direction = dirs.southwest, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+4, pos.y-10}, direction = dirs.north, force = game.forces.player} + end + elseif dir == dirs.southwest then + if anchor_dir == dirs.northwest then--2 + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x-2, pos.y+4}, direction = dirs.northeast, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-4, pos.y+8}, direction = dirs.north, force = game.forces.player} + elseif anchor_dir == dirs.southeast then--3 + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-0, pos.y+2}, direction = dirs.northwest, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x-2, pos.y+6}, direction = dirs.northeast, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-4, pos.y+10}, direction = dirs.north, force = game.forces.player} + end + elseif dir == dirs.southeast then + if anchor_dir == dirs.southwest then--2 + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x+4, pos.y+4}, direction = dirs.northwest, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+8, pos.y+4}, direction = dirs.east, force = game.forces.player} + elseif anchor_dir == dirs.northeast then--3 + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+2, pos.y+0}, direction = dirs.southwest, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x+6, pos.y+4}, direction = dirs.northwest, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+10, pos.y+4}, direction = dirs.east, force = game.forces.player} + end + elseif dir == dirs.northwest then + if anchor_dir == dirs.northeast then--2 + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x-2, pos.y-2}, direction = dirs.southeast, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-8, pos.y-4}, direction = dirs.east, force = game.forces.player} + elseif anchor_dir == dirs.southwest then--3 + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-2, pos.y-0}, direction = dirs.northeast, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x-4, pos.y-2}, direction = dirs.southeast, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-10, pos.y-4}, direction = dirs.east, force = game.forces.player} + end + end + + if not can_place_all then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout("Building area occupied.", pindex) + game.get_player(pindex).clear_cursor() + return + end + + --5. Build the rail entities to create the turn + if dir == dirs.north then + surf.create_entity{name = "curved-rail", position = {pos.x+0, pos.y-4}, direction = dirs.north, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-4, pos.y-8}, direction = dirs.northeast, force = game.forces.player} + elseif dir == dirs.east then + surf.create_entity{name = "curved-rail", position = {pos.x+6, pos.y+0}, direction = dirs.east, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+8, pos.y-4}, direction = dirs.southeast, force = game.forces.player} + elseif dir == dirs.south then + surf.create_entity{name = "curved-rail", position = {pos.x+2, pos.y+6}, direction = dirs.south, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+4, pos.y+8}, direction = dirs.southwest, force = game.forces.player} + elseif dir == dirs.west then + surf.create_entity{name = "curved-rail", position = {pos.x-4, pos.y+2}, direction = dirs.west, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-8, pos.y+4}, direction = dirs.northwest, force = game.forces.player} + elseif dir == dirs.northeast then + if anchor_dir == dirs.southeast then--2 + surf.create_entity{name = "curved-rail", position = {pos.x+4, pos.y-2}, direction = dirs.southwest, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+4, pos.y-8}, direction = dirs.north, force = game.forces.player} + elseif anchor_dir == dirs.northwest then--3 + surf.create_entity{name = "straight-rail", position = {pos.x+0, pos.y-2}, direction = dirs.southeast, force = game.forces.player} + surf.create_entity{name = "curved-rail", position = {pos.x+4, pos.y-4}, direction = dirs.southwest, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+4, pos.y-10}, direction = dirs.north, force = game.forces.player} + end + elseif dir == dirs.southwest then + if anchor_dir == dirs.northwest then--2 + surf.create_entity{name = "curved-rail", position = {pos.x-2, pos.y+4}, direction = dirs.northeast, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-4, pos.y+8}, direction = dirs.north, force = game.forces.player} + elseif anchor_dir == dirs.southeast then--3 + surf.create_entity{name = "straight-rail", position = {pos.x-0, pos.y+2}, direction = dirs.northwest, force = game.forces.player} + surf.create_entity{name = "curved-rail", position = {pos.x-2, pos.y+6}, direction = dirs.northeast, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-4, pos.y+10}, direction = dirs.north, force = game.forces.player} + end + elseif dir == dirs.southeast then + if anchor_dir == dirs.southwest then--2 + surf.create_entity{name = "curved-rail", position = {pos.x+4, pos.y+4}, direction = dirs.northwest, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+8, pos.y+4}, direction = dirs.east, force = game.forces.player} + elseif anchor_dir == dirs.northeast then--3 + surf.create_entity{name = "straight-rail", position = {pos.x+2, pos.y+0}, direction = dirs.southwest, force = game.forces.player} + surf.create_entity{name = "curved-rail", position = {pos.x+6, pos.y+4}, direction = dirs.northwest, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+10, pos.y+4}, direction = dirs.east, force = game.forces.player} + end + elseif dir == dirs.northwest then + if anchor_dir == dirs.northeast then--2 + surf.create_entity{name = "curved-rail", position = {pos.x-2, pos.y-2}, direction = dirs.southeast, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-8, pos.y-4}, direction = dirs.east, force = game.forces.player} + elseif anchor_dir == dirs.southwest then--3 + surf.create_entity{name = "straight-rail", position = {pos.x-2, pos.y-0}, direction = dirs.northeast, force = game.forces.player} + surf.create_entity{name = "curved-rail", position = {pos.x-4, pos.y-2}, direction = dirs.southeast, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-10, pos.y-4}, direction = dirs.east, force = game.forces.player} + end + end + + + --6 Remove rail units from the player's hand + game.get_player(pindex).cursor_stack.count = game.get_player(pindex).cursor_stack.count - 2 + if (dir == dirs.northeast and anchor_dir == dirs.northwest) or (dir == dirs.southwest and anchor_dir == dirs.southeast) or (dir == dirs.southeast and anchor_dir == dirs.northeast) or (dir == dirs.northwest and anchor_dir == dirs.southwest) then + game.get_player(pindex).cursor_stack.count = game.get_player(pindex).cursor_stack.count - 1 + end + game.get_player(pindex).clear_cursor() + + --7. Sounds and results + game.get_player(pindex).play_sound{path = "entity-build/straight-rail"} + game.get_player(pindex).play_sound{path = "entity-build/curved-rail"} + printout("Rail turn built 45 degrees left, " .. build_comment, pindex) + return + +end + + +--Builds a 90 degree rail turn to the left from a horizontal or vertical end rail that is the anchor rail. +function build_rail_turn_left_90_degrees(anchor_rail, pindex) + local build_comment = "" + local surf = game.get_player(pindex).surface + local stack = game.get_player(pindex).cursor_stack + local stack2 = nil + local pos = nil + local dir = -1 + local build_area = nil + local can_place_all = true + local is_end_rail + + --1. Firstly, check if the player has enough rails to place this (10 units) + if not (stack.valid and stack.valid_for_read and stack.name == "rail" and stack.count > 10) then + --Check if the inventory has enough + if players[pindex].inventory.lua_inventory.get_item_count("rail") < 10 then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout("You need at least 10 rails in your inventory to build this turn.", pindex) + return + else + --Take from the inventory. + stack2 = players[pindex].inventory.lua_inventory.find_item_stack("rail") + game.get_player(pindex).cursor_stack.swap_stack(stack2) + stack = game.get_player(pindex).cursor_stack + players[pindex].inventory.max = #players[pindex].inventory.lua_inventory + end + end + + --2. Secondly, verify the end rail and find its direction + is_end_rail, dir, build_comment = check_end_rail(anchor_rail,pindex) + if not is_end_rail then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout(build_comment, pindex) + game.get_player(pindex).clear_cursor() + return + end + pos = anchor_rail.position + if dir == dirs.northeast or dir == dirs.southeast or dir == dirs.southwest or dir == dirs.northwest then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout("This structure is for horizontal or vertical end rails only.", pindex) + game.get_player(pindex).clear_cursor() + return + end + + --3. Clear trees and rocks in the build area + -- if dir == dirs.north then + -- build_area = {{pos.x+2, pos.y+2},{pos.x-16,pos.y-16}} + -- elseif dir == dirs.east then + -- build_area = {{pos.x+2, pos.y+2},{pos.x+16,pos.y-16}} + -- elseif dir == dirs.south then + -- build_area = {{pos.x+2, pos.y+2},{pos.x+16,pos.y+16}} + -- elseif dir == dirs.west then + -- build_area = {{pos.x+2, pos.y+2},{pos.x-16,pos.y+16}} + -- end + temp1, build_comment = mine_trees_and_rocks_in_circle(pos,18, pindex) + + --4. Check if every object can be placed + if dir == dirs.north then + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x+0, pos.y-4}, direction = dirs.north, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-4, pos.y-8}, direction = dirs.northeast, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x-6, pos.y-10}, direction = dirs.southeast, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-12, pos.y-12}, direction = dirs.east, force = game.forces.player} + elseif dir == dirs.east then + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x+6, pos.y+0}, direction = dirs.east, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+8, pos.y-4}, direction = dirs.southeast, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x+12, pos.y-6}, direction = dirs.southwest, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+12, pos.y-12}, direction = dirs.south, force = game.forces.player} + elseif dir == dirs.south then + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x+2, pos.y+6}, direction = dirs.south, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+4, pos.y+8}, direction = dirs.southwest, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x+8, pos.y+12}, direction = dirs.northwest, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+12, pos.y+12}, direction = dirs.west, force = game.forces.player} + elseif dir == dirs.west then + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x-4, pos.y+2}, direction = dirs.west, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-8, pos.y+4}, direction = dirs.northwest, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "curved-rail", position = {pos.x-10, pos.y+8}, direction = dirs.northeast, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-12, pos.y+12}, direction = dirs.north, force = game.forces.player} + end + + if not can_place_all then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout("Building area occupied.", pindex) + game.get_player(pindex).clear_cursor() + return + end + + --5. Build the five rail entities to create the turn + if dir == dirs.north then + surf.create_entity{name = "curved-rail", position = {pos.x+0, pos.y-4}, direction = dirs.north, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-4, pos.y-8}, direction = dirs.northeast, force = game.forces.player} + surf.create_entity{name = "curved-rail", position = {pos.x-6, pos.y-10}, direction = dirs.southeast, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-12, pos.y-12}, direction = dirs.east, force = game.forces.player} + elseif dir == dirs.east then + surf.create_entity{name = "curved-rail", position = {pos.x+6, pos.y+0}, direction = dirs.east, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+8, pos.y-4}, direction = dirs.southeast, force = game.forces.player} + surf.create_entity{name = "curved-rail", position = {pos.x+12, pos.y-6}, direction = dirs.southwest, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+12, pos.y-12}, direction = dirs.south, force = game.forces.player} + elseif dir == dirs.south then + surf.create_entity{name = "curved-rail", position = {pos.x+2, pos.y+6}, direction = dirs.south, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+4, pos.y+8}, direction = dirs.southwest, force = game.forces.player} + surf.create_entity{name = "curved-rail", position = {pos.x+8, pos.y+12}, direction = dirs.northwest, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+12, pos.y+12}, direction = dirs.west, force = game.forces.player} + elseif dir == dirs.west then + surf.create_entity{name = "curved-rail", position = {pos.x-4, pos.y+2}, direction = dirs.west, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-8, pos.y+4}, direction = dirs.northwest, force = game.forces.player} + surf.create_entity{name = "curved-rail", position = {pos.x-10, pos.y+8}, direction = dirs.northeast, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-12, pos.y+12}, direction = dirs.north, force = game.forces.player} + end + + --6 Remove 10 rail units from the player's hand + game.get_player(pindex).cursor_stack.count = game.get_player(pindex).cursor_stack.count - 10 + game.get_player(pindex).clear_cursor() + + --7. Sounds and results + game.get_player(pindex).play_sound{path = "entity-build/straight-rail"} + game.get_player(pindex).play_sound{path = "entity-build/curved-rail"} + printout("Rail turn built 90 degrees left, " .. build_comment, pindex) + return +end + + +--Builds a minimal straight rail intersection on a horizontal or vertical end rail. Note: We should build other intersections with blueprint imports. +function build_small_plus_intersection(anchor_rail, pindex) + local build_comment = "" + local surf = game.get_player(pindex).surface + local stack = game.get_player(pindex).cursor_stack + local stack2 = nil + local pos = nil + local dir = -1 + local build_area = nil + local can_place_all = true + local is_end_rail + + --1. Firstly, check if the player has enough rails to place this (5 units) + if not (stack.valid and stack.valid_for_read and stack.name == "rail" and stack.count > 5) then + --Check if the inventory has enough + if players[pindex].inventory.lua_inventory.get_item_count("rail") < 5 then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout("You need at least 5 rails in your inventory to build this turn.", pindex) + return + else + --Take from the inventory. + stack2 = players[pindex].inventory.lua_inventory.find_item_stack("rail") + game.get_player(pindex).cursor_stack.swap_stack(stack2) + stack = game.get_player(pindex).cursor_stack + players[pindex].inventory.max = #players[pindex].inventory.lua_inventory + end + end + + --2. Secondly, verify the end rail and find its direction + is_end_rail, dir, build_comment = check_end_rail(anchor_rail,pindex) + if not is_end_rail then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout(build_comment, pindex) + game.get_player(pindex).clear_cursor() + return + end + pos = anchor_rail.position + if dir == dirs.northeast or dir == dirs.southeast or dir == dirs.southwest or dir == dirs.northwest then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout("This structure is for horizontal or vertical end rails only.", pindex) + game.get_player(pindex).clear_cursor() + return + end + + --3. Clear trees and rocks in the build area + temp1, build_comment = mine_trees_and_rocks_in_circle(pos,10, pindex) + + --4. Check if every object can be placed + if dir == dirs.north then + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+0, pos.y-2}, direction = dirs.north, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+0, pos.y-4}, direction = dirs.north, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+0, pos.y-2}, direction = dirs.east, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-2, pos.y-2}, direction = dirs.east, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+2, pos.y-2}, direction = dirs.east, force = game.forces.player} + + elseif dir == dirs.east then + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+2, pos.y+0}, direction = dirs.east, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+4, pos.y+0}, direction = dirs.east, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+2, pos.y-2}, direction = dirs.north, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+2, pos.y+0}, direction = dirs.north, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+2, pos.y+2}, direction = dirs.north, force = game.forces.player} + + elseif dir == dirs.south then + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+0, pos.y+2}, direction = dirs.north, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+0, pos.y+4}, direction = dirs.north, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+0, pos.y+2}, direction = dirs.east, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-2, pos.y+2}, direction = dirs.east, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x+2, pos.y+2}, direction = dirs.east, force = game.forces.player} + + elseif dir == dirs.west then + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-2, pos.y+0}, direction = dirs.east, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-4, pos.y+0}, direction = dirs.east, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-2, pos.y-2}, direction = dirs.north, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-2, pos.y+0}, direction = dirs.north, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "straight-rail", position = {pos.x-2, pos.y+2}, direction = dirs.north, force = game.forces.player} + + end + + if not can_place_all then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout("Building area occupied.", pindex) + game.get_player(pindex).clear_cursor() + return + end + + --5. Build the five rail entities to create the structure. Also add signals for free + if dir == dirs.north then + surf.create_entity{name = "straight-rail", position = {pos.x+0, pos.y-2}, direction = dirs.north, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+0, pos.y-4}, direction = dirs.north, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+0, pos.y-2}, direction = dirs.east, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-2, pos.y-2}, direction = dirs.east, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+2, pos.y-2}, direction = dirs.east, force = game.forces.player} + + surf.create_entity{name = "rail-chain-signal" , position = {pos.x-2, pos.y-0}, direction = dirs.north, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal" , position = {pos.x+1, pos.y-5}, direction = dirs.south, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal" , position = {pos.x-3, pos.y-4}, direction = dirs.east, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal" , position = {pos.x+2, pos.y-1}, direction = dirs.west, force = game.forces.player} + + surf.create_entity{name = "rail-chain-signal", position = {pos.x+1, pos.y-0}, direction = dirs.south, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal", position = {pos.x-2, pos.y-5}, direction = dirs.north, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal", position = {pos.x-3, pos.y-1}, direction = dirs.west, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal", position = {pos.x+2, pos.y-4}, direction = dirs.east, force = game.forces.player} + + elseif dir == dirs.east then + surf.create_entity{name = "straight-rail", position = {pos.x+2, pos.y+0}, direction = dirs.east, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+4, pos.y+0}, direction = dirs.east, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+2, pos.y-2}, direction = dirs.north, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+2, pos.y+0}, direction = dirs.north, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+2, pos.y+2}, direction = dirs.north, force = game.forces.player} + + surf.create_entity{name = "rail-chain-signal" , position = {pos.x-1, pos.y-2}, direction = dirs.east, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal" , position = {pos.x+4, pos.y+1}, direction = dirs.west, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal" , position = {pos.x+3, pos.y-3}, direction = dirs.south, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal" , position = {pos.x-0, pos.y+2}, direction = dirs.north, force = game.forces.player} + + surf.create_entity{name = "rail-chain-signal", position = {pos.x-1, pos.y+1}, direction = dirs.west, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal", position = {pos.x+4, pos.y-2}, direction = dirs.east, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal", position = {pos.x-0, pos.y-3}, direction = dirs.north, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal", position = {pos.x+3, pos.y+2}, direction = dirs.south, force = game.forces.player} + + + + elseif dir == dirs.south then + surf.create_entity{name = "straight-rail", position = {pos.x+0, pos.y+2}, direction = dirs.north, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+0, pos.y+4}, direction = dirs.north, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+0, pos.y+2}, direction = dirs.east, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-2, pos.y+2}, direction = dirs.east, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x+2, pos.y+2}, direction = dirs.east, force = game.forces.player} + + surf.create_entity{name = "rail-chain-signal" , position = {pos.x+1, pos.y-1}, direction = dirs.south, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal" , position = {pos.x-2, pos.y+4}, direction = dirs.north, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal" , position = {pos.x-3, pos.y+0}, direction = dirs.east, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal" , position = {pos.x+2, pos.y+3}, direction = dirs.west, force = game.forces.player} + + surf.create_entity{name = "rail-chain-signal", position = {pos.x-2, pos.y-1}, direction = dirs.north, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal", position = {pos.x+1, pos.y+4}, direction = dirs.south, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal", position = {pos.x-3, pos.y+3}, direction = dirs.west, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal", position = {pos.x+2, pos.y+0}, direction = dirs.east, force = game.forces.player} + + elseif dir == dirs.west then + surf.create_entity{name = "straight-rail", position = {pos.x-2, pos.y+0}, direction = dirs.east, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-4, pos.y+0}, direction = dirs.east, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-2, pos.y-2}, direction = dirs.north, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-2, pos.y+0}, direction = dirs.north, force = game.forces.player} + surf.create_entity{name = "straight-rail", position = {pos.x-2, pos.y+2}, direction = dirs.north, force = game.forces.player} + + surf.create_entity{name = "rail-chain-signal" , position = {pos.x-0, pos.y+1}, direction = dirs.west, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal" , position = {pos.x-5, pos.y-2}, direction = dirs.east, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal" , position = {pos.x-4, pos.y+2}, direction = dirs.north, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal" , position = {pos.x-1, pos.y-3}, direction = dirs.south, force = game.forces.player} + + surf.create_entity{name = "rail-chain-signal", position = {pos.x-0, pos.y-2}, direction = dirs.east, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal", position = {pos.x-5, pos.y+1}, direction = dirs.west, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal", position = {pos.x-1, pos.y+2}, direction = dirs.south, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal", position = {pos.x-4, pos.y-3}, direction = dirs.north, force = game.forces.player} + + end + + --6 Remove 5 rail units from the player's hand + game.get_player(pindex).cursor_stack.count = game.get_player(pindex).cursor_stack.count - 5 + game.get_player(pindex).clear_cursor() + + --7. Sounds and results + game.get_player(pindex).play_sound{path = "entity-build/straight-rail"} + game.get_player(pindex).play_sound{path = "entity-build/straight-rail"} + printout("Intersection built." .. build_comment, pindex) + return +end + + +--Appends a new straight or diagonal rail to a rail end found near the input position. The cursor needs to be holding rails. +function append_rail(pos, pindex) + local surf = game.get_player(pindex).surface + local stack = game.get_player(pindex).cursor_stack + local is_end_rail = false + local end_found = nil + local end_dir = nil + local end_dir_1 = nil + local end_dir_2 = nil + local rail_api_dir = nil + local is_end_rail = nil + local end_rail_dir = nil + local comment = "" + + --0 Check if there is at least 1 rail in hand, else return + if not (stack.valid and stack.valid_for_read and stack.name == "rail" and stack.count > 0) then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout("You need at least 1 rail in hand.", pindex) + return + end + + --1 Check the cursor entity. If it is an end rail, use this instead of scanning to extend the rail you want. + local ent = players[pindex].tile.ents[1] + is_end_rail, end_rail_dir, comment = check_end_rail(ent,pindex) + if is_end_rail then + end_found = ent + end_rail_1, end_dir_1 = ent.get_rail_segment_end(defines.rail_direction.front) + end_rail_2, end_dir_2 = ent.get_rail_segment_end(defines.rail_direction.back) + if ent.unit_number == end_rail_1.unit_number then + end_dir = end_dir_1 + elseif ent.unit_number == end_rail_2.unit_number then + end_dir = end_dir_2 + end + else + --2 Scan the area around within a X tile radius of pos + local ents = surf.find_entities_filtered{position = pos, radius = 3, name = "straight-rail"} + if #ents == 0 then + ents = surf.find_entities_filtered{position = pos, radius = 3, name = "curved-rail"} + if #ents == 0 then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + if players[pindex].build_lock == false then + printout("No rails found nearby.",pindex) + return + end + end + end + + --3 For the first rail found, check if it is at the end of its segment and if the rail is not within X tiles of pos, try the other end + for i,rail in ipairs(ents) do + end_rail_1, end_dir_1 = rail.get_rail_segment_end(defines.rail_direction.front) + end_rail_2, end_dir_2 = rail.get_rail_segment_end(defines.rail_direction.back) + if util.distance(pos, end_rail_1.position) < 3 then--is within range + end_found = end_rail_1 + end_dir = end_dir_1 + elseif util.distance(pos, end_rail_2.position) < 3 then--is within range + end_found = end_rail_2 + end_dir = end_dir_2 + end + end + if end_found == nil then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + if players[pindex].build_lock == false then + printout("No end rails found nearby", pindex) + end + return + end + + --4 Check if the found segment end is an end rail + is_end_rail, end_rail_dir, comment = check_end_rail(end_found,pindex) + if not is_end_rail then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + --printout(comment, pindex) + printout("No end rails found nearby", pindex) + return + end + end + + --5 Confirmed as an end rail. Get its position and find the correct position and direction for the appended rail. + end_rail_pos = end_found.position + end_rail_dir = end_found.direction + append_rail_dir = -1 + append_rail_pos = nil + rail_api_dir = end_found.direction + + --printout(" Rail end found at " .. end_found.position.x .. " , " .. end_found.position.y .. " , facing " .. end_found.direction, pindex)--Checks + + if end_found.name == "straight-rail" then + if end_rail_dir == dirs.north or end_rail_dir == dirs.south then + append_rail_dir = dirs.north + if end_dir == defines.rail_direction.front then + append_rail_pos = {end_rail_pos.x+0, end_rail_pos.y-2} + else + append_rail_pos = {end_rail_pos.x+0, end_rail_pos.y+2} + end + + elseif end_rail_dir == dirs.east or end_rail_dir == dirs.west then + append_rail_dir = dirs.east + if end_dir == defines.rail_direction.front then + append_rail_pos = {end_rail_pos.x+2, end_rail_pos.y+0} + else + append_rail_pos = {end_rail_pos.x-2, end_rail_pos.y-0} + end + + elseif end_rail_dir == dirs.northeast then + append_rail_dir = dirs.southwest + if end_dir == defines.rail_direction.front then + append_rail_pos = {end_rail_pos.x+0, end_rail_pos.y-2} + else + append_rail_pos = {end_rail_pos.x+2, end_rail_pos.y+0} + end + elseif end_rail_dir == dirs.southwest then + append_rail_dir = dirs.northeast + if end_dir == defines.rail_direction.front then + append_rail_pos = {end_rail_pos.x+0, end_rail_pos.y+2} + else + append_rail_pos = {end_rail_pos.x-2, end_rail_pos.y+0} + end + + elseif end_rail_dir == dirs.southeast then + append_rail_dir = dirs.northwest + if end_dir == defines.rail_direction.front then + append_rail_pos = {end_rail_pos.x+2, end_rail_pos.y+0} + else + append_rail_pos = {end_rail_pos.x+0, end_rail_pos.y+2} + end + elseif end_rail_dir == dirs.northwest then + append_rail_dir = dirs.southeast + if end_dir == defines.rail_direction.front then + append_rail_pos = {end_rail_pos.x-2, end_rail_pos.y+0} + else + append_rail_pos = {end_rail_pos.x+0, end_rail_pos.y-2} + end + end + + elseif end_found.name == "curved-rail" then + --Make sure to use the reported end direction for curved rails + is_end_rail, end_rail_dir, comment = check_end_rail(ent,pindex) + if end_rail_dir == dirs.north then + if rail_api_dir == dirs.south then + append_rail_pos = {end_rail_pos.x-2, end_rail_pos.y-6} + append_rail_dir = dirs.north + elseif rail_api_dir == dirs.southwest then + append_rail_pos = {end_rail_pos.x-0, end_rail_pos.y-6} + append_rail_dir = dirs.north + end + elseif end_rail_dir == dirs.northeast then + if rail_api_dir == dirs.northeast then + append_rail_pos = {end_rail_pos.x+2, end_rail_pos.y-4} + append_rail_dir = dirs.northwest + elseif rail_api_dir == dirs.east then + append_rail_pos = {end_rail_pos.x+2, end_rail_pos.y-4} + append_rail_dir = dirs.southeast + end + elseif end_rail_dir == dirs.east then + if rail_api_dir == dirs.west then + append_rail_pos = {end_rail_pos.x+4, end_rail_pos.y-2} + append_rail_dir = dirs.east + elseif rail_api_dir == dirs.northwest then + append_rail_pos = {end_rail_pos.x+4, end_rail_pos.y-0} + append_rail_dir = dirs.east + end + elseif end_rail_dir == dirs.southeast then + if rail_api_dir == dirs.southeast then + append_rail_pos = {end_rail_pos.x+2, end_rail_pos.y+2} + append_rail_dir = dirs.northeast + elseif rail_api_dir == dirs.south then + append_rail_pos = {end_rail_pos.x+2, end_rail_pos.y+2} + append_rail_dir = dirs.southwest + end + elseif end_rail_dir == dirs.south then + if rail_api_dir == dirs.north then + append_rail_pos = {end_rail_pos.x-0, end_rail_pos.y+4} + append_rail_dir = dirs.north + elseif rail_api_dir == dirs.northeast then + append_rail_pos = {end_rail_pos.x-2, end_rail_pos.y+4} + append_rail_dir = dirs.north + end + elseif end_rail_dir == dirs.southwest then + if rail_api_dir == dirs.southwest then + append_rail_pos = {end_rail_pos.x-4, end_rail_pos.y+2} + append_rail_dir = dirs.southeast + elseif rail_api_dir == dirs.west then + append_rail_pos = {end_rail_pos.x-4, end_rail_pos.y+2} + append_rail_dir = dirs.northwest + end + elseif end_rail_dir == dirs.west then + if rail_api_dir == dirs.east then + append_rail_pos = {end_rail_pos.x-6, end_rail_pos.y-0} + append_rail_dir = dirs.east + elseif rail_api_dir == dirs.southeast then + append_rail_pos = {end_rail_pos.x-6, end_rail_pos.y-2} + append_rail_dir = dirs.east + end + elseif end_rail_dir == dirs.northwest then + if rail_api_dir == dirs.north then + append_rail_pos = {end_rail_pos.x-4, end_rail_pos.y-4} + append_rail_dir = dirs.northeast + elseif rail_api_dir == dirs.northwest then + append_rail_pos = {end_rail_pos.x-4, end_rail_pos.y-4} + append_rail_dir = dirs.southwest + end + end + end + + --6. Clear trees and rocks nearby and check if the selected 2x2 space is free for building, else return + if append_rail_pos == nil then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout(end_rail_dir .. " and " .. rail_api_dir .. ", rail appending direction error.",pindex) + return + end + temp1, build_comment = mine_trees_and_rocks_in_circle(append_rail_pos,3, pindex) + if not surf.can_place_entity{name = "straight-rail", position = append_rail_pos, direction = append_rail_dir} then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout("Cannot place here to extend the rail.",pindex) + return + end + + --7. Create the appended rail and subtract 1 rail from the hand. + --game.get_player(pindex).build_from_cursor{position = append_rail_pos, direction = append_rail_dir}--acts unsolvably weird when building diagonals of rotation 5 and 7 + created_rail = surf.create_entity{name = "straight-rail", position = append_rail_pos, direction = append_rail_dir, force = game.forces.player} + game.get_player(pindex).cursor_stack.count = game.get_player(pindex).cursor_stack.count - 1 + game.get_player(pindex).play_sound{path = "entity-build/straight-rail"} + + if not (created_rail ~= nil and created_rail.valid) then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout("Rail invalid error.",pindex) + return + end + + --8. Check if the appended rail is with 4 tiles of a parallel rail. If so, delete it. + if created_rail.valid and has_parallel_neighbor(created_rail,pindex) then + game.get_player(pindex).mine_entity(created_rail,true) + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout("Cannot place, parallel rail segments should be at least 4 tiles apart.",pindex) + end + + --9. Check if the appended rail has created an intersection. If so, notify the player. + if created_rail.valid and is_intersection_rail(created_rail,pindex) then + printout("Intersection created.",pindex) + end + +end + +--laterdo maybe revise build-item-in-hand for single placed rails so that you can have more control on it. Create a new place single rail function +--function place_single_rail(pindex) +--end + +--Counts rails within range of a selected rail. +function count_rails_within_range(rail, range, pindex) + --1. Scan around the rail for other rails + local counter = 0 + local pos = rail.position + local scan_area = {{pos.x-range,pos.y-range},{pos.x+range,pos.y+range}} + local ents = game.get_player(pindex).surface.find_entities_filtered{area = scan_area, name = "straight-rail"} + for i,other_rail in ipairs(ents) do + --2. Increase counter for each straight rail + counter = counter + 1 + end + ents = game.get_player(pindex).surface.find_entities_filtered{area = scan_area, name = "curved-rail"} + for i,other_rail in ipairs(ents) do + --3. Increase counter for each curved rail + counter = counter + 1 + end + --Draw the range for visual debugging + rendering.draw_circle{color = {0, 1, 0}, radius = range, width = range, target = rail, surface = rail.surface,time_to_live = 100} + return counter +end + +--Checks if the rail is parallel to another neighboring segment. +function has_parallel_neighbor(rail, pindex) + --1. Scan around the rail for other rails + local pos = rail.position + local dir = rail.direction + local range = 4 + if dir % 2 == 1 then + range = 3 + end + local scan_area = {{pos.x-range,pos.y-range},{pos.x+range,pos.y+range}} + local ents = game.get_player(pindex).surface.find_entities_filtered{area = scan_area, name = "straight-rail"} + for i,other_rail in ipairs(ents) do + --2. For each rail, does it have the same rotation but a different segment? If yes return true. + local pos2 = other_rail.position + if rail.direction == other_rail.direction and not rail.is_rail_in_same_rail_segment_as(other_rail) then + --3. Also ignore cases where the rails are directly facing each other so that they can be connected + if (pos.x ~= pos2.x) and (pos.y ~= pos2.y) and (math.abs(pos.x - pos2.x) - math.abs(pos.y - pos2.y)) > 1 then + --4. Parallel neighbor found + rendering.draw_circle{color = {1, 0, 0},radius = range,width = range,target = pos,surface = rail.surface,time_to_live = 100} + return true + end + end + end + --4. No parallel neighbor found + return false +end + +--Checks if the rail is amid an intersection. +function is_intersection_rail(rail, pindex) + --1. Scan around the rail for other rails + local pos = rail.position + local dir = rail.direction + local scan_area = {{pos.x-1,pos.y-1},{pos.x+1,pos.y+1}} + local ents = game.get_player(pindex).surface.find_entities_filtered{area = scan_area, name = "straight-rail"} + for i,other_rail in ipairs(ents) do + --2. For each rail, does it have a different rotation and a different segment? If yes return true. + local dir_2 = other_rail.direction + dir = dir % 4 + dir_2 = dir_2 % 4 + if dir ~= dir_2 and not rail.is_rail_in_same_rail_segment_as(other_rail) then + rendering.draw_circle{color = {0, 0, 1},radius = 1.5,width = 1.5,target = pos,surface = rail.surface,time_to_live = 100} + return true + end + end + return false +end + +--Places a chain signal pair around a rail depending on its direction. May fail if the spots are full. +function place_chain_signal_pair(rail,pindex) + local stack = game.get_player(pindex).cursor_stack + local stack2 = nil + local build_comment = "no comment" + local successful = true + local dir = rail.direction + local pos = rail.position + local surf = rail.surface + local can_place_all = true + + --1. Check if signals can be placed, based on direction + if dir == dirs.north or dir == dirs.south then + can_place_all = can_place_all and surf.can_place_entity{name = "rail-chain-signal", position = {pos.x+1, pos.y}, direction = dirs.south, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "rail-chain-signal", position = {pos.x-2, pos.y}, direction = dirs.north, force = game.forces.player} + elseif dir == dirs.east or dir == dirs.west then + can_place_all = can_place_all and surf.can_place_entity{name = "rail-chain-signal", position = {pos.x, pos.y-2}, direction = dirs.east, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "rail-chain-signal", position = {pos.x, pos.y+1}, direction = dirs.west, force = game.forces.player} + elseif dir == dirs.northeast then + can_place_all = can_place_all and surf.can_place_entity{name = "rail-chain-signal", position = {pos.x-1, pos.y-0}, direction = dirs.northwest, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "rail-chain-signal", position = {pos.x+1, pos.y-2}, direction = dirs.southeast, force = game.forces.player} + elseif dir == dirs.southwest then + can_place_all = can_place_all and surf.can_place_entity{name = "rail-chain-signal", position = {pos.x-2, pos.y+1}, direction = dirs.northwest, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "rail-chain-signal", position = {pos.x+0, pos.y-1}, direction = dirs.southeast, force = game.forces.player} + elseif dir == dirs.southeast then + can_place_all = can_place_all and surf.can_place_entity{name = "rail-chain-signal", position = {pos.x-1, pos.y-1}, direction = dirs.northeast, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "rail-chain-signal", position = {pos.x+1, pos.y+1}, direction = dirs.southwest, force = game.forces.player} + elseif dir == dirs.northwest then + can_place_all = can_place_all and surf.can_place_entity{name = "rail-chain-signal", position = {pos.x-2, pos.y-2}, direction = dirs.northeast, force = game.forces.player} + can_place_all = can_place_all and surf.can_place_entity{name = "rail-chain-signal", position = {pos.x+0, pos.y+0}, direction = dirs.southwest, force = game.forces.player} + else + successful = false + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + build_comment = "direction error" + return successful, build_comment + end + + if not can_place_all then + successful = false + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + build_comment = "cannot place" + return successful, build_comment + end + + --2. Check if there are already chain signals or rail signals nearby. If yes, stop. + local signals_found = 0 + local signals = surf.find_entities_filtered{position = pos, radius = 3, name="rail-chain-signal"} + for i,signal in ipairs(signals) do + signals_found = signals_found + 1 + end + local signals = surf.find_entities_filtered{position = pos, radius = 3, name="rail-signal"} + for i,signal in ipairs(signals) do + signals_found = signals_found + 1 + end + if signals_found > 0 then + successful = false + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + build_comment = "Too close to existing signals." + return successful, build_comment + end + + --3. Check whether the player has enough rail chain signals. + if not (stack.valid and stack.valid_for_read and stack.name == "rail-chain-signal" and stack.count >= 2) then + --Check if the inventory has one instead + if players[pindex].inventory.lua_inventory.get_item_count("rail-chain-signal") < 2 then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + build_comment = "You need to have at least 2 rail chain signals on you." + successful = false + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + return successful, build_comment + else + --Take from the inventory. + stack2 = players[pindex].inventory.lua_inventory.find_item_stack("rail-chain-signal") + game.get_player(pindex).cursor_stack.swap_stack(stack2) + stack = game.get_player(pindex).cursor_stack + players[pindex].inventory.max = #players[pindex].inventory.lua_inventory + end + end + + --4. Place the signals. + if dir == dirs.north or dir == dirs.south then + surf.create_entity{name = "rail-chain-signal", position = {pos.x+1, pos.y}, direction = dirs.south, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal", position = {pos.x-2, pos.y}, direction = dirs.north, force = game.forces.player} + elseif dir == dirs.east or dir == dirs.west then + surf.create_entity{name = "rail-chain-signal", position = {pos.x, pos.y-2}, direction = dirs.east, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal", position = {pos.x, pos.y+1}, direction = dirs.west, force = game.forces.player} + elseif dir == dirs.northeast then + surf.create_entity{name = "rail-chain-signal", position = {pos.x-1, pos.y-0}, direction = dirs.northwest, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal", position = {pos.x+1, pos.y-2}, direction = dirs.southeast, force = game.forces.player} + elseif dir == dirs.southwest then + surf.create_entity{name = "rail-chain-signal", position = {pos.x-2, pos.y+1}, direction = dirs.northwest, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal", position = {pos.x+0, pos.y-1}, direction = dirs.southeast, force = game.forces.player} + elseif dir == dirs.southeast then + surf.create_entity{name = "rail-chain-signal", position = {pos.x-1, pos.y-1}, direction = dirs.northeast, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal", position = {pos.x+1, pos.y+1}, direction = dirs.southwest, force = game.forces.player} + elseif dir == dirs.northwest then + surf.create_entity{name = "rail-chain-signal", position = {pos.x-2, pos.y-2}, direction = dirs.northeast, force = game.forces.player} + surf.create_entity{name = "rail-chain-signal", position = {pos.x+0, pos.y+0}, direction = dirs.southwest, force = game.forces.player} + else + successful = false + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + build_comment = "direction error" + return successful, build_comment + end + + --Reduce the signal count and restore the cursor and wrap up + game.get_player(pindex).cursor_stack.count = game.get_player(pindex).cursor_stack.count - 2 + game.get_player(pindex).clear_cursor() + + game.get_player(pindex).play_sound{path = "entity-build/rail-chain-signal"} + game.get_player(pindex).play_sound{path = "entity-build/rail-chain-signal"} + return successful, build_comment +end + +--Deletes rail signals around a rail. +function destroy_signals(rail) + local chains = rail.surface.find_entities_filtered{position = rail.position, radius = 2, name = "rail-chain-signal"} + for i,chain in ipairs(chains) do + chain.destroy() + end + local signals = rail.surface.find_entities_filtered{position = rail.position, radius = 2, name = "rail-signal"} + for i,signal in ipairs(signals) do + signal.destroy() + end +end + +--Mines for the player the rail signals around a rail. +function mine_signals(rail,pindex) + local chains = rail.surface.find_entities_filtered{position = rail.position, radius = 2, name = "rail-chain-signal"} + for i,chain in ipairs(chains) do + game.get_player(pindex).mine_entity(chain,true) + end + local signals = rail.surface.find_entities_filtered{position = rail.position, radius = 2, name = "rail-signal"} + for i,signal in ipairs(signals) do + game.get_player(pindex).mine_entity(signal,true) + end +end + + +--Places a train stop facing the direction of the end rail. +function build_train_stop(anchor_rail, pindex) + local build_comment = "" + local surf = game.get_player(pindex).surface + local stack = game.get_player(pindex).cursor_stack + local stack2 = nil + local pos = nil + local dir = -1 + local build_area = nil + local can_place_all = true + local is_end_rail + + --1. Firstly, check if the player has a train stop in hand + if not (stack.valid and stack.valid_for_read and stack.name == "train-stop" and stack.count > 0) then + --Check if the inventory has enough + if players[pindex].inventory.lua_inventory.get_item_count("train-stop") < 1 then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout("You need at least 1 train stop in your inventory to build this turn.", pindex) + return + else + --Take from the inventory. + stack2 = players[pindex].inventory.lua_inventory.find_item_stack("train-stop") + game.get_player(pindex).cursor_stack.swap_stack(stack2) + stack = game.get_player(pindex).cursor_stack + players[pindex].inventory.max = #players[pindex].inventory.lua_inventory + end + end + + --2. Secondly, find the direction based on end rail or player direction + is_end_rail, end_rail_dir, build_comment = check_end_rail(anchor_rail,pindex) + if is_end_rail then + dir = end_rail_dir + else + --Choose the dir based on player direction + if anchor_rail.direction == dirs.north or anchor_rail.direction == dirs.south then + if players[pindex].player_direction == dirs.north or players[pindex].player_direction == dirs.east then + dir = dirs.north + elseif players[pindex].player_direction == dirs.south or players[pindex].player_direction == dirs.west then + dir = dirs.south + end + elseif anchor_rail.direction == dirs.east or anchor_rail.direction == dirs.west then + if players[pindex].player_direction == dirs.north or players[pindex].player_direction == dirs.east then + dir = dirs.east + elseif players[pindex].player_direction == dirs.south or players[pindex].player_direction == dirs.west then + dir = dirs.west + end + end + end + pos = anchor_rail.position + if dir == dirs.northeast or dir == dirs.southeast or dir == dirs.southwest or dir == dirs.northwest then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout("This structure is for horizontal or vertical end rails only.", pindex) + game.get_player(pindex).clear_cursor() + return + end + + --3. Clear trees and rocks in the build area + temp1, build_comment = mine_trees_and_rocks_in_circle(pos,3, pindex) + + --4. Check if every object can be placed + if dir == dirs.north then + can_place_all = can_place_all and surf.can_place_entity{name = "train-stop", position = {pos.x+2, pos.y+0}, direction = dirs.north, force = game.forces.player} + + elseif dir == dirs.east then + can_place_all = can_place_all and surf.can_place_entity{name = "train-stop", position = {pos.x+0, pos.y+2}, direction = dirs.east, force = game.forces.player} + + elseif dir == dirs.south then + can_place_all = can_place_all and surf.can_place_entity{name = "train-stop", position = {pos.x-2, pos.y+0}, direction = dirs.south, force = game.forces.player} + + elseif dir == dirs.west then + can_place_all = can_place_all and surf.can_place_entity{name = "train-stop", position = {pos.x-0, pos.y-2}, direction = dirs.west, force = game.forces.player} + + end + + if not can_place_all then + game.get_player(pindex).play_sound{path = "utility/cannot_build"} + printout("Building area occupied, possibly by the player. Cursor mode recommended.", pindex) + game.get_player(pindex).clear_cursor() + return + end + + --5. Build the five rail entities to create the structure + if dir == dirs.north then + surf.create_entity{name = "train-stop", position = {pos.x+2, pos.y+0}, direction = dirs.north, force = game.forces.player} + + elseif dir == dirs.east then + surf.create_entity{name = "train-stop", position = {pos.x+0, pos.y+2}, direction = dirs.east, force = game.forces.player} + + elseif dir == dirs.south then + surf.create_entity{name = "train-stop", position = {pos.x-2, pos.y+0}, direction = dirs.south, force = game.forces.player} + + elseif dir == dirs.west then + surf.create_entity{name = "train-stop", position = {pos.x-0, pos.y-2}, direction = dirs.west, force = game.forces.player} + + end + + --6 Remove 5 rail units from the player's hand + game.get_player(pindex).cursor_stack.count = game.get_player(pindex).cursor_stack.count - 1 + game.get_player(pindex).clear_cursor() + + --7. Sounds and results + game.get_player(pindex).play_sound{path = "entity-build/train-stop"} + printout("Train stop built facing" .. direction_lookup(dir) .. ", " .. build_comment, pindex) + return +end + +--Converts the entity orientation value to a heading +function get_heading(ent) + local heading = "unknown" + if ent == nil then + return "nill error" + end + local ori = ent.orientation + if ori < 0.0625 then + heading = "North" + elseif ori < 0.1875 then + heading = "Northeast" + elseif ori < 0.3125 then + heading = "East" + elseif ori < 0.4375 then + heading = "Southeast" + elseif ori < 0.5625 then + heading = "South" + elseif ori < 0.6875 then + heading = "Southwest" + elseif ori < 0.8125 then + heading = "West" + elseif ori < 0.9375 then + heading = "Northwest" + else + heading = "North" + end + return heading +end + +--Directions lookup table +function direction_lookup(dir) + local reading = "unknown" + if dir < 0 then + return "direction error 1" + end + + if dir == dirs.north then + reading = "North" + elseif dir == dirs.northeast then + reading = "Northeast" + elseif dir == dirs.east then + reading = "East" + elseif dir == dirs.southeast then + reading = "Southeast" + elseif dir == dirs.south then + reading = "South" + elseif dir == dirs.southwest then + reading = "Southwest" + elseif dir == dirs.west then + reading = "West" + elseif dir == dirs.northwest then + reading = "Northwest" + elseif dir == 99 then --Internally defined + reading = "Here" + else + reading = "direction error 2" + end + return reading +end + + +function rail_builder_open(pindex, rail) + --Set the player menu tracker to this menu + players[pindex].menu = "rail_builder" + players[pindex].in_menu = true + + --Set the menu line counter to 0 + players[pindex].rail_builder.index = 0 + + --Determine rail type + local is_end_rail, end_dir, comment = check_end_rail(rail,pindex) + local dir = rail.direction + if is_end_rail then + if dir == dirs.north or dir == dirs.east or dir == dirs.south or dir == dirs.west then + --Straight end rails + players[pindex].rail_builder.rail_type = 1 + players[pindex].rail_builder.index_max = 6 + else + --Diagonal end rails + players[pindex].rail_builder.rail_type = 2 + players[pindex].rail_builder.index_max = 2 + end + else + if dir == dirs.north or dir == dirs.east or dir == dirs.south or dir == dirs.west then + --Straight mid rails + players[pindex].rail_builder.rail_type = 3 + players[pindex].rail_builder.index_max = 2 + else + --Diagonal mid rails + players[pindex].rail_builder.rail_type = 4 + players[pindex].rail_builder.index_max = 2 + end + end + + --Play sound + game.get_player(pindex).play_sound{path = "Open-Inventory-Sound"} + + --Load menu + players[pindex].rail_builder.rail = rail + rail_builder(pindex, false) +end + + +function rail_builder_close(pindex, mute_in) + local mute = mute_in or false + --Set the player menu tracker to none + players[pindex].menu = "none" + players[pindex].in_menu = false + + --Set the menu line counter to 0 + players[pindex].rail_builder.index = 0 + + --play sound + if not mute then + game.get_player(pindex).play_sound{path="Close-Inventory-Sound"} + end +end + + +function rail_builder_up(pindex) + --Decrement the index + players[pindex].rail_builder.index = players[pindex].rail_builder.index - 1 + + --Check the index against the limit + if players[pindex].rail_builder.index < 0 then + players[pindex].rail_builder.index = 0 + game.get_player(pindex).play_sound{path = "Mine-Building"} + else + --Play sound + game.get_player(pindex).play_sound{path = "Inventory-Move"} + end + + --Load menu + rail_builder(pindex, false) +end + + +function rail_builder_down(pindex) + --Increment the index + players[pindex].rail_builder.index = players[pindex].rail_builder.index + 1 + + --Check the index against the limit + if players[pindex].rail_builder.index > players[pindex].rail_builder.index_max then + players[pindex].rail_builder.index = players[pindex].rail_builder.index_max + game.get_player(pindex).play_sound{path = "Mine-Building"} + else + --Play sound + game.get_player(pindex).play_sound{path = "Inventory-Move"} + end + + --Load menu + rail_builder(pindex, false) +end + + +--Builder menu to build rail structures +function rail_builder(pindex, clicked_in) + local clicked = clicked_in + local comment = "" + local menu_line = players[pindex].rail_builder.index + local rail_type = players[pindex].rail_builder.rail_type + local rail = players[pindex].rail_builder.rail + + if rail == nil then + comment = " Rail nil error " + printout(comment,pindex) + rail_builder_close(pindex, false) + return + end + + if menu_line == 0 then + comment = comment .. "Select a structure to build by going up or down this menu, attempt to build it via LEFT BRACKET, " + printout(comment,pindex) + return + end + + if rail_type == 1 then + --Straight end rails + if menu_line == 1 then + if not clicked then + comment = comment .. "Left turn 45 degrees" + printout(comment,pindex) + else + --Build it here + build_rail_turn_left_45_degrees(rail, pindex) + end + elseif menu_line == 2 then + if not clicked then + comment = comment .. "Right turn 45 degrees" + printout(comment,pindex) + else + --Build it here + build_rail_turn_right_45_degrees(rail, pindex) + end + elseif menu_line == 3 then + if not clicked then + comment = comment .. "Left turn 90 degrees" + printout(comment,pindex) + else + --Build it here + build_rail_turn_left_90_degrees(rail, pindex) + end + elseif menu_line == 4 then + if not clicked then + comment = comment .. "Right turn 90 degrees" + printout(comment,pindex) + else + --Build it here + build_rail_turn_right_90_degrees(rail, pindex) + end + elseif menu_line == 5 then + if not clicked then + comment = comment .. "Train stop facing end rail direction" + printout(comment,pindex) + else + --Build it here + build_train_stop(rail, pindex) + end + --elseif menu_line == 6 then + -- if not clicked then + -- comment = comment .. "Plus intersection" + -- printout(comment,pindex) + -- else + -- --Build it here + -- build_small_plus_intersection(rail, pindex) + -- end + end + elseif rail_type == 2 then + --Diagonal end rails + if menu_line == 1 then + if not clicked then + comment = comment .. "Left turn 45 degrees" + printout(comment,pindex) + else + --Build it here + build_rail_turn_left_45_degrees(rail, pindex) + end + elseif menu_line == 2 then + if not clicked then + comment = comment .. "Right turn 45 degrees" + printout(comment,pindex) + else + --Build it here + build_rail_turn_right_45_degrees(rail, pindex) + end + end + elseif rail_type == 3 then + --Straight mid rails + if menu_line == 1 then + if not clicked then + comment = comment .. "Pair of chain rail signals." + printout(comment,pindex) + else + local success, build_comment = place_chain_signal_pair(rail,pindex) + if success then + comment = "Signals placed." + else + comment = comment .. build_comment + end + printout(comment,pindex) + end + elseif menu_line == 2 then + if not clicked then + comment = comment .. "Clear rail signals" + printout(comment,pindex) + else + mine_signals(rail,pindex) + printout("Signals cleared.",pindex) + end + end + --After implementing junctions we will allow building mid rail train stops. This is commented out for now. + --if menu_line == 3 then + -- if not clicked then + -- comment = comment .. "Train stop facing the player direction" + -- printout(comment,pindex) + -- else + -- --Build it here + -- build_train_stop(rail, pindex) + -- end + --end + elseif rail_type == 4 then + --Diagonal mid rails + if menu_line == 1 then + if not clicked then + comment = comment .. "Pair of chain rail signals." + printout(comment,pindex) + else + local success, build_comment = place_chain_signal_pair(rail,pindex) + if success then + comment = "Signals placed." + else + comment = comment .. build_comment + end + printout(comment,pindex) + end + elseif menu_line == 2 then + if not clicked then + comment = comment .. "Clear rail signals" + printout(comment,pindex) + else + mine_signals(rail,pindex) + printout("Signals cleared.",pindex) + end + end + end + return +end + +--This menu opens when the player presses LEFT BRACKET on a locomotive that they are either riding or looking at with the cursor. +function train_menu(menu_index, pindex, clicked, other_input) + local index = menu_index + local other = other_input or -1 + local locomotive = nil + if game.get_player(pindex).vehicle ~= nil and game.get_player(pindex).vehicle.name == "locomotive" then + locomotive = game.get_player(pindex).vehicle + players[pindex].train_menu.locomotive = locomotive + elseif players[pindex].tile.ents[1] ~= nil and players[pindex].tile.ents[1].name == "locomotive" then + locomotive = players[pindex].tile.ents[1] + players[pindex].train_menu.locomotive = locomotive + else + players[pindex].train_menu.locomotive = nil + printout("Train menu requires a locomotive", pindex) + return + end + local train = locomotive.train + + if index == 0 then + --Give basic info about this train, such as its name and ID. Instructions. + printout("Train ".. get_train_name(train) .. ", with ID " .. train.id + .. ", Press UP ARROW and DOWN ARROW to navigate options, press LEFT BRACKET to select an option or press E to exit this menu.", pindex) + elseif index == 1 then + printout("Train state, " .. get_train_state_info(train) .. " ", pindex) + elseif index == 2 then + if not clicked then + printout("Rename this train, press LEFT BRACKET.", pindex) + else + if train.locomotives == nil then + printout("The train must have locomotives for it to be named.", pindex) + return + end + printout("Enter a new name for this train, then press ENTER to confirm.", pindex) + players[pindex].train_menu.renaming = true + local frame = game.get_player(pindex).gui.screen.add{type = "frame", name = "train-rename"} + frame.bring_to_front() + frame.force_auto_center() + frame.focus() + game.get_player(pindex).opened = frame + local input = frame.add{type="textfield", name = "input"} + input.focus() + end + elseif index == 3 then + local locos = train.locomotives + printout("Vehicle counts, " .. #locos["front_movers"] .. " locomotives facing front, " + .. #locos["back_movers"] .. " locomotives facing back, " .. #train.cargo_wagons .. " cargo wagons, " + .. #train.fluid_wagons .. " fluid wagons, ", pindex) + elseif index == 4 then + --Train contents + printout("Cargo " .. train_top_contents_info(train) .. " ", pindex) + elseif index == 5 then + --Instant schedule + if not clicked then + local result = "" + local namelist = "" + local schedule = train.schedule + local records = {} + if schedule ~= nil then + records = schedule.records + end + if #records == 0 then + result = " No schedule, " + else + for i,record in ipairs(records) do + if record.station ~= nil then + namelist = namelist .. record.station .. ", " + end + end + result = " Train schedule has stations " .. namelist + end + printout(result .. " press LEFT BRACKET to set an instant schedule where the train waits at each reachable station for 5 minutes. ", pindex) + else + local comment = instant_schedule(train) + printout(comment,pindex) + end + elseif index == 6 then + --Review schedule + if not clicked then + local result = "" + local namelist = "" + local schedule = train.schedule + local records = {} + if schedule ~= nil then + records = schedule.records + end + if #records == 0 then + result = " No schedule, " + else + for i,record in ipairs(records) do + if record.station ~= nil then + namelist = namelist .. record.station .. ", " + end + end + result = " Train schedule has stations " .. namelist + end + printout(result .. " press LEFT BRACKET to clear the schedule and drive manually. ", pindex) + else + train.schedule = nil + printout("Train schedule cleared.",pindex) + end + elseif index == 7 then + --Click here to travel to the next train stop + if not clicked then + printout("Single-time travel to a new train stop, press LEFT BRACKET, the train waits until all passengers get off, then resumes its schedule.", pindex) + else + local comment = sub_automatic_travel_to_other_stop(train) + printout(comment,pindex) + end + end + --[[ Train menu options summary + 0. name, id, menu instructions + 1. Train state , destination + 2. click to rename + 3. vehicles + 4. Cargo + 5. Review and set automatic schedule + 6. Review and clear automatic schedule. + 7. Subautomatic travel. + ]] +end + + +function train_menu_open(pindex) + --Set the player menu tracker to this menu + players[pindex].menu = "train_menu" + players[pindex].in_menu = true + + --Set the menu line counter to 0 + players[pindex].train_menu.index = 0 + + --Play sound + game.get_player(pindex).play_sound{path = "Open-Inventory-Sound"} + + --Load menu + train_menu(players[pindex].train_menu.index, pindex, false) +end + + +function train_menu_close(pindex, mute_in) + local mute = mute_in + --Set the player menu tracker to none + players[pindex].menu = "none" + players[pindex].in_menu = false + + --Set the menu line counter to 0 + players[pindex].train_menu.index = 0 + + --play sound + if not mute then + game.get_player(pindex).play_sound{path="Close-Inventory-Sound"} + end + + --Destroy GUI + if game.get_player(pindex).gui.screen["train-rename"] ~= nil then + game.get_player(pindex).gui.screen["train-rename"].destroy() + end +end + + +function train_menu_up(pindex) + players[pindex].train_menu.index = players[pindex].train_menu.index - 1 + if players[pindex].train_menu.index < 0 then + players[pindex].train_menu.index = 0 + game.get_player(pindex).play_sound{path = "Mine-Building"} + else + --Play sound + game.get_player(pindex).play_sound{path = "Inventory-Move"} + end + --Load menu + train_menu(players[pindex].train_menu.index, pindex, false) +end + + +function train_menu_down(pindex) + players[pindex].train_menu.index = players[pindex].train_menu.index + 1 + if players[pindex].train_menu.index > 7 then + players[pindex].train_menu.index = 7 + game.get_player(pindex).play_sound{path = "Mine-Building"} + else + --Play sound + game.get_player(pindex).play_sound{path = "Inventory-Move"} + end + --Load menu + train_menu(players[pindex].train_menu.index, pindex, false) +end + + +--This menu opens when the cursor presses LEFT BRACKET on a train stop. +function train_stop_menu(menu_index, pindex, clicked, other_input) + local index = menu_index + local other = other_input or -1 + local train_stop = nil + if players[pindex].tile.ents[1] ~= nil and players[pindex].tile.ents[1].name == "train-stop" then + train_stop = players[pindex].tile.ents[1] + players[pindex].train_stop_menu.stop = train_stop + else + printout("Train stop menu error", pindex) + players[pindex].train_stop_menu.stop = nil + return + end + + if index == 0 then + printout("Train stop " .. train_stop.backer_name .. ", Press W and S to navigate options, press LEFT BRACKET to select an option or press E to exit this menu.", pindex) + elseif index == 1 then + if not clicked then + printout("Rename this stop.", pindex) + else + printout("Enter a new name for this train stop, then press ENTER to confirm.", pindex) + players[pindex].train_stop_menu.renaming = true + local frame = game.get_player(pindex).gui.screen.add{type = "frame", name = "train-stop-rename"} + frame.bring_to_front() + frame.force_auto_center() + frame.focus() + game.get_player(pindex).opened = frame + local input = frame.add{type="textfield", name = "input"} + input.focus() + end + elseif index == 2 then + printout("Note, you are recommended to set up a fast travel point near this stop.",pindex)--laterdo: add clickable option to add/remove this stop to the list. + end +end + + +function train_stop_menu_open(pindex) + --Set the player menu tracker to this menu + players[pindex].menu = "train_stop_menu" + players[pindex].in_menu = true + + --Set the menu line counter to 0 + players[pindex].train_stop_menu.index = 0 + + --Play sound + game.get_player(pindex).play_sound{path = "Open-Inventory-Sound"} + + --Load menu + train_stop_menu(players[pindex].train_stop_menu.index, pindex, false) +end + + +function train_stop_menu_close(pindex, mute_in) + local mute = mute_in + --Set the player menu tracker to none + players[pindex].menu = "none" + players[pindex].in_menu = false + + --Set the menu line counter to 0 + players[pindex].train_stop_menu.index = 0 + + --Destroy GUI + if game.get_player(pindex).gui.screen["train-stop-rename"] ~= nil then + game.get_player(pindex).gui.screen["train-stop-rename"].destroy() + end + + --play sound + if not mute then + game.get_player(pindex).play_sound{path="Close-Inventory-Sound"} + end +end + + +function train_stop_menu_up(pindex) + players[pindex].train_stop_menu.index = players[pindex].train_stop_menu.index - 1 + if players[pindex].train_stop_menu.index < 0 then + players[pindex].train_stop_menu.index = 0 + game.get_player(pindex).play_sound{path = "Mine-Building"} + else + --Play sound + game.get_player(pindex).play_sound{path = "Inventory-Move"} + end + --Load menu + train_stop_menu(players[pindex].train_stop_menu.index, pindex, false) +end + + +function train_stop_menu_down(pindex) + players[pindex].train_stop_menu.index = players[pindex].train_stop_menu.index + 1 + if players[pindex].train_stop_menu.index > 2 then + players[pindex].train_stop_menu.index = 2 + game.get_player(pindex).play_sound{path = "Mine-Building"} + else + --Play sound + game.get_player(pindex).play_sound{path = "Inventory-Move"} + end + --Load menu + train_stop_menu(players[pindex].train_stop_menu.index, pindex, false) +end + +--Returns most common items in a cargo wagon. laterdo a full inventory screen maybe. +function cargo_wagon_top_contents_info(wagon) + local result = "" + local itemset = wagon.get_inventory(defines.inventory.cargo_wagon).get_contents() + local itemtable = {} + for name, count in pairs(itemset) do + table.insert(itemtable, {name = name, count = count}) + end + table.sort(itemtable, function(k1, k2) + return k1.count > k2.count + end) + if #itemtable == 0 then + result = result .. " Contains no items. " + else + result = result .. " Contains " .. itemtable[1].name .. " times " .. itemtable[1].count .. ", " + if #itemtable > 1 then + result = result .. " and " .. itemtable[2].name .. " times " .. itemtable[2].count .. ", " + end + if #itemtable > 2 then + result = result .. " and " .. itemtable[3].name .. " times " .. itemtable[3].count .. ", " + end + if #itemtable > 3 then + result = result .. " and " .. itemtable[4].name .. " times " .. itemtable[4].count .. ", " + end + if #itemtable > 4 then + result = result .. " and " .. itemtable[5].name .. " times " .. itemtable[5].count .. ", " + end + if #itemtable > 5 then + result = result .. " and other items " + end + end + result = result .. ", Use inserters or cursor shortcuts to fill and empty this wagon. " + return result +end + +--Returns most common items in a fluid wagon or train. +function fluid_contents_info(wagon) + local result = "" + local itemset = wagon.get_fluid_contents() + local itemtable = {} + for name, amount in pairs(itemset) do + table.insert(itemtable, {name = name, amount = amount}) + end + table.sort(itemtable, function(k1, k2) + return k1.amount > k2.amount + end) + if #itemtable == 0 then + result = result .. " Contains no fluids. " + else + result = result .. " Contains " .. itemtable[1].name .. " times " .. string.format(" %.0f ", itemtable[1].amount) .. ", " + if #itemtable > 1 then + result = result .. " and " .. itemtable[2].name .. " times " .. string.format(" %.0f ", itemtable[2].amount) .. ", " + end + if #itemtable > 2 then + result = result .. " and " .. itemtable[3].name .. " times " .. string.format(" %.0f ", itemtable[3].amount) .. ", " + end + if #itemtable > 3 then + result = result .. " and other fluids " + end + end + if wagon.object_name ~= "LuaTrain" and wagon.name == "fluid-wagon" then + result = result .. ", Use pumps to fill and empty this wagon. " + end + return result +end + + +--Returns most common items and fluids in a train (sum of all wagons) +function train_top_contents_info(train) + local result = "" + local itemset = train.get_contents() + local itemtable = {} + for name, count in pairs(itemset) do + table.insert(itemtable, {name = name, count = count}) + end + table.sort(itemtable, function(k1, k2) + return k1.count > k2.count + end) + if #itemtable == 0 then + result = result .. " Contains no items, " + else + result = result .. " Contains " .. itemtable[1].name .. " times " .. itemtable[1].count .. ", " + if #itemtable > 1 then + result = result .. " and " .. itemtable[2].name .. " times " .. itemtable[2].count .. ", " + end + if #itemtable > 2 then + result = result .. " and " .. itemtable[3].name .. " times " .. itemtable[3].count .. ", " + end + if #itemtable > 3 then + result = result .. " and other items, " + end + end + result = result .. fluid_contents_info(train) + return result +end + + +--Return fuel content in a fuel inventory +function fuel_inventory_info(ent) + local result = "Contains no fuel." + local itemset = ent.get_fuel_inventory().get_contents() + local itemtable = {} + for name, count in pairs(itemset) do + table.insert(itemtable, {name = name, count = count}) + end + table.sort(itemtable, function(k1, k2) + return k1.count > k2.count + end) + if #itemtable > 0 then + result = "Contains as fuel, " .. itemtable[1].name .. " times " .. itemtable[1].count .. " " + if #itemtable > 1 then + result = result .. " and " .. itemtable[2].name .. " times " .. itemtable[2].count .. " " + end + if #itemtable > 2 then + result = result .. " and " .. itemtable[3].name .. " times " .. itemtable[3].count .. " " + end + end + return result +end + + +--For the selected train, adds every reachable train stop to its schedule with the waiting condition of 5 minutes. +function instant_schedule(train) + local surf = train.front_stock.surface + local train_stops = surf.get_train_stops() + local valid_stops = 0 + train.schedule = nil + for i,stop in ipairs(train_stops) do + --Add the stop to the schedule's first row + local wait_condition_1 = {type = "time" , ticks = 18000 , compare_type = "and"} + local new_record = {wait_conditions = {wait_condition_1}, station = stop.backer_name, temporary = false} + + local schedule = train.schedule + if schedule == nil then + schedule = {current = 1, records = {new_record}} + --game.get_player(pindex).print("made new schedule") + else + local records = schedule.records + table.insert(records,1, new_record) + --game.get_player(pindex).print("added to schedule row 1, schedule length now " .. #records) + end + train.schedule = schedule + + --Make the train aim for the stop + train.go_to_station(1) + train.recalculate_path() + + --React according to valid path + if not train.has_path then + --Clear the invalid schedule record + --game.get_player(pindex).print("invalid " .. stop.backer_name) + local schedule = train.schedule + if schedule ~= nil then + --game.get_player(pindex).print("Removing " .. stop.backer_name) + local records = schedule.records + table.remove(records, 1) + if #records == 0 then + train.schedule = nil + train.manual_mode = true + else + train.schedule = schedule + end + --game.get_player(pindex).print("schedule length now " .. #records) + end + else + --Valid station and path selected. + valid_stops = valid_stops + 1 + --game.get_player(pindex).print("valid " .. stop.backer_name .. ", path size " .. train.path.size) + end + end + if valid_stops == 0 then + --Announce error to all passengers + str = " No reachable trainstops detected. Check whether you have locomotives facing both directions as required." + for i,player in ipairs(train.passengers) do + players[player.index].last = str + localised_print{"","out ",str} + end + elseif valid_stops == 1 then + --Announce error to all passengers + str = " Only one reachable trainstop detected. Check whether you have locomotives facing both directions as required." + for i,player in ipairs(train.passengers) do + players[player.index].last = str + localised_print{"","out ",str} + end + else + str = "Train schedule created with " .. valid_stops .. " stops. " + end + return str +end + + + +--Subautomatic one-time travel to a reachable train stop that is at least 3 rails away. Does not delete the train schedule. +function sub_automatic_travel_to_other_stop(train) + local surf = train.front_stock.surface + local train_stops = surf.get_train_stops() + for i,stop in ipairs(train_stops) do + --Set a stop + local wait_condition_1 = {type = "passenger_not_present", compare_type = "and"} + local new_record = {wait_conditions = {wait_condition_1}, station = stop.backer_name, temporary = true} + + --train.schedule = {current = 1, records = {new_record}} + local schedule = train.schedule + if schedule == nil then + schedule = {current = 1, records = {new_record}} + --game.get_player(pindex).print("made new schedule") + else + local records = schedule.records + table.insert(records,1, new_record) + end + train.schedule = schedule + + --Make the train aim for the stop + train.go_to_station(1) + if not train.has_path or train.path.size < 3 then + --Invalid path or path to an station nearby + local records = schedule.records + table.remove(records, 1) + if #records == 0 then + train.schedule = nil + train.manual_mode = true + else + train.schedule = schedule + end + else + --Valid station and path selected. + --(do nothing) + end + + end + + if train.path_end_stop == nil then + --Announce error to all passengers + str = " No reachable trainstops detected. Check whether you have locomotives facing both directions as required." + for i,player in ipairs(train.passengers) do + players[player.index].last = str + localised_print{"","out ",str} + end + else + str = "Path set." + end + return str +end + + +--Plays a train track alert sound for every player standing on or facing train tracks that meet the condition. +function play_train_track_alert_sounds(step) + for pindex, player in pairs(players) do + --Check if the player is standing on a rail + local p = game.get_player(pindex) + local floor_ent = p.surface.find_entities_filtered{position = p.position, limit = 1}[1] + local facing_ent = players[p.index].tile.ents[1] + local found_rail = nil + local skip = false + if p.driving then + skip = true + elseif facing_ent ~= nil and facing_ent.valid and (facing_ent.name == "straight-rail" or facing_ent.name == "curved-rail") then + found_rail = facing_ent + elseif floor_ent ~= nil and floor_ent.valid and (floor_ent.name == "straight-rail" or floor_ent.name == "curved-rail") then + found_rail = floor_ent + else + --Check further around the player because the other scans do not cover the back + local floor_ent_2 = p.surface.find_entities_filtered{name = {"straight-rail","curved-rail"}, position = p.position, radius = 1, limit = 1}[1] + if floor_ent_2 ~= nil and floor_ent_2.valid then + found_rail = floor_ent_2 + else + skip = true + end + end + + --Condition for step 1: Any moving trains nearby (within 200 tiles) + if not skip and step == 1 then + local trains = p.surface.get_trains() + for i,train in ipairs(trains) do + if train.speed ~= 0 and (util.distance(p.position,train.front_stock.position) < 100 or util.distance(p.position,train.back_stock.position) < 200) then + p.play_sound{path = "utility/blueprint_selection_ended"} + rendering.draw_circle{color = {1, 1, 0},radius = 2,width = 2,target = found_rail.position,surface = found_rail.surface,time_to_live = 10} + end + end + --Condition for step 2: Any moving trains nearby (within 100 tiles), and heading towards the player + elseif not skip and step == 2 then + local trains = p.surface.get_trains() + for i,train in ipairs(trains) do + if train.speed ~= 0 and (util.distance(p.position,train.front_stock.position) < 100 or util.distance(p.position,train.back_stock.position) < 100) + and ((train.speed > 0 and util.distance(p.position,train.front_stock.position) <= util.distance(p.position,train.back_stock.position)) + or (train.speed < 0 and util.distance(p.position,train.front_stock.position) >= util.distance(p.position,train.back_stock.position))) then + p.play_sound{path = "utility/blueprint_selection_ended"} + rendering.draw_circle{color = {1, 1, 0},radius = 3,width = 3,target = found_rail.position,surface = found_rail.surface,time_to_live = 10} + end + end + --Condition for step 3: Any moving trains in the same rail block, and heading towards the player OR if the block inbound signals are yellow + elseif not skip and step == 3 then + local trains = p.surface.get_trains() + for i,train in ipairs(trains) do + if train.speed ~= 0 and (found_rail.is_rail_in_same_rail_block_as(train.front_rail) or found_rail.is_rail_in_same_rail_block_as(train.back_rail)) + and ((train.speed > 0 and util.distance(p.position,train.front_stock.position) <= util.distance(p.position,train.back_stock.position)) + or (train.speed < 0 and util.distance(p.position,train.front_stock.position) >= util.distance(p.position,train.back_stock.position))) then + p.play_sound{path = "utility/new_objective"} + p.play_sound{path = "utility/new_objective"} + rendering.draw_circle{color = {1, 0, 0},radius = 4,width = 4,target = found_rail.position,surface = found_rail.surface,time_to_live = 10} + end + end + local signals = found_rail.get_inbound_signals() + for i,signal in ipairs(signals) do + if signal.signal_state == defines.signal_state.reserved then + p.play_sound{path = "utility/new_objective"} + p.play_sound{path = "utility/new_objective"} + rendering.draw_circle{color = {1, 0.3, 0},radius = 4,width = 4,target = found_rail.position,surface = found_rail.surface,time_to_live = 10} + end + end + end + + end +end +