The JavaScript code below implements a prototype of a 4X strategy game set in space, similar to the old DOS game Ascendancy. It makes use of pieces of functionality from several previous posts. As usual, it’s nowhere near finished yet, and what code is in place could stand to be cleaned up.
To see the code in action, copy it into an .html file and open that file in a web browser that runs JavaScript. Or, for an online version, visit the URL “https://thiscouldbebetter.neocities.org/spacestrategygame.html“.
UPDATE 2018/01/06 – A much more advanced, albeit still unfinished, version of this code is available at the URL “https://github.com/thiscouldbebetter/SpaceStrategy4XGame“.
UPDATE 2017/12/19 – Some time ago I moved this to Github, at the URL “https://github.com/thiscouldbebetter/SpaceStrategy4XGame“. Some updates have been made to it since then, and future updates will likely be made there as well.
<html> <body> <script type="text/javascript"> // main function main() { //localStorage.clear(); var viewSize = new Coords(400, 300, 1); var universe0 = Universe.new(null); var applicationName = "Fretwork"; Globals.prototype.initialize ( applicationName, 50, // millisecondsPerTimerTick viewSize, universe0, // sounds [ new Sound("Click", "Click.wav"), new Sound("Music", "Music.mp3"), ], // videos [ new Video("Intro", "Intro.webm"), ] ); } // extensions function ArrayExtensions() { // do nothing } { Array.prototype.addLookups = function(keyName) { for (var i = 0; i < this.length; i++) { var item = this[i]; var key = DataBinding.get(item, keyName); this[key] = item; } } Array.prototype.intersectionWith = function(other) { var returnValues = []; for (var i = 0; i < this.length; i++) { var item = this[i]; if (other.indexOf(item) >= 0) { returnValues.push(item); } } return returnValues; } } // classes function Action_CameraMove(displacementAmountsRightAndDown) { this.displacementAmountsRightAndDown = displacementAmountsRightAndDown; this.displacement = new Coords(0, 0, 0); } { Action_CameraMove.prototype.perform = function(camera) { this.displacement.overwriteWith ( camera.orientation.right ).multiplyScalar ( this.displacementAmountsRightAndDown[0] ).add ( camera.orientation.down.clone().multiplyScalar ( this.displacementAmountsRightAndDown[1] ) ); var cameraPos = camera.loc.pos; cameraPos.add(this.displacement); var cameraPosAsPolar = Polar.fromCoords(cameraPos); cameraPosAsPolar.radius = camera.focalLength; cameraPos.overwriteWith(cameraPosAsPolar.toCoords()); var cameraOrientationForward = cameraPos.clone().multiplyScalar(-1); camera.orientation = new Orientation ( cameraOrientationForward, camera.orientation.down ); } } function Action_CylinderMove_Yaw(cyclesToMove) { this.cyclesToMove = cyclesToMove; } { Action_CylinderMove_Yaw.prototype.perform = function(actor) { var constraintCylinder = actor.constraints["PositionOnCylinder"]; constraintCylinder.yaw = NumberHelper.wrapValueToRangeMinMax ( constraintCylinder.yaw + this.cyclesToMove, 0, 1 ); } } function Action_CylinderMove_DistanceAlongAxis(distanceToMove) { this.distanceToMove = distanceToMove; } { Action_CylinderMove_DistanceAlongAxis.prototype.perform = function(actor) { var constraintCylinder = actor.constraints["PositionOnCylinder"]; constraintCylinder.distanceFromCenterAlongAxis += this.distanceToMove; } } function Action_CylinderMove_Radius(distanceToMove) { this.distanceToMove = distanceToMove; } { Action_CylinderMove_Radius.prototype.perform = function(actor) { var constraintCylinder = actor.constraints["PositionOnCylinder"]; constraintCylinder.radius += this.distanceToMove; } } function Activity(defnName, variables) { this.defnName = defnName; this.variables = variables; } { Activity.prototype.defn = function() { return (Globals.Instance.universe.activityDefns[this.defnName]); } Activity.prototype.perform = function(actor) { this.defn().perform(actor, this); } } function ActivityDefn(name, perform) { this.name = name; this.perform = perform; } { function ActivityDefn_Instances() { this.DoNothing = new ActivityDefn ( "DoNothing", function(actor, activity) { // do nothing } ); this.MoveToTarget = new ActivityDefn ( "MoveToTarget", // perform function(actor, activity) { var variables = activity.variables; var target = variables[0]; var distanceMovedThisStep = variables[1]; if (distanceMovedThisStep == null) { distanceMovedThisStep = 0; } var distanceToMoveThisTick = 3; // hack var distancePerStepMax = 100000; // hack if (distanceMovedThisStep + distanceToMoveThisTick > distancePerStepMax) { distanceToMoveThisTick = distancePerStepMax - distanceMovedThisStep; } var actorPos = actor.loc.pos; var targetPos = target.loc.pos; var displacementFromActorToTarget = targetPos.clone().subtract ( actorPos ); var distanceFromActorToTarget = displacementFromActorToTarget.magnitude(); if (distanceFromActorToTarget < distanceToMoveThisTick) { distanceToMoveThisTick = distanceFromActorToTarget; actorPos.overwriteWith(targetPos); actor.activity = null; // hack actor.order = null; // hack Globals.Instance.inputHelper.isEnabled = true; var targetDefnName = target.defn.name; if (targetDefnName == "LinkPortal") { var universe = Globals.Instance.universe; var links = universe.world.network.links; var venueCurrent = universe.venueCurrent; var starsystemFrom = venueCurrent.model; var starsystemNamesFromAndTo = target.starsystemNamesFromAndTo; var starsystemNameFrom = starsystemNamesFromAndTo[0]; var starsystemNameTo = starsystemNamesFromAndTo[1]; var link = links[starsystemNameFrom][starsystemNameTo]; starsystemFrom.ships.splice ( starsystemFrom.ships.indexOf(actor), 1 ); venueCurrent.bodies.splice ( venueCurrent.bodies.indexOf(actor), 1 ) link.ships.push(actor); var direction; if (link.nodesLinked()[0].starsystem == starsystemFrom) { direction = 1; } else { direction = -1; } actorPos.overwriteWithDimensions(0, 0, 0); var speed = 10; // hack actor.vel.overwriteWithDimensions(speed * direction, 0, 0); } else if (targetDefnName == "Planet") { alert("todo - planet collision"); } } else { var directionFromActorToTarget = displacementFromActorToTarget.divideScalar ( distanceFromActorToTarget ); actor.vel.overwriteWith ( directionFromActorToTarget ).multiplyScalar ( distanceToMoveThisTick ); actorPos.add(actor.vel); } distanceMovedThisStep += distanceToMoveThisTick; variables[1] = distanceMovedThisStep; } ); this._All = [ this.DoNothing, this.MoveToTarget, ]; } ActivityDefn.Instances = new ActivityDefn_Instances(); } function Body(name, defn, pos) { this.name = name; this.defn = defn; this.loc = new Location(pos); } function BodyDefn(name, size, visual) { this.name = name; this.size = size; this.visual = visual; this.sizeHalf = this.size.clone().divideScalar(2); } function Bounds(min, max) { this.min = min; this.max = max; } function BuildableDefn(name, systemColor, industryToBuild) { this.name = name; this.systemColor = systemColor; this.industryToBuild = industryToBuild; } function Camera(viewSize, focalLength, pos, orientation) { this.viewSize = viewSize; this.focalLength = focalLength; this.loc = new Location(pos); this.orientation = orientation; this.viewSizeHalf = this.viewSize.clone().divideScalar(2); } { Camera.prototype.convertWorldCoordsToViewCoords = function(coordsToConvert) { coordsToConvert.subtract ( this.loc.pos ); coordsToConvert.overwriteWithDimensions ( this.orientation.right.dotProduct(coordsToConvert), this.orientation.down.dotProduct(coordsToConvert), this.orientation.forward.dotProduct(coordsToConvert) ); var distanceForwardInFocalLengths = coordsToConvert.z / this.focalLength; coordsToConvert.x /= distanceForwardInFocalLengths; coordsToConvert.y /= distanceForwardInFocalLengths; coordsToConvert.x += this.viewSize.x / 2; coordsToConvert.y += this.viewSize.y / 2; return coordsToConvert; } Camera.prototype.rayToViewPos = function(posToProjectRayTo) { var orientation = this.orientation; var returnValue = new Ray ( this.loc.pos, orientation.forward.clone().multiplyScalar ( this.focalLength ).add ( orientation.right.clone().multiplyScalar ( posToProjectRayTo.x ) ).add ( orientation.down.clone().multiplyScalar ( posToProjectRayTo.y ) ) ); return returnValue; } } function Collision() { this.pos = new Coords(0, 0, 0); this.distanceToCollision = null; this.colliders = []; } { // static methods Collision.rayAndBodies = function(ray, bodies, bodyRadius, listToAddTo) { for (var i = 0; i < bodies.length; i++) { var body = bodies[i]; var collisionOfRayWithBody = new Collision().rayAndSphere ( ray, new Sphere ( bodyRadius, body.loc.pos ) ); if (collisionOfRayWithBody.distanceToCollision != null) { collisionOfRayWithBody.colliders.push ( body ); listToAddTo.push ( collisionOfRayWithBody ); } } return listToAddTo; } // instance methods Collision.prototype.rayAndFace = function(ray, face) { this.rayAndPlane ( ray, face.plane ); if (this.colliders["Plane"] != null) { if (this.isPosWithinFace(face) == false) { this.colliders["Face"] = null; } else { this.colliders["Face"] = face; var displacementFromVertex0ToCollision = new Coords(0, 0, 0); for (var t = 0; t < face.triangles.length; t++) { var triangle = face.triangles[t]; if (this.isPosWithinFace(triangle) == true) { this.colliders["Triangle"] = triangle; break; } } } } return this; } Collision.prototype.rayAndPlane = function(ray, plane) { this.distanceToCollision = ( plane.distanceFromOrigin - plane.normal.dotProduct(ray.startPos) ) / plane.normal.dotProduct(ray.direction); if (this.distanceToCollision >= 0) { this.pos.overwriteWith ( ray.direction ).multiplyScalar ( this.distanceToCollision ).add ( ray.startPos ); this.colliders["Plane"] = plane; } return this; } Collision.prototype.rayAndSphere = function(ray, sphere) { var rayDirection = ray.direction; var displacementFromSphereCenterToCamera = ray.startPos.clone().subtract ( sphere.centerPos ); var sphereRadius = sphere.radius; var sphereRadiusSquared = sphereRadius * sphereRadius; var a = rayDirection.dotProduct(rayDirection); var b = 2 * rayDirection.dotProduct ( displacementFromSphereCenterToCamera ); var c = displacementFromSphereCenterToCamera.dotProduct ( displacementFromSphereCenterToCamera ) - sphereRadiusSquared; var discriminant = (b * b) - 4 * a * c; if (discriminant >= 0) { var rootOfDiscriminant = Math.sqrt(discriminant); var distanceToCollision1 = (rootOfDiscriminant - b) / (2 * a); var distanceToCollision2 = (0 - rootOfDiscriminant - b) / (2 * a); if (distanceToCollision1 >= 0) { if ( distanceToCollision2 >= 0 && distanceToCollision2 < distanceToCollision1 ) { this.distanceToCollision = distanceToCollision2; } else { this.distanceToCollision = distanceToCollision1; } } else { this.distanceToCollision = distanceToCollision2; } this.pos.overwriteWith ( ray.direction ).multiplyScalar ( this.distanceToCollision ).add ( ray.startPos ); this.colliders["Sphere"] = sphere; } return this; } Collision.prototype.isPosWithinFace = function(face) { var displacementFromVertex0ToCollision = new Coords(0, 0); var isPosWithinAllEdgesOfFaceSoFar = true; var edges = face.edgesRectified; for (var e = 0; e < edges.length; e++) { var edgeFromFace = edges[e]; displacementFromVertex0ToCollision.overwriteWith ( this.pos ).subtract ( edgeFromFace.vertices[0].pos ); // hack? var epsilon = .01; var dotProduct = displacementFromVertex0ToCollision.dotProduct ( edgeFromFace.transverse ); if (dotProduct >= epsilon) { isPosWithinAllEdgesOfFaceSoFar = false; break; } } return isPosWithinAllEdgesOfFaceSoFar; } } function Color(name, systemColor) { this.name = name; this.systemColor = systemColor; } { function Color_Instances() { this.Black = new Color("Black", "rgb(0, 0, 0)"); this.Blue = new Color("Blue", "rgb(0, 0, 255)"); this.Brown = new Color("Brown", "Brown"); this.Cyan = new Color("Cyan", "rgb(0, 255, 255)"); //this.CyanHalfTranslucent = new Color("CyanHalfTranslucent", "rgba(0, 128, 128, .5)"); this.CyanHalfTranslucent = new Color("CyanHalfTranslucent", "rgba(128, 128, 128, .1)"); this.Gray = new Color("Gray", "rgb(128, 128, 128)"); this.GrayLight = new Color("Gray", "rgb(224, 224, 224)"); this.Green = new Color("Green", "rgb(0, 255, 0)"); this.Orange = new Color("Orange", "Orange"); this.Red = new Color("Red", "rgb(255, 0, 0)"); this.Violet = new Color("Violet", "Violet"); this.Yellow = new Color("Yellow", "rgb(255, 255, 0)"); this.YellowDark = new Color("YellowDark", "rgb(192, 192, 0)"); this.White = new Color("White", "rgb(255, 255, 255)"); } Color.Instances = new Color_Instances(); } function Constraint_Cursor() { this.name = "Cursor"; } { Constraint_Cursor.prototype.applyToBody = function(body) { var cursor = body; var venue = Globals.Instance.universe.venueCurrent; var mousePos = Globals.Instance.inputHelper.mouseMovePos.clone(); var camera = venue.camera; var cameraPos = camera.loc.pos; var cameraOrientation = camera.orientation; mousePos.subtract(camera.viewSizeHalf); var xyPlaneNormal = new Coords(0, 0, 1); var boundsToRestrictTo; var cursorPos = cursor.loc.pos; var cameraForward = cameraOrientation.forward; var displacementFromCameraToMousePosProjected = cameraForward.clone().multiplyScalar ( camera.focalLength ).add ( cameraOrientation.right.clone().multiplyScalar ( mousePos.x ) ).add ( cameraOrientation.down.clone().multiplyScalar ( mousePos.y ) ); var rayFromCameraToMousePos = new Ray ( cameraPos, displacementFromCameraToMousePosProjected ); if (cursor.hasXYPositionBeenSpecified == false) { boundsToRestrictTo = new Bounds ( new Coords ( Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, 0 ), new Coords ( Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, 0 ) ); var planeToRestrictTo = new Plane ( xyPlaneNormal, 0 ); var collisionPos = new Collision().rayAndPlane ( rayFromCameraToMousePos, planeToRestrictTo ).pos; if (collisionPos != null) { body.loc.pos.overwriteWith(collisionPos); } } else { boundsToRestrictTo = new Bounds ( new Coords ( cursorPos.x, cursorPos.y, Number.NEGATIVE_INFINITY ), new Coords ( cursorPos.x, cursorPos.y, Number.POSITIVE_INFINITY ) ); var planeNormal = xyPlaneNormal.clone().crossProduct ( cameraOrientation.right ); cursorPos.z = 0; var planeToRestrictTo = new Plane ( planeNormal, cursorPos.dotProduct(planeNormal) ); var collisionPos = new Collision().rayAndPlane ( rayFromCameraToMousePos, planeToRestrictTo ).pos; if (collisionPos != null) { cursorPos.z = collisionPos.z; } } cursorPos.trimToRangeMinMax ( boundsToRestrictTo.min, boundsToRestrictTo.max ); } } function Constraint_LookAtBody ( targetBody ) { this.name = "LookAtBody"; this.targetBody = targetBody; } { Constraint_LookAtBody.prototype.applyToBody = function(body) { var targetPos = this.targetBody; // hack var bodyPos = body.loc.pos var bodyOrientationForward = targetPos.clone().subtract ( bodyPos ).normalize(); body.orientation = new Orientation ( bodyOrientationForward, body.orientation.down ); } } function Constraint_PositionOnCylinder ( center, orientation, yaw, radius, distanceFromCenterAlongAxis ) { this.name = "PositionOnCylinder"; this.center = center; this.orientation = orientation; this.yaw = yaw; this.radius = radius; this.distanceFromCenterAlongAxis = distanceFromCenterAlongAxis; } { Constraint_PositionOnCylinder.prototype.applyToBody = function(body) { this.yaw = NumberHelper.wrapValueToRangeMinMax(this.yaw, 0, 1); var yawInRadians = this.yaw * Polar.RadiansPerCycle; var bodyPos = body.loc.pos; bodyPos.overwriteWith ( this.orientation.down ).multiplyScalar ( this.distanceFromCenterAlongAxis ).add ( this.center ).add ( this.orientation.forward.clone().multiplyScalar ( Math.cos(yawInRadians) ).add ( this.orientation.right.clone().multiplyScalar ( Math.sin(yawInRadians) ) ).multiplyScalar ( this.radius ) ) body.orientation.overwriteWith(this.orientation); } } function Control() {} { Control.isEnabled = function() { return this.dataBindingForIsEnabled.get(); } } function ControlBuilder() {} { // constants ControlBuilder.ColorsForeAndBackDefault = [ "Gray", "White" ]; // static methods ControlBuilder.configure = function() { var displayHelper = Globals.Instance.displayHelper; var containerSize = displayHelper.viewSize.clone(); var controlHeight = containerSize.y / 12; var columnWidth = 100; var labelWidth = 75; var margin = 10; var buttonWidth = (columnWidth - margin) / 2; var returnValue = new ControlContainer ( "containerConfigure", ControlBuilder.ColorsForeAndBackDefault, new Coords(0, 0), // pos containerSize, // children [ new ControlButton ( "buttonSave", new Coords((containerSize.x - columnWidth) / 2, 15), // pos new Coords(columnWidth, controlHeight), // size "Save", null, // dataBindingForIsEnabled // click function () { var universe = Globals.Instance.universe; var profile = universe.profile; var world = universe.world; world.dateSaved = DateTime.now(); Globals.Instance.profileHelper.profileSave ( profile ); var venueNext = new VenueWorld(world); venueNext = new VenueFader(venueNext); universe.venueNext = venueNext; } ), new ControlLabel ( "labelMusicVolume", new Coords((containerSize.x - columnWidth) / 2, 45), // pos new Coords(labelWidth, controlHeight), // size false, // isTextCentered new DataBinding("Music Volume:") ), new ControlSelect ( "selectMusicVolume", new Coords((containerSize.x - columnWidth) / 2 + labelWidth, 45), // pos new Coords(controlHeight, controlHeight), // size // dataBindingForValueSelected new DataBinding ( Globals.Instance.soundHelper, "musicVolume" ), // dataBindingForOptions new DataBinding ( SoundHelper.controlSelectOptionsVolume(), null ), "value", // bindingExpressionForOptionValues, "text", // bindingExpressionForOptionText new DataBinding(true) // dataBindingForIsEnabled ), new ControlLabel ( "labelSoundVolume", new Coords((containerSize.x - columnWidth) / 2, 75), // pos new Coords(labelWidth, controlHeight), // size false, // isTextCentered new DataBinding("Sound Volume:") ), new ControlSelect ( "selectSoundVolume", new Coords ( (containerSize.x - columnWidth) / 2 + labelWidth, 75 ), // pos new Coords(controlHeight, controlHeight), // size // dataBindingForValueSelected new DataBinding ( Globals.Instance.soundHelper, "soundVolume" ), // dataBindingForOptions new DataBinding ( SoundHelper.controlSelectOptionsVolume(), null ), "value", // bindingExpressionForOptionValues, "text", // bindingExpressionForOptionText new DataBinding(true) // dataBindingForIsEnabled ), new ControlButton ( "buttonReturn", new Coords((containerSize.x - columnWidth) / 2, 105), // pos new Coords(buttonWidth, controlHeight), // size "Return", null, // dataBindingForIsEnabled // click function() { var universe = Globals.Instance.universe; var world = universe.world; var venueNext = new VenueWorld(world); venueNext = new VenueFader(venueNext); universe.venueNext = venueNext; } ), new ControlButton ( "buttonQuit", new Coords((containerSize.x - columnWidth) / 2 + buttonWidth + margin, 105), // pos new Coords(buttonWidth, controlHeight), // size "Quit", null, // dataBindingForIsEnabled // click function() { Globals.Instance.reset(); var universe = Globals.Instance.universe; var venueNext = new VenueControls ( ControlBuilder.title() ); venueNext = new VenueFader(venueNext); universe.venueNext = venueNext; } ), ] ); return returnValue; } ControlBuilder.confirm = function(message, confirm, cancel) { var containerSize = Globals.Instance.displayHelper.viewSize.clone(); var controlHeight = containerSize.y / 12; var buttonWidth = 45; var margin = 10; var returnValue = new ControlContainer ( "containerConfirm", ControlBuilder.ColorsForeAndBackDefault, new Coords(0, 0), // pos containerSize, // children [ new ControlLabel ( "labelMessage", new Coords((containerSize.x - 100) / 2, 50), // pos new Coords(100, controlHeight), // size true, // isTextCentered new DataBinding(message) ), new ControlButton ( "buttonConfirm", new Coords ( ( containerSize.x - (buttonWidth * 2 + margin) ) / 2, 100 ), // pos new Coords(buttonWidth, controlHeight), // size "Confirm", null, // dataBindingForIsEnabled confirm ), new ControlButton ( "buttonCancel", new Coords ( ( containerSize.x - (buttonWidth * 2 + margin) ) / 2 + buttonWidth + margin, 100 ), // pos new Coords(buttonWidth, controlHeight), // size "Cancel", null, // dataBindingForIsEnabled cancel ), ] ); return returnValue; } ControlBuilder.profileDetail = function() { var containerSize = Globals.Instance.displayHelper.viewSize.clone(); var controlHeight = containerSize.y / 12; var buttonWidth = 45; var margin = 10; var returnValue = new ControlContainer ( "containerProfileDetail", ControlBuilder.ColorsForeAndBackDefault, new Coords(0, 0), // pos containerSize, // children [ new ControlLabel ( "labelProfileName", new Coords ( (containerSize.x - 100) / 2, controlHeight ), // pos new Coords(100, controlHeight), // size true, // isTextCentered new DataBinding(Globals.Instance.universe.profile.name) ), new ControlSelect ( "listWorlds", new Coords((containerSize.x - 150) / 2, 50), // pos new Coords(150, 50), // size // dataBindingForValueSelected new DataBinding(Globals.Instance.universe, "world"), // dataBindingForOptions new DataBinding ( Globals.Instance.universe.profile.worlds, null ), null, // bindingExpressionForOptionValues "name", // bindingExpressionForOptionText new DataBinding(true), // dataBindingForIsEnabled 4 // numberOfItemsVisible ), new ControlButton ( "buttonBack", new Coords(margin, margin), // pos new Coords(controlHeight, controlHeight), // size "<", null, // dataBindingForIsEnabled // click function () { var venueNext = new VenueControls ( ControlBuilder.profileSelect() ); venueNext = new VenueFader(venueNext); var universe = Globals.Instance.universe; universe.venueNext = venueNext; } ), new ControlButton ( "buttonNew", new Coords ( ( containerSize.x - (buttonWidth * 2 + margin) ) / 2, 110 ), // pos new Coords(buttonWidth, controlHeight), // size "New", null, // dataBindingForIsEnabled // click function () { var world = World.new(); var universe = Globals.Instance.universe; var profile = universe.profile; profile.worlds.push(world); Globals.Instance.profileHelper.profileSave ( profile ); universe.world = world; var venueNext = new VenueControls ( ControlBuilder.worldDetail() ); venueNext = new VenueVideo ( "Intro", // videoName venueNext ); venueNext = new VenueFader(venueNext); universe.venueNext = venueNext; } ), new ControlButton ( "buttonSelectWorld", new Coords ( ( containerSize.x - (buttonWidth * 2 + margin) ) / 2 + 45 + margin, 110 ), // pos new Coords(buttonWidth, controlHeight), // size "Select", // dataBindingForIsEnabled new DataBinding(Globals.Instance.universe, "isWorldSelected"), // click function () { var venueNext = new VenueControls ( ControlBuilder.worldDetail() ); venueNext = new VenueFader(venueNext); Globals.Instance.universe.venueNext = venueNext; } ), new ControlButton ( "buttonDeleteProfile", new Coords(containerSize.x - controlHeight - margin, margin), // pos new Coords(controlHeight, controlHeight), // size "X", null, // dataBindingForIsEnabled // click function () { var universe = Globals.Instance.universe; var profile = universe.profile; var controlConfirm = ControlBuilder.confirm ( "Delete Profile \"" + profile.name + "\"?", // confirm function() { var universe = Globals.Instance.universe; var profile = universe.profile; Globals.Instance.profileHelper.profileDelete ( profile ); universe.profile = null; var venueNext = new VenueControls ( ControlBuilder.profileSelect() ); venueNext = new VenueFader(venueNext); universe.venueNext = venueNext; }, // cancel function() { var venueNext = new VenueControls ( ControlBuilder.profileDetail() ); venueNext = new VenueFader(venueNext); var universe = Globals.Instance.universe; universe.venueNext = venueNext; } ); var venueNext = new VenueControls(controlConfirm); venueNext = new VenueFader(venueNext); universe.venueNext = venueNext; } ), ] ); return returnValue; } ControlBuilder.profileNew = function() { var containerSize = Globals.Instance.displayHelper.viewSize.clone(); var buttonWidth = 45; var controlHeight = containerSize.y / 12; var margin = 10; return new ControlContainer ( "containerProfileNew", ControlBuilder.ColorsForeAndBackDefault, new Coords(0, 0), // pos containerSize, // children [ new ControlLabel ( "labelName", new Coords((containerSize.x - 100) / 2, 25), // pos new Coords(100, 25), // size true, // isTextCentered new DataBinding("Name:") ), new ControlTextBox ( "textBoxName", new Coords((containerSize.x - 100) / 2, 50), // pos new Coords(100, 25), // size "" ), new ControlButton ( "buttonCreate", new Coords((containerSize.x - (buttonWidth * 2 + margin)) / 2, 80), // pos new Coords(buttonWidth, controlHeight), // size "Create", null, // dataBindingForIsEnabled // click function () { var container = Globals.Instance.universe.venueCurrent.controlRoot; var textBoxName = container.children["textBoxName"]; var profileName = textBoxName.text(); if (profileName == "") { return; } var profile = new Profile(profileName, []); Globals.Instance.profileHelper.profileAdd ( profile ); var universe = Globals.Instance.universe; universe.profile = profile; var venueNext = new VenueControls ( ControlBuilder.profileDetail() ); venueNext = new VenueFader(venueNext); universe.venueNext = venueNext; } ), new ControlButton ( "buttonCancel", new Coords ( ( containerSize.x - (buttonWidth * 2 + margin) ) / 2 + buttonWidth + margin, 80 ), // pos new Coords(buttonWidth, controlHeight), // size "Cancel", null, // dataBindingForIsEnabled // click function () { var universe = Globals.Instance.universe; var venueNext = new VenueControls ( ControlBuilder.profileSelect() ); venueNext = new VenueFader(venueNext); universe.venueNext = venueNext; } ), ] ); } ControlBuilder.profileSelect = function() { var profiles = Globals.Instance.profileHelper.profiles(); var containerSize = Globals.Instance.displayHelper.viewSize.clone(); var controlHeight = containerSize.y / 12; var buttonWidth = 45; var margin = 10; var returnValue = new ControlContainer ( "containerProfileSelect", ControlBuilder.ColorsForeAndBackDefault, new Coords(0, 0), // pos containerSize, // children [ new ControlLabel ( "labelSelectAProfile", new Coords((containerSize.x - 100) / 2, 25), // pos new Coords(100, controlHeight), // size true, // isTextCentered new DataBinding("Select a Profile:") ), new ControlSelect ( "listProfiles", new Coords((containerSize.x - 100) / 2, 55), // pos new Coords(100, 50), // size // dataBindingForValueSelected new DataBinding(Globals.Instance.universe, "profile"), new DataBinding(profiles), // options null, // bindingExpressionForOptionValues "name", // bindingExpressionForOptionText, new DataBinding(true), // isEnabled 4 // numberOfItemsVisible ), new ControlButton ( "buttonNew", new Coords((containerSize.x - 100) / 2, 110), // pos new Coords(buttonWidth, controlHeight), // size "New", null, // dataBindingForIsEnabled // click function () { var universe = Globals.Instance.universe; var venueNext = new VenueControls ( ControlBuilder.profileNew() ); venueNext = new VenueFader(venueNext); universe.venueNext = venueNext; } ), new ControlButton ( "buttonInstant", new Coords((containerSize.x - 100) / 2, 150), // pos new Coords(100, controlHeight), // size "Instant", null, // dataBindingForIsEnabled // click function () { var universe = Globals.Instance.universe; var profile = new Profile("Default", []); universe.profile = profile; var world = World.new(); profile.worlds.push(world); universe.world = world; var venueNext = new VenueWorld ( universe.world ); venueNext = new VenueFader(venueNext); universe.venueNext = venueNext; } ), new ControlButton ( "buttonSelectProfile", new Coords((containerSize.x - 100) / 2 + buttonWidth + margin, 110), // pos new Coords(buttonWidth, controlHeight), // size "Select", // dataBindingForIsEnabled new DataBinding(Globals.Instance.universe, "isProfileSelected"), // click function() { var venueNext = new VenueControls ( ControlBuilder.profileDetail() ); venueNext = new VenueFader(venueNext); Globals.Instance.universe.venueNext = venueNext; } ), ] ); return returnValue; } ControlBuilder.selection = function ( pos, size, margin, controlHeight ) { var returnValue = new ControlContainer ( "containerSelected", ControlBuilder.ColorsForeAndBackDefault, pos.clone(), size.clone(), // children [ new ControlLabel ( "labelSelected", new Coords(margin, 0), // pos new Coords(0, controlHeight), // size false, // isTextCentered new DataBinding("Selection:") ), new ControlPlaceholder ( "placeholderSelection", new Coords(margin, controlHeight), // pos size.clone(), // size - hack "[none]", new DataBinding ( Globals.Instance.universe, "venueCurrent.selection.controlBuild_Selection" ) ), ] ); return returnValue; } ControlBuilder.timeAndPlace = function ( containerMainSize, containerInnerSize, margin, controlHeight ) { var returnValue = new ControlContainer ( "containerTimeAndPlace", ControlBuilder.ColorsForeAndBackDefault, new Coords ( margin, margin ), containerInnerSize, // children [ new ControlLabel ( "textDate", new Coords(margin, margin), // pos new Coords ( containerInnerSize.x - 30 - margin * 3, controlHeight ), // size false, // isTextCentered new DataBinding(Globals.Instance.universe, "venueCurrent.model.name") ), new ControlLabel ( "labelTurn", new Coords(margin, margin + controlHeight), // pos new Coords(0, controlHeight), // size false, // isTextCentered new DataBinding("Turn:") ), new ControlLabel ( "textTurn", new Coords(margin + 25, margin + controlHeight), // pos new Coords ( containerInnerSize.x - 30 - margin * 3, controlHeight ), // size false, // isTextCentered new DataBinding(Globals.Instance.universe.world, "turnsSoFar") ), new ControlButton ( "buttonTurnNext", // name, new Coords(margin + 50, margin + controlHeight), // pos new Coords(controlHeight, controlHeight), // size, ">", // text, null, // dataBindingForIsEnabled, // click function() { var universe = Globals.Instance.universe; universe.world.updateForTurn(); } ), new ControlButton ( "buttonTurnFastForward", // name, new Coords(margin + 50 + controlHeight, margin + controlHeight), // pos new Coords(controlHeight, controlHeight), // size, ">>", // text, null, // dataBindingForIsEnabled, // click function() { alert("todo - fast forward"); } ) ] ); return returnValue; } ControlBuilder.title = function() { var containerSize = Globals.Instance.displayHelper.viewSize.clone(); var returnValue = new ControlContainer ( "containerTitle", ControlBuilder.ColorsForeAndBackDefault, new Coords(0, 0), // pos containerSize, // children [ new ControlImage ( "imageTitle", new Coords(0, 0), containerSize, "Title.png" ), new ControlButton ( "buttonStart", new Coords ( (containerSize.x - 50) / 2, containerSize.y - 50 ), // pos new Coords(50, 25), // size "Start", null, // dataBindingForIsEnabled // click function() { var venueNext = new VenueControls ( ControlBuilder.profileSelect() ); venueNext = new VenueFader(venueNext); var universe = Globals.Instance.universe; universe.venueNext = venueNext; } ), ] ); return returnValue; } ControlBuilder.view = function ( containerMainSize, containerInnerSize, margin, controlHeight ) { var cameraSpeed = 10; var returnValue = new ControlContainer ( "containerViewControls", ControlBuilder.ColorsForeAndBackDefault, new Coords ( margin, containerMainSize.y - margin - containerInnerSize.y ), containerInnerSize, // children [ new ControlLabel ( "labelControls", new Coords(margin, 0),// pos new Coords(0, controlHeight), // size false, // isTextCentered new DataBinding("View") ), new ControlButton ( "buttonViewUp", new Coords ( (containerInnerSize.x - (controlHeight * 3)) / 2 + controlHeight, controlHeight ), // pos new Coords(controlHeight, controlHeight), // size "^", null, // dataBindingForIsEnabled // click function () { var venueCurrent = Globals.Instance.universe.venueCurrent; var camera = venueCurrent.camera; new Action_CameraMove([0, cameraSpeed]).perform(camera); } ), new ControlButton ( "buttonViewDown", new Coords ( ( containerInnerSize.x - (controlHeight * 3) ) / 2 + controlHeight, controlHeight * 2 ), // pos new Coords(controlHeight, controlHeight), // size "v", null, // dataBindingForIsEnabled // click function () { var universe = Globals.Instance.universe; var venueCurrent = universe.venueCurrent; var camera = venueCurrent.camera; new Action_CameraMove([0, 0 - cameraSpeed]).perform(camera); } ), new ControlButton ( "buttonViewLeft", new Coords ( (containerInnerSize.x - (controlHeight * 3)) / 2, controlHeight * 2 ), // pos new Coords(controlHeight, controlHeight), // size "<", null, // dataBindingForIsEnabled // click function () { var venueCurrent = Globals.Instance.universe.venueCurrent; var camera = venueCurrent.camera; new Action_CameraMove([cameraSpeed, 0]).perform(camera); } ), new ControlButton ( "buttonViewRight", new Coords ( ( containerInnerSize.x - (controlHeight * 3) ) / 2 + controlHeight * 2, controlHeight * 2 ), // pos new Coords(controlHeight, controlHeight), // size ">", null, // dataBindingForIsEnabled // click function () { var venueCurrent = Globals.Instance.universe.venueCurrent; var camera = venueCurrent.camera; new Action_CameraMove([0 - cameraSpeed, 0]).perform ( camera ); } ), ] ); return returnValue; } ControlBuilder.worldDetail = function() { var universe = Globals.Instance.universe; var world = universe.world; var containerSize = Globals.Instance.displayHelper.viewSize.clone(); var controlHeight = containerSize.y / 12; var margin = 10; var returnValue = new ControlContainer ( "containerWorldDetail", ControlBuilder.ColorsForeAndBackDefault, new Coords(0, 0), // pos containerSize, // children [ new ControlLabel ( "labelProfileName", new Coords((containerSize.x - 100) / 2, 15), // pos new Coords(100, controlHeight), // size true, // isTextCentered new DataBinding(universe.profile.name) ), new ControlLabel ( "labelWorldName", new Coords((containerSize.x - 100) / 2, 30), // pos new Coords(100, controlHeight), // size true, // isTextCentered new DataBinding(world.name) ), new ControlLabel ( "labelStartDate", new Coords((containerSize.x - 100) / 2, 45), // pos new Coords(100, controlHeight), // size true, // isTextCentered new DataBinding ( "Started:" + world.dateCreated.toStringTimestamp() ) ), new ControlLabel ( "labelSavedDate", new Coords((containerSize.x - 100) / 2, 60), // pos new Coords(100, controlHeight), // size true, // isTextCentered new DataBinding ( "Saved:" + world.dateSaved.toStringTimestamp() ) ), new ControlButton ( "buttonStart", new Coords((containerSize.x - 100) / 2, 100), // pos new Coords(100, controlHeight), // size "Start", null, // dataBindingForIsEnabled // click function () { var universe = Globals.Instance.universe; var venueNext = new VenueWorld ( universe.world ); venueNext = new VenueFader(venueNext); universe.venueNext = venueNext; } ), new ControlButton ( "buttonBack", new Coords(margin, margin), // pos new Coords(controlHeight, controlHeight), // size "<", null, // dataBindingForIsEnabled // click function () { var venueNext = new VenueControls ( ControlBuilder.profileDetail() ); venueNext = new VenueFader(venueNext); var universe = Globals.Instance.universe; universe.venueNext = venueNext; } ), new ControlButton ( "buttonDeleteWorld", new Coords(containerSize.x - margin - controlHeight, margin), // pos new Coords(controlHeight, controlHeight), // size "X", null, // dataBindingForIsEnabled // click function () { var universe = Globals.Instance.universe; var profile = universe.profile; var world = universe.world; var controlConfirm = ControlBuilder.confirm ( "Delete World \"" + world.name + "\"?", // confirm function() { var universe = Globals.Instance.universe; var profile = universe.profile; var world = universe.world; var worlds = profile.worlds; var worldIndex = worlds.indexOf(world); worlds.splice ( worldIndex, 1 ); universe.world = null; Globals.Instance.profileHelper.profileSave ( profile ); var venueNext = new VenueControls ( ControlBuilder.profileDetail() ); venueNext = new VenueFader(venueNext); universe.venueNext = venueNext; }, // cancel function() { var venueNext = new VenueControls ( ControlBuilder.worldDetail() ); venueNext = new VenueFader(venueNext); var universe = Globals.Instance.universe; universe.venueNext = venueNext; } ); var venueNext = new VenueControls(controlConfirm); venueNext = new VenueFader(venueNext); universe.venueNext = venueNext; } ), ] ); return returnValue; } } function ControlPlaceholder(name, pos, size, textToDisplayIfEmpty, dataBinding) { this.name = name; this.pos = pos; this.size = size; this.textToDisplayIfEmpty = textToDisplayIfEmpty; this.dataBinding = dataBinding; this.controlLabelToDisplayIfEmpty = new ControlLabel ( this.name + "_Empty", this.pos, new Coords(0, 0), // this.size false, // isTextCentered new DataBinding(this.textToDisplayIfEmpty) ); } { ControlPlaceholder.prototype.childWithFocus = function() { return this.controlBound().childWithFocus(); } ControlPlaceholder.prototype.childrenAtPosAddToList = function ( posAbsolute, posToCheck, listToAddTo, addFirstChildOnly ) { return this.controlBound().childrenAtPosAddtoList ( posAbsolute, posToCheck, listToAddTo, addFirstChildOnly ); } ControlPlaceholder.prototype.controlBound = function() { var returnValue = this.dataBinding.get(); if (returnValue == null) { returnValue = this.controlLabelToDisplayIfEmpty; } return returnValue; } ControlPlaceholder.prototype.draw = function(pos) { this.controlBound().draw(pos); } ControlPlaceholder.prototype.isEnabled = function() { return this.controlBound().isEnabled(); } ControlPlaceholder.prototype.mouseClick = function(mouseClickPos, pos) { var controlBound = this.controlBound(); if (controlBound.mouseClick != null) { controlBound.mouseClick(mouseClickPos, pos); } } ControlPlaceholder.prototype.mouseEnter = function(mouseMovePos) { // fix - see mouseClick var mouseEnter = this.controlBound().mouseEnter; if (mouseEnter != null) { mouseEnter(mouseMovePos); } } ControlPlaceholder.prototype.mouseExit = function(mouseMovePos) { // fix - see mouseClick var mouseExit = this.controlBound().mouseExit; if (mouseExit != null) { mouseExit(mouseMovePos); } } } function ControlButton(name, pos, size, text, dataBindingForIsEnabled, click) { this.name = name; this.pos = pos; this.size = size; this.text = text; this.dataBindingForIsEnabled = dataBindingForIsEnabled; this.click = click; if (this.dataBindingForIsEnabled == null) { this.dataBindingForIsEnabled = new DataBinding(true); } this.isHighlighted = false; } { ControlButton.prototype.draw = function(pos) { Globals.Instance.displayHelper.drawControlButton(this, pos); } ControlButton.prototype.isEnabled = Control.isEnabled; ControlButton.prototype.mouseClick = function(clickPos) { if (this.isEnabled() == true) { this.click(); } Globals.Instance.inputHelper.isMouseLeftPressed = false; } ControlButton.prototype.mouseEnter = function(mouseMovePos) { this.isHighlighted = true; } ControlButton.prototype.mouseExit = function(mouseMovePos) { this.isHighlighted = false; } } function ControlContainer(name, colorsForeAndBack, pos, size, children) { if (colorsForeAndBack == null) { colorsForeAndBack = [ "Gray", "rgba(0, 0, 0, 0)" ]; } this.name = name; this.colorsForeAndBack = colorsForeAndBack; this.pos = pos; this.size = size; this.children = children; this.children.addLookups("name"); this.indexOfChildWithFocus = null; this.childrenContainingPos = []; this.childrenContainingPosPrev = []; } { // instance methods ControlContainer.prototype.childWithFocus = function() { var returnValue = ( this.indexOfChildWithFocus == null ? null : this.children[this.indexOfChildWithFocus] ); return returnValue; } ControlContainer.prototype.childrenAtPosAddToList = function ( posAbsolute, posToCheck, listToAddTo, addFirstChildOnly ) { var children = this.children; for (var i = children.length - 1; i >= 0; i--) { var child = children[i]; var childPos = child.pos.clone().add(posAbsolute); var doesChildContainPos = posToCheck.isWithinRangeMinMax ( childPos, childPos.clone().add(child.size) ); if (doesChildContainPos == true) { listToAddTo.push(child); if (addFirstChildOnly == true) { break; } } } return listToAddTo; } ControlContainer.prototype.draw = function(pos) { Globals.Instance.displayHelper.drawControlContainer(this, pos); } ControlContainer.prototype.keyPressed = function ( keyCodePressed, isShiftKeyPressed ) { var childWithFocus = this.childWithFocus(); if (childWithFocus != null) { if (childWithFocus.keyPressed != null) { childWithFocus.keyPressed ( keyCodePressed, isShiftKeyPressed ); } } } ControlContainer.prototype.mouseClick = function(mouseClickPos, pos) { var childrenContainingPos = this.childrenContainingPos; childrenContainingPos.length = 0; this.childrenAtPosAddToList ( pos, mouseClickPos, childrenContainingPos, true ); for (var i = 0; i < childrenContainingPos.length; i++) { var child = childrenContainingPos[i]; if (child.mouseClick != null) { this.indexOfChildWithFocus = this.children.indexOf(child); child.mouseClick(mouseClickPos, pos.clone().add(child.pos)); } } } ControlContainer.prototype.mouseMove = function(mouseMovePos, pos) { var temp = this.childrenContainingPosPrev; this.childrenContainingPosPrev = this.childrenContainingPos; this.childrenContainingPos = temp; var childrenContainingPos = this.childrenContainingPos; childrenContainingPos.length = 0; this.childrenAtPosAddToList ( pos, mouseMovePos, childrenContainingPos, true ); for (var i = 0; i < childrenContainingPos.length; i++) { var child = childrenContainingPos[i]; if (child.mouseMove != null) { child.mouseMove(mouseMovePos, pos.clone().add(child.pos)); } if (this.childrenContainingPosPrev.indexOf(child) == -1) { if (child.mouseEnter != null) { child.mouseEnter(); } } } for (var i = 0; i < this.childrenContainingPosPrev.length; i++) { var child = this.childrenContainingPosPrev[i]; if (childrenContainingPos.indexOf(child) == -1) { if (child.mouseExit != null) { child.mouseExit(); } } } } } function ControlImage(name, pos, size, imageSrc) { this.name = name; this.pos = pos; this.size = size; this.imageSrc = imageSrc; } { ControlImage.prototype.draw = function(pos) { if (this.systemImage == null) { this.systemImage = document.createElement("img"); this.systemImage.src = this.imageSrc; } Globals.Instance.displayHelper.drawControlImage(this, pos); } } function ControlLabel(name, pos, size, isTextCentered, dataBindingForText) { this.name = name; this.pos = pos; this.size = size; this.isTextCentered = isTextCentered; this.dataBindingForText = dataBindingForText; if (this.dataBindingForText.constructor.name != "DataBinding") { this.dataBindingForText = new DataBinding(this.dataBindingForText); } } { ControlLabel.prototype.draw = function(pos) { Globals.Instance.displayHelper.drawControlLabel(this, pos); } ControlLabel.prototype.text = function() { return this.dataBindingForText.get(); } } function ControlScrollbar ( name, pos, size, dataBindingForMin, dataBindingForMax, dataBindingForValue ) { this.name = name; this.pos = pos; this.size = size; this.dataBindingForMin = dataBindingForMin; this.dataBindingForMax = dataBindingForMax; this.dataBindingForValue = dataBindingForValue } { ControlScrollbar.prototype.draw = function(pos) { Globals.Instance.displayHelper.drawControlScrollbar(this, pos); } ControlScrollbar.prototype.max = function() { return this.dataBindingForMax.get(); } ControlScrollbar.prototype.min = function() { return this.dataBindingForMin.get(); } ControlScrollbar.prototype.range = function() { return this.max() - this.min(); } ControlScrollbar.prototype.value = function() { return this.dataBindingForValue.get(); } } function ControlSelect ( name, pos, size, dataBindingForValueSelected, dataBindingForOptions, bindingExpressionForOptionValues, bindingExpressionForOptionText, dataBindingForIsEnabled, numberOfItemsVisible ) { this.name = name; this.pos = pos; this.size = size; this.dataBindingForValueSelected = dataBindingForValueSelected; this.dataBindingForOptions = dataBindingForOptions; this.bindingExpressionForOptionValues = bindingExpressionForOptionValues; this.bindingExpressionForOptionText = bindingExpressionForOptionText; this.dataBindingForIsEnabled = dataBindingForIsEnabled; this.numberOfItemsVisible = (numberOfItemsVisible == null ? 1 : numberOfItemsVisible); if (this.dataBindingForValueSelected == null) { this.dataBindingForValueSelected = new DataBinding(this, "_valueSelected"); } var options = this.options(); var numberOfOptions = (options == null ? 0 : options.length); this.indexOfFirstOptionVisible = 0; var scrollbarWidth = 12; this.scrollbar = new ControlScrollbar ( name, new Coords(this.size.x - scrollbarWidth, 0), // pos new Coords(scrollbarWidth, this.size.y), // size new DataBinding(0), // dataBindingForMin new DataBinding(numberOfOptions), // dataBindingForMax new DataBinding(0) // dataBindingForValue ); } { ControlSelect.prototype.draw = function(pos) { if (this.numberOfItemsVisible == 1) { Globals.Instance.displayHelper.drawControlSelect(this, pos); } else { Globals.Instance.displayHelper.drawControlList(this, pos); } } ControlSelect.prototype.isEnabled = Control.isEnabled; ControlSelect.prototype.optionSelected = function() { var returnValue = null; var options = this.options(); if (options != null) { var valueSelected = this.dataBindingForValueSelected.get(); for (var i = 0; i < options.length; i++) { var optionAsObject = options[i]; var optionValue = DataBinding.get ( optionAsObject, this.bindingExpressionForOptionValues ); if (optionValue == valueSelected) { returnValue = optionAsObject; break; } } } return returnValue; } ControlSelect.prototype.options = function() { return this.dataBindingForOptions.get(); } ControlSelect.prototype.mouseClick = function(clickPos, pos) { if (this.isEnabled() == false) { return; } if (this.numberOfItemsVisible == 1) { this.mouseClick1(clickPos, pos); } else { this.mouseClick2(clickPos, pos); } Globals.Instance.inputHelper.isMouseLeftPressed = false; } ControlSelect.prototype.mouseClick1 = function(clickPos, pos) { var options = this.options(); var valueSelected = this.dataBindingForValueSelected.get(); var optionSelected = this.optionSelected(); if (optionSelected == null) { optionSelected = options[0]; } else for (var i = 0; i < options.length; i++) { var optionAsObject = options[i]; var optionValue = DataBinding.get ( optionAsObject, this.bindingExpressionForOptionValues ); if (optionValue == valueSelected) { i++; if (i >= options.length) { i = 0; } optionSelected = options[i]; break; } } valueSelected = DataBinding.get ( optionSelected, this.bindingExpressionForOptionValues ); this.dataBindingForValueSelected.set(valueSelected); } ControlSelect.prototype.mouseClick2 = function(clickPos, pos) { var itemSpacing = 12; // hack var offsetOfOptionClicked = clickPos.y - pos.y; var indexOfOptionClicked = this.indexOfFirstOptionVisible + Math.floor ( offsetOfOptionClicked / itemSpacing ); var options = this.options(); if (indexOfOptionClicked < options.length) { var optionClicked = options[indexOfOptionClicked]; valueSelected = DataBinding.get ( optionClicked, this.bindingExpressionForOptionValues ); this.dataBindingForValueSelected.set ( valueSelected ); } } ControlSelect.prototype.valueSelected = function() { return this.dataBindingForValueSelected.get(); } } function ControlSelectOption(value, text) { this.value = value; this.text = text; } function ControlTextBox(name, pos, size, dataBindingForText) { this.name = name; this.pos = pos; this.size = size; this.dataBindingForText = dataBindingForText; if (this.dataBindingForText.constructor.name == "String") { // hack this.dataBindingForText = new DataBinding(this.dataBindingForText); } this.isHighlighted = false; } { ControlTextBox.prototype.draw = function(pos) { Globals.Instance.displayHelper.drawControlTextBox(this, pos); } ControlTextBox.prototype.keyPressed = function(keyCodePressed, isShiftKeyPressed) { var text = this.text(); if (keyCodePressed == 8) // backspace { text = text.substr(0, this.text.length - 1); } else { var charTyped = String.fromCharCode(keyCodePressed); if (isShiftKeyPressed == false) { charTyped = charTyped.toLowerCase(); } text += charTyped; } this.dataBindingForText.set(text); // hack Globals.Instance.inputHelper.keyCodePressed = null; } ControlTextBox.prototype.mouseClick = function(mouseClickPos) { this.isHighlighted = true; } ControlTextBox.prototype.text = function() { return this.dataBindingForText.get(); } } function Coords(x, y, z) { this.x = x; this.y = y; this.z = z; } { // constants Coords.NumberOfDimensions = 3; // instances function Coords_Instances() { this.Zeroes = new Coords(0, 0, 0); } Coords.Instances = new Coords_Instances(); // methods Coords.prototype.absolute = function(other) { this.x = Math.abs(this.x); this.y = Math.abs(this.y); this.z = Math.abs(this.z); return this; } Coords.prototype.add = function(other) { this.x += other.x; this.y += other.y; this.z += other.z; return this; } Coords.prototype.clone = function() { return new Coords(this.x, this.y, this.z); } Coords.prototype.crossProduct = function(other) { return this.overwriteWithDimensions ( this.y * other.z - this.z * other.y, this.z * other.x - this.x * other.z, this.x * other.y - this.y * other.x ); } Coords.prototype.dimension = function(dimensionIndex) { var returnValue; if (dimensionIndex == 0) { returnValue = this.x; } else if (dimensionIndex == 1) { returnValue = this.y; } else { returnValue = this.z; } return returnValue; } Coords.prototype.dimensions = function() { return [ this.x, this.y, this.z ]; } Coords.prototype.dimension_Set = function(dimensionIndex, value) { if (dimensionIndex == 0) { this.x = value; } else if (dimensionIndex == 1) { this.y = value; } else { this.z = value; } return this; } Coords.prototype.divide = function(other) { this.x /= other.x; this.y /= other.y; this.z /= other.z; return this; } Coords.prototype.divideScalar = function(scalar) { this.x /= scalar; this.y /= scalar; this.z /= scalar; return this; } Coords.prototype.dotProduct = function(other) { var returnValue = this.x * other.x + this.y * other.y + this.z * other.z; return returnValue; } Coords.prototype.equals = function(other) { var returnValue = ( this.x == other.x && this.y == other.y && this.z == other.z ); return returnValue; } Coords.prototype.floor = function(other) { this.x = Math.floor(this.x); this.y = Math.floor(this.y); this.z = Math.floor(this.z); return this; } Coords.prototype.isWithinRangeMax = function(max) { var returnValue = ( this.x >= 0 && this.x <= max.x && this.y >= 0 && this.y <= max.y && this.z >= 0 && this.z <= max.z ); return returnValue; } Coords.prototype.isWithinRangeMinMax = function(min, max) { var returnValue = ( this.x >= min.x && this.x <= max.x && this.y >= min.y && this.y <= max.y ); return returnValue; } Coords.prototype.magnitude = function() { return Math.sqrt ( this.x * this.x + this.y * this.y + this.z * this.z ); } Coords.prototype.multiply = function(other) { this.x *= other.x; this.y *= other.y; this.z *= other.z; return this; } Coords.prototype.multiplyScalar = function(scalar) { this.x *= scalar; this.y *= scalar; this.z *= scalar; return this; } Coords.prototype.normalize = function() { return this.divideScalar(this.magnitude()); } Coords.prototype.overwriteWith = function(other) { this.x = other.x; this.y = other.y; this.z = other.z; return this; } Coords.prototype.overwriteWithDimensions = function(x, y, z) { this.x = x; this.y = y; this.z = z; return this; } Coords.prototype.perpendicular2D = function() { var temp = this.x; this.x = this.y; this.y = 0 - temp; return this; } Coords.prototype.perpendicular3D = function() { var dimensionsAbsolute = this.clone().absolute().dimensions(); var dimensionAbsoluteLeast = dimensionsAbsolute[0]; var indexOfDimensionAbsoluteLeast = 0; for (var d = 1; d < dimensionsAbsolute.length; d++) { var dimensionAbsolute = dimensionsAbsolute[d]; if (dimensionAbsolute < dimensionAbsoluteLeast) { dimensionAbsoluteLeast = dimensionAbsolute; indexOfDimensionAbsoluteLeast = d; } } var basisVector = new Coords(0, 0, 0).dimension_Set ( indexOfDimensionAbsoluteLeast, 1 ) this.crossProduct ( basisVector ); return this; } Coords.prototype.randomize = function() { this.overwriteWithDimensions ( Math.random(), Math.random(), Math.random() ); return this; } Coords.prototype.subtract = function(other) { this.x -= other.x; this.y -= other.y; this.z -= other.z; return this; } Coords.prototype.trimToRangeMinMax = function(min, max) { for (var d = 0; d < Coords.NumberOfDimensions; d++) { var thisDimension = this.dimension(d); var minDimension = min.dimension(d); var maxDimension = max.dimension(d); if (thisDimension < minDimension) { thisDimension = minDimension; } else if (thisDimension > maxDimension) { thisDimension = maxDimension; } this.dimension_Set(d, thisDimension); } } } function Cursor(bodyParent) { this.bodyParent = bodyParent; this.hasXYPositionBeenSpecified = false; this.hasZPositionBeenSpecified = false; this.defn = Cursor.BodyDefn; this.loc = this.bodyParent.loc.clone(); this.constraints = [ new Constraint_Cursor() ]; } { Cursor.BodyDefn = new BodyDefn ( "Cursor", new Coords(10, 10), // size new VisualGroup ([ new VisualRectangle(Color.Instances.Brown, new Coords(10, 10)), ]) ); // controls Cursor.prototype.controlBuild_Selection = function() { return this.bodyParent.controlBuild_Selection(); } } function DataBinding(context, bindingExpression) { this.context = context; this.bindingExpression = bindingExpression; } { DataBinding.get = function(context, bindingExpression) { var expressionValue = context; if (bindingExpression != null) { var bindingExpressionHierarchy = bindingExpression.split("."); for (var i = 0; i < bindingExpressionHierarchy.length; i++) { var bindingExpressionLevel = bindingExpressionHierarchy[i]; var expressionValue = context[bindingExpressionLevel]; if (expressionValue == null) { break; } else { if (expressionValue.constructor.name == "Function") { expressionValue = expressionValue.call(context); } context = expressionValue; } } } return expressionValue; } DataBinding.set = function(context, bindingExpression, valueToSet) { var contextCurrent = context; if (bindingExpression != null) { var bindingExpressionHierarchy = bindingExpression.split("."); var hierarchySizeMinusOne = bindingExpressionHierarchy.length - 1; for (var i = 0; i < hierarchySizeMinusOne; i++) { var bindingExpressionLevel = bindingExpressionHierarchy[i]; contextCurrent = contextCurrent[bindingExpressionLevel]; if (contextCurrent == null) { break; } else { if (contextCurrent.constructor.name == "Function") { contextCurrent = contextCurrent.call(context); } } } var bindingExpressionLevelLast = bindingExpressionHierarchy[hierarchySizeMinusOne]; contextCurrent[bindingExpressionLevelLast] = valueToSet; } } // instance methods DataBinding.prototype.get = function() { return DataBinding.get(this.context, this.bindingExpression); } DataBinding.prototype.set = function(valueToSet) { if (this.bindingExpression == null) { this.context = valueToSet; } else { DataBinding.set(this.context, this.bindingExpression, valueToSet); } } } function DateTime(year, month, day, hours, minutes, seconds) { this.year = year; this.month = month; this.day = day; this.hours = hours; this.minutes = minutes; this.seconds = seconds; } { // static methods DateTime.fromSystemDate = function(systemDate) { var returnValue = new DateTime ( systemDate.getFullYear(), systemDate.getMonth() + 1, systemDate.getDate(), systemDate.getHours(), systemDate.getMinutes(), systemDate.getSeconds() ); return returnValue; } DateTime.now = function() { return DateTime.fromSystemDate(new Date()); } // instance methods DateTime.prototype.toStringTimestamp = function() { var returnValue = "" + this.year + "/" + this.month.toString().padLeft(2, "0") + "/" + this.day.toString().padLeft(2, "0") + "-" + this.hours.toString().padLeft(2, "0") + ":" + this.minutes.toString().padLeft(2, "0") + ":" + this.seconds.toString().padLeft(2, "0") return returnValue; } } function Device(defn) { this.defn = defn; } { Device.prototype.use = function(actor, target) { this.defn.use(actor, this, target); } } function DeviceDefn(name, range, use) { this.name = name; this.range = range; this.use = use; } function DiplomaticAction(name, effect) { this.name = name; this.effect = effect; } { function DiplomaticAction_Instances() { this.War = new DiplomaticAction ( "Declare War on", function(universe, factionActing, factionReceiving) { var message; var stateExisting = factionActing.relationships[factionReceiving.name].state; if (stateExisting == Relationship.States.War) { message = "The " + factionActing.name + " are already at war with the " + factionReceiving.name + "."; } else { var relationship = factionActing.relationships[factionReceiving.name]; relationship.state = Relationship.States.War; relationship = factionReceiving.relationships[factionActing.name]; relationship.state = Relationship.States.War; message = "The " + factionActing.name + " have declared war on the " + factionReceiving.name + "."; } alert(message); } ), this.Peace = new DiplomaticAction ( "Offer Peace to", function(universe, factionActing, factionReceiving) { var message; var stateExisting = factionActing.relationships[factionReceiving.name].state; if (stateExisting == Relationship.States.Alliance) { message = "The " + factionReceiving.name + " are already allied with the " + factionActing.name + "."; } else if (stateExisting == Relationship.States.Peace) { message = "The " + factionReceiving.name + " are already at peace with the " + factionActing.name + "."; } else // if (stateExisting == Relationship.States.War) { var strengthOfEnemies = DiplomaticRelationship.strengthOfFactions ( factionReceiving.enemies() ); var strengthOfSelfAndAllies = DiplomaticRelationship.strengthOfFactions ( factionReceiving.selfAndAllies() ); var strengthOfAlliesMinusEnemies = strengthOfSelfAndAllies - strengthOfEnemies; if (strengthOfAlliesMinusEnemies <= 0) { var relationship = factionActing.relationships[factionReceiving.name]; relationship.state = Relationship.States.Peace; relationship = factionReceiving.relationships[factionActing.name]; relationship.state = Relationship.States.Peace; message = "The " + factionReceiving.name + " have accepted a peace offer from the " + factionActing.name + "."; } else { message = "The " + factionReceiving.name + " have rejected a peace offer from the " + factionActing.name + "."; } } alert(message); } ); this.Alliance = new DiplomaticAction ( "Propose Alliance with", // effect function(universe, factionActing, factionReceiving) { var message; var stateExisting = factionActing.relationships[factionReceiving.name].state; if (stateExisting == Relationship.States.Alliance) { message = "The " + factionReceiving.name + " are already allied with the " + factionActing.name + "."; } else if (stateExisting == Relationship.States.War) { message = "The " + factionReceiving.name + " are currently at war with the " + factionActing.name + "."; } else { var factions = [ factionActing, factionReceiving ]; var doAlliesAndEnemiesOfFactionsClash = false; var enemiesOfEitherFaction = []; for (var f = 0; f < factions.length; f++) { var factionThis = factions[f]; var factionOther = factions[1 - f]; var enemiesOfFactionThis = factionThis.enemies(); enemiesOfEitherFaction = enemiesOfEitherFaction.concat ( enemiesOfFactionThis ); var alliesOfFactionOther = factionOther.allies(); var intersection = enemiesOfFactionThis.intersectionWith ( alliesOfFactionOther ); if (intersection.length > 0) { doAlliesAndEnemiesOfFactionsClash = true; message = "An alliance between the " + factionThis.name + " and the " + factionOther.name + " is impossible because the " + factionThis.name + " are at war with some allies of the " + factionOther.name + " (" + intersection.join(", ") + ")."; } } if (doAlliesAndEnemiesOfFactionsClash == false) { var factionsDeclaringWarOnActing = []; var factionsDeclaringWarOnReceiving = []; for (var i = 0; i < enemiesOfEitherFaction.length; i++) { var enemy = enemiesOfEitherFaction[i]; if (factionActing.enemies().indexOf(enemy) == -1) { factionsDeclaringWarOnActing.push(enemy); } if (factionReceiving.enemies().indexOf() == -1) { factionsDeclaringWarOnReceiving.push(enemy); } } var strengthOfNewEnemies = DiplomaticRelationship.strengthOfFactions ( factionsDeclaringWarOnReceiving ); if (strengthOfNewEnemies >= factionActing.strength) { message = "The " + factionReceiving.name + " have declined to join an alliance with the " + factionActing.name + "."; } else { var relationship = factionActing.relationships[factionReceiving.name] relationship.state = Relationship.States.Alliance; relationship = factionReceiving.relationships[factionActing.name]; relationship.state = Relationship.States.Alliance; message = "The " + factionReceiving.name + " have joined an alliance with the " + factionActing.name + "."; if (factionsDeclaringWarOnActing.length > 0) { message += " Some enemies of the " + factionReceiving.name + " (" + factionsDeclaringWarOnActing.join(", ") + ") have declared war on the " + factionActing.name + "." } if (factionsDeclaringWarOnReceiving.length > 0) { message += " Some enemies of the " + factionActing.name + " (" + factionsDeclaringWarOnReceiving.join(", ") + ") have declared war on the " + factionReceiving.name + "." } } // end if strengthOfNewEnemies >= factionActing.strength else } // end if doAlliesAndEnemiesOfFactionsClash } // end if stateExisting alert(message); } // end effect ); // end new DiplomaticAction this._All = [ this.Peace, this.Alliance, this.War, ]; } DiplomaticAction.Instances = new DiplomaticAction_Instances(); } function DiplomaticRelationship(factionNameOther, state) { this.factionNameOther = factionNameOther; this.state = state; } { // static methods DiplomaticRelationship.initializeForFactions = function(factions) { for (var f = 0; f < factions.length; f++) { var factionThis = factions[f]; for (var g = 0; g < f; g++) { var factionOther = factions[g]; factionThis.relationships.push ( new DiplomaticRelationship ( factionOther.name, DiplomaticRelationship.States.Peace ) ); factionOther.relationships.push ( new DiplomaticRelationship ( factionThis.name, DiplomaticRelationship.States.Peace ) ); } } for (var f = 0; f < factions.length; f++) { var faction = factions[f]; faction.relationships.addLookups("factionNameOther"); } } DiplomaticRelationship.setStateForFactions = function(factions, state) { for (var i = 0; i < factions.length; i++) { var faction = factions[i]; var factionOther = factions[1 - i]; faction.relationships[factionOther.name].state = state; } return state; } DiplomaticRelationship.strengthOfFactions = function(factions) { var returnValue = 0; for (var i = 0; i < factions.length; i++) { var faction = factions[i]; returnValue += faction.strength(); } return returnValue; } // instances function DiplomaticRelationship_States() { this.Alliance = "Alliance"; this.Peace = "Peace"; this.War = "War"; } DiplomaticRelationship.States = new DiplomaticRelationship_States(); // instance methods DiplomaticRelationship.prototype.factionOther = function() { return Globals.Instance.universe.factions[this.factionNameOther]; } DiplomaticRelationship.prototype.toString = function() { var returnValue = this.factionNameOther + ":" + this.state; return returnValue; } } function DiplomaticSession(diplomaticActions, factionActing, factions) { this.diplomaticActions = diplomaticActions; this.diplomaticActions.addLookups("name"); this.factionActing = factionActing; this.factions = factions; this.factions.addLookups("name"); this.factionSelected = null; } { // static methods DiplomaticSession.buildExample = function(factionActing, factions) { var diplomaticActions = DiplomaticAction.Instances._All; var session = new DiplomaticSession ( diplomaticActions, factionActing, factions ); return session; } // instance methods DiplomaticSession.prototype.isFactionSelected = function() { return (this.factionSelected != null); } DiplomaticSession.prototype.talkSessionInitialize = function() { var universe = Globals.Instance.universe; var talkSession = TalkSession.buildExample ( this.factionActing, this.factionSelected ); var venueNext = new VenueTalkSession ( universe.venueCurrent, talkSession ); venueNext = new VenueFader(venueNext); universe.venueNext = venueNext; } // controls DiplomaticSession.prototype.controlBuild = function() { var containerSize = Globals.Instance.displayHelper.viewSize.clone(); var margin = 10; var controlHeight = 20; var listWidth = 100; var returnValue = new ControlContainer ( "containerProfileSelect", ControlBuilder.ColorsForeAndBackDefault, new Coords(0, 0), // pos containerSize, // children [ new ControlButton ( "buttonDone", new Coords ( margin, margin ), // pos new Coords ( controlHeight, controlHeight ), // size "<", null, // dataBindingForIsEnabled // click function () { var universe = Globals.Instance.universe; var venueNext = universe.venueCurrent.venueParent; venueNext = new VenueFader(venueNext); universe.venueNext = venueNext; } ), new ControlLabel ( "labelFactions", new Coords(margin, margin * 2 + controlHeight), // pos new Coords(100, controlHeight), // size false, // isTextCentered new DataBinding("Factions:") ), new ControlSelect ( "listFactions", new Coords(margin, margin * 2 + controlHeight * 2), // pos new Coords(listWidth, controlHeight * 4), // size // dataBindingForValueSelected new DataBinding(this, "factionSelected"), new DataBinding(this.factions), // options null, // bindingExpressionForOptionValues "name", // bindingExpressionForOptionText, new DataBinding(true), // isEnabled 6 // numberOfItemsVisible ), new ControlButton ( "buttonTalk", new Coords(margin, margin * 3 + controlHeight * 6), // pos new Coords(listWidth, controlHeight), // size "Talk", // dataBindingForIsEnabled new DataBinding(this, "isFactionSelected"), // click this.talkSessionInitialize.bind(this) ), Faction.controlBuild_Intelligence ( this, new Coords(margin * 2 + listWidth, 0), // pos new Coords ( containerSize.x - listWidth - margin * 2, containerSize.y ) ), ] ); return returnValue; } } function DisplayHelper(viewSize, fontHeightInPixels) { this.viewSize = viewSize; this.fontHeightInPixels = fontHeightInPixels; } { DisplayHelper.prototype.clear = function(colorBorder, colorBack) { this.drawRectangle ( new Coords(0, 0), this.viewSize, (colorBorder == null ? "Gray" : colorBorder), (colorBack == null ? "White" : colorBack) ); } DisplayHelper.prototype.drawControlButton = function(control, pos) { var size = control.size; var colorsForeAndBack; if (control.isEnabled() == false) { colorsForeAndBack = ["LightGray", "White"]; } else if (control.isHighlighted == true) { colorsForeAndBack = ["White", "Gray"]; } else { colorsForeAndBack = ["Gray", "White"]; } this.drawRectangle ( pos, size, colorsForeAndBack[0], colorsForeAndBack[1] ) var text = control.text; var textWidth = this.graphics.measureText(text).width; var textMarginLeft = (size.x - textWidth) / 2; var textHeight = this.fontHeightInPixels; var textMarginTop = (size.y - textHeight) / 2; this.graphics.fillStyle = colorsForeAndBack[0]; this.graphics.fillText ( text, pos.x + textMarginLeft, pos.y + textMarginTop + textHeight ); } DisplayHelper.prototype.drawControlContainer = function(container, pos) { var size = container.size; this.drawRectangle ( pos, size, container.colorsForeAndBack[0], container.colorsForeAndBack[1] ) var children = container.children; for (var i = 0; i < children.length; i++) { var child = children[i]; child.draw ( pos.clone().add(child.pos) ); } } DisplayHelper.prototype.drawControlImage = function(controlImage, pos) { var size = controlImage.size; this.drawRectangle ( pos, size, "Gray", "White" ) this.graphics.drawImage ( controlImage.systemImage, pos.x, pos.y, this.viewSize.x, this.viewSize.y ); } DisplayHelper.prototype.drawControlLabel = function(control, pos) { var size = control.size; var text = control.text(); var textHeight = this.fontHeightInPixels; var textMargins; if (control.isTextCentered == true) { var textWidth = this.graphics.measureText(text).width; textMargins = new Coords ( (size.x - textWidth) / 2, (size.y - textHeight) / 2 ); } else { textMargins = new Coords ( 2, (size.y - textHeight) / 2 ); } this.graphics.fillStyle = "Gray"; this.graphics.fillText ( text, pos.x + textMargins.x , pos.y + textMargins.y + textHeight ); } DisplayHelper.prototype.drawControlList = function(list, pos) { var size = list.size; this.drawRectangle ( pos, size, "Gray", "White" ); this.graphics.fillStyle = "Gray"; var itemSpacing = 12; // hack var itemSizeY = itemSpacing; var textMarginLeft = 2; var itemPosY = pos.y; var items = list.options(); var numberOfItems = (items == null ? 0 : items.length); var numberOfItemsVisible = Math.floor(size.y / itemSizeY); var indexStart = list.indexOfFirstOptionVisible; var indexEnd = indexStart + numberOfItemsVisible - 1; if (indexEnd >= numberOfItems) { indexEnd = numberOfItems - 1; } var optionSelected = list.optionSelected(); var scrollbar = list.scrollbar; var scrollbarSize = scrollbar.size; var textAreaSize = new Coords(size.x - size.y, size.y); for (var i = indexStart; i <= indexEnd; i++) { var item = items[i]; if (item == optionSelected) { this.graphics.fillStyle = "Gray"; this.graphics.fillRect ( pos.x + textMarginLeft, itemPosY, size.x - textMarginLeft * 2, itemSizeY ); this.graphics.fillStyle = "White"; } else { this.graphics.fillStyle = "Gray"; } var text = DataBinding.get ( item, list.bindingExpressionForOptionText ); itemPosY += itemSizeY; this.graphics.fillText ( text, pos.x + textMarginLeft, itemPosY ); } this.drawControlScrollbar ( scrollbar, scrollbar.pos.clone().add(pos) ); } DisplayHelper.prototype.drawControlScrollbar = function(control, pos) { var size = control.size; this.drawRectangle ( pos, control.size, "White", "Gray" ); var buttonSize = new Coords(size.x, size.x); var buttonPositions = [ pos, new Coords ( pos.x, pos.y + size.y - buttonSize.y ) ]; var verticesForButtonArrow = [ new Coords(1/3, 2/3), new Coords(2/3, 2/3), new Coords(1/2, 1/3), ]; var vertexPos = new Coords(0, 0, 0); var ones = new Coords(1, 1, 1); for (var b = 0; b < buttonPositions.length; b++) { var buttonPos = buttonPositions[b]; this.drawRectangle ( buttonPos, buttonSize, "Gray", "White" ); this.graphics.beginPath(); for (var v = 0; v < verticesForButtonArrow.length; v++) { vertexPos.overwriteWith(verticesForButtonArrow[v]); if (b == 1) { vertexPos.multiplyScalar(-1).add ( ones ); } vertexPos.multiply ( buttonSize ).add ( buttonPos ); if (v == 0) { this.graphics.moveTo ( vertexPos.x, vertexPos.y ); } else { this.graphics.lineTo ( vertexPos.x, vertexPos.y ); } } this.graphics.closePath(); this.graphics.stroke(); } var value = control.value(); var min = control.min(); var range = control.range(); var sliderSize = new Coords(size.x, size.x); var slideRangeInPixels = size.y - buttonSize.y * 2 - sliderSize.y; var sliderPos = new Coords ( 0, buttonSize.y + ((value - min) / range) * slideRangeInPixels ).add ( pos ); this.drawRectangle ( sliderPos, sliderSize, "Gray", "White" ); } DisplayHelper.prototype.drawControlSelect = function(control, pos) { var size = control.size; var colorsForeAndBack = ["Gray", "White"]; this.drawRectangle ( pos, size, colorsForeAndBack[0], colorsForeAndBack[1] ) var optionSelected = control.optionSelected(); var text; if (optionSelected == null) { text = "[none]"; } else { text = DataBinding.get(optionSelected, control.bindingExpressionForOptionText); } var textWidth = this.graphics.measureText(text).width; var textMarginLeft = (control.size.x - textWidth) / 2; var textHeight = this.fontHeightInPixels; var textMarginTop = (control.size.y - textHeight) / 2; this.graphics.fillStyle = colorsForeAndBack[0]; this.graphics.fillText ( text, pos.x + textMarginLeft, pos.y + textMarginTop + textHeight ); } DisplayHelper.prototype.drawControlTextBox = function(control, pos) { var size = control.size; var text = control.text(); var colorsForeAndBack; if (control.isHighlighted == true) { colorsForeAndBack = ["White", "Gray"]; text += "|"; } else { colorsForeAndBack = ["Gray", "White"]; } this.drawRectangle ( pos, size, colorsForeAndBack[0], colorsForeAndBack[1] ) var textWidth = this.graphics.measureText(text).width; var textMarginLeft = (size.x - textWidth) / 2; var textHeight = this.fontHeightInPixels; var textMarginTop = (size.y - textHeight) / 2; this.graphics.fillStyle = colorsForeAndBack[0]; this.graphics.fillText ( text, pos.x + textMarginLeft , pos.y + textMarginTop + textHeight ); } DisplayHelper.prototype.drawNetworkForCamera = function(network, camera) { var drawPos = this.drawPos; var drawPosFrom = new Coords(0, 0, 0); var drawPosTo = new Coords(0, 0, 0); var cameraPos = camera.loc.pos; var networkNodes = network.nodes; var numberOfNetworkNodes = networkNodes.length; var networkLinks = network.links; var numberOfNetworkLinks = networkLinks.length; this.graphics.fillStyle = "rgba(128, 128, 128, .1)"; var nodeRadiusActual = NetworkNode.RadiusActual; for (var i = 0; i < numberOfNetworkLinks; i++) { var link = networkLinks[i]; this.drawNetworkForCamera_Link ( network, camera, link, nodeRadiusActual, drawPos, drawPosFrom, drawPosTo ); } for (var i = 0; i < numberOfNetworkNodes; i++) { var node = networkNodes[i]; this.drawNetworkForCamera_Node ( node, nodeRadiusActual, camera, drawPos ); } } DisplayHelper.prototype.drawNetworkForCamera_Link = function ( network, camera, link, nodeRadiusActual, drawPos, drawPosFrom, drawPosTo ) { var nodesLinked = link.nodesLinked(network); var nodeFromPos = nodesLinked[0].loc.pos; var nodeToPos = nodesLinked[1].loc.pos; camera.convertWorldCoordsToViewCoords ( drawPosFrom.overwriteWith(nodeFromPos) ); camera.convertWorldCoordsToViewCoords ( drawPosTo.overwriteWith(nodeToPos) ); var directionFromNode0To1InView = drawPosTo.clone().subtract ( drawPosFrom ).normalize(); var perpendicular = directionFromNode0To1InView.clone().perpendicular2D(); var perspectiveFactorFrom = camera.focalLength / drawPosFrom.z; var perspectiveFactorTo = camera.focalLength / drawPosTo.z; var radiusApparentFrom = nodeRadiusActual * perspectiveFactorFrom; var radiusApparentTo = nodeRadiusActual * perspectiveFactorTo; this.graphics.beginPath(); this.graphics.moveTo(drawPosFrom.x, drawPosFrom.y); this.graphics.lineTo(drawPosTo.x, drawPosTo.y); this.graphics.lineTo ( drawPosTo.x + perpendicular.x * radiusApparentTo, drawPosTo.y + perpendicular.y * radiusApparentTo ); this.graphics.lineTo ( drawPosFrom.x + perpendicular.x * radiusApparentFrom, drawPosFrom.y + perpendicular.y * radiusApparentFrom ); this.graphics.fill(); for (var i = 0; i < link.ships.length; i++) { var ship = link.ships[i]; this.drawNetworkForCamera_Link_Ship ( camera, link, ship, drawPos, nodeFromPos, nodeToPos ); } } DisplayHelper.prototype.drawNetworkForCamera_Link_Ship = function ( camera, link, ship, drawPos, nodeFromPos, nodeToPos ) { var forward = link.direction(); var linkLength = link.length(); var fractionOfLinkTraversed = ship.loc.pos.x / linkLength; if (ship.vel.x < 0) { fractionOfLinkTraversed = 1 - fractionOfLinkTraversed; forward.multiplyScalar(-1); } drawPos.overwriteWith ( nodeFromPos ).multiplyScalar ( 1 - fractionOfLinkTraversed ).add ( nodeToPos.clone().multiplyScalar ( fractionOfLinkTraversed ) ); var right = forward.clone().perpendicular3D(); var down = right.clone().crossProduct(forward); var scaleFactorLength = 8; var scaleFactorBase = scaleFactorLength / 3; forward.multiplyScalar(scaleFactorLength); right.multiplyScalar(scaleFactorBase); down.multiplyScalar(scaleFactorBase); var vertices = [ drawPos.clone().add(forward), drawPos.clone().add(right).add(down), drawPos.clone().add(right).subtract(down), drawPos.clone().subtract(right).subtract(down), drawPos.clone().subtract(right).add(down), ]; var vertexIndicesForFaces = [ [ 0, 1, 2 ], [ 0, 2, 3 ], [ 0, 3, 4 ], [ 0, 4, 1 ], ]; this.graphics.strokeStyle = ship.faction().color.systemColor; for (var f = 0; f < vertexIndicesForFaces.length; f++) { var vertexIndicesForFace = vertexIndicesForFaces[f]; this.graphics.beginPath(); for (var vi = 0; vi < vertexIndicesForFace.length; vi++) { var vertexIndex = vertexIndicesForFace[vi]; drawPos.overwriteWith(vertices[vertexIndex]); camera.convertWorldCoordsToViewCoords(drawPos); var moveToOrLineTo = (vi == 0 ? this.graphics.moveTo : this.graphics.lineTo); moveToOrLineTo.call(this.graphics, drawPos.x, drawPos.y); } this.graphics.closePath(); this.graphics.stroke(); } } DisplayHelper.prototype.drawNetworkForCamera_Node = function ( node, nodeRadiusActual, camera, drawPos ) { var nodePos = node.loc.pos; drawPos.overwriteWith(nodePos); camera.convertWorldCoordsToViewCoords(drawPos); var perspectiveFactor = camera.focalLength / drawPos.z; var radiusApparent = nodeRadiusActual * perspectiveFactor; var alpha = Math.pow(perspectiveFactor, 4); // hack //var nodeColor = node.defn.color.systemColor; var nodeColor = "rgba(128, 128, 128, " + alpha + ")" this.graphics.strokeStyle = nodeColor; this.graphics.fillStyle = nodeColor; this.graphics.beginPath(); this.graphics.arc ( drawPos.x, drawPos.y, radiusApparent, 0, 2 * Math.PI, // start and stop angles false // counterClockwise ); this.graphics.stroke(); this.graphics.fillText ( node.starsystem.name, (drawPos.x + radiusApparent), drawPos.y ); } DisplayHelper.prototype.drawLayout = function(layout) { this.clear(); this.drawMap(layout.map); } DisplayHelper.prototype.drawMap = function(map) { var pos = map.pos; var mapSizeInCells = map.sizeInCells; var cellPos = new Coords(0, 0); var drawPos = this.drawPos; var cellSizeInPixels = map.cellSizeInPixels; var cellSizeInPixelsHalf = cellSizeInPixels.clone().divideScalar(2); for (var y = 0; y < mapSizeInCells.y; y++) { cellPos.y = y; for (var x = 0; x < mapSizeInCells.x; x++) { cellPos.x = x; drawPos.overwriteWith ( cellPos ).multiply ( cellSizeInPixels ).add ( pos ); var cell = map.cellAtPos(cellPos); var cellBody = cell.body; var colorFill = ( cellBody == null ? "Transparent" : cellBody.defn.color ); var colorBorder = cell.terrain.color; this.graphics.fillStyle = colorFill; this.graphics.fillRect ( drawPos.x, drawPos.y, cellSizeInPixels.x, cellSizeInPixels.y ); this.graphics.strokeStyle = colorBorder; this.graphics.strokeRect ( drawPos.x, drawPos.y, cellSizeInPixels.x, cellSizeInPixels.y ); } } var cursor = map.cursor; var cursorPos = cursor.pos; var cursorIsWithinMap = cursorPos.isWithinRangeMax ( map.sizeInCellsMinusOnes ); if (cursorIsWithinMap == true) { drawPos.overwriteWith ( cursorPos ).multiply ( cellSizeInPixels ).add ( pos ); this.graphics.strokeStyle = "Cyan"; if (cursor.bodyDefn == null) { this.graphics.beginPath(); this.graphics.moveTo(drawPos.x, drawPos.y); this.graphics.lineTo ( drawPos.x + cellSizeInPixels.x, drawPos.y + cellSizeInPixels.y ); this.graphics.moveTo ( drawPos.x + cellSizeInPixels.x, drawPos.y ); this.graphics.lineTo ( drawPos.x, drawPos.y + cellSizeInPixels.y ); this.graphics.stroke(); } else { this.graphics.fillStyle = cursor.bodyDefn.color; this.graphics.fillRect ( drawPos.x, drawPos.y, cellSizeInPixels.x, cellSizeInPixels.y ); } this.graphics.strokeRect ( drawPos.x, drawPos.y, cellSizeInPixels.x, cellSizeInPixels.y ); } } DisplayHelper.prototype.drawRectangle = function ( pos, size, colorBorder, colorBack ) { this.graphics.fillStyle = colorBack; this.graphics.fillRect ( pos.x, pos.y, size.x, size.y ); if (colorBorder != null) { this.graphics.strokeStyle = colorBorder; this.graphics.strokeRect ( pos.x, pos.y, size.x, size.y ); } } DisplayHelper.prototype.drawStarsystemForCamera = function(starsystem, camera) { var cameraViewSize = camera.viewSize; var cameraPos = camera.loc.pos; var drawPos = this.drawPos; var drawPosFrom = new Coords(0, 0, 0); var drawPosTo = new Coords(0, 0, 0); var gridCellSizeInPixels = new Coords(10, 10, 0); var gridSizeInCells = new Coords(40, 40, 0); var gridSizeInPixels = gridSizeInCells.clone().multiply ( gridCellSizeInPixels ); var gridSizeInCellsHalf = gridSizeInCells.clone().divideScalar(2); var gridSizeInPixelsHalf = gridSizeInPixels.clone().divideScalar(2); var graphics = this.graphics; graphics.strokeStyle = Color.Instances.CyanHalfTranslucent.systemColor; for (var d = 0; d < 2; d++) { var multiplier = new Coords(0, 0, 0); multiplier.dimension_Set(d, gridCellSizeInPixels.dimension(d)); for (var i = 0 - gridSizeInCellsHalf.x; i <= gridSizeInCellsHalf.x; i++) { drawPosFrom.overwriteWith ( gridSizeInPixelsHalf ).multiplyScalar(-1); drawPosTo.overwriteWith(gridSizeInPixelsHalf); drawPosFrom.dimension_Set(d, 0); drawPosTo.dimension_Set(d, 0); drawPosFrom.add(multiplier.clone().multiplyScalar(i)); drawPosTo.add(multiplier.clone().multiplyScalar(i)); camera.convertWorldCoordsToViewCoords(drawPosFrom); camera.convertWorldCoordsToViewCoords(drawPosTo); graphics.beginPath(); graphics.moveTo(drawPosFrom.x, drawPosFrom.y); graphics.lineTo(drawPosTo.x, drawPosTo.y); graphics.stroke(); } } var bodiesByType = [ [ starsystem.star ], starsystem.linkPortals, starsystem.planets, starsystem.ships, ]; for (var t = 0; t < bodiesByType.length; t++) { var bodies = bodiesByType[t]; for (var i = 0; i < bodies.length; i++) { var body = bodies[i]; this.drawStarsystemForCamera_Body ( camera, body ); } } } DisplayHelper.prototype.drawStarsystemForCamera_Body = function(camera, body) { var graphics = this.graphics; var drawPos = this.drawPos; var bodyDefn = body.defn; var bodySize = bodyDefn.size; var bodySizeHalf = bodyDefn.sizeHalf; var bodyPos = body.loc.pos; drawPos.overwriteWith(bodyPos); camera.convertWorldCoordsToViewCoords(drawPos); bodyDefn.visual.draw(drawPos); if (bodyPos.z < 0) { graphics.strokeStyle = Color.Instances.Green.systemColor; } else { graphics.strokeStyle = Color.Instances.Red.systemColor; } graphics.beginPath(); graphics.moveTo(drawPos.x, drawPos.y); drawPos.overwriteWith(bodyPos); drawPos.z = 0; camera.convertWorldCoordsToViewCoords(drawPos); graphics.lineTo(drawPos.x, drawPos.y); graphics.stroke(); } DisplayHelper.prototype.hide = function() { Globals.Instance.divMain.removeChild(this.canvasLive); } DisplayHelper.prototype.initialize = function() { this.canvasBuffer = document.createElement("canvas"); this.canvasBuffer.width = this.viewSize.x; this.canvasBuffer.height = this.viewSize.y; this.graphics = this.canvasBuffer.getContext("2d"); this.graphics.font = "" + this.fontHeightInPixels + "px sans-serif"; // hack - double-buffering test this.canvasLive = this.canvasBuffer; this.graphicsLive = this.graphics; this.drawPos = new Coords(0, 0, 0); Globals.Instance.divMain.appendChild(this.canvasLive); } DisplayHelper.prototype.refresh = function() { this.graphicsLive.drawImage(this.canvasBuffer, 0, 0); } DisplayHelper.prototype.show = function() { Globals.Instance.divMain.appendChild(this.canvasLive); } } function Faction(name, color, relationships, technology, planets, ships, knowledge) { this.name = name; this.color = color; this.relationships = relationships; this.technology = technology; this.planets = planets; this.ships = ships; this.knowledge = knowledge; this.notifications = [ new Notification("Default", "This is a test."), ]; } { // static methods // controls Faction.controlBuild_Intelligence = function(diplomaticSession, pos, containerSize) { var margin = 10; var controlSpacing = 20; var listWidth = 100; var columnWidth = 60; var returnValue = new ControlContainer ( "containerFactionIntelligence", ControlBuilder.ColorsForeAndBackDefault, pos, containerSize, // children [ new ControlLabel ( "labelFaction", new Coords(margin, margin), // pos new Coords(columnWidth, controlSpacing), // size false, // isTextCentered new DataBinding("Faction:") ), new ControlLabel ( "textFaction", new Coords(margin + columnWidth, margin), // pos new Coords(columnWidth, controlSpacing), // size, false, // isTextCentered new DataBinding(diplomaticSession, "factionSelected.name") ), new ControlLabel ( "labelRelationship", new Coords(margin, margin + controlSpacing), // pos new Coords(columnWidth, controlSpacing), // size false, // isTextCentered new DataBinding("Relationship:") ), new ControlLabel ( "textRelationship", new Coords(margin + columnWidth, margin + controlSpacing), // pos new Coords(columnWidth, controlSpacing), // size, false, // isTextCentered new DataBinding("[relationship]") ), new ControlLabel ( "labelPlanets", new Coords(margin, margin + controlSpacing * 2), // pos new Coords(columnWidth, controlSpacing), // size false, // isTextCentered new DataBinding("Planets:") ), new ControlSelect ( "listPlanets", new Coords(margin, margin + controlSpacing * 3), // pos new Coords(listWidth, controlSpacing * 4), // size // dataBindingForValueSelected new DataBinding(diplomaticSession, "factionSelected.planetSelected"), new DataBinding(diplomaticSession, "factionSelected.planets"), // options null, // bindingExpressionForOptionValues "name", // bindingExpressionForOptionText, new DataBinding(true), // isEnabled 6 // numberOfItemsVisible ), new ControlLabel ( "labelShips", new Coords(margin, margin + controlSpacing * 7), // pos new Coords(columnWidth, controlSpacing), // size false, // isTextCentered new DataBinding("Ships:") ), new ControlSelect ( "listShips", new Coords(margin, margin + controlSpacing * 8), // pos new Coords(listWidth, controlSpacing * 4), // size // dataBindingForValueSelected new DataBinding(diplomaticSession, "factionSelected.shipSelected"), new DataBinding(diplomaticSession, "factionSelected.ships"), // options null, // bindingExpressionForOptionValues "name", // bindingExpressionForOptionText, new DataBinding(true), // isEnabled 6 // numberOfItemsVisible ), ] ); return returnValue; } // instance methods Faction.prototype.researchInitialize = function() { var researchSession = new TechnologyResearchSession ( Globals.Instance.universe.technologyTree, this.technology ); var venueNext = new VenueTechnologyResearchSession(researchSession); venueNext = new VenueFader(venueNext); var universe = Globals.Instance.universe; universe.venueNext = venueNext; } Faction.prototype.toString = function() { return this.name; } // controls Faction.prototype.controlBuild = function ( containerMainSize, containerInnerSize, margin, controlHeight, buttonWidth ) { var returnValue = new ControlContainer ( "containerFaction", ControlBuilder.ColorsForeAndBackDefault, new Coords ( containerMainSize.x - margin - containerInnerSize.x, margin ), containerInnerSize, // children [ new ControlLabel ( "textBoxFaction", new Coords(margin, 0),// pos new Coords ( containerInnerSize.x - 40 - margin * 3, controlHeight ), // size false, // isTextCentered new DataBinding(this.name) ), new ControlButton ( "buttonTechnology", new Coords(margin, controlHeight), // pos new Coords(buttonWidth, controlHeight), // size "Tech", null, // dataBindingForIsEnabled this.researchInitialize.bind(this) ), new ControlButton ( "buttonRelations", new Coords ( margin * 2 + buttonWidth, controlHeight ), // pos new Coords(buttonWidth, controlHeight), // size "Others", null, // dataBindingForIsEnabled // click this.relationsInitialize.bind(this) ), new ControlButton ( "buttonNotifications", new Coords ( margin, controlHeight * 2 ), // pos new Coords(buttonWidth, controlHeight), // size "Notes", null, // dataBindingForIsEnabled // click this.notificationSessionInitialize.bind(this) ), ] ); return returnValue; } // diplomacy Faction.prototype.allianceProposalAcceptFrom = function(factionOther) { return true; } Faction.prototype.allies = function() { return this.factionsMatchingRelationshipState ( Relationship.States.Alliance ); } Faction.prototype.enemies = function() { return this.factionsMatchingRelationshipState ( Relationship.States.War ); } Faction.prototype.factionsMatchingRelationshipState = function(stateToMatch) { var returnValues = []; for (var i = 0; i < this.relationships.length; i++) { var relationship = this.relationships[i]; if (relationship.state == stateToMatch) { var factionOther = relationship.factionOther(); returnValues.push(factionOther); } } return returnValues; } Faction.prototype.notificationSessionInitialize = function() { var universe = Globals.Instance.universe; var notificationSession = new NotificationSession(this.notifications); var notificationSessionAsControl = notificationSession.controlBuild(); var venueNext = new VenueControls(notificationSessionAsControl); venueNext = new VenueFader(venueNext); universe.venueNext = venueNext; } Faction.prototype.peaceOfferAcceptFrom = function(factionOther) { return true; } Faction.prototype.relationsInitialize = function() { var universe = Globals.Instance.universe; var world = universe.world; var factionCurrent = world.factionCurrent(); var factionsOther = world.factionsOtherThanCurrent(); var diplomaticSession = DiplomaticSession.buildExample ( factionCurrent, factionsOther ); var diplomaticSessionAsControl = diplomaticSession.controlBuild(); var venueNext = new VenueControls(diplomaticSessionAsControl, universe.venueCurrent); venueNext = new VenueFader(venueNext); universe.venueNext = venueNext; } Faction.prototype.selfAndAllies = function() { var returnValues = this.factionsMatchingRelationshipState ( Relationship.States.Alliance ); returnValues.push(this); return returnValues; } Faction.prototype.strength = function() { var returnValue = 0; var ships = this.ships; for (var i = 0; i < ships.length; i++) { var ship = ships[i]; returnValue += ship.strength(); } var planets = this.planets; for (var i = 0; i < planets.length; i++) { var planet = planets[i]; returnValue += planet.strength(); } this.technologyResearcher.strength(); return returnValue; } Faction.prototype.warThreatOfferConcessionsTo = function(factionOther) { return true; } // turns Faction.prototype.updateForTurn = function() { for (var i = 0; i < this.planets.length; i++) { var planet = this.planets[i]; planet.updateForTurn(); } for (var i = 0; i < this.ships.length; i++) { var ship = this.ships[i]; ship.updateForTurn(); } this.technology.updateForTurn(); } } function FactionKnowledge(starsystems, links) { this.starsystems = starsystems; this.links = links; } function Globals() {} { // instance Globals.Instance = new Globals(); // instance methods Globals.prototype.handleEventTimerTick = function() { this.universe.updateForTimerTick(); } Globals.prototype.initialize = function ( programName, millisecondsPerTimerTick, viewSize, universe, sounds, videos ) { this.programName = programName; this.serializer = new Serializer ([ Body, BodyDefn, Camera, Color, Coords, Cursor, DateTime, Device, DeviceDefn, DiplomaticRelationship, Faction, Layout, LayoutElement, LayoutElementDefn, LinkPortal, Map, MapCell, MapCursor, MapTerrain, Network, NetworkLink, NetworkNode, NetworkNodeDefn, Orientation, Planet, PlanetDemographics, PlanetIndustry, Profile, Ship, Starsystem, TechnologyResearcher, World, Universe, VisualGroup, VisualRectangle, VisualSphere, VisualText, ]); this.displayHelper = new DisplayHelper(viewSize, 10); this.inputHelper = new InputHelper(); this.profileHelper = new ProfileHelper(); this.soundHelper = new SoundHelper(sounds); this.videoHelper = new VideoHelper(videos); this.universe = universe; var divMain = document.createElement("div"); divMain.style.position = "absolute"; divMain.style.left = "50%"; divMain.style.top = "50%"; divMain.style.marginTop = 0 - viewSize.x / 2; divMain.style.marginLeft = 0 - viewSize.y / 2; document.body.appendChild(divMain); this.divMain = divMain; this.displayHelper.initialize(); this.inputHelper.initialize(); this.universe.initialize(); this.timer = setInterval ( this.handleEventTimerTick.bind(this), millisecondsPerTimerTick ); } Globals.prototype.reset = function() { this.soundHelper.reset(); } } function Gradient(stops) { this.stops = stops; } { Gradient.prototype.toSystemGraphicsStyle = function(graphics, center, radius) { var returnValue = graphics.createRadialGradient ( center.x, center.y, 0, // startRadius center.x, center.y, radius ); for (var i = 0; i < this.stops.length; i++) { var stop = this.stops[i]; returnValue.addColorStop(stop.position, stop.systemColor); returnValue.addColorStop(stop.position, stop.systemColor); } return returnValue; } } function GradientStop(position, systemColor) { this.position = position; this.systemColor = systemColor; } function InputHelper() { this.keyCodePressed = null; this.isShiftKeyPressed = false; this.isMouseLeftPressed = false; this.mouseClickPos = new Coords(0, 0); this.mouseMovePos = new Coords(0, 0); this.mouseMovePosPrev = new Coords(0, 0); this.tempPos = new Coords(0, 0); } { InputHelper.prototype.handleEventKeyDown = function(event) { if (this.isEnabled == true) { event.preventDefault(); this.isShiftKeyPressed = event.shiftKey; this.keyCodePressed = event.keyCode; } } InputHelper.prototype.handleEventKeyUp = function(event) { this.keyCodePressed = null; } InputHelper.prototype.handleEventMouseDown = function(event) { if (this.isEnabled == true) { this.isMouseLeftPressed = true; this.mouseClickPos.overwriteWithDimensions ( event.layerX, event.layerY, 0 ); } } InputHelper.prototype.handleEventMouseMove = function(event) { if (this.isEnabled == true) { this.mouseMovePosPrev.overwriteWith ( this.mouseMovePos ); this.mouseMovePos.overwriteWithDimensions ( event.layerX, event.layerY, 0 ); } } InputHelper.prototype.handleEventMouseUp = function(event) { this.isMouseLeftPressed = false; } InputHelper.prototype.initialize = function() { document.body.onkeydown = this.handleEventKeyDown.bind(this); document.body.onkeyup = this.handleEventKeyUp.bind(this); var divMain = Globals.Instance.divMain; divMain.onmousedown = this.handleEventMouseDown.bind(this); divMain.onmousemove = this.handleEventMouseMove.bind(this); divMain.onmouseup = this.handleEventMouseUp.bind(this); this.isEnabled = true; } } function Layout(sizeInPixels, bodyDefns, map, bodies) { this.sizeInPixels = sizeInPixels; this.bodyDefns = bodyDefns; this.map = map; this.bodies = bodies; for (var i = 0; i < this.bodies.length; i++) { var body = this.bodies[i]; var bodyPosInCells = body.loc.pos; var cell = this.map.cellAtPos(bodyPosInCells); cell.body = body; } } { // static methods Layout.generateRandom = function(parent) { var terrains = [ new MapTerrain("Default", ".", "LightGray", false), ]; terrains.addLookups("codeChar"); var viewSize = Globals.Instance.displayHelper.viewSize; var mapSizeInPixels = viewSize.clone().multiplyScalar(.5); var mapPosInPixels = viewSize.clone().subtract ( mapSizeInPixels ).divideScalar ( 2 ); mapPosInPixels.z = 0; var map = Map.fromCellsAsStrings ( mapSizeInPixels, mapPosInPixels, terrains, // cellsAsStrings [ "........", "........", "........", "........", "........", "........", "........", "........", ] ); var bodyDefns = [ new LayoutElementDefn("Default", "Gray", 10), new LayoutElementDefn("Factory", "Red", 20), new LayoutElementDefn("Farm", "Green", 30), new LayoutElementDefn("Laboratory", "Blue", 40), ]; bodyDefns.addLookups("name"); var layout = new Layout ( viewSize.clone(), // sizeInPixels bodyDefns, map, // bodies [ new LayoutElement(bodyDefns["Default"], new Coords(0, 0)) ] ); return layout; } // instance methods Layout.prototype.elementAdd = function(elementToAdd) { this.bodies.push(elementToAdd); this.map.cellAtPos(elementToAdd.loc.pos).body = elementToAdd; } Layout.prototype.elementRemove = function(elementToRemove) { this.bodies.splice(this.bodies.indexOf(elementToRemove), 1); var elementPos = elementToRemove.loc.pos; var cell = this.map.cellAtPos(elementPos); cell.body = null; } Layout.prototype.updateForTurn = function() { var cells = this.map.cells; for (var i = 0; i < cells.length; i++) { var cell = cells[i]; var cellBody = cell.body; if (cellBody != null) { if (cellBody.updateForTurn != null) { cellBody.updateForTurn(); } } } } } function LayoutElement(defn, pos) { this.defn = defn; this.loc = new Location(pos); } function LayoutElementDefn(name, color, industryToBuild) { this.name = name; this.color = color; this.industryToBuild = industryToBuild; } function LinkPortal(name, defn, pos, starsystemNamesFromAndTo) { this.name = name; this.defn = defn; this.loc = new Location(pos); this.starsystemNamesFromAndTo = starsystemNamesFromAndTo; } { LinkPortal.prototype.link = function() { // todo } LinkPortal.prototype.starsystemNameTo = function() { return this.starsystemNamesFromAndTo[0]; } // controls LinkPortal.prototype.controlBuild_Selection = function() { var returnValue = new ControlLabel ( "labelLinkPortalAsSelection", new Coords(0, 0), new Coords(0, 0), // this.size false, // isTextCentered new DataBinding("Link to " + this.starsystemNamesFromAndTo[1]) ); return returnValue; } } function Location(pos) { this.pos = pos; } { Location.prototype.clone = function() { return new Location(this.pos.clone()); } Location.prototype.equals = function(other) { var returnValue = ( this.pos.equals(other.pos) ); return returnValue; } } function Map(sizeInPixels, sizeInCells, pos, terrains, cells) { this.sizeInPixels = sizeInPixels; this.sizeInCells = sizeInCells; this.pos = pos; this.terrains = terrains; this.cells = cells; this.sizeInCellsMinusOnes = this.sizeInCells.clone().subtract ( new Coords(1, 1, 1) ); this.cellSizeInPixels = this.sizeInPixels.clone().divide ( this.sizeInCells ); this.cursor = new MapCursor(null, new Coords(0, 0, 0)); } { // static methods Map.fromCellsAsStrings = function ( sizeInPixels, pos, terrains, cellsAsStrings ) { var sizeInCells = new Coords ( cellsAsStrings[0].length, cellsAsStrings.length, 1 ); var cells = []; var cellPos = new Coords(0, 0, 0); for (var y = 0; y < sizeInCells.y; y++) { cellPos.y = y; cellRowAsString = cellsAsStrings[y]; for (var x = 0; x < sizeInCells.x; x++) { cellPos.x = x; var cellAsChar = cellRowAsString[x]; var cellTerrain = terrains[cellAsChar]; var cell = new MapCell ( cellPos.clone(), cellTerrain, null // body ); cells.push(cell); } } var returnValue = new Map ( sizeInPixels, sizeInCells, pos, terrains, cells ); return returnValue; } // instance methods Map.prototype.cellAtPos = function(cellPos) { var cellIndex = this.indexOfCellAtPos(cellPos); var returnValue = this.cells[cellIndex]; return returnValue; } Map.prototype.indexOfCellAtPos = function(cellPos) { return cellPos.y * this.sizeInCells.x + cellPos.x; } } function MapCell(pos, terrain, body) { this.pos = pos; this.terrain = terrain; this.body = body; } function MapCursor(bodyDefn, pos) { this.bodyDefn = bodyDefn; this.pos = pos; } function MapTerrain(name, codeChar, color, isBlocked) { this.name = name this.codeChar = codeChar; this.color = color; this.isBlocked = isBlocked; } function NameGenerator() { // static class } { NameGenerator.generateName = function() { var returnValue = ""; var numberOfSyllablesMin = 2; var numberOfSyllablesMax = 3; var numberOfSyllablesRange = numberOfSyllablesMax - numberOfSyllablesMin; var numberOfSyllables = numberOfSyllablesMin + Math.floor ( Math.random() * numberOfSyllablesRange ); var consonants = "bdfghjklmnprstvwxyz"; var vowels = "aeiou"; for (var s = 0; s < numberOfSyllables; s++) { var syllable = consonants[Math.floor(Math.random() * consonants.length)] + vowels[Math.floor(Math.random() * vowels.length)] + consonants[Math.floor(Math.random() * consonants.length)]; returnValue += syllable; } returnValue = returnValue[0].toUpperCase() + returnValue.substr(1); return returnValue; } } function Network(name, nodes, links) { this.name = name; this.nodes = nodes; this.links = links; this.nodes.addLookups("name"); for (var i = 0; i < this.links.length; i++) { var link = this.links[i]; var namesOfNodesLinked = link.namesOfNodesLinked; for (var n = 0; n < namesOfNodesLinked.length; n++) { var nameOfNodeFrom = namesOfNodesLinked[n]; var nameOfNodeTo = namesOfNodesLinked[1 - n]; var linksOriginatingAtNodeFrom = this.links[nameOfNodeFrom]; if (linksOriginatingAtNodeFrom == null) { linksOriginatingAtNodeFrom = []; this.links[nameOfNodeFrom] = linksOriginatingAtNodeFrom; } linksOriginatingAtNodeFrom[nameOfNodeTo] = link; } } } { Network.generateRandom = function ( name, nodeDefns, numberOfNodes, minAndMaxDistanceOfNodesFromOrigin, distanceBetweenNodesMin ) { var nodesNotYetLinked = []; var radiusMinAndMax = minAndMaxDistanceOfNodesFromOrigin; var radiusMin = radiusMinAndMax[0]; var radiusMax = radiusMinAndMax[1]; var radiusRange = radiusMax - radiusMin; var nodePos = new Coords(0, 0, 0); var displacementOfNodeNewFromOther = new Coords(0, 0, 0); var minusOnes = new Coords(-1, -1, -1); for (var i = 0; i < numberOfNodes; i++) { var distanceOfNodeNewFromExisting = 0; while (distanceOfNodeNewFromExisting < distanceBetweenNodesMin) { nodePos.randomize().multiplyScalar(2).add ( minusOnes ).normalize().multiplyScalar ( radiusMin + radiusRange * Math.random() ); distanceOfNodeNewFromExisting = distanceBetweenNodesMin; for (var j = 0; j < i; j++) { var nodeOtherPos = nodesNotYetLinked[j].loc.pos; displacementOfNodeNewFromOther.overwriteWith ( nodePos ).subtract ( nodeOtherPos ); var distanceOfNodeNewFromOther = displacementOfNodeNewFromOther.magnitude(); if (distanceOfNodeNewFromOther < distanceBetweenNodesMin) { distanceOfNodeNewFromExisting = distanceOfNodeNewFromOther; break; } } } var nodeDefnIndexRandom = Math.floor(nodeDefns.length * Math.random()); var nodeDefn = nodeDefns[nodeDefnIndexRandom]; var nodeStarsystem = Starsystem.generateRandom(); var node = new NetworkNode ( nodeStarsystem.name, nodeDefn, nodePos.clone(), nodeStarsystem ); nodesNotYetLinked.push(node); } var nodesLinked = [ nodesNotYetLinked[0] ]; nodesNotYetLinked.splice(0, 1); var links = []; var bodyDefnLinkPortal = new BodyDefn ( "LinkPortal", new Coords(10, 10), // size new VisualGroup ([ new VisualSphere(Color.Instances.Gray, 10) ]) ); var tempPos = new Coords(0, 0, 0); while (nodesLinked.length < numberOfNodes) { var nodePairClosestSoFar = null; var distanceBetweenNodePairClosestSoFar = minAndMaxDistanceOfNodesFromOrigin[1] * 4; for (var i = 0; i < nodesLinked.length; i++) { var nodeLinked = nodesLinked[i]; var nodeLinkedPos = nodeLinked.loc.pos; for (var j = 0; j < nodesNotYetLinked.length; j++) { var nodeToLink = nodesNotYetLinked[j]; var distanceBetweenNodes = tempPos.overwriteWith ( nodeLinkedPos ).subtract ( nodeToLink.loc.pos ).magnitude(); if (distanceBetweenNodes <= distanceBetweenNodePairClosestSoFar) { distanceBetweenNodePairClosestSoFar = distanceBetweenNodes; nodePairClosestSoFar = [nodeToLink, nodeLinked]; } } } var nodeToLink = nodePairClosestSoFar[0]; var nodeLinked = nodePairClosestSoFar[1]; var link = new NetworkLink ([ nodeToLink.name, nodeLinked.name ]); var linkIndex = links.length; links.push(link); nodesLinked.push(nodeToLink); nodesNotYetLinked.splice(nodesNotYetLinked.indexOf(nodeToLink), 1); for (var i = 0; i < nodePairClosestSoFar.length; i++) { var node = nodePairClosestSoFar[i]; var starsystem = node.starsystem; var starsystemSize = starsystem.size; var starsystemOther = nodePairClosestSoFar[1 - i]; var linkPortal = new LinkPortal ( "LinkPortal" + linkIndex, bodyDefnLinkPortal, new Coords().randomize().multiply ( starsystemSize ).multiplyScalar ( 2 ).subtract ( starsystemSize ), // starsystemNamesFromAndTo [ starsystem.name, starsystemOther.name ] ); var starsystemPortals = starsystem.linkPortals; starsystemPortals.push(linkPortal); starsystemPortals[starsystemOther.name] = linkPortal; }; } var returnValue = new Network ( name, nodesLinked, links ); return returnValue; } // turns Network.prototype.updateForTurn = function() { for (var i = 0; i < this.links.length; i++) { var link = this.links[i]; link.updateForTurn(this); } } } function NetworkLink(namesOfNodesLinked) { this.namesOfNodesLinked = namesOfNodesLinked; this.ships = []; } { NetworkLink.prototype.direction = function() { return this.displacement().normalize(); } NetworkLink.prototype.displacement = function() { var nodesLinked = this.nodesLinked(); var returnValue = nodesLinked[1].loc.pos.clone().subtract ( nodesLinked[0].loc.pos ); return returnValue; } NetworkLink.prototype.length = function() { return this.displacement().magnitude(); } NetworkLink.prototype.nodesLinked = function() { var network = Globals.Instance.universe.world.network; var returnValue = [ network.nodes[this.namesOfNodesLinked[0]], network.nodes[this.namesOfNodesLinked[1]], ]; return returnValue; } // turns NetworkLink.prototype.updateForTurn = function(network) { if (this.ships.length > 0) { var nodesLinked = this.nodesLinked(); var length = this.length(); var shipsExitingLink = []; for (var i = 0; i < this.ships.length; i++) { var ship = this.ships[i]; var shipPos = ship.loc.pos; shipPos.x += Math.abs(ship.vel.x); if (shipPos.x >= length) { var indexOfNodeDestination = (ship.vel.x > 0 ? 1 : 0); var indexOfNodeSource = 1 - indexOfNodeDestination; var nodeDestination = nodesLinked[indexOfNodeDestination]; var nodeSource = nodesLinked[indexOfNodeSource]; var starsystemDestination = nodeDestination.starsystem; var starsystemSource = nodeSource.starsystem; var portalToExitFrom = starsystemDestination.linkPortals[starsystemSource.name]; var exitPos = portalToExitFrom.loc.pos; shipPos.overwriteWith(exitPos); starsystemDestination.ships.push(ship); shipsExitingLink.push(ship); } } for (var i = 0; i < shipsExitingLink.length; i++) { this.ships.splice ( this.ships.indexOf(shipsExitingLink[i]), 1 ); } } } } function NetworkNode(name, defn, pos, starsystem) { this.name = name; this.defn = defn; this.loc = new Location(pos); this.starsystem = starsystem; } { // constants NetworkNode.RadiusActual = 4; } { NetworkNode.prototype.controlBuild_Selection = function() { /* var returnValue = new ControlLabel ( "labelNetworkNodeAsSelection", new Coords(0, 0), new Coords(0, 0), // this.size false, // isTextCentered new DataBinding(this.starsystem.name) ); */ var viewSize = Globals.Instance.displayHelper.viewSize; var containerSize = new Coords(100, 80); var margin = 10; var controlSpacing = 8; var buttonSize = new Coords ( containerSize.x - margin * 4, 10 ); var starsystem = this.starsystem; var returnValue = new ControlContainer ( "containerStarsystem", ControlBuilder.ColorsForeAndBackDefault, new Coords(viewSize.x - margin - containerSize.x, margin), // pos new Coords(containerSize.x - margin * 2, 40), // size // children [ new ControlLabel ( "labelStarsystemName", new Coords(margin, margin), new Coords(0, 0), // this.size false, // isTextCentered new DataBinding(starsystem.name) ), new ControlLabel ( "labelStarsystemHolder", new Coords(margin, margin + controlSpacing), new Coords(0, 0), // this.size false, // isTextCentered new DataBinding(starsystem, "faction.name") ), new ControlButton ( "buttonView", new Coords(margin, margin + controlSpacing * 2), // pos buttonSize, // size "View", null, // dataBindingForIsEnabled // click function () { alert("todo - view") } ), ] ); return returnValue; } } function NetworkNodeDefn(name, color) { this.name = name; this.color = color; } { function NetworkNodeDefn_Instances() { var colors = Color.Instances; this.Blue = new NetworkNodeDefn("Blue", colors.GrayLight); this.Green = new NetworkNodeDefn("Green", colors.GrayLight); this.Red = new NetworkNodeDefn("Red", colors.GrayLight); this._All = [ this.Blue, this.Green, this.Red, ]; } NetworkNodeDefn.Instances = new NetworkNodeDefn_Instances(); } function Notification(typeName, message) { this.typeName = typeName; this.message = message; } { Notification.prototype.defn = function() { return NotificationType.Instances._All[this.defnName]; } } function NotificationSession(notifications) { this.notifications = notifications; this.notificationSelected = null; } { NotificationSession.prototype.controlBuild = function() { var displayHelper = Globals.Instance.displayHelper; var containerSize = displayHelper.viewSize.clone(); var controlHeight = containerSize.y / 12; var margin = 10; var columnWidth = containerSize.x - margin * 2; var buttonWidth = (columnWidth - margin) / 2; var returnValue = new ControlContainer ( "containerNotificationSession", ControlBuilder.ColorsForeAndBackDefault, new Coords(0, 0), // pos containerSize, // children [ new ControlLabel ( "labelNotifications", new Coords(margin, margin), // pos new Coords(columnWidth, controlHeight), // size false, // isTextCentered new DataBinding("Notifications:") ), new ControlSelect ( "listNotifications", new Coords(margin, margin + controlHeight), // pos new Coords(columnWidth, controlHeight * 4), // size // dataBindingForValueSelected new DataBinding(this, "notificationSelected"), // dataBindingForOptions new DataBinding(this.notifications), null, // bindingExpressionForOptionValues "message", // bindingExpressionForOptionText new DataBinding(true), // dataBindingForIsEnabled 4 // numberOfItemsVisible ), new ControlLabel ( "labelMessage", new Coords(margin, margin * 2 + controlHeight * 5), // pos new Coords(columnWidth, controlHeight), // size false, // isTextCentered new DataBinding("Details:") ), new ControlLabel ( "textMessage", new Coords(margin, margin * 2 + controlHeight * 6), // pos new Coords(columnWidth, controlHeight), // size false, // isTextCentered new DataBinding(this, "notificationSelected.message") ), new ControlButton ( "buttonGoTo", new Coords(margin, margin * 2 + controlHeight * 7), // pos new Coords(columnWidth, controlHeight), // size "Go To", null, // dataBindingForIsEnabled // click function() { alert("todo - goto"); } ), new ControlButton ( "buttonDone", new Coords(margin, containerSize.y - margin - controlHeight), // pos new Coords(columnWidth, controlHeight), // size "Done", null, // dataBindingForIsEnabled // click function() { var universe = Globals.Instance.universe; var world = universe.world; var venueNext = new VenueWorld(world); venueNext = new VenueFader(venueNext); universe.venueNext = venueNext; } ), ] ); return returnValue; } } function NotificationType(name) { this.name = name; } { function NotificationType_Instances() { this.Default = new NotificationType("Default"); } NotificationType.Instances = new NotificationType_Instances(); } function NumberHelper() {} { NumberHelper.wrapValueToRangeMinMax = function(value, min, max) { var range = max - min; while (value < min) { value += range; } while (value > max) { value -= range; } return value; } } function Order(defnName, target) { this.defnName = defnName; this.target = target; this.isComplete = false; } { Order.prototype.defn = function() { return OrderDefn.Instances._All[this.defnName]; } Order.prototype.obey = function(actor) { if (this.isComplete == false) { this.defn().obey(actor, this); } } } function OrderDefn(name, obey) { this.name = name; this.obey = obey; } { function OrderDefn_Instances() { this.Go = new OrderDefn ( "Go", // obey function(actor, order) { var target = order.target; if (actor.loc.equals(target.loc) == true) { order.isComplete = true; } else if (actor.activity == null) { actor.activity = new Activity ( "MoveToTarget", [ order.target ] ); } } ); this._All = [ this.Go, ]; this._All.addLookups("name"); } OrderDefn.Instances = new OrderDefn_Instances(); } function Orientation(forward, down) { this.forward = forward.clone().normalize(); this.right = down.clone().crossProduct(this.forward).normalize(); this.down = this.forward.clone().crossProduct(this.right).normalize(); } { Orientation.prototype.overwriteWith = function(other) { this.forward.overwriteWith(other.forward); this.right.overwriteWith(other.right); this.down.overwriteWith(other.down); } } function Plane(normal, distanceFromOrigin) { this.normal = normal; this.distanceFromOrigin = distanceFromOrigin; } function Planet(name, factionName, pos, demographics, industry, layout) { this.name = name; this.factionName = factionName; this.loc = new Location(pos); this.demographics = demographics; this.industry = industry; this.layout = layout; this.defn = Planet.BodyDefn; } { // constants Planet.BodyDefn = new BodyDefn ( "Planet", new Coords(10, 10), // size new VisualGroup ([ new VisualSphere(Color.Instances.Cyan, 10), ]) ); // instance methods Planet.prototype.faction = function() { return (this.factionName == null ? null : Globals.Instance.universe.world.factions[this.factionName]); } // controls Planet.prototype.controlBuild_Selection = function() { var returnValue = new ControlLabel ( "labelPlanetAsSelection", new Coords(0, 0), new Coords(0, 0), // this.size false, // isTextCentered new DataBinding(this.name) ); return returnValue; } // diplomacy Planet.prototype.strength = function() { return 1; } // turns Planet.prototype.updateForTurn = function() { this.layout.updateForTurn(); this.industry.updateForTurn(this); this.demographics.updateForTurn(this); } } function PlanetDemographics(population) { this.population = population; } { PlanetDemographics.prototype.updateForTurn = function(planet) { var prosperityThisTurn = 1; this.prosperityAccumulated += prosperityThisTurn; var prosperityRequiredForGrowth = this.population * 2; if (this.prosperityAccumulated >= prosperityRequiredForGrowth) { this.prosperityAccumulated = 0; var populationMax = 10; // todo if (this.population < populationMax); { this.population++; } } } } function PlanetIndustry(industryAccumulated, buildableInProgress) { this.industryAccumulated = industryAccumulated; this.buildableInProgress = buildableInProgress; } { PlanetIndustry.prototype.updateForTurn = function(planet) { var industryThisTurn = 1; // hack this.industryAccumulated += industryThisTurn; if (this.buildableInProgress != null) { var buildableDefn = this.buildableInProgress.defn; var industryToComplete = buildableDefn.industryToComplete; if (this.industryAccumulated >= industryToComplete) { this.industryAccumulated = 0; // todo var one = 1; } } } } function Polar(azimuth, elevation, radius) { // values in radians this.azimuth = azimuth; this.elevation = elevation; this.radius = radius; } { // constants Polar.RadiansPerCycle = Math.PI * 2; Polar.RadiansPerRightAngle = Math.PI / 2; // static methods Polar.fromCoords = function(coordsToConvert) { var azimuthInRadians = Math.atan2(coordsToConvert.y, coordsToConvert.x); var azimuth = azimuthInRadians / Polar.RadiansPerCycle; if (azimuth < 0) { azimuth += 1; } var radius = coordsToConvert.magnitude(); var elevationInRadians = Math.asin(coordsToConvert.z / radius); var elevation = elevationInRadians / Polar.RadiansPerRightAngle; var returnValue = new Polar ( azimuth, elevation, radius ); return returnValue; } // instance methods Polar.random = function() { return new Polar ( Math.random(), Math.random() * 2 - 1, Math.random() ); } Polar.prototype.toCoords = function() { var azimuthInRadians = this.azimuth * Polar.RadiansPerCycle; var elevationInRadians = this.elevation * Polar.RadiansPerRightAngle; var cosineOfElevation = Math.cos(elevationInRadians); var returnValue = new Coords ( Math.cos(azimuthInRadians) * cosineOfElevation, Math.sin(azimuthInRadians) * cosineOfElevation, Math.sin(elevationInRadians) ).multiplyScalar(this.radius); return returnValue; } } function Profile(name, worlds) { this.name = name; this.worlds = worlds; } function ProfileHelper() {} { ProfileHelper.prototype.profileAdd = function(profile) { var profiles = this.profiles(); profiles.push(profile); var propertyName = Globals.Instance.programName + ".Profiles"; StorageHelper.save ( propertyName, profiles ); } ProfileHelper.prototype.profileDelete = function(profileToDelete) { var profilesStored = this.profiles(); var profileIndex = this.profileIndexFindByName ( profilesStored, profileToDelete.name ); profilesStored.splice ( profileIndex, 1 ); var propertyName = Globals.Instance.programName + ".Profiles"; StorageHelper.save ( propertyName, profilesStored ); } ProfileHelper.prototype.profileIndexFindByName = function ( profiles, profileNameToFind ) { var returnValue = null; for (var i = 0; i < profiles.length; i++) { var profile = profiles[i]; if (profile.name == profileNameToFind) { returnValue = i; break; } } return returnValue; } ProfileHelper.prototype.profileSave = function(profileToSave) { var profilesStored = this.profiles(); var profileIndex = this.profileIndexFindByName ( profilesStored, profileToSave.name ); profilesStored.splice ( profileIndex, 1, profileToSave ); var propertyName = Globals.Instance.programName + ".Profiles"; StorageHelper.save ( propertyName, profilesStored ); } ProfileHelper.prototype.profiles = function() { var propertyName = Globals.Instance.programName + ".Profiles"; var profiles = StorageHelper.load ( propertyName ); if (profiles == null) { profiles = []; StorageHelper.save ( propertyName, profiles ); } return profiles; } } function Ray(startPos, direction) { this.startPos = startPos; this.direction = direction; } function Serializer(knownTypes) { this.knownTypes = knownTypes; for (var i = 0; i < this.knownTypes.length; i++) { var knownType = this.knownTypes[i]; this.knownTypes[knownType.name] = knownType; } } { // constants Serializer.TypeNamesBuiltIn = [ "Boolean", "Function", "Number", "String" ]; // instance methods Serializer.prototype.deleteClassNameRecursively = function(objectToDeleteClassNameOn) { if (objectToDeleteClassNameOn == null) { return; } var className = objectToDeleteClassNameOn.constructor.name; if (this.knownTypes[className] != null) { delete objectToDeleteClassNameOn.className; for (var childPropertyName in objectToDeleteClassNameOn) { var childProperty = objectToDeleteClassNameOn[childPropertyName]; this.deleteClassNameRecursively(childProperty); } } else if (className == "Array") { for (var i = 0; i < objectToDeleteClassNameOn.length; i++) { var element = objectToDeleteClassNameOn[i]; this.deleteClassNameRecursively(element); } } } Serializer.prototype.deserialize = function(stringToDeserialize) { var objectDeserialized = JSON.parse(stringToDeserialize); this.setPrototypeRecursively(objectDeserialized); this.deleteClassNameRecursively(objectDeserialized); return objectDeserialized; } Serializer.prototype.serialize = function(objectToSerialize) { this.setClassNameRecursively(objectToSerialize); var returnValue = JSON.stringify(objectToSerialize); this.deleteClassNameRecursively(objectToSerialize); return returnValue; } Serializer.prototype.setClassNameRecursively = function(objectToSetClassNameOn) { if (objectToSetClassNameOn == null) { return; } var className = objectToSetClassNameOn.constructor.name; if (this.knownTypes[className] != null) { for (var childPropertyName in objectToSetClassNameOn) { var childProperty = objectToSetClassNameOn[childPropertyName]; this.setClassNameRecursively(childProperty); } objectToSetClassNameOn.className = className; } else if (className == "Array") { for (var i = 0; i < objectToSetClassNameOn.length; i++) { var element = objectToSetClassNameOn[i]; this.setClassNameRecursively(element); } } else if (Serializer.TypeNamesBuiltIn.indexOf(className) == -1) { throw "Unknown type!"; } } Serializer.prototype.setPrototypeRecursively = function(objectToSetPrototypeOn) { if (objectToSetPrototypeOn == null) { return; } var className = objectToSetPrototypeOn.className; var typeOfObjectToSetPrototypeOn = this.knownTypes[className]; if (typeOfObjectToSetPrototypeOn != null) { objectToSetPrototypeOn.__proto__ = typeOfObjectToSetPrototypeOn.prototype; for (var childPropertyName in objectToSetPrototypeOn) { var childProperty = objectToSetPrototypeOn[childPropertyName]; this.setPrototypeRecursively(childProperty); } } else { var typeName = objectToSetPrototypeOn.constructor.name; if (typeName == "Array") { for (var i = 0; i < objectToSetPrototypeOn.length; i++) { var element = objectToSetPrototypeOn[i]; this.setPrototypeRecursively(element); } } else if (Serializer.TypeNamesBuiltIn.indexOf(typeName) == -1) { throw "Unknown type!"; } } } } function Ship(name, defn, pos, factionName) { this.name = name; this.defn = defn; this.loc = new Location(pos); this.factionName = factionName; this.vel = new Coords(0, 0, 0); var deviceDefnDefault = new DeviceDefn ( "DeviceDefn0", 100, // range function (actor, device, target) { var universe = Globals.Instance.universe; var venueCurrent = universe.venueCurrent; var projectile = new Ship ( "[projectile]", Ship.bodyDefnBuild(Color.Instances.Yellow), actor.loc.pos.clone(), actor.factionName ); projectile.activity = new Activity ( "MoveToTarget", [ target ] ); venueCurrent.model.bodies.push(projectile); } ); this.devices = [ new Device(deviceDefnDefault), new Device(deviceDefnDefault), new Device(deviceDefnDefault), ]; this.movesThisTurn = 0; } { // static methods Ship.bodyDefnBuild = function(color) { var returnValue = new BodyDefn ( "Ship", new Coords(10, 10), // size new VisualGroup ([ new VisualRectangle(color, new Coords(10, 10)), ]) ); return returnValue; } // instance methods Ship.prototype.faction = function() { return (this.factionName == null ? null : Globals.Instance.universe.world.factions[this.factionName]); } Ship.prototype.id = function() { return this.factionName + this.name; } // controls Ship.prototype.controlBuild_Selection = function() { var viewSize = Globals.Instance.displayHelper.viewSize; var containerSize = new Coords(100, viewSize.y); var margin = 10; var controlSpacing = 20; var buttonSize = new Coords ( containerSize.x - margin * 4, 15 ); var returnValue = new ControlContainer ( "containerShip", ControlBuilder.ColorsForeAndBackDefault, new Coords(viewSize.x - margin - containerSize.x, margin), // pos new Coords(containerSize.x - margin * 2, containerSize.y - margin * 4), // children [ new ControlLabel ( "labelShipAsSelection", new Coords(margin, margin), new Coords(0, 0), // this.size false, // isTextCentered new DataBinding(this.name) ), new ControlButton ( "buttonView", new Coords(margin, margin + controlSpacing), // pos buttonSize, // size "View", null, // dataBindingForIsEnabled // click function () { alert("todo - view") } ), new ControlButton ( "buttonMove", new Coords(margin, margin + controlSpacing * 2), // pos buttonSize, "Move", null, // dataBindingForIsEnabled function () { var venueCurrent = Globals.Instance.universe.venueCurrent; venueCurrent.cursorBuild(); } ), new ControlButton ( "buttonRepeat", new Coords(margin, margin + controlSpacing * 3), // pos buttonSize, "Repeat", null, // dataBindingForIsEnabled // click function () { alert("todo - repeat") } ), new ControlLabel ( "labelDevices", new Coords(margin, margin + controlSpacing * 4), // pos new Coords(0, 0), // this.size false, // isTextCentered new DataBinding("Devices:") ), new ControlSelect ( "listDevices", new Coords(margin, margin + controlSpacing * 5), // pos new Coords(buttonSize.x, controlSpacing * 4), // size // dataBindingForValueSelected new DataBinding(Globals.Instance.universe.venueCurrent.selection, "deviceSelected"), // dataBindingForOptions new DataBinding ( this.devices, null ), null, // bindingExpressionForOptionValues "defn.name", // bindingExpressionForOptionText new DataBinding(true), // dataBindingForIsEnabled 4 // numberOfItemsVisible ), new ControlButton ( "buttonDeviceUse", new Coords(margin, margin + controlSpacing * 10), // pos buttonSize, "Use Device", null, // dataBindingForIsEnabled // click function () { alert("todo - use device") } ), ] ); return returnValue; } // diplomacy Ship.prototype.strength = function() { return 1; } // turns Ship.prototype.updateForTurn = function() { // todo } } function Sound(name, sourcePath) { this.name = name; this.sourcePath = sourcePath; this.offsetInSeconds = 0; } { Sound.prototype.domElementBuild = function(volume) { this.domElement = document.createElement("audio"); this.domElement.sound = this; this.domElement.autoplay = true; this.domElement.oncanplay = this.play2.bind(this); this.domElement.onended = this.stop.bind(this); this.domElement.volume = volume; var domElementForSoundSource = document.createElement("source"); domElementForSoundSource.src = this.sourcePath; this.domElement.appendChild ( domElementForSoundSource ); return this.domElement; } Sound.prototype.pause = function() { var offsetInSeconds = this.domElement.currentTime; this.stop(); this.offsetInSeconds = offsetInSeconds; } Sound.prototype.play = function(volume) { Globals.Instance.divMain.appendChild ( this.domElementBuild(volume) ); } Sound.prototype.play2 = function() { this.domElement.currentTime = this.offsetInSeconds; this.domElement.play(); } Sound.prototype.reset = function() { this.offsetInSeconds = 0; } Sound.prototype.stop = function(event) { var domElement = (event == null ? this.domElement : event.srcElement); Globals.Instance.divMain.removeChild(domElement); this.offsetInSeconds = 0; } } function SoundHelper(sounds) { this.sounds = sounds; this.sounds.addLookups("name"); this.musicVolume = 1; this.soundVolume = 1; this.soundForMusic = null; } { SoundHelper.controlSelectOptionsVolume = function() { var returnValue = [ new ControlSelectOption(1, "100%"), new ControlSelectOption(0, "0%"), new ControlSelectOption(.1, "10%"), new ControlSelectOption(.2, "20%"), new ControlSelectOption(.3, "30%"), new ControlSelectOption(.4, "40%"), new ControlSelectOption(.5, "50%"), new ControlSelectOption(.6, "60%"), new ControlSelectOption(.7, "70%"), new ControlSelectOption(.8, "80%"), new ControlSelectOption(.9, "90%"), ]; return returnValue; } // instance methods SoundHelper.prototype.reset = function() { for (var i = 0; i < this.sounds.length; i++) { var sound = this.sounds[i]; sound.offsetInSeconds = 0; } } SoundHelper.prototype.soundWithNamePlayAsEffect = function(soundName) { this.sounds[soundName].play(this.soundVolume); } SoundHelper.prototype.soundWithNamePlayAsMusic = function(soundName) { if ( this.soundForMusic != null && this.soundForMusic.name != soundName ) { this.soundForMusic.stop(); } this.soundForMusic = this.sounds[soundName]; this.soundForMusic.play(this.musicVolume); } } function Sphere(radius, centerPos) { this.radius = radius; this.centerPos = centerPos; } function Starsystem(name, size, star, linkPortals, planets, factionName) { this.name = name; this.size = size; this.star = star; this.linkPortals = linkPortals; this.planets = planets; this.factionName = factionName; this.ships = []; } { // constants Starsystem.SizeStandard = new Coords(100, 100, 100); // static methods Starsystem.generateRandom = function() { var name = NameGenerator.generateName(); var size = Starsystem.SizeStandard; var star = new Body ( "Star", new BodyDefn ( "Star", new Coords(40, 40), // size new VisualGroup ([ new VisualSphere(Color.Instances.Yellow, 40), new VisualText(name) ]) ), new Coords(0, 0, -10) ); var numberOfPlanetsMin = 1; var numberOfPlanetsMax = 4; var numberOfPlanetsRange = numberOfPlanetsMax - numberOfPlanetsMin; var numberOfPlanets = numberOfPlanetsMin + Math.floor ( Math.random() * numberOfPlanetsRange ); var spaceBetweenPlanets = 40; var planets = []; for (var i = 0; i < numberOfPlanets; i++) { var planet = new Planet ( name + " " + (i + 1), null, // factionName // pos new Coords().randomize().multiply ( size ).multiplyScalar ( 2 ).subtract ( size ), new PlanetDemographics(1), new PlanetIndustry(0, null), Layout.generateRandom() ); planets.push(planet); } var returnValue = new Starsystem ( name, size, star, [], // linkPortals - generated later planets ); return returnValue; } // instance methods Starsystem.prototype.faction = function() { return (this.factionName == null ? null : Globals.Instance.universe.world.factions[this.factionName]); } Starsystem.prototype.links = function() { var returnValues = []; for (var i = 0; i < this.linkPortals.length; i++) { var linkPortal = this.linkPortals[i]; var link = linkPortal.link(); returnValues.push(link); } return returnValues; } // moves Starsystem.prototype.updateForMove = function() { alert("todo"); } // turns Starsystem.prototype.updateForTurn = function() { for (var i = 0; i < this.bodies.length; i++) { var body = this.bodies[i]; if (body.updateForTurn != null) { body.updateForTurn(); } } } } function StorageHelper() {} { StorageHelper.load = function(propertyName) { var returnValue; var returnValueAsString = localStorage.getItem ( propertyName ); if (returnValueAsString == null) { returnValue = null; } else { returnValue = Globals.Instance.serializer.deserialize ( returnValueAsString ); } return returnValue; } StorageHelper.save = function(propertyName, valueToSave) { var valueToSaveSerialized = Globals.Instance.serializer.serialize ( valueToSave ); localStorage.setItem ( propertyName, valueToSaveSerialized ); } } function StringHelper() { // static class } { String.prototype.padLeft = function ( lengthAfterPadding, characterToPadWith ) { var stringToPad = this; while (stringToPad.length < lengthAfterPadding) { stringToPad = characterToPadWith + stringToPad; } return stringToPad; } } // classes function TalkDefn(name, talkNodes) { this.name = name; this.talkNodes = talkNodes; for (var i = 0; i < this.talkNodes.length; i++) { var talkNode = this.talkNodes[i]; if (talkNode.defn.name == "label") { var label = talkNode.parameters[0]; this.talkNodes[TalkNode.Underscore + label] = talkNode; } } } { TalkDefn.prototype.talkNodeByLabel = function(nameOfTalkNodeToGet) { return this.talkNodes[TalkNode.Underscore + nameOfTalkNodeToGet]; } } function TalkNode(defn, parameters) { this.defn = defn; this.parameters = parameters; } { // constant TalkNode.Underscore = "_"; // Prepended for array lookup in case name is numeric // static methods TalkNode.fromString = function(stringToParse) { var returnValue = null; stringToParse = stringToParse.trim(); var stringSplitOnQuotes = stringToParse.split("'"); for (var i = 1; i < stringSplitOnQuotes.length; i += 2) { var stringLiteral = stringSplitOnQuotes[i]; stringLiteral = stringLiteral.split(" ").join("_"); stringSplitOnQuotes[i] = stringLiteral; } stringToParse = stringSplitOnQuotes.join("'"); stringToParse = stringToParse.split("\t").join(" "); var talkNodeDefns = TalkNodeDefn.Instances._All; var stringAsTokens = stringToParse.trim().split(" "); for (var i = 0; i < stringAsTokens.length; i++) { var token = stringAsTokens[i]; token = token.split("_").join(" "); token = token.split("'").join(""); token = token.split("\"").join("'"); stringAsTokens[i] = token; if (token == "") { stringAsTokens.splice(i, 1); i--; } } var talkNodeDefnName = stringAsTokens[0]; var talkNodeDefn = talkNodeDefns[talkNodeDefnName]; if (talkNodeDefn == null) { throw "Unrecognized command."; } else { var parameters = stringAsTokens.slice(1); returnValue = new TalkNode ( talkNodeDefn, parameters ) } return returnValue; } // instance methods TalkNode.prototype.click = function(talkSession, scope) { if (this.defn.click != null) { this.defn.click(talkSession, scope, this); } } TalkNode.prototype.execute = function(talkSession, scope) { this.defn.execute(talkSession, scope, this); } TalkNode.prototype.htmlElementBuild = function(talkSession) { var returnValue = document.createElement("div"); returnValue.innerHTML = this.parameters; returnValue.talkSession = talkSession; returnValue.onclick = TalkNode.handleClickEvent; returnValue.talkNode = this; this.htmlElement = returnValue; return returnValue; } TalkNode.prototype.text = function() { return this.parameters[1]; // hack } } function TalkNodeDefn(name, execute) { this.name = name; this.execute = execute; } { // instances TalkNodeDefn.Instances = new TalkNodeDefn_Instances(); function TalkNodeDefn_Instances() { this.Display = new TalkNodeDefn ( "display", // execute function(talkSession, scope, talkNode) { scope.areOptionsVisible = false; var textToDisplay = talkNode.parameters[0]; var textToDisplaySplit = textToDisplay.split("$"); for (var i = 1; i < textToDisplaySplit.length; i += 2) { var variableName = textToDisplaySplit[i]; var variableValue = talkSession.variableLookup[variableName]; textToDisplaySplit[i] = variableValue; } textToDisplay = textToDisplaySplit.join(""); talkSession.log.push(talkSession.factions[1] + ": " + textToDisplay); scope.displayTextCurrent = textToDisplay; scope.talkNodeAdvance(talkSession); } ); this.Goto = new TalkNodeDefn ( "goto", // execute function(talkSession, scope, talkNode) { scope.talkNodeCurrent = talkSession.defn.talkNodeByLabel ( talkNode.parameters[0] ); talkSession.update(); } ); this.IfGoto = new TalkNodeDefn ( "ifGoto", // execute function(talkSession, scope, talkNode) { var parameters = talkNode.parameters; var variableName = parameters[0]; var variableValueToMatch = parameters[1]; if (variableValueToMatch == "null") { variableValueToMatch = null; } var variableValueActual = talkSession.variableLookup[variableName]; if (variableValueActual != null) { variableValueActual = variableValueActual.toString(); } if (variableValueActual == variableValueToMatch) { var nameOfDestinationNode = parameters[2]; scope.talkNodeCurrent = talkSession.defn.talkNodeByLabel ( nameOfDestinationNode ); } else { scope.talkNodeAdvance(talkSession); } talkSession.update(); } ); this.Label = new TalkNodeDefn ( "label", // execute function(talkSession, scope, talkNode) { // do nothing scope.talkNodeAdvance(talkSession); talkSession.update(); } ); this.Option = new TalkNodeDefn ( "option", // execute function(talkSession, scope, talkNode) { scope.talkNodesForOptions.push ( talkNode ); scope.talkNodeAdvance(talkSession); talkSession.update(); } ); this.OptionsClear = new TalkNodeDefn ( "optionsClear", // execute function(talkSession, scope, talkNode) { scope.talkNodesForOptions.length = 0; scope.talkNodeAdvance(talkSession); talkSession.update(); } ); this.Pop = new TalkNodeDefn ( "pop", function(talkSession, scope, talkNode) { var scope = scope.parent; talkSession.scopeCurrent = scope; scope.talkNodeCurrent = talkSession.defn.talkNodeByLabel ( talkNode.parameters[0] ); talkSession.update(); } ); this.Program = new TalkNodeDefn ( "program", function(talkSession, scope, talkNode) { var parameters = talkNode.parameters; var variableNameForReturnValue = parameters[0]; var programAsText = parameters[1]; var program = new Function ( "talkSession", "scope", "talkNode", programAsText ); var returnValue = program(talkSession, scope, talkNode); talkSession.variableLookup[variableNameForReturnValue] = returnValue; scope.talkNodeAdvance(talkSession); talkSession.update(); } ); this.Prompt = new TalkNodeDefn ( "prompt", function(talkSession, scope, talkNode) { var optionSelected = talkSession.optionSelected; if (optionSelected == null) { scope.areOptionsVisible = true; } else { talkSession.log.push(talkSession.factions[0].name + ":" + optionSelected.parameters[1]); talkSession.optionSelected = null; var nameOfTalkNodeNext = optionSelected.parameters[0]; var talkNodeNext = talkSession.defn.talkNodeByLabel(nameOfTalkNodeNext); scope.talkNodeCurrent = talkNodeNext; talkSession.update(); } } ); this.Push = new TalkNodeDefn ( "push", function(talkSession, scope, talkNode) { var runDefn = talkSession.defn; var talkNodeIndex = runDefn.talkNodes.indexOf(talkNode); var talkNodeNext = runDefn.talkNodes[talkNodeIndex + 1]; talkSession.scopeCurrent = new TalkScope ( scope, // parent talkNodeNext, [] // options ); talkSession.update(); } ); this.Quit = new TalkNodeDefn ( "quit", function(talkSession, scope, talkNode) { talkSession.isTerminated = true; // todo } ); this.Set = new TalkNodeDefn ( "set", function(talkSession, scope, talkNode) { var parameters = talkNode.parameters; var variableName = parameters[0]; var variableValueToSet = parameters[1]; if (variableValueToSet == "null") { variableValueToSet = null; } talkSession.variableLookup[variableName] = variableValueToSet; scope.talkNodeAdvance(talkSession); talkSession.update(); } ); this._All = [ this.Display, this.Goto, this.IfGoto, this.Label, this.Option, this.OptionsClear, this.Pop, this.Program, this.Prompt, this.Push, this.Set, this.Quit, ]; this._All.addLookups("name"); } } function TalkSession(defn, factions) { this.defn = defn; this.factions = factions; var talkNodeStart = this.defn.talkNodes[0]; this.scopeCurrent = new TalkScope ( null, // parent talkNodeStart, // talkNodesForOptions [] ); this.variableLookup = []; this.isTerminated = false; this.log = []; } { // static methods TalkSession.buildExample = function(factionActing, factionReceiving) { var factions = [factionActing, factionReceiving]; var talkNodesAsStrings = [ "program 'RelationsState' ' return talkSession.factions[0].relationships[talkSession.factions[1].name].state; ' ", "set FactionName '" + factionReceiving.name + "' ", "display 'Greetings. We are the $FactionName$.' ", "label Subject", "display 'What do you want to talk about?' ", "label SubjectOptions", "optionsClear", "ifGoto RelationsState Peace SubjectOptions.PeaceOrAlliance ", "ifGoto RelationsState Alliance SubjectOptions.PeaceOrAlliance ", "option OfferPeace 'War is waste of everyone\"s resources. Let there be peace between us.' ", "goto SubjectOptions.End", "label SubjectOptions.PeaceOrAlliance", "ifGoto RelationsState Alliance SubjectOptions.Alliance", "option DeclareWar 'Your abuses have become intolerable. We hereby declare war on you.' ", "option ProposeAlliance 'Would you consider joining an alliance against our common enemies?' ", "goto SubjectOptions.End", "label SubjectOptions.Alliance", "option 'TradeInfo' 'As allies, would you like to trade information with us?' ", "option 'CancelAlliance' 'We regret to say that we can no longer participate in this alliance.' ", "label SubjectOptions.End", "option Quit 'That\"s all for now.' ", "label SubjectPrompt", "prompt", "label OfferPeace", "program 'PeaceOfferAccepted' ' return talkSession.factions[1].peaceOfferAcceptFrom(talkSession.factions[0]); ' ", "ifGoto 'PeaceOfferAccepted' true 'OfferPeace.Accepted' ", "label OfferPeace.Accepted", "program 'RelationsState' ' return DiplomaticRelationship.setStateForFactions(talkSession.factions, DiplomaticRelationship.States.Peace); ' ", "display 'We accept your offer of peace with relief.' ", "goto SubjectOptions", "label OfferPeace.Rejected", "display 'Your pathetic begging notwitstanding, this war will continue.' ", "prompt", "label DeclareWar", "program OfferAppeasement ' return talkSession.factions[1].warThreatOfferConcessionsTo(talkSession.factions[0]); ' ", "ifGoto OfferAppeasement null DeclareWar.Bargain ", "label DeclareWar.Acquiesce", "program RelationsState ' return DiplomaticRelationship.setStateForFactions(talkSession.factions, DiplomaticRelationship.States.War); ' ", "display 'War it is, then. You will regret this unprovoked belligerence.' ", "goto SubjectOptions", "label DeclareWar.Bargain", "push", "display 'Let us not be rash. How can we change your mind?' ", "option DeclareWar.Bargain.Tech 'We demand technological secrets.' ", "option DeclareWar.Bargain.Territory 'We demand that you cede starsystems to us.' ", "option DeclareWar.Bargain.Refuse 'There is nothing you can say that will blunt our resolve.' ", "prompt", "label DeclareWar.Bargain.Tech", "display 'Our secrets are our own, and shall remain so.' ", "prompt", "label DeclareWar.Bargain.Territory", "display 'You would threaten our territorial integrity? We will never agree to this.' ", "prompt", "label DeclareWar.Bargain.Refuse", "display 'If you refuse to be even slightly reasonable about it...' ", "pop DeclareWar.Acquiesce", "label ProposeAlliance", "program AcceptAlliance ' return talkSession.factions[1].allianceProposalAcceptFrom(talkSession.factions[0]); ' ", "ifGoto AcceptAllance false ProposeAlliance.Reject", "label ProposeAlliance.Accept", "program RelationsState ' return DiplomaticRelationship.setStateForFactions(talkSession.factions, DiplomaticRelationship.States.Alliance); ' ", "display 'Yes, the universe is a dangerous place. Let us join forces.' ", "goto SubjectOptions", "label ProposeAlliance.Reject", "display 'While we value your friendship, we wish to avoid such entanglements for now.' ", "prompt", "label CancelAlliance", "program RelationsState ' return DiplomaticRelationship.setStateForFactions(talkSession.factions, DiplomaticRelationship.States.Peace); ' ", "display 'This is unfortunate news. We shall miss being your comrades.' ", "goto SubjectOptions", "label TradeInfo", "push", "display 'It depends on what you have to offer.' ", "display 'What kind of information are you interested in trading?' ", "option TradeInfo.Tech 'Let\"s trade technology.' ", "option TradeInfo.Maps 'Let\"s compare maps.' ", "option TradeInfo.Factions 'Can you offer any information on other factions?' ", "option TradeInfo.Exit 'That\"s all we\"d like to trade for now.' ", "prompt", "label TradeInfo.Factions", "display 'We embrace this opportunity to better know our neighbors.' ", "prompt", "label TradeInfo.Maps", "display 'Yes, we would like to know what you know of this space we share.' ", "prompt", "label TradeInfo.Tech", "display 'The free exchange of scientific ideas enriches us both.' ", "prompt", "label TradeInfo.Exit", "display 'Very well.' ", "pop Subject ", "label Quit", "display 'Until next time...' ", "display '[This conversation is over.]' ", "quit", ]; var returnValue = TalkSession.fromStrings ( factions, talkNodesAsStrings ); return returnValue; } TalkSession.fromStrings = function(factions, talkNodesAsStrings) { var talkNodes = []; for (var i = 0; i < talkNodesAsStrings.length; i++) { var talkNodeAsString = talkNodesAsStrings[i]; var talkNode = TalkNode.fromString(talkNodeAsString); talkNodes.push(talkNode); } var talkDefn = new TalkDefn ( "TalkDefn", talkNodes ); var talkSession = new TalkSession ( talkDefn, factions ); return talkSession; } // instance methods TalkSession.prototype.displayTextCurrent = function() { return this.scopeCurrent.displayTextCurrent; } TalkSession.prototype.hasResponseBeenSpecified = function() { var returnValue = ( (this.optionsAvailable().length == 0) || (this.optionSelected != null) ); return returnValue; } TalkSession.prototype.optionsAvailable = function() { var returnValues = (this.scopeCurrent.areOptionsVisible ? this.scopeCurrent.talkNodesForOptions : []); return returnValues; } TalkSession.prototype.respond = function() { this.update(); } TalkSession.prototype.start = function() { document.body.appendChild(this.htmlElementBuild()); //this.update(); } TalkSession.prototype.update = function() { this.scopeCurrent.update(this); } } function TalkScope(parent, talkNodeCurrent, talkNodesForOptions) { this.parent = parent; this.talkNodeCurrent = talkNodeCurrent; this.talkNodesForOptions = talkNodesForOptions; this.displayTextCurrent = ""; this.areOptionsVisible = false; } { TalkScope.prototype.talkNodeAdvance = function(talkSession) { var talkNodeIndex = talkSession.defn.talkNodes.indexOf(this.talkNodeCurrent); var talkNodeNext = talkSession.defn.talkNodes[talkNodeIndex + 1]; this.talkNodeCurrent = talkNodeNext; } TalkScope.prototype.update = function(talkSession) { this.talkNodeCurrent.execute(talkSession, this); } } function Technology ( name, researchRequired, namesOfPrerequisiteTechnologies, namesOfBuildablesEnabled ) { this.name = name; this.researchRequired = researchRequired; this.namesOfPrerequisiteTechnologies = namesOfPrerequisiteTechnologies; this.namesOfBuildablesEnabled = namesOfBuildablesEnabled; } function TechnologyResearcher ( name, nameOfTechnologyBeingResearched, researchAccumulated, namesOfTechnologiesKnown ) { this.name = name; this.nameOfTechnologyBeingResearched = nameOfTechnologyBeingResearched; this.researchAccumulated = researchAccumulated; this.namesOfTechnologiesKnown = namesOfTechnologiesKnown; } { TechnologyResearcher.prototype.buildablesAvailable = function() { var returnValues = []; return returnValues; } TechnologyResearcher.prototype.strength = function() { var returnValue = 0; var technologiesKnown = this.technologiesKnown(); for (var i = 0; i < technologiesKnown.length; i++) { var tech = technologiesKnown[i]; returnValue += tech.strength(); } return returnValue; } TechnologyResearcher.prototype.technologiesAvailable = function() { var technologyTree = Globals.Instance.universe.technologyTree; var technologies = technologyTree.technologies; var technologiesKnown = this.namesOfTechnologiesKnown; var technologiesUnknown = []; for (var i = 0; i < technologies.length; i++) { var technology = technologies[i]; var technologyName = technology.name; var isAlreadyKnown = (technologiesKnown.indexOf(technologyName) >= 0); if (isAlreadyKnown == false) { technologiesUnknown.push(technology); } } var technologiesUnknownWithKnownPrerequisites = []; for (var i = 0; i < technologiesUnknown.length; i++) { var technology = technologiesUnknown[i]; var prerequisites = technology.namesOfPrerequisiteTechnologies; var areAllPrerequisitesKnown = true; for (var p = 0; p < prerequisites.length; p++) { var prerequisite = prerequisites[p]; var isPrerequisiteKnown = ( technologiesKnown.indexOf(prerequisite) >= 0 ); if (isPrerequisiteKnown == false) { areAllPrerequisitesKnown = false; break; } } if (areAllPrerequisitesKnown == true) { technologiesUnknownWithKnownPrerequisites.push ( technology ); } } return technologiesUnknownWithKnownPrerequisites; } TechnologyResearcher.prototype.technologiesKnown = function() { var returnValues = []; for (var i = 0; i < this.namesOfTechnologiesKnown.length; i++) { var techName = this.namesOfTechnologiesKnown[i]; var technology = Globals.Instance.universe.technologyTree.technologies[techName]; returnValues.push(technology); } return returnValues; } TechnologyResearcher.prototype.technologyAccumulatedIncrement = function() { var technologyBeingResearched = this.technologyBeingResearched(); this.researchAccumulated += amountToIncrement; if (this.researchAccumulated >= technologyBeingResearched.researchRequired) { this.namesOfTechnologiesKnown.push ( this.nameOfTechnologyBeingResearched ); this.nameOfTechnologyBeingResearched = null; this.researchAccumulated = 0; } } TechnologyResearcher.prototype.technologyBeingResearched = function() { var technologyTree = Globals.Instance.universe.technologyTree; var returnValue = technologyTree.technologies[this.nameOfTechnologyBeingResearched]; return returnValue; } // turns TechnologyResearcher.prototype.updateForTurn = function() { var todo = 1; } } function TechnologyResearchSession(technologyTree, researcher) { this.technologyTree = technologyTree; this.researcher = researcher; } { // static methods TechnologyResearchSession.buildExample = function() { var technologies = [ new Technology("A", 5, [], []), new Technology("A.1", 8, ["A"], []), new Technology("A.2", 8, ["A"], []), new Technology("A.3", 8, ["A"], []), new Technology("B", 5, [], []), new Technology("C", 5, [], []), new Technology("A+B", 10, ["A", "B"], []), new Technology("A+C", 10, ["A", "C"], []), new Technology("B+C", 10, ["B", "C"], []), new Technology("A+B+C", 15, ["A", "B", "C"], []), new Technology("(A+B)+(B+C)", 20, ["A+B", "B+C"], []), ]; var technologyTree = new TechnologyTree ( "All Technologies", technologies ); var researcher = new TechnologyResearcher ( "Researcher0", null, // nameOfTechnologyBeingResearched, 0, // researchAccumulated // namesOfTechnologiesKnown [ "A", ] ); var researchSession = new TechnologyResearchSession ( technologyTree, researcher ); return researchSession; } // instance methods TechnologyResearchSession.prototype.isResearchNotInProgress = function() { var returnValue = (this.researcher.researchAccumulated == 0); return returnValue; } TechnologyResearchSession.prototype.isTechnologyBeingResearched = function() { return (this.researcher.nameOfTechnologyBeingResearched != null); } TechnologyResearchSession.prototype.researchAccumulatedIncrement = function ( amountToIncrement ) { this.researcher.researchAccumulatedIncrement(); } TechnologyResearchSession.prototype.researchRequired = function() { var technologyBeingResearched = this.technologyBeingResearched(); var returnValue = ( technologyBeingResearched == null ? 0 : technologyBeingResearched.researchRequired ); return returnValue; } TechnologyResearchSession.prototype.technologyBeingResearched = function() { var techName = this.researcher.nameOfTechnologyBeingResearched; var returnValue = this.technologyTree.technologies[techName]; return returnValue; } // controls TechnologyResearchSession.prototype.controlBuild = function(size) { var margin = 10; var returnValue = new ControlContainer ( "containerResearchSession", // name, [ "Gray", "White" ], // colorsForeAndBack, new Coords(0, 0), // pos, size, // children [ new ControlLabel ( "labelResearcher", // name, new Coords(margin, margin), // pos, new Coords(10, 10), // size, false, // isTextCentered, new DataBinding("Researcher:") //text ), new ControlLabel ( "textResearcher", // name, new Coords(70, margin), // pos, new Coords(10, 10), // size, false, // isTextCentered, new DataBinding(this.researcher.name) //text ), new ControlLabel ( "labelTechnologiesKnown", // name, new Coords(margin, 30), // pos, new Coords(10, 10), // size, false, // isTextCentered, new DataBinding("Technologies Known:") //text ), new ControlSelect ( "listTechnologiesKnown", new Coords(margin, 45), // pos new Coords(110, 50), // size null, // dataBindingForValueSelected // options new DataBinding ( this.researcher.namesOfTechnologiesKnown, null ), null, // optionvalues, null, // optionText, new DataBinding(true), // isEnabled 4 // itemsVisible ), new ControlLabel ( "labelTechnologiesAvailable", // name, new Coords(140, 30), // pos, new Coords(10, 10), // size, false, // isTextCentered, new DataBinding("Technologies Available:") // text ), new ControlSelect ( "listTechnologiesAvailable", // name, new Coords(140, 45), // pos, new Coords(110, 50), // size, // dataBindingForValueSelected, new DataBinding ( this.researcher, "nameOfTechnologyBeingResearched" ), // dataBindingForOptions, new DataBinding ( this.researcher, "technologiesAvailable" ), "name", // bindingExpressionForOptionValues, "name", // bindingExpressionForOptionText // dataBindingForIsEnabled new DataBinding(this, "isResearchNotInProgress"), 4 // numberOfItemsVisible ), new ControlLabel ( "labelTechnologyBeingResearched", // name, new Coords(margin, 120), // pos, new Coords(10, 10), // size, false, // isTextCentered, new DataBinding("Technology Being Researched:") // text ), new ControlLabel ( "textTechnologyBeingResearched", // name, new Coords(160, 120), // pos, new Coords(10, 10), // size, false, // isTextCentered, new DataBinding ( this.researcher, "nameOfTechnologyBeingResearched" ) ), new ControlLabel ( "labelResearchAccumulated", // name, new Coords(10, 135), // pos, new Coords(10, 10), // size, false, // isTextCentered, new DataBinding("Research Accumulated:") // text ), new ControlLabel ( "textResearchAccumulated", // name, new Coords(120, 135), // pos, new Coords(30, 10), // size, true, // isTextCentered, new DataBinding(this.researcher, "researchAccumulated") // text ), new ControlLabel ( "labelSlash", // name, new Coords(130, 135), // pos, new Coords(30, 10), // size, true, // isTextCentered, new DataBinding("/") // text ), new ControlLabel ( "textResearchRequired", // name, new Coords(140, 135), // pos, new Coords(30, 10), // size, true, // isTextCentered, new DataBinding(this, "researchRequired") // text ), new ControlButton ( "buttonResearchPlusOne", //name, new Coords(10, 155), //pos, new Coords(100, 25), // size, "Research + 1", // text, new DataBinding ( this, "isTechnologyBeingResearched" ), // dataBindingForIsEnabled // click function() { var universe = Globals.Instance.universe; var session = universe.venueCurrent.researchSession; session.researchAccumulatedIncrement(1); } ) ] ); this.control = returnValue; return returnValue; } } function TechnologyTree(name, technologies) { this.name = name; this.technologies = technologies; for (var i = 0; i < this.technologies.length; i++) { var technology = this.technologies[i]; this.technologies[technology.name] = technology; } } { TechnologyTree.buildExample = function() { var returnValue = new TechnologyTree ( "All Technologies", // technologies [ new Technology("A", 5, []), new Technology("A.1", 8, ["A"]), new Technology("A.2", 8, ["A"]), new Technology("A.3", 8, ["A"]), new Technology("B", 5, []), new Technology("C", 5, []), new Technology("A+B", 10, ["A", "B"]), new Technology("A+C", 10, ["A", "C"]), new Technology("B+C", 10, ["B", "C"]), new Technology("A+B+C", 15, ["A", "B", "C"]), new Technology("(A+B)+(B+C)", 20, ["A+B", "B+C"]), ] ); return returnValue; } } function Universe(name, activityDefns, technologyTree, world) { this.name = name; this.activityDefns = activityDefns; this.technologyTree = technologyTree; this.world = world; this.activityDefns.addLookups("name"); this.venueNext = null; } { // static methods Universe.new = function(world) { var technologyTree = TechnologyTree.buildExample(); var returnValue = new Universe ( "Universe0", ActivityDefn.Instances._All, technologyTree, world, // venues [ // none ] ); return returnValue; } // instance methods Universe.prototype.initialize = function() { var venueControlsTitle = new VenueControls ( ControlBuilder.title() ); venueControlsTitle = new VenueFader ( venueControlsTitle, venueControlsTitle ); this.venueNext = venueControlsTitle; } Universe.prototype.isProfileSelected = function() { return (this.profile != null); } Universe.prototype.isWorldSelected = function() { return (this.world != null); } Universe.prototype.updateForTimerTick = function() { if (this.venueNext != null) { if ( this.venueCurrent != null && this.venueCurrent.finalize != null ) { this.venueCurrent.finalize(); } this.venueCurrent = this.venueNext; this.venueNext = null; if (this.venueCurrent.initialize != null) { this.venueCurrent.initialize(); } } this.venueCurrent.updateForTimerTick(); this.venueCurrent.draw(); } } function VenueControls(controlRoot, venueParent) { this.controlRoot = controlRoot; this.venueParent = venueParent; } { VenueControls.prototype.draw = function() { this.controlRoot.draw(Coords.Instances.Zeroes); } VenueControls.prototype.initialize = function() { // do nothing } VenueControls.prototype.updateForTimerTick = function() { this.draw(); var inputHelper = Globals.Instance.inputHelper; if (inputHelper.isMouseLeftPressed == true) { var mouseClickPos = inputHelper.mouseClickPos; this.controlRoot.mouseClick(mouseClickPos, Coords.Instances.Zeroes); // inputHelper.isMouseLeftPressed = false; } else if (inputHelper.keyCodePressed != null) { var keyCodePressed = inputHelper.keyCodePressed; var isShiftKeyPressed = inputHelper.isShiftKeyPressed; this.controlRoot.keyPressed ( keyCodePressed, isShiftKeyPressed ); } var mouseMovePos = inputHelper.mouseMovePos; var mouseMovePosPrev = inputHelper.mouseMovePosPrev; if (mouseMovePos.equals(mouseMovePosPrev) == false) { this.controlRoot.mouseMove ( mouseMovePos, Coords.Instances.Zeroes ); } } } function VenueFader ( venueToFadeTo, venueToFadeFrom, millisecondsPerFade ) { this.millisecondsPerFade = millisecondsPerFade; if (this.millisecondsPerFade == null) { this.millisecondsPerFade = 250; } if (venueToFadeFrom == null) { venueToFadeFrom = Globals.Instance.universe.venueCurrent; } this.venuesToFadeFromAndTo = [ venueToFadeFrom, venueToFadeTo ]; if (venueToFadeFrom == venueToFadeTo) { this.venueIndexCurrent = 1; this.millisecondsPerFade *= 2; } else { this.venueIndexCurrent = 0; } } { VenueFader.prototype.draw = function() { var venueCurrent = this.venuesToFadeFromAndTo[this.venueIndexCurrent]; venueCurrent.draw(); var now = new Date(); if (this.timeFadeStarted == null) { this.timeFadeStarted = now; } var millisecondsSinceFadeStarted = now - this.timeFadeStarted; var fractionOfFadeCompleted = millisecondsSinceFadeStarted / this.millisecondsPerFade; var alphaOfFadeColor; if (this.venueIndexCurrent == 0) { if (fractionOfFadeCompleted > 1) { fractionOfFadeCompleted = 1; this.venueIndexCurrent++; this.timeFadeStarted = null; var venueToFadeTo = this.venuesToFadeFromAndTo[1]; if (venueToFadeTo.draw == null) { Globals.Instance.universe.venueNext = venueToFadeTo; } } alphaOfFadeColor = fractionOfFadeCompleted; } else // this.venueIndexCurrent == 1 { if (fractionOfFadeCompleted > 1) { fractionOfFadeCompleted = 1; Globals.Instance.universe.venueNext = venueCurrent; } alphaOfFadeColor = 1 - fractionOfFadeCompleted; } alphaOfFadeColor *= alphaOfFadeColor; var displayHelper = Globals.Instance.displayHelper; displayHelper.drawRectangle ( new Coords(0, 0), displayHelper.viewSize, null, "rgba(0, 0, 0, " + alphaOfFadeColor + ")" ); } VenueFader.prototype.initialize = function() { var venueToFadeTo = this.venuesToFadeFromAndTo[1]; venueToFadeTo.initialize(); } VenueFader.prototype.updateForTimerTick = function() { // do nothing } } function VenueLayout(venueParent, layout) { this.venueParent = venueParent; this.layout = layout; this.layoutElementInProgress = null; } { VenueLayout.prototype.draw = function() { Globals.Instance.displayHelper.drawLayout(this.layout); this.venueControls.draw(); } VenueLayout.prototype.initialize = function() { var controlRoot = this.controlBuild(); this.venueControls = new VenueControls(controlRoot); } VenueLayout.prototype.updateForTimerTick = function() { this.venueControls.updateForTimerTick(); var inputHelper = Globals.Instance.inputHelper; var layout = this.layout; var map = layout.map; var cursor = map.cursor; var cursorPos = cursor.pos; cursorPos.overwriteWith ( inputHelper.mouseMovePos ).subtract ( map.pos ).divide ( map.cellSizeInPixels ).floor(); if (cursorPos.isWithinRangeMax(map.sizeInCellsMinusOnes) == true) { if (inputHelper.isMouseLeftPressed == true) { inputHelper.isMouseLeftPressed = false; var cursorBodyDefn = cursor.bodyDefn; var cellAtCursor = map.cellAtPos(cursorPos); if (cursorBodyDefn == null) { var bodyToRemove = cellAtCursor.body; if (bodyToRemove != null) { layout.elementRemove(bodyToRemove); if (bodyToRemove == this.layoutElementInProgress) { this.layoutElementInProgress = null; } } } else { if (this.layoutElementInProgress != null) { layout.elementRemove ( this.layoutElementInProgress ); } this.layoutElementInProgress = new LayoutElement ( cursorBodyDefn, cursorPos.clone() ); layout.elementAdd(this.layoutElementInProgress); } } else if (inputHelper.keyPressed == 13) // return { inputHelper.keyPressed = null; var cursorBodyDefnPrev = cursor.body; var bodyDefns = layout.bodyDefns; if (cursorBodyDefnPrev == null) { cursor.bodyDefn = bodyDefns[0]; } else { var indexOfCursorBodyDefn = bodyDefns.indexOf ( cursorBodyDefnPrev ); indexOfCursorBodyDefn++; if (indexOfCursorBodyDefn < bodyDefns.length) { cursor.bodyDefn = bodyDefns[indexOfCursorBodyDefn]; } else { cursor.bodyDefn = null; } } } } this.draw(); } // controls VenueLayout.prototype.controlBuild = function() { var returnValue = null; var containerMainSize = Globals.Instance.displayHelper.viewSize.clone(); var controlHeight = 16; var buttonWidth = 30; var margin = 10; var containerInnerSize = new Coords(100, 60); var faction = Globals.Instance.universe.world.factionCurrent(); var returnValue = new ControlContainer ( "containerMain", [ "Transparent", "Transparent" ], // colorsForeAndBack new Coords(0, 0), // pos containerMainSize, // children [ new ControlButton ( "buttonMenu", new Coords ( (containerMainSize.x - buttonWidth) / 2, containerMainSize.y - margin - controlHeight ), // pos new Coords(buttonWidth, controlHeight), // size "Back", null, // dataBindingForIsEnabled // click function() { var universe = Globals.Instance.universe; var venueNext = universe.venueCurrent.venueParent; venueNext = new VenueFader(venueNext); universe.venueNext = venueNext; } ), faction.controlBuild ( containerMainSize, containerInnerSize, margin, controlHeight, buttonWidth ), this.controlBuild_Industry ( containerMainSize, containerInnerSize, margin, controlHeight, buttonWidth ), ControlBuilder.timeAndPlace ( containerMainSize, containerInnerSize, margin, controlHeight ), ControlBuilder.selection ( new Coords ( containerMainSize.x - margin - containerInnerSize.x, containerMainSize.y - margin - containerInnerSize.y ), containerInnerSize, margin, controlHeight ), ] ); return returnValue; } VenueLayout.prototype.controlBuild_Industry = function ( containerMainSize, containerInnerSize, margin, controlHeight, buttonWidth ) { var returnValue = new ControlContainer ( "containerViewControls", ControlBuilder.ColorsForeAndBackDefault, new Coords ( margin, containerMainSize.y - margin - containerInnerSize.y ), containerInnerSize, // children [ new ControlLabel ( "labelBuilding", new Coords(margin, margin), // pos new Coords(0, 0), // size false, // isTextCentered new DataBinding("Building:") ), new ControlButton ( "buttonRemove", new Coords(containerInnerSize.x - margin - buttonWidth, margin / 2), // pos new Coords ( buttonWidth, controlHeight ), // size "X", null, // dataBindingForIsEnabled // click function () { var universe = Globals.Instance.universe; var venueCurrent = universe.venueCurrent; venueCurrent.layout.map.cursor.bodyDefn = null; } ), new ControlSelect ( "selectBuilding", new Coords(margin, controlHeight + margin), // pos new Coords(containerInnerSize.x - margin * 2, controlHeight), // size, new DataBinding(Globals.Instance.universe.venueCurrent, "layout.map.cursor.bodyDefn" ), // dataBindingForValueSelected, new DataBinding(this.layout.bodyDefns), // dataBindingForOptions, null, // bindingExpressionForOptionValues, "name", // bindingExpressionForOptionText, new DataBinding(true), // dataBindingForIsEnabled, 1 // numberOfItemsVisible ), new ControlLabel ( "labelProgress", new Coords(margin, controlHeight * 2 + margin), // pos new Coords(50, controlHeight), // size false, // isTextCentered new DataBinding("[progress]") ), new ControlLabel ( "labelRequired", new Coords(margin + 50, controlHeight * 2 + margin), // pos new Coords(50, controlHeight), // size false, // isTextCentered new DataBinding("[required]") ), ] ); return returnValue; } } function VenueStarsystem(starsystem) { this.starsystem = starsystem; this.model = this.starsystem; this.venueControls = new VenueControls ( this.controlBuild() ); } { VenueStarsystem.prototype.cursorBuild = function() { var ship = this.selection; var cursor = new Cursor ( ship, false, false ); this.cursor = cursor; this.bodies.push(cursor); this.selection = cursor; } VenueStarsystem.prototype.cursorClear = function() { var bodyIndexOfCursor = this.bodies.indexOf(this.cursor); this.bodies.splice(bodyIndexOfCursor, 1); this.selection = this.cursor.bodyParent; this.cursor = null; } VenueStarsystem.prototype.draw = function() { var displayHelper = Globals.Instance.displayHelper; displayHelper.clear(); displayHelper.drawStarsystemForCamera ( this.starsystem, this.camera ); if (this.cursor != null) { displayHelper.drawStarsystemForCamera_Body ( this.camera, this.cursor ); } this.venueControls.draw(); } VenueStarsystem.prototype.finalize = function() { Globals.Instance.soundHelper.soundForMusic.pause(); } VenueStarsystem.prototype.initialize = function() { var starsystem = this.starsystem; var soundHelper = Globals.Instance.soundHelper; soundHelper.soundWithNamePlayAsMusic("Music"); var viewSize = Globals.Instance.displayHelper.viewSize.clone(); var focalLength = viewSize.y; viewSize.z = focalLength * 4; this.camera = new Camera ( viewSize, focalLength, new Coords(0 - focalLength, 0, 0), //pos, new Orientation ( new Coords(1, 0, 0), // forward new Coords(0, 0, 1) // down ) ); var targetForCamera = new Coords(0, 0, 0); this.camera.constraints = []; this.camera.constraints.push ( new Constraint_PositionOnCylinder ( targetForCamera, // center new Orientation ( new Coords(1, 0, 0), new Coords(0, 0, 1) // axis ), 0, // yaw this.camera.focalLength, // radius 0 - this.camera.focalLength / 2 // distanceFromCenterAlongAxisMax ) ); this.camera.constraints.push ( new Constraint_LookAtBody ( targetForCamera ) ); this.camera.constraints.addLookups("name"); this.bodies = []; this.bodies.push(starsystem.star); this.bodies = this.bodies.concat(starsystem.linkPortals); this.bodies = this.bodies.concat(starsystem.planets); this.bodies = this.bodies.concat(starsystem.ships); } VenueStarsystem.prototype.updateForTimerTick = function() { this.venueControls.updateForTimerTick(); var camera = this.camera; var cameraConstraints = camera.constraints; for (var i = 0; i < cameraConstraints.length; i++) { var constraint = cameraConstraints[i]; constraint.applyToBody(camera); } if (this.cursor != null) { var constraints = this.cursor.constraints; for (var i = 0; i < constraints.length; i++) { var constraint = constraints[i]; constraint.applyToBody(this.cursor); } } var bodies = this.starsystem.ships; for (var i = 0; i < bodies.length; i++) { var body = bodies[i]; var bodyDefnName = body.defn.name; if (bodyDefnName == "Ship") { var ship = body; var shipOrder = ship.order; if (shipOrder != null) { shipOrder.obey(ship); } var shipActivity = ship.activity; if (shipActivity != null) { shipActivity.perform(ship); } } } this.draw(); var inputHelper = Globals.Instance.inputHelper; var keyCode = inputHelper.keyCodePressed; if (keyCode == 65) // A { new Action_CylinderMove_Yaw(-.01).perform(camera); } else if (keyCode == 68) // D { new Action_CylinderMove_Yaw(.01).perform(camera); } else if (keyCode == 70) // F { new Action_CylinderMove_DistanceAlongAxis(10).perform(camera); } else if (keyCode == 82) // R { new Action_CylinderMove_DistanceAlongAxis(-10).perform(camera); } else if (keyCode == 83) // S { new Action_CylinderMove_Radius(10).perform(camera); } else if (keyCode == 87) // W { new Action_CylinderMove_Radius(-10).perform(camera); } else if (inputHelper.isMouseLeftPressed == true) { inputHelper.isMouseLeftPressed = false; Globals.Instance.soundHelper.soundWithNamePlayAsEffect("Click"); var mouseClickPos = inputHelper.mouseClickPos.clone().subtract ( camera.viewSizeHalf ); var rayFromCameraThroughClick = camera.rayToViewPos(mouseClickPos); var bodiesClickedAsCollisions = Collision.rayAndBodies ( rayFromCameraThroughClick, this.bodies, 10, // bodyRadius [] ); var bodyClicked; if (bodiesClickedAsCollisions.length == 0) { bodyClicked = null; } else { var bodiesClickedAsCollisionsSorted = []; for (var i = 0; i < bodiesClickedAsCollisions.length; i++) { var collisionToSort = bodiesClickedAsCollisions[i]; var j = 0; for (j = 0; j < bodiesClickedAsCollisionsSorted.length; j++) { var collisionSorted = bodiesClickedAsCollisionsSorted[j]; if (collisionToSort.distance < collisionSorted.distance) { break; } } bodiesClickedAsCollisionsSorted.splice ( j, 0, collisionToSort ); } var numberOfCollisions = bodiesClickedAsCollisionsSorted.length; if (this.selection == null || numberOfCollisions == 1) { bodyClicked = bodiesClickedAsCollisionsSorted[0].colliders[0]; } else { for (var c = 0; c < numberOfCollisions; c++) { var collision = bodiesClickedAsCollisionsSorted[c]; bodyClicked = collision.colliders[0]; if (bodyClicked == this.selection) { var cNext = c + 1; if (cNext >= numberOfCollisions) { cNext = 0; } collision = bodiesClickedAsCollisionsSorted[cNext]; bodyClicked = collision.colliders[0]; break; } } } } if (this.selection == null) { this.selection = bodyClicked; } else { var selectionDefnName = this.selection.defn.name; if (selectionDefnName == "Cursor") { var cursor = this.selection; if (bodyClicked != null && bodyClicked.defn.name != "Cursor") { var targetBody = bodyClicked; var ship = cursor.bodyParent; ship.order = new Order ( "Go", targetBody ); this.cursorClear(); Globals.Instance.inputHelper.isEnabled = false; } else if (cursor.hasXYPositionBeenSpecified == false) { cursor.hasXYPositionBeenSpecified = true; } else if (cursor.hasZPositionBeenSpecified == false) { var targetBody = new Body ( "Target", new BodyDefn ( "MoveTarget", new Coords(0, 0, 0) ), cursor.loc.pos.clone() ); var ship = cursor.bodyParent; ship.order = new Order ( "Go", targetBody ); this.cursorClear(); Globals.Instance.inputHelper.isEnabled = false; } } else if (this.selection == bodyClicked) { if (selectionDefnName == "Planet") { var layout = bodyClicked.layout; var venueNext = new VenueLayout(this, layout); venueNext = new VenueFader(venueNext); Globals.Instance.universe.venueNext = venueNext; } } else { this.selection = bodyClicked; } } } } // controls VenueStarsystem.prototype.controlBuild = function() { var returnValue = null; var containerMainSize = Globals.Instance.displayHelper.viewSize.clone(); var controlHeight = 16; var margin = 10; var containerInnerSize = new Coords(100, 60); var buttonWidth = (containerInnerSize.x - margin * 3) / 2; var returnValue = new ControlContainer ( "containerMain", [ "Transparent", "Transparent" ], // colorsForeAndBack new Coords(0, 0), // pos containerMainSize, // children [ new ControlButton ( "buttonMenu", new Coords ( (containerMainSize.x - buttonWidth) / 2, containerMainSize.y - margin - controlHeight ), // pos new Coords(buttonWidth, controlHeight), // size "Back", null, // dataBindingForIsEnabled // click function() { var universe = Globals.Instance.universe; var world = universe.world; var venueNext = new VenueWorld(world); venueNext = new VenueFader(venueNext); universe.venueNext = venueNext; } ), ControlBuilder.view ( containerMainSize, containerInnerSize, margin, controlHeight ), ControlBuilder.timeAndPlace ( containerMainSize, containerInnerSize, margin, controlHeight ), ControlBuilder.selection ( new Coords ( containerMainSize.x - margin - containerInnerSize.x, margin ), new Coords ( containerInnerSize.x, containerMainSize.y - margin * 2 ), margin, controlHeight ), ] ); return returnValue; } } function VenueTalkSession(venueParent, talkSession) { this.venueParent = venueParent; this.talkSession = talkSession; } { VenueTalkSession.prototype.draw = function() { this.venueControls.draw(); } VenueTalkSession.prototype.initialize = function() { var controlRoot = this.controlBuild(); this.venueControls = new VenueControls(controlRoot); this.talkSession.update(); } VenueTalkSession.prototype.updateForTimerTick = function() { this.venueControls.updateForTimerTick(); } // controls VenueTalkSession.prototype.controlBuild = function() { var containerSize = Globals.Instance.displayHelper.viewSize.clone(); var margin = 10; var controlHeight = 15; var returnValue = new ControlContainer ( "containerConfigure", ControlBuilder.ColorsForeAndBackDefault, new Coords(0, 0), // pos containerSize, // children [ new ControlButton ( "buttonDone", new Coords ( margin, margin ), // pos new Coords ( controlHeight, controlHeight ), // size "<", null, // dataBindingForIsEnabled // click function () { var universe = Globals.Instance.universe; var venueNext = universe.venueCurrent.venueParent; venueNext = new VenueFader(venueNext); universe.venueNext = venueNext; } ), new ControlButton ( "buttonLog", new Coords ( containerSize.x - margin - controlHeight * 2, margin ), // pos new Coords ( controlHeight * 2, controlHeight ), // size "Log", null, // dataBindingForIsEnabled // click function () { var talkSession = Globals.Instance.universe.venueCurrent.talkSession; alert(talkSession.log.join("\n")); } ), new ControlLabel ( "labelTalk", new Coords(margin, controlHeight + margin * 2), // pos new Coords ( containerSize.x - margin * 2, controlHeight ), // size false, // isTextCentered new DataBinding(this.talkSession, "displayTextCurrent") ), new ControlSelect ( "listResponses", new Coords ( margin, controlHeight * 2 + margin * 3 ), // pos new Coords ( containerSize.x - margin * 2, controlHeight * 4 ), // size // dataBindingForValueSelected new DataBinding(this.talkSession, "optionSelected"), // options new DataBinding ( this.talkSession, "optionsAvailable" ), null, // bindingExpressionForOptionValues "text", // bindingExpressionForOptionText new DataBinding(true), // isEnabled 4 // numberOfItemsVisible ), new ControlButton ( "buttonContinue", new Coords ( margin, controlHeight * 6 + margin * 4 ), // pos new Coords ( containerSize.x - margin * 2, controlHeight ), // size "Continue", // dataBindingForIsEnabled new DataBinding(this.talkSession, "hasResponseBeenSpecified"), // click this.talkSession.respond.bind(this.talkSession) ), ] ); return returnValue; } } function VenueTechnologyResearchSession(researchSession) { this.researchSession = researchSession; this.venueControls = new VenueControls ( this.researchSession.controlBuild(Globals.Instance.displayHelper.viewSize) ); } { VenueTechnologyResearchSession.prototype.draw = function() { this.venueControls.draw(); } VenueTechnologyResearchSession.prototype.initialize = function() { // do nothing } VenueTechnologyResearchSession.prototype.updateForTimerTick = function() { this.venueControls.updateForTimerTick(); } } function VenueVideo(videoName, venueNext) { this.videoName = videoName; this.venueNext = venueNext; this.hasVideoBeenStarted = false; } { VenueVideo.prototype.draw = function() { // do nothing } VenueVideo.prototype.initialize = function() { // do nothing } VenueVideo.prototype.updateForTimerTick = function() { if (this.video == null) { Globals.Instance.displayHelper.hide(); this.video = Globals.Instance.videoHelper.videos[this.videoName]; this.video.play(); } var inputHelper = Globals.Instance.inputHelper; if (inputHelper.isMouseLeftPressed == true) { inputHelper.isMouseLeftPressed = false; this.video.stop(); } if (this.video.isFinished == true) { var displayHelper = Globals.Instance.displayHelper; displayHelper.clear("Black"); displayHelper.show(); var universe = Globals.Instance.universe; universe.venueNext = this.venueNext; } } } function VenueWorld(world) { this.world = world; this.model = this.world; this.camera = this.world.camera; this.venueControls = new VenueControls ( this.controlBuild() ); } { // controls VenueWorld.prototype.controlBuild = function() { var returnValue = null; var containerMainSize = Globals.Instance.displayHelper.viewSize.clone(); var controlHeight = 16; var margin = 10; var containerInnerSize = new Coords(100, 60); var buttonWidth = (containerInnerSize.x - margin * 3) / 2; var faction = Globals.Instance.universe.world.factionCurrent(); var returnValue = new ControlContainer ( "containerMain", [ "Transparent", "Transparent" ], // colorsForeAndBack new Coords(0, 0), // pos containerMainSize, // children [ new ControlButton ( "buttonMenu", new Coords ( (containerMainSize.x - buttonWidth) / 2, containerMainSize.y - margin - controlHeight ), // pos new Coords(buttonWidth, controlHeight), // size "Menu", null, // dataBindingForIsEnabled // click function() { var universe = Globals.Instance.universe; var venueNext = new VenueControls ( ControlBuilder.configure() ); venueNext = new VenueFader(venueNext); universe.venueNext = venueNext; } ), faction.controlBuild ( containerMainSize, containerInnerSize, margin, controlHeight, buttonWidth ), ControlBuilder.view ( containerMainSize, containerInnerSize, margin, controlHeight ), ControlBuilder.timeAndPlace ( containerMainSize, containerInnerSize, margin, controlHeight ), ControlBuilder.selection ( new Coords ( containerMainSize.x - margin - containerInnerSize.x, containerMainSize.y - margin - containerInnerSize.y ), containerInnerSize, margin, controlHeight ), ] ); return returnValue; } // venue VenueWorld.prototype.draw = function() { var displayHelper = Globals.Instance.displayHelper; displayHelper.clear(); displayHelper.drawNetworkForCamera ( this.world.network, this.world.camera ); this.venueControls.draw(); } VenueWorld.prototype.finalize = function() { Globals.Instance.soundHelper.soundForMusic.pause(); } VenueWorld.prototype.initialize = function() { var soundHelper = Globals.Instance.soundHelper; soundHelper.soundWithNamePlayAsMusic("Music"); } VenueWorld.prototype.updateForTimerTick = function() { this.venueControls.updateForTimerTick(); var world = this.world; var camera = world.camera; var inputHelper = Globals.Instance.inputHelper; if (inputHelper.isMouseLeftPressed == true) { inputHelper.isMouseLeftPressed = false; Globals.Instance.soundHelper.soundWithNamePlayAsEffect("Click"); var mouseClickPos = inputHelper.mouseClickPos.clone().subtract ( camera.viewSizeHalf ); var rayFromCameraThroughClick = camera.rayToViewPos(mouseClickPos); var bodiesClickedAsCollisions = Collision.rayAndBodies ( rayFromCameraThroughClick, Globals.Instance.universe.world.network.nodes, NetworkNode.RadiusActual, [] // listToAddTo ); if (bodiesClickedAsCollisions.length > 0) { var collisionNearest = bodiesClickedAsCollisions[0]; for (var i = 1; i < bodiesClickedAsCollisions.length; i++) { var collision = bodiesClickedAsCollisions[i]; if (collision.distance < collisionNearest.distance) { collisionNearest = collision; } } var bodyClicked = collisionNearest.colliders[0]; // todo if (bodyClicked == this.selection) { var venueNext = new VenueStarsystem(bodyClicked.starsystem); venueNext = new VenueFader(venueNext); Globals.Instance.universe.venueNext = venueNext; } this.selection = bodyClicked; } } var keyCode = inputHelper.keyCodePressed; if (keyCode == 27) // escape { var universe = Globals.Instance.universe; var venueNext = new VenueControls ( ControlBuilder.configure() ); venueNext = new VenueFader(venueNext); universe.venueNext = venueNext; } var cameraSpeed = 20; var displacementToMoveCamera = null; if (keyCode == 65) // A { displacementToMoveCamera = [cameraSpeed, 0]; } else if (keyCode == 68) // D { displacementToMoveCamera = [0 - cameraSpeed, 0]; } else if (keyCode == 83) // S { displacementToMoveCamera = [0, 0 - cameraSpeed]; } else if (keyCode == 87) // W { displacementToMoveCamera = [0, cameraSpeed]; } if (displacementToMoveCamera != null) { new Action_CameraMove(displacementToMoveCamera).perform(camera); } } } function Video(name, sourcePath) { this.name = name; this.sourcePath = sourcePath; } { Video.prototype.domElementBuild = function() { this.domElement = document.createElement("video"); this.domElement.src = this.sourcePath; this.domElement.video = this; this.domElement.autoplay = true; this.domElement.onended = this.stop.bind(this); var viewSize = Globals.Instance.displayHelper.viewSize; this.domElement.width = viewSize.x; this.domElement.height = viewSize.y; return this.domElement; } Video.prototype.play = function() { this.isFinished = false; Globals.Instance.divMain.appendChild(this.domElementBuild()); } Video.prototype.stop = function(event) { var domElement = (event == null ? this.domElement : event.srcElement); Globals.Instance.divMain.removeChild(domElement); this.isFinished = true; } } function VideoHelper(videos) { this.videos = videos; this.videos.addLookups("name"); } function VisualGroup(visuals) { this.visuals = visuals; } { VisualGroup.prototype.draw = function(drawPos) { for (var i = 0; i < this.visuals.length; i++) { var visual = this.visuals[i]; visual.draw(drawPos); } } } function VisualRectangle(color, size) { this.color = color; this.size = size; this.sizeHalf = this.size.clone().divideScalar(2); } { VisualRectangle.prototype.draw = function(drawPos) { var graphics = Globals.Instance.displayHelper.graphics; graphics.strokeStyle = this.color.systemColor; graphics.beginPath(); graphics.strokeRect ( drawPos.x - this.sizeHalf.x, drawPos.y - this.sizeHalf.y, this.size.x, this.size.y ); graphics.stroke(); } } function VisualSphere(color, radius) { this.color = color; this.radius = radius; } { VisualSphere.prototype.draw = function(drawPos) { var radius = this.radius; var graphics = Globals.Instance.displayHelper.graphics; graphics.strokeStyle = this.color.systemColor; graphics.beginPath(); graphics.arc ( drawPos.x, drawPos.y, radius, 0, 2 * Math.PI, // start and stop angles false // counterClockwise ); graphics.stroke(); } } function VisualText(text) { this.text = text; } { VisualText.prototype.draw = function(drawPos) { var graphics = Globals.Instance.displayHelper.graphics; graphics.fillStyle = "LightGray"; graphics.fillText ( this.text, drawPos.x, drawPos.y ); } } function World(name, network, factions, ships, camera) { this.name = name; this.network = network; this.factions = factions; this.ships = ships; this.camera = camera; this.dateCreated = DateTime.now(); this.dateSaved = this.dateCreated; this.factions.addLookups("name"); this.ships.addLookups("name"); this.turnsSoFar = 0; this.factionIndexCurrent = 0; } { // static methods World.new = function() { var worldName = NameGenerator.generateName() + " Cluster"; var viewSize = Globals.Instance.displayHelper.viewSize.clone(); var viewDimension = viewSize.y; var networkRadius = viewDimension * .35; var numberOfNetworkNodes = 6; // 128; var network = Network.generateRandom ( "Test Network", NetworkNodeDefn.Instances._All, numberOfNetworkNodes, // minAndMaxDistanceOfNodesFromOrigin [ networkRadius / 2, networkRadius ], 20 // distanceBetweenNodesMin ); var focalLength = viewDimension; viewSize.z = focalLength; var numberOfFactions = 6; var factions = []; var ships = []; var colors = Color.Instances; var colorsForFactions = [ colors.Red, colors.Orange, colors.YellowDark, colors.Green, colors.Blue, colors.Violet, ]; for (var i = 0; i < numberOfFactions; i++) { var factionHomeStarsystem = null; var random = Math.random(); var starsystemIndexStart = Math.floor ( random * numberOfNetworkNodes ); var starsystemIndex = starsystemIndexStart; while (factionHomeStarsystem == null) { factionHomeStarsystem = network.nodes[starsystemIndex].starsystem; if (factionHomeStarsystem.planets.length == 0) { factionHomestarsystem = null; } else if (factionHomeStarsystem.factionName != null) { factionHomeStarsystem = null; } starsystemIndex++; if (starsystemIndex >= numberOfNetworkNodes) { starsystemIndex = 0; } if (starsystemIndex == starsystemIndexStart) { throw "There are more factions than starsystems with planets."; } } var factionName = factionHomeStarsystem.name + "ians"; factionHomeStarsystem.factionName = factionName; var factionColor = colorsForFactions[i]; var ship = new Ship ( factionName + " Ship0", Ship.bodyDefnBuild(factionColor), new Coords().randomize().multiply ( factionHomeStarsystem.size ).multiplyScalar ( 2 ).subtract ( factionHomeStarsystem.size ), factionName ); ships.push(ship); factionHomeStarsystem.ships.push(ship); var faction = new Faction ( factionName, factionColor, [], // relationships new TechnologyResearcher ( factionName + "_Research", null, // nameOfTechnologyBeingResearched, 0, // researchAccumulated // namesOfTechnologiesKnown [] ), [ factionHomeStarsystem.planets[0] ], [ ship ], new FactionKnowledge ( [ factionHomeStarsystem ], [ factionHomeStarsystem.links() ] ) ); factions.push(faction); } DiplomaticRelationship.initializeForFactions(factions); var camera = new Camera ( viewSize, focalLength, new Coords(-viewDimension, 0, 0), //pos, new Orientation ( new Coords(1, 0, 0), // forward new Coords(0, 0, 1) // down ) ); var returnValue = new World ( worldName, network, factions, ships, camera ); return returnValue; } // instance methods World.prototype.factionCurrent = function() { return this.factions[this.factionIndexCurrent]; } World.prototype.factionsOtherThanCurrent = function() { var factionCurrent = this.factionCurrent(); var returnValues = this.factions.slice(); returnValues.splice ( this.factionIndexCurrent, 1 ); return returnValues; } World.prototype.updateForTurn = function() { this.network.updateForTurn(); for (var i = 0; i < this.factions.length; i++) { var faction = this.factions[i]; faction.updateForTurn(); } this.turnsSoFar++; } } // run main(); </script> </body> </html>