From e4ad38cd566dbc01ad8b46267c1606d3c25c86a1 Mon Sep 17 00:00:00 2001 From: cecilkorik Date: Sun, 24 Apr 2011 18:30:40 -0600 Subject: [PATCH] first add of roc --- camera.py | 190 +++++++++++++++++++++ data/shaders/shadow_pass1.frag | 13 ++ data/shaders/shadow_pass1.vert | 24 +++ data/shaders/shadow_pass2.frag | 25 +++ data/shaders/shadow_pass2.vert | 49 ++++++ data/shaders/sprite.frag | 17 ++ data/shaders/sprite.vert | 15 ++ data/shaders/standard.frag | 141 ++++++++++++++++ data/shaders/standard.vert | 15 ++ data/shaders/test.frag | 12 ++ data/shaders/test.vert | 13 ++ data/sprite/bluelight_static.png | Bin 0 -> 9512 bytes data/sprite/fuzzy_particle.png | Bin 0 -> 21742 bytes data/sprite/hard_particle.png | Bin 0 -> 890 bytes data/sprite/medium_particle.png | Bin 0 -> 968 bytes data/sprite/redlight_static.png | Bin 0 -> 12682 bytes data/sprite/soft_particle.png | Bin 0 -> 865 bytes data/sprite/star_sprite_inner.png | Bin 0 -> 11942 bytes data/sprite/star_sprite_main.png | Bin 0 -> 28477 bytes data/sprite/star_sprite_washout.png | Bin 0 -> 16255 bytes data/sprite/whitelight_static.png | Bin 0 -> 5487 bytes data/sprite/yellowlight_static.png | Bin 0 -> 8531 bytes data/xml/constants.xml | 6 + data/xml/def/bases.xml | 34 ++++ data/xml/def/commodities.xml | 10 ++ data/xml/def/components.xml | 27 +++ data/xml/def/engines.xml | 16 ++ data/xml/def/factions.xml | 16 ++ data/xml/def/models.xml | 43 +++++ data/xml/def/shaders.xml | 8 + data/xml/def/ships.xml | 37 +++++ data/xml/def/stellars.xml | 35 ++++ data/xml/def/systems.xml | 9 + data/xml/def/weapons.xml | 38 +++++ data/xml/engines.xml | 20 +++ data/xml/factions.xml | 54 ++++++ data/xml/models.xml | 101 +++++++++++ data/xml/shaders.xml | 22 +++ data/xml/ships.xml | 83 ++++++++++ data/xml/stellars.xml | 9 + data/xml/weapons.xml | 22 +++ enums.py | 51 ++++++ files.py | 28 ++++ files.pyc | Bin 0 -> 1344 bytes gamedata.py | 248 ++++++++++++++++++++++++++++ gamedata.pyc | Bin 0 -> 8376 bytes gametimer.py | 41 +++++ gametimer.pyc | Bin 0 -> 1531 bytes inputs.py | 77 +++++++++ looprun.bat | 5 + pipeline.py | 167 +++++++++++++++++++ refuge.py | 97 +++++++++++ roc_test.py | 19 +++ shader.py | 194 ++++++++++++++++++++++ shader.pyc | Bin 0 -> 6445 bytes video.py | 80 +++++++++ video.pyc | Bin 0 -> 3250 bytes 57 files changed, 2111 insertions(+) create mode 100644 camera.py create mode 100644 data/shaders/shadow_pass1.frag create mode 100644 data/shaders/shadow_pass1.vert create mode 100644 data/shaders/shadow_pass2.frag create mode 100644 data/shaders/shadow_pass2.vert create mode 100644 data/shaders/sprite.frag create mode 100644 data/shaders/sprite.vert create mode 100644 data/shaders/standard.frag create mode 100644 data/shaders/standard.vert create mode 100644 data/shaders/test.frag create mode 100644 data/shaders/test.vert create mode 100644 data/sprite/bluelight_static.png create mode 100644 data/sprite/fuzzy_particle.png create mode 100644 data/sprite/hard_particle.png create mode 100644 data/sprite/medium_particle.png create mode 100644 data/sprite/redlight_static.png create mode 100644 data/sprite/soft_particle.png create mode 100644 data/sprite/star_sprite_inner.png create mode 100644 data/sprite/star_sprite_main.png create mode 100644 data/sprite/star_sprite_washout.png create mode 100644 data/sprite/whitelight_static.png create mode 100644 data/sprite/yellowlight_static.png create mode 100644 data/xml/constants.xml create mode 100644 data/xml/def/bases.xml create mode 100644 data/xml/def/commodities.xml create mode 100644 data/xml/def/components.xml create mode 100644 data/xml/def/engines.xml create mode 100644 data/xml/def/factions.xml create mode 100644 data/xml/def/models.xml create mode 100644 data/xml/def/shaders.xml create mode 100644 data/xml/def/ships.xml create mode 100644 data/xml/def/stellars.xml create mode 100644 data/xml/def/systems.xml create mode 100644 data/xml/def/weapons.xml create mode 100644 data/xml/engines.xml create mode 100644 data/xml/factions.xml create mode 100644 data/xml/models.xml create mode 100644 data/xml/shaders.xml create mode 100644 data/xml/ships.xml create mode 100644 data/xml/stellars.xml create mode 100644 data/xml/weapons.xml create mode 100644 enums.py create mode 100644 files.py create mode 100755 files.pyc create mode 100644 gamedata.py create mode 100755 gamedata.pyc create mode 100644 gametimer.py create mode 100755 gametimer.pyc create mode 100644 inputs.py create mode 100644 looprun.bat create mode 100644 pipeline.py create mode 100755 refuge.py create mode 100644 roc_test.py create mode 100644 shader.py create mode 100755 shader.pyc create mode 100644 video.py create mode 100755 video.pyc diff --git a/camera.py b/camera.py new file mode 100644 index 0000000..184c183 --- /dev/null +++ b/camera.py @@ -0,0 +1,190 @@ +from gameobj import game_object +import math +from quat import * +from py3dutil import vect +from OpenGL.GL import * +import files + +class game_camera(game_object): + def __init__(self): + self.tracking = None + game_object.__init__(self) + self.target_pos = self.pos.copy() + self.target_rot = self.rot.copy() + self.real_pos = self.pos.copy() + self.real_rot = self.rot.copy() + self.starbox = None + self.starboxes = [] + + def track(self, obj): + self.tracking = obj + self.target_at(obj, immediate=True) + + def target_at(self, obj, immediate=False): + self.target_pos = obj.pos.copy() + desired_v = -self.get_camera_vector(obj.rot) + self.target_rot = quat(desired_v.x, desired_v.y, desired_v.z, 0.0) + self.target_rot = obj.rot.copy() + if immediate: + self.pos = obj.pos.copy() + self.rot = obj.rot.copy() + self.real_pos = obj.pos.copy() + self.real_rot = obj.rot.copy() + else: + self.camera_slew() + + + # apply translation vector to current position! + #print "Realrot: %s" % (self.real_rot,) #DELME + self.rot = self.real_rot + self.pos = self.real_pos + self.get_camera_vector(self.rot) + + def get_camera_vector(self, rot): + camera_pitch = 15.0 + camera_dist = 16.0 + camera_pitch_axis = rot.get_conjugate() * vect(0.0, 1.0, 0.0) + camera_pitch_axis.normalize() + #print "Rot: %s" % (rot,) #DELME + + # find the direction we want to be pointing (looking forward) + camera_v = (rot.get_conjugate() * vect(1.0, 0.0, 0.0)) + # find the angle at which the camera should be pitched + camera_q = get_angle(camera_pitch, camera_pitch_axis) + # rotate the direction to include the pitch component + camera_v = camera_q * camera_v + # apply the distance to turn our unit vector into a translation vector + camera_v = camera_v.normalize() * -camera_dist + #print camera_v + return camera_v + + def draw_starbox(self): + return + starbox = 'plain' + #starbox = 'test' + if starbox != self.starbox: + import sprite + self.starbox = starbox + self.starboxes = [] + for i in range(6): + fn = 'img/starbox/starbox_%s_%d.png' % (starbox, i + 1) + f = files.mgr.open(fn) + self.starboxes.append(sprite.sprite_tex()) + self.starboxes[i].set_file(f) + starbox_locs = [ + (90.0, 1.0, 0.0, 0.0), + (0.0, 0.0, 0.0, 1.0), + (270.0, 0.0, 0.0, 1.0), + (180.0, 0.0, 0.0, 1.0), + (90.0, 0.0, 0.0, 1.0), + (270.0, 1.0, 0.0, 0.0) + ] + + clr = (1.0, 1.0, 1.0, 1.0) + glDisable(GL_LIGHTING) + glDepthMask(0) + glMaterialfv(GL_FRONT, GL_AMBIENT, clr) + glMaterialfv(GL_FRONT, GL_DIFFUSE, clr) + glMaterialfv(GL_FRONT, GL_SPECULAR, clr) + glColor4f(*clr) + glMatrixMode(GL_PROJECTION) + glPushMatrix() + glLoadIdentity() + fsize = 0.8 + wsize = 1.1 + fardist = 100.0 + glFrustum(-wsize, wsize, -fsize, fsize, 2.0, fardist * 10.0) + #glOrtho(-1.0, 1.0, -1.0, 1.0, 0.1, fardist * 10.0) + glMatrixMode(GL_MODELVIEW) + + for i in range(6): + glPushMatrix() + glRotatef(*starbox_locs[i]) + if i == 0: + glRotatef(270.0, 0.0, 1.0, 0.0) + if i == 5: + glRotatef(90.0, 0.0, 1.0, 0.0) + glTranslatef(0.0, fardist, 0.0) + glScalef(fardist, fardist, fardist) + #glTranslatef(0.0, 100.0, 0.0) + #glScalef(100.0, 100.0, 100.0) + self.starboxes[i].render() + glPopMatrix() + glDepthMask(1) + glEnable(GL_LIGHTING) + + glMatrixMode(GL_PROJECTION) + glPopMatrix() + glMatrixMode(GL_MODELVIEW) + + def calc_render_pos(self): + self.render_pos = vect(0.0, 0.0, 0.0) + + def clear_render_pos(self): + self.render_pos = None + + def render(self): + glPushMatrix() + glRotatef(90.0, 0.0, 0.0, 1.0) + glMultMatrixf(self.rot.get_conjugate().get_matrix()) + self.draw_starbox() + #glTranslatef(*[-x for x in self.pos]) + + + def update(self): + #return + if self.tracking != None: + self.target_at(self.tracking) + + def camera_slew(self): + #return + currv = self.get_camera_vector(self.real_rot) + targv = self.get_camera_vector(self.target_rot) + currpos = self.real_pos + currv + targpos = self.target_pos + targv + + currv.normalize() + targv.normalize() + + rotdiff = currv.dot(targv) + + if rotdiff != 0.0: + rotspeed = (math.sqrt(1.0 + rotdiff) - 1.0) / 10.0 + maxrot = 25.0 + if rotdiff > maxrot: + rotspeed += (rotdiff - maxrot) + amt = abs(rotspeed / rotdiff) + if amt >= 1.0: + self.real_rot = self.target_rot.copy() + elif amt < 1e-7: + self.real_rot = self.rot.copy() + else: + self.real_rot = self.rot.slerp(self.target_rot, amt) + + d = currpos.dist(targpos) + if d != 0.0: + maxdist = 3.0 + followspeed = (math.sqrt(1.0 + d) - 1.0) + #followspeed = 0.000001 + if d > maxdist: + followspeed += d - maxdist + amt = abs(followspeed / d) + #print "%s, %s, %s" % (d, amt, followspeed) + if amt > 1.0 or amt < 1e-7: + self.real_pos = self.target_pos.copy() + else: + self.real_pos = self.real_pos.slerp(self.target_pos, amt) + + #self.real_rot = self.real_rot * get_angle(90.0, vect(0.0, 0.0, 1.0)) + + + def get_forward_vector(self): + return (self.rot * vect(1.0, 0.0, 0.0)).normalize() + + + + def done(self): + glPopMatrix() + + + + diff --git a/data/shaders/shadow_pass1.frag b/data/shaders/shadow_pass1.frag new file mode 100644 index 0000000..f4a2ab7 --- /dev/null +++ b/data/shaders/shadow_pass1.frag @@ -0,0 +1,13 @@ +uniform sampler2D TextureMap; + +varying float CameraDepth; +varying vec2 TexCoord; + +void main() +{ + // draw the typical textured output to visual framebuffer + gl_FragData[0] = texture2D(TextureMap, TexCoord); + + // write "normaliized vertex depth" to the depthmap's alpha. + gl_FragData[1] = vec4(vec3(0.0), CameraDepth); +} \ No newline at end of file diff --git a/data/shaders/shadow_pass1.vert b/data/shaders/shadow_pass1.vert new file mode 100644 index 0000000..377efc0 --- /dev/null +++ b/data/shaders/shadow_pass1.vert @@ -0,0 +1,24 @@ +uniform vec3 CameraPos; +uniform vec3 CameraDir; +uniform float DepthNear; +uniform float DepthFar; + +varying float CameraDepth; // normalized camera depth +varying vec2 TexCoord; + +void main() +{ + // offset = vector to vertex from camera's position + vec3 offset = (gl_Vertex.xyz / gl_Vertex.w) - CameraPos; + + // z = distance from vertex to camera plane + float z = -dot(offset, CameraDir); + + // Depth from vertex to camera, mapped to [0,1] + CameraDepth = (z - DepthNear) / (DepthFar - DepthNear); + + // typical interpolated coordinate for texture lookup + TexCoord = gl_MultiTexCoord0.xy; + + gl_Position = ftransform(); +} \ No newline at end of file diff --git a/data/shaders/shadow_pass2.frag b/data/shaders/shadow_pass2.frag new file mode 100644 index 0000000..ab181e9 --- /dev/null +++ b/data/shaders/shadow_pass2.frag @@ -0,0 +1,25 @@ +uniform sampler2D DepthTexture; +uniform sampler2D ShadowTexture; + +varying vec2 DepthTexCoord; +varying vec3 ShadowNear; +varying vec3 ShadowDir; + +const vec3 shadowColor = vec3(0.0); + +void main() +{ + // read from DepthTexture + // (depth is stored in texture's alpha component) + float cameraDepth = texture2D(DepthTexture, DepthTexCoord).a; + + vec3 shadowPos = (cameraDepth * ShadowDir) + ShadowNear; + float l = dot(shadowPos.yz, shadowPos.yz); + float d = shadowPos.x; + + // k = shadow density: 0=opaque, 1=transparent + // (use texture's red component as the density) + float k = texture2D(ShadowTexture, vec2(l, d)).r; + + gl_FragColor = vec4(shadowColor, k); +} \ No newline at end of file diff --git a/data/shaders/shadow_pass2.vert b/data/shaders/shadow_pass2.vert new file mode 100644 index 0000000..52e01e3 --- /dev/null +++ b/data/shaders/shadow_pass2.vert @@ -0,0 +1,49 @@ +uniform mat3 WorldToShadow; +uniform vec3 SphereOrigin; + +uniform vec3 CameraPos; +uniform vec3 CameraDir; +uniform float DepthNear; +uniform float DepthFar; + +varying vec2 DepthTexCoord; +varying vec3 ShadowNear; +varying vec3 ShadowDir; + +void main() +{ + vec4 tmp1 = ftransform(); + gl_Position = tmp1; + + // Predivide out w to avoid perspective-correct interpolation. + // The quantities being interpolated are screen-space texture + // coordinates and vectors to the near and far shadow plane, + // all of which have to be bilinearly interpolated. + // This could potentially be done by setting glHint, + // but it wouldn't be guaranteed to work on all hardware. + + gl_Position.xyz /= gl_Position.w; + gl_Position.w = 1.0; + + // Grab the transformed vertex's XY components as a texcoord + // for sampling from the depth texture from pass 1. + // Normalize them from [0,0] to [1,1] + + DepthTexCoord = gl_Position.xy * 0.5 + 0.5; + + // offset = vector to vertex from camera's position + vec3 offset = (gl_Vertex.xyz / gl_Vertex.w) - CameraPos; + + // z = distance from vertex to camera plane + float z = -dot(offset, CameraDir); + + vec3 shadowOffsetNear = offset * DepthNear / z; + vec3 shadowOffsetFar = offset * DepthFar / z; + + vec3 worldPositionNear = CameraPos + shadowOffsetNear; + vec3 worldPositionFar = CameraPos + shadowOffsetFar; + + vec3 shadowFar = WorldToShadow * (worldPositionFar - SphereOrigin); + ShadowNear = WorldToShadow * (worldPositionNear - SphereOrigin); + ShadowDir = shadowFar - ShadowNear; +} \ No newline at end of file diff --git a/data/shaders/sprite.frag b/data/shaders/sprite.frag new file mode 100644 index 0000000..fb132e5 --- /dev/null +++ b/data/shaders/sprite.frag @@ -0,0 +1,17 @@ +varying vec4 ambientGlobal/*,ambientA[8],diffuseA[8]*/; +varying vec4 pixelpos; +uniform sampler2D tex; + +void main() +{ + vec4 color = ambientGlobal; + vec4 white = vec4(1.0, 1.0, 1.5, 1.0); + //color = white; + + vec4 texcolor = texture2D(tex, gl_TexCoord[0].st); + color = clamp(texcolor * color, 0.0, 1.0); + //color = color * vec4(1.0, 1.0, 1.0, 0.5); + gl_FragColor = color; + // gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + +} \ No newline at end of file diff --git a/data/shaders/sprite.vert b/data/shaders/sprite.vert new file mode 100644 index 0000000..e43949b --- /dev/null +++ b/data/shaders/sprite.vert @@ -0,0 +1,15 @@ +varying vec4 ambientGlobal/*,ambientA[8],diffuseA[8]*/; +varying vec3 normal/*,lightDirA[8],halfVectorA[8]*/; +/*varying float distA[8];*/ +varying vec4 pixelpos; + +void main() +{ + normal = normalize(gl_NormalMatrix * gl_Normal); + + pixelpos = gl_ModelViewMatrix * gl_Vertex; + ambientGlobal = gl_LightModel.ambient * gl_FrontMaterial.ambient; + + gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0; + gl_Position = ftransform(); +} diff --git a/data/shaders/standard.frag b/data/shaders/standard.frag new file mode 100644 index 0000000..3aec6dd --- /dev/null +++ b/data/shaders/standard.frag @@ -0,0 +1,141 @@ +varying vec4 ambientGlobal/*,ambientA[8],diffuseA[8]*/; +varying vec4 pixelpos; +uniform sampler2D tex; +varying vec3 normal/*,lightDirA[8],halfVectorA[8]*/; +/*varying float distA[8];*/ + + +vec4 overlay_blend(vec4 base, vec4 blend) +{ + vec4 lumCoeff = vec4(0.2125, 0.7154, 0.0721, 1.0); + vec4 white = vec4(1.0, 1.0, 1.0, 1.0); + vec4 result; + float luminance = dot(base, lumCoeff); + + if (luminance < 0.45) + result = 2.0 * blend * base; + else if (luminance > 0.55) + result = white - 2.0 * (white - blend) * (white - base); + else + { + vec4 result1 = 2.0 * blend * base; + vec4 result2 = white - 2.0 * (white - blend) * (white - base); + result = mix(result1, result2, (luminance - 0.45) * 10.0); + } + return result; +} + +vec4 process_lightsource(gl_LightSourceParameters light, vec4 incolor) +{ + vec3 n,halfV,viewV,lightDir,halfVector; + float NdotL,NdotHV,dist; + vec4 color = vec4(0.0, 0.0, 0.0, 1.0); + vec4 spec = vec4(0.0, 0.0, 0.0, 1.0); + vec4 white = vec4(1.0, 1.0, 1.0, 1.0); + float att; + + /* a fragment shader can't write a varying variable, hence we need + a new variable to store the normalized interpolated normal */ + n = normalize(normal); + + vec3 aux, ldir; + vec4 d,a; + if (light.position[3] == 1.0) + { + aux = vec3(light.position-pixelpos); + ldir = normalize(aux); + dist = length(aux); + } + else + { + ldir = normalize(vec3(light.position)); + dist = 0.0; + } + + halfVector = normalize(light.halfVector.xyz); + + /* Compute the diffuse, ambient and globalAmbient terms */ + d = gl_FrontMaterial.diffuse * light.diffuse; + + /* The ambient terms have been separated since one of them */ + /* suffers attenuation */ + a = gl_FrontMaterial.ambient * light.ambient; + + /* compute the dot product between normal and normalized lightdir */ + NdotL = dot(n,ldir); + +#define SHADOW_FUZZ 0.1 + + if (NdotL > 0.0) { + NdotL = ((1.0 - SHADOW_FUZZ) * NdotL) + SHADOW_FUZZ; + + att = 1.0 / (light.constantAttenuation + + light.linearAttenuation * dist + + light.quadraticAttenuation * dist * dist); + color = att * (d * NdotL + a); + + + halfV = normalize(halfVector); + NdotHV = max(dot(n,halfV),0.0); + + //spec = att * gl_FrontMaterial.specular * light.specular * pow(NdotHV,gl_FrontMaterial.shininess); + spec = att * white * light.specular * pow(NdotHV,10.0); + + } + else if (NdotL > (-1.0 * SHADOW_FUZZ)) { + float fraction; + NdotL = (NdotL+SHADOW_FUZZ); + fraction = NdotL / SHADOW_FUZZ; + + att = 1.0 / (light.constantAttenuation + + light.linearAttenuation * dist + + light.quadraticAttenuation * dist * dist); + color = att * fraction * (d * NdotL + a); + + halfV = normalize(halfVector); + NdotHV = max(dot(n,halfV),0.0); + + //spec = att * fraction * gl_FrontMaterial.specular * light.specular * pow(NdotHV,gl_FrontMaterial.shininess); + spec = att * white * light.specular * pow(NdotHV,10.0); + + } + + + // blend mode SCREEN + color = white - ((white - color) * (white - incolor)); + // blend mode AVG + //color = (color + incolor) * 0.5; + // blend mode LIGHTEN + //color = max(color, incolor); + // blend mode OVERLAY + //color = overlay_blend(incolor, color); + // blend mode DODGE + //color = incolor / (white - color); + // blend mode MULTIPLY + //color = color * incolor; + + color += clamp(spec, 0.0, 1.0); + return clamp(color, 0.0, 1.0); + +} + +void main() +{ + vec4 color = ambientGlobal; + vec4 white = vec4(1.0, 1.0, 1.0, 1.0); + //color = white; + + color = process_lightsource(gl_LightSource[0], color); + color = process_lightsource(gl_LightSource[1], color); + color = process_lightsource(gl_LightSource[2], color); + color = process_lightsource(gl_LightSource[3], color); + color = process_lightsource(gl_LightSource[4], color); + color = process_lightsource(gl_LightSource[5], color); + color = process_lightsource(gl_LightSource[6], color); + color = process_lightsource(gl_LightSource[7], color); + + vec4 texcolor = texture2D(tex, gl_TexCoord[0].st); + gl_FragColor = clamp(texcolor * color, 0.0, 1.0); + // gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + +} \ No newline at end of file diff --git a/data/shaders/standard.vert b/data/shaders/standard.vert new file mode 100644 index 0000000..9d75179 --- /dev/null +++ b/data/shaders/standard.vert @@ -0,0 +1,15 @@ +varying vec4 ambientGlobal/*,ambientA[8],diffuseA[8]*/; +varying vec3 normal/*,lightDirA[8],halfVectorA[8]*/; +/*varying float distA[8];*/ +varying vec4 pixelpos; + +void main() +{ + normal = normalize(gl_NormalMatrix * gl_Normal); + + pixelpos = gl_ModelViewMatrix * gl_Vertex; + ambientGlobal = gl_LightModel.ambient * gl_FrontMaterial.ambient; + + gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0; + gl_Position = ftransform(); +} diff --git a/data/shaders/test.frag b/data/shaders/test.frag new file mode 100644 index 0000000..03f0e92 --- /dev/null +++ b/data/shaders/test.frag @@ -0,0 +1,12 @@ +varying vec3 normal; +varying vec3 lightdir; +uniform sampler2D tex; + +void main() +{ + float intensity; + intensity = dot(lightdir, normalize(normal)); + + var; + gl_FragColor = texture2D(tex, gl_TexCoord[0].st) * vec4(intensity, intensity, intensity, 1.0); +} \ No newline at end of file diff --git a/data/shaders/test.vert b/data/shaders/test.vert new file mode 100644 index 0000000..5cdab5e --- /dev/null +++ b/data/shaders/test.vert @@ -0,0 +1,13 @@ +varying vec3 normal; +varying vec3 lightdir; + +void main() +{ + gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0; + + normal = gl_NormalMatrix * gl_Normal; + vec4 newpos = ftransform(); + gl_Position = newpos; + + lightdir = normalize(vec3(gl_LightSource[0].position) - vec3(gl_ModelViewMatrix * gl_Vertex)); +} \ No newline at end of file diff --git a/data/sprite/bluelight_static.png b/data/sprite/bluelight_static.png new file mode 100644 index 0000000000000000000000000000000000000000..d7197dae3aed078fd7519fc7c9534faffc000530 GIT binary patch literal 9512 zcmV+@CD+=CP)z@;j(q!3lK=n!AY({UO#lFTB>(_`g8%^e{{R4h=>PzA zFaQARU;qF*m;eA5Z<1fdMgRaLo=HSORCwC#U0IgnFpdNv+BGMxL(GZ0YzmwY0#pzJ zC3(r}?&;3={3I{wMF<2IfcZ9EUDzn_nV{bRfPdVctMxb4pZs0_F97iE3j_j3VV`Mv zdiUclutPc0aB_+MH0`ooic&qa?20zNp8 zZYlWxqsM3eKRe%j*KY{#C;g7EGr*U=Y^3dH$E(l1L;rPhJbJGF1S@=-4ZaG1F9`ok z@UK4hs!wp$=0^g)`q;-n_$CPWUqSf#gCe@+fU4QiES@jHx!p3{mdWktHPQP=-zA;= z1|}l@M2O!s314A=cfSyL^n9Sz>EJO0@hfKID*a#4Vu>Px$!q<&d=fc)#lQFz03V;uQdR5JUnOcdWvJ>q$G zkNaD8ul4_rpK%G=T z_hs_@+3h1Xc$W#@0>B5t=L=NN7leM~B=*ObG=3JRLw{hPdZ~LFU+W+sIS~30803O! zkiP|jmjUpq@YT`d7qtG!^Pk;cK-jFp?o7k!Qpz~ge5speDkua1u{tow$^EO$K#EwKHYaN6>yLm92 zF~VCW;S~^E2Ed2HpLy~}8ox{&L0%Fqs^yClJ|2_4X83#s#-Rz2I?}NUzoO9Han;wX z3WPNf{(8a)a$*jC1O%4=@TTy262=`SNFsqN#WN8tyd6>a3kn~E&R`00u>B@rJl_dlDiwWwQAQ{yV5k>>@Kq3evn1?Q;V-i!4`{pzK0D!O z_dM^|fd&BhXy7ai+`RwPHNQ&gv_AkqDiu!c=m5ZaKdG?OI^OqAsQ_EaLGtdcF!3b@ zcv1V0gg>+O;lhn-y$LyUYFRdY?8b9&Fpn&-fD`DW0*eUD-V>+6_E)Fz73_TzF7@*Q z+HQh>0vi;4O@Ez&V0BEep7;hIF~N)lLIAuW{B)4!JfizS@G9sGH@km>`!_TROanV) z=L{}B8ki)5q6h$Ef75mY1ZsK_pnR$H1YkT-@OHE39T3jU!civjZ4m4L@QUzVlII=a zXQ2F>6Zpc3?nIO`w;a74IZ@^f1h>FH=nf%90Z~H8F+mL&prXeg*!gD{aI^7U&_(+F z2fVi6|0xK{yC8TqS>!?Rkq>Yn{B$CGTRh3jUd!!vhx3~~pBq!$yzt?*7$znc7ABVv z3NwL$=U>6e6AMd(S?fKZ5@nTe(L-~WJXQoQ|N>)}eJOdMBM;mcQifH$ar zajS}F_I_AFTSkH3GItQ%b~g&Zura~{CIZ=Div}cV-g%!>)Ap-ZVVum7EbgmLM)Y*BE;$prXrLwQ7Ui^Gw(qlb;<&fbBM1qdDO21 z;DGv%y#8IKEeanry9v5c{x5yaF9(9V+5Wr%BAYW9IDwW6NW3M5X2l^909yRNIf3Gx z!0zldAUvcJmzMooOzSx{!51Mi%_kv}vk$Xy`CV-FK9mp)E@yM%IjkYhm`Jx|34^n z1Ob?aT_m9A3vZ5JHvuU8fB`)14h-O(K=1=rA1Mve|nRw$VQcB_U#Jf+x{rj|pDStARj|m58eQ zeh3183r&H_OuhktD@B~41Bh8V+Q;+V<~!m4>gTEZ1or>{x7^NA@LBIKM*Rm7Kw=*O z1d)I<1$~u|QwPGrOpC_)eI3F=J*kw;cNfU^IS>%1E_gf+asLSjyiaqA>?( zK@rE6w+BZpvoZh$0^iR6CvQTx$8LK6p2zIr(oFzpe&(%gj4zr1PR;(hP;f2Vf)J~% zUpUcPgjkP>>%ap2nkn5U5=upB9<{&U7pkQ>fKr6SjzLZ0;zGjed;t%Lj{xvW&A#0% z)8}*ioBgLSEeAn48)9II5yx#jt@BwAJT zSE;meqD7rSR&cP2#lp@p#8Z(e?f2MTflxD%6wrQS1F0mO1{Pot{@EUgLlEQ_=$}p; zzkYOa`2xZo!@e}ydrqD98w+qhznk!#;E@4L@FsLSl8Xec`T$1zWE!uDomIm<1WFY8Ni+gaKgXm%?H3+qkY;OKLrFk21uF=q8Fn8k`V-}fkDkcVh!(jp%qwQ zyJkWOKrn`JLS`^J&1crdA0a8!%#B_@OG&SsXjyBw|Ckm|0ShFMWqs8Iq>FJsWA!cw z+~@tHMI9gZ-SIK7`8RL^vkL!~H?N;T_zNdZzR<`3sQo-y4KUi@F#u62(gcB&uTEb9 zK_{22t;hsYJMCAdelD45EkY}+pTX4qjhnJTHZ!oKz7Yj|0hxf{&>74xF89(=ou9AP z1@GV1^9Mhmd-tO`xPim>D-eFZbN2&G*b4%|1aBEQ06^FLNh+8oCV-_6%-ZG z_iuoBstMRG}Z_T_FnkozA#|8=$ z`~s!!tfgdW;ty0jM$TY<(fJEEb_Ckr>-K3B{+Jh_U5dEp_R-^44M*X#X}$`70X9gE z{)rQ%9Gig12k4qF%|0h!f@EP^k(S}(4Na>9K@}eM88odIx~a1#(zL&jkVvAU*P&#l z8}2uNYr5Jsh8;!DwRY-zG~S!wb_0xzrr=O)bdR|{O0K=60_~O)eziOur~$} z^lR2?3$ytFnsiz?Q2{?7xhkP_H5-^ugzkNS9rZ8c;>!Y>KllS~*%pgAZdm_^3q4)a zQ>Xf*=3vQb5WYRvHD85K28Ig&Kp$YZY1}VG3r98XpH&qV)zGTK7Pawps4z|&?MtOw zPE=&?FFj$EGb7v7 z#rdfDk?`+}0w7URW=Qv?AGhbX*V27yxAsN1*VyazKI=K(_5I;}@H_B*GfVbeGlZ&` zuh=O>aPvMC?t?t)Idrr$J00!|a9y{LOhLlL5A4Dj?nhxEX#X-S6j2v^b|A3uuG@S_ z`k?SRr|$iC%?Ab`NC-`19!LNsSRk^r?m|Su+X^A6V}}0U(i2IQmdqs5a{r<=gDu?C z`bDV?7*bnJ=&rD3U*KYq0Gm0meTQ`35iNN?AYTaoPMtx&9V_w&AYYG16nGc3(Hu}e zL#Wq1eV~0-%|zj+%)0uP1`u5J0h;}%E_}<~v#!$dqpjNYI9n=lPE@7+o~mmHW(smvEu#&ByGM9ZGLE3k(UEqmCnx-2I z6eh=vOpC0$jV|q2haA9Q0+t2>lf^o50*~_m!Qh2i@@J?CzQkAVPUdy?AAy;EjlQAbc;w`gLAfd%|^ z+IBfXY1_c0?mCN>oJrj&s1VI%iGIMD35e)~BjHCyp+HrOFW89eoYDMlA*b2yA<1*3 z`5^qH&k+J3S`vor-v9zI4@vX-sV8>YUIdY?;+K{BS(7{X8W$O{bNAO&EcLGzQEgftX)@c90q(Tt2>_8z=? z^YCc^NsAOHr|pIw{^b8i|Ce0I$@mz4M-ONyP+|Q2v~O-P>~!u5L2!-JVLM$q2vY}H z=Zj@vQ)iIVyuTj`);Wqkh0JdKesO^Ef==oT4qA>eKhQs+KVT);xIH{D0f~5X3ZbC0 zN~NSl8%Fz0-n7pG~C1YZI7uV7y&^mk&(U`i&jy%z!XE98wT4C zOh5q`4h8?tS^#>i(7KX#o&bhVN=^?ig# z*<8PoScYkv1~pd=kkQcr3BD?5Wu?>iJzU?%89>wV4M@P1xkM0-s>0>ust&IDNF z9%KRkx#8yomwjNwv=`{Cz}CGsyngmN5d15$dVhED6{qm%i}(u_36H7MS@N$7E_?(w zn$`U~hjI6wO7%o(=od78UhRAKI{gDcs{tzl2X0K^07Sv}P3W!#YZq6XzK<9ny#WZX z1?fUa=`Ymf?-N{Pir&vN!%TaDu0vnp?ESA?yWsnVe$K33@dMd}MIk>vKAsc()Bdh} zc4qpQ`;+?GZ?Wf=H?H;3&wsTSZN2c=Y;xAS@Bi|J+?DUhXYVt4?$zHly(XM4UDsBW zSYN+(Z}|X~9e!AK6*rM^$N_Hm z{P0-XQoF7aX!_pKdqwZMgN zvPKL76}rRgy|9M=N#koUvE$t$Ie0&*G4LXwpzyp_x@MH&Z}%YZ*Ul?Sbp8ngRN7aN z(t$RIG$kqQwO2K$+1-N1Oaqv(Je#&!y^7s7u!b+H=<#{}Stig`0^@lNgkBveu#9}y zc+#_)Kf2!=KBu2qBha15iDrIAzG}Aulv^W%;oSXC-wP-E)+-WGARI-3Gq4QOs;l1F z3B6Qm?Hp-q#$-t$-z(T0LJxu>Q$a_22tCEyxf}@}dJ#klA~cz0=oDqjrmvcSI*BvG zk?6Y3X*UTA1T1ds3jV+q9}jQ)=aOt-=t2eIk8IHP-uHY%)Rik*4V-9x)#qwp3Wgp- z2jAcK9asZK6rpic4FJJk=cCc}r_n&Z;4oa7h%NY|v@Oyw8_)LG6AW8|i?jXMwl^*Z z+Fk=QuC?l^}vR7?N<{pXuoN=M=rKtjc&B{fL$XZ4-umM80=io6^fWDg;O&vCvz?p+yyWz14`~RZh zRYM1}gRs0FfuSdY>hD8aQ4c^6jxiaUD*7gi$_)-Xz1jX z+{~T}KiGI3{(p_v56E60~2ni!-o-Uer~zof_Ui6J^liIx@gx-2^!> z!n(h~^VP9K7rs8$g$$tB=%3v8-?N?n{Wf`I-T6v2o*ojI0HXaciJdI5N5GK}kR}d= z1b%>TsctCl(=T9z+Umxr;gCgEyKXc07SMR%wv)Cyaz#ih^E(suHYt)?w4h7BN&S>p z_XTuEP@M1qYzDy9gsu0*0o!*OUx&$K)_xYIiqSZh&YKAd?7tjEY*)g-dI1F8p35wz+z}=YE^AVe~(A;I#g`+Abxj^EWI|5)p~+_Ob73;h?Sg zyK3OXurdgJf(I7x4xiD!)CLHo|A%xfH(+$ShvQm=p25taQtEEX=rb^vK8!q?D!R)H zoc0K!{ZtA+oPb?MY8ZwLwmwT5hAU?GgYB_r5#18Sw3xuj>f@PKZUY#tUakbKS$f$X zX)ibJzr*ZGybgK<%@&|`pKZc8pFjkN()b% z_Se<|bl!|#G(~h_9~zAFnbTnFGbbv#P5P<(+Pm;U*jYiqy$Lh)-tBpOqNn@b)h*Mq z*7n;j-K5*MS}~{FQ*cnAQhjb8LcGD*>yBQfg2GoJoA94t3Q8I01>&(dMrZ>l$H>W# zt-UgOz5^nfB^0%ZBZ?af&HMplfoQts(^{(Q59-aMRO6YXk6OcF3mB$tg_5%S?uFXc ztwobRN*2nD^vHwu5#ZWQuEwf*THf9izMhXaP?1)g|K zAu5QdzaB*bQN!qbVby4s9UOcBz2JVR@=}CK=0@@GSXOhe2+>2Sp8HTm)|TtOE`Hg8 zVCk!GtnFB~<_py}m)c%E%m}*b+8UVvdvD0-ZJJ+0hp%aSV}u82|3g{f0e*l1V$ghG zgHkr?f1+~PajnxyqI4GiT>$_#Jok}-cr&nS+f!;yj&ZV96Fis#8>lFiSa8?^?uOm^ z?T=c@y>`%qvZ!}oCAH2R+*+$AV}Yy9qH#?s$J-+@ksY&gP7lMQ2QoMZKrPU zr_|rNdp00cKP@EKk`S>6I|zEpZX)-G(*{68x6eqJj5k&zX)8x26% zWlz_7I!pLe`wszd;tU>{f(6pQ$x^)fqZkA~#QFLhuIZ;9+{uV+ZMb|RCXuzZU{j&> ze!&_6Fl6tza^_k}9ajM&vElscFI6MR7-g3!J2|A0yOgFrA}!3*DnzSb@2b|Cz-0@0TNaK#_6ThX<6 zHZuVClh*Wg?RQL&4#pj}+Qaso*oYOqNWBfKSf`Q}ZWiBh>HAx`l(IP_V&IP zY;Jac@W^v(oYGpZr(Td^OUCTltge}fsrBPe!FKZ_2G{@qHH}4Dlw&J=;0J8Uoz44y zpoRnO{}~=@_Zt|%_QNV+uM=47Zt*7q16>vVs{lxo+_E21U?4!j7u0gg()roB0D_8k zpVP=RoJfdim9+P64ytt29-fO5D*MXhDJtd*NABk0xR#TBwN|J zuRGw>DsQZtwkDh|4Udj;0nzMWENdHl0NGYNgT0Oka6 z5$LK3P?>#YZ=gj4+aQ$VsG$EFj5EQM>@YPOkGA{@T5tXK4G3b1CTc=A`=6O8%Nlmx zOr8bkczU?k{u-2U*&Kx1*?sugk$jD%~@;!AiRDlqc=g6ZGDT`W8{N^G3FL z!5aV|6^k}n&mWk|HO&VAbaMb6|KR*Zjvsjav)j%gKZ^U`2Ee3fG%^JQW`Kxv-@5d+ zYR_;-ZyLd%D|_brnccrh8?P#T(gtrG$#I0N_T06I0K1w};0Q(l5ZJUKu1tHVVj6P! z)v>^)`2!%l@PSD>xYjxSAA0>)h5yd(;3zLZbUvp(Mcb^A1vFt($p$I&HcpkcDVn8^ zRTCYvQn&Fczy#QFbMYhOu^K3d1iTqofepk}HRHk!CT~hW>l+AY_qbmQM!~Nad;ogI z@%w0dI5-zPSwKn@v@2h?G3rBGmZ1eJ=?L|fTD8$?ViPrq`Utff^qz=#0= zZ-C%za@B6UJi#cL+vvdEhz3C1hY@PD1Hl3l$#CQhP9oX5ms8wVFe(bQl|Z+dbq)Lg zEPh#uobYK7^23$EKh=z^>hP_aMvrp+N6w#I@WDdizhwe0`U6pjxO@I7L`&*rSH!8? z{;Df^ck=^CV}7<;!xsNP)h`&PZ>Z208mF3!CZJB2gKMy@UED^^)e9ffwq>u`4_D@m z|F;I-{#CF4ExSK80Uwxx*<8WMBrM)6Ec+ozp4x9Vc#2b-B8o#Q-8oLwLi-W`PXX{I2+oEW&in!f5G){Q4}^si z-H7^Zq4v5B062Puy$PUwBW5}%3PA=y6X5d!<`AxKE7vpF4Ye!rc1ycQ_P)MS=596?&sM?PG8@D zl-gZS%DcZV{EvNrlS0y2)is8QyVa`|^6$DHu);dTg2f(wmae6MiHNnYJ_ixNQFG;} z@r7VkkoNl^*T65pNI{^Ir?@5E8Qp(P_;2|DAF#j`CWy>I=p!tnM~}=f%p@e*9X;(2 z^A7n00OGd7`({^u|Neo4*v-YHlIKcp|0CfaY5yevd=Uf`_ym{0uP&(-=g_xwk)Vj@RJ0nz!WbH2h1OCWR{ zs}^|u>=Ho7CZG;}%PuoG0|LP&nG;Pv+tL+vWBI!9Uk1QCAkdk_d;)@_u+f7dIUpnm z`O=~7PvKN8&j5Z+fM{&RGUNwn?9ef*)w>eb?S%hMlIIoSzXgCdnSf5r!4V@|J@OLM zel&7zn6`xtBIuw|Gn#|79@6*`y*RrqfM;6o6k7uXE;{H)&S^e zO#lRb)`jEJirtykzeD-&3jZSjybA&_3F!hGqzfz&jkosz^OPTgU`cHj!lc@X+$!9a z)+|?q{&FGbw}k&O06t)XBQ}_UfhL0XuVE+Tvw@g1XD~`sQIN!{6JQWsE7^Wq>pvAf z(HDkBeeeZp{k`?Vo!H8mGi4!F??dR$*WkH#Z%6NG=h0TJS%L@8SG@-R6ybk`0lvTl zXJ+Au9f;27=)S7`5epo#!fZEAJ^0`H=`!v&g#T3ldOVR(4K zOdJaT(_H?yg#UE_dak`Be89bAHVvKjiyLwTjPkm&)OIFvJ;V>13Wu) z;ECbjfxSU@sBwFHS=*FnYBD~_3IWs9WHgrWOl@lngt#%*12k^7G@6Kruy%b2w-uvlL}VA~n1)d>^0)#@0gZ63zzX?1S3Iz3ivmJv~Mk&Qks z6bYx4*vOJxYy-k66c7T6tYpi615?r0mwz%QCoRi`UP2gTV=lUT-WR1Y$upW0PTMN2krpblCIT%+gz8)5r3WeQe0jbCV>#;{iUw;sI`J z%#1L5rQrb*h&+)+&e%H&4RDE`?2K$Sv=}$HGP7BFve^Yt1DKO9Kt< zE}NNgJIppKZMM-ac->|@1FwyNHn8Kg!V9uaT(<)sUR$e;dCcYR=(77AULRw1+L=zL z!{&CnyR7!kF00pP^JZYm@l102Ys>!ZRAhxXz?`LVfLlESktsI%lrI9AB7x`D1{g2P z$O2jBKfRJ-7GtrZmy2@z5S$~}_N@?k#8_6_V(}cvYz~*B)!b@zJFKLHX>!txSTdwV zEN#$kr$w1oGuCpF5~dWU3tFIrmR6VDN=lecpkXqhgz0zMAUJRd@=&^Q=>kb8abGqF z6g11nLElxtaaOnm=p(HYHJJ3!Bw>u%&poEZYrQKi? zmsr>B!n$r3mT|kVjN46Q+%%!FH8-*5cHB4Voo1KWZ3bv) z+DtQM+GeI(%@D>QL_5t8^9;zsN`Qp`Ys3!A1)?1m2E_Yg&Mu~-tJCG}uv`7sE^nu` z(+&~uvbud%+UaQZNt2l~V_$cWFPx*6-&+^hOT&GE5xII=je0as!2MKB=9}C?-9&hmq$I`m?;=FB?vQGQVVDoZ z5^#ZxjRjMLQ?#0JHb7Z{h7pefD25U!4&OXtfexaSWaDEZ3D(nTLLn5QL>uc#y$F}Y zkBWu*6sbSFP~Skj`IOZ4q=Z7UQby`<4;RZkD&Dg#%iO_58lw3bky(Z8L~SdSI#G*o zzaaEF?BQ~c_BWD$L=3Q_uvQVi@r#h`y_M88l*q+>;9l_kU>%V!B`w%LXwy6{zLI5( zW45iexT@`kLi7_?k#xTpBc4DU3L^pSmJk` z30w?*ZcATcoXu&`1){=mU_7U-GRT4+J0W3i?}7y?{cmU*8A?k_^`!>A-cVCsR$jBZ%3!EkU0++fy0*5y#y}qFOROv|sfzOQ zipq-W%F61x%F4<*TvXPHDm6+Bq+dfdr7$FumTE+r8cJJ3rT-n)vL=0%+74b8Q{(}M z@uP*o6&34BO7&&s)M6&=EZPjS8c{`*R#T)YF42`1YYjGF)@X|!Sw$CjJQ=8M{Lc3n zUEL3U_)6!uHm)9geazO%KX=_*(lk5t$<5yd6ZSRd|K^W2`A!Unx_)v&a6I($owwNk z`rGNB{v!GQr~XGz{^;=EJ%9D>Uw`&j7k~E72cPc>@B8CF`|C^p^zMiM?d%>I-~atT zf8p={`Mv-Aq6TR+;B67{sYF+7CoVifuPOo$zSCG+%zW<$b=ZT~2d{r(+clOtU=q+=UXB_{*%Ax zGf(aue(TzApQ+gU``0fX3pRYTckMf^v%TN$-8egxe(Uq$+1{qj8)tj_)Bh^Q3r5Hgrg>Ma6_hXpCxDp!ZPCk_?18MU$-oc2LAq459J*`P1)5$d-WJ{t>)yp|ZQb zLJ0O~={wJVMnL%A9e8NkV|AD^|Ly=xAniFy>=&OV&S@6xo6c!v8VkasqqLHbu#3v$ zpf&TYug_r{jp@-ka5?iWju?QX(3c!<=8gylENj279D{fV3%5&Gsc8!$G4 z{0Tpw=^JmR8S6W|u`D0)xCS|L3=mH;ph+5lFGTiNbEEpb{5FAtpKbGKuR}>ATcv4SA@BsiLR)p)p=omio6k*_TJrWB~Nqkb@8{5le z*Q1GeIHw+rV*5E*g{OGDKrucmJtkn7L{y{`=5x4r5fx(WOG#BRDh3G^Q;>_ppv5 z{rZObx=KA+19%4$HMRQcdSla;7MiwNey_>+P>r5gAc0Du;)*q2+K5PON4>!Zk5aJI_&m28=Y-aip9~$X)H9uSdKPV`qU(;moetOS=!^cmaI`fyO zPn|q|IJM`gZhO-j_`O3FRhwwv$ew49%$~k5H~-T73+GRtn0a>3h}UYWfe6*tZ*Ci4 zQ!^*e&AoEz@~f9Gy)t+9`6DTIplx%#9%-tKw11R8eB$i<#Vc3exc0`+u3VZwd*U!Z z>Sv5qT3v0EyFYyJ_~{ofzIN^UyYIbw{ms`dzIf*N!EnF3sZOV_-}3lKZ2IJdm#o-2Q{qgM&Zd|{5>4j5AlB2$s zhH}GtYxnNd(bMyn-+1Td?a#ja^!Ckn-ncw}=4fhnH@&W+VgudtOzPN~mtMX0-mmX` zk^b_|`@ekiA6`0hEEVWsHdGka(cQbJMD1Ik{n_nX@4k6?{E#C^}5nh{2QmWFcheETkL`sDeSue|y8jho>4+u-?y6Nh8ibG@#v$~tNSNapvwC+$ zo}D>y`uyDd{QTVcGqW=XVa8}vby30jhG<#a?COT&^ug&PM~@wynLfy~Bi&Aj7C3+5 zfWWcl#jz$#9hgdT;n4xFU5Ygvi@Kah-PP}ZjK-0g31l4S#?1_g^KL25D@YuZJs;MW zz9zFuTDEL5K2)uTAGX+VW*HUftLp04uHS&ySQV@>Nsp`(BB;@ok`-F2gA-7O3xwuD zKDW{j@{zsIID|(d`$qhRI(e4OFd`GQR!A|DZoV7?Ch{j4$Rqi(O#U=eAkRSOc@s^+ zOj97$6v{PJfn-A!$Tp}zxVL(QgZ)>OAcLRDv(Z>jq__GeJf3N}FOsA>;LGa6ox`)zKnowT>h0r}1+gGS8E0N-s zyD(rPTD3Td-b43*^%YvRO!~YFn)gsczT9~ityv*+f?-s@;_2)@IJ-Xo@yu9g-GD&; zBU&1*&wogRUqbnhX~sgi(^x2Z8VhAlW1;kEER;XNLjEIL#)lO%C>ce*6lyG#Lo-OO zm_%VN@*m#Plp*K%c7MJi_xPqH9`YyB`{?17&v5tAU9>je2@V!f-Lgp(FEDW_)-9Vu c??JYp@Df|*_y)@twBZ*zEVQb{+4C;?U&-?f*Z=?k literal 0 HcmV?d00001 diff --git a/data/sprite/hard_particle.png b/data/sprite/hard_particle.png new file mode 100644 index 0000000000000000000000000000000000000000..630fbec8a913c620e3db18e01bfec32f565bc08f GIT binary patch literal 890 zcmV-=1BLvFP)kdg00009a7bBm000XU z000XU0RWnu7ytkOAY({UO#lFTB>(_`g8%^e{{R4h=>PzAFaQARU;qF*m;eA5Z<1fd zMgRZ<07*naRCwBy)@x4`R~QHI-yF^^0}Ev@sJO0DgId#sq>11E3$*ErMq><>MiD3? zu#3R5mtm(bW|m8}70ZlGNd5(fL?TE8xy-p^hX!Jlsax^0HEnfsDgs}D!;AzIBWJok zcqI!mO;0^??|JvBvY@VIMbo_M7oYBH z6p0xXH7#wAECfGTDNo(ZH^5gU(X5)LriG}getGJWTTo2DM5?Bx8ZdtezwJr{RkIc? z1W`0|D{VIpR!|iR2b1pvU#Ag_jE&?b-v!N0To}rNfxe-UiTpd@7cwJ5lVGUl%)r=0 zehZSkR6qAw&mcJT$&nKy*)2WfE)4V?`J^8VY}wUyV(j9Z9t!=DzC(NV^@3>Ird?fU z#wL7|V(eVkt`E8fQFLm5V<7vRH=7Z7@<()1jK-z^|&vbS$Z<+_)V@<3`T) z{OzsZy|(SNn2E}!VFCy|k%U#7i{wz-z~j9a`1VQj@Bd zxsVw;ab(w)*WTH7P|C(6Ps?m9R_eKD*0eRvn-}FS44pc*kdg00009a7bBm000XU z000XU0RWnu7ytkOAY({UO#lFTB>(_`g8%^e{{R4h=>PzAFaQARU;qF*m;eA5Z<1fd zMgRZd9}EW)>B(2m zzIgIzZK0H7(m6S5?7Ukj?VMz0vn-_KOG{fXzWeUk)}xhW0vPqq4)*?7sRad2F>ov) zxA5e}4?q3z)fZ0|D&<`h4?e6?YiE+j{om#fu-d)|M&NSi)#fE0qF9or{aJgE^R$)*gNF?3-_%JX)z!0kMgr{tm9! zIyu@q8-rYF<FnU$ba#A(-2xZYjfo4Ji%jEfA z@|^GfKmFnA67$_vcFip}nG7bQ(QHab9B$`$Oo_ztn2hFNG8l9^z20a@{xhZIBgmAV zjwZcMr!!muIv1_8vrcbFtjq3-n+%EngHEq?(Q0+3pm%Y0baHUK$u&=%j{tFTj2mfu zaCUYv29xv6Z5j{B2_tPH6JnQQp3ztudyS)uA;`4$8oMN-^2WOI1~3{^nR|_QJ3Eb| z-V|g;2Rkbd$u8oOA(1(XsneX+* zG+JZy#4CAEvJ~rRZ)2StFxqGrC(p!T3rD}z@;j(q!3lK=n!AY({UO#lFTB>(_`g8%^e{{R4h=>PzA zFaQARU;qF*m;eA5Z<1fdMgRaY07*naRCwC#ed~5=Igjm>WU~AHpSCMl()l6TmKTd# z!miPM`rBhr5J&}Bnx48#4)$a z*ugiJ_#5B%e=Pt$dokz*fzuPg|C9mzsfxG-g#T{2QC1zs~@H=%XO`90UAD_(ApjhVbdhYtPu=9R|23@OzK_1+X~W?bp}re+&SA z`bCzG=NE*14*>iFweefwSK-ckNed;g|CJ9RPkJ{Ks_v>Wv>fj|ZT7#|F43tfSD5K=3zipHU0H zkm1h(@UxSD@6^8|biMh3fufjb(zWter0FoInBIbGe<9$~!iHN30Mo0h)FQ0rWR{pCe{!HlmjcC7t z7z_rOr{F`_l@Tyc{$zn0uzH|j%EMsppcK9jAebc(1fIfoU$+H-3WPH-++zgK?}@in z!kZvye^vO8X`i2*c-e&x23SB~P@Z@u387QG&KzKf-%0(C3WgLt0p!5|5)9y`;O+`@2cR%FxakQH^Z*9CaS3k&M||))J`RHR7lnV%(!VG8vs-|GSilrN zGYCU@FED>za;!K+J7u`Kthl#;1u}wc)XSXA>v>J;s8d1V5%TO zf`Jwoe8a%B_6HC)qpwoD=Sg>kQ`|Ef%rx0R!KN@`m@8m;KbhcoJ*s=jcW9sg1_;`} zEc|;qUiK4DEd|^lls~8Nk@q|R!6|ge1VUv19!6m?1PKJEJtE+~^lf+makF{qeLMN= zLG$LCl&@e?sB5qRDc~Js0;3K#hI@xQ6%d?VKYQRQd>jOyOa{Fxd^rF>01_}ls3FWD z_!iHRK+5x!&xa~FumjO%B+{gQN?_ALh$#!7*56#bEv%2!6x>@16c<(K)(9^_A|&sc!2&Z$twWmlSw_Vt`|+ zfCNbxgEIC1VuZl}>zWUlg9-~&zdt7nmU;k+KJb+BDwBp~WAzJe^6 zh>fA>5r85FEJ0xUAVh5kPv9)2{VNc(UkU#xE_uLW~ z0SFc;eo60j37k5JzF`!R zN-~Pa2v^iV!`!&@?;Z&F?QG%+2!3Y+tO(yL)_xU!%hNedb)ND<`^;Ynh6dqFf}%mB z0^~B8AXGsEGf!IpOvwa%L;o{B;43NMB?k~7$P*8bQx1ix!M($*gI!^Ef!P)83biZP z;x)A^NEtSb((h%LXa@<>76<^C(u1>XcZ|jnEV9qo06)|FxD(_#)AxDuF$}o8Q3#nO zh0K=(#04Z52wx@`c>BG!)WJ|0!(N~fGoVxn#Ri}vcbd#UV*z{MC+??<$HUc2>emkQ z7Jx8;*%hV%N~8SQ7^VarU;u%9xus@+^DsZEfp`Xlidui*Hon9H?Ozi9PW$8ZuKOX> z5H!mdm@W`muMJQHP!?k>VDN4*Qn-Kw2{n+(AuEo;3Fs%)MeW0S|DNHuPUaN8Tta|T_+NR_zv3}}h0x_Ih5iMWhL-o$W(Zc`X(+A; zBCY!a0}T3KQNZE?jvImj+#Q>a!;9`Jz{%vLc1_PKYTrM!{5bH2&FiwMy8pN*XBp?n0 zkVs&fpTP)9A$+7#&>{xYy)?lYfccY9?lG58vyLAIzqP8C2IC|U;KZ8Y#u1TtAb;pTDjGr42) z)}hv6+`-ns{0*~6MyW3KqfDT_7o#u&2fjiP#g5AdmO)`H?6f6~?w7m0p$gtf2S4)n z?pS|mi~SMz&k`cl*0gI4(bmZcexVGHE@3T1kjL+Din(20o^`hV2|3?ooW07xv#f=Txtf`>W4UR=|oO>j)N`vifzzQO|S4a4u9^zMkv#VMU` zo-7N$6)RubAG8#{$e(%YTT68y!3wQiiVA4OGZ2WRogtjTA)YKiLk=~9H>+7R|1v;l;1`sD&?%M}bjgaIhT z;1+5i*nkoQOZc-;ltTHI62TG)Ojpo7=zrJ%=8!@E!caA^=IJK^*)`qIQIs;?YrPK< z?bXQRjsWqMg7#941c{6!!=ZYwmXJ>J11}kb?*QOYz-RaShtQddewLQT-}3QUiq3C; z(5A5e3$1-k07#3|-_j;%txbcFo1jUm07>01ob~`E8|3NdkOYsiuu%>+9078euX6Cv z{~D*y6lP)IsR@`hPP}O7dF7!05$Piug>2gZ3qLBJA;Y$+jRhfiz~Jg&_N-1exXHmK z^g96}d;$RP)a-L9<`|ZEoO~|&UsC@MFJN>1+E=K86n<+deE9=u7c{vfC|WW>3*n2P zNuGRRunYzea0)*F5cJO(eWUFfd6of$1s?H#hgn1O)cK}hH8fSID6|XIIyx~#7`;9C&KUxy%UPi1^72_WVf(NY7wC z{zAKiA=oZ0Ss>Veg+m>PBpU?%vk)Xz0TXR@03iWUBY_0~aD?%fMpv+a!GvJuA$$(2 zU&r6gn8N@VCEHApg?C6uBmoFWGIujmc%77T!346wlLmcZIkDxWH{xANM$5F42ud;enWPg5c;8oVCoK zMhDxAr~ixzN`D^9YSx&=7*VY=?EyIybI1jaTM7H7kKeu^mvY(@AeYu4ttBfoTrLeF zgm#ex0Ea4ILK6yCu&96t$Q=N%`o$JKJJR=06<`ii2M-odhN?n4p%a0U{z3OD^;Twa zrkG({nz_j(s*L;+v4*TUWNr}eaKFGw;T$DrY!XHxjY<+eX+gs)EYMys{(x8d`Itt7 zMoq|+GA|mHJ(qd%2O~7OfZIy)guj1T248DvmlXb`<>{xw0*uz;^f#eY1u1+XNO}U2 z9so1;s-TjG3E@-P0R+}y0Wpf;4@aV9#uYk&sE7MofwiTVmxtjk6HAgc2G%3mnC0d! z3bqawg?j_p1xy+-2G43LW{^jeQ?<&$?$`l$jluS&@ps$Uv6W-b4xYB|i|_{v$Y6tM z&0i)1G>Mbmq=muP($y=%zqB~*mnnQnK%@&;Zk#7yB!e^g(K$15guPJGY=wf=9inOd zn}a)n*(0>yY3lvZ`8uyB`RX(9ws#hpUAEJ2)18+xa9A( zMv(LX+J$ks1pT+k0wL@s2z!7DELbstQIrEZlt=lj&UGW0JH!AJ&#*5+*wK3cfXQ%W z0M}zmsjZpe} z{^`|dV0$8T+@u;eHkcK!K}+Bo0hFl{qL5?R0>dzz8NE~kBi7G6bpJTrEl)h9{+D>| z5{6&91Otdrxo5B-JOTztLIsS9tXd&lTyu;hoM}8(1%w4E292`*Y&@Y;E6LHrP|F-b z1e7jtcSuI}nk!rl$_kOFEFJ|WA`NCO%jB;sz3nYp%t+wGX%Fb^jP^lM zeE_eFV43O8(u^^iG^ovbe=hZVBqNqTz%W=Lt@$mu6T)woIQen<8JBV|jc~bC0HiI@ zC_#X%3qYP;Y}R^&S4;zEK41mnb2p2BJ(iTV8K39Zy_~Oz9*{ydEFyfObd!KT`#?1%8-# z>&u%!km?eS^l;Ysr&<^V9W$$!g&m<1B!fJRCPDj``Ix60c)3vi9Kydegz+apYr_sW zr~x{j+&uk;X#ko5!VVQeSLFAzEV#(&9WDoVPnN-Fk_KKz6Rknhg&ZectwBtndI9rw zuWT9ywFtx*#>>@qaNYz2h2I^Kx6AQh2ecR5zdLL|2Rnd@L0HB@1FxN)X;Q`j! z)xSVNDR@G=Fq$CPAcZe2^uI`g;GPP|?`RoncSBhH9MwBSUSbFtgD0D6BTi!ezm5a} zLz_d4(M%$ms1g(}QA@xQK0WIyX|ud8G$v-e;-lnGyIT4sfgg z?*)Grl~B&)88dpQ8ijLMLlj7o$m;#0!3Lv-FN91YQ{a(BjNF0hjL%KPjcAkeWcEF1&;EYb{Xf~QNc~Czb^Xkmm z48u~YMF%}MO1V?`TOud3ds2ct3jjsUsejDy!vOoahn981obOx zvYi(88zp3G>Iv6tMs+3| zE^LrD9_iH-dj2fJXp;#BD{u&wQ}BWW0;UZR%&=?#iH2dYlxw&5g}^xJoP08=&*>r# zcM7XMY2PzhWIS>OnSfG`s39?gEm^M5?@Ge?JEfMOToWSNdmD)llTT<<0RtfJkC^;n zE`VMx3Z-rQY7SqR4Odkq(5%akjGPq2^DNb#UpmXUD3doLSy;(S9Z1IZhl-ddI|bbs zoA5bRg0vA2z~%~88)&$JW`Gd^=Cgp4yt;|$DTwE(A8I22sv`@TH_{P~7If)*(+W{( zX&?95$%O1@97f-YVwIM699ZDhuMN zNZk^pz=>vFp9m8K5?yEUIKdDGVw`er!3Ki`CIF-uf><^AJ_Kp2c>}TvIV6)rZUke8 z2_P{949WzNh#82+_n2Uq2O%FzHjGR|Z&~uFY)KqPYp`lwPX@!VgMi)>{7-TLHJDIG zu3>Aqq*4RVv`lU~+-ALg5&BHs66JKTRmsCN^fygrVGeL`!;JS!>iA#-n)<&bD5L=3 z$c$P%mr{$JrkHb(#^aILu>MB)FTcM6@|Lm*olhw zf&Ea`DW_DcT7ERb{nf^hLp*tV&m#Qdjua>?VUNVcXwoe6{AbPHI6YjMB>yyT|NDH* z#~@EssPDo$0D)vBb!Jg$sp_#b`nJEFbbkaMXua=vJi%SNgQygxVs$Ek_L~>aV>|I1 zQ`V8U_r|=?Cjf9#GCo?YeH1$vq33^`PC#?~Xw?$W3*_aCq$b#%QYx{0&{m8&;#A8H zS%TS4tkhGR-PpDgLY}AHxUf+EBG13?(*ozL0*R9I6)vk&bg0yzZ7w|a#8l0-Zt|sl zrpda-ftDX`?rCqF{My-)8ijWKe=A@*lt|Hr-;GVqOqVUrK{pvZDtep_tXP4?^+S{I5~Z^m#O>e~yN6au`FzD`>mjUPj_~TVz`I2uKU*K~ z#W&viPw4Sm)445q7khsC#^s{=;qQ*mwO^<8d9j0K28DR3m^w&_?!{E@fc01txV2|E zZVE@wuIc=JI=LiXuDkD_74#!3fS-x^E}(9TjLFa2i|U0ab9nJ!hynRlQ@KladLZ{d zsUI#Voy^ymfgfFu>mTw*i^GnEq&O)oIQekyIByWmAw_erL;XDEx{gEUqBUzjjq=DkuB=iYWbHCaE%6z+5qzc z46NrXbL3#ruqV8L!|VQm)qz|GQeu8jpVznk-ekNRs`A;@R3!5ni~lMPP>0)=4?P4* z*K2rp4C-P(DF(Fy1Ydyg130_@ z!Z!f=R#sMJW%K941^EGN){>W91+|m74vr{;tVYTkWV%1DZ;2+3B#Il zv5jBHlHBTx^X)_|;rkriXjr{^X4$l{rDRtP-x9cIQAZYdXkp&W`c=JI0iZhS0RlLx zz6sy6adZdF8ZI$eZ+t)ZWGml>Pt<87u)J=C4SX{O>P&&^lKTsC0?rp(U(u zYFk5A%dyIO_@mI_WebPjv}40pfBv}G^MVghYyzM>?K==uJ-c_dI-Usd*r6f_VGs`L zA0UY4(0G3QO)aHgdmFhIaAb#ZP95LCZaeMAUXr64SdF8rXQ8s{0$Cky^;mW>1%Ge%?f7t(A68I5aD3WEjxA`-6+$zp1A~HBU-!+e-K*X;1WQYDqy;U zK2LpDp!cMHO;#xCK@IWwG==ZJTFJ+B5sQ5T>hWDhi`W`KX>&-#+)6U&-?facU2E*t zVhh_nic^*C&*uX?9xr;bSGO8^ThIH@iFw+wR;dlj8!J}K$_Umx5t>67qj!#=rrDXB zLf7cj5g~Lnurf5~Fj5MC1t4g%^4Yu;gU}%z?3*{RguH~`r%DK62N*1VC)>zPE!0s8 z-E=g8CIgHn5g)IuMO1Su3DzbuD~l-|hv89my7`SA;18U*y9{tFeLh1~qHj38JgCXC z_igDfOQpKLoR+n#`CLxi8k4ugI4_t$H7P%*bZE9{ix7Hu5CgqiRvmkZr>`wg?tlv| z>bDX4^_y6_A6>^O_;}nE=+|fv?Fw|Yvfs$o)(J$Knf8*f>Bo=Gpag*}HPF@3d$3MV!3QV|+BNmQLc78G z7$%j#R!xA%h7r{qJnic_)3`=s36+o6e5ui)CWcWM6GzIVvAYS3mu3X{3n1|M=0jl` z8+gpQDZ0#kjaH97cY|w(ie)==d~xR%^Y?l*PO4}wUg)J(P;(aFsGW-%yDKAxCuO@< z3`+LW=@Y^?$r`><(2)U@Iw{09|9%C5!Wc|-phNhEejSWp2@ZpHLsw0PP;~1P1ZoQ; zrW!yP*TD_yeX)V{*h=P$1uz+*s#-6Ls{IAwKWq>8Lkkoh=#*Td2w7{@T&Le}n_;s? ztebVXUSZtkz8#ew{c+ME%FrHj_U|tJvwOEdNxf^j$&ab5vc1n*<*eYmqSv>5(E(+5JA`rQsM)}3rK5Ww*iK4 zpYed+!>!Y42RN?RCH&qE*Pa6J9xTv9$e|uW(ApKI*9nHn0@|V47YGs*R@9}N!VtCu zh6hy^UG1_uj|)7V5p=ti;<%iEH#Oh`g|MV3jbR4XVu!BB7dCEF@MFU!TLas5ZrD(g zpn0yHG65!@)iQnCyR_yl$Iyg%ikqSdPby~$4}j?&1MF^~<*>-%+Vv3`)UfvZ)q?s9 z2(XN;44N%#ziXcIu4xl!+^&w^J9_W9 z_G>dhZE}?+K16cw5Huw4uDZiz}k&u*0dx3##A&0G1tqJ8GcLp&6E_ z#y(t%wdtPoZkS_*D(e+WG3Q1-PJXZgqidTj;>--{f#m9 zlj5~sVf`AXUIz55Y;R?^ko@?qBD3+&X^Ym8`+^7GOVBfvn!DoYo&h3HWmr^2= zNatKzvum_Jdk6UyrVTdMsg2btxoT}TNfNKh+tPt_Gq)No_N^i2@w7RXWsG%Wh!L!h zX`!BdhCau*6YceSqcRvJes3ebpP}6VK=}AQb$-x(&szvwdxu`HDb#D8^zZp|?|DB} z!FQhi0EGI83tac;>GZH{hJnxZ{w@8VM0(o`S)+T=L0h+E0LKUbrQWBQr%&49Bv8J_ zutRNN3RWQco}IyKr2AU#M8|UI@-e~#85y*k%+)xAp9!LH{c3MsttO@w+8%k8l5(DE zKz-&UtQ)#n3SJi=ChZRu@V#iie?zZV^!^>JpjVhBAbO8$rF{y&8p-CvLns~r_ISYk z;Ez8`_B{fCm7>w3XJDHeFgK7{&_5}Rf&qfQ1V&e=2`E7kz=)OVv4vyb@JTuPq?dB& zS#?}yx<;LWlxQELxqvmsyQ{8=p;Wq#2)83*)}LLu_mSL*JwfRTng$5e(k)Ek>oq}< z0`E!x{hujx)ho1z;Cug074QwU#SQ}uuGKJ%gEb98Tai~hv;q9&wU>>+dl{vpJupC^ zF|^1#%ww3L@ik`J@EDAgJN0tCYVT#nP3C8Z&Igx%fGf&^=UQ`UfVKGBO=b>Wm|_x# z#H7zTBCd2Dp_Z1@D%%?6R<;#(j3kw3JU@N>boIIpRd5Z#>lN2=;`9;#F`bAanV`K1E75Iz+NZ0}BIbx-QjDrPoo2K@% zOj*~8cPfh&X^v#TR=4HuXwByYPBTX{%aa;x(u6>25aG4dfl8BRQ7cY+HS?@NL@n$H zgU<#4%)*k_;qDoI65p_p3N9U-?30Bg>1@m)R}7T(HqCTq-h;{d)X|yoLbx9 zc7e5=>u)yq2AL38B!@d=4XNOLZCu?Nf=d6jjB6N!nbM8OUKoMX*Vp0V^#nkkeEr6? ze?za|xb}ZS(7m4g5PY}q1q!Tc*J(NpZSQ>hLHH)pDfEdIUrO%~?E>FC(!@T7}2Z5SFSRpx;i&u?TV9MorPvoZrlP@~+Yow{Ec zeQN>?p>*GyK>&lu5Kg8{{`AEh?YvKT`Z>XGbm zi+I0TVSwr9$0?5h(9-u^zoGrV6Aub^+K^yT+IqJMu@3G{Ab$eAs0 zoJ^f+zzJrVa&rP%PQcI{6{8HTjiJYu^U?^nHH*MYJ>3P?A{8o}Ll|33A5|M8=M*dq z>V2(@;j+(XivsOMmB%XOVQXj#A|iLT0MLhPmzIB8`{RW7Li^+NhYR=*tp8Iyfe|w9 zTWEOI31n9ACh%hqYB%vI0MMP-@^MLl!cy=y=L7XJNMg+dP(5h|bN`t6?r5E~_eRpv20#1WV1pr&G!pyp*Fw}}r`bzSQc>WZA z(E4=!CIG(C?Y|QgB|?~9fcc6Zh6~td5a>1Gm#X2kIgdysMZnalQYda^ydq`K1+&|17Y zPi0Nz?f~dB;UUg;iNrc`5D_+?XX@Ay%2n;h<`HfzhVGp5fQYki?AEv z+sWy_2LyXk;r&AZ?8PFFQ~`=>n7MhBzG+;y_33n2WF%M(po@xg$&z0}2aBcNkF6tW z0-*+#mSy6y1}!rmMy#d^@HI-t4_iI=#*9_1TMIZtSP`L9TUor3^Vf9gW{{`XkZ-xA z@HK6S$u83XJf8gL)<3=8D*kSF@L5~{c(kY9Q}DCFg%0EF5J^3eMgLwpir5U^G|SFI zAF_2gUs4FMfI{gC$?Y3NoXaXG=kMN*sJq!7UP`8BV`^>>4-jhFA3X~`gNoC!3b7@K2!L%ibEpoyVAY^fJZg}-ZA`s*B-u%3%-*;A{@t+FX7WZo5T;( zIFPGa#}~}?YBLKa7Z@wpFR@X}8`v6@ud#T+a%r{1a_5Z!%0wCu}&h zx{VU|Qt51^Tx+Z2vs&;uvU-&fI(#O5ZWBJ|!haO-!J~BV-T5AO%EEsF0zL^8+1>wV zrdk=0?5IUCzG7{<0UQDliBrez5^O*rgjh3k*%wmfKBBqAGOIthj}z*t>%0vTYuz3#%xHRnuXn{xK3bfL{&3(Rph&`XF`20=!o1 zX0?u%u)k<+MEJWYXyhc$w%`+O9^I?z9DJ zmF`N`sPzovW)-wwPgTX<`ZYqlkA!b`t9;%O{>d1jcJ~+GQw8^`PVRwV4T-WcJSrHV zZr5(B3Dr7CE?EyZ7E_K5WU`i5>v7EruT?gi2e1!<^erh=d+!G*b#^>qlx59sC!KWe zUQTxcL~AwAtn<@Z-0|4Wd*=85SoqH>f_`cboPmHIOdMgDJII#n#WV0@q4PZrJs&I7 z$0p4vJttawcj}rNP~Bu!X6u)g@wFa+{5bn)A8Ij5-`qxVW|W3GO6hi~-Zk&p2HuPD zs3*qWnYC|Es<3`7{NLLEuY;f(hJ4z^I>N6YgOh{ZK3IpO~* z4)7ieyb1!pZ#25YTtMV-mS6&x=>DC;mkju1*3TJ0(7w^WYHhiFivfHXf_^(=c+LQw zIRV>yAiV%!4tTPygLk%Z*E;?>7wI&s_iC8;ec?YDA4vYne8Kmv%(G?F$LH()`Hgdu zGecHThxbK_48Ga1w=n?fIy-D5+jA)Yq<@r(<2u}GHeh5c)@;$vW=)J-fmct#*2ous zC86`Iy!+$Ae;oin2f+~tXU`vjFdhr$2C}h;a@$5qYi&I^SGs3^0eIiDcUVkttO&Z9 zt>a||uvxJS?7##iLg*gGq1)cJHT(DO>G(5a4><4i{iyKY1HkV=036rP-vWdmGDB?6 z+Bayl>thiW$37#}WsImRVC(Lk zvzmZanT$GwX-gH`9V6|Xll~dX|Do`I2Y{b~V5tOpfeq*dmN;8)&+`LPJ@Ly7yeVr5 z`@qY*AYb_bKPI{CiJSJ0(BCfP{FLy24}hOD!6P=f2L{{`^znE2q(1SbEt(HX-Oj3? z{3Hebs6PK*$@b4r{V#?8836nS1n%Qx@&kcAVhTJ^C%`)^)gR|K9u3Pr0|Z_W{M!Yb zpBDb-0Ps5|c%~Nc1OW8pw7)C-S3v+g?#A(l0QzMb7ax+_=K%0q5WIGZ-+k_$;OU9b z?>*-)0OGmue;dpHpv*oCfX{&7T{=G#_M_L{Wq?O)@Qf)wEBFr!|L*|clR#KD!eOi9 zQvtt9`>*ZYd_HpZ?DdsM`qZ`f8)Euv0QejmJYt1sr~K_@%69P2gMX;M3ptZ;I!y1K@7~;ap9e8G)Z*fO8}8ZwUNf68ygpfWHZX zvkSMg#BTtw_YMA=-{Jo|0Q~3Q|G0nrpG)w6```Z?0Ola5nFbk3oB#j-07*qoM6N<$ Ef|rvEX8-^I literal 0 HcmV?d00001 diff --git a/data/sprite/soft_particle.png b/data/sprite/soft_particle.png new file mode 100644 index 0000000000000000000000000000000000000000..09c56436d70be18238f12882dc58cb54b1bcae62 GIT binary patch literal 865 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 literal 0 HcmV?d00001 diff --git a/data/sprite/whitelight_static.png b/data/sprite/whitelight_static.png new file mode 100644 index 0000000000000000000000000000000000000000..b15444a878fbc48938e461472e7ca507700597cb GIT binary patch literal 5487 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 literal 0 HcmV?d00001 diff --git a/data/sprite/yellowlight_static.png b/data/sprite/yellowlight_static.png new file mode 100644 index 0000000000000000000000000000000000000000..78331a73df9754c5d21bf5ca5e291e0d7ff0fcc3 GIT binary patch literal 8531 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 literal 0 HcmV?d00001 diff --git a/data/xml/constants.xml b/data/xml/constants.xml new file mode 100644 index 0000000..4b97529 --- /dev/null +++ b/data/xml/constants.xml @@ -0,0 +1,6 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/xml/def/commodities.xml b/data/xml/def/commodities.xml new file mode 100644 index 0000000..d5a4c91 --- /dev/null +++ b/data/xml/def/commodities.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/data/xml/def/components.xml b/data/xml/def/components.xml new file mode 100644 index 0000000..44c7278 --- /dev/null +++ b/data/xml/def/components.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/xml/def/engines.xml b/data/xml/def/engines.xml new file mode 100644 index 0000000..b75e3c2 --- /dev/null +++ b/data/xml/def/engines.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/xml/def/factions.xml b/data/xml/def/factions.xml new file mode 100644 index 0000000..7b4dae9 --- /dev/null +++ b/data/xml/def/factions.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/data/xml/def/models.xml b/data/xml/def/models.xml new file mode 100644 index 0000000..42935d0 --- /dev/null +++ b/data/xml/def/models.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/xml/def/shaders.xml b/data/xml/def/shaders.xml new file mode 100644 index 0000000..87c9135 --- /dev/null +++ b/data/xml/def/shaders.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/data/xml/def/ships.xml b/data/xml/def/ships.xml new file mode 100644 index 0000000..5f0d76e --- /dev/null +++ b/data/xml/def/ships.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/xml/def/stellars.xml b/data/xml/def/stellars.xml new file mode 100644 index 0000000..15cf568 --- /dev/null +++ b/data/xml/def/stellars.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/xml/def/systems.xml b/data/xml/def/systems.xml new file mode 100644 index 0000000..df6d0af --- /dev/null +++ b/data/xml/def/systems.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/data/xml/def/weapons.xml b/data/xml/def/weapons.xml new file mode 100644 index 0000000..7b23f39 --- /dev/null +++ b/data/xml/def/weapons.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/xml/engines.xml b/data/xml/engines.xml new file mode 100644 index 0000000..5efd993 --- /dev/null +++ b/data/xml/engines.xml @@ -0,0 +1,20 @@ + + + LX-20 Light Freighter Engine + The LX-20 is one of the cheapest, simplest, and least effective engine designs available. It is commonly found equipped on the cheapest freighters, where its general reliability and the ease of finding replacement parts for it serves well. No reputable dealer would ever stock an engine this decrepit, but you can probably find a servicable one littering any spaceport since most people consider them useless. + 18000 + 0 + 150 + 3800 + eng_large + + + Plasma Vector Drive + This is the engine equipped as standard on the Kestrel fighter. Top-of-the-line by any account, there is no downside except the price. It's small and lightweight, and it provides tremendous speed and agility with a minimal fuel cost. + 0.05 + 25 + 22000 + 550000 + eng_advanced + + diff --git a/data/xml/factions.xml b/data/xml/factions.xml new file mode 100644 index 0000000..e93b0a7 --- /dev/null +++ b/data/xml/factions.xml @@ -0,0 +1,54 @@ + + + Government FactionGroup + -1 + + + Neutral FactionGroup + -1 + + + Criminal FactionGroup + -1 + + + Colonial Alliance Navy + 10000 + + + Colonial Alliance Police + 10000 + + + Associated Worlds Navy + 10000 + + + Associated Worlds Police + 10000 + + + Evon Naval Forces + 10000 + + + Evon Police Forces + 10000 + + + Royal Victorian Navy + 10000 + + + Victorian Police Forces + 10000 + + + Independent Defense Fleet + 10001 + + + Corsair Navy + 10002 + + diff --git a/data/xml/models.xml b/data/xml/models.xml new file mode 100644 index 0000000..3d49092 --- /dev/null +++ b/data/xml/models.xml @@ -0,0 +1,101 @@ + + + + star_sprite_washout + + 1.0,1.0,0.5,1.0 + + + star_sprite_main + + 1.0,1.0,0.7,1.0 + + + star_sprite_inner + + 1.0,1.0,1.0,1.0 + + + + + 1.0 + + + + + kestrel + kestrel + + + + + thor + + + + 0.5 + + + + + + navbuoy + + + + 1.0 + + + + + + + navbuoy + + + + 2.5 + + + + + + planet1 + 100.0 + + + + + soft_particle + + + + + + + hard_particle + + + + + + + medium_particle + + + + + + + kestrel + kestrel + + + + + planet1 + planet1_earthy + 1000.0 + + + diff --git a/data/xml/shaders.xml b/data/xml/shaders.xml new file mode 100644 index 0000000..be9262d --- /dev/null +++ b/data/xml/shaders.xml @@ -0,0 +1,22 @@ + + + standard + standard + + + test + test + + + sprite + sprite + + + shadow_pass1 + shadow_pass1 + + + shadow_pass2 + shadow_pass2 + + \ No newline at end of file diff --git a/data/xml/ships.xml b/data/xml/ships.xml new file mode 100644 index 0000000..d2f65dc --- /dev/null +++ b/data/xml/ships.xml @@ -0,0 +1,83 @@ + + + Spacetruck + m_fr_spacetruck + 55 + 64 + 35 + 15000 + 15000 + 2450 + 0 + 1 + The commercial backbone of the colonies, the Space Truck carries a lot of cargo and does it very cheaply. They are perfect for trading in heavily patrolled sectors, but in rougher areas their lack of defensive systems and voluminous cargo hold makes them an obvious target. + + + Kestrel + m_fi_kestrel + Graceful lines and top-end equipment make this fighter a favourite of civilian pilots. + 215 + 120 + 200 + 480 + 5 + 28 + 6 + 1 + + + Thor Class Battlestation + m_bs_thor + Massive and bristling with armaments and defenses, the Thor class battlestations are the Colonial Alliance's most powerful line of defense against any aggressor. Indeed, simulations show that in a best case scenario one of thsese battlestations could potentially defeat an entire foreign Navy single-handedly. + 451000000 + 6000000 + 140000 + 28 + + + Nav Buoy + m_misc_navbuoy0 + 1500 + + + Nav Buoy Test + m_misc_navbuoy_test + 1500 + + + Bob + m_pl_bob + 4.5e22 + + + + Starter Pack - Not for Resale + + + New pilots get this ship with this equipment + + + Thor Class Battlestation + + Massive and bristling with armaments and defenses, the Thor class battlestations are the Colonial Alliance's most powerful line of defense against any aggressor. Indeed, simulations show that in a best case scenario one of thsese battlestations could potentially defeat an entire foreign Navy single-handedly. + + + Bob + + A planet + + + + Nav Buoy + Navigation buoys are typically used to mark a route or a specific point in space. + + + + + Nav Buoy + Navigation buoys are typically used to mark a route or a specific point in space. + + + + + diff --git a/data/xml/stellars.xml b/data/xml/stellars.xml new file mode 100644 index 0000000..60def60 --- /dev/null +++ b/data/xml/stellars.xml @@ -0,0 +1,9 @@ + + + Bob + earthy + Bob is a rich agricultural planet, with a moderate industrial sector, large population centers, and a stable ecosystem. It's a pleasant place, with a little something for everyone, but is otherwise fairly nondescript. + 7.4e24 + 8552.3 + + \ No newline at end of file diff --git a/data/xml/weapons.xml b/data/xml/weapons.xml new file mode 100644 index 0000000..22bba03 --- /dev/null +++ b/data/xml/weapons.xml @@ -0,0 +1,22 @@ + + + Light Blaster + The staggering ubiquity of the blaster class weapons is due to their simplicity and reliability. This particular model is one of the smallest blaster models, and it is more of a mild deterrant than an actual combat weapon. Against any moderately capable fighter it would prove almost ineffective even in large numbers, and it would be absolutely laughable to suggest that it would be of any use at all against a larger ship. Still, they are often found on shuttles and small freighters hoping that any agressor will see that numerous guns are mounted without bothering to scan too closely. + 250 + 0.2 + 2 + w_lightblaster + + + 250 + wp_lightblaster + 1000 + + + hard_particle + + 400 + 20 + 20 + + \ No newline at end of file diff --git a/enums.py b/enums.py new file mode 100644 index 0000000..8e6b649 --- /dev/null +++ b/enums.py @@ -0,0 +1,51 @@ + +class enum(object): + pass + +def reverse(enumobj, value): + for name in dir(enumobj): + if name[0] == '_': + continue + if value == eval("enumobj.%s" % (name,)): + return name + + return None + + +# render types (for model.renderable_layer) +rt = enum() +rt.model = 0 # a 3d model made out of triangles, go figure! +rt.sprite = 1 # a 2d texture applied to a 2d plane. +rt.beam = 2 # a 2d plane between two points, similar to a sprite + +# rendering billboard modes (for model.renderable_layer) +rtbb = enum() +rtbb.none = 0 # not billboarded, has its own independent rotation +rtbb.full = 1 # the sprite always squarely faces the camera with no rotation, only its position is relevant +rtbb.yaxis = 2 # pitch and yaw axes are available for rotation, but rotation around the roll axis is always fixed to face the camera. + +# coordinate mode +cm = enum() +cm.universe = 0 +cm.camera = 1 +cm.relative = 2 +cm.aa_relative = 3 + +# log level +logl = enum() +logl.debug = 9 +logl.info = 8 +logl.warn = 4 +logl.err = 2 +logl.crit = 0 + +# pipelines +pl = enum() +pl.camera = 0 +pl.player = 1 +pl.model = 2 +pl.shadow = 3 +pl.particle = 4 +pl.sprite = 5 +pl.composite1 = 6 +pl.composite2 = 7 diff --git a/files.py b/files.py new file mode 100644 index 0000000..ceb6549 --- /dev/null +++ b/files.py @@ -0,0 +1,28 @@ +import os +import sys +import pygame + +def get_basedir(): + execpath = sys.argv[0] + execpath = os.path.split(execpath)[0] + if not execpath: + return '.' + return execpath + +class filemanager(object): + def __init__(self): + self.basepath = get_basedir() + self.datapath = "data" + + def path(self, *args): + return os.path.join(self.basepath, self.datapath, *args) + + def open(self, *args): + filename = self.path(*args) + return open(filename, 'rb') + + def png(self, *args): + filename = self.path(*args) + return pygame.image.load(filename) + +mgr = filemanager() diff --git a/files.pyc b/files.pyc new file mode 100755 index 0000000000000000000000000000000000000000..f3a61b958e87916c5ff8daefc5d046d3c5034be1 GIT binary patch literal 1344 zcma)5O>fgc5S?|LHYusfmm(@5!O;iQBL`F=1W25S(B?!`$#UbZaY<~;yC5R9r*h=q z^q=tu;JtB^3J_AF?09x|XXeeD+3TPCdrvQ}-_Padw}bC1EdK!_$KS|jk=aH!GBay? zB(q5JNWSAjPx79)Sk7=7({d&9an={tr#9PRplQ-a(DE046HREajj-Ne`R5P;#|U8L z@DiIy%AS<5Tp-%UicK>RgS6Aih{1N6^x!%vAtW&5k>&}s`jrnczFAv*MLdzZ+c#PG z6o|$*i!vni3IhoD&CPk1Ko=GMA+)zIPMWqZPTG2Ya#k*!KWbLt4h{+z($mblylgev z9{tR|8Jk@*FnWdemsyn+uGOOT2E=PD{|v%`ALW!hf>5(2v|~0VvpnjHohB@1PPl(? zcL)QxyApDhDpynxjP$VXiPDf~Ayc;OEDU&kk9d)k56Agmi#bp#WaJ%CV_ zIRsUf$}dz&`B;MzLqtMMAi|=m))!ifY8A&mzYRpbx~*e zUzqKET;2k!zOkk%9^v{1Zo*7jb(*r~G!5)onl9`7%K}@=0Z~wvwc0VnZ8FvV`|9if zm&vWCvSWtvus4hoj&nCGoLu{Wg0?^`_362r2lZ6I%c9*jon|@ifWKsD&r(L1>rph^ H9pC*8KOxx8 literal 0 HcmV?d00001 diff --git a/gamedata.py b/gamedata.py new file mode 100644 index 0000000..638e76c --- /dev/null +++ b/gamedata.py @@ -0,0 +1,248 @@ +import glob +import os +try: + from xml.etree.cElementTree import parse +except ImportError: + from elementtree.ElementTree import parse +import files +import shader + +class MissingNode(object): + def __init__(self): + self.text = None + def get(self, name): + return None + +class GameDataTagDef(object): + def __init__(self): + self.multi = False + self.opt = False + self.type = None + self.default = None + +class GameDataNode(object): + def __init__(self): + self.dict = {} + def __setitem__(self, name, val): + self.dict[name] = val + def __getitem__(self, name): + return self.dict[name]._value + def __getattr__(self, name): + if name == 'dict': + raise AttributeError, name + try: + return self.dict[name] + except KeyError: + if name == '_value': + return None + raise AttributeError, name + def __setattr__(self, name, val): + if name == 'dict': + return object.__setattr__(self, name, val) + try: + self.dict[name] = val + except KeyError: + object.__setattr__(self, name, val) + def add_multi(self, key, value): + if not self.dict.has_key(key): + self.dict[key] = [] + self.dict[key].append(value) + def add_single(self, key, value): + self.dict[key] = value + def __repr__(self): + return """""" % (self.dict['_name'],) + def has_key(self, key): + return self.dict.has_key(key) + +class XMLGameDataReader(object): + def __init__(self, bin, xml): + self._bin = bin + self._xml = xml + self._tree = self.construct() + self._name = xml.tag + + + def value_as_type(self, value, type): + if type == 'string': + return value + elif type == 'bool': + if value and value[0].lower() in ('y', 't', '1'): + return True + else: + return False + elif type == 'int': + return int(value) + elif type == 'float': + return float(value) + elif type == 'csvarray': + # csvarrays are always ints... + return [int(x.strip()) for x in value.split(',')] + else: + raise TypeError, type + + def create_datatag_def(self, tag): + d = GameDataTagDef() + if tag.get('multiple') != None and tag.get('multiple')[0].lower() == 'y': + d.multi = True + if tag.get('optional') != None and tag.get('optional')[0].lower() == 'y': + d.opt = True + if tag.get('data') != None: + d.type = tag.get('data') + if tag.get('default') != None: + d.default = self.value_as_type(tag.get('default'), d.type) + d.opt = True + return d + + def create_attribute_def(self, tag): + return self.create_datatag_def(tag) + + def construct_node(self, bin, xml): + node = GameDataNode() + value = None + if bin.tag == 'datatag': + df = self.create_datatag_def(bin) + if df.type: + value = xml.text + if type(xml) == MissingNode and df.opt: + value = df.default + elif type(xml) == MissingNode: + raise ValueError, "Missing value for mandatory tag %s" % (bin.get('name'),) + elif df.type == 'bool': + # tag exists, and since it is a bool, that means it's True + value = True + else: + value = self.value_as_type(value, df.type) + + node['_value'] = value + elif bin.tag == 'attribute': + df = self.create_attribute_def(bin) + if df.type: + value = xml.get(bin.get('name')) + if value == None and df.opt: + value = df.default + elif not value: + raise ValueError, "Missing value for mandatory tag %s" % (bin.get('name'),) + else: + value = self.value_as_type(value, df.type) + + node['_value'] = value + + node['_def'] = df + node['_name'] = bin.get('name') + return node + + + def construct_recurse(self, bin, xml): + xmldict = {} + tagdict = {} + attrdict = {} + + if bin.tag == 'xml_binary_packing': + node = GameDataNode() + else: + node = self.construct_node(bin, xml) + + for child in bin.getchildren(): + if child.tag == 'datatag': + tagdict[child.get('name')] = child + elif child.tag == 'attribute': + attrdict[child.get('name')] = child + else: + raise ValueError + + xmlattrdict = {} + for k, v in xml.items(): + if not k in attrdict: + raise ValueError, "Key %s not a valid attribute: %s" % (k, attrdict.keys()) + continue + binchild = attrdict[k] + xmlchild = xml + xmlattrdict[k] = 0 + subnode = self.construct_node(binchild, xml) + if subnode._def.multi: + node.add_multi(k, subnode) + else: + node.add_single(k, subnode) + + xmltagdict = {} + for child in xml.getchildren(): + if not child.tag in tagdict: + raise ValueError + continue + binchild = tagdict[child.tag] + xmlchild = child + xmltagdict[child.tag] = 0 + subnode = self.construct_recurse(binchild, xmlchild) + if subnode._def.multi: + node.add_multi(child.tag, subnode) + else: + node.add_single(child.tag, subnode) + + missing = MissingNode() + for k in tagdict.keys(): + if not k in xmltagdict: + # Missing datatag + subnode = self.construct_node(tagdict[k], missing) + if not subnode._def.multi: + node.add_single(k, subnode) + else: + node.add_single(k, []) + + for k in attrdict.keys(): + if not k in xmlattrdict: + # Missing attribute + subnode = self.construct_node(attrdict[k], missing) + if not subnode._def.multi: + node.add_single(k, subnode) + else: + node.add_single(k, []) + + + + return node + + def construct(self): + rootname = self._bin.get('name') + assert rootname == self._xml.tag + + return self.construct_recurse(self._bin, self._xml) + + def __getattr__(self, name): + if name == 'tree': + raise AttributeError, name + return self._tree.__getattr__(name) + def __getitem__(self, name): + return self._tree.__getitem__(name) + + +def load_xml_files(binfile, xmlfile): + bintree = parse(binfile).getroot() + xmltree = parse(xmlfile).getroot() + + return XMLGameDataReader(bintree, xmltree) + + +def load_gamedata(): + bins = glob.glob(files.mgr.path("xml/def", "*.xml")) + xmls = glob.glob(files.mgr.path("xml", "*.xml")) + + binfns = [os.path.split(x)[1] for x in bins] + xmlfns = [os.path.split(x)[1] for x in xmls] + ffns = [] + for bfn in binfns: + if bfn in xmlfns: + ffns.append(bfn) + + dataobjs = {} + for fn in ffns: + print fn #DELME + binfile = files.mgr.path("xml/def", fn) + xmlfile = files.mgr.path("xml", fn) + dataobj = load_xml_files(binfile, xmlfile) + dataobjs[dataobj._name] = dataobj + + shader.mgr.load_shaders(dataobjs['shaders']) + return dataobjs + + + diff --git a/gamedata.pyc b/gamedata.pyc new file mode 100755 index 0000000000000000000000000000000000000000..490069222fc558cb46b2396bb715ebb798488d46 GIT binary patch literal 8376 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 PX9_dLnTs=@my7=c-h6O$ literal 0 HcmV?d00001 diff --git a/gametimer.py b/gametimer.py new file mode 100644 index 0000000..f642d6c --- /dev/null +++ b/gametimer.py @@ -0,0 +1,41 @@ +import pygame + +def start_loop(): + global g_timer, g_elapsed + g_elapsed = 0 + g_elapsed_sec = 0.0 + g_timer = pygame.time.get_ticks() + +def next_frame(skipping=False): + global g_timer, g_elapsed, g_elapsed_sec + + newticks = pygame.time.get_ticks() + #if skipping: + # # reset g_timer to it's value from the previous frame + # g_timer -= g_elapsed + g_elapsed = newticks - g_timer + g_timer = newticks + g_elapsed_sec = float(g_elapsed) / 1000.0 + +def elapsed(): + """ + get the amount of time in milliseconds passed since the last frame was displayed + """ + return g_elapsed + +def elapsed_sec(): + """ + get the amount of time in seconds passed since the last frame was displayed + """ + return g_elapsed_sec + +def num_frames(delay, offset=0): + """ + if you want something to occur every "delay" milliseconds, + this will return the number of times you should make it happen + """ + global g_elapsed, g_timer + return int((g_timer - offset) / delay) - int((g_timer - g_elapsed - offset) / delay) + +def get_timer(): + return g_timer diff --git a/gametimer.pyc b/gametimer.pyc new file mode 100755 index 0000000000000000000000000000000000000000..8d5adf6f22f3816ca3ee4c5d49beb24b3823a92f GIT binary patch literal 1531 zcmbVMO>fgc5FOh|nxw4&iAoU{Rz2l_xNt)VMFMexAQ?_orD(A?$tsT5@@^=RS}yIK z{{nvu@dx0|I3;jNDXZ*c_G@O|ycvJ{aoGPp{rV=O%}DS+!t}2pa{MA%5S>7Y=?Y3j zCZ;^0tB6h#%3HG8qP#6ioAM6jdvXe;E1P?4e()G-JVI*oFvfcs4PGuDr=Z2dip5(@PUtd}^V{4HG88W(yzo zH$Hguu+g{>-E&SX)VGs(qmfR^S{m`rOVmec9kCM9=T zeTK?94~|NpO6oaw-tq4t`n{P6D$I@2OSdY6a#O_|C|jzfEeeagyE6Bx(jGi2Z_CWc z{X+Yo#IZWlUgg$TgT z-SO|^?jP0q0Lu*qX^-r>=$HgygAJqb&{e3i!8y?pDy-QEF*dfFgAHS!@uJ_eQ?+(0 zWF2&Qhen#CfK+hGW!b7$=A)_C>NrO%>*K#jeAMsZu2*LuR@Ek~>QbmISIZBkzNX%b zbw78jB3DbjKrI9{*HvXoaGP+@ENmIJ_1zRSr%c>RySwSsoA9sM9mwu4W->d- literal 0 HcmV?d00001 diff --git a/inputs.py b/inputs.py new file mode 100644 index 0000000..61680b3 --- /dev/null +++ b/inputs.py @@ -0,0 +1,77 @@ +from pygame.locals import * +import video + +state = {} +commands = {} +bindings = { + K_LEFT: '+left', + K_RIGHT: '+right', + K_UP: '+up', + K_DOWN: '+down', + K_RETURN: 'select', + K_COMMA: '+leanleft', + K_PERIOD: '+leanright', + K_q: 'exit', + K_d: 'debug', + K_TAB: '+accel', + K_BACKSPACE: '+brake', +} +aliases = {} +keys = {} + +def keyup(key): + global commands, keys, bindings, aliases + if not key in keys: + return + del keys[key] + if key in bindings: + bind = bindings[key] + if bind in commands: + del commands[bind] + if bind and bind[1:] in state: + del state[bind[1:]] +def keydown(key): + global commands, keys, bindings, aliases + keys[key] = 0 + if key in bindings: + bind = bindings[key] + if bind in aliases: + "execute alias TODO" + elif bind and bind[0] == '+': + state[bind[1:]] = 0 + add_command(bind[1:]) + elif bind: + add_command(bind) + +def add_command(cmd): + global commands + + if not cmd in commands: + commands[cmd] = 1 + return + else: + commands[cmd] += 1 + +def received_command(cmd): + if cmd in commands: + if commands[cmd] > 0: + commands[cmd] -= 1 + return True + return False + +def received_command_multi(cmd): + if cmd in commands: + if commands[cmd] > 0: + commands[cmd] = 0 + return True + return False + +def next_frame(): + global commands + if video.skipping_next_frame(): + return + + commands = {} + + + diff --git a/looprun.bat b/looprun.bat new file mode 100644 index 0000000..c24d6b9 --- /dev/null +++ b/looprun.bat @@ -0,0 +1,5 @@ +@echo off +:restart +roc_test.py +pause +goto restart diff --git a/pipeline.py b/pipeline.py new file mode 100644 index 0000000..d4e860c --- /dev/null +++ b/pipeline.py @@ -0,0 +1,167 @@ +from OpenGL.GL import * +import shader +import physics +from py3dutil import * +from enums import * + +class pipeline_manager(object): + def __init__(self): + self.phase = 'move' + self.updates = [] + self.renders = [] + self.phases = [ + ('handle_input', {}), + ('ai', {}), + ('animate', {}), + ('move', {}), + ('collide', {'func': physics.mgr.postcollide}), + ('prerender', {}) + ] + self.pipelines = [ + ('camera', {}), + ('player', {'lighting': False, 'shader': 'standard'}), + ('model', {'lighting': False, 'shader': 'standard'}), +# ('shadow', {}), +# ('particle', {'lighting': False, 'depthsort': obarr(), 'nodepthbuffer': True, 'shader': 'sprite'}), +# ('sprite', {'lighting': False, 'depthsort': obarr(), 'nodepthbuffer': True, 'shader': 'sprite'}), + ('composite1', {}), + ('composite2', {}), + + ] + self.phase_index = {} + for i, phase in enumerate(self.phases): + self.phase_index[phase[0]] = i + self.updates.append(obarr()) + + self.pipeline_index = {} + for i, pipe in enumerate(self.pipelines): + self.pipeline_index[pipe[0]] = i + self.renders.append(obarr()) + + + def update(self): + for i, updarr in enumerate(self.updates): + phase = self.phases[i] + self.phase = phase[0] + retval = False + repeat_count = 1 + if 'repeat' in phase and phase[1]['repeat']: + repeat_count = phase[1]['repeat'] + while repeat_count > 0: + if 'func' in phase: + retval = phase['func']() + else: + for o in updarr: + c = eval("o.%s" % (phase[0],)) + if callable(c): + rv = c() + if rv: + retval = rv + + if not ('repeat' in phase and phase[1]['repeat']) or retval: + repeat_count -= 1 + else: + if 'fail_func' in phase[1]: + phase[1]['fail_func']() + break + + physics.mgr.postcollide() + + def render(self): + for i, renarr in enumerate(self.renders): + for ren in renarr: + ren.owner.calc_render_pos() + for i, renarr in enumerate(self.renders): + type, typedata = self.pipelines[i] + if 'depthsort' in typedata: + depthsort = typedata['depthsort'] + depthsort.resize(len(renarr)) + for i in range(len(renarr)): + depthsort[i] = renarr[i].owner.render_pos.mag() + renarr.sort(depthsort) + + + + lighting_on = 'lighting' in typedata and typedata['lighting'] + shader_on = 'shader' in typedata + depth_off = 'nodepthbuffer' in typedata and typedata['nodepthbuffer'] + if lighting_on: + glEnable(GL_LIGHTING) + if shader_on: + shader.mgr.select(typedata['shader']) + if depth_off: + glDepthMask(GL_FALSE) + + for o in renarr: + if lighting_on: + o.owner.illuminate() + o.owner.render(o.layer) + + if depth_off: + glDepthMask(GL_TRUE) + if lighting_on: + glDisable(GL_LIGHTING) + if shader_on: + shader.mgr.select("ffp") + for i, renarr in enumerate(self.renders): + for ren in renarr: + ren.owner.clear_render_pos() + + + +mgr = pipeline_manager() + +def register_ship(obj): + render_pipes = ('model',) + update_pipes = ('ai', 'move', 'collide') + register(obj, update_pipes) + +def register_player(obj): + render_pipes = ('model',) + update_pipes = ('handle_input', 'move', 'collide') + register(obj, update_pipes) + +def register_particles(obj): + render_pipes = ('particle',) + update_pipes = ('move',) + register(obj, update_pipes) + +def register_direct(obj, update_pipes, render_pipes): + assert False + obj._pipeline_update = tuple(update_pipes) + obj._pipeline_render = tuple(render_pipes) + + for phase in update_pipes: + i = mgr.phase_index[phase] + mgr.updates[i].append(obj) + + for pipe in render_pipes: + i = mgr.pipeline_index[pipe] + mgr.renders[i].append(obj) + +def register(obj, update_pipes): + obj._pipeline_update = tuple(update_pipes) + + for phase in update_pipes: + i = mgr.phase_index[phase] + mgr.updates[i].append(obj) + + for lc in obj.layers: + i = lc.layer.pipeline + mgr.renders[i].append(lc) + + +def unregister(obj): + update = obj._pipeline_update + + del obj._pipeline_update + + for lc in obj.layers: + ir = lc.layer.pipeline + mgr.renders[ir].remove(lc) + + for u in update: + iu = mgr.phase_index[u] + mgr.updates[iu].remove(obj) + + diff --git a/refuge.py b/refuge.py new file mode 100755 index 0000000..44ab9e8 --- /dev/null +++ b/refuge.py @@ -0,0 +1,97 @@ +#!/usr/local/bin/python2.4 + +import pygame +from pygame.locals import * +import os +import sys +import math +import random +import inputs +import gamedata +import gametimer +import video +import glwrapper +import init_universe +import universe +import time +import components +try: + from PIL import PngImagePlugin +except: + import PngImagePlugin +from gameobj import * +from player import * +from particles import * +from ai import * + +pygame.init() +gamedata.load_gamedata() + +size = width, height = (1600,1200) +size = width, height = (1024,768) + +video.set_res(size) +video.init() +video.enable_vsync() +gametimer.start_loop() +init_universe.init() +view = None +skipping = False +while True: + events = pygame.event.get() + #if skipping: + # print "SKIPPING! Forced? %s" % (video.get_forced_redraw(),) + gametimer.next_frame(skipping) + """ + if guiscreen != None or guidlg != None: + video.force_redraw() # irrelevant right now, but when we exit the gui, we'll need a full redraw! + for ev in events: + if ev.type == QUIT: + inputs.add_command('exit') + break + if 'exit' in inputs.commands: + break + if guiscreen: + guiscreen.distribute_events(*events) + continue + """ + pygame.event.pump() + for ev in events: + if ev.type == QUIT: + inputs.add_command('exit') + break + elif ev.type == KEYUP: + inputs.keyup(ev.key) + elif ev.type == KEYDOWN: + inputs.keydown(ev.key) + #elif ev.type == VIDEOEXPOSE: + # video.force_redraw() + elif ev.type in (MOUSEBUTTONDOWN, MOUSEBUTTONUP): + pass + + if 'exit' in inputs.commands: + break + + #map.handle_keys(keys) + #units.mgr.update() + #view.update() + + #if video.get_forced_redraw(): + # video.get_scr().fill((0,0,0)) + + #skipping = video.skipping_next_frame() + + + if not skipping: + universe.next_frame() + video.predraw() + universe.render() + + if not skipping: + video.next_frame() + inputs.next_frame() + else: + video.reset_skipping() + + + diff --git a/roc_test.py b/roc_test.py new file mode 100644 index 0000000..9f39b7e --- /dev/null +++ b/roc_test.py @@ -0,0 +1,19 @@ +import pygame +from pygame.locals import * +import gamedata +import gametimer +import video +import shader + +pygame.init() +gamedata.load_gamedata() + +size = width, height = (1600,1200) +size = width, height = (1024,768) + +video.set_res(size) +video.init() +video.enable_vsync() +gametimer.start_loop() + +print shader.mgr.detect() diff --git a/shader.py b/shader.py new file mode 100644 index 0000000..a0ab67f --- /dev/null +++ b/shader.py @@ -0,0 +1,194 @@ +import ctypes +from OpenGL.GL import * +try: + import OpenGL.raw.GL + raw = OpenGL.raw +except ImportError: + pass +from OpenGL.arrays import GLintArray, GLcharArray, GLsizeiArray, GLcharARBArray +import sys + +class shader_manager(object): + def __init__(self): + # Fixed Pipeline is always zero + self.shaderlib = {'ffp': 0} + self.shaderprograms = {} + self.shadersources = [] + def use_gl2_shaders(self): + self.funcs = { + 'create': glCreateShader, + 'source': glShaderSource, + 'compile': glCompileShader, + 'program': glCreateProgram, + 'attach': glAttachShader, + 'link': glLinkProgram, + 'use': glUseProgram, + 'param': glGetShaderiv, + 'uniform': glUniform4f, + 'arraytype': GLcharArray + } + def use_raw_shaders(self): + self.funcs = { + 'create': raw.GL.glCreateShader, + 'source': raw.GL.glShaderSource, + 'compile': raw.GL.glCompileShader, + 'program': raw.GL.glCreateProgram, + 'attach': raw.GL.glAttachShader, + 'link': raw.GL.glLinkProgram, + 'use': raw.GL.glUseProgram, + 'param': raw.GL.glGetShaderiv + } + + def use_arb_shaders(self): + from OpenGL.GL.ARB import shader_objects + if not shader_objects.glInitShaderObjectsARB(): + raise RuntimeError("ARB Shaders failed to initialize") + + self.funcs = { + 'create': shader_objects.glCreateShaderObjectARB, + 'source': shader_objects.glShaderSourceARB, + 'compile': shader_objects.glCompileShaderARB, + 'program': shader_objects.glCreateProgramObjectARB, + 'attach': shader_objects.glAttachObjectARB, + 'link': shader_objects.glLinkProgramARB, + 'use': shader_objects.glUseProgramObjectARB, + 'uniform': shader_objects.glUniform4fARB, + 'param': shader_objects.glGetObjectParameterivARB + } + def use_arb_new_shaders(self): + from OpenGL.GL.ARB import shader_objects_new + shader_objects = shader_objects_new + if not shader_objects.glInitShaderObjectsARB(): + raise RuntimeError("ARB New Shaders failed to initialize") + + self.funcs = { + 'create': shader_objects.glCreateShaderObjectARB, + 'source': shader_objects.base_glShaderSourceARB, + 'compile': shader_objects.glCompileShaderARB, + 'program': shader_objects.glCreateProgramObjectARB, + 'attach': shader_objects.glAttachObjectARB, + 'link': shader_objects.glLinkProgramARB, + 'use': shader_objects.glUseProgramObjectARB, + 'param': shader_objects.glGetObjectParameterivARB, + 'arraytype': GLcharARBArray + } + + def init_gl(self): + import files + #print glGetString(GL_EXTENSIONS) + if True: + self.use_gl2_shaders() + else: + self.use_arb_shaders() + #self.use_gl2_shaders() + + for name, vertex, fragment in self.shadersources: + vf = files.mgr.open('shaders/%s.vert' % (vertex,)) + ff = files.mgr.open('shaders/%s.frag' % (fragment,)) + vs = self.funcs['create'](GL_VERTEX_SHADER) + fs = self.funcs['create'](GL_FRAGMENT_SHADER) + self.funcs['source'](vs, vf.read()) + self.funcs['source'](fs, ff.read()) + rv = self.funcs['compile'](vs) + rv = self.funcs['compile'](fs) + sp = self.funcs['program']() + self.funcs['attach'](sp, vs) + self.funcs['attach'](sp, fs) + rv = self.funcs['link'](sp) + self.shaderlib[name] = sp + self.shaderprograms[name] = (vs, fs) + + def detect(self): + shaderlist = [ + ('GL2', self.use_gl2_shaders), + ('ARB', self.use_arb_shaders), + ('ARBNEW', self.use_arb_new_shaders), + ('RAW', self.use_raw_shaders) + ] + + workable_shaders = [] + + for shadername, shaderinit in shaderlist: + try: + shaderinit() + except: + print "Shader type %s failed to initialize" % (shadername,) + continue + try: + self.select("ffp") + except: + print "Shader type %s failed to access the fixed function pipeline" % (shadername,) + continue + try: + self.select("standard") + except: + print "Shader type %s failed to load the \"standard\" vertex and fragment shaders" % (shadername,) + continue + + workable_shaders.append(shadername) + + return workable_shaders + + + def init_shadertype(self, shadertype): + if shadertype == "GL2": + self.use_gl2_shaders() + elif shadertype == "ARB": + self.use_arb_shaders() + elif shadertype == "ARBNEW": + self.use_arb_new_shaders() + elif shadertype == "RAW": + self.use_raw_shaders() + + + + def select(self, shader): + if self.shadersources: + self.init_gl() + self.shadersources = [] + if not shader in self.shaderlib: + self.funcs['use'](0) + return True + try: + self.funcs['use'](self.shaderlib[shader]) + except GLError: + p = self.shaderlib[shader] + v, f = self.shaderprograms[shader] + print "Shader failed to execute: %s" % (shader,) + print "Vertex shader log:" + lenarr = GLintArray.zeros(1) + glGetShaderiv(v, GL_INFO_LOG_LENGTH, lenarr) + infoarr = GLcharArray.zeros(lenarr[0]) + outsize = GLsizeiArray.zeros(1) + glGetShaderInfoLog(v, lenarr[0], outsize, infoarr) + print ''.join([chr(x) for x in infoarr[:outsize[0]]]) + print "Fragment shader log:" + lenarr = GLintArray.zeros(1) + glGetShaderiv(f, GL_INFO_LOG_LENGTH, lenarr) + infoarr = GLcharArray.zeros(lenarr[0]) + outsize = GLsizeiArray.zeros(1) + glGetShaderInfoLog(f, lenarr[0], outsize, infoarr) + print ''.join([chr(x) for x in infoarr[:outsize[0]]]) + print "Program info log:" + lenarr = GLintArray.zeros(1) + glGetProgramiv(p, GL_INFO_LOG_LENGTH, lenarr) + infoarr = GLcharArray.zeros(lenarr[0]) + outsize = GLsizeiArray.zeros(1) + glGetProgramInfoLog(p, lenarr[0], outsize, infoarr) + print ''.join([chr(x) for x in infoarr[:outsize[0]]]) + glDeleteShader(v) + glDeleteShader(f) + glDeleteProgram(p) + del self.shaderprograms[shader] + del self.shaderlib[shader] + glUseProgram(0) + + return False + + return True + + def load_shaders(self, shaders): + for shader in shaders.shader: + self.shadersources.append((shader['id'], shader['vertex'], shader['fragment'])) + +mgr = shader_manager() diff --git a/shader.pyc b/shader.pyc new file mode 100755 index 0000000000000000000000000000000000000000..fedb2798d82202f0e4354cafc47dc0b4b9b5cd9d GIT binary patch literal 6445 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 EzbGZTUH||9 literal 0 HcmV?d00001 diff --git a/video.py b/video.py new file mode 100644 index 0000000..d04334a --- /dev/null +++ b/video.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python2.4 + +from OpenGL.GL import * +from OpenGL.GLU import * +from OpenGL.arrays import GLcharArray +import pygame +from pygame.locals import * +import sys + +def enable_vsync(): + if sys.platform != 'darwin': + return + try: + import ctypes + import ctypes.util + ogl = ctypes.cdll.LoadLibrary(ctypes.util.find_library("OpenGL")) + v = ctypes.c_int(1) + ogl.CGLSetParameter(ogl.CGLGetCurrentContext(), ctypes.c_int(222), ctypes.pointer(v)) + except: + print "Unable to set vsync mode, using driver defaults" +def skipping_next_frame(): + return False +def set_res((width, height)): + if height==0: + height=1 + pygame.display.gl_set_attribute(GL_DEPTH_SIZE, 24) + video_flags = OPENGL|DOUBLEBUF + #video_flags = OPENGL|DOUBLEBUF|FULLSCREEN + pygame.display.set_mode((width, height), video_flags) + #print pygame.display.gl_get_attribute(GL_DEPTH_SIZE) + glViewport(0, 0, width, height) + glMatrixMode(GL_PROJECTION) + glLoadIdentity() + gluPerspective(45, 1.0*width/height, 1.0, 10e6) + glMatrixMode(GL_MODELVIEW) + glLoadIdentity() + +def init(): + import shader + glShadeModel(GL_SMOOTH) + glClearColor(0.0, 0.0, 0.0, 0.0) + glClearDepth(1.0) + glEnable(GL_DEPTH_TEST) + glEnable(GL_CULL_FACE) + glEnable(GL_TEXTURE_2D) + glDepthFunc(GL_LEQUAL) + #glEnable(GL_BLEND) + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST) + glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 0) + glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1) + """ + """ + LightAmbient = ( (0.2, 0.2, 0.2, 1.0) ) + LightDiffuse = ( (0.5, 0.5, 0.5, 1.0) ) + LightPosition = ( (0.0, 0.2, 2.0, 1.0) ) + glEnable(GL_LIGHTING) + glLightfv( GL_LIGHT1, GL_AMBIENT, LightAmbient ) + glLightfv( GL_LIGHT1, GL_DIFFUSE, LightDiffuse ) + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition ) + glEnable( GL_LIGHT1 ) + """ + """ + shader.mgr.select("standard") + glLightModelfv(GL_LIGHT_MODEL_AMBIENT, (0.3, 0.3, 0.3, 1.0)) + #glEnable(GL_AUTO_NORMAL) + +def predraw(): + glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) + glLoadIdentity() + + #glTranslatef(-1.0, 0.0, -9.0) + glRotatef(270.0, 1.0, 0.0, 0.0) + +def next_frame(): + pygame.display.flip() + + + +if __name__ == '__main__': main() + diff --git a/video.pyc b/video.pyc new file mode 100755 index 0000000000000000000000000000000000000000..0a6d6c3d360957f5b54a5759e9218efd4c3b4c18 GIT binary patch literal 3250 zcma)8%Wm6N5S=T@wj^7Q6X($+XzEoV1gX=t?z(Ev4E1Oev<)SV=00Zb%)MvM<&VEt$`$vAy*^Ez zBK{xZmwpZ5;;ZO{s5jA6rse1)M`4~0@}zhqPbURBC`{)sWV6t_NC%7L7U(OYgC%ko zL@AQHC`yUkB~i-c7DZVmwL|LQ&>E!7`@%N>YPUKaPW{zGc3Q8i&95~xzbb&`%U#1s1vBmy9iB?D{g)M39 zB7~<{yjxvDj4izg={m`I5J|4mZV$bvVIp|A`4u9)53wIPN1?Z&TjIJ zu6J)^lm?N%;U>YEmu$G+z!`-)t**dE^QNTZRP%`sLq`wd+#S_HSc7mZjN*i z>H@djFq8o^c3d+!N}OaY12Ti%4uZ(F!^y}3o7h35^(APvhS~LW$4Q)1PkV{Jn)Nk2 zT^l8d7wK9YY45q#oc%C{nU_>KN{cr_;*I@K4*RT%9ukqVo1IS&he_-oCUO7pEO5Pe zb2!!+}0af(o zh{2kHILPtVFcx#@Dv(~F?U(rp!h=NyA?ye6GMNJ43iPvqivp~Qq)UuPr3J1EM+_(f zub6Wc*4Ll{pwqc4f1f+YYk;u1tRUWD@d1R7AD-VHi1PE5{#Lbuw%}|y_8AF$+HQ~n zf@8e~-4AV`*mkr|f}@f4G#fNbd&lVXcI|HSz{m=rW3(D3;>PK>_qR=Bd%rGNW@83F za$#qW1MhqoCtBd(hkFj(KHuZm^|9#6VP9WP0TUSFe* z#1Z1zYwsB5zm3y zCnCnz9MQu`_|u8gKxPrmm25FgybVpDmmWJVNR~;^54+r8(1%hmKyP=i-R|v9hHIhc zB(*q1nPl^&W5*i;#zk>p2#-!nCM(6B(e256xU21(rd{8v8Ph2}<7sc-GVG6c1pR&} z-s+>MKaIdNzTDq3C00M& zZODF0!ds_D0XS;ju@ejiBTxo!ip=Q5X`q8RdKpIimp zQ^_wX{*-Zp@~50%%h-o4X7|@UG;ld(q_ZtG3Vwl0Tu;nOV1w+&!rdbU%d59Cu)iA4 zQ9rBRD!Z18!Vd+SpaR+OTH9<}HkiL|;IG-6_GbW{_gn9=1HLQ#(2BLAy}^7}MEh?h z9wwffIOoEx{4zpnRW0TFtno8@XSj^U>{{lGB|RQ)Wp6Tk?^}G(^42s6gW+4p!5Evr zKs-Wg@&X|xYTKty5ZShnpJbZ7DQ@D9WXoA=UjQ;`I{c2DS{$Iop~+ZwFK>o%A8$Gq zpN(~e#Z?vz5UbAO3l@72ll3E9=C&<)aVT$_S6YON&v0RnLhtiD^z;@4LvBqVxRNU^ QmvW^`rFTn9rG*Op1r8XT!T