/*
* 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.buildings;
import org.lwjgl.util.Color;
import org.lwjgl.util.ReadableColor;
import worm.Layers;
import worm.Res;
import worm.Worm;
import worm.effects.RangeEffect;
import worm.entities.Building;
import worm.entities.Capacitor;
import worm.screens.GameScreen;
import worm.weapons.WeaponFeature;
import worm.weapons.WeaponFeature.WeaponInstance;
import com.shavenpuppy.jglib.sprites.Appearance;
import com.shavenpuppy.jglib.sprites.Sprite;
import com.shavenpuppy.jglib.util.FPMath;
/**
* Capacitors
*/
public class CapacitorBuildingFeature extends BuildingFeature {
private static final long serialVersionUID = 1L;
private static final ReadableColor RANGE_COLOR = new Color(255, 224, 255, 48);
private static final int LIGHTS_OFF_TIME = 20;
private static final int ALPHA_ADJUST = 16;
private static final float ATTENUATION = 256.0f;
private float baseRange;
private float rangePerReactor;
private int maxReactors;
private String weapon;
private int beamOffsetX;
private int beamOffsetY;
/** Light */
private String light;
/** Light layer */
private int lightLayer;
/** Beam */
private String beam;
/** Beam layer */
private int beamLayer;
/** Min. light radius */
private float lightRadius;
/** Light scaling factor */
private float lightScale;
/** Light movement speed */
private float lightSpeed;
private transient WeaponFeature weaponFeature;
private transient Appearance lightResource, beamResource;
/**
* Building instances
*/
private class CapacitorBuildingInstance extends Building implements Capacitor {
private static final long serialVersionUID = 1L;
private WeaponInstance weaponInstance;
/** Lighting */
private Sprite lightSprite, beamSprite;
/** Light position */
private float lightX, lightY;
/** Target light alpha */
private int targetLightAlpha, currentLightAlpha;
/** Lights off tick */
private int lightsOffTick;
/** Light attenuation */
private float attenuation;
/** Energy level */
private Sprite reloadSprite;
/** Range effect */
private transient RangeEffect rangeEffect;
/**
* @param feature
* @param x
* @param y
*/
protected CapacitorBuildingInstance(boolean ghost) {
super(CapacitorBuildingFeature.this, ghost);
}
@Override
public boolean zap(float mx, float my) {
cancelUndo();
// Check we're in range. Range is increased by nearby reactors.
if (getDistanceTo(mx, my) > getZapRadius()) {
// Scale to maximum range
double dx = mx - (getMapX() + getBeamOffsetX());
double dy = my - (getMapY() + getBeamOffsetY());
double len = Math.sqrt(dx * dx + dy * dy);
dx *= getZapRadius();
dy *= getZapRadius();
dx /= len;
dy /= len;
mx = getMapX() + getBeamOffsetX() + (float) dx;
my = getMapY() + getBeamOffsetY() + (float) dy;
}
return weaponInstance.fire((int) mx, (int) my);
}
@Override
protected void doOnBuild() {
// Create lights
if (lightResource != null) {
lightSprite = GameScreen.getInstance().allocateSprite(this);
lightSprite.setLayer(lightLayer);
lightSprite.setAppearance(lightResource);
lightSprite.setAlpha(0);
}
if (beamResource != null) {
beamSprite = GameScreen.getInstance().allocateSprite(this);
beamSprite.setLayer(beamLayer);
beamSprite.setAppearance(beamResource);
beamSprite.setAlpha(0);
}
}
private void updateLight() {
if (lightSprite != null) {
float lx = GameScreen.getSpriteOffset().getX() + lightX;
float ly = GameScreen.getSpriteOffset().getY() + lightY;
lightSprite.setLocation(lx, ly);
}
if (beamSprite != null) {
beamSprite.setLocation(getScreenX() + getBeamOffsetX(), getScreenY() + getBeamOffsetY());
}
}
@Override
protected void doBuildingSpawn() {
weaponInstance = weaponFeature.spawn(this);
reloadSprite = GameScreen.getInstance().allocateSprite(this);
reloadSprite.setLayer(Layers.BUILDING_INFO);
reloadSprite.setAppearance(Res.getEnergyAmmo(1, 1));
reloadSprite.setScale(FPMath.fpValue(CapacitorBuildingFeature.this.getAppearance(this).getScale()));
}
@Override
protected void doBuildingDestroy() {
if (weaponInstance != null) {
weaponInstance.remove();
weaponInstance = null;
}
}
@Override
protected void doBuildingRemove() {
if (weaponInstance != null) {
weaponInstance.remove();
weaponInstance = null;
}
}
@Override
public boolean usesAmmo() {
return true;
}
@Override
protected void doBuildingUpdate() {
if (weaponInstance != null && reloadSprite != null) {
reloadSprite.setLocation(getScreenX(), getScreenY());
}
updateLight();
}
@Override
protected void doRemoveSpecialEffects() {
if (reloadSprite != null) {
reloadSprite.deallocate();
reloadSprite = null;
}
if (lightSprite != null) {
lightSprite.deallocate();
lightSprite = null;
}
if (beamSprite != null) {
beamSprite.deallocate();
beamSprite = null;
}
if (rangeEffect != null) {
rangeEffect.finish();
rangeEffect = null;
}
}
@Override
protected void doBuildingSetLocation() {
lightX = getMapX() + getBeamOffsetX();
lightY = getMapY() + getBeamOffsetY();
if (rangeEffect != null) {
rangeEffect.setLocation(getMapX() + getCollisionX(), getMapY() + getCollisionY());
}
}
@Override
public void createGhostProximityEffects() {
super.createGhostProximityEffects();
// Show the turret range effect for ghosts
rangeEffect = new RangeEffect(RANGE_COLOR);
rangeEffect.setLocation(getMapX() + getCollisionX(), getMapY() + getCollisionY());
rangeEffect.spawn(GameScreen.getInstance());
rangeEffect.setShow(true);
rangeEffect.setRadius(getZapRadius());
}
@Override
protected void doGhostTick() {
rangeEffect.setLocation(getMapX() + getCollisionX(), getMapY() + getCollisionY());
rangeEffect.setRadius(getZapRadius());
}
@Override
protected void doBuildingTick() {
// Turn light to meet mouse
float mx = GameScreen.getInstance().getMouseX() - GameScreen.getSpriteOffset().getX();
float my = GameScreen.getInstance().getMouseY() - GameScreen.getSpriteOffset().getY();
if (getDistanceTo(mx, my) <= getZapRadius()) {
if (rangeEffect == null) {
rangeEffect = new RangeEffect(RANGE_COLOR);
rangeEffect.setLocation(getX(), getY());
rangeEffect.spawn(GameScreen.getInstance());
}
rangeEffect.setShow(true);
targetLightAlpha = 255;
lightsOffTick = 0;
float dx = mx - lightX;
float dy = my - lightY;
float dist = (float) Math.sqrt(dx * dx + dy * dy);
if (dist > 0.0f) {
float move = lightSpeed / dist;
if (Math.abs(dx) <= lightSpeed) {
lightX = mx;
} else {
lightX += dx * move;
}
if (Math.abs(dy) <= lightSpeed) {
lightY = my;
} else {
lightY += dy * move;
}
}
// What's the new distance?
dx = lightX - (getX() + getBeamOffsetX());
dy = lightY - (getY() + getBeamOffsetY());
// Scale the light according to current light distance from us
if (lightSprite != null) {
dist = (float) Math.sqrt(dx * dx + dy * dy);
attenuation = Math.max(0.0f, 1.0f - dist / ATTENUATION);
lightSprite.setScale(FPMath.fpValue(dist * lightScale / lightRadius));
}
if (beamSprite != null) {
beamSprite.setAngle(FPMath.fpYaklyDegrees(Math.atan2(dy, dx)));
}
} else {
if (lightsOffTick < LIGHTS_OFF_TIME) {
lightsOffTick ++;
if (lightsOffTick == LIGHTS_OFF_TIME) {
targetLightAlpha = 0;
}
}
if (rangeEffect != null) {
rangeEffect.setShow(false);
}
}
if (rangeEffect != null) {
rangeEffect.setRadius(getZapRadius());
}
if (currentLightAlpha < targetLightAlpha) {
currentLightAlpha = Math.min(targetLightAlpha, currentLightAlpha + ALPHA_ADJUST);
} else if (currentLightAlpha > targetLightAlpha) {
currentLightAlpha = Math.max(targetLightAlpha, currentLightAlpha - ALPHA_ADJUST);
}
if (lightSprite != null) {
lightSprite.setAlpha((int) (currentLightAlpha * attenuation));
}
if (beamSprite != null) {
beamSprite.setAlpha(currentLightAlpha);
}
reloadSprite.setAppearance(Res.getEnergyAmmo(weaponInstance.getAmmo(), weaponInstance.getMaxAmmo()));
weaponInstance.tick();
}
@Override
public float getZapRadius() {
return baseRange + (Worm.getGameState().getCapacitorBoost() + Math.min(maxReactors, getReactors())) * rangePerReactor;
}
public float getBeamOffsetX() {
return beamOffsetX;
}
public float getBeamOffsetY() {
return beamOffsetY;
}
}
/**
* @param name
*/
public CapacitorBuildingFeature(String name) {
super(name);
}
@Override
public Building doSpawn(boolean ghost) {
return new CapacitorBuildingInstance(ghost);
}
@Override
public boolean isAffectedBy(BuildingFeature feature) {
return
feature instanceof ReactorBuildingFeature
|| feature instanceof ShieldGeneratorBuildingFeature
|| feature instanceof BatteryBuildingFeature
|| feature instanceof CloakBuildingFeature
;
}
}