forked from Mirrors/openclonk
Guardians of the Windmills: First draft. Unbalanced, untested, unplayable! (please test)
parent
d737dc1b05
commit
b7b6fcfa3f
|
@ -121,6 +121,14 @@ public func UpdateIndexedItem(int index)
|
|||
return false;
|
||||
}
|
||||
|
||||
public func GetEntryByID(def id)
|
||||
{
|
||||
for (var i = 0; i < GetLength(base_material); i++)
|
||||
if (base_material[i].item == id)
|
||||
return i;
|
||||
return nil;
|
||||
}
|
||||
|
||||
public func GetEntryInformation(int entry_idx)
|
||||
{
|
||||
// Fill with current information for this entry
|
||||
|
@ -269,6 +277,18 @@ public func SellItem(item)
|
|||
return success;
|
||||
}
|
||||
|
||||
// Makes an item available even though the requirements aren't yet met
|
||||
public func SetItemAvailable(int entry_idx)
|
||||
{
|
||||
// Safety
|
||||
var entry = base_material[entry_idx];
|
||||
if (!entry) return false;
|
||||
|
||||
entry.requirements = nil;
|
||||
entry.cost = nil;
|
||||
return true;
|
||||
}
|
||||
|
||||
public func OnOwnerChanged(new_owner)
|
||||
{
|
||||
if (buy_menu) buy_menu->SetOwner(new_owner);
|
||||
|
|
Binary file not shown.
|
@ -5,8 +5,8 @@
|
|||
An evil rocket which is hungry on the destruction of windmills
|
||||
--*/
|
||||
|
||||
#include Boomattack
|
||||
|
||||
/*
|
||||
local hits;
|
||||
|
||||
protected func Construction()
|
||||
|
@ -68,7 +68,7 @@ public func OnProjectileHit(object shot)
|
|||
}
|
||||
|
||||
/* Contact */
|
||||
|
||||
/*
|
||||
protected func ContactBottom() { DoDrill(180); }
|
||||
protected func ContactTop() { DoDrill(0); }
|
||||
protected func ContactLeft() { DoDrill(-40); }
|
||||
|
@ -109,3 +109,4 @@ local ActMap = {
|
|||
Hgt = 100
|
||||
},
|
||||
};
|
||||
*/
|
|
@ -1,7 +1,7 @@
|
|||
[DefCore]
|
||||
id=Boomattack
|
||||
Version=6,0
|
||||
Category=C4D_Object
|
||||
Version=7,0
|
||||
Category=C4D_Vehicle
|
||||
ContactCalls=1
|
||||
Width=15
|
||||
Height=27
|
||||
|
@ -14,3 +14,4 @@ VertexFriction=80,60,60,60
|
|||
Rotate=1
|
||||
StretchGrowth=1
|
||||
Oversize=1
|
||||
IncompleteActivity=1
|
|
@ -1,135 +1,133 @@
|
|||
/*--
|
||||
Boom attack
|
||||
Authors: Randrian, Newton
|
||||
Authors: Randrian, Newton, Sven2
|
||||
|
||||
An evil rocket which is hungry on the destruction of windmills
|
||||
An evil rocket which is hungry on the destruction of Windmills
|
||||
--*/
|
||||
|
||||
local fuel;
|
||||
|
||||
protected func Construction()
|
||||
/* Init */
|
||||
|
||||
public func Construction()
|
||||
{
|
||||
//flight length
|
||||
fuel=1000;
|
||||
SetAction("Fly");
|
||||
SetComDir(COMD_None);
|
||||
var fx = AddEffect("Flight", this, 150, 10, this);
|
||||
fx.target = GetRandomWindmill();
|
||||
FxFlightTimer(this, fx, 0);
|
||||
AddEffect("FlightRotation", this, 151, 1, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Flight */
|
||||
|
||||
protected func FxFlightTimer(object pTarget, effect, int iEffectTime)
|
||||
local rotation = 0;
|
||||
|
||||
private func FxFlightRotationTimer(object _this, effect, int time)
|
||||
{
|
||||
if(fuel<=0)
|
||||
{
|
||||
DoFireworks();
|
||||
return;
|
||||
}
|
||||
if (rider) return FX_Execute_Kill;
|
||||
|
||||
var ignition = iEffectTime % 10;
|
||||
|
||||
if(!ignition)
|
||||
rotation += 2;
|
||||
if (rotation > 360) rotation = 0;
|
||||
|
||||
this.MeshTransformation = Trans_Rotate(rotation, 0,1);
|
||||
return FX_OK;
|
||||
}
|
||||
|
||||
private func FxFlightTimer(object _this, effect, int time)
|
||||
{
|
||||
// Attack!
|
||||
if (!effect.target)
|
||||
{
|
||||
var angle = GetR()+RandomX(-3,3);
|
||||
SetXDir(Sin(angle,100), 100);
|
||||
SetYDir(-Cos(angle,100), 100);
|
||||
if (g_lost) DoFireworks(NO_OWNER);
|
||||
effect.target = GetRandomWindmill();
|
||||
}
|
||||
if(!(time % 10))
|
||||
{
|
||||
// Adjust angle
|
||||
var dx = effect.target->GetX() - GetX(), dy = effect.target->GetY()+50 - GetY();
|
||||
var aim_dist = 600; // at this distance, fly horizontally. when getting closer, gradually turn to direct flight into target
|
||||
var aim_dy = dy * (aim_dist - Abs(dx)) / aim_dist;
|
||||
var angle = Angle(0,0,dx,aim_dy);
|
||||
SetXDir(Sin(angle, FlySpeed), 100);
|
||||
SetYDir(-Cos(angle, FlySpeed), 100);
|
||||
SetR(angle);
|
||||
}
|
||||
|
||||
if(GetAction() != "Fly")
|
||||
{
|
||||
SetAction("Fly");
|
||||
SetComDir(COMD_None);
|
||||
}
|
||||
|
||||
|
||||
var x = -Sin(GetR(), 15);
|
||||
var y = +Cos(GetR(), 15);
|
||||
|
||||
var xdir = GetXDir() / 2;
|
||||
var ydir = GetYDir() / 2;
|
||||
CreateParticle("FireDense", x, y, PV_Random(xdir - 4, xdir + 4), PV_Random(ydir - 4, ydir + 4), PV_Random(16, 38), Particles_Thrust(), 5);
|
||||
|
||||
|
||||
fuel--;
|
||||
|
||||
return FX_OK;
|
||||
}
|
||||
|
||||
public func IsProjectileTarget(target,shooter)
|
||||
/* Riding */
|
||||
|
||||
local riderattach;
|
||||
local rider;
|
||||
|
||||
public func OnMount(clonk)
|
||||
{
|
||||
if(target->GetID() == GetID())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
rider = clonk;
|
||||
var iDir = -1;
|
||||
if (GetX() > LandscapeWidth()/2) iDir = 1;
|
||||
clonk->PlayAnimation("PosRocket", CLONK_ANIM_SLOT_Arms, Anim_Const(0), Anim_Const(1000));
|
||||
riderattach = AttachMesh(clonk, "main", "pos_tool1", Trans_Mul(Trans_Translate(-1000,2000*iDir,2000)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public func OnProjectileHit(object shot)
|
||||
public func OnUnmount(clonk)
|
||||
{
|
||||
var gol = FindObject(Find_ID(Goal_SaveTheWindmills));
|
||||
if(gol) gol->IncShotScore(shot->GetController());
|
||||
DoFireworks();
|
||||
return 1;
|
||||
clonk->StopAnimation(clonk->GetRootAnimation(10));
|
||||
DetachMesh(riderattach);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Contact */
|
||||
/* Contact / Explosion */
|
||||
|
||||
protected func ContactBottom() { return Hit(); }
|
||||
protected func ContactTop() { return Hit(); }
|
||||
protected func ContactLeft() { return Hit(); }
|
||||
protected func ContactRight() { return Hit(); }
|
||||
public func IsProjectileTarget(target,shooter) { return true; }
|
||||
public func OnProjectileHit(object shot) { return DoFireworks(shot->GetController()); }
|
||||
|
||||
protected func Hit()
|
||||
{
|
||||
//Message("I have hit something");
|
||||
if(GetEffect("Flight",this)) DoFireworks();
|
||||
else Sound("Hits::Materials::Wood::WoodHit*");
|
||||
}
|
||||
|
||||
protected func HitObject()
|
||||
{
|
||||
DoFireworks();
|
||||
}
|
||||
|
||||
func Launch(int angle)
|
||||
{
|
||||
SetProperty("Collectible",0);
|
||||
SetCategory(C4D_Vehicle);
|
||||
SetAction("Fly");
|
||||
SetComDir(COMD_None);
|
||||
|
||||
Exit();
|
||||
AddEffect("Flight",this,150,1,this);
|
||||
//AddEffect("HitCheck", this, 1,1, nil,nil, 0, 0);
|
||||
|
||||
SetR(angle);
|
||||
}
|
||||
|
||||
func DoFireworks(int speed)
|
||||
public func ContactBottom() { return Hit(); }
|
||||
public func ContactTop() { return Hit(); }
|
||||
public func ContactLeft() { return Hit(); }
|
||||
public func ContactRight() { return Hit(); }
|
||||
|
||||
public func Hit() { return DoFireworks(NO_OWNER); }
|
||||
public func HitObject() { return DoFireworks(NO_OWNER); }
|
||||
|
||||
private func DoFireworks(int killed_by)
|
||||
{
|
||||
if (rider) {
|
||||
rider->SetAction("Walk");
|
||||
rider->Fling(RandomX(-5,5), -5);
|
||||
}
|
||||
GameCallEx("OnClonkDeath", this, killed_by); // for reward
|
||||
RemoveEffect("Flight",this);
|
||||
Fireworks();
|
||||
Explode(40);
|
||||
return true;
|
||||
}
|
||||
|
||||
func SetFuel(int new)
|
||||
{
|
||||
fuel = new;
|
||||
}
|
||||
/* Status */
|
||||
|
||||
func GetFuel()
|
||||
{
|
||||
return fuel;
|
||||
}
|
||||
public func IsFlyingEnemy() { return true; }
|
||||
|
||||
local ActMap = {
|
||||
|
||||
Fly = {
|
||||
Prototype = Action,
|
||||
Name = "Fly",
|
||||
Procedure = DFA_FLOAT,
|
||||
Length = 1,
|
||||
Delay = 0,
|
||||
Wdt = 15,
|
||||
Hgt = 27
|
||||
},
|
||||
Fly = {
|
||||
Prototype = Action,
|
||||
Name = "Fly",
|
||||
Procedure = DFA_FLOAT,
|
||||
Length = 1,
|
||||
Delay = 0,
|
||||
Wdt = 15,
|
||||
Hgt = 27,
|
||||
},
|
||||
};
|
||||
local PerspectiveR = 20000;
|
||||
local PerspectiveTheta = 25;
|
||||
local PerspectivePhi = 30;
|
||||
local Name = "$Name$";
|
||||
local Collectible = 1;
|
||||
|
||||
local FlySpeed = 100;
|
||||
local Name = "$Name$";
|
|
@ -0,0 +1,8 @@
|
|||
[DefCore]
|
||||
id=CustomAI
|
||||
Version=7
|
||||
Category=C4D_Vehicle | C4D_MouseIgnore
|
||||
Width=1
|
||||
Height=1
|
||||
Mass=1
|
||||
|
|
@ -0,0 +1,554 @@
|
|||
#include AI
|
||||
|
||||
// How far must someone be away from an airship to be independent from it
|
||||
local AIRSHIP_LOST_DIST = 50;
|
||||
// How near must an airship be to the target to dispatch its troops
|
||||
local AIRSHIP_BOARD_DIST = 60;
|
||||
// How near must neighbouring airships be to trigger evasive maneuvers
|
||||
local AIRSHIP_VICINITY_DIST = 60;
|
||||
// How near must an archer be to a target to shoot. Theoretically, archers can shoot from much farer away but that's too brutal for the players
|
||||
local ARCHER_SHOOT_DIST = 500;
|
||||
|
||||
func AddAI(object clonk)
|
||||
{
|
||||
var fx = AI->AddAI(clonk);
|
||||
if (fx)
|
||||
{
|
||||
clonk.ExecuteAI = CustomAI.Execute;
|
||||
fx.ai = CustomAI;
|
||||
fx.ignore_allies = true;
|
||||
}
|
||||
return fx;
|
||||
}
|
||||
|
||||
func SetEnemyData(object clonk, proplist data)
|
||||
{
|
||||
var fx = GetEffect("AI", clonk);
|
||||
if (fx)
|
||||
{
|
||||
if (data.Siege) fx.is_siege = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
func FindTarget(fx, bool parent)
|
||||
{
|
||||
if (parent) return _inherited(fx);
|
||||
return this->GetNearestWindmill();
|
||||
}
|
||||
|
||||
private func FindInventoryWeapon(fx)
|
||||
{
|
||||
// Extra weapons
|
||||
if (fx.weapon = FindContents(Axe))
|
||||
{ fx.strategy = fx.ai.ExecuteMelee; return true; }
|
||||
if (fx.vehicle && fx.vehicle->GetID() == Boomattack)
|
||||
{ fx.weapon = FindContents(Bow); fx.strategy = fx.ai.ExecuteRanged; fx.projectile_speed = 100; fx.aim_wait = 0; fx.ammo_check = fx.ai.HasArrows; fx.ranged=true; return true; }
|
||||
if (inherited(fx, ...)) return true;
|
||||
// no weapon :(
|
||||
return false;
|
||||
}
|
||||
|
||||
func Execute(proplist fx, int time)
|
||||
{
|
||||
if (!fx.target) fx.target = GetNearestWindmill();
|
||||
if (GetAction() == "Ride")
|
||||
{
|
||||
var action_target = GetActionTarget();
|
||||
if (action_target && action_target->GetID() == Balloon)
|
||||
return ExecuteIdle(); // do nothing if hanging from a balloon
|
||||
// else do everything! (action_target == Boomattack)
|
||||
}
|
||||
if (fx.parachute_lost)
|
||||
{
|
||||
if (GetAction() != "Walk")
|
||||
return inherited(fx, time, ...);
|
||||
fx.target = nil;
|
||||
fx.parachute_lost = nil;
|
||||
SetCommand();
|
||||
}
|
||||
|
||||
return inherited(fx, time, ...);
|
||||
}
|
||||
|
||||
private func ExecuteVehicle(fx)
|
||||
{
|
||||
if (!fx.vehicle) return false;
|
||||
if (fx.vehicle->GetID() == Catapult) return _inherited(fx, ...);
|
||||
if (fx.vehicle->GetID() == Airship) return ExecutePilot(fx);
|
||||
return false;
|
||||
}
|
||||
|
||||
private func ExecutePilot(fx) // this function name doesn't sound right
|
||||
{
|
||||
var target = fx.target ?? GetNearestWindmill();
|
||||
if (!target) return false; // ???
|
||||
|
||||
if (GetProcedure() != "PUSH" || GetActionTarget() != fx.vehicle)
|
||||
{
|
||||
if (ObjectDistance(fx.vehicle) > CustomAI.AIRSHIP_LOST_DIST) // We lost the airship (maybe we were flung from it)
|
||||
{
|
||||
fx.strategy = nil;
|
||||
fx.weapon = nil;
|
||||
fx.vehicle->PromoteNewCaptain();
|
||||
fx.vehicle = nil;
|
||||
return true;
|
||||
}
|
||||
if (!GetCommand() || !Random(4)) SetCommand("Grab", fx.vehicle);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Close to the target
|
||||
if (ObjectDistance(target) < CustomAI.AIRSHIP_BOARD_DIST)
|
||||
{
|
||||
fx.vehicle->PrepareToBoard(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
// No command check
|
||||
if (!fx.vehicle->GetCommand())
|
||||
{
|
||||
var point = this->GetBoardingPoint(target);
|
||||
fx.vehicle->SetCommand("MoveTo", nil, point[0], point[1]);
|
||||
fx.target = FindTarget(fx);
|
||||
// Unmovable check
|
||||
} else if (fx.vehicle->GetXDir() == 0 && fx.vehicle->GetYDir() == 0)
|
||||
{
|
||||
fx.stuck_time++;
|
||||
if (fx.stuck_time > 20)
|
||||
fx.vehicle->SetYDir(10);
|
||||
if (fx.stuck_time > 40)
|
||||
fx.vehicle->SetXDir(RandomX(-10,10));
|
||||
if (fx.stuck_time > 60)
|
||||
{
|
||||
if(InsideIslandRectangle(fx.vehicle))
|
||||
return fx.vehicle->PrepareToBoard(this);
|
||||
else
|
||||
fx.vehicle->Sink();
|
||||
}
|
||||
} else if(fx.stuck_time)
|
||||
{
|
||||
fx.stuck_time = 0;
|
||||
}
|
||||
|
||||
// Vicinity check
|
||||
var ships = FindObjects(Find_Distance(CustomAI.AIRSHIP_VICINITY_DIST), Find_ID(Airship), Find_Exclude(fx.vehicle));
|
||||
if (GetLength(ships) && !InsideIslandRectangle(fx.vehicle))
|
||||
{
|
||||
var centerX = 0, centerY = 0;
|
||||
for (var ship in ships)
|
||||
{
|
||||
centerX += ship->GetX();
|
||||
centerY += ship->GetY();
|
||||
}
|
||||
centerX += fx.vehicle->GetX();
|
||||
centerY += fx.vehicle->GetY();
|
||||
centerX /= GetLength(ships) + 1;
|
||||
centerY /= GetLength(ships) + 1;
|
||||
// Move away from mass center
|
||||
var targetX = fx.vehicle->GetX() - centerX;
|
||||
var targetY = fx.vehicle->GetY() - centerY;
|
||||
fx.vehicle->SetCommand("MoveTo", nil, BoundBy(GetX() + targetX * 2, 30, LandscapeWidth()-30), BoundBy(GetY() + targetY * 2, 30, LandscapeHeight()-30));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private func CheckVehicleAmmo(fx, object catapult)
|
||||
{
|
||||
// Ammo is auto-refilled
|
||||
return true;
|
||||
}
|
||||
|
||||
func ExecuteIdle(proplist fx)
|
||||
{
|
||||
// Idle execution overridden by Execute
|
||||
return true;
|
||||
}
|
||||
|
||||
// Wait until airship arrives
|
||||
func ExecuteMelee(fx)
|
||||
{
|
||||
// Don't attack if still on the carrier
|
||||
if (fx.carrier)
|
||||
{
|
||||
if (ObjectDistance(fx.carrier) > CustomAI.AIRSHIP_LOST_DIST) fx.carrier = nil;
|
||||
if (GetCommand()) SetCommand("None");
|
||||
if (fx.shield) return ExecuteProtection(fx);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Still carrying the melee weapon?
|
||||
if (fx.weapon->Contained() != this) { fx.weapon=nil; return false; }
|
||||
// Are we in range?
|
||||
var x=GetX(), y=GetY(), tx=fx.target->GetX(), ty=fx.target->GetY();
|
||||
var dx = tx-x, dy = ty-y;
|
||||
var dy_tolerance = -15;
|
||||
if (fx.target->~IsMainObjective()) dy_tolerance = -40; // Don't jump in front of the windmills you will fall off the islands
|
||||
|
||||
if (Abs(dx) <= 10 && PathFree(x,y,tx,ty))
|
||||
{
|
||||
if (dy >= dy_tolerance)
|
||||
{
|
||||
// target is under us - sword slash downwards!
|
||||
if (!CheckHandsAction(fx)) return true;
|
||||
// Stop here
|
||||
SetCommand("None"); SetComDir(COMD_None);
|
||||
// cooldown?
|
||||
if (!fx.weapon->CanStrikeWithWeapon(this))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// OK, slash!
|
||||
SelectItem(fx.weapon);
|
||||
return fx.weapon->ControlUse(this, tx,ty);
|
||||
}
|
||||
if (fx.target->~IsMainObjective())
|
||||
{
|
||||
// Don't jump for higher windmills, get a new target!
|
||||
fx.target = nil;
|
||||
SetCommand();
|
||||
return true;
|
||||
}
|
||||
// Clonk is above us - jump there
|
||||
ExecuteJump();
|
||||
if (dx<-5) SetComDir(COMD_Left); else if (dx>5) SetComDir(COMD_Right); else SetComDir(COMD_None);
|
||||
}
|
||||
// Not in range. Walk there.
|
||||
if (!GetCommand() || !Random(10)) SetCommand("MoveTo", fx.target);
|
||||
//Message("Melee %s @ %s!!!", fx.weapon->GetName(), fx.target->GetName());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Use shields only if still on the airship
|
||||
func ExecuteProtection(fx)
|
||||
{
|
||||
if (!fx.carrier) return false;
|
||||
return _inherited(fx);
|
||||
}
|
||||
|
||||
// Don't evade you will fall off the islands
|
||||
func ExecuteEvade(fx, int foo, int bar)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Always shoot
|
||||
private func ExecuteRanged(fx)
|
||||
{
|
||||
// Still carrying the bow?
|
||||
if (fx.weapon->Contained() != this) { fx.weapon=fx.post_aim_weapon=nil; return false; }
|
||||
// Finish shooting process
|
||||
if (fx.post_aim_weapon)
|
||||
{
|
||||
// wait max one second after shot (otherwise may be locked in wait animation forever if something goes wrong during shot)
|
||||
if (FrameCounter() - fx.post_aim_weapon_time < 36)
|
||||
if (IsAimingOrLoading()) return true;
|
||||
fx.post_aim_weapon = nil;
|
||||
}
|
||||
// Target still in guard range?
|
||||
//if (!CheckTargetInGuardRange(fx)) return false;
|
||||
// Don't check for this guard range crap, fire if near enough
|
||||
if (ObjectDistance(fx.target, this) > CustomAI.ARCHER_SHOOT_DIST) return false;
|
||||
|
||||
// Shooting at windmill
|
||||
if (fx.target->~IsMainObjective())
|
||||
{
|
||||
// Look for clonks in range
|
||||
if (!Random(25))
|
||||
{
|
||||
var target = FindTarget(fx, true);
|
||||
if (target)
|
||||
fx.target = target;
|
||||
}
|
||||
} else {
|
||||
// Firing at a clonk, maybe fire at a windmill
|
||||
if (!Random(50) || !PathFree(GetX(), GetY(), fx.target->GetX(), fx.target->GetY()))
|
||||
fx.target = FindTarget(fx);
|
||||
if (!fx.target) return; // ???
|
||||
}
|
||||
|
||||
// Look at target
|
||||
ExecuteLookAtTarget(fx);
|
||||
// Make sure we can shoot
|
||||
if (!IsAimingOrLoading() || !fx.aim_weapon)
|
||||
{
|
||||
CancelAiming(fx);
|
||||
if (!CheckHandsAction(fx)) return true;
|
||||
// Start aiming
|
||||
SelectItem(fx.weapon);
|
||||
if (!fx.weapon->ControlUseStart(this, fx.target->GetX()-GetX(), fx.target->GetY()-GetY())) return false; // something's broken :(
|
||||
fx.aim_weapon = fx.weapon;
|
||||
fx.aim_time = fx.time;
|
||||
fx.post_aim_weapon = nil;
|
||||
// Enough for now
|
||||
return;
|
||||
}
|
||||
// Stuck in aim procedure check?
|
||||
if (GetEffect("IntAimCheckProcedure", this) && !this->ReadyToAction())
|
||||
return ExecuteStand(fx);
|
||||
// Calculate offset to target. Take movement into account
|
||||
// Also aim for the head (y-4) so it's harder to evade by jumping
|
||||
var x=GetX(), y=GetY(), tx=fx.target->GetX(), ty=fx.target->GetY()-4;
|
||||
var d = Distance(x,y,tx,ty);
|
||||
var dt = d * 10 / fx.projectile_speed; // projected travel time of the arrow
|
||||
tx += GetTargetXDir(fx.target, dt);
|
||||
ty += GetTargetYDir(fx.target, dt);
|
||||
if (!fx.target->GetContact(-1)) if (!fx.target->GetCategory() & C4D_StaticBack) ty += GetGravity()*dt*dt/200;
|
||||
|
||||
// Get shooting angle
|
||||
var shooting_angle;
|
||||
if (fx.ranged_direct)
|
||||
shooting_angle = Angle(x, y, tx, ty, 10);
|
||||
else {
|
||||
if (PathFree(x,y,tx,ty))
|
||||
shooting_angle = GetBallisticAngle(tx-x, ty-y, fx.projectile_speed, 160);
|
||||
else
|
||||
shooting_angle = GetUpperBallisticAngle(tx-x, ty-y, fx.projectile_speed, 160);
|
||||
}
|
||||
if (GetType(shooting_angle) != C4V_Nil)
|
||||
{
|
||||
// No ally on path? Also search for allied animals, just in case.
|
||||
var ally;
|
||||
if (!fx.ignore_allies) ally = FindObject(Find_OnLine(0,0,tx-x,ty-y), Find_Exclude(this), Find_OCF(OCF_Alive), Find_Owner(GetOwner()));
|
||||
if (ally)
|
||||
{
|
||||
if (ExecuteJump()) return true;
|
||||
// can't jump and ally is in the way. just wait.
|
||||
}
|
||||
else
|
||||
{
|
||||
//Message("Bow @ %d!!!", shooting_angle);
|
||||
// Aim/Shoot there
|
||||
x = Sin(shooting_angle, 1000, 10);
|
||||
y = -Cos(shooting_angle, 1000, 10);
|
||||
fx.aim_weapon->ControlUseHolding(this, x,y);
|
||||
if (this->IsAiming() && fx.time >= fx.aim_time + fx.aim_wait)
|
||||
{
|
||||
//Log("Throw angle %v speed %v to reach %d %d", shooting_angle, fx.projectile_speed, tx-GetX(), ty-GetY());
|
||||
fx.aim_weapon->ControlUseStop(this, x,y);
|
||||
fx.post_aim_weapon = fx.aim_weapon; // assign post-aim status to allow slower shoot animations to pass
|
||||
fx.post_aim_weapon_time = FrameCounter();
|
||||
fx.aim_weapon = nil;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Path not free or out of range. Just wait for enemy to come...
|
||||
fx.aim_weapon->ControlUseHolding(this,tx-x,ty-y);
|
||||
// Might also change target if current is unreachable
|
||||
var new_target;
|
||||
if (!Random(3)) if (new_target = FindTarget(fx)) fx.target = new_target;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Used when no free path to target (presumably because of the airship gondola floor)
|
||||
// Returns the 'upper' shooting angle, see GetBallisticAngle etc.
|
||||
private func GetUpperBallisticAngle(int dx, int dy, int v, int max_angle)
|
||||
{
|
||||
// v is in 1/10 pix/frame
|
||||
// gravity is in 1/100 pix/frame^2
|
||||
var g = GetGravity();
|
||||
// correct vertical distance to account for integration error
|
||||
// engine adds gravity after movement, so targets fly higher than they should
|
||||
// thus, we aim lower. we don't know the travel time yet, so we assume some 90% of v is horizontal
|
||||
// (it's ~2px correction for 200px shooting distance)
|
||||
dy += Abs(dx)*g*10/(v*180);
|
||||
//Log("Correction: Aiming %d lower!", Abs(dx)*q*10/(v*180));
|
||||
// q is in 1/10000 (pix/frame)^4
|
||||
var q = v**4 - g*(g*dx*dx-2*dy*v*v); // dy is negative up
|
||||
if (q<0) return nil; // out of range
|
||||
var a = (Angle(0, 0, g * dx, -Sqrt(q) - v * v, 10) + 1800) % 3600 - 1800;
|
||||
// Check bounds
|
||||
if(!Inside(a, -10 * max_angle, 10 * max_angle)) return nil;
|
||||
return a;
|
||||
}
|
||||
|
||||
// Don't move exacty to target's position (throwing check will fail then)
|
||||
private func ExecuteThrow(fx)
|
||||
{
|
||||
// Still carrying the weapon to throw?
|
||||
if (fx.weapon->Contained() != this) { fx.weapon=nil; return false; }
|
||||
// Path to target free?
|
||||
var x=GetX(), y=GetY(), tx=fx.target->GetX(), ty=fx.target->GetY();
|
||||
if (PathFree(x,y,tx,ty))
|
||||
{
|
||||
var throw_speed = this.ThrowSpeed;
|
||||
if (fx.weapon->GetID() == Javelin) throw_speed *= 2;
|
||||
var rx = (throw_speed*throw_speed)/(100*GetGravity()); // horizontal range for 45 degree throw if enemy is on same height as we are
|
||||
var ry = throw_speed*7/(GetGravity()*10); // vertical range of 45 degree throw
|
||||
var dx = tx-x, dy = ty-y+15*GetCon()/100; // distance to target. Reduce vertical distance a bit because throwing exit point is not at center
|
||||
// Check range
|
||||
// Could calculate the optimal parabulum here, but that's actually not very reliable on moving targets
|
||||
// It's usually better to throw straight at the target and only throw upwards a bit if the target stands on high ground or is far away
|
||||
// Also ignoring speed added by own velocity, etc...
|
||||
if (Abs(dx)*ry-Min(dy)*rx <= rx*ry)
|
||||
{
|
||||
// We're in range. Can throw?
|
||||
if (!CheckHandsAction(fx)) return true;
|
||||
// OK. Calc throwing direction
|
||||
dy -= dx*dx/rx; // big math!
|
||||
// And throw!
|
||||
//Message("Throw!");
|
||||
SetCommand("None"); SetComDir(COMD_Stop);
|
||||
SelectItem(fx.weapon);
|
||||
return this->ControlThrow(fx.weapon, dx, dy);
|
||||
}
|
||||
}
|
||||
// Can't reach target yet. Walk towards it.
|
||||
if (!GetCommand() || !Random(3))
|
||||
{
|
||||
var tx = fx.target->GetX();
|
||||
SetCommand("MoveTo", nil, BoundBy(GetX(), tx-30, tx+30), fx.target->GetY());
|
||||
}
|
||||
//Message("Throw %s @ %s!!!", fx.weapon->GetName(), fx.target->GetName());
|
||||
return true;
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
/* Enemy creation */
|
||||
|
||||
static g_last_airship;
|
||||
|
||||
func Departure_WeaponRespawn(object container)
|
||||
{
|
||||
// Weapon used? Schedule to respawn a new one!
|
||||
if (container->GetAlive() || container->GetID()==Catapult) container->ScheduleCall(container, CustomAI.DoWeaponRespawn, 5, 1, GetID());
|
||||
this.Departure = nil;
|
||||
}
|
||||
|
||||
func DoWeaponRespawn(id_weapon)
|
||||
{
|
||||
if (GetAlive() || GetID()==Catapult)
|
||||
{
|
||||
var re_weapon = CreateContents(id_weapon);
|
||||
if (re_weapon) re_weapon.Departure = CustomAI.Departure_WeaponRespawn;
|
||||
return re_weapon;
|
||||
}
|
||||
}
|
||||
|
||||
func Inventory_GetCarryTransform()
|
||||
{
|
||||
if (GetID().GetCarryTransform)
|
||||
return Trans_Mul(Call(GetID().GetCarryTransform, ...), this.ExtraTransform);
|
||||
else
|
||||
return this.ExtraTransform;
|
||||
}
|
||||
|
||||
func LaunchEnemy(proplist enemy, int xmin, int xrange, int ymin, yrange)
|
||||
{
|
||||
// Create enemy (usually a Clonk)
|
||||
var x = xmin+Random(xrange);
|
||||
var y = ymin+Random(yrange);
|
||||
var obj = CreateObjectAbove(enemy.Type ?? Clonk, x,y, ENEMY), clonk;
|
||||
if (!obj) return nil;
|
||||
obj->SetController(ENEMY);
|
||||
// Enemy visuals
|
||||
if (enemy.Skin)
|
||||
{
|
||||
if (GetType(enemy.Skin) == C4V_Array)
|
||||
{
|
||||
obj->SetSkin(enemy.Skin[0]);
|
||||
obj->SetMeshMaterial(enemy.Skin[1]);
|
||||
}
|
||||
else
|
||||
obj->SetSkin(enemy.Skin);
|
||||
}
|
||||
if (GetType(enemy.Backpack)) obj->~RemoveBackpack();
|
||||
if (enemy.Scale) obj->SetMeshTransformation(Trans_Scale(enemy.Scale[0], enemy.Scale[1], enemy.Scale[2]), 6);
|
||||
if (enemy.Name) obj->SetName(enemy.Name);
|
||||
obj->SetColor(enemy.Color);
|
||||
// Physical properties
|
||||
obj.MaxEnergy = (enemy.Energy ?? 50) * 1000;
|
||||
obj->DoEnergy(10000);
|
||||
if (enemy.Speed)
|
||||
{
|
||||
// Speed: Modify Speed in all ActMap entries
|
||||
if (obj.ActMap == obj.Prototype.ActMap) obj.ActMap = new obj.ActMap {};
|
||||
for (var action in /*obj.ActMap->GetProperties()*/ ["Walk", "Scale", "Dig", "Swim", "Hangle", "Jump", "WallJump", "Dive", "Push"]) // obj.ActMap->GetProperties() doesn't work :(
|
||||
{
|
||||
if (action == "Prototype") continue;
|
||||
if (obj.ActMap[action] == obj.Prototype.ActMap[action]) obj.ActMap[action] = new obj.ActMap[action] {};
|
||||
obj.ActMap[action].Speed = obj.ActMap[action].Speed * enemy.Speed / 100;
|
||||
}
|
||||
obj.JumpSpeed = obj.JumpSpeed * enemy.Speed / 100;
|
||||
obj.FlySpeed = obj.FlySpeed * enemy.Speed / 100;
|
||||
}
|
||||
obj.MaxContentsCount = CustomAI.Clonk_MaxContentsCount;
|
||||
obj->MakeInvincibleToFriendlyFire();
|
||||
obj.MaxContentsCountVal = 2;
|
||||
// Reward for killing enemy
|
||||
obj.Bounty = enemy.Bounty;
|
||||
// Vehicles
|
||||
var vehicle;
|
||||
if (enemy.Vehicle)
|
||||
{
|
||||
if (enemy.Vehicle == Balloon)
|
||||
{
|
||||
Balloon->ControlUseStart(obj);
|
||||
} else if (enemy.Vehicle == Boomattack) {
|
||||
vehicle = CreateObjectAbove(enemy.Vehicle, x,y+10, ENEMY);
|
||||
// Add boomattack to enemy array
|
||||
g_spawned_enemies[GetLength(g_spawned_enemies)] = vehicle;
|
||||
obj->SetAction("Ride", vehicle);
|
||||
} else {
|
||||
vehicle = CreateObjectAbove(enemy.Vehicle, x,y+10, ENEMY);
|
||||
obj->SetAction("Push", vehicle);
|
||||
if (vehicle && vehicle->GetID() == Airship)
|
||||
g_last_airship = vehicle;
|
||||
}
|
||||
}
|
||||
// Enemy inventory
|
||||
if (enemy.Inventory) for (var inv in ForceVal2Array(enemy.Inventory))
|
||||
{
|
||||
var inv_type = inv.InvType;
|
||||
if (!inv_type) inv_type = inv;
|
||||
var inv_obj = obj->CreateContents(inv_type);
|
||||
if (inv_obj)
|
||||
{
|
||||
// Marker for custom weapon behaviour
|
||||
inv_obj.IsAIWeapon = true;
|
||||
// Infinite ammo
|
||||
inv_obj->~SetInfiniteStackCount();
|
||||
if (GetIndexOf(g_respawning_weapons, inv_type) >= 0) inv_obj.Departure = CustomAI.Departure_WeaponRespawn;
|
||||
// Extra settings?
|
||||
if (inv.InvType)
|
||||
{
|
||||
// Visuals
|
||||
if (inv.Scale) { inv_obj.GetCarryTransform = CustomAI.Inventory_GetCarryTransform; inv_obj.ExtraTransform = Trans_Scale(inv.Scale, inv.Scale, inv.Scale); }
|
||||
if (inv.Material) inv_obj->SetMeshMaterial(inv.Material);
|
||||
// Strength
|
||||
if (inv.Strength) inv_obj->SetStrength(inv.Strength);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Flying AI
|
||||
if (obj->~IsFlyingEnemy())
|
||||
{
|
||||
// Flying enemies all init themselves to fly at the statue at the moment
|
||||
}
|
||||
else
|
||||
{
|
||||
// Init AI: Run towards statue
|
||||
CustomAI->AddAI(obj);
|
||||
CustomAI->SetMaxAggroDistance(obj, LandscapeWidth());
|
||||
CustomAI->SetGuardRange(obj, 0,0,LandscapeWidth(),LandscapeHeight()); // nowhere to run!
|
||||
var fx = GetEffect("AI", obj);
|
||||
if (fx) fx.vehicle = vehicle;
|
||||
if (enemy.IsCrew) // is airship crew member, spawn on last created airship
|
||||
if (g_last_airship)
|
||||
{
|
||||
obj->SetPosition(g_last_airship->GetX() + RandomX(-15,15), g_last_airship->GetY()+12);
|
||||
if (fx)
|
||||
fx.carrier = g_last_airship;
|
||||
}
|
||||
}
|
||||
// Remember this clonk to end wave when all enemies have been killed
|
||||
g_spawned_enemies[GetLength(g_spawned_enemies)] = obj;
|
||||
return obj;
|
||||
}
|
||||
|
||||
// forward max contents count to property
|
||||
func Clonk_MaxContentsCount() { return this.MaxContentsCountVal; }
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
Binary file not shown.
|
@ -0,0 +1,56 @@
|
|||
/* Automatically created objects file */
|
||||
|
||||
static g_goal, g_object_fade, g_flag, g_windgen1, g_windgen2, g_windgen3, g_windmill, g_chest, g_windbag, g_lorry;
|
||||
|
||||
func InitializeObjects()
|
||||
{
|
||||
g_goal = CreateObject(Goal_SaveTheWindmills, 10, 10);
|
||||
g_goal.StaticSaveVar = "g_goal";
|
||||
|
||||
g_object_fade = CreateObject(Rule_ObjectFade, 0, 0);
|
||||
g_object_fade.StaticSaveVar = "g_object_fade";
|
||||
|
||||
g_flag = CreateObject(Flagpole, 1033, 937);
|
||||
g_flag.StaticSaveVar = "g_flag";
|
||||
g_flag->SetNeutral(true);
|
||||
|
||||
g_windgen1 = CreateObject(WindGenerator, 1041, 799);
|
||||
g_windgen1.StaticSaveVar = "g_windgen1";
|
||||
g_windgen2 = CreateObject(WindGenerator, 1105, 907);
|
||||
g_windgen2.StaticSaveVar = "g_windgen2";
|
||||
g_windgen3 = CreateObject(WindGenerator, 898, 908);
|
||||
g_windgen3.StaticSaveVar = "g_windgen3";
|
||||
g_windmill = CreateObjectAbove(Windmill, 991, 970);
|
||||
g_windmill.StaticSaveVar = "g_windmill";
|
||||
|
||||
g_chest = CreateObjectAbove(Chest, 997, 1023);
|
||||
g_chest.StaticSaveVar = "g_chest";
|
||||
g_windbag = g_chest->CreateContents(WindBag);
|
||||
g_windbag.StaticSaveVar = "g_windbag";
|
||||
|
||||
g_lorry = CreateObjectAbove(Lorry, 924, 938);
|
||||
g_lorry.StaticSaveVar = "g_lorry";
|
||||
|
||||
CreateObjectAbove(Grass, 1006, 826);
|
||||
CreateObjectAbove(Grass, 990, 826);
|
||||
CreateObjectAbove(Grass, 955, 835);
|
||||
CreateObjectAbove(Grass, 975, 827);
|
||||
|
||||
CreateObjectAbove(Flower, 965, 840);
|
||||
CreateObjectAbove(Flower, 1016, 835);
|
||||
|
||||
var Trunk001 = CreateObject(Trunk, 827, 937);
|
||||
Trunk001->SetR(-30);
|
||||
|
||||
CreateObjectAbove(Tree_Coniferous3, 1167, 945);
|
||||
|
||||
var Branch001 = CreateObject(Branch, 938, 839);
|
||||
Branch001->SetR(-30);
|
||||
|
||||
var Ropeladder001 = CreateObject(Ropeladder, 1057, 826);
|
||||
Ropeladder001->Unroll(DIR_Right);
|
||||
|
||||
CreateObject(Basement, 957, 859);
|
||||
|
||||
return true;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 9.0 KiB |
|
@ -0,0 +1,3 @@
|
|||
[Particle]
|
||||
Name=Planks
|
||||
Face=0,0,12,104,-6,-52
|
|
@ -0,0 +1,15 @@
|
|||
[PlayerInfoList]
|
||||
LastPlayerID=1
|
||||
|
||||
[Client]
|
||||
ID=0
|
||||
Flags=Initial
|
||||
|
||||
[Player]
|
||||
Name="Don Quijote"
|
||||
Type=Script
|
||||
Flags=Joined|AttributesFixed|NoScenarioInit|NoEliminationCheck
|
||||
ID=1
|
||||
Color=4294901760
|
||||
Team=2
|
||||
GameNumber=10
|
|
@ -2,111 +2,17 @@
|
|||
|
||||
#include Library_Goal
|
||||
|
||||
static const SBRD_Rockets = 1;
|
||||
local score, boss, wave;
|
||||
|
||||
func Initialize()
|
||||
{
|
||||
score = [];
|
||||
boss = false;
|
||||
wave = 1;
|
||||
// init scoreboard
|
||||
Scoreboard->Init(
|
||||
[{key = "windmills", title = Goal_SaveTheWindmills, sorted = true, desc = true, default = 0, priority = 80}]
|
||||
);
|
||||
Scoreboard->SetTitle("$ScoreCaption$");
|
||||
// Remove settlement eval data.
|
||||
HideSettlementScoreInEvaluation(true);
|
||||
inherited(...);
|
||||
}
|
||||
|
||||
public func IsFulfilled()
|
||||
{
|
||||
if(!FindObject(Find_ID(WindGenerator)))
|
||||
{
|
||||
for(var i = 0; i<GetPlayerCount(); ++i)
|
||||
{
|
||||
EliminatePlayer(GetPlayerByIndex(i));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if(boss)
|
||||
{
|
||||
if(!FindObject(Find_ID(BigBoomattack)))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return is_fulfilled;
|
||||
}
|
||||
|
||||
local is_fulfilled = false;
|
||||
|
||||
public func GetDescription(int plr)
|
||||
{
|
||||
return this.Description;
|
||||
}
|
||||
|
||||
public func Activate(int byplr)
|
||||
{
|
||||
MessageWindow(this.Description, byplr);
|
||||
return;
|
||||
}
|
||||
|
||||
public func IncShotScore(int plr)
|
||||
{
|
||||
var plrid = GetPlayerID(plr);
|
||||
score[plrid]++;
|
||||
if (plr != NO_OWNER)
|
||||
{
|
||||
Scoreboard->SetPlayerData(plr, "windmills", score[plrid]);
|
||||
}
|
||||
NotifyHUD();
|
||||
}
|
||||
|
||||
public func SetWave(int num)
|
||||
{
|
||||
wave = num;
|
||||
}
|
||||
|
||||
public func BossAttacks()
|
||||
{
|
||||
boss = true;
|
||||
}
|
||||
|
||||
public func GetShortDescription(int plr)
|
||||
{
|
||||
//var allscore = 0;
|
||||
//for(var i=0; i<GetLength(score); ++i)
|
||||
// allscore += score[i];
|
||||
//return Format("$ShotScore$",allscore);
|
||||
if (wave < 13)
|
||||
return Format("$WaveCount$", wave);
|
||||
else if (wave == 13)
|
||||
return "$WaveBoss$";
|
||||
}
|
||||
|
||||
protected func InitializePlayer(int plr)
|
||||
{
|
||||
var plrid = GetPlayerID(plr);
|
||||
score[plrid] = 0;
|
||||
// Create scoreboard player entry for this player
|
||||
Scoreboard->NewPlayerEntry(plr);
|
||||
return _inherited(plr, ...);
|
||||
}
|
||||
|
||||
protected func RemovePlayer(int plr)
|
||||
{
|
||||
var plrid = GetPlayerID(plr);
|
||||
AddEvaluationData(Format("$MsgEval$", score[plrid]), plrid);
|
||||
return _inherited(plr, ...);
|
||||
}
|
||||
|
||||
public func OnGameOver()
|
||||
{
|
||||
for (var i = 0; i < GetPlayerCount(); i++)
|
||||
{
|
||||
var plrid = GetPlayerID(GetPlayerByIndex(i));
|
||||
AddEvaluationData(Format("$MsgEval$", score[plrid]), plrid);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
local Name = "$Name$";
|
||||
local Description = "$Description$";
|
||||
|
|
|
@ -1,8 +1,2 @@
|
|||
Name=Rette die Windräder
|
||||
ShotScore=%d abgeschossen
|
||||
Shots=Treffer
|
||||
ScoreCaption=Die beste Hüter
|
||||
WaveCount=Wave %d
|
||||
WaveBoss=Boss wave
|
||||
MsgEval=You have shot %d windmill-eating rockets.
|
||||
Description=Zerstöre die Windrad-fressenden Raketen bevor sie die Windräder erreichen! Wenn kein Windrad mehr übrig ist, verlierst Du
|
||||
Name=Hüte die Windräder
|
||||
Description=Verteidige die Windräder gegen die Feinde!
|
|
@ -1,8 +1,2 @@
|
|||
Name=Save the windmills
|
||||
ShotScore=shot %d
|
||||
Shots=Shot
|
||||
ScoreCaption=The best guardian
|
||||
WaveCount=Wave %d
|
||||
WaveBoss=Boss wave
|
||||
MsgEval=You have shot %d windmill-eating rockets.
|
||||
Description=Destroy the windmill-eating rockets before they reach the windmills! If no windmill remains, you loose.
|
||||
Name=Guard the windmills
|
||||
Description=Defend the windmills against the enemies!
|
|
@ -1,29 +1,28 @@
|
|||
[Head]
|
||||
Icon=39
|
||||
Title=Windmill
|
||||
Version=6,0
|
||||
Version=7
|
||||
Difficulty=90
|
||||
|
||||
[Definitions]
|
||||
Definition1=Objects.ocd
|
||||
Definition2=Decoration.ocd
|
||||
|
||||
[Player1]
|
||||
Crew=Clonk=1
|
||||
Wealth=50,0,0,250
|
||||
|
||||
[Player2]
|
||||
Crew=Clonk=1
|
||||
Wealth=50,0,0,250
|
||||
|
||||
[Player3]
|
||||
Crew=Clonk=1
|
||||
Wealth=50,0,0,250
|
||||
|
||||
[Player4]
|
||||
Crew=Clonk=1
|
||||
Wealth=50,0,0,250
|
||||
|
||||
[Landscape]
|
||||
Sky=Sky
|
||||
MapZoom=10
|
||||
BottomOpen=1
|
||||
SkyScrollMode=2
|
||||
|
||||
[Weather]
|
||||
Climate=0,0,0,100
|
||||
|
|
|
@ -1,134 +1,511 @@
|
|||
/*
|
||||
Boom command
|
||||
Authors: Randrian, Newton
|
||||
|
||||
Funny little survival scenario to train with the bow.
|
||||
Guardians of the Windmills
|
||||
Authors: Randrian, Newton, Clonkonaut
|
||||
|
||||
Defend the windmills against waves of enemies
|
||||
*/
|
||||
|
||||
// in seconds
|
||||
static const Boomattack_wave_delay = 25;
|
||||
static g_goal, g_object_fade, g_flag, g_windgen1, g_windgen2, g_windgen3, g_windmill, g_chest, g_windbag, g_lorry;
|
||||
|
||||
static const Boomattack_angle_spread = 45;
|
||||
// the bigger this value, the slower does the attack size grow
|
||||
static const Boomattack_attack_growth = 90;
|
||||
static g_wave; // index of current wave
|
||||
static g_spawned_enemies;
|
||||
static g_relaunchs; // array of relaunch counts
|
||||
static g_scores; // array of player scores
|
||||
static g_ai; // derived from AI; contains changes for this scenario
|
||||
static g_homebases; // item management / buy menus for each player
|
||||
static g_lost; // True if all windmills are destroyed
|
||||
static const ENEMY = 10; // player number of enemy
|
||||
static const ALLOW_DEBUG_COMMANDS = true;
|
||||
|
||||
static const MAX_RELAUNCH = 10;
|
||||
|
||||
static shared_wealth_remainder;
|
||||
|
||||
//======================================================================
|
||||
/* Initialization */
|
||||
|
||||
func Initialize()
|
||||
{
|
||||
var offs = 45;
|
||||
CreateObjectAbove(WindGenerator, 1147, 938+offs)->SetR(7);
|
||||
CreateObjectAbove(WindGenerator, 985, 1105+offs)->SetR(-170);
|
||||
CreateObjectAbove(WindGenerator, 1055, 1085+offs)->SetR(140);
|
||||
CreateObjectAbove(WindGenerator, 971, 857+offs)->SetR(-20);
|
||||
CreateObjectAbove(WindGenerator, 1147, 1035+offs)->SetR(160);
|
||||
CreateObjectAbove(WindGenerator, 1036, 870+offs)->SetR(-10);
|
||||
CreateObjectAbove(WindGenerator, 1081, 873+offs)->SetR(18);
|
||||
CreateObjectAbove(WindGenerator, 858, 930+offs)->SetR(-10);
|
||||
|
||||
CreateObject(Goal_SaveTheWindmills,10,10);
|
||||
PlaceGrass(100, 800, 1400);
|
||||
SetSkyParallax(0,25,25,0,0,0,0);
|
||||
AddEffect("BoomAttack", nil, 100, 35);
|
||||
Sound("Environment::WindLoop",true,40,nil,+1);
|
||||
// dev stuff (we will forget to turn this off for release)
|
||||
AddMsgBoardCmd("waveinfo", "GameCall(\"ShowWaveInfo\")");
|
||||
AddMsgBoardCmd("next", "GameCall(\"SetNextWave\", \"%s\")");
|
||||
AddMsgBoardCmd("nextwait", "GameCall(\"SetNextWave\", \"%s\", true)");
|
||||
AddMsgBoardCmd("scrooge", "GameCall(\"DoWealthForAll\", 1000000000)");
|
||||
// Wealth shown at all time
|
||||
GUI_Controller->ShowWealth();
|
||||
// static variable init
|
||||
g_homebases = [];
|
||||
InitWaveData();
|
||||
}
|
||||
|
||||
global func FxBoomAttackTimer(object target, effect, int time)
|
||||
func InitializePlayer(int plr, int iX, int iY, object pBase, int iTeam)
|
||||
{
|
||||
var wave = 1+time/35/Boomattack_wave_delay;
|
||||
if (GetPlayerType(plr) != C4PT_User) return;
|
||||
|
||||
if(time/35 % Boomattack_wave_delay == 1)
|
||||
if (g_lost) { EliminatePlayer(plr); return; } // no post-elimination join
|
||||
if (!g_relaunchs)
|
||||
{
|
||||
var gol = FindObject(Find_ID(Goal_SaveTheWindmills));
|
||||
if(gol)
|
||||
gol->SetWave(wave);
|
||||
if (wave < 13)
|
||||
{
|
||||
CustomMessage(Format(" $MsgWave$ ",wave),nil,NO_OWNER);
|
||||
var wave_strength = Sqrt(9+GetPlayerCount()*time/Boomattack_attack_growth);
|
||||
CreateAttackWave( Random(360) , wave_strength,Boomattack_angle_spread);
|
||||
}
|
||||
else if (wave == 13)
|
||||
{
|
||||
CustomMessage(" $MsgBoss$ ",nil,NO_OWNER);
|
||||
CreateAttackWave( Random(360) , -1, Boomattack_angle_spread);
|
||||
var gol = FindObject(Find_ID(Goal_SaveTheWindmills));
|
||||
if(gol) gol->BossAttacks();
|
||||
}
|
||||
|
||||
g_relaunchs = [];
|
||||
g_scores = [];
|
||||
Scoreboard->Init([{key = "relaunchs", title = Rule_Restart, sorted = true, desc = true, default = "", priority = 75},
|
||||
{key = "score", title = Nugget, sorted = true, desc = true, default = "0", priority = 100}]);
|
||||
}
|
||||
}
|
||||
g_relaunchs[plr] = MAX_RELAUNCH;
|
||||
g_scores[plr] = 0;
|
||||
Scoreboard->NewPlayerEntry(plr);
|
||||
Scoreboard->SetPlayerData(plr, "relaunchs", g_relaunchs[plr]);
|
||||
Scoreboard->SetPlayerData(plr, "score", g_scores[plr]);
|
||||
|
||||
global func CreateAttackWave(int angle, int rockets, int anglespread)
|
||||
{
|
||||
var radius = Min(LandscapeWidth()/2, LandscapeHeight()/2);
|
||||
var rocket_id = Boomattack;
|
||||
// boss
|
||||
if(rockets == -1)
|
||||
{
|
||||
rockets = 1;
|
||||
rocket_id = BigBoomattack;
|
||||
}
|
||||
|
||||
for(var i=0; i<rockets; ++i)
|
||||
{
|
||||
var rocket_angle = angle + Random(anglespread) - anglespread/2;
|
||||
var rocket_radius = radius * RandomX(80,100) / 100;
|
||||
var x = Sin(rocket_angle, rocket_radius)+LandscapeWidth()/2;
|
||||
var y = -Cos(rocket_angle, rocket_radius)+LandscapeHeight()/2;
|
||||
CreateObject(Homebase, 0,0, plr);
|
||||
|
||||
CreateObjectAbove(rocket_id, x, y)->Launch(rocket_angle + 180);
|
||||
}
|
||||
|
||||
for(var i=0; i<GetPlayerCount(); ++i)
|
||||
{
|
||||
var owner = GetPlayerByIndex(i);
|
||||
var gui_arrow = FindObject(Find_ID(GUI_GoalArrow), Find_Owner(owner));
|
||||
if(!gui_arrow)
|
||||
{
|
||||
gui_arrow = CreateObjectAbove(GUI_GoalArrow,0,0,owner);
|
||||
gui_arrow->SetAction("Show", GetCursor(owner));
|
||||
gui_arrow->SetClrModulation(RGB(255,0,0));
|
||||
gui_arrow->SetObjectBlitMode(GFX_BLIT_Additive);
|
||||
}
|
||||
gui_arrow->SetR(angle);
|
||||
gui_arrow.Plane = 500;
|
||||
}
|
||||
}
|
||||
SetPlayerZoomByViewRange(plr, 1200, 0, PLRZOOM_LimitMax);
|
||||
|
||||
func InitializePlayer(int iPlr, int iX, int iY, object pBase, int iTeam)
|
||||
{
|
||||
SetFoW(false,iPlr);
|
||||
JoinPlayer(iPlr);
|
||||
GetHiRank(iPlr)->SetPosition(LandscapeWidth()/2, LandscapeHeight()/2);
|
||||
return;
|
||||
}
|
||||
//DoWealth(plr, 10000000);
|
||||
|
||||
func RemovePlayer(int iPlr)
|
||||
{
|
||||
for(var obj in FindObjects(Find_Owner(iPlr)))
|
||||
{
|
||||
if(obj)
|
||||
obj->RemoveObject();
|
||||
}
|
||||
}
|
||||
|
||||
func RelaunchPlayer(int plr)
|
||||
{
|
||||
var clonk = CreateObjectAbove(Clonk, LandscapeWidth()/2, 600, plr);
|
||||
clonk->MakeCrewMember(plr);
|
||||
SetCursor(plr, clonk);
|
||||
JoinPlayer(plr);
|
||||
var gui_arrow = FindObject(Find_ID(GUI_GoalArrow), Find_Owner(plr));
|
||||
gui_arrow->SetAction("Show", GetCursor(plr));
|
||||
if (!g_wave) StartGame();
|
||||
}
|
||||
|
||||
func JoinPlayer(int iPlr)
|
||||
func RemovePlayer(int plr)
|
||||
{
|
||||
var clonk = GetCrew(iPlr);
|
||||
clonk->DoEnergy(1000);
|
||||
Scoreboard->SetPlayerData(plr, "relaunchs", Icon_Cancel);
|
||||
// Split player's wealth among the remaining players
|
||||
ScheduleCall(nil, Scenario.DoSharedWealth, 50, 1, GetWealth(plr));
|
||||
}
|
||||
|
||||
clonk->CreateContents(Bow);
|
||||
clonk->Collect(CreateObjectAbove(Arrow));
|
||||
clonk->CreateContents(Musket);
|
||||
clonk->Collect(CreateObjectAbove(LeadShot));
|
||||
private func TransferInventory(object from, object to)
|
||||
{
|
||||
// Drop some items that cannot be transferred (such as connected pipes and dynamite igniters)
|
||||
var i = from->ContentsCount(), contents;
|
||||
while (i--)
|
||||
if (contents = from->Contents(i))
|
||||
if (contents->~IsDroppedOnDeath(from))
|
||||
contents->Exit();
|
||||
return to->GrabContents(from);
|
||||
}
|
||||
|
||||
func JoinPlayer(plr, prev_clonk)
|
||||
{
|
||||
var x=991,y = 970;
|
||||
var clonk = GetCrew(plr);
|
||||
if (clonk)
|
||||
{
|
||||
clonk->SetPosition(x,y-10);
|
||||
}
|
||||
else
|
||||
{
|
||||
clonk = CreateObjectAbove(Clonk, x,y, plr);
|
||||
clonk->MakeCrewMember(plr);
|
||||
}
|
||||
SetCursor(plr, clonk);
|
||||
clonk->DoEnergy(1000);
|
||||
clonk->MakeInvincibleToFriendlyFire();
|
||||
// contents
|
||||
clonk.MaxContentsCount = CustomAI.Clonk_MaxContentsCount;
|
||||
clonk.MaxContentsCountVal = 1;
|
||||
if (prev_clonk) TransferInventory(prev_clonk, clonk);
|
||||
if (!clonk->ContentsCount())
|
||||
{
|
||||
clonk->CreateContents(Bow);
|
||||
var arrow = CreateObjectAbove(Arrow);
|
||||
clonk->Collect(arrow);
|
||||
arrow->SetInfiniteStackCount();
|
||||
}
|
||||
clonk->~CrewSelection(); // force update HUD
|
||||
}
|
||||
|
||||
// Enter all buyable things into the homebase
|
||||
func FillHomebase(object homebase)
|
||||
{
|
||||
// Quick buy items on hotkeys
|
||||
homebase->SetQuickbuyItems([WindBag, Bow, Javelin, Musket, GrenadeLauncher, nil, nil, nil, nil, nil]);
|
||||
|
||||
// Buy menu entries
|
||||
homebase->AddCaption("$HomebaseWeapons$");
|
||||
homebase->AddHomebaseItem(new Homebase.ITEMTYPE_Weapon { item = Bow, ammo = Arrow, desc = "$HomebaseDescBow$" });
|
||||
homebase->AddHomebaseItem(new Homebase.ITEMTYPE_Weapon { item = Javelin, cost = 1, desc = "$HomebaseDescJavelin$" , infinite = true});
|
||||
homebase->AddHomebaseItem(new Homebase.ITEMTYPE_Weapon { item = Musket, cost = 50, ammo = LeadShot, desc = "$HomebaseDescMusket$", requirements = ["AdvancedWeapons"] });
|
||||
homebase->AddHomebaseItem(new Homebase.ITEMTYPE_Weapon { item = GrenadeLauncher, ammo = IronBomb, desc = "$HomebaseDescGrenadeLauncher$", requirements = ["MasterWeapons"] });
|
||||
homebase->AddHomebaseItem(new Homebase.ITEMTYPE_Weapon { item = WindBag, cost = 500, desc = "$HomebaseDescWindBag$", requirements = ["MasterWeapons"] });
|
||||
|
||||
homebase->AddCaption("$HomebaseItems$");
|
||||
homebase->AddHomebaseItem(new Homebase.ITEMTYPE_Consumable { item = Bread, cost = 5 });
|
||||
// homebase->AddHomebaseItem(new Homebase.ITEMTYPE_Weapon { item = Hammer, cost = 1000, desc = "$HomebaseDescHammer$", extra_width = 1 });
|
||||
|
||||
homebase->AddCaption("$HomebaseTechnology$");
|
||||
homebase->AddHomebaseItem(new Homebase.ITEMTYPE_Technology { name="$HomebaseAdvancedWeapons$", item = Icon_World,cost = 100, desc="$HomebaseDescAdvancedWeapons$", tech = "AdvancedWeapons" });
|
||||
homebase->AddHomebaseItem(new Homebase.ITEMTYPE_Technology { name="$HomebaseMasterWeapons$", item = Icon_World,cost = 1000, desc = "$HomebaseDescMasterWeapons$", tech = "MasterWeapons", requirements = ["AdvancedWeapons"] });
|
||||
|
||||
homebase->AddCaption("$HomebaseUpgrades$");
|
||||
homebase->AddHomebaseItem(new Homebase.ITEMTYPE_Technology { name="$HomebaseLoadSpeed$", item = Homebase_Icon, graphics="LoadSpeed%d", costs = [100, 500, 1000], desc = "$HomebaseDescLoadSpeed$", tech = "LoadSpeed", tiers=3 });
|
||||
homebase->AddHomebaseItem(new Homebase.ITEMTYPE_Technology { name="$HomebaseShootingStrength$", item = Homebase_Icon, graphics="ShootingStrength%d", costs = [50, 150, 350], desc = "$HomebaseDescShootingStrength$", tech = "ShootingStrength", tiers=3 });
|
||||
homebase->AddHomebaseItem(new Homebase.ITEMTYPE_Technology { name="$HomebaseLife$", item = Homebase_Icon, graphics="Life%d", costs = [10, 50, 100], desc = "$HomebaseDescLife$", tech = "Life", tiers=3 });
|
||||
homebase->AddCaption("$HomebaseArtifacts$");
|
||||
}
|
||||
|
||||
// Clonk death callback
|
||||
func OnClonkDeath(clonk, killed_by)
|
||||
{
|
||||
// Player died?
|
||||
if (!clonk) return;
|
||||
var plr = clonk->GetOwner();
|
||||
if (GetPlayerType(plr) == C4PT_User)
|
||||
{
|
||||
// Relaunch count
|
||||
if (!g_relaunchs[plr])
|
||||
{
|
||||
Log("$MsgOutOfRelaunchs$", GetTaggedPlayerName(plr));
|
||||
Scoreboard->SetPlayerData(plr, "relaunchs", Icon_Cancel);
|
||||
EliminatePlayer(plr);
|
||||
return false;
|
||||
}
|
||||
// Relaunch count
|
||||
--g_relaunchs[plr];
|
||||
Scoreboard->SetPlayerData(plr, "relaunchs", g_relaunchs[plr]);
|
||||
Log("$MsgRelaunch$", GetTaggedPlayerName(plr));
|
||||
JoinPlayer(plr, clonk);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Enemy clonk death
|
||||
// Remove inventory
|
||||
var i = clonk->ContentsCount(), obj;
|
||||
while (i--) if (obj=clonk->Contents(i))
|
||||
if (!obj->~OnContainerDeath())
|
||||
obj->RemoveObject();
|
||||
// Clear enemies from list
|
||||
i = GetIndexOf(g_spawned_enemies, clonk);
|
||||
if (i>=0)
|
||||
{
|
||||
g_spawned_enemies[i] = nil;
|
||||
// Kill bounty
|
||||
if (killed_by>=0)
|
||||
{
|
||||
Scoreboard->SetPlayerData(killed_by, "score", ++g_scores[killed_by]);
|
||||
DoWealth(killed_by, clonk.Bounty);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Killer could not be determined. Just give gold to everyone.
|
||||
DoSharedWealth(clonk.Bounty);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
/* The Game */
|
||||
|
||||
func StartGame()
|
||||
{
|
||||
// Init objects to defend
|
||||
var obj;
|
||||
for (obj in [g_windgen1, g_windgen2, g_windgen3, g_windmill]) if (obj)
|
||||
{
|
||||
obj->SetCategory(C4D_Living);
|
||||
obj->SetAlive(true);
|
||||
obj.MaxEnergy = 800000;
|
||||
obj->DoEnergy(obj.MaxEnergy/1000);
|
||||
obj->AddEnergyBar();
|
||||
obj.FxNoPlayerDamageDamage = Scenario.Object_NoPlayerDamage;
|
||||
AddEffect("NoPlayerDamage", obj, 500, 0, obj);
|
||||
}
|
||||
// Launch first wave!
|
||||
g_wave = 1;
|
||||
ScheduleCall(nil, Scenario.LaunchWave, 50, 1, g_wave);
|
||||
return true;
|
||||
}
|
||||
|
||||
func Object_NoPlayerDamage(object target, fx, dmg, cause, cause_player)
|
||||
{
|
||||
// players can't damage windmills
|
||||
if (GetPlayerType(cause_player) == C4PT_User) return 0;
|
||||
return dmg;
|
||||
}
|
||||
|
||||
public func WindmillDown(object windmill)
|
||||
{
|
||||
if (g_windgen1 == windmill) g_windgen1 = nil;
|
||||
if (g_windgen2 == windmill) g_windgen2 = nil;
|
||||
if (g_windgen3 == windmill) g_windgen3 = nil;
|
||||
if (g_windmill == windmill) g_windmill = nil;
|
||||
|
||||
Sound("Objects::Plane::PlaneCrash", true);
|
||||
|
||||
// Nothing left to defend?
|
||||
if (!g_windgen1 && !g_windgen2 && !g_windgen3 && !g_windmill)
|
||||
{
|
||||
// Fail!
|
||||
var i=GetPlayerCount(C4PT_User);
|
||||
while (i--) EliminatePlayer(GetPlayerByIndex(i, C4PT_User));
|
||||
g_lost = true;
|
||||
ScheduleCall(nil, Global.GameOver, 50, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public func DoSharedWealth(int amount)
|
||||
{
|
||||
// Split gold among all players. Keep track of remainder and use it next time
|
||||
shared_wealth_remainder += amount;
|
||||
var cnt = GetPlayerCount(C4PT_User);
|
||||
if (cnt)
|
||||
{
|
||||
var wealth_add = shared_wealth_remainder / cnt;
|
||||
if (wealth_add)
|
||||
{
|
||||
shared_wealth_remainder -= wealth_add * cnt;
|
||||
DoWealthForAll(wealth_add);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public func DoWealthForAll(int amount)
|
||||
{
|
||||
// Add wealth to all players
|
||||
for (var iplr = 0; iplr < GetPlayerCount(C4PT_User); ++iplr)
|
||||
DoWealth(GetPlayerByIndex(iplr, C4PT_User), amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
/* Enemy waves */
|
||||
|
||||
func LaunchWave(int wave)
|
||||
{
|
||||
// * Schedules spawning of all enemies
|
||||
// * Schedules call to LaunchWaveDone() after last enemy has been spawned
|
||||
var wave_data = ENEMY_WAVE_DATA[g_wave];
|
||||
g_spawned_enemies = [];
|
||||
if (wave_data)
|
||||
{
|
||||
var wave_spawn_time = 0;
|
||||
CustomMessage(Format("$MsgWave$: %s", wave, wave_data.Name));
|
||||
Sound("UI::Ding");
|
||||
for (var enemy in ForceVal2Array(wave_data.Enemies)) if (enemy)
|
||||
{
|
||||
if (enemy.Delay)
|
||||
ScheduleCall(nil, Scenario.ScheduleLaunchEnemy, enemy.Delay, 1, enemy);
|
||||
else
|
||||
ScheduleLaunchEnemy(enemy);
|
||||
wave_spawn_time = Max(wave_spawn_time, enemy.Delay + enemy.Interval * enemy.Num);
|
||||
}
|
||||
for (var arrow in ForceVal2Array(wave_data.Arrows))
|
||||
{
|
||||
CreateArrowForPlayers(arrow.X, arrow.Y);
|
||||
}
|
||||
ScheduleCall(nil, Scenario.LaunchWaveDone, wave_spawn_time+5, 1, wave);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
func CreateArrowForPlayers(int x, int y)
|
||||
{
|
||||
for (var i = 0; i < GetPlayerCount(C4PT_User); i++)
|
||||
{
|
||||
var plr = GetPlayerByIndex(i, C4PT_User);
|
||||
var cursor = GetCursor(plr);
|
||||
if (!cursor) continue;
|
||||
var arrow = CreateObject(GUI_GoalArrow, cursor->GetX(), cursor->GetY(), plr);
|
||||
if (!arrow) continue;
|
||||
arrow->SetAction("Show", cursor);
|
||||
arrow->SetR(Angle(cursor->GetX(), cursor->GetY(), x, y));
|
||||
arrow->SetClrModulation(RGBa(255, 50, 0, 128));
|
||||
Schedule(arrow, "RemoveObject()", 100);
|
||||
}
|
||||
}
|
||||
|
||||
func ScheduleLaunchEnemy(proplist enemy)
|
||||
{
|
||||
// Schedules spawning of enemy definition
|
||||
// Spawn on ground or in air?
|
||||
var xmin, xmax, ymin, ymax;
|
||||
var def = enemy.Type ?? enemy.Vehicle;
|
||||
if (!def) def = Clonk;
|
||||
var width = def->GetDefWidth();
|
||||
var height = def->GetDefWidth();
|
||||
|
||||
xmin = BoundBy(enemy.PosX - 100, 0 + width/2, LandscapeWidth() - width/2);
|
||||
xmax = BoundBy(enemy.PosX + 100, 0 + width/2, LandscapeWidth() - width/2);
|
||||
ymin = BoundBy(enemy.PosY - 100, 0 + height/2, LandscapeHeight() - height/2);
|
||||
ymax = BoundBy(enemy.PosY + 100, 0 + height/2, LandscapeHeight() - height/2);
|
||||
|
||||
ScheduleCall(nil, CustomAI.LaunchEnemy, Max(enemy.Interval,1), Max(enemy.Num,1), enemy, xmin, xmax - xmin, ymin, ymax - ymin);
|
||||
return true;
|
||||
}
|
||||
|
||||
func LaunchWaveDone(int wave)
|
||||
{
|
||||
// All enemies spawned! Now start timer to check whether they are all dead
|
||||
ScheduleCall(nil, Scenario.CheckWaveCleared, 20, 9999999, wave);
|
||||
return true;
|
||||
}
|
||||
|
||||
func CheckWaveCleared(int wave)
|
||||
{
|
||||
// Check timer to determine if enemy wave has been cleared.
|
||||
// Enemies nil themselves when they're dead. So clear out nils and we're done when the list is empty
|
||||
var nil_idx;
|
||||
while ( (nil_idx=GetIndexOf(g_spawned_enemies))>=0 )
|
||||
{
|
||||
var l = GetLength(g_spawned_enemies) - 1;
|
||||
if (nil_idx<l) g_spawned_enemies[nil_idx] = g_spawned_enemies[l];
|
||||
SetLength(g_spawned_enemies, l);
|
||||
}
|
||||
if (!GetLength(g_spawned_enemies))
|
||||
{
|
||||
// All enemies dead!
|
||||
ClearScheduleCall(nil, Scenario.CheckWaveCleared);
|
||||
OnWaveCleared(wave);
|
||||
}
|
||||
}
|
||||
|
||||
func OnWaveCleared(int wave)
|
||||
{
|
||||
var bounty = ENEMY_WAVE_DATA[g_wave].Bounty, bounty_msg = "";
|
||||
if (bounty)
|
||||
{
|
||||
bounty_msg = Format("|<c ffff00>+%d</c>{{Icon_Wealth}}", bounty);
|
||||
DoWealthForAll(bounty);
|
||||
}
|
||||
CustomMessage(Format("$MsgWaveCleared$%s| ", wave, bounty_msg));
|
||||
Sound("NextWave");
|
||||
// Fade out stuff
|
||||
Airship->AllStop();
|
||||
if (g_object_fade)
|
||||
for (var obj in FindObjects(Find_Or(Find_And(Find_ID(Clonk), Find_Not(Find_OCF(OCF_Alive))), Find_ID(Catapult), Find_ID(Airship))))
|
||||
obj->AddEffect("IntFadeOut", obj, 100, 1, g_object_fade, Rule_ObjectFade);
|
||||
// Next wave!
|
||||
++g_wave;
|
||||
if (ENEMY_WAVE_DATA[g_wave])
|
||||
ScheduleCall(nil, Scenario.LaunchWave, 500, 1, g_wave);
|
||||
else
|
||||
{
|
||||
// There is no next wave? Game done D:
|
||||
ScheduleCall(nil, Scenario.OnAllWavesCleared, 50, 1);
|
||||
}
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
/* Game end */
|
||||
|
||||
func OnAllWavesCleared()
|
||||
{
|
||||
// Success!
|
||||
if (g_goal) g_goal.is_fulfilled = true;
|
||||
if (GetPlayerType(ENEMY) == C4PT_Script) EliminatePlayer(ENEMY);
|
||||
GainScenarioAchievement("Done");
|
||||
GameOver();
|
||||
return true;
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
/* Wave and enemy definitions */
|
||||
|
||||
static const CSKIN_Default = 0,
|
||||
CSKIN_Steampunk = 1,
|
||||
CSKIN_Alchemist = 2,
|
||||
CSKIN_Farmer = 3;
|
||||
|
||||
static ENEMY_WAVE_DATA;
|
||||
|
||||
static const g_respawning_weapons = [Firestone, Rock];
|
||||
|
||||
func InitWaveData()
|
||||
{
|
||||
// Define different enemy types
|
||||
var pilot = { Name="$EnemyPilot$", Inventory=Rock, Energy= 30, Bounty= 10, Color=0xff0000ff, Skin=CSKIN_Alchemist, Backpack=0, Vehicle=Airship };
|
||||
var swordman = { Name="$EnemyCrewman$", Inventory=Sword, Energy= 50, Bounty= 15, Color=0xffff0000, Skin=CSKIN_Default, Backpack=0, IsCrew=true };
|
||||
var defender = { Name="$EnemyDefender$", Inventory=[Shield, Axe],Energy= 50, Bounty= 5, Color=0xff00ff00, Skin=CSKIN_Farmer, Backpack=0, IsCrew=true };
|
||||
var bowman = { Name="$EnemyBow$", Inventory=[Bow, Arrow], Energy= 30, Bounty= 10, Color=0xff80ff80, Skin=CSKIN_Steampunk, Backpack=0, IsCrew=true };
|
||||
var ballooner = { Name="$EnemyBalloon$", Inventory=Sword, Energy= 30, Bounty= 15, Color=0xff008000, Skin=CSKIN_Default, Vehicle=Balloon };
|
||||
var rocketeer = { Name="$EnemyRocket$", Inventory=[Bow, Arrow], Energy= 15, Bounty= 10, Color=0xffffffff, Skin=CSKIN_Steampunk, Vehicle=Boomattack };
|
||||
var boomattack = { Type=Boomattack, Bounty=1 };
|
||||
var boomattackf= { Type=Boomattack, Bounty=15, Speed=300 };
|
||||
|
||||
// Define composition of waves
|
||||
ENEMY_WAVE_DATA = [nil,
|
||||
{ Name = "$WaveFirst$", Bounty = 1, Enemies =
|
||||
new boomattack { Num= 1, Interval=10, PosX = 0, PosY = 500 },
|
||||
Arrows = { X = 0, Y = 500 }
|
||||
}, { Name = "$WaveSecond$", Bounty = 30, Enemies =
|
||||
[new boomattack { Num= 3, Interval=10, PosX = 0, PosY = 500 },
|
||||
new boomattack { Num= 3, Interval=20, PosX = 2000, PosY = 500 },],
|
||||
Arrows = [{ X = 0, Y = 500 },{ X = 2000, Y = 500 }]
|
||||
}, { Name = "$WaveThird$", Bounty = 10, Enemies =
|
||||
new rocketeer { Num= 8, PosX = 0, PosY = 500 },
|
||||
Arrows = { X = 0, Y = 500 }
|
||||
}, { Name = "$WaveFourth$", Bounty = 15, Enemies =
|
||||
[new rocketeer { Num= 8, PosX = 0, PosY = 500 },
|
||||
new rocketeer { Num= 8, PosX = 2000, PosY = 500 },],
|
||||
Arrows = [{ X = 0, Y = 500 },{ X = 2000, Y = 500 }]
|
||||
}, { Name = "$WaveFifth$", Bounty = 20, Enemies =
|
||||
[new boomattack { Num= 10, PosX = 1000, PosY = 2000 },
|
||||
new pilot { Num= 1, Interval = 1, PosX = 2000, PosY = 750 },
|
||||
new defender { Num= 1, Interval = 2, PosX = 2000, PosY = 750 },
|
||||
new pilot { Num= 1, Interval = 3, PosX = 0, PosY = 750 },
|
||||
new defender { Num= 1, Interval = 4, PosX = 0, PosY = 750 },],
|
||||
Arrows = [{ X = 0, Y = 750 },{ X = 2000, Y = 750 },{ X = 1000, Y = 2000 }]
|
||||
}, { Name = "$WaveSixth$", Bounty = 20, Enemies =
|
||||
[new pilot { Num= 1, Interval = 1, PosX = 2000, PosY = 1250 },
|
||||
new defender { Num= 2, Interval = 2, PosX = 2000, PosY = 1250 },
|
||||
new bowman { Num= 2, Interval = 2, PosX = 2000, PosY = 1250 },
|
||||
new swordman { Num= 1, Interval = 2, PosX = 2000, PosY = 1250 },
|
||||
new pilot { Num= 1, Interval = 3, PosX = 0, PosY = 1250 },
|
||||
new defender { Num= 2, Interval = 4, PosX = 0, PosY = 1250 },
|
||||
new bowman { Num= 2, Interval = 4, PosX = 0, PosY = 1250 },
|
||||
new swordman { Num= 1, Interval = 4, PosX = 0, PosY = 1250 },],
|
||||
Arrows = [{ X = 0, Y = 1250 },{ X = 2000, Y = 1250 }]
|
||||
}, { Name = "$WaveSeventh$", Bounty = 50, Enemies =
|
||||
new ballooner { Num= 10, PosX = 1000, PosY = 0 },
|
||||
Arrows = { X = 1000, Y = 0 }
|
||||
}, { Name = "$WaveEighth$", Bounty = 50, Enemies =
|
||||
[new boomattack { Num= 15, Interval = 1, PosX = 500, PosY = 0 },
|
||||
new pilot { Num= 1, Interval = 80, PosX = 0, PosY = 1250 },
|
||||
new defender { Num= 3, Interval = 81, PosX = 0, PosY = 1250 },
|
||||
new bowman { Num= 3, Interval = 81, PosX = 0, PosY = 1250 },
|
||||
new pilot { Num= 1, Interval = 82, PosX = 2000, PosY = 1250 },
|
||||
new defender { Num= 3, Interval = 83, PosX = 2000, PosY = 1250 },
|
||||
new bowman { Num= 3, Interval = 83, PosX = 2000, PosY = 1250 },
|
||||
new pilot { Num= 1, Interval = 84, PosX = 200, PosY = 2000 },
|
||||
new defender { Num= 3, Interval = 85, PosX = 200, PosY = 2000 },
|
||||
new swordman { Num= 3, Interval = 85, PosX = 200, PosY = 2000 },
|
||||
new pilot { Num= 1, Interval = 86, PosX = 1800, PosY = 2000 },
|
||||
new defender { Num= 3, Interval = 87, PosX = 1800, PosY = 2000 },
|
||||
new swordman { Num= 3, Interval = 87, PosX = 1800, PosY = 2000 },],
|
||||
Arrows = [{ X = 500, Y = 0 },{ X = 0, Y = 1250 },{ X = 2000, Y = 1250 },{ X = 200, Y = 2000 },{ X = 1800, Y = 2000 }]
|
||||
}, { Name = "$WaveNinth$", Bounty = 100, Enemies =
|
||||
[new ballooner { Num= 10, Interval = 350, PosX = 1000, PosY = 0 },
|
||||
new boomattackf { Num= 8, Interval = 1, PosX = 0, PosY = 300 },
|
||||
new boomattackf { Num= 8, Interval = 1, PosX = 2000, PosY = 300 },],
|
||||
Arrows = [{ X = 1000, Y = 0 },{ X = 0, Y = 300 },{ X = 2000, Y = 300 }]
|
||||
}, { Name = "$WaveTenth$", Bounty = 1000, Enemies =
|
||||
[new boomattack { Num= 7, Interval = 1, PosX = 0, PosY = 0 },
|
||||
new boomattack { Num= 7, Interval = 1, PosX = 2000, PosY = 0 },
|
||||
new rocketeer { Num= 4, Interval = 10, PosX = 0, PosY = 300 },
|
||||
new rocketeer { Num= 4, Interval = 10, PosX = 2000, PosY = 300 },
|
||||
new ballooner { Num= 10, Interval = 100, PosX = 1000, PosY = 0 },
|
||||
new pilot { Num= 1, Interval = 80, PosX = 0, PosY = 1250 },
|
||||
new defender { Num= 3, Interval = 81, PosX = 0, PosY = 1250 },
|
||||
new bowman { Num= 4, Interval = 81, PosX = 0, PosY = 1250 },
|
||||
new pilot { Num= 1, Interval = 82, PosX = 2000, PosY = 1250 },
|
||||
new defender { Num= 3, Interval = 83, PosX = 2000, PosY = 1250 },
|
||||
new bowman { Num= 4, Interval = 83, PosX = 2000, PosY = 1250 },
|
||||
new pilot { Num= 1, Interval = 84, PosX = 200, PosY = 2000 },
|
||||
new defender { Num= 3, Interval = 85, PosX = 200, PosY = 2000 },
|
||||
new swordman { Num= 4, Interval = 85, PosX = 200, PosY = 2000 },
|
||||
new pilot { Num= 1, Interval = 86, PosX = 1800, PosY = 2000 },
|
||||
new defender { Num= 3, Interval = 87, PosX = 1800, PosY = 2000 },
|
||||
new swordman { Num= 4, Interval = 87, PosX = 1800, PosY = 2000 },
|
||||
new pilot { Num= 1, Interval = 88, PosX = 880, PosY = 2000 },
|
||||
new bowman { Num= 3, Interval = 89, PosX = 880, PosY = 2000 },
|
||||
new swordman { Num= 3, Interval = 89, PosX = 880, PosY = 2000 },
|
||||
new pilot { Num= 1, Interval = 88, PosX = 1120, PosY = 2000 },
|
||||
new bowman { Num= 3, Interval = 89, PosX = 1120, PosY = 2000 },
|
||||
new swordman { Num= 3, Interval = 89, PosX = 1120, PosY = 2000 },],
|
||||
Arrows = [{ X = 0, Y = 0 },{ X = 2000, Y = 0 },{ X = 0, Y = 300 },{ X = 2000, Y = 300 },{ X = 1000, Y = 0 },{ X = 0, Y = 1250 },{ X = 2000, Y = 1250 },{ X = 200, Y = 2000 },{ X = 1800, Y = 2000 },{ X = 880, Y = 2000 },{ X = 1120, Y = 2000 }]
|
||||
}];
|
||||
return true;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 10 MiB |
|
@ -1,2 +1,49 @@
|
|||
MsgWave=%d. Angriff
|
||||
MsgBoss=Bossangriff!!!
|
||||
MsgWave=Welle %d
|
||||
MsgWaveCleared=Welle %d geschafft!
|
||||
MsgOutOfRelaunchs=%s hat keinen Relaunch mehr.
|
||||
MsgRelaunch=Relaunch %s.
|
||||
|
||||
EnemyPilot=Pilot
|
||||
EnemyCrewman=Matrose
|
||||
EnemyDefender=Schildkämpfer
|
||||
EnemyBow=Bogenschütze
|
||||
EnemyBalloon=Fallschirmspringer
|
||||
EnemyRocket=Raketenreiter
|
||||
|
||||
WaveFirst=Nur die Vorhut
|
||||
WaveSecond=Raketengeschwader
|
||||
WaveThird=Raketenreiter
|
||||
WaveFourth=Raketenreitergeschwader
|
||||
WaveFifth=Luftschifffinte
|
||||
WaveSixth=Luftwaffe
|
||||
WaveSeventh=Fallschirmabwurf
|
||||
WaveEighth=Schwere Luftwaffe
|
||||
WaveNinth=Sonderkommando
|
||||
WaveTenth=Volle Attacke
|
||||
|
||||
TeamAttackers=Angreifer
|
||||
TeamDefenders=Verteidiger
|
||||
|
||||
HomebaseWeapons=Waffen
|
||||
HomebaseItems=Gegenstände
|
||||
HomebaseTechnology=Technologie
|
||||
HomebaseUpgrades=Verbesserungen
|
||||
HomebaseArtifacts=Artefakte
|
||||
|
||||
HomebaseDescBow=Mittlere Reichweite, geringer Schaden. Endlos Pfeile.
|
||||
HomebaseDescJavelin=Kurze Reichweite, hoher Schaden. Endlos Speere.
|
||||
HomebaseDescMusket=Hohe Reichweite, mittlerer Schaden. Endlos Munition.
|
||||
HomebaseDescGrenadeLauncher=Schleudert Bomben in die Gegner. Endlos Bomben.
|
||||
HomebaseDescWindBag=Lässt Luftstöße los, die Gegner von den Füßen fegen.
|
||||
HomebaseDescHammer=Zum Errichten von Verteidigungstuermen und anderen Gebaeuden.
|
||||
|
||||
HomebaseLoadSpeed=Nachladezeit
|
||||
HomebaseDescLoadSpeed=Verkürzt den Ladevorgang von Fernkampfwaffen (alle).
|
||||
HomebaseShootingStrength=Schusskraft
|
||||
HomebaseDescShootingStrength=Verstärkt die Schusskraft, wodurch die Waffen weiter schießen (außer Muskete).
|
||||
HomebaseLife=Lebensenergie
|
||||
HomebaseDescLife=Erhöht die Lebensenergie des Clonks.
|
||||
HomebaseAdvancedWeapons=Fortgeschrittene Waffen
|
||||
HomebaseDescAdvancedWeapons=Schaltet mehr Waffen und Upgrades frei.
|
||||
HomebaseMasterWeapons=Meisterwaffen
|
||||
HomebaseDescMasterWeapons=Schaltet mehr Waffen und Upgrades frei.
|
|
@ -1,2 +1,49 @@
|
|||
MsgWave=Attack wave %d
|
||||
MsgBoss=Boss attacks!!!
|
||||
MsgWave=Wave %d
|
||||
MsgWaveCleared=Wave %d done!
|
||||
MsgOutOfRelaunchs=%s has no more relaunchs.
|
||||
MsgRelaunch=Relaunch %s.
|
||||
|
||||
EnemyPilot=Pilot
|
||||
EnemyCrewman=Crewman
|
||||
EnemyDefender=Shield Fighter
|
||||
EnemyBow=Archer
|
||||
EnemyBalloon=Parachutist
|
||||
EnemyRocket=Rocketeer
|
||||
|
||||
WaveFirst=Just the vanguard
|
||||
WaveSecond=Rocket squadron
|
||||
WaveThird=Rocket riders
|
||||
WaveFourth=Rocket rider squadron
|
||||
WaveFifth=Airship maneuver
|
||||
WaveSixth=Air force
|
||||
WaveSeventh=Parachute drop
|
||||
WaveEighth=Heavy air force
|
||||
WaveNinth=Task force
|
||||
WaveTenth=Full attack
|
||||
|
||||
TeamAttackers=Attackers
|
||||
TeamDefenders=Defenders
|
||||
|
||||
HomebaseWeapons=Weapons
|
||||
HomebaseItems=Items
|
||||
HomebaseTechnology=Technology
|
||||
HomebaseUpgrades=Upgrades
|
||||
HomebaseArtifacts=Artifacts
|
||||
|
||||
HomebaseDescBow=Medium range, low damage. Endless arrows.
|
||||
HomebaseDescJavelin=Short range, high damage. Endless javelins.
|
||||
HomebaseDescMusket=High range, medium damage. Endloss bullets.
|
||||
HomebaseDescGrenadeLauncher=Launches bombs at the enemies. Infinite bombs.
|
||||
HomebaseDescWindBag=Releases air blasts to knock enemies of their feet.
|
||||
HomebaseDescHammer=To construct defensive towers and other buildings.
|
||||
|
||||
HomebaseLoadSpeed=Load speed
|
||||
HomebaseDescLoadSpeed=Shortens reload time of ranged weapons (all).
|
||||
HomebaseShootingStrength=Shooting strength.
|
||||
HomebaseDescShootingStrength=Strengthens weapons, makes them shoot farer (except musket).
|
||||
HomebaseLife=Life energy
|
||||
HomebaseDescLife=Makes your clonk able to take more damage.
|
||||
HomebaseAdvancedWeapons=Advanced weapons
|
||||
HomebaseDescAdvancedWeapons=Unlocks more weapons and upgrades.
|
||||
HomebaseMasterWeapons=Master weapons
|
||||
HomebaseDescMasterWeapons=Unlocks more weapons and upgrades.
|
|
@ -0,0 +1,60 @@
|
|||
#appendto Airship
|
||||
|
||||
local health = 500;
|
||||
|
||||
public func AllStop()
|
||||
{
|
||||
if (GetType(this) != C4V_Def) return;
|
||||
|
||||
for (var airship in FindObjects(Find_ID(Airship)))
|
||||
{
|
||||
airship->SetCommand();
|
||||
airship->SetXDir();
|
||||
airship->SetYDir();
|
||||
}
|
||||
}
|
||||
|
||||
public func PromoteNewCaptain()
|
||||
{
|
||||
var crew = GetCrewMembers();
|
||||
if (!GetLength(crew)) return Sink();
|
||||
var captain = RandomElement(crew);
|
||||
var fx = GetEffect("AI", captain);
|
||||
if (!fx) return Sink(); // shouldn't happen
|
||||
fx.weapon = this;
|
||||
fx.vehicle = this;
|
||||
fx.strategy = CustomAI.ExecutePilot;
|
||||
}
|
||||
|
||||
public func PrepareToBoard(object cpt)
|
||||
{
|
||||
var crew = GetCrewMembers();
|
||||
var count = 0;
|
||||
for (var member in crew)
|
||||
{
|
||||
var fx = GetEffect("AI", member);
|
||||
if (!fx) continue;
|
||||
fx.weapon = nil;
|
||||
fx.strategy = nil;
|
||||
fx.vehicle = nil;
|
||||
fx.carrier = nil;
|
||||
if (member->GetProcedure() == "PUSH") member->SetAction("Walk");
|
||||
count++;
|
||||
}
|
||||
// Suitable crows
|
||||
if (count >= 5)
|
||||
Sound("Attack");
|
||||
// Let the captain yell something
|
||||
if (!Random(3) && cpt)
|
||||
cpt->Message(Translate(Format("MsgAttack%d", Random(4))));
|
||||
}
|
||||
|
||||
private func Sink()
|
||||
{
|
||||
Incinerate();
|
||||
}
|
||||
|
||||
private func GetCrewMembers()
|
||||
{
|
||||
return FindObjects(Find_InRect(this.gondola[0], this.gondola[1], this.gondola[2], this.gondola[3]), Find_Owner(GetOwner()), Find_OCF(OCF_Alive));
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
#appendto Axe
|
||||
|
||||
// EnemyAI calls this function when trying to attack with an axe
|
||||
// Useful because circumvents tree chopping
|
||||
public func ControlUse(object clonk, int iX, int iY)
|
||||
{
|
||||
// Combat
|
||||
if(!CanStrikeWithWeapon(clonk) || !clonk->HasHandAction())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var rand = Random(2)+1;
|
||||
var arm = "R";
|
||||
var animation = Format("SwordSlash%d.%s", rand, arm);
|
||||
carry_bone = "pos_hand2";
|
||||
|
||||
var length = this.StrikingLength;
|
||||
|
||||
if(clonk->IsWalking())
|
||||
{
|
||||
if(!GetEffect("AxeStrikeStop", clonk, 0))
|
||||
AddEffect("AxeStrikeStop", clonk, 2, length, this);
|
||||
}
|
||||
if(clonk->GetHandPosByItemPos(clonk->GetItemPos(this)) == 1)
|
||||
{
|
||||
arm = "L";
|
||||
carry_bone = "pos_hand1";
|
||||
animation = Format("SwordSlash%d.%s", rand, arm);
|
||||
}
|
||||
if(clonk->IsJumping())
|
||||
{
|
||||
rand = 1;
|
||||
if(clonk->GetYDir() < -5) rand = 2;
|
||||
animation = Format("SwordJump%d.%s",rand,arm);
|
||||
}
|
||||
|
||||
PlayWeaponAnimation(clonk, animation, 10, Anim_Linear(0, 0, clonk->GetAnimationLength(animation), length, ANIM_Remove), Anim_Const(1000));
|
||||
clonk->UpdateAttach();
|
||||
|
||||
magic_number=((magic_number+1)%10) + (ObjectNumber()*10);
|
||||
StartWeaponHitCheckEffect(clonk, length, 1);
|
||||
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
#appendto Balloon
|
||||
|
||||
public func ControlUseStart(object clonk)
|
||||
{
|
||||
// Create the balloon and set its speed and rider.
|
||||
var balloon = CreateObjectAbove(BalloonDeployed, clonk->GetX(), clonk->GetY()+5);
|
||||
balloon->SetSpeed(clonk->GetXDir(), clonk->GetYDir());
|
||||
balloon->SetRider(clonk);
|
||||
// balloon->SetParent(this);
|
||||
|
||||
// Sound.
|
||||
// Sound("Objects::Balloon::Inflate");
|
||||
|
||||
// Make the clonk ride the balloon.
|
||||
clonk->SetAction("Ride", balloon);
|
||||
|
||||
// Make sure clonk is not diving.
|
||||
var side = ["L", "R"][Random(2)];
|
||||
clonk->PlayAnimation(Format("Jump.%s", side), CLONK_ANIM_SLOT_Movement, Anim_Linear(clonk->GetAnimationLength("Jump.L"), 0, clonk->GetAnimationLength("Jump.L"), 36, ANIM_Hold), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
|
||||
return true;
|
||||
}
|
||||
|
||||
public func FxControlFloatTimer(object target, proplist effect, int time)
|
||||
{
|
||||
// Balloon deflates if any vertex has contact
|
||||
if (GetContact(-1, CNAT_Bottom))
|
||||
{
|
||||
Deflate();
|
||||
return FX_Execute_Kill;
|
||||
}
|
||||
return _inherited(target, effect, time, ...);
|
||||
}
|
||||
|
||||
private func Deflate()
|
||||
{
|
||||
if (GetAction() != "Deflate")
|
||||
{
|
||||
SetAction("Deflate");
|
||||
SetComDir(COMD_None);
|
||||
if (this.rider)
|
||||
{
|
||||
var fx = GetEffect("AI", this.rider);
|
||||
if (!fx) return;
|
||||
// Tell rider to get a new target
|
||||
fx.target = nil;
|
||||
this.rider->SetCommand();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func OnProjectileHit()
|
||||
{
|
||||
if (this.rider)
|
||||
{
|
||||
var fx = GetEffect("AI", this.rider);
|
||||
if (!fx) return;
|
||||
fx.parachute_lost = true; // rider must get a new target as soon as he lands
|
||||
}
|
||||
_inherited(...);
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
#appendto BalloonDeployed
|
||||
|
||||
public func FxControlFloatTimer(object target, proplist effect, int time)
|
||||
{
|
||||
// Balloon deflates if any vertex has contact
|
||||
if (GetContact(-1, CNAT_Bottom))
|
||||
{
|
||||
Deflate();
|
||||
return FX_Execute_Kill;
|
||||
}
|
||||
return _inherited(target, effect, time, ...);
|
||||
}
|
||||
|
||||
private func Deflate()
|
||||
{
|
||||
if (GetAction() != "Deflate")
|
||||
{
|
||||
SetAction("Deflate");
|
||||
SetComDir(COMD_None);
|
||||
if (this.rider)
|
||||
{
|
||||
var fx = GetEffect("AI", this.rider);
|
||||
if (!fx) return;
|
||||
// Tell rider to get a new target
|
||||
fx.target = nil;
|
||||
this.rider->SetCommand();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func OnProjectileHit()
|
||||
{
|
||||
if (this.rider)
|
||||
{
|
||||
var fx = GetEffect("AI", this.rider);
|
||||
if (!fx) return;
|
||||
fx.parachute_lost = true; // rider must get a new target as soon as he lands
|
||||
}
|
||||
_inherited(...);
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
#appendto Bow
|
||||
|
||||
local shooting_strength = 100;
|
||||
|
||||
public func FinishedAiming(object clonk, int angle)
|
||||
{
|
||||
clonk->DetachMesh(iArrowMesh);
|
||||
iArrowMesh = nil;
|
||||
|
||||
// shoot
|
||||
if(Contents(0))
|
||||
{
|
||||
if(Contents(0)->~IsArrow())
|
||||
{
|
||||
var arrow = Contents(0)->TakeObject();
|
||||
arrow->Launch(angle,shooting_strength,clonk);
|
||||
Sound("Objects::Weapons::Bow::Shoot?");
|
||||
}
|
||||
}
|
||||
|
||||
// Open the hand to let the string go and play the fire animation
|
||||
PlayAnimation("Fire", 6, Anim_Linear(0, 0, GetAnimationLength("Fire"), animation_set["ShootTime"], ANIM_Hold), Anim_Const(1000));
|
||||
clonk->PlayAnimation("Close1Hand", 11, Anim_Const(0), Anim_Const(1000));
|
||||
clonk->StartShoot(this);
|
||||
return true;
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
#appendto Arrow
|
||||
#appendto LeadShot
|
||||
|
||||
public func SetStackCount(int amount)
|
||||
{
|
||||
count = MaxStackCount();
|
||||
UpdateStackDisplay();
|
||||
}
|
||||
|
||||
public func Hit()
|
||||
{
|
||||
if(GetEffect("HitCheck",this))
|
||||
RemoveObject();
|
||||
}
|
||||
|
||||
public func HitObject(object obj)
|
||||
{
|
||||
if(obj->GetOCF() & OCF_CrewMember) return;
|
||||
inherited(obj,...);
|
||||
if (this) RemoveObject();
|
||||
}
|
||||
|
||||
func UpdatePicture()
|
||||
{
|
||||
SetGraphics("1");
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
#appendto Lorry
|
||||
#appendto Chest
|
||||
|
||||
func RejectCollect() { return true; }
|
|
@ -0,0 +1,3 @@
|
|||
// helper: put single elements into array
|
||||
|
||||
global func ForceVal2Array(v) { if (GetType(v) != C4V_Array) return [v]; else return v; }
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
Returns a suitable position to fly to in order to perform a boarding attack on one of the islands.
|
||||
Will make enemies fly around the islands first if they spawn below them.
|
||||
|
||||
Frequently used by Airship captains.
|
||||
*/
|
||||
|
||||
static IslandRectangle;
|
||||
|
||||
global func GetBoardingPoint(object target)
|
||||
{
|
||||
if (!this) return nil;
|
||||
if (!target) return nil;
|
||||
|
||||
var x,y;
|
||||
|
||||
// A rectangle surrounding the islands + some space (30px or so)
|
||||
if (!IslandRectangle) IslandRectangle = Shape->Rectangle(780, 780, 430, 310);
|
||||
|
||||
// Below the target
|
||||
if (this->GetY() > target->GetY())
|
||||
{
|
||||
// Below the islands
|
||||
if (Inside(this->GetX(), 780, 1110))
|
||||
{
|
||||
if (GetX() < LandscapeWidth())
|
||||
{
|
||||
y = this->GetY() / 2;
|
||||
x = RandomX(100, 200);
|
||||
} else {
|
||||
y = this->GetY() / 2;
|
||||
x = RandomX(LandscapeWidth() - 200, LandscapeWidth() - 100);
|
||||
}
|
||||
} else { // way up is clear
|
||||
x = GetX();
|
||||
y = 780 - Random(200);
|
||||
}
|
||||
} else
|
||||
{
|
||||
// Draw a straight line towards the target and pick a point 30 pixels away
|
||||
var dist = ObjectDistance(target);
|
||||
var xdist = target->GetX() - GetX();
|
||||
var ydist = target->GetY() - GetY();
|
||||
x = target->GetX() - (xdist * 30) / dist;
|
||||
y = target->GetY() - (ydist * 30) / dist;
|
||||
}
|
||||
|
||||
// Near enough to try an attack
|
||||
if (IslandRectangle->IsPointContained(this->GetX(), this->GetY()))
|
||||
{
|
||||
this->~PrepareToBoard();
|
||||
x = this->GetX();
|
||||
y = this->GetY();
|
||||
}
|
||||
|
||||
return [x,y];
|
||||
}
|
||||
|
||||
global func InsideIslandRectangle(object ship)
|
||||
{
|
||||
if (!IslandRectangle) IslandRectangle = Shape->Rectangle(780, 780, 430, 310);
|
||||
|
||||
return IslandRectangle->IsPointContained(ship->GetX(), ship->GetY());
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/* Variant of FxHitCheckDoCheck that does exclude the windmills if anything was shot by a player
|
||||
*/
|
||||
|
||||
global func FxHitCheckDoCheck(object target, proplist effect)
|
||||
{
|
||||
var obj;
|
||||
// rather search in front of the projectile, since a hit might delete the effect,
|
||||
// and clonks can effectively hide in front of walls.
|
||||
var oldx = target->GetX();
|
||||
var oldy = target->GetY();
|
||||
var newx = target->GetX() + target->GetXDir() / 10;
|
||||
var newy = target->GetY() + target->GetYDir() / 10;
|
||||
var dist = Distance(oldx, oldy, newx, newy);
|
||||
var is_human = GetPlayerType(target->GetController()) == C4PT_User;
|
||||
|
||||
var shooter = effect.shooter;
|
||||
var live = effect.live;
|
||||
|
||||
if (live)
|
||||
shooter = target;
|
||||
|
||||
if (dist <= Max(1, Max(Abs(target->GetXDir()), Abs(target->GetYDir()))) * 2)
|
||||
{
|
||||
// We search for objects along the line on which we moved since the last check
|
||||
// and sort by distance (closer first).
|
||||
for (obj in FindObjects(Find_OnLine(oldx, oldy, newx, newy),
|
||||
Find_NoContainer(),
|
||||
Find_Layer(target->GetObjectLayer()),
|
||||
Find_PathFree(target),
|
||||
Sort_Distance(oldx, oldy)))
|
||||
{
|
||||
// Excludes
|
||||
if (!obj) continue; // hit callback of one object might have removed other objects
|
||||
if(obj == target) continue;
|
||||
if(obj == shooter) continue;
|
||||
if (is_human) {
|
||||
if (obj == g_windgen1) continue;
|
||||
if (obj == g_windgen2) continue;
|
||||
if (obj == g_windgen3) continue;
|
||||
if (obj == g_windmill) continue;
|
||||
}
|
||||
|
||||
// Unlike in hazard, there is no NOFF rule (yet)
|
||||
// CheckEnemy
|
||||
//if(!CheckEnemy(obj,target)) continue;
|
||||
|
||||
// IsProjectileTarget or Alive will be hit
|
||||
if (obj->~IsProjectileTarget(target, shooter) || obj->GetOCF() & OCF_Alive)
|
||||
{
|
||||
target->~HitObject(obj);
|
||||
if (!target)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not reapply C4D_Object to arrows
|
||||
|
||||
global func FxHitCheckStop(object target, proplist effect, int reason, bool temp)
|
||||
{
|
||||
if (temp)
|
||||
return;
|
||||
|
||||
return;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
global func Particles_Planks()
|
||||
{
|
||||
return
|
||||
{
|
||||
Alpha = PV_KeyFrames(0, 0, 255, 900, 255, 1000, 0),
|
||||
Size = PV_Random(20, 30),
|
||||
Phase = PV_Random(0, 2),
|
||||
Rotation = PV_Speed(),
|
||||
ForceX = PV_Wind(50),
|
||||
ForceY = PV_Gravity(100),
|
||||
CollisionVertex = 500,
|
||||
OnCollision = PC_Die(),
|
||||
};
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
#appendto Ropeladder_Grabber
|
||||
|
||||
public func IsInteractable(object clonk)
|
||||
{
|
||||
return false;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
#appendto Rule_ObjectFade
|
||||
|
||||
func Timer()
|
||||
{
|
||||
for (var fade in FindObjects(Find_Or(Find_Category(C4D_Object), Find_ID(Arrow), Find_ID(Javelin)), Find_NoContainer(), Find_Not(Find_OCF(OCF_HitSpeed1))))
|
||||
{
|
||||
if (!CheckFadeConditions(fade)) continue;
|
||||
if (GetEffect("IntFadeOut*", fade)) continue;
|
||||
AddEffect("IntFadeOutCandidate", fade, 1, 36, this, Rule_ObjectFade);
|
||||
}
|
||||
}
|
||||
|
||||
func CheckFadeConditions(object fade)
|
||||
{
|
||||
if (fade->GetID() == Airship) return true;
|
||||
return _inherited(fade);
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
#appendto WindGenerator
|
||||
|
||||
func Damage()
|
||||
{
|
||||
Explode(30);
|
||||
}
|
||||
|
||||
// No lightbulbs
|
||||
func IsPowerProducer() { return false; }
|
||||
|
||||
private func RegisterPowerProduction(int amount) {}
|
||||
|
||||
//No triangles
|
||||
func RedrawFlagRadius() { return; }
|
|
@ -0,0 +1,6 @@
|
|||
MsgAttack0=ANGRIFF!|
|
||||
MsgAttack1=Zum Angriff!|
|
||||
MsgAttack2=Attacke!|
|
||||
MsgAttack3=Vorwärts!|
|
||||
|
||||
MsgGotWindbag=Windbeutel jetzt verfügbar.|
|
|
@ -0,0 +1,6 @@
|
|||
MsgAttack0=ATTACK!|
|
||||
MsgAttack1=Charge!|
|
||||
MsgAttack2=Get them!|
|
||||
MsgAttack3=For glory!|
|
||||
|
||||
MsgGotWindbag=Windbag now available.|
|
|
@ -0,0 +1,40 @@
|
|||
/* Returns a random still alive windmill */
|
||||
|
||||
global func GetRandomWindmill()
|
||||
{
|
||||
if (g_lost) return nil;
|
||||
|
||||
var mills = [];
|
||||
if (g_windgen1) PushBack(mills, g_windgen1);
|
||||
if (g_windgen2) PushBack(mills, g_windgen2);
|
||||
if (g_windgen3) PushBack(mills, g_windgen3);
|
||||
if (g_windmill) PushBack(mills, g_windmill);
|
||||
|
||||
return RandomElement(mills);
|
||||
}
|
||||
|
||||
/* Returns the nearest still alive windmill */
|
||||
|
||||
global func GetNearestWindmill()
|
||||
{
|
||||
if (g_lost) return nil;
|
||||
if (!this) return nil;
|
||||
|
||||
var mills = [];
|
||||
if (g_windgen1) PushBack(mills, g_windgen1);
|
||||
if (g_windgen2) PushBack(mills, g_windgen2);
|
||||
if (g_windgen3) PushBack(mills, g_windgen3);
|
||||
if (g_windmill) PushBack(mills, g_windmill);
|
||||
|
||||
var nearest = 0, dist = LandscapeWidth();
|
||||
for (var i = 0; i < GetLength(mills); i++)
|
||||
{
|
||||
if (ObjectDistance(mills[i]) < dist)
|
||||
{
|
||||
nearest = i;
|
||||
dist = ObjectDistance(mills[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return mills[nearest];
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
#appendto WindGenerator
|
||||
#appendto Windmill
|
||||
|
||||
// Brutal
|
||||
func Death()
|
||||
{
|
||||
GameCall("WindmillDown", this);
|
||||
|
||||
if (GetID() == Windmill)
|
||||
{
|
||||
var ruin = CreateObjectAbove(Ruin_Windmill, 0, GetDefBottom()-GetY(), NO_OWNER);
|
||||
ruin->MakeInvincible();
|
||||
RemoveObject();
|
||||
} else {
|
||||
CreateParticle("Planks", PV_Random(-10,10), PV_Random(-30,30), PV_Random(-30,30), PV_Random(-100,-50), 500, Particles_Planks(), 15);
|
||||
RemoveObject();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Overload structure library
|
||||
func Damage()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// No lightbulbs
|
||||
func IsPowerProducer() { return false; }
|
||||
|
||||
private func RegisterPowerProduction(int amount) {}
|
||||
private func UnregisterPowerProduction(int amount) {}
|
||||
|
||||
// No triangles
|
||||
func RedrawFlagRadius() { return; }
|
||||
|
||||
// Is objective
|
||||
public func IsMainObjective() { return true; }
|
||||
|
||||
|
||||
local ContactIncinerate = 0;
|
||||
local BlastIncinerate = 0;
|
|
@ -0,0 +1,22 @@
|
|||
#appendto WindBag
|
||||
|
||||
func Entrance(object container)
|
||||
{
|
||||
if (this == g_windbag && container && container->IsClonk())
|
||||
{
|
||||
var plr = container->GetOwner();
|
||||
if (GetPlayerType(plr) != C4PT_User) return _inherited(container, ...);
|
||||
|
||||
var homebase = FindObject(Find_ID(Homebase), Find_Owner(plr));
|
||||
if (!homebase) return _inherited(container, ...); // ???
|
||||
|
||||
homebase->SetItemAvailable(homebase->GetEntryByID(GetID()));
|
||||
g_windbag = nil;
|
||||
Sound("UI::Cleared", false, nil, plr);
|
||||
CustomMessage("$MsgGotWindbag$", container, plr);
|
||||
var menu = FindObject(Find_ID(GUI_ObjectInteractionMenu), Find_Owner(plr));
|
||||
if (menu) Schedule(menu, "RemoveObject()", 1);
|
||||
return RemoveObject();
|
||||
}
|
||||
return _inherited(container, ...);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
[Teams]
|
||||
TeamColors=false
|
||||
|
||||
[Team]
|
||||
id=1
|
||||
Name=$TeamDefenders$
|
||||
|
||||
[Team]
|
||||
id=2
|
||||
Name=$TeamAttackers$
|
||||
MaxPlayer=1
|
|
@ -0,0 +1,8 @@
|
|||
Sky.jpg by http://www.splitshire.com/
|
||||
CC0
|
||||
|
||||
NextWave.ogg by daveincamas (http://www.freesound.org/people/daveincamas/sounds/27082/)
|
||||
CC-BY 3.0
|
||||
|
||||
Attack.ogg by Erdie (http://www.freesound.org/people/Erdie/sounds/165614/)
|
||||
CC-BY 3.0
|
Loading…
Reference in New Issue