15 | Derived copyright notices 16 |
17 |
18 |
19 |
21 | feedback@networkmom.net 22 |
23 | 24 | -------------------------------------------------------------------------------- /NetrekMacOS/Views/StrategicOffset.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StrategicOffset.swift 3 | // Netrek2 4 | // 5 | // Created by Darrell Root on 5/9/20. 6 | // Copyright © 2020 Darrell Root. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol StrategicOffset { 12 | 13 | } 14 | 15 | extension StrategicOffset { 16 | func screenX(netrekPositionX: Int,screenWidth: CGFloat) -> CGFloat { 17 | //return (screenWidth * CGFloat(netrekPositionX) / CGFloat(NetrekMath.galacticSize)) - screenWidth / 2 18 | return (screenWidth * CGFloat(netrekPositionX) / CGFloat(NetrekMath.galacticSize)) //- screenWidth / 2 19 | } 20 | func screenY(netrekPositionY: Int,screenHeight: CGFloat) -> CGFloat { 21 | //return -(screenHeight * CGFloat(netrekPositionY) / CGFloat(NetrekMath.galacticSize)) + screenHeight / 2 22 | return -(screenHeight * CGFloat(netrekPositionY) / CGFloat(NetrekMath.galacticSize)) + screenHeight /// 2 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Shared/Enumerations/ShipType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShipType.swift 3 | // Netrek2 4 | // 5 | // Created by Darrell Root on 5/5/20. 6 | // Copyright © 2020 Darrell Root. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum ShipType: Int, CaseIterable { 12 | case scout = 0 13 | case destroyer = 1 14 | case cruiser = 2 15 | case battleship = 3 16 | case assault = 4 17 | case starbase = 5 18 | case battlecruiser = 6 19 | //case att = 7 20 | var description: String { 21 | switch self { 22 | 23 | case .scout: 24 | return "SC" 25 | case .destroyer: 26 | return "DD" 27 | case .cruiser: 28 | return "CA" 29 | case .battleship: 30 | return "BB" 31 | case .assault: 32 | return "AS" 33 | case .starbase: 34 | return "SB" 35 | case .battlecruiser: 36 | return "BC" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /NetrekIPad/Views/ServerSelectedView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServerSelectedView.swift 3 | // NetrekIPad 4 | // 5 | // Created by Darrell Root on 6/10/20. 6 | // Copyright © 2020 Darrell Root. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ServerSelectedView: View { 12 | var appDelegate: AppDelegate 13 | var server: String 14 | var body: some View { 15 | VStack { 16 | HStack { 17 | Image(systemName: "chevron.left") 18 | Text("Abort Connection") 19 | Spacer() 20 | }.foregroundColor(Color.blue) 21 | .onTapGesture { 22 | self.appDelegate.newGameState(.noServerSelected) 23 | } 24 | Spacer() 25 | Text("Server \(server) Selected") 26 | Text("Attempting to connect") 27 | Spacer() 28 | }.font(.title) 29 | } 30 | } 31 | 32 | /*struct ServerSelectedView_Previews: PreviewProvider { 33 | static var previews: some View { 34 | ServerSelectedView() 35 | } 36 | }*/ 37 | -------------------------------------------------------------------------------- /NetrekMacOS/Resources/Netrek.help/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 |15 | Have fun 16 |
17 |18 | While Netrek is an intense team game, make sure to have fun. There are always players better or (once you get a bit of practice) worse than you. The robots are pretty good (much better than the 1991 robots) so don’t be frustrated while learning. Be friendly to your teammates and opposing players. 19 |
20 |21 | feedback@networkmom.net 22 |
23 | 24 | -------------------------------------------------------------------------------- /NetrekMacOS/Views/ManualServerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ManualServerView.swift 3 | // Netrek2 4 | // 5 | // Created by Darrell Root on 6/1/20. 6 | // Copyright © 2020 Darrell Root. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ManualServerView: View { 12 | let appDelegate = NSApplication.shared.delegate as! AppDelegate 13 | @State var server: String = "" 14 | @Environment(\.presentationMode) var presentationMode 15 | 16 | var body: some View { 17 | VStack { 18 | HStack { 19 | TextField("Input server name or IP", text: $server,onCommit: self.commit).frame(width: 350) 20 | Button("Connect") { 21 | self.commit() 22 | } 23 | } 24 | Text("pickled.netrek.org is a well-known Netrek server") 25 | }.padding(20) 26 | } 27 | func commit() -> Void { 28 | if self.server != "" { 29 | self.appDelegate.connectToServer(server: self.server) 30 | self.presentationMode.wrappedValue.dismiss() 31 | } 32 | } 33 | } 34 | 35 | struct ManualServerView_Previews: PreviewProvider { 36 | static var previews: some View { 37 | ManualServerView() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /NetrekIPad/Views/ServerSlotView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServerSlotView.swift 3 | // NetrekIPad 4 | // 5 | // Created by Darrell Root on 6/11/20. 6 | // Copyright © 2020 Darrell Root. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ServerSlotView: View { 12 | var appDelegate: AppDelegate 13 | //var universe: Universe 14 | 15 | var body: some View { 16 | VStack { 17 | HStack { 18 | HStack { 19 | Image(systemName: "chevron.left") 20 | Text("Disconnect From Server") 21 | }.onTapGesture { 22 | self.appDelegate.newGameState(.noServerSelected) 23 | } 24 | Spacer() 25 | }//HStack 26 | Spacer() 27 | Text("Server \(appDelegate.reader?.hostname ?? "unknown") Slot Found") 28 | appDelegate.loginInformationController.loginAuthenticated ? Text("Attempting to login as user \(appDelegate.loginInformationController.loginName)") : Text("Attempting to login as guest") 29 | Spacer() 30 | }//VStack 31 | }//var body 32 | } 33 | 34 | /*struct ServerConnectedView_Previews: PreviewProvider { 35 | static var previews: some View { 36 | ServerConnectedView() 37 | } 38 | }*/ 39 | -------------------------------------------------------------------------------- /Shared/Enumerations/Team.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Team.swift 3 | // Netrek2 4 | // 5 | // Created by Darrell Root on 5/5/20. 6 | // Copyright © 2020 Darrell Root. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // teams_numeric = {IND: -1, FED: 0, ROM: 1, KLI: 2, ORI: 3} for joining games 12 | 13 | enum Team: Int, CaseIterable { 14 | case independent = 0 15 | case federation = 1 16 | case roman = 2 17 | case kazari = 4 18 | case orion = 8 19 | case ogg = 15 20 | 21 | var description: String { 22 | switch self { 23 | 24 | case .independent: 25 | return "Independent" 26 | case .federation: 27 | return "Federation" 28 | case .roman: 29 | return "Roman" 30 | case .kazari: 31 | return "Kazari" 32 | case .orion: 33 | return "Orion" 34 | case .ogg: 35 | return "Ogg" 36 | } 37 | } 38 | var letter: String { 39 | switch self { 40 | case .independent: 41 | return "I" 42 | case .federation: 43 | return "F" 44 | case .roman: 45 | return "R" 46 | case .kazari: 47 | return "K" 48 | case .orion: 49 | return "O" 50 | case .ogg: 51 | return "I" 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /NetrekMacOS/Controllers/PreferencesController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreferencesController.swift 3 | // Netrek2 4 | // 5 | // Created by Darrell Root on 6/4/20. 6 | // Copyright © 2020 Darrell Root. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | class PreferencesController: ObservableObject { 13 | //let appDelegate = NSApplication.shared.delegate as! AppDelegate 14 | 15 | static let hideHintsKey = "showHints" 16 | static let preferUdpKey = "preferUdp" 17 | 18 | let defaults: UserDefaults 19 | @Published var hideHints = false { 20 | didSet { 21 | defaults.set(hideHints, forKey: PreferencesController.hideHintsKey) 22 | debugPrint("set userdefaults hideHints \(hideHints)") 23 | } 24 | } 25 | @Published var preferUdp = false { 26 | didSet { 27 | defaults.set(preferUdp, forKey: PreferencesController.preferUdpKey) 28 | debugPrint("set userdefaults preferUdp \(preferUdp)") 29 | } 30 | } 31 | 32 | init(defaults: UserDefaults) { 33 | self.defaults = defaults 34 | self.hideHints = defaults.bool(forKey: PreferencesController.hideHintsKey) 35 | self.preferUdp = defaults.bool(forKey: PreferencesController.preferUdpKey) 36 | } 37 | /*func setShowHints(_ newValue: Bool) { 38 | self.showHints = newValue 39 | defaults.set(newValue, forKey: showHintsKey) 40 | debugPrint("set userdefaults showHints \(newValue)") 41 | }*/ 42 | } 43 | -------------------------------------------------------------------------------- /Shared/Model/ShipInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Ship.swift 3 | // Netrek 4 | // 5 | // Created by Darrell Root on 3/9/19. 6 | // Copyright © 2019 Network Mom LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class ShipInfo { 12 | var shipType: ShipType 13 | var torpSpeed: Int 14 | var phaserRange: Int 15 | var phaserRecharge: Double { 16 | switch self.shipType { 17 | case .battleship: 18 | return 0.5 19 | default: 20 | return 1.0 21 | } 22 | } 23 | var maxSpeed: Int 24 | var maxFuel: Int 25 | var maxShield: Int 26 | var maxDamage: Int 27 | var maxWpnTmp: Int 28 | var maxEngTmp: Int 29 | var width: Int 30 | var height: Int 31 | var maxArmies: Int 32 | //var letter: String 33 | //var shipName: String 34 | //var designator: String 35 | //var bitmap: Int 36 | 37 | init(shipType: ShipType, torpSpeed: Int, phaserRange: Int, maxSpeed: Int, maxFuel: Int, maxShield: Int, maxDamage: Int, maxWpnTmp: Int, maxEngTmp: Int, width: Int, height: Int, maxArmies: Int) { 38 | self.shipType = shipType 39 | self.torpSpeed = torpSpeed 40 | self.phaserRange = phaserRange 41 | self.maxSpeed = maxSpeed 42 | self.maxFuel = maxFuel 43 | self.maxShield = maxShield 44 | self.maxDamage = maxDamage 45 | self.maxWpnTmp = maxWpnTmp 46 | self.maxEngTmp = maxEngTmp 47 | self.width = width 48 | self.height = height 49 | self.maxArmies = maxArmies 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Shared/Views/PlasmaView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlasmaView.swift 3 | // Netrek2 4 | // 5 | // Created by Darrell Root on 5/7/20. 6 | // Copyright © 2020 Darrell Root. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct PlasmaView: View, TacticalOffset { 12 | @ObservedObject var plasma: Plasma 13 | @ObservedObject var me: Player 14 | @ObservedObject var universe: Universe 15 | var screenWidth: CGFloat 16 | var screenHeight: CGFloat 17 | 18 | //@ViewBuilder 19 | var body: some View { 20 | //if torpedo.status == 1 { 21 | //return GeometryReader { geo in 22 | self.plasma.color 23 | .frame(width: self.plasmaWidth(screenWidth: self.screenWidth, visualWidth: self.universe.visualWidth), height: self.plasmaWidth(screenWidth: self.screenHeight, visualWidth: self.universe.visualWidth)) 24 | .contentShape(Rectangle()) 25 | .offset(x: self.xOffset(positionX: self.plasma.positionX, myPositionX: self.me.positionX,tacticalWidth: self.screenWidth, visualWidth: self.universe.visualWidth), y: self.yOffset(positionY: self.plasma.positionY, myPositionY: self.me.positionY, tacticalHeight: self.screenHeight, visualHeight: self.universe.visualWidth * self.screenHeight / self.screenWidth)) 26 | .opacity(self.plasma.status == 1 ? 1 : 0) 27 | //.animation(Animation.linear) 28 | //} 29 | //} 30 | } 31 | } 32 | 33 | /*struct TorpedoView_Previews: PreviewProvider { 34 | static var previews: some View { 35 | TorpedoView() 36 | } 37 | }*/ 38 | -------------------------------------------------------------------------------- /NetrekMacOS/Resources/Netrek.help/Contents/Resources/en-US.lproj/Page11.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |15 | Tournament Mode 16 |
17 |18 | Tournament Mode is when there are enough humans (as opposed to robots) playing that the game “counts”. Usually this is 4 or more humans per side. 19 |
20 |21 | Once you are in tournament mode, you will earn “Damage Inflicted” with every ship killed, every army bombed, and every planet captured. Sufficient damage inflicted, plus a high enough offense rating (damage inflicted per hour) will result in “promotions” to higher rank. If you are not playing as a guest, these promotions will persist to future games on the same server. 22 |
23 |
24 |
25 |
27 | feedback@networkmom.net 28 |
29 | 30 | -------------------------------------------------------------------------------- /NetrekIPad/Views/TeamListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TeamListView.swift 3 | // NetrekIPad 4 | // 5 | // Created by Darrell Root on 6/10/20. 6 | // Copyright © 2020 Darrell Root. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct TeamListView: View { 12 | @ObservedObject var universe: Universe 13 | 14 | var body: some View { 15 | VStack { 16 | Spacer() 17 | ForEach(self.universe.allPlayers, id: \.playerId) { player in 18 | HStack { 19 | Text("\(NetrekMath.teamLetter(team: player.team))\(NetrekMath.playerLetter(playerId: player.playerId))") 20 | Text(" ").overlay(Text(player.name)) 21 | //Text(player.name) 22 | Text(" ").overlay(Text(player.rank.description)) 23 | //Text(player.rank.description) 24 | Text(player.ship?.description ?? "??") 25 | Text("Kills \(player.kills,specifier: "%.2f")") 26 | } 27 | .font(.system(.body, design: .monospaced)) 28 | .border(NetrekMath.color(team: player.team), width: player === self.universe.players[self.universe.me] ? 1 : 0) 29 | 30 | //player === universe.players[me] ? .fontWeight(.bold) : .fontWeight(.regular) 31 | 32 | .foregroundColor(NetrekMath.color(team: player.team)) 33 | } 34 | } 35 | } 36 | } 37 | 38 | /*struct TeamListView_Previews: PreviewProvider { 39 | static var previews: some View { 40 | TeamListView() 41 | } 42 | }*/ 43 | -------------------------------------------------------------------------------- /NetrekMacOS/Global Group/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "netrek-icon-16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "netrek-icon-33.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "netrek-icon-32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "netrek-icon-64.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "netrek-icon-128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "netrek-icon-257.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "netrek-icon-256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "netrek-icon-513.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "netrek-icon-512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "netrek-icon-1024.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /SUPPORT: -------------------------------------------------------------------------------- 1 | Netrek is the _original_ Internet team strategy game. First implemented in 1989, 2 | Netrek pits two teams of up to 8 players each against each other for control of the galaxy. 3 | 4 | Use torpedoes and lasers to destroy enemy ships. 5 | 6 | Bomb enemy planets. 7 | 8 | Land armies to capture planets. 9 | 10 | Work as a team to wipe your enemy from the galaxy. 11 | 12 | This Netrek client is implemented in Swift and SwiftUI and is open source at 13 | https://github.com/darrellroot/Netrek-SwiftUI 14 | 15 | Historic information and documentation about Netrek is available at http://www.netrek.org 16 | 17 | Join the "Netrek-forever" Google group to get notifications of scheduled "human" games. 18 | In the meantime practice against the robots! 19 | 20 | Starting Instructions: 21 | 1) Select a server (we recommend a "Bronco" style server such as pickled.netrek.org). 22 | 2) Launch cruiser. 23 | 3) A "Team not allowed" message may appear. If that happens select a different team and repeat #2. 24 | 25 | If no other humans are playing, practice against the robots! 26 | 27 | Game controls: 28 | 29 | Tapping near center of the screen fires torpedoes. 30 | Tapping near the edge of the screen sets course. 31 | Stepper in bottom-right sets speed. 32 | The first tap on an enemy ship fires a laser. 33 | More taps on enemy ships fire torpedoes. 34 | Lasers recharge in 1 second. 35 | Tapping on a planet locks onto the planet for orbit. 36 | Pinch to zoom in the tactical window. 37 | Exit by clicking both self destruct buttons. 38 | 39 | At this time, the iOS and iPadOS versions of Netrek are not recommend for the "hockey" 40 | variant of the game due to lack of tractor/pressor controls. 41 | 42 | For questions or support email networkmom@proton.me 43 | -------------------------------------------------------------------------------- /Shared/Views/DetonationView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetonationView.swift 3 | // Netrek2 4 | // 5 | // Created by Darrell Root on 5/31/20. 6 | // Copyright © 2020 Darrell Root. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct DetonationView: View, TacticalOffset { 12 | @ObservedObject var torpedo: Torpedo 13 | @ObservedObject var me: Player 14 | @ObservedObject var universe: Universe 15 | var screenWidth: CGFloat 16 | var screenHeight: CGFloat 17 | 18 | @State var scale: CGFloat = 0.0 19 | @State var opacity: Double = 1.0 20 | 21 | var body: some View { 22 | return GeometryReader { geo in 23 | Circle() 24 | .scale(self.scale) 25 | .fill(Color.red) 26 | .opacity(self.opacity) 27 | .frame(width: self.torpedoWidth(screenWidth: geo.size.width,visualWidth: self.universe.visualWidth), height: self.torpedoWidth(screenWidth: geo.size.height, visualWidth: self.universe.visualWidth)) 28 | .offset(x: self.xOffset(positionX: self.torpedo.positionX, myPositionX: self.me.positionX,tacticalWidth: self.screenWidth, visualWidth: self.universe.visualWidth), y: self.yOffset(positionY: self.torpedo.positionY, myPositionY: self.me.positionY, tacticalHeight: geo.size.height, visualHeight: self.universe.visualWidth * self.screenHeight / self.screenWidth)) 29 | }.onAppear { 30 | return withAnimation(.linear(duration: 1.0)) { 31 | self.scale = 6 32 | self.opacity = 0.0 33 | } 34 | } 35 | } 36 | } 37 | 38 | /*struct ExplosionView_Previews: PreviewProvider { 39 | static var previews: some View { 40 | ExplosionView() 41 | } 42 | }*/ 43 | -------------------------------------------------------------------------------- /Shared/Views/ExplosionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExplosionView.swift 3 | // Netrek2 4 | // 5 | // Created by Darrell Root on 5/31/20. 6 | // Copyright © 2020 Darrell Root. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ExplosionView: View, TacticalOffset { 12 | @ObservedObject var player: Player 13 | @ObservedObject var me: Player 14 | @ObservedObject var universe: Universe 15 | var screenWidth: CGFloat 16 | var screenHeight: CGFloat 17 | 18 | @State var scale: CGFloat = 0.0 19 | @State var opacity: Double = 1.0 20 | 21 | var body: some View { 22 | //return GeometryReader { geo in 23 | Circle() 24 | .scale(self.scale) 25 | .fill(Color.orange) 26 | .opacity(self.opacity) 27 | .frame(width: self.playerWidth(screenWidth: self.screenWidth, visualWidth: self.universe.visualWidth), height: self.playerWidth(screenWidth: self.screenHeight, visualWidth: self.universe.visualWidth)) 28 | .offset(x: self.xOffset(positionX: self.player.positionX, myPositionX: self.me.positionX,tacticalWidth: self.screenWidth, visualWidth: self.universe.visualWidth), y: self.yOffset(positionY: self.player.positionY, myPositionY: self.me.positionY, tacticalHeight: self.screenHeight, visualHeight: self.universe.visualWidth * self.screenHeight / self.screenWidth)) 29 | .onAppear { 30 | return withAnimation(.linear(duration: 1.0)) { 31 | self.scale = 3 32 | self.opacity = 0.0 33 | } 34 | } 35 | } 36 | } 37 | 38 | /*struct ExplosionView_Previews: PreviewProvider { 39 | static var previews: some View { 40 | ExplosionView() 41 | } 42 | }*/ 43 | -------------------------------------------------------------------------------- /Shared/Views/DetonationPlasmaView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetonationPlasmaView.swift 3 | // Netrek2 4 | // 5 | // Created by Darrell Root on 5/31/20. 6 | // Copyright © 2020 Darrell Root. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct DetonationPlasmaView: View, TacticalOffset { 12 | @ObservedObject var plasma: Plasma 13 | @ObservedObject var me: Player 14 | @ObservedObject var universe: Universe 15 | var screenWidth: CGFloat 16 | var screenHeight: CGFloat 17 | 18 | @State var scale: CGFloat = 0.0 19 | @State var opacity: Double = 1.0 20 | 21 | var body: some View { 22 | return GeometryReader { geo in 23 | Circle() 24 | .scale(self.scale) 25 | .fill(Color.red) 26 | .opacity(self.opacity) 27 | .frame(width: self.plasmaWidth(screenWidth: geo.size.width,visualWidth: self.universe.visualWidth), height: self.plasmaWidth(screenWidth: geo.size.height, visualWidth: self.universe.visualWidth)) 28 | .offset(x: self.xOffset(positionX: self.plasma.positionX, myPositionX: self.me.positionX,tacticalWidth: self.screenWidth, visualWidth: self.universe.visualWidth), y: self.yOffset(positionY: self.plasma.positionY, myPositionY: self.me.positionY, tacticalHeight: geo.size.height, visualHeight: self.universe.visualWidth * self.screenHeight / self.screenWidth)) 29 | }.onAppear { 30 | return withAnimation(.linear(duration: 1.0)) { 31 | self.scale = 6 32 | self.opacity = 0.0 33 | } 34 | } 35 | } 36 | } 37 | 38 | /*struct ExplosionView_Previews: PreviewProvider { 39 | static var previews: some View { 40 | ExplosionView() 41 | } 42 | }*/ 43 | -------------------------------------------------------------------------------- /Shared/Views/TorpedoView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TorpedoView.swift 3 | // Netrek2 4 | // 5 | // Created by Darrell Root on 5/7/20. 6 | // Copyright © 2020 Darrell Root. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct TorpedoView: View, TacticalOffset { 12 | var torpedo: Torpedo 13 | var me: Player 14 | var universe: Universe 15 | @ObservedObject var serverUpdate = Universe.universe.serverUpdate 16 | var screenWidth: CGFloat 17 | var screenHeight: CGFloat 18 | 19 | //@ViewBuilder 20 | var body: some View { 21 | //if torpedo.status == 1 { 22 | //return GeometryReader { geo in 23 | self.torpedo.color 24 | .frame(width: self.torpedoWidth(screenWidth: self.screenWidth, visualWidth: self.universe.visualWidth), height: self.torpedoWidth(screenWidth: self.screenWidth, visualWidth: self.universe.visualWidth)) 25 | .contentShape(Rectangle()) 26 | .offset(x: self.xOffset(positionX: self.torpedo.positionX, myPositionX: self.me.positionX,tacticalWidth: self.screenWidth, visualWidth: self.universe.visualWidth), y: self.yOffset(positionY: self.torpedo.positionY, myPositionY: self.me.positionY, tacticalHeight: self.screenHeight, visualHeight: self.universe.visualWidth * self.screenHeight / self.screenWidth)) 27 | 28 | //.opacity(self.torpedo.status == 1 ? 1 : 0) 29 | //.animation(Animation.linear(duration: 0.1)) 30 | 31 | //.animation(Animation.linear(duration: 0.1)) 32 | } 33 | //} 34 | //} 35 | } 36 | 37 | /*struct TorpedoView_Previews: PreviewProvider { 38 | static var previews: some View { 39 | TorpedoView() 40 | } 41 | }*/ 42 | -------------------------------------------------------------------------------- /PRIVACY: -------------------------------------------------------------------------------- 1 | Swift Netrek Client Privacy Policy 2 | 3 | The “Netrek” client published by Darrell Root (formerly Network Mom LLC) in the MacOS App 4 | Store and on this website is referred to as the “Swift Netrek Client” in this policy. 5 | 6 | Darrell does not want your private information and does not use the “Swift Netrek Client” 7 | to harvest your private information. But using the “Swift Netrek client” requires connecting 8 | to Netrek servers which are outside of our control. 9 | 10 | 1. The “Swift Netrek Client” requires outbound network connections to play the game and update 11 | the list of game servers. It does not use outbound network connections for any other purpose. 12 | 13 | 2. Playing the game Netrek requires that the “Swift Netrek Client” connect to Netrek game 14 | servers on the Internet. These are public-domain game servers run by volunteers outside 15 | of our control. The servers receive client IP address and login information (name, 16 | username, and password as entered in on the login information window). The servers also 17 | receive any chat messages sent. The servers may log this information. In particular, 18 | netrek servers are known for storing the password in an insecure manner. We recommend 19 | you use a different Netrek password than any other account, and do not consider the password 20 | secure. Alternatively, play as a guest. 21 | 22 | 3. The password sent to the Netrek game servers is sent via an obsolete network protocol 23 | outside of our control. This protocol is _not_ encrypted. We recommend you use a different 24 | Netrek password than any other account, and do not consider this password secure. Alternatively, 25 | play as a guest. 26 | 27 | 4. The “Swift Netrek Client” does not include any 3rd party app metrics or advertisement libraries. 28 | -------------------------------------------------------------------------------- /NetrekMacOS/Resources/Netrek.help/Contents/Resources/txt/jquery-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright jQuery Foundation and other contributors, https://jquery.org/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals. For exact contribution history, see the revision history 5 | available at https://github.com/jquery/jquery 6 | 7 | The following license applies to all parts of this software except as 8 | documented below: 9 | 10 | ==== 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | "Software"), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | ==== 32 | 33 | All files located in the node_modules and external directories are 34 | externally maintained libraries used by this software which have their 35 | own licenses; we recommend you read them, as their terms may differ from 36 | the terms above. 37 | -------------------------------------------------------------------------------- /NetrekMacOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 |15 | Original Netrek Copyright Notice 16 |
17 |18 | /* 19 |
20 |21 | * Copyright 1989 Kevin P. Smith Scott Silvey 22 |
23 |24 | * 25 |
26 |27 | * Permission to use, copy, modify, and distribute this software and its 28 |
29 |30 | * documentation for any purpose and without fee is hereby granted, provided 31 |
32 |33 | * that the above copyright notice appear in all copies. 34 |
35 |36 | */ 37 |
38 |39 | feedback@networkmom.net 40 |
41 | 42 | -------------------------------------------------------------------------------- /NetrekMacOS/Views/PlayerStrategicView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlayerView.swift 3 | // Netrek2 4 | // 5 | // Created by Darrell Root on 5/6/20. 6 | // Copyright © 2020 Darrell Root. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct PlayerStrategicView: View, StrategicOffset { 12 | var player: Player 13 | @ObservedObject var updateCounter = Universe.universe.seconds 14 | var body: some View { 15 | return GeometryReader { geo in 16 | Text(self.playerText).foregroundColor(self.playerColor) 17 | .offset(x: self.screenX(netrekPositionX: self.player.positionX, screenWidth: geo.size.width), y: self.screenY(netrekPositionY: self.player.positionY, screenHeight: geo.size.height)) 18 | } 19 | } 20 | var playerColor: Color { 21 | if player.cloak == true { 22 | return Color.gray 23 | } else { 24 | return NetrekMath.color(team: self.player.team) 25 | } 26 | } 27 | var playerText: String { 28 | if player.cloak == true { 29 | return "??" 30 | } else { 31 | let playerLetter = NetrekMath.playerLetter(playerId: player.playerId) 32 | let teamLetter = NetrekMath.teamLetter(team: player.team) 33 | return teamLetter + playerLetter 34 | } 35 | } 36 | /*func screenX(netrekPositionX: Int,screenWidth: CGFloat) -> CGFloat { 37 | return (screenWidth * CGFloat(netrekPositionX) / CGFloat(NetrekMath.galacticSize)) - screenWidth / 2 38 | } 39 | func screenY(netrekPositionY: Int,screenHeight: CGFloat) -> CGFloat { 40 | return -(screenHeight * CGFloat(netrekPositionY) / CGFloat(NetrekMath.galacticSize)) + screenHeight / 2 41 | }*/ 42 | 43 | } 44 | 45 | /*struct PlayerView_Previews: PreviewProvider { 46 | static var previews: some View { 47 | PlayerView() 48 | } 49 | }*/ 50 | -------------------------------------------------------------------------------- /Shared/Views/TractorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TractorView.swift 3 | // Netrek2 4 | // 5 | // Created by Darrell Root on 6/3/20. 6 | // Copyright © 2020 Darrell Root. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct TractorView: View, TacticalOffset { 12 | @ObservedObject var target: Player 13 | @ObservedObject var me: Player 14 | @ObservedObject var universe: Universe 15 | var screenWidth: CGFloat 16 | var screenHeight: CGFloat 17 | 18 | @State var phase: CGFloat = 0 19 | 20 | var body: some View { 21 | return GeometryReader { geo in 22 | Path { path in 23 | path.move(to: CGPoint(x: 0, y: 0)) 24 | path.addLine(to: CGPoint(x: self.xOffset(positionX: self.target.positionX, myPositionX: self.me.positionX, tacticalWidth: self.screenWidth, visualWidth: self.universe.visualWidth),y: self.yOffset(positionY: self.target.positionY, myPositionY: self.me.positionY, tacticalHeight: self.screenHeight, visualHeight: self.universe.visualWidth * self.screenHeight / self.screenWidth))) 25 | }.offset(x: geo.size.width / 2, y: geo.size.height / 2) 26 | .stroke(self.me.pressor ? Color.blue : Color.green, style: StrokeStyle(lineWidth: self.playerWidth(screenWidth: geo.size.width, visualWidth: self.universe.visualWidth),dash: [self.playerWidth(screenWidth: geo.size.width, visualWidth: self.universe.visualWidth)], dashPhase: self.phase)) 27 | .opacity(0.5) 28 | .onAppear { self.phase = self.me.pressor ? self.playerWidth(screenWidth: geo.size.width,visualWidth: self.universe.visualWidth) : self.playerWidth(screenWidth: geo.size.width * -1, visualWidth: self.universe.visualWidth) } 29 | .animation(Animation.linear.repeatForever(autoreverses: false)) 30 | } 31 | } 32 | } 33 | 34 | /*struct TractorView_Previews: PreviewProvider { 35 | static var previews: some View { 36 | TractorView() 37 | } 38 | }*/ 39 | -------------------------------------------------------------------------------- /NetrekMacOS/Views/EverythingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EverythingView.swift 3 | // Netrek 4 | // 5 | // Created by Darrell Root on 3/4/22. 6 | // Copyright © 2022 Darrell Root. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct EverythingView: View { 12 | @ObservedObject var help: Help 13 | @ObservedObject var universe = Universe.universe 14 | @ObservedObject var preferencesController: PreferencesController 15 | @FocusState var textFieldFocused 16 | 17 | var body: some View { 18 | GeometryReader { geo in 19 | VStack(spacing: 0) { 20 | HStack(spacing: 0) { 21 | TacticalView(help: help, preferencesController: preferencesController) 22 | .frame(width: geo.size.width / 2, height: geo.size.width / 2) 23 | .border(universe.players[Universe.universe.me].alertCondition.color.opacity(0.5), width: 10) 24 | .onTapGesture { 25 | textFieldFocused = false 26 | } 27 | .clipped() 28 | 29 | StrategicView() 30 | .frame(width: geo.size.width / 2, height: geo.size.width / 2) 31 | .border(universe.players[Universe.universe.me].alertCondition.color.opacity(0.5), width: 10) 32 | .onTapGesture { 33 | textFieldFocused = false 34 | } 35 | .clipped() 36 | } 37 | CommunicationsView(textFieldFocused: _textFieldFocused) 38 | .frame(width: geo.size.width) 39 | .border(universe.players[Universe.universe.me].alertCondition.color.opacity(0.5), width: 3) 40 | .clipped() 41 | } 42 | } 43 | } 44 | } 45 | 46 | /*struct EverythingView_Previews: PreviewProvider { 47 | static var previews: some View { 48 | EverythingView() 49 | } 50 | }*/ 51 | -------------------------------------------------------------------------------- /NetrekMacOS/Resources/Netrek.help/Contents/Resources/txt/jquery-ui-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright jQuery Foundation and other contributors, https://jquery.org/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals. For exact contribution history, see the revision history 5 | available at https://github.com/jquery/jquery-ui 6 | 7 | The following license applies to all parts of this software except as 8 | documented below: 9 | 10 | ==== 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | "Software"), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | ==== 32 | 33 | Copyright and related rights for sample code are waived via CC0. Sample 34 | code is defined as all source code contained within the demos directory. 35 | 36 | CC0: http://creativecommons.org/publicdomain/zero/1.0/ 37 | 38 | ==== 39 | 40 | All files located in the node_modules and external directories are 41 | externally maintained libraries used by this software which have their 42 | own licenses; we recommend you read them, as their terms may differ from 43 | the terms above. 44 | -------------------------------------------------------------------------------- /NetrekMacOS/Views/StatisticsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatisticsView.swift 3 | // Netrek2 4 | // 5 | // Created by Darrell Root on 5/9/20. 6 | // Copyright © 2020 Darrell Root. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct StatisticsView: View { 12 | var universe: Universe 13 | var me: Player 14 | @ObservedObject var updateCounter = Universe.universe.seconds 15 | 16 | var body: some View { 17 | VStack(alignment: .leading) { 18 | HStack { 19 | Text("Speed \(me.speed)") 20 | Text("Shield \(me.shieldStrength)") 21 | Text("Damage \(me.damage)") 22 | Text("Armies \(me.armies)") 23 | Text("Fuel \(me.fuel)") 24 | Text("Etmp \(me.engineTemp / 10)") 25 | Text("Wtmp \(me.weaponsTemp / 10)") 26 | }.font(.system(.body, design: .monospaced)) 27 | HStack { 28 | if me.tractorFlag { Text("Tractor") } 29 | if me.pressor { Text("Pressor")} 30 | if me.enginesOverheated { Text("EngineFail")} 31 | }.font(.system(.body, design: .monospaced)) 32 | ForEach(self.universe.activePlayers, id: \.playerId) { player in 33 | HStack { 34 | Text("\(NetrekMath.teamLetter(team: player.team))\(NetrekMath.playerLetter(playerId: player.playerId))") 35 | Text(" ").overlay(Text(player.name)) 36 | //Text(player.name) 37 | Text(" ").overlay(Text(player.rank.description)) 38 | //Text(player.rank.description) 39 | Text(player.ship?.description ?? "??") 40 | Text("Kills \(player.kills,specifier: "%.2f")") 41 | }.padding(.leading) 42 | .font(.system(.body, design: .monospaced)) 43 | .foregroundColor(NetrekMath.color(team: player.team)) 44 | } 45 | Spacer() 46 | }.padding(10) 47 | } 48 | } 49 | 50 | /*struct StatisticsView_Previews: PreviewProvider { 51 | static var previews: some View { 52 | StatisticsView() 53 | } 54 | }*/ 55 | -------------------------------------------------------------------------------- /NetrekIPad/Model/EligibleTeams.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EligibleTeams.swift 3 | // NetrekIPad 4 | // 5 | // Created by Darrell Root on 6/7/20. 6 | // Copyright © 2020 Darrell Root. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | class EligibleTeams: ObservableObject { 13 | @Published var fedEligible = true 14 | @Published var romEligible = true 15 | @Published var kazariEligible = true 16 | @Published var oriEligible = true 17 | @Published var preferredTeam: Team = .federation 18 | @Published var preferredShip: ShipType = .cruiser 19 | var initialTeamSet = false 20 | 21 | public func updateEligibleTeams(mask: UInt8) { 22 | debugPrint("start update eligible teams") 23 | if mask & UInt8(Team.federation.rawValue) != 0 { 24 | self.fedEligible = true 25 | } else { 26 | self.fedEligible = false 27 | } 28 | if mask & UInt8(Team.roman.rawValue) != 0 { 29 | self.romEligible = true 30 | } else { 31 | self.romEligible = false 32 | } 33 | if mask & UInt8(Team.kazari.rawValue) != 0 { 34 | self.kazariEligible = true 35 | } else { 36 | self.kazariEligible = false 37 | } 38 | if mask & UInt8(Team.orion.rawValue) != 0 { 39 | self.oriEligible = true 40 | } else { 41 | oriEligible = false 42 | } 43 | if !self.initialTeamSet && mask != 0 { 44 | debugPrint("initial team set") 45 | if fedEligible { 46 | self.preferredTeam = .federation 47 | self.initialTeamSet = true 48 | return 49 | } 50 | if romEligible { 51 | self.preferredTeam = .roman 52 | self.initialTeamSet = true 53 | return 54 | } 55 | if kazariEligible { 56 | self.preferredTeam = .kazari 57 | self.initialTeamSet = true 58 | return 59 | } 60 | if oriEligible { 61 | self.preferredTeam = .orion 62 | self.initialTeamSet = true 63 | return 64 | } 65 | } 66 | 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /NetrekIPad/Controllers/AudioController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AudioController.swift 3 | // NetrekIPad 4 | // 5 | // Created by Darrell Root on 6/8/20. 6 | // Copyright © 2020 Darrell Root. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Speech 11 | 12 | class AudioController: NSObject, SFSpeechRecognizerDelegate { 13 | let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US"))! 14 | private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest? 15 | private var recognitionTask: SFSpeechRecognitionTask? 16 | 17 | private let audioEngine = AVAudioEngine() 18 | 19 | init?(keymapController: KeymapController) { 20 | super.init() 21 | debugPrint("activating speech controller") 22 | speechRecognizer.delegate = self 23 | 24 | let audioSession = AVAudioSession.sharedInstance() 25 | do { 26 | try audioSession.setCategory(.record, mode: .measurement, options: .duckOthers) 27 | try audioSession.setActive(true, options: .notifyOthersOnDeactivation) 28 | } catch { 29 | debugPrint("audio session setup failed") 30 | return nil 31 | } 32 | let inputNode = audioEngine.inputNode 33 | recognitionRequest = SFSpeechAudioBufferRecognitionRequest() 34 | guard let recognitionRequest = recognitionRequest else { 35 | debugPrint("Unable to create a SFSpeechAudioBufferRecognitionRequest object") 36 | return nil 37 | } 38 | recognitionRequest.shouldReportPartialResults = true 39 | recognitionRequest.requiresOnDeviceRecognition = true 40 | recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest) { result, error in 41 | var isFinal = false 42 | if let result = result { 43 | isFinal = result.isFinal 44 | debugPrint("speech recorded \(result.bestTranscription.formattedString)") 45 | } 46 | if error != nil || isFinal { 47 | self.audioEngine.stop() 48 | inputNode.removeTap(onBus: 0) 49 | self.recognitionRequest = nil 50 | self.recognitionTask = nil 51 | } 52 | } 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /Shared/Views/PlanetView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlanetView.swift 3 | // Netrek2 4 | // 5 | // Created by Darrell Root on 5/6/20. 6 | // Copyright © 2020 Darrell Root. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct PlanetView: View, TacticalOffset { 12 | @ObservedObject var planet: Planet 13 | @ObservedObject var me: Player 14 | @ObservedObject var universe: Universe 15 | //@ObservedObject var serverUpdate = universe.serverUpdate 16 | var imageSize: CGFloat 17 | var screenWidth: CGFloat 18 | var screenHeight: CGFloat 19 | 20 | var body: some View { 21 | //return GeometryReader { geo in 22 | VStack { 23 | Text(" ").fontWeight(self.planet.armies > 4 ? .heavy : .light) 24 | Image(self.planet.imageName(myTeam: self.me.team)) 25 | .resizable() 26 | .aspectRatio(contentMode: .fit) 27 | .frame(width: self.planetWidth(screenWidth: self.screenWidth, visualWidth: self.universe.visualWidth), height: self.planetWidth(screenWidth: self.screenHeight, visualWidth: self.universe.visualWidth)) 28 | .colorMultiply(self.planet.seen[self.me.team]! ? NetrekMath.color(team: self.planet.owner) : Color.gray) 29 | .contentShape(Rectangle()) 30 | Text(self.planet.name).fontWeight((self.planet.armies > 4 && self.planet.seen[self.me.team]!) ? .heavy : .light) 31 | } 32 | .offset(x: self.xOffset(positionX: self.planet.positionX, myPositionX: self.me.positionX,tacticalWidth: self.screenWidth, visualWidth: self.universe.visualWidth), y: self.yOffset(positionY: self.planet.positionY, myPositionY: self.me.positionY, tacticalHeight: self.screenHeight, visualHeight: self.universe.visualWidth * self.screenHeight / self.screenWidth)) 33 | .animation(Animation.linear(duration: 0.1)) 34 | //.offset(x: self.xOffset(positionX: self.planet.positionX, myPositionX: self.me.positionX,tacticalWidth: geo.size.width), y: self.yOffset(positionY: self.planet.positionY, myPositionY: self.me.positionY, tacticalHeight: geo.size.height)) 35 | 36 | //} 37 | 38 | } 39 | 40 | } 41 | 42 | /*struct PlanetView_Previews: PreviewProvider { 43 | static var previews: some View { 44 | PlanetView() 45 | } 46 | }*/ 47 | -------------------------------------------------------------------------------- /NetrekMacOS/Resources/Netrek.help/Contents/Resources/en-US.lproj/Page3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |15 | Teams 16 |
17 |18 | Federation 19 |
20 |21 | The Federation are in the bottom left quadrant of the strategic map and have Earth as their homeworld. Their planets are yellow and their ships are white with yellow shields. 22 |
23 |24 | Romans 25 |
26 |27 | The Romans are in the top left quadrant and have Rome as their homeworld. Their planets and ships are red. 28 |
29 |30 | Kazari 31 |
32 |33 | The Kazari are in the top right quadrant and have Kazari as their homeworld. Their planets and ships are green. 34 |
35 |36 | Orions 37 |
38 |39 | The Orions are in the bottom right quadrant and have Orion as their homeworld. Their planets and ships are blue. 40 |
41 |42 | Indi 43 |
44 |45 | Occasionally an “independent” robot will spawn on the map to “discourage” players who attack neutral planets. Indi are white or gray. 46 |
47 |48 | feedback@networkmom.net 49 |
50 | 51 | -------------------------------------------------------------------------------- /NetrekIPad/Views/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // NetrekIPad 4 | // 5 | // Created by Darrell Root on 6/7/20. 6 | // Copyright © 2020 Darrell Root. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ContentView: View { 12 | 13 | @ObservedObject var serverUpdate = Universe.universe.serverUpdate 14 | @ObservedObject var metaServer: MetaServer 15 | @ObservedObject var universe: Universe 16 | @ObservedObject var appDelegate: AppDelegate 17 | @State var displayHelp = false 18 | //let appDelegate = UIApplication.shared.delegate as! AppDelegate 19 | 20 | var body: some View { 21 | switch (appDelegate.gameScreen, universe.players[universe.me].slotStatus) { 22 | case (.howToPlay,_): 23 | return AnyView(HowToPlayView()) 24 | case (.credits,_): 25 | return AnyView(CreditsView(appDelegate: appDelegate)) 26 | case (.preferences,_): 27 | return AnyView(LoginView(loginName: appDelegate.loginInformationController.loginName,loginPassword: appDelegate.loginInformationController.loginPassword, userInfo: appDelegate.loginInformationController.userInfo, loginInformationController: appDelegate.loginInformationController)) 28 | case (.noServerSelected,_): 29 | return AnyView(PickServerView(metaServer: metaServer, universe: universe)) 30 | case (.serverSelected,_): 31 | return AnyView(ServerSelectedView(appDelegate: appDelegate, server: appDelegate.reader?.hostname ?? "unknown")) 32 | case (.serverConnected,_): 33 | return AnyView(ServerConnectedView(appDelegate: appDelegate, universe: universe)) 34 | case (.serverSlotFound,_): 35 | return AnyView(ServerSlotView(appDelegate: appDelegate)) 36 | case (.loginAccepted,.explode): 37 | return AnyView(TacticalHudView(universe: universe, me: universe.players[universe.me], help: appDelegate.help)) 38 | case (.loginAccepted,_): 39 | return AnyView(SelectTeamView(eligibleTeams: self.appDelegate.eligibleTeams, universe: universe)) 40 | case (.gameActive,_): 41 | return AnyView(TacticalHudView(universe: universe, me: universe.players[universe.me],help: appDelegate.help)) 42 | //default: 43 | //return AnyView(Text("Unexpected Error")) 44 | } 45 | } 46 | } 47 | 48 | /*struct ContentView_Previews: PreviewProvider { 49 | static var previews: some View { 50 | ContentView() 51 | } 52 | }*/ 53 | -------------------------------------------------------------------------------- /NetrekIPad/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 |15 | Other commands 16 |
17 |18 | Information 19 |
20 |21 | The i key will show information about the planet or player closest to the mouse location. This information will show up on the tactical map for 2 seconds, plus in the message window in the bottom right (this is useful when getting information for a far away planet or player on the strategic map). 22 |
23 |24 | Repair 25 |
26 |27 | R will stop your ship, lower your shields, and start repairs. This increases the normal repair rate. Orbiting a friendly planet with a repair icon, or being docked with a friendly starbase, will also increase the repair rate. Any movement or weapon command will cancel repairs. 28 |
29 |30 | Even while not using the full R repair mode, your ship automatically repairs shields at a slow pace. If your shields are down then your ship automatically repairs damage at a slow pace. 31 |
32 |33 | Docking permission toggle 34 |
35 |36 | A starbase can permit or deny permission for friendly ships to dock with it. This is to prevent a starbase at the front lines from being crippled by friendly ships exploding on top of it. Some netrek clients support a “transwarp to the starbase”, which this also denies. This client does not currently support transwarping to the starbase. 37 |
38 |39 | feedback@networkmom.net 40 |
41 | 42 | -------------------------------------------------------------------------------- /Shared/Views/BoundaryView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BoundaryView.swift 3 | // Netrek2 4 | // 5 | // Created by Darrell Root on 6/4/20. 6 | // Copyright © 2020 Darrell Root. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct BoundaryView: View, TacticalOffset { 12 | @ObservedObject var me: Player 13 | @ObservedObject var universe: Universe 14 | var screenWidth: CGFloat 15 | var screenHeight: CGFloat 16 | 17 | var body: some View { 18 | return GeometryReader { geo in 19 | Path { path in 20 | path.move(to: CGPoint(x: self.xOffset(positionX: 0, myPositionX: self.me.positionX,tacticalWidth: geo.size.width, visualWidth: self.universe.visualWidth), y: self.yOffset(positionY: 0 ,myPositionY: self.me.positionY, tacticalHeight: geo.size.width, visualHeight: self.universe.visualWidth * self.screenHeight / self.screenWidth))) 21 | path.addLine(to: CGPoint(x: self.xOffset(positionX: NetrekMath.galacticSize, myPositionX: self.me.positionX,tacticalWidth: geo.size.width, visualWidth: self.universe.visualWidth), y: self.yOffset(positionY: 0 ,myPositionY: self.me.positionY, tacticalHeight: geo.size.width, visualHeight: self.universe.visualWidth * self.screenHeight / self.screenWidth))) 22 | path.addLine(to: CGPoint(x: self.xOffset(positionX: NetrekMath.galacticSize, myPositionX: self.me.positionX,tacticalWidth: geo.size.width, visualWidth: self.universe.visualWidth), y: self.yOffset(positionY: NetrekMath.galacticSize ,myPositionY: self.me.positionY, tacticalHeight: geo.size.width, visualHeight: self.universe.visualWidth * self.screenHeight / self.screenWidth))) 23 | path.addLine(to: CGPoint(x: self.xOffset(positionX: 0, myPositionX: self.me.positionX,tacticalWidth: geo.size.width, visualWidth: self.universe.visualWidth), y: self.yOffset(positionY: NetrekMath.galacticSize ,myPositionY: self.me.positionY, tacticalHeight: geo.size.width, visualHeight: self.universe.visualWidth * self.screenHeight / self.screenWidth))) 24 | path.addLine(to: CGPoint(x: self.xOffset(positionX: 0, myPositionX: self.me.positionX,tacticalWidth: geo.size.width, visualWidth: self.universe.visualWidth), y: self.yOffset(positionY: 0,myPositionY: self.me.positionY, tacticalHeight: geo.size.width, visualHeight: self.universe.visualWidth * self.screenHeight / self.screenWidth))) 25 | }.offset(x: geo.size.width / 2, y: geo.size.height / 2) 26 | .stroke(Color.blue,style: StrokeStyle(lineWidth: self.playerWidth(screenWidth: geo.size.width, visualWidth: self.universe.visualWidth))) 27 | .opacity(0.5) 28 | .animation(Animation.linear(duration: 0.1)) 29 | } 30 | } 31 | } 32 | 33 | /*struct BoundaryView_Previews: PreviewProvider { 34 | static var previews: some View { 35 | BoundaryView() 36 | } 37 | }*/ 38 | -------------------------------------------------------------------------------- /NetrekMacOS/Resources/Netrek.help/Contents/Resources/en-US.lproj/Page15.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |15 | Netrek Server Types 16 |
17 |18 | Bronco 19 |
20 |21 | Bronco Netrek servers are “standard” Netrek servers with 6 ship types and standard weapons and gameplay. pickled.netrek.org and continuum.uc.netrek.org are Bronco servers. 22 |
23 |24 | Sturgeon 25 |
26 |27 | Sturgeon servers add some special weapons (missiles for example) and ships (this client calls it the battlecruiser for legal reasons). This client does not support all Sturgeon features but is playable on Sturgeon servers. netrek.beesenterprises.com is a Sturgeon server. 28 |
29 |30 | Paradise 31 |
32 |33 | Paradise was a Netrek variant including larger numbers of ships, large map, star clusters, and more strategic gameplay. Paradise required a custom client. This client does not support Paradise mode. There are no known operational Paradise Netrek servers at this time. 34 |
35 |36 | Hockey 37 |
38 |39 | Netrek Hockey was a silly but fun Netrek variant where you use improved tractor and pressor beams to move a “puck” into the “enemy goal” to score. Standard Bronco clients should work with Netrek Hockey, but there are no known operational Netrek Hockey servers online at this time. 40 |
41 |42 | Other variants 43 |
44 |45 | There were other Netrek server types, including “Chaos”, “Base Practice”, “International Netrek League”. 46 |
47 |48 | feedback@networkmom.net 49 |
50 | 51 | -------------------------------------------------------------------------------- /Shared/Model/Plasma.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Plasma.swift 3 | // Netrek 4 | // 5 | // Created by Darrell Root on 3/5/19. 6 | // Copyright © 2019 Network Mom LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | class Plasma: ObservableObject { 13 | #if os(macOS) 14 | lazy var appDelegate = NSApplication.shared.delegate as! AppDelegate 15 | #elseif os(iOS) 16 | lazy var appDelegate = UIApplication.shared.delegate as! AppDelegate 17 | #endif 18 | 19 | private(set) var plasmaId: Int 20 | @Published private(set) var status = 0 21 | private(set) var war: [Team:Bool] = [:] 22 | private(set) var directionNetrek = 0 23 | private(set) var direction = 0.0 24 | @Published private(set) var positionX = 0 25 | @Published private(set) var positionY = 0 26 | @Published var color: Color = Color.red 27 | 28 | private var soundPlayed = false 29 | 30 | init(plasmaId: Int) { 31 | self.plasmaId = plasmaId 32 | } 33 | public func reset() { 34 | self.positionX = 0 35 | self.positionY = 0 36 | self.status = 0 37 | } 38 | 39 | //from SP_PLASMA_INFO 8 40 | public func update(plasmaId: Int, war: UInt8, status: Int) { 41 | DispatchQueue.main.async { 42 | self.plasmaId = plasmaId 43 | for team in Team.allCases { 44 | if UInt8(team.rawValue) & war != 0 { 45 | self.war[team] = true 46 | } else { 47 | self.war[team] = false 48 | } 49 | } 50 | let myTeam = Universe.universe.players[Universe.universe.me].team 51 | //DispatchQueue.main.async { 52 | if self.war[myTeam] == true { 53 | self.color = Color.red 54 | } else { 55 | self.color = Color.green 56 | } 57 | self.status = status 58 | //} 59 | if status == 1 { 60 | self.soundPlayed = false 61 | } 62 | } 63 | } 64 | // from SP_PLASMA 9 65 | func update(positionX: Int, positionY: Int) { 66 | DispatchQueue.main.async { 67 | self.positionX = positionX 68 | self.positionY = positionY 69 | if self.soundPlayed == false { 70 | let me = Universe.universe.me 71 | let taxiDistance = abs(Universe.universe.players[me].positionX - self.positionX) + abs(Universe.universe.players[me].positionY - self.positionY) 72 | if taxiDistance < NetrekMath.displayDistance / 3 { 73 | let volume = 1.0 - (3.0 * Float(taxiDistance) / (NetrekMath.displayDistanceFloat)) 74 | SoundController.soundController.play(sound: .plasma, volume: volume) 75 | debugPrint("playing plasma sound volume \(volume)") 76 | self.soundPlayed = true 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /NetrekMacOS/Resources/Netrek.help/Contents/Resources/en-US.lproj/Page17.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |15 | Original XTrek Copyright notice 16 |
17 |18 | /* 19 |
20 |21 | * Copyright (c) 1986 Chris Guthrie 22 |
23 |24 | * 25 |
26 |27 | * Permission to use, copy, modify, and distribute this software and its 28 |
29 |30 | * documentation for any purpose and without fee is hereby granted, provided 31 |
32 |33 | * that the above copyright notice appear in all copies and that both that 34 |
35 |36 | * copyright notice and this permission notice appear in supporting 37 |
38 |39 | * documentation. No representations are made about the suitability of this 40 |
41 |42 | * software for any purpose. It is provided "as is" without express or 43 |
44 |45 | * implied warranty. 46 |
47 |48 | * 49 |
50 |51 | * Copyright 1989 Kevin P. Smith Scott Silvey 52 |
53 |54 | * 55 |
56 |57 | * ditto. 58 |
59 |60 | */ 61 |
62 |
63 |
64 |
66 | feedback@networkmom.net 67 |
68 | 69 | -------------------------------------------------------------------------------- /Shared/Model/Torpedo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Torpedo.swift 3 | // Netrek 4 | // 5 | // Created by Darrell Root on 3/5/19. 6 | // Copyright © 2019 Network Mom LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | import Combine 12 | 13 | class Torpedo: ObservableObject { 14 | 15 | var torpedoId: Int = 0 16 | @Published var status: UInt8 = 0 17 | //0 = inactive, 1=active, 2 = exploding? 18 | 19 | //public var displayed: Bool = false 20 | private(set) var war: [Team:Bool] = [:] 21 | var directionNetrek: Int = 0 // netrek format direction for now 22 | var direction: Double = 0.0 // in radians 23 | @Published var positionX: Int = 0 24 | @Published var positionY: Int = 0 25 | @Published var color: Color = Color.red 26 | 27 | public func reset() { 28 | self.positionX = 0 29 | self.positionY = 0 30 | self.status = 0 31 | } 32 | 33 | private var soundPlayed = false 34 | /*var torpedoNode = SKSpriteNode(color: .red, 35 | size: CGSize(width: NetrekMath.torpedoSize, height: NetrekMath.torpedoSize))*/ 36 | 37 | init(torpedoId: Int) { 38 | self.torpedoId = torpedoId 39 | } 40 | func update(war: UInt8, status: UInt8) { 41 | DispatchQueue.main.async { 42 | for team in Team.allCases { 43 | if UInt8(team.rawValue) & war != 0 { 44 | self.war[team] = true 45 | } else { 46 | self.war[team] = false 47 | } 48 | } 49 | let myTeam = Universe.universe.players[Universe.universe.me].team 50 | //DispatchQueue.main.async { 51 | if self.war[myTeam] == true { 52 | self.color = Color.red 53 | } else { 54 | self.color = Color.green 55 | } 56 | self.status = status 57 | //} 58 | if status == 1 { 59 | self.soundPlayed = false 60 | } 61 | } 62 | } 63 | func update(directionNetrek: Int, positionX: Int, positionY: Int) { 64 | if self.status == 0 { 65 | return 66 | } 67 | DispatchQueue.main.async { 68 | self.positionX = positionX 69 | self.positionY = positionY 70 | } 71 | if soundPlayed == false { 72 | let me = Universe.universe.players[Universe.universe.me] 73 | let taxiDistance = abs(me.positionX - self.positionX) + abs(me.positionY - self.positionY) 74 | if taxiDistance < NetrekMath.displayDistance / 4 { 75 | let volume = 1.0 - (4.0 * Float(taxiDistance) / (NetrekMath.displayDistanceFloat)) 76 | 77 | SoundController.soundController.play(sound: .torpedo, volume: volume) 78 | debugPrint("playing torpedo sound volume \(volume)") 79 | soundPlayed = true 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /NetrekIPad/Global Group/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "netrek-icon-40.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "netrek-icon-60.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "netrek-icon-58.png", 17 | "idiom" : "iphone", 18 | "scale" : "2x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "netrek-icon-87.png", 23 | "idiom" : "iphone", 24 | "scale" : "3x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "netrek-icon-80.png", 29 | "idiom" : "iphone", 30 | "scale" : "2x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "filename" : "netrek-icon-120.png", 35 | "idiom" : "iphone", 36 | "scale" : "3x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "netrek-icon-121.png", 41 | "idiom" : "iphone", 42 | "scale" : "2x", 43 | "size" : "60x60" 44 | }, 45 | { 46 | "filename" : "netrek-icon-180.png", 47 | "idiom" : "iphone", 48 | "scale" : "3x", 49 | "size" : "60x60" 50 | }, 51 | { 52 | "filename" : "netrek-icon-20.png", 53 | "idiom" : "ipad", 54 | "scale" : "1x", 55 | "size" : "20x20" 56 | }, 57 | { 58 | "filename" : "netrek-icon-41.png", 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "20x20" 62 | }, 63 | { 64 | "filename" : "netrek-icon-29.png", 65 | "idiom" : "ipad", 66 | "scale" : "1x", 67 | "size" : "29x29" 68 | }, 69 | { 70 | "filename" : "netrek-icon-59.png", 71 | "idiom" : "ipad", 72 | "scale" : "2x", 73 | "size" : "29x29" 74 | }, 75 | { 76 | "filename" : "netrek-icon-42.png", 77 | "idiom" : "ipad", 78 | "scale" : "1x", 79 | "size" : "40x40" 80 | }, 81 | { 82 | "filename" : "netrek-icon-81.png", 83 | "idiom" : "ipad", 84 | "scale" : "2x", 85 | "size" : "40x40" 86 | }, 87 | { 88 | "filename" : "netrek-icon-76.png", 89 | "idiom" : "ipad", 90 | "scale" : "1x", 91 | "size" : "76x76" 92 | }, 93 | { 94 | "filename" : "netrek-icon-152.png", 95 | "idiom" : "ipad", 96 | "scale" : "2x", 97 | "size" : "76x76" 98 | }, 99 | { 100 | "filename" : "netrek-icon-167.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "83.5x83.5" 104 | }, 105 | { 106 | "filename" : "netrek-icon-1024-beta.png", 107 | "idiom" : "ios-marketing", 108 | "scale" : "1x", 109 | "size" : "1024x1024" 110 | } 111 | ], 112 | "info" : { 113 | "author" : "xcode", 114 | "version" : 1 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Shared/Communication/MessagesController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessagesController.swift 3 | // NetrekIPad 4 | // 5 | // Created by Darrell Root on 6/10/20. 6 | // Copyright © 2020 Darrell Root. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | class MessagesController { 13 | #if os(macOS) 14 | let appDelegate = NSApplication.shared.delegate as! AppDelegate 15 | #elseif os(iOS) 16 | let appDelegate = UIApplication.shared.delegate as! AppDelegate 17 | #endif 18 | 19 | var universe: Universe 20 | 21 | init(universe: Universe) { 22 | self.universe = universe 23 | } 24 | 25 | func sendMayday() { 26 | guard appDelegate.gameState == .gameActive else { return } 27 | let me = Universe.universe.players[Universe.universe.me] 28 | let (planetOptional,_) = findClosestPlanet(location: CGPoint(x: me.positionX,y: me.positionY)) 29 | guard let planet = planetOptional else { return } 30 | 31 | let message = "MAYDAY near \(planet.name) shields \(me.shieldStrength) damage \(me.damage) armies \(me.armies)" 32 | self.sendMessage(message: message, sendToAll: false) 33 | } 34 | func sendEscort() { 35 | guard appDelegate.gameState == .gameActive else { return } 36 | let me = Universe.universe.players[Universe.universe.me] 37 | let (planetOptional,_) = findClosestPlanet(location: CGPoint(x: me.positionX,y: me.positionY)) 38 | guard let planet = planetOptional else { return } 39 | 40 | let message = "Request Escort near \(planet.name) shields \(me.shieldStrength) damage \(me.damage) armies \(me.armies)" 41 | self.sendMessage(message: message, sendToAll: false) 42 | } 43 | 44 | func sendMessage(message: String) { 45 | self.sendMessage(message: message, sendToAll: false) 46 | } 47 | func sendMessage(message: String, sendToAll: Bool) { 48 | if message == "" { 49 | return 50 | } 51 | if sendToAll { 52 | let data = MakePacket.cpMessage(message: message, team: .independent, individual: 0) 53 | self.appDelegate.reader?.send(content: data) 54 | } else { 55 | let data = MakePacket.cpMessage(message: message, team: self.universe.players[self.universe.me].team, individual: 0) 56 | self.appDelegate.reader?.send(content: data) 57 | } 58 | } 59 | private func findClosestPlanet(location: CGPoint) -> (planet: Planet?,distance: Int) { 60 | var closestPlanetDistance = 10000 61 | var closestPlanet: Planet? 62 | for planet in Universe.universe.planets { 63 | let thisPlanetDistance = abs(planet.positionX - Int(location.x)) + abs(planet.positionY - Int(location.y)) 64 | if thisPlanetDistance < closestPlanetDistance { 65 | closestPlanetDistance = thisPlanetDistance 66 | closestPlanet = planet 67 | } 68 | } 69 | return (closestPlanet,closestPlanetDistance) 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /NetrekMacOS/Views/PointingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PointingView.swift 3 | // Netrek2 4 | // 5 | // Created by Darrell Root on 5/7/20. 6 | // Copyright © 2020 Darrell Root. All rights reserved. 7 | // 8 | // Code in this file based don https://swiftui-lab.com/a-powerful-combo/ 9 | 10 | import Foundation 11 | import SwiftUI 12 | 13 | extension View { 14 | func pointingMouse(onPoint: @escaping (NSEvent,NSPoint) -> Void) -> some View { 15 | PointingAreaView(onPoint: onPoint) { self } 16 | } 17 | } 18 | 19 | struct PointingAreaView15 | Planets 16 |
17 |18 | A planet can be owned by one of the 4 teams. In rare cases, a failed attempt to capture a planet may result in that planet having 0 armies and being “independent”. Independent planets appear gray. A planet that has not been visited by your team since the start of the game, or since the last time it was captured, will also appear gray. 19 |
20 |
21 |
22 |
24 | A planet with extra repair facilities will show a wrench icon in the center. A planet with extra fuel will show a fuel can on the right side. Orbiting a friendly planet with these facilities will result in faster repair or refueling. 25 |
26 |
27 |
28 |
30 | A planet may not have any special facilities: 31 |
32 |
33 |
34 |
36 | You can use the l (lowercase L) command to “lock on” to a planet and, when you get there, orbit it. A planet with more than 4 armies will also show the army icon on its left side. When you orbit a friendly or enemy planet, you can bomb it (b), beam up armies (z) or beam down armies (x). Bombing is only effective when there are more than 4 enemy armies. The last few enemy armies have to be destroyed by beaming down armies. Bombing, followed by 5 armies, is generally sufficient to capture any enemy planet. 37 |
38 |
39 |
40 |
42 | Enemy planets damage your ship if you are close to it. 43 |
44 |
45 |
46 |
48 |
49 |
51 | feedback@networkmom.net 52 |
53 | 54 | --------------------------------------------------------------------------------