Guardians of the Windmills: First draft. Unbalanced, untested, unplayable! (please test)

shapetextures
Clonkonaut 2015-12-28 14:43:21 +01:00
parent d737dc1b05
commit b7b6fcfa3f
45 changed files with 1892 additions and 371 deletions

View File

@ -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.

View File

@ -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
},
};
*/

View File

@ -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

View File

@ -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$";

View File

@ -0,0 +1,8 @@
[DefCore]
id=CustomAI
Version=7
Category=C4D_Vehicle | C4D_MouseIgnore
Width=1
Height=1
Mass=1

View File

@ -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.

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.

View File

@ -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

View File

@ -0,0 +1,3 @@
[Particle]
Name=Planks
Face=0,0,12,104,-6,-52

View File

@ -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

View File

@ -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$";

View File

@ -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!

View File

@ -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!

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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));
}

View File

@ -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;
}

View File

@ -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(...);
}

View File

@ -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(...);
}

View File

@ -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;
}

View File

@ -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");
}

View File

@ -0,0 +1,4 @@
#appendto Lorry
#appendto Chest
func RejectCollect() { return true; }

View File

@ -0,0 +1,3 @@
// helper: put single elements into array
global func ForceVal2Array(v) { if (GetType(v) != C4V_Array) return [v]; else return v; }

View File

@ -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());
}

View File

@ -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;
}

View File

@ -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(),
};
}

View File

@ -0,0 +1,6 @@
#appendto Ropeladder_Grabber
public func IsInteractable(object clonk)
{
return false;
}

View File

@ -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);
}

View File

@ -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; }

View File

@ -0,0 +1,6 @@
MsgAttack0=ANGRIFF!|
MsgAttack1=Zum Angriff!|
MsgAttack2=Attacke!|
MsgAttack3=Vorwärts!|
MsgGotWindbag=Windbeutel jetzt verfügbar.|

View File

@ -0,0 +1,6 @@
MsgAttack0=ATTACK!|
MsgAttack1=Charge!|
MsgAttack2=Get them!|
MsgAttack3=For glory!|
MsgGotWindbag=Windbag now available.|

View File

@ -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];
}

View File

@ -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;

View File

@ -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, ...);
}

View File

@ -0,0 +1,11 @@
[Teams]
TeamColors=false
[Team]
id=1
Name=$TeamDefenders$
[Team]
id=2
Name=$TeamAttackers$
MaxPlayer=1

View File

@ -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