# 000 000 0000000 00000000 000 0000000 # 000 0 000 000 000 000 000 000 000 000 # 000000000 000 000 0000000 000 000 000 # 000 000 000 000 000 000 000 000 000 # 00 00 0000000 000 000 0000000 0000000 { absMin, randInt, clamp, first, last} = require "/Users/kodi/s/ko/js/tools/tools" log = require "/Users/kodi/s/ko/js/tools/log" Pos = require './lib/pos' Size = require './lib/size' Cell = require './cell' Gate = require './gate' Camera = require './camera' Light = require './light' Levels = require './levels' Player = require './player' Sound = require './sound' Cage = require './cage' Timer = require './timer' Actor = require './actor' Item = require './item' Action = require './action' TmpObject = require './tmpobject' Pushable = require './pushable' Material = require './material' Scheme = require './scheme' Quaternion = require './lib/quaternion' Vector = require './lib/vector' Pos = require './lib/pos' _ = require 'lodash' now = require 'performance-now' { Wire, Gear, Stone, Switch, MotorGear, MotorCylinder, Face} = require './items' world = null class World extends Actor @levels = null @normals = [ new Vector 1, 0, 0 new Vector 0, 1, 0 new Vector 0, 0, 1 new Vector -1,0, 0 new Vector 0,-1, 0 new Vector 0, 0,-1 ] constructor: (@view) -> @speed = 6 @rasterSize = 0.05 super @noRotations = false @screenSize = new Size @view.clientWidth, @view.clientHeight # log "view @screenSize:", @screenSize @renderer = new THREE.WebGLRenderer antialias: true logarithmicDepthBuffer: false autoClear: true sortObjects: true @renderer.setClearColor 0x000000 @renderer.setSize @view.offsetWidth, @view.offsetHeight @renderer.shadowMap.type = THREE.PCFSoftShadowMap # 0000000 0000000 00000000 000 000 00000000 # 000 000 000 0000 000 000 # 0000000 000 0000000 000 0 000 0000000 # 000 000 000 000 0000 000 # 0000000 0000000 00000000 000 000 00000000 @scene = new THREE.Scene() # 000 000 0000000 000 000 000000000 # 000 000 000 000 000 000 # 000 000 000 0000 000000000 000 # 000 000 000 000 000 000 000 # 0000000 000 0000000 000 000 000 @sun = new THREE.PointLight 0xffffff @sun.position.copy @player.camera.getPosition() if @player? @scene.add @sun @ambient = new THREE.AmbientLight 0x111111 @scene.add @ambient @preview = false @objects = [] @lights = [] @cells = [] @size = new Pos() @depth = -Number.MAX_SAFE_INTEGER @deinit: () -> world = null @init: (view) -> return if world? @initGlobal() world = new World view world.name = 'world' global.world = world Timer.init() world.create first @levels.list world @initGlobal: () -> return if @levels? Sound.init() global.log = log global.rot0 = Quaternion.rot_0 global.rotx90 = Quaternion.rot_90_X global.roty90 = Quaternion.rot_90_Y global.rotz90 = Quaternion.rot_90_Z global.rotx180 = Quaternion.rot_180_X global.roty180 = Quaternion.rot_180_Y global.rotz180 = Quaternion.rot_180_Z global.rotx270 = Quaternion.rot_270_X global.roty270 = Quaternion.rot_270_Y global.rotz270 = Quaternion.rot_270_Z global.XupY = Quaternion.XupY global.XupZ = Quaternion.XupZ global.XdownY = Quaternion.XdownY global.XdownZ = Quaternion.XdownZ global.YupX = Quaternion.YupX global.YupZ = Quaternion.YupZ global.YdownX = Quaternion.YdownX global.YdownZ = Quaternion.YdownZ global.ZupX = Quaternion.ZupX global.ZupY = Quaternion.ZupY global.ZdownX = Quaternion.ZdownX global.ZdownY = Quaternion.ZdownY global.minusXupY = Quaternion.minusXupY global.minusXupZ = Quaternion.minusXupZ global.minusXdownY = Quaternion.minusXdownY global.minusXdownZ = Quaternion.minusXdownZ global.minusYupX = Quaternion.minusYupX global.minusYupZ = Quaternion.minusYupZ global.minusYdownX = Quaternion.minusYdownX global.minusYdownZ = Quaternion.minusYdownZ global.minusZupX = Quaternion.minusZupX global.minusZupY = Quaternion.minusZupY global.minusZdownX = Quaternion.minusZdownX global.minusZdownY = Quaternion.minusZdownY @levels = new Levels # 0000000 00000000 00000000 0000000 000000000 00000000 # 000 000 000 000 000 000 000 000 # 000 0000000 0000000 000000000 000 0000000 # 000 000 000 000 000 000 000 000 # 0000000 000 000 00000000 000 000 000 00000000 create: (worldDict={}) -> # creates the world from a level name or a dictionary # log "world.create", worldDict if worldDict if _.isString worldDict @level_name = worldDict @dict = World.levels.dict[worldDict] else @level_name = worldDict.name @dict = worldDict @level_index = World.levels.list.indexOf @level_name log "World.create #{@level_index} size: #{new Pos(@dict["size"]).str()} ---------------------- '#{@level_name}' scheme: '#{@dict.scheme ? 'default'}'" @creating = true @setSize @dict["size"] # this removes all objects @applyScheme @dict.scheme ? 'default' # ............................................................ intro text # if "intro" in @dict # if not @preview # intro_text = KikiScreenText() # intro_text.addText @dict["intro"] # intro_text.show() # @setName @dict["intro"] # else # @setName "noname" # ............................................................ escape # escape_event = Controller.getEventWithName ("escape") # escape_event.removeAllActions() # escape_event.addAction(continuous(@escape, "escape")) # ............................................................ exits if @dict.exits? exit_id = 0 for entry in @dict.exits exit_gate = new Gate entry["active"] exit_gate.name = entry["name"] ? "exit #{exit_id}" Action.id ?= 0 exitAction = new Action id: Action.id func: @exitLevel name: "exit #{exit_id}" mode: Action.ONCE exit_gate.getEventWithName("enter").addAction exitAction if entry.position? pos = @decenter entry.position else if entry.coordinates? pos = new Pos entry.coordinates @addObjectAtPos exit_gate, pos exit_id += 1 # ............................................................ creation if @dict.create? if _.isFunction @dict.create @dict.create() else log "World.create [WARNING] @dict.create not a function!" # exec @dict["create"] in globals() # ............................................................ player @player = new Player # log "player_dict", player_dict @player.setOrientation @dict.player.orientation ? rotx90 @player.camera.setOrientation @player.orientation if @dict.player.position? @addObjectAtPos @player, @decenter @dict.player.position else if @dict.player.coordinates? @addObjectAtPos @player, new Pos @dict.player.coordinates @player.camera.setPosition @player.currentPos() @setCameraMode Camera.INSIDE if @dict.camera == 'inside' @creating = false restart: () -> @create @dict finish: () -> # TODO: save progress # 0000000 0000000 000 000 00000000 00 00 00000000 # 000 000 000 000 000 000 000 000 # 0000000 000 000000000 0000000 000000000 0000000 # 000 000 000 000 000 000 0 000 000 # 0000000 0000000 000 000 00000000 000 000 00000000 applyScheme: (scheme) -> return if not Scheme[scheme] # log "world.applyScheme #{scheme}" colors = _.clone Scheme[scheme] opacity = stone: 0.7 bomb: 0.9 shininess = tire: 4 plate: 10 raster: 20 wall: 20 stone: 20 gear: 20 colors.plate.emissive ?= colors.plate.color colors.bulb.emissive ?= colors.bulb.color colors.raster ?= {} colors.raster.color ?= colors.plate.color colors.wall ?= {} colors.wall.color ?= new THREE.Color(colors.plate.color).multiplyScalar 0.6 colors.wirePlate ?= {} colors.wirePlate.color ?= colors.wire.color for k,v of colors # log "#{k} #{v.color?.r} #{v.color?.g} #{v.color?.b}", v continue if k == 'text' mat = Material[k] mat.color = v.color mat.opacity = v.opacity ? opacity[k] ? 1 mat.specular = v.specular ? new THREE.Color(v.color).multiplyScalar 0.2 mat.emissive = v.emissive ? new THREE.Color 0,0,0 if shininess[k]? mat.shininess = v.shininess ? shininess[k] # 000 000 0000000 000 000 000000000 # 000 000 000 000 000 000 # 000 000 000 0000 000000000 000 # 000 000 000 000 000 000 000 # 0000000 000 0000000 000 000 000 addLight: (light) -> @lights.push light @enableShadows true if light.shadow removeLight: (light) -> _.pull @lights, light for l in @lights shadow = true if l.shadow @enableShadows shadow enableShadows: (enable) -> @renderer.shadowMap.enabled = enable # 0000000 0000000 000000000 000 0000000 000 000 # 000 000 000 000 000 000 000 0000 000 # 000000000 000 000 000 000 000 000 0 000 # 000 000 000 000 000 000 000 000 0000 # 000 000 0000000 000 000 0000000 000 000 exitLevel: (action) => @finish() # log "world.level_index #{world.level_index} nextLevel #{World.levels.list[world.level_index+1]}" nextLevel = (world.level_index+(_.isNumber(action) and action or 1)) % World.levels.list.length world.create World.levels.list[nextLevel] activate: (objectName) -> @getObjectWithName(objectName)?.setActive? true decenter: (x,y,z) -> new Pos(x,y,z).plus @size.div 2 isValidPos: (pos) -> p = new Pos pos p.x >= 0 and p.x < @size.x and p.y >= 0 and p.y < @size.y and p.z >= 0 and p.z < @size.z isInvalidPos: (pos) -> not @isValidPos pos # 0000000 00000000 000 000 0000000 # 000 000 000 000 000 # 000 0000000 000 000 0000000 # 000 000 000 000 000 # 0000000 00000000 0000000 0000000 0000000 setSize: (size) -> @deleteAllObjects() @cells = [] @size = new Pos size # calcuate max distance (for position relative sound) @max_distance = Math.max(@size.x, Math.max(@size.y, @size.z)) # heuristic of a heuristic :-) @cage?.del() @cage = new Cage @size, @rasterSize getCellAtPos: (pos) -> return @cells[@posToIndex(pos)] if @isValidPos pos getBotAtPos: (pos) -> @getObjectOfTypeAtPos Bot, new Pos pos posToIndex: (pos) -> p = new Pos pos p.x * @size.z * @size.y + p.y * @size.z + p.z indexToPos: (index) -> lsize = @size.z * @size.y lrest = index % lsize new Pos index/lsize, lrest/@size.z, lrest%@size.z # 0000000 0000000 0000000 0000000 0000000 000 00000000 0000000 000000000 # 000 000 000 000 000 000 000 000 000 000 000 000 000 000 # 000000000 000 000 000 000 000 000 0000000 000 0000000 000 000 # 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 # 000 000 0000000 0000000 0000000 0000000 0000000 00000000 0000000 000 addObjectAtPos: (object, x, y, z) -> pos = new Pos x, y, z object = @newObject object @setObjectAtPos object, pos # log "addObjectAtPos #{object.name}", pos @addObject object addObjectLine: (object, sx,sy,sz, ex,ey,ez) -> # log "world.addObjectLine sx:#{sx} sy:#{sy} sz:#{sz} ex:#{ex} ey:#{ey} ez:#{ez}" if sx instanceof Pos or Array.isArray sx start = sx end = sy else start = new Pos sx,sy,sz end = new Pos ex,ey,ez # adds a line of objects of type to the world. start and end should be 3-tuples or Pos objects if end instanceof Pos end = [end.x, end.y, end.z] [ex, ey, ez] = end if start instanceof Pos start = [start.x, start.y, start.z] [sx, sy, sz] = start # log "world.addObjectLine sx:#{sx} sy:#{sy} sz:#{sz} ex:#{ex} ey:#{ey} ez:#{ez}" diff = [ex-sx, ey-sy, ez-sz] maxdiff = _.max diff.map Math.abs deltas = diff.map (a) -> a/maxdiff for i in [0...maxdiff] # pos = apply(Pos, (map (lambda a, b: int(a+i*b), start, deltas))) pos = new Pos (start[j]+i*deltas[j] for j in [0..2]) # log "addObjectLine #{i}:", pos if @isUnoccupiedPos pos @addObjectAtPos object, pos addObjectPoly: (object, points, close=true) -> # adds a polygon of objects of type to the world. points should be 3-tuples or Pos objects if close points.push points[0] for index in [1...points.length] @addObjectLine object, points[index-1], points[index] addObjectRandom: (object, number) -> # adds number objects of type at random positions to the world for i in [0...number] if _.isString object @setObjectRandom eval object else @setObjectRandom object() setObjectRandom: (object) -> # adds number objects of type at random positions to the world objectSet = false object = @newObject object while not objectSet # hack alert! randomPos = new Pos randInt(@size.x), randInt(@size.y), randInt(@size.z) if not object.isSpaceEgoistic() or @isUnoccupiedPos randomPos @addObjectAtPos object, randomPos objectSet = true # 0000000 0000000 000 00000000 0000000 000000000 0000000 # 000 000 000 000 000 000 000 000 000 # 000 000 0000000 000 0000000 000 000 0000000 # 000 000 000 000 000 000 000 000 000 000 # 0000000 0000000 0000000 00000000 0000000 000 0000000 getObjectsOfType: (clss) -> @objects.filter (o) -> o instanceof clss getObjectsOfTypeAtPos: (clss, pos) -> @getCellAtPos(pos)?.getObjectsOfType(clss) ? [] getObjectOfTypeAtPos: (clss, pos) -> @getCellAtPos(pos)?.getRealObjectOfType(clss) getOccupantAtPos: (pos) -> @getCellAtPos(pos)?.getOccupant() getRealOccupantAtPos: (pos) -> occupant = @getOccupantAtPos pos if occupant and occupant instanceof TmpObject occupant.object else occupant switchAtPos: (pos) -> @getObjectOfTypeAtPos Switch, pos setObjectAtPos: (object, pos) -> pos = new Pos pos if @isInvalidPos pos log "World.setObjectAtPos [WARNING] invalid pos:", pos return if object.isSpaceEgoistic() if cell = @getCellAtPos pos if occupant = cell.getOccupant() if occupant instanceof TmpObject if occupant.time > 0 log "World.setObjectAtPos [WARNING] already occupied pos:", pos log "World.setObjectAtPos [WARNING] already occupied time:", occupant.time occupant.del() # temporary object at new pos will vanish anyway . delete it cell = @getCellAtPos pos if not cell? cellIndex = @posToIndex(pos) cell = new Cell() @cells[cellIndex] = cell object.setPosition pos cell.addObject object unsetObject: (object) -> pos = object.getPos() if cell = @getCellAtPos pos cell.removeObject object if cell.isEmpty() @cells[@posToIndex(pos)] = null else log 'world.unsetObject [WARNING] no cell at pos:', pos newObject: (object) -> if _.isString object if object.startsWith 'new' return eval object return new (require "./#{object.toLowerCase()}")() if object instanceof Item return object else return object() addObject: (object) -> object = @newObject object if object instanceof Light @lights.push object # if lights.indexOf(object) < 0 else @objects.push object # if objects.indexOf(object) < 0 removeObject: (object) -> @unsetObject object _.pull @lights, object _.pull @objects, object moveObjectToPos: (object, pos) -> return false if @isInvalidPos(pos) or @isOccupiedPos(pos) @unsetObject object @setObjectAtPos object, pos world.playSound 'BOT_LAND' true toggle: (objectName) -> object = @getObjectWithName objectName object.getActionWithName("toggle").perform() # 0000000 00000000 000 00000000 000000000 00000000 # 000 000 000 000 000 000 000 # 000 000 0000000 000 0000000 000 0000000 # 000 000 000 000 000 000 000 # 0000000 00000000 0000000 00000000 000 00000000 deleteAllObjects: () -> Timer.removeAllActions() if @player? @player.del() while @lights.length oldSize = @lights.length last(@lights).del() # destructor will call remove object if oldSize == @lights.length log "WARNING World.deleteAllObjects light no auto remove" @lights.pop() while @objects.length oldSize = @objects.length last(@objects).del() # destructor will call remove object if oldSize == @objects.length log "WARNING World.deleteAllObjects object no auto remove #{last(@objects).name}" @objects.pop() deleteObjectsWithClassName: (className) -> for o in _.clone @objects if className == o.getClassName() o.del() getObjectWithName: (objectName) -> for o in @objects if objectName == o.name return o log "World.getObjectWithName [WARNING] no object with name #{objectName}" null setCameraMode: (mode) -> @player.camera.mode = clamp Camera.INSIDE, Camera.FOLLOW, mode changeCameraMode: -> @player.camera.mode = (@player.camera.mode+1) % (Camera.FOLLOW+1) # 0000000 0000000 000 00 00 0000000 000 000 00000000 # 000 000 000 000 000 000 000 000 000 000 000 000 # 000 000 0000000 000 000000000 000 000 000 000 0000000 # 000 000 000 000 000 000 000 0 000 000 000 000 000 # 0000000 0000000 0000000 000 000 0000000 0 00000000 objectWillMoveToPos: (object, pos, duration) -> sourcePos = object.getPos() targetPos = new Pos pos # log "world.objectWillMoveToPos #{object.name} #{duration}", targetPos if @isInvalidPos targetPos log "world.objectWillMoveToPos [WARNING] #{object.name} invalid targetPos:", targetPos return if sourcePos.eql targetPos log "world.objectWillMoveToPos [WARNING] #{object.name} equal pos:", targetPos return targetCell = @getCellAtPos pos if targetCell if objectAtNewPos = targetCell.getOccupant() if objectAtNewPos instanceof TmpObject if objectAtNewPos.time < 0 and -objectAtNewPos.time <= duration # temporary object at new pos will vanish before object will arrive . delete it objectAtNewPos.del() else log "world.objectWillMoveToPos [WARNING] #{object.name} timing conflict at pos:", targetPos else log "world.objectWillMoveToPos [WARNING] #{object.name} already occupied:", targetPos if object.name != 'player' @unsetObject object # remove object from cell grid # log 'world.objectWillMoveToPos tmpObject at old pos', sourcePos tmpObject = new TmpObject object # insert tmp object at old pos tmpObject.setPosition sourcePos tmpObject.time = -duration @addObjectAtPos tmpObject, sourcePos # log 'world.objectWillMoveToPos tmpObject at new pos', targetPos tmpObject = new TmpObject object # insert tmp object at new pos tmpObject.setPosition targetPos tmpObject.time = duration @addObjectAtPos tmpObject, targetPos objectMoved: (movedObject, from, to) -> sourcePos = new Pos from targetPos = new Pos to if @isInvalidPos targetPos log "World.objectMoved [WARNING] #{movedObject.name} invalid targetPos:", targetPos return # log "world.objectMoved #{movedObject.name}", sourcePos sourceCell = @getCellAtPos sourcePos targetCell = @getCellAtPos targetPos if tmpObject = sourceCell?.getObjectOfType TmpObject tmpObject.del() if tmpObject.object == movedObject if tmpObject = targetCell?.getObjectOfType TmpObject tmpObject.del() if tmpObject.object == movedObject if @isOccupiedPos targetPos log "World.objectMoved [WARNING] #{movedObject.name} occupied target pos:", targetPos if sourceCell? sourceCell.removeObject movedObject if sourceCell.isEmpty() @cells[@posToIndex(sourcePos)] = null targetCell = @getCellAtPos targetPos if not targetCell? cellIndex = @posToIndex targetPos targetCell = new Cell() @cells[cellIndex] = targetCell if targetCell? targetCell.addObject movedObject else log "world.objectMoved [WARNING] #{movedObject.name} no target cell?" # 0000000 000000000 00000000 00000000 # 000 000 000 000 000 # 0000000 000 0000000 00000000 # 000 000 000 000 # 0000000 000 00000000 000 step: (step) -> camera = @player.camera.cam if false quat = camera.quaternion.clone() quat.multiply new THREE.Quaternion().setFromAxisAngle new THREE.Vector3(1,0,0), step.dsecs*0.2 quat.multiply new THREE.Quaternion().setFromAxisAngle new THREE.Vector3(0,1,0), step.dsecs*0.1 center = @size.div 2 camera.position.set(center.x,center.y,center.z+@dist).applyQuaternion quat camera.quaternion.copy quat Timer.event.triggerActions() Timer.event.finishActions() o.step?(step) for o in @objects @player.camera.step step Sound.setMatrix @player.camera @player.setOpacity clamp 0, 1, @player.camera.getPosition().minus(@player.current_position).length()-0.4 @sun.position.copy camera.position @renderer.render @scene, camera # 000000000 000 00 00 00000000 # 000 000 000 000 000 # 000 000 000000000 0000000 # 000 000 000 0 000 000 # 000 000 000 000 00000000 getTime: -> now().toFixed 0 setSpeed: (s) -> @speed = s getSpeed: -> @speed mapMsTime: (unmapped) -> parseInt 10.0 * unmapped/@speed unmapMsTime: (mapped) -> parseInt mapped * @speed/10.0 continuous: (cb) -> new Action func: cb name: "continuous" mode: Action.CONTINUOUS once: (cb) -> new Action func: cb name: "once" mode: Action.ONCE # 00000000 00000000 0000000 000 0000000 00000000 0000000 # 000 000 000 000 000 000 000 000 000 # 0000000 0000000 0000000 000 000 0000000 000 000 # 000 000 000 000 000 000 000 000 000 # 000 000 00000000 0000000 000 0000000 00000000 0000000 resized: (w,h) -> @aspect = w/h camera = @player.camera.cam camera?.aspect = @aspect camera?.updateProjectionMatrix() @renderer?.setSize w,h @screenSize = new Size w,h getNearestValidPos: (pos) -> new KikiPos Math.min(size.x-1, Math.max(pos.x, 0)), Math.min(size.y-1, Math.max(pos.y, 0)), Math.min(size.z-1, Math.max(pos.z, 0)) isUnoccupiedPos: (pos) -> not @isOccupiedPos pos isOccupiedPos: (pos) -> if @isInvalidPos pos return true if @getOccupantAtPos pos # log "isOccupiedPos occupant: #{@getOccupantAtPos(pos).name} at pos:", new Pos pos return true mayObjectPushToPos: (object, pos, duration) -> # log "world.mayObjectPushToPos object:#{object.name} duration:#{duration}", pos # returns true, if a pushable object is at pos and may be pushed return false if @isInvalidPos pos direction = pos.minus object.getPos() # direction from object to pushable object return false if @isInvalidPos pos.plus direction objectAtNewPos = @getOccupantAtPos pos.plus direction if objectAtNewPos if objectAtNewPos instanceof TmpObject tmpObject = objectAtNewPos if tmpObject.time < 0 and -tmpObject.time <= duration # temporary object at new pos will vanish before object will arrive -> delete it tmpObject.del() else return false else return false pushableObject = @getOccupantAtPos pos # log "pushableObject #{pushableObject?.name}" if pushableObject? and pushableObject instanceof Pushable #and # pushableObject instanceof MotorGear # bad pushableObject.pushedByObjectInDirection object, direction, duration return true false reinit: () -> for o in @objects if o instanceof Light o.initialize() # Text::reinit() # 000 000 00000000 000 00000000 # 000 000 000 000 000 000 # 000000000 0000000 000 00000000 # 000 000 000 000 000 # 000 000 00000000 0000000 000 help: (index=0) -> # displays help messages # text_list = @dict["help"] # more_text = index < len (text_list) - 1 # less_text = index > 0 # # list = text_list[index].split("$key(") # for i in range (1, len(list)) # close = list[i].find(")") # list[i] = Controller.player.getKeyForAction (list[i][:close]) + list[i][close+1:] # # list.append ("\n\n$scale(0.5)(%d/%d)" % (index+1, len (text_list))) # help_text = KikiPageText ("".join(list), more_text, less_text) # # if more_text: # help_text.getEventWithName ("next").addAction (once (lambda i=index+1: @help (i))) # if less_text: # help_text.getEventWithName ("previous").addAction (once (lambda i=index-1: @help (i))) outro: (index=0) -> # well hidden outro :-) outro_text = """ $scale(1.5)congratulations!\n\n$scale(1)you rescued\nthe nano world! the last dumb mutant bot\nhas been destroyed.\n\nthe maker is functioning again. kiki will go now\nand see all his new friends.\n\nyou should maybe\ndo the same? the maker wants to thank you!\n\n(btw.: you thought\nyou didn't see\nkiki's maker in the game? you are wrong!\nyou saw him\nall the time,\nbecause kiki\nlives inside him!)\n\n$scale(1.5)the end p.s.: the maker of the game\nwants to thank you as well!\n\ni definitely want your feedback: please send me a mail (monsterkodi@users.sf.net)\nwith your experiences, which levels you liked, etc.\n\nthanks in advance and have a nice day,\n\nyours kodi """ more_text = index < outro_text.length-1 less_text = index > 0 page_text = outro_text[index] page_text += "\n\n$scale(0.5)(#{index+1}/#{outro_text.length})" page = KikiPageText(page_text, more_text, less_text) page.getEventWithName("hide").addAction(once(display_main_menu)) if more_text page.getEventWithName("next").addAction (i=index+1) => @outro i if less_text page.getEventWithName("previous").addAction (i=index-1) => @outro i resetProjection: -> @player.camera.setViewport 0.0, 0.0, 1.0, 1.0 # 00000000 0000000 0000000 # 000 000 000 # 0000000 0000000 000 # 000 000 000 # 00000000 0000000 0000000 escape: (self) -> # handles an ESC key event @resetProjection() if "escape" in @dict if _.isFunction @dict["escape"] @dict["escape"]() else exec @dict["escape"] in globals() return menu = new Menu() menu.getEventWithName("hide").addAction once @resetProjection # if Controller.isDebugVersion() # menu.addItem (Controller.getLocalizedString("next level"), once(lambda w=self: w.performAction("exit 0",0))) # if "help" in @dict # menu.addItem (Controller.getLocalizedString("help"), once(@help)) menu.addItem(Controller.getLocalizedString("restart"), once(@restart)) esc_menu_action = once @escape log "level_index #{world.level_index}" menu.addItem(Controller.getLocalizedString("load level"), (i=world.level_index,a=esc_menu_action) -> levelSelection(i, a)) menu.addItem(Controller.getLocalizedString("setup"), once @quickSetup) menu.addItem(Controller.getLocalizedString("about"), once @display_about) menu.addItem(Controller.getLocalizedString("quit"), once world.quit) # 000 000 0000000 000 000 # 000 0 000 000 000 000 000 # 000000000 000000000 000 000 # 000 000 000 000 000 000 # 00 00 000 000 0000000 0000000 getInsideWallPosWithDelta: (pos, delta) -> insidePos = new Vector pos for w in [0..5] planePos = new Vector -0.5, -0.5, -0.5 if w >= 3 then planePos.add @size f = Vector.rayPlaneIntersectionFactor pos, World.normals[w].neg(), planePos, World.normals[w] if f < delta insidePos.add World.normals[w].mul delta-f insidePos getWallDistanceForPos: (pos) -> # distance to the next wall (positive or negative) min_f = 10000 for w in [0..5] planePos = new Vector -0.5, -0.5, -0.5 if w >= 3 then planePos.add @size f = Vector.rayPlaneIntersectionFactor pos, World.normals[w].neg(), planePos, World.normals[w] min_f = absMin min_f, f min_f getWallDistanceForRay: (rayPos, rayDir) -> # distance to the next wall in rayDir min_f = 10000 for w in [0..5] planePos = new Vector -0.5, -0.5, -0.5 if w >= 3 then planePos.add @size f = Vector.rayPlaneIntersectionFactor rayPos, rayDir, planePos, World.normals[w] min_f = f if f >= 0.0 and f < min_f min_f displayLights: () -> for light in @lights lignt.display() playSound: (sound, pos, time) -> Sound.play sound, pos, time if not @creating # 000 000 00000000 000 000 # 000 000 000 000 000 # 0000000 0000000 00000 # 000 000 000 000 # 000 000 00000000 000 modKeyComboEventDown: (mod, key, combo, event) -> return if @player?.modKeyComboEventDown mod, key, combo, event switch combo when '=' then @speed = Math.min 10, @speed+1 when '-' then @speed = Math.max 1, @speed-1 when 'r' then @restart() when 'n' then @exitLevel() when 'm' then @exitLevel 5 modKeyComboEventUp: (mod, key, combo, event) -> return if @player?.modKeyComboEventUp mod, key, combo, event module.exports = World