+ vecs = shotorg_adjustfromclient(vecs, y_is_right, algn);
+ }
+#ifdef SVQC
+ else if (autocvar_g_shootfromeye)
+ {
+ vecs.y = vecs.z = 0;
+ }
+ else if (autocvar_g_shootfromcenter)
+ {
+ vecs.y = 0;
+ vecs.z -= 2;
+ }
+ else if ((s = autocvar_g_shootfromfixedorigin) != "")
+ {
+ vector v = stov(s);
+ if (y_is_right) v.y = -v.y;
+ if (v.x != 0) vecs.x = v.x;
+ vecs.y = v.y;
+ vecs.z = v.z;
+ }
+#endif
+ else // just do the same as top
+ {
+ vecs = shotorg_adjustfromclient(vecs, y_is_right, algn);
+ }
+
+ return vecs;
+}
+
+#define shotorg_adjust shotorg_adjust_values
+
+/**
+ * supported formats:
+ *
+ * 1. simple animated model, muzzle flash handling on h_ model:
+ * h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation
+ * tags:
+ * shot = muzzle end (shot origin, also used for muzzle flashes)
+ * shell = casings ejection point (must be on the right hand side of the gun)
+ * weapon = attachment for v_tuba.md3
+ * v_tuba.md3 - first and third person model
+ * g_tuba.md3 - pickup model
+ *
+ * 2. simple animated model, muzzle flash handling on v_ model:
+ * h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation
+ * tags:
+ * weapon = attachment for v_tuba.md3
+ * v_tuba.md3 - first and third person model
+ * tags:
+ * shot = muzzle end (shot origin, also used for muzzle flashes)
+ * shell = casings ejection point (must be on the right hand side of the gun)
+ * g_tuba.md3 - pickup model
+ *
+ * 3. fully animated model, muzzle flash handling on h_ model:
+ * h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model
+ * tags:
+ * shot = muzzle end (shot origin, also used for muzzle flashes)
+ * shell = casings ejection point (must be on the right hand side of the gun)
+ * handle = corresponding to the origin of v_tuba.md3 (used for muzzle flashes)
+ * v_tuba.md3 - third person model
+ * g_tuba.md3 - pickup model
+ *
+ * 4. fully animated model, muzzle flash handling on v_ model:
+ * h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model
+ * tags:
+ * shot = muzzle end (shot origin)
+ * shell = casings ejection point (must be on the right hand side of the gun)
+ * v_tuba.md3 - third person model
+ * tags:
+ * shot = muzzle end (for muzzle flashes)
+ * g_tuba.md3 - pickup model
+ *
+ * writes:
+ * this.origin, this.angles
+ * this.weaponchild
+ * this.movedir, this.view_ofs
+ * attachment stuff
+ * anim stuff
+ * to free:
+ * call again with ""
+ * remove the ent
+ */
+void CL_WeaponEntity_SetModel(entity this, string name, bool _anim)
+{
+ if (name == "")
+ {
+ this.model = "";
+ if (this.weaponchild) delete(this.weaponchild);
+ this.weaponchild = NULL;
+ this.movedir = '0 0 0';
+ this.spawnorigin = '0 0 0';
+ this.oldorigin = '0 0 0';
+ this.anim_fire1 = '0 1 0.01';
+ this.anim_fire2 = '0 1 0.01';
+ this.anim_idle = '0 1 0.01';
+ this.anim_reload = '0 1 0.01';
+ }
+ else
+ {
+ // if there is a child entity, hide it until we're sure we use it
+ if (this.weaponchild) this.weaponchild.model = "";
+ _setmodel(this, W_Model(strcat("v_", name, ".md3")));
+ int v_shot_idx; // used later
+ (v_shot_idx = gettagindex(this, "shot")) || (v_shot_idx = gettagindex(this, "tag_shot"));
+
+ _setmodel(this, W_Model(strcat("h_", name, ".iqm")));
+ // preset some defaults that work great for renamed zym files (which don't need an animinfo)
+ this.anim_fire1 = animfixfps(this, '0 1 0.01', '0 0 0');
+ this.anim_fire2 = animfixfps(this, '1 1 0.01', '0 0 0');
+ this.anim_idle = animfixfps(this, '2 1 0.01', '0 0 0');
+ this.anim_reload = animfixfps(this, '3 1 0.01', '0 0 0');
+
+ // if we have a "weapon" tag, let's attach the v_ model to it ("invisible hand" style model)
+ // if we don't, this is a "real" animated model
+ string t;
+ if ((t = "weapon", gettagindex(this, t)) || (t = "tag_weapon", gettagindex(this, t)))
+ {
+ if (!this.weaponchild)
+ {
+ this.weaponchild = new(weaponchild);
+#ifdef CSQC
+ this.weaponchild.drawmask = MASK_NORMAL;
+ this.weaponchild.renderflags |= RF_VIEWMODEL;
+#endif
+ }
+ _setmodel(this.weaponchild, W_Model(strcat("v_", name, ".md3")));
+ setsize(this.weaponchild, '0 0 0', '0 0 0');
+ setattachment(this.weaponchild, this, t);
+ }
+ else
+ {
+ if (this.weaponchild) delete(this.weaponchild);
+ this.weaponchild = NULL;
+ }
+
+ setsize(this, '0 0 0', '0 0 0');
+ setorigin(this, '0 0 0');
+ this.angles = '0 0 0';
+ this.frame = 0;
+#ifdef SVQC
+ this.viewmodelforclient = NULL;
+#else
+ this.renderflags &= ~RF_VIEWMODEL;
+#endif
+ if (v_shot_idx) // v_ model attached to invisible h_ model
+ {
+ this.movedir = gettaginfo(this.weaponchild, v_shot_idx);
+ }
+ else
+ {
+ int idx;
+ if ((idx = gettagindex(this, "shot")) || (idx = gettagindex(this, "tag_shot")))
+ {
+ this.movedir = gettaginfo(this, idx);
+ }
+ else
+ {
+ LOG_WARNF("weapon model %s does not support the 'shot' tag, will display shots TOTALLY wrong",
+ this.model);
+ this.movedir = '0 0 0';
+ }
+ }
+ {
+ int idx = 0;
+ // v_ model attached to invisible h_ model
+ if (this.weaponchild
+ && ((idx = gettagindex(this.weaponchild, "shell")) || (idx = gettagindex(this.weaponchild, "tag_shell"))))
+ {
+ this.spawnorigin = gettaginfo(this.weaponchild, idx);
+ }
+ else if ((idx = gettagindex(this, "shell")) || (idx = gettagindex(this, "tag_shell")))
+ {
+ this.spawnorigin = gettaginfo(this, idx);
+ }
+ else
+ {
+ LOG_WARNF("weapon model %s does not support the 'shell' tag, will display casings wrong",
+ this.model);
+ this.spawnorigin = this.movedir;
+ }
+ }
+ if (v_shot_idx)
+ {
+ this.oldorigin = '0 0 0'; // use regular attachment
+ }
+ else
+ {
+ int idx;
+ if (this.weaponchild)
+ (idx = gettagindex(this, "weapon")) || (idx = gettagindex(this, "tag_weapon"));
+ else
+ (idx = gettagindex(this, "handle")) || (idx = gettagindex(this, "tag_handle"));
+ if (idx)
+ {
+ this.oldorigin = this.movedir - gettaginfo(this, idx);
+ }
+ else
+ {
+ LOG_WARNF(
+ "weapon model %s does not support the 'handle' tag "
+ "and neither does the v_ model support the 'shot' tag, "
+ "will display muzzle flashes TOTALLY wrong\n",
+ this.model);
+ this.oldorigin = '0 0 0'; // there is no way to recover from this
+ }
+ }
+
+#ifdef SVQC
+ this.viewmodelforclient = this.owner;
+#else
+ this.renderflags |= RF_VIEWMODEL;
+#endif
+ }
+
+ this.view_ofs = '0 0 0';
+
+ if (this.movedir.x >= 0)
+ {
+ int algn = STAT(GUNALIGN, this.owner);
+ vector v = this.movedir;
+ this.movedir = shotorg_adjust(v, false, false, algn);
+ this.view_ofs = shotorg_adjust(v, false, true, algn) - v;
+ }
+ int compressed_shotorg = compressShotOrigin(this.movedir);
+ // make them match perfectly
+#ifdef SVQC
+ this.movedir = decompressShotOrigin(this.owner.stat_shotorg = compressed_shotorg);
+#else
+ this.movedir = decompressShotOrigin(compressed_shotorg);
+#endif
+
+ this.spawnorigin += this.view_ofs; // offset the casings origin by the same amount
+
+ // check if an instant weapon switch occurred
+ setorigin(this, this.view_ofs);
+ if (!_anim) return;
+ // reset animstate now
+ this.wframe = WFRAME_IDLE;
+ setanim(this, this.anim_idle, true, false, true);
+}
+#endif
+
+#ifndef MENUQC
+
+REGISTER_NET_TEMP(wframe)
+#ifdef CSQC
+NET_HANDLE(wframe, bool isNew)
+{
+ vector a;
+ a.x = ReadCoord();
+ a.y = ReadCoord();
+ a.z = ReadCoord();
+ bool restartanim = ReadByte();
+ anim_set(viewmodel, a, !restartanim, restartanim, restartanim);
+ viewmodel.state = ReadByte();
+ viewmodel.weapon_nextthink = ReadFloat();
+ viewmodel.alpha = ReadByte() / 255;
+ switch (viewmodel.state)
+ {
+ case WS_RAISE:
+ viewmodel.weapon_switchdelay = activeweapon.switchdelay_raise;
+ break;
+ case WS_DROP:
+ viewmodel.weapon_switchdelay = activeweapon.switchdelay_drop;
+ break;
+ default:
+ viewmodel.weapon_switchdelay = 0;
+ break;
+ }
+ return true;
+}
+#endif
+
+#ifdef SVQC
+void wframe_send(entity actor, entity weaponentity, vector a, bool restartanim)
+{
+ if (!IS_REAL_CLIENT(actor)) return;
+ int channel = MSG_ONE;
+ msg_entity = actor;
+ WriteHeader(channel, wframe);
+ WriteCoord(channel, a.x);
+ WriteCoord(channel, a.y);
+ WriteCoord(channel, a.z);
+ WriteByte(channel, restartanim);
+ WriteByte(channel, weaponentity.state);
+ WriteFloat(channel, weaponentity.weapon_nextthink);
+ WriteByte(channel, weaponentity.m_alpha * 255);
+}
+#endif
+
+REGISTER_NET_C2S(w_whereis)
+#ifdef SVQC
+void Weapon_whereis(Weapon this, entity cl);
+NET_HANDLE(w_whereis, bool)
+{
+ Weapon wpn = ReadRegistered(Weapons);
+ if (wpn != WEP_Null) Weapon_whereis(wpn, sender);
+ return true;
+}
+#else
+void w_whereis(Weapon this)
+{
+ int channel = MSG_C2S;
+ WriteHeader(channel, w_whereis);
+ WriteRegistered(Weapons, channel, this);
+}
+CLIENT_COMMAND(weapon_find, "Show spawn locations of a weapon")
+{
+ switch (request)
+ {
+ case CMD_REQUEST_COMMAND:
+ {
+ string s = argv(1);
+ if (s == "all")
+ {
+ FOREACH(Weapons, it != WEP_Null, w_whereis(it));
+ return;
+ }
+ if (s == "unowned")
+ {
+ FOREACH(Weapons, it != WEP_Null && !(STAT(WEAPONS) & it.m_wepset), w_whereis(it));
+ return;
+ }
+ FOREACH(Weapons, it != WEP_Null && it.netname == s,
+ {
+ w_whereis(it);
+ return;
+ });
+ }
+ default:
+ {
+ LOG_INFOF("Incorrect parameters for ^2%s^7\n", "weapon_find");
+ }
+ case CMD_REQUEST_USAGE:
+ {
+ LOG_INFO("\nUsage:^3 cl_cmd weapon_find weapon\n");
+ LOG_INFO(" Where 'weapon' is the lowercase weapon name, 'all' or 'unowned'.\n");
+ return;
+ }