/* Crosshair Author: Newton Virtual cursor for gamepad controls */ local crew, angle, xpos, ypos, aiming, menu; static const CURSOR_Radius = 100; // This is supposed to be a constant, but C4Script doesn't allow constant expressions there. private func CURSOR_Deadzone() { return PLRCON_MaxStrength / 5; } protected func Initialize() { SetVisibility(false); xpos = ypos = 0; aiming = false; } public func FxMoveTimer() { if (!crew) { RemoveObject(); return FX_Execute_Kill; } var target_angle = Angle(0,0,xpos,ypos)*10; if (!Visible() && !InDeadzone()) { // The player moved the aiming stick while the crosshair wasn't visible: Use angle directly. angle = target_angle; SetVisibility(true); } else if (!InDeadzone()) { // Smooth small movements of the stick while the crosshair is visible. var angle_diff = Normalize(target_angle - angle, -1800, 10); if (Abs(angle_diff) < 450) angle = angle + angle_diff / 8; else angle = target_angle; } else if (!aiming) { // The player doesn't touch the stick and no item is using the crosshair right now. SetVisibility(false); // Aim somewhere useful. Note that this can be overwritten by objects and isn't used for throwing. angle = 800*(crew->GetDir()*2-1); } UpdatePosition(); crew->TriggerHoldingControl(); } private func AnalogStrength() { return BoundBy(Sqrt(xpos*xpos+ypos*ypos), 0, PLRCON_MaxStrength); } private func InDeadzone() { return AnalogStrength() < CURSOR_Deadzone(); } private func Visible() { return this.Visibility != VIS_None; } // Updates the visibility, returing true if it was changed. private func SetVisibility(bool visible) { var newvis, oldvis; if (visible) newvis = VIS_Owner; else newvis = VIS_None; oldvis = this.Visibility; this.Visibility = newvis; return newvis != oldvis; } private func CreateMoveEffect(object clonk) { crew = clonk; UpdatePosition(); RemoveEffect("Move",this); AddEffect("Move",this,1,1,this); } public func StartAim(object clonk, int default_angle, object GUImenu) { aiming = true; // gui or landscape mode: if (GUImenu) { SetCategory(C4D_StaticBack | C4D_IgnoreFoW | C4D_Foreground | C4D_Parallax); menu = GUImenu; this["Parallaxity"] = [0,0]; } else { SetCategory(C4D_StaticBack | C4D_IgnoreFoW); menu = nil; } // Use the given angle if the player wasn't aiming before. if (SetVisibility(true) && default_angle) angle = default_angle; CreateMoveEffect(clonk); } private func UpdatePosition() { var x = +Sin(angle,CURSOR_Radius,10); var y = -Cos(angle,CURSOR_Radius,10); if (menu) SetPosition(menu->GetX()+x,menu->GetY()+y); else SetPosition(crew->GetX()+x,crew->GetY()+y); crew->UpdateVirtualCursorPos(); } private func MirrorCursor() { return; angle = -Normalize(angle,-1800,10); } public func StopAim() { aiming = false; } // Aiming means that some object is currently actively using the crosshair. public func IsAiming() { return aiming; } // The crosshair is also active when the player is holding the aiming stick. public func IsActive() { return aiming || Visible(); } public func Aim(int ctrl, object clonk, int strength, int repeat, int status) { // start (stealth) aiming if(!GetEffect("Move",this)) CreateMoveEffect(clonk); // aiming with analog pad if (status == CONS_Moved && (ctrl == CON_AimAxisUp || ctrl == CON_AimAxisDown || ctrl == CON_AimAxisLeft || ctrl == CON_AimAxisRight)) { if(ctrl == CON_AimAxisUp) ypos = -strength; if(ctrl == CON_AimAxisDown) ypos = strength; if(ctrl == CON_AimAxisLeft) xpos = -strength; if(ctrl == CON_AimAxisRight) xpos = strength; return true; } return false; } public func Direction(int ctrl) { if(!crew) return; angle = Normalize(angle,-1800,10); //Message("%d, %d",this,angle,ctrl); if(ctrl == CON_Left) if(angle > 0) MirrorCursor(); if(ctrl == CON_Right) if(angle < 0) MirrorCursor(); return; }