Go on, ask me anything.

We don’t do any auto patching. Used Webstart in the past (shit). Did consider getdown at one point but never got around to it. All we do now is just have a check run on the title screen that asks our server what the latest version of the game is and pops up a dialog to tell the player there’s a newer version available.

As soon as Steam for Linux is ready I think we’re going to totally ditch direct sales and downloads.

Cas :slight_smile:

Could you please elaborate on Webstart’s shortcomings?

Yes, it’s shit.

Cas :slight_smile:

There is an article on the GetDown site about Webstarts shortcomings here.

IMO GetDown is much nicer and has proven to work well in several high profile Java games.

Cas,

I like the games your company made. Really liked how you did the electricity effect that I saw in the Revenage youtube video(@0.40). Is there any information you can share about how you implemented it?

thanks
jose

getdown is extremely simple and very robust. If I were to start implementing an auto patching system today, that’s what I’d use.

Cas :slight_smile:


/*
 * Copyright (c) 2003-onwards Shaven Puppy Ltd
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'Shaven Puppy' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package worm.effects;

import java.util.ArrayList;

import net.puppygames.applet.Game;
import net.puppygames.applet.effects.Effect;
import net.puppygames.applet.effects.Emitter;
import net.puppygames.applet.effects.EmitterFeature;

import org.lwjgl.util.ReadableColor;
import org.lwjgl.util.ReadablePoint;
import org.lwjgl.util.Rectangle;

import worm.Entity;
import worm.GameMap;
import worm.Layers;
import worm.MapRenderer;
import worm.Tile;
import worm.Worm;
import worm.WormGameState;
import worm.screens.GameScreen;

import com.shavenpuppy.jglib.interpolators.Interpolator;
import com.shavenpuppy.jglib.interpolators.LinearInterpolator;
import com.shavenpuppy.jglib.interpolators.SineInterpolator;
import com.shavenpuppy.jglib.openal.ALBuffer;
import com.shavenpuppy.jglib.opengl.ColorUtil;
import com.shavenpuppy.jglib.opengl.GLRenderable;
import com.shavenpuppy.jglib.sound.SoundEffect;
import com.shavenpuppy.jglib.util.ShortList;
import com.shavenpuppy.jglib.util.Util;

import static org.lwjgl.opengl.GL11.*;

public class ElectronZapEffect extends Effect {

	private static final long serialVersionUID = 1L;

	/** Length of a segment */
	private static final int SEGMENT_LENGTH = 8;

	/** Zap fade out duration in ticks */
	private static final int FADE_DURATION = 15;

	/** Damage */
	private static final int DAMAGE_INTERVAL = 2;

	static final Interpolator INTERPOLATOR;
	static {
		INTERPOLATOR = new Interpolator() {
			private static final long serialVersionUID = 1L;

			@Override
			public float interpolate(float a, float b, float ratio) {
				if (ratio < 0.5f) {
					return SineInterpolator.instance.interpolate(a, b, ratio * 2.0f);
				} else {
					return SineInterpolator.instance.interpolate(a, b, 1.0f - (ratio - 0.5f) * 2.0f);
				}
			}
		};
	}

	private static final GLRenderable SETUP = new GLRenderable() {
		@Override
		public void render() {
			glDisable(GL_TEXTURE_2D);
			glEnable(GL_BLEND);
			glBlendFunc(GL_ONE, GL_ONE);
			glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
		}
	};

	private static final ArrayList<Entity> TEMP_ENTITIES = new ArrayList<Entity>();
	private static final Rectangle TEMP_RECT = new Rectangle();

	/** Rendering indices */
	private final ShortList indices = new ShortList(true, 32);

	private final float maxWidth;
	private final float maxWobble;
	private final float wobbleFactor;
	private final float widthFactor;
	private final boolean collides;

	private final EmitterFeature beamStartEmitter, beamEndEmitter;
	private final ALBuffer soundBuffer;

	private final ReadableColor color0, color1;
	private final int alpha;

	/** Source & target location */
	private final float sx, sy;
	private float tx, ty;

	/** Actual beam endpoint */
	private int x, y;

	/** Fading out */
	private boolean fading, done;

	/** Ticker */
	private int tick, soundTick, fadeSoundInTick;

	/** Segments */
	private float[] x0, y0, x1, y1, x2, y2, x3, y3;

	/** Current length (distance) */
	private int length;

	/** Number of segments */
	private int numSegments;

	/** Indices start and end */
	private int start, end, startPos, endPos;

	/** Sound effect */
	private SoundEffect soundEffect;

	public ElectronZapEffect
		(
			boolean collides,
			ALBuffer soundBuffer,
			ReadableColor color0,
			ReadableColor color1,
			int alpha,
			EmitterFeature beamStartEmitter,
			EmitterFeature beamEndEmitter,
			float sx,
			float sy,
			float maxWidth,
			float maxWobble,
			float wobbleFactor,
			float widthFactor
		)
	{
		this.collides = collides;
		this.soundBuffer = soundBuffer;
		this.color0 = color0;
		this.color1 = color1;
		this.alpha = alpha;
		this.beamStartEmitter = beamStartEmitter;
		this.beamEndEmitter = beamEndEmitter;
		this.sx = sx;
		this.sy = sy;
		this.maxWidth = maxWidth;
		this.maxWobble = maxWobble;
		this.wobbleFactor = wobbleFactor;
		this.widthFactor = widthFactor;
	}

	public void setTarget(float tx, float ty) {
		this.tx = tx;
		this.ty = ty;

		double dx = sx - tx;
		double dy = sy - ty;

		length = (int) Math.sqrt(dx * dx + dy * dy);
	}

	@Override
	protected void doUpdate() {
		ReadablePoint offset = getOffset();
		int ox, oy;
		if (offset == null) {
			ox = 0;
			oy = 0;
		} else {
			ox = offset.getX();
			oy = offset.getY();
		}

		// Calculate length & angle
		double dx = x - sx;
		double dy = y - sy;
		double tangent = Math.atan2(dy, dx) + Math.PI / 2.0;
		double dist = Math.sqrt(dx * dx + dy * dy);
		numSegments = (int) dist / SEGMENT_LENGTH + 1;
		if (x0 == null || x0.length < numSegments) {
			x0 = null;
			y0 = null;
			x1 = null;
			y1 = null;
			x2 = null;
			y2 = null;
			x3 = null;
			y3 = null;
			x0 = new float[numSegments];
			y0 = new float[numSegments];
			x1 = new float[numSegments];
			y1 = new float[numSegments];
			x2 = new float[numSegments];
			y2 = new float[numSegments];
			x3 = new float[numSegments];
			y3 = new float[numSegments];
		}
		for (int i = 0; i < numSegments; i ++) {
			float r2 = (float) i / (float) (numSegments - 1);
			double width = INTERPOLATOR.interpolate(0.0f, Math.min(maxWidth, i * widthFactor * SEGMENT_LENGTH), r2);
			double wobble = INTERPOLATOR.interpolate(0, Math.min(maxWobble, i * wobbleFactor * SEGMENT_LENGTH), r2);
			wobble *= Util.random() - 0.5;
			float xx = LinearInterpolator.instance.interpolate(sx, x, r2) + (float) (Math.cos(tangent) * wobble) + ox;
			float yy  = LinearInterpolator.instance.interpolate(sy, y, r2) + (float) (Math.sin(tangent) * wobble) + oy;
			x0[i] = xx - (float) (Math.cos(tangent) * width);
			y0[i] = yy - (float) (Math.sin(tangent) * width);
			x1[i] = xx + (float) (Math.cos(tangent) * width);
			y1[i] = yy + (float) (Math.sin(tangent) * width);
			x2[i] = xx - (float) (Math.cos(tangent) * (width + 1.0));
			y2[i] = yy - (float) (Math.sin(tangent) * (width + 1.0));
			x3[i] = xx + (float) (Math.cos(tangent) * (width + 1.0));
			y3[i] = yy + (float) (Math.sin(tangent) * (width + 1.0));
		}

		if (soundEffect != null) {
			soundEffect.setGain(Worm.calcGain(tx, ty) * Game.getSFXVolume() * soundBuffer.getGain(), this);
		}
	}

	@Override
	public void finish() {
		if (!fading) {
			fading = true;
			tick = 0;
		}
	}

	@Override
	protected void doTick() {
		tick ++;
		if (soundEffect != null && soundEffect.getOwner() != this) {
			// We lost the looping sound effect, so bring it back in a mo
			soundTick ++;
			if (soundTick >= FADE_DURATION) {
				soundEffect = Game.allocateSound(soundBuffer, 0.0f, 1.0f, this);
				fadeSoundInTick = FADE_DURATION;
			}
		}
		if (fading && soundEffect != null) {
			soundEffect.setGain(Math.max(0.0f, Math.min(1.0f, Worm.calcGain(tx, ty) * Game.getSFXVolume() * (1.0f - (float) tick / (float) FADE_DURATION) * soundBuffer.getGain())), this);
			return;
		}
		if (fadeSoundInTick > 0) {
			fadeSoundInTick --;
		}
		if (soundEffect != null) {
			soundEffect.setGain(Math.max(0.0f, Math.min(1.0f, Worm.calcGain(tx, ty) * Game.getSFXVolume() * (1.0f - (float) fadeSoundInTick / (float) FADE_DURATION) * soundBuffer.getGain())), this);
		}

		int totalLength = 0;
		if (collides) {
			WormGameState gameState = Worm.getGameState();
			x = (int) sx;
			y = (int) sy;
			GameMap map = gameState.getMap();
			int tileX = x / MapRenderer.TILE_SIZE;
			int tileY = y / MapRenderer.TILE_SIZE;

			int ox = x, oy = y;
			outer: for (int i = 0; i < length; i ++) {
				x = (int) LinearInterpolator.instance.interpolate(sx, tx, (float) i / (float) length);
				y = (int) LinearInterpolator.instance.interpolate(sy, ty, (float) i / (float) length);

				// Check map collision
				int newTileX = x / MapRenderer.TILE_SIZE;
				int newTileY = y / MapRenderer.TILE_SIZE;

				if (newTileX != tileX || newTileY != tileY) {
					tileX = newTileX;
					tileY = newTileY;

					for (int tileZ = 0; tileZ < GameMap.LAYERS; tileZ ++) {
						Tile t = map.getTile(tileX, tileY, tileZ);
						if (t != null && !t.isBulletThrough()) {
							// Hit a wall
							break outer;
						}
					}
				}

				// Check collision with entities
				if (ox != x || oy != y) {
					TEMP_ENTITIES.clear();
					TEMP_RECT.setBounds(x, y, 1, 1);
					Entity.getCollisions(TEMP_RECT, TEMP_ENTITIES);
					int numEntities = TEMP_ENTITIES.size();
					for (int j = 0; j < numEntities; j ++) {
						Entity entity = TEMP_ENTITIES.get(j);
						if (entity.isActive() && entity.isShootable() && entity.canCollide()) {
							if (tick % DAMAGE_INTERVAL == 0) {
								entity.capacitorDamage(1);
							} else {
								entity.stunDamage(3);
							}

							// And here the beam stops.
							break outer;
						}
					}
					ox = x;
					oy = y;
				}
				totalLength ++;
			}
		} else {
			x = (int) tx;
			y = (int) ty;

		}
		if (!collides || totalLength > 0) {
			// Spawn sparks and ting at end of beam
			Emitter e = beamEndEmitter.spawn(GameScreen.getInstance());
			e.setLocation(x, y);
			e.setOffset(GameScreen.getSpriteOffset());
			Emitter e2 = beamStartEmitter.spawn(GameScreen.getInstance());
			e2.setLocation(sx, sy);
			e2.setOffset(GameScreen.getSpriteOffset());
		}
	}

	@Override
	public boolean isEffectActive() {
		return (!fading || tick < FADE_DURATION) && !done;
	}

	@Override
	protected void doRemove() {
		done = true;

		if (soundEffect != null) {
			soundEffect.stop(this);
			soundEffect = null;
		}
	}

	@Override
	protected void render() {
		if (!isStarted() || !isVisible()) {
			return;
		}
		glRender(SETUP);
		int endAlpha = fading ? (int) LinearInterpolator.instance.interpolate(255.0f, 0.0f, (float) tick / FADE_DURATION) : 255;

		indices.ensureCapacity(numSegments * 4);
		indices.clear();

		for (int i = 0; i < numSegments; i ++) {
			if (i == 0 || i == numSegments - 1) {
				ColorUtil.setGLColorPre(color0, 0, this);
			} else {
				ColorUtil.setGLColorPre(color0, endAlpha * alpha / 255, this);
			}
			indices.add(glVertex2f(x2[i], y2[i]));
			indices.add(glVertex2f(x3[i], y3[i]));
		}
		glRender(GL_TRIANGLE_STRIP, indices.toArray(null));

		indices.clear();
		ColorUtil.setGLColorPre(color1, endAlpha * alpha / 255, this);
		for (int i = 0; i < numSegments; i ++) {
			indices.add(glVertex2f(x0[i], y0[i]));
			indices.add(glVertex2f(x1[i], y1[i]));
		}
		glRender(GL_TRIANGLE_STRIP, indices.toArray(null));
	}

	@Override
	protected void doSpawnEffect() {
		soundEffect = Game.allocateSound(soundBuffer, 1.0f, 1.0f, this);
	}

	@Override
	public int getDefaultLayer() {
		return Layers.CAPACITOR_EFFECT;
	}
}

Cas :slight_smile:

Interpolators, electrons, and emitters, oh my! :smiley:

hey thanks. How often is doUpdate called?

its just that my games all have the C++ games standard structure, exe files and folders with game content.
with getdown all game contents have to be in jars =/

Cero are you trying to get a regular executable file for your java program?

I’ve been in contact with a guy implementing a framework for auto-updating jars for an installed application and still have the executable functional. He showed me a preview and it worked just like he said. In your game the user would get some sort of message that there is an update available then if they click on it their game will be updated, with installer progress bar and everything. He is probrably a few months off from finishing but I can put you in contact with him.

thanks
jose

Getdown can deliver any kind of file, not just jars.

One thing Oddlabs did back in the day was release using SVN. They had a SVN client built into Tribal Trouble. A very elegant and nifty solution but unfortunately SVN is mysteriously dog slow. That’s the reason I’ve been looking in to GIT lately but I’ve been thwarted by beards who have tried to make GIT as unusably difficult as possible.

Hmm. GIT would probably work quite well as a version delivery mechanism too.

Cas :slight_smile:

Every frame update, which is usually every tick, which is usually 60Hz.

Cas :slight_smile:

WRT Git. If you use just the subset most projects use its pretty straightforward. However it has a lot of stuff in there pretty close to the surface which is really just for projects like the Linux kernel. Also a lot of the git people that help are kernel folks, pushing this odd and complicated stuff even closer to the surface…

Aye, hence looking at Mercurial right now. I still don’t get why even simply installing the bastard serverside part has to be so hard. Linux is shit. Damn the beards.

Cas :slight_smile:

Consider hiring a bearded kid…could be cheaper than wasting time yourself.

I think I might need to understand this absolutely fully myself before entrusting it to someone foolish enough to work for me.

Cas :slight_smile:

[quote]Linux is shit.
[/quote]
OS Flamewar!!!

Haven’t had one of those for ages.

But in truth i don’t understand? Install what server where? If you mean for use with windows, then well yea git has never worked well on windows. At least the few times i tried.

I use git, but don’t run any git server.

I think here we are bumping into on of my fundamental misunderstandings :slight_smile: How do Chaz, myself and Allicorn work together with git? At the very least I want a fully-backed up canonical representation of the status quo on my server somewhere which is what we’re all ultimately working against.

Cas :slight_smile: