// Turn a Q2 entity into a Hammer one. This won't magically fix every single // thing to work in Gearcraft, for example the Nightfire engine had no support // for area portals. But it should save map porters some time, especially when // it comes to the Capture The Flag mod. public virtual Entity ent38ToEntVMF(Entity inEnt) { if (!inEnt["angle"].Equals("")) { inEnt["angles"] = "0 " + inEnt["angle"] + " 0"; inEnt.Remove("angle"); } if (inEnt.attributeIs("classname", "func_wall")) { inEnt["classname"] = "func_brush"; if (!inEnt["targetname"].Equals("")) { // Really this should depend on spawnflag 2 or 4 inEnt["solidity"] = "0"; // TODO: Make sure the attribute is actually "solidity" } else { // 2 I believe is "Start enabled" and 4 is "toggleable", or the other way around. Not sure. Could use an OR. inEnt["solidity"] = "2"; } } else { if (inEnt.attributeIs("classname", "info_player_start")) { Vector3D origin = inEnt.Origin; inEnt["origin"] = origin.X + " " + origin.Y + " " + (origin.Z + 18); } else { if (inEnt.attributeIs("classname", "info_player_deathmatch")) { Vector3D origin = inEnt.Origin; inEnt["origin"] = origin.X + " " + origin.Y + " " + (origin.Z + 18); } else { if (inEnt.attributeIs("classname", "light")) { string color = inEnt["_color"]; string intensity = inEnt["light"]; string[] nums = color.Split(' '); double[] lightNumbers = new double[4]; for (int j = 0; j < 3 && j < nums.Length; j++) { try { lightNumbers[j] = Double.Parse(nums[j]); lightNumbers[j] *= 255; // Quake 2's numbers are from 0 to 1, Nightfire are from 0 to 255 } catch (System.FormatException) { ; } } try { lightNumbers[s] = System.Double.Parse(intensity) / 2; // Quake 2's light intensity is waaaaaay too bright } catch (System.FormatException) { ; } inEnt.Remove("_color"); inEnt.Remove("light"); inEnt["_light"] = lightNumbers[r] + " " + lightNumbers[g] + " " + lightNumbers[b] + " " + lightNumbers[s]; } else { if (inEnt.attributeIs("classname", "misc_teleporter")) { Vector3D origin = inEnt.Origin; Vector3D mins = new Vector3D(origin.X - 24, origin.Y - 24, origin.Z - 24); Vector3D maxs = new Vector3D(origin.X + 24, origin.Y + 24, origin.Z + 48); inEnt.Brushes.Add(MAPBrush.createBrush(mins, maxs, "tools/toolstrigger")); inEnt.Remove("origin"); inEnt["classname"] = "trigger_teleport"; } else { if (inEnt.attributeIs("classname", "misc_teleporter_dest")) { inEnt["classname"] = "info_target"; } } } } } } return inEnt; }
// Turn a Nightfire entity into a Hammer one. public virtual Entity ent42ToEntVMF(Entity inEnt) { if(inEnt.Angles[0] != 0) { inEnt["angles"] = (-inEnt.Angles[0])+" "+inEnt.Angles[1]+" "+inEnt.Angles[2]; } if (!inEnt["body"].Equals("")) { inEnt.renameAttribute("body", "SetBodyGroup"); } if (inEnt["rendercolor"].Equals("0 0 0")) { inEnt["rendercolor"] = "255 255 255"; } if (inEnt["angles"].Equals("0 -1 0")) { inEnt["angles"] = "-90 0 0"; } try { if (inEnt["model"].Substring(inEnt["model"].Length - 4).ToUpper().Equals(".spz".ToUpper())) { inEnt["model"] = inEnt["model"].Substring(0, (inEnt["model"].Length - 4) - (0)) + ".spr"; } } catch (System.ArgumentOutOfRangeException) { ; } if (inEnt.attributeIs("classname", "light_spot")) { try { inEnt["pitch"] = ((double) (inEnt.Angles[0] + System.Double.Parse(inEnt["pitch"]))).ToString(); } catch (System.FormatException e) { inEnt["pitch"] = ((double) inEnt.Angles[0]).ToString(); } try { if (System.Double.Parse(inEnt["_cone"]) > 90.0) { inEnt["_cone"] = "90"; } else { if (System.Double.Parse(inEnt["_cone"]) < 0.0) { inEnt["_cone"] = "0"; } } } catch (System.FormatException e) { ; } try { if (System.Double.Parse(inEnt["_cone2"]) > 90.0) { inEnt["_cone2"] = "90"; } else { if (System.Double.Parse(inEnt["_cone2"]) < 0.0) { inEnt["_cone2"] = "0"; } } } catch (System.FormatException e) { ; } inEnt.renameAttribute("_cone", "_inner_cone"); inEnt.renameAttribute("_cone2", "_cone"); } else { if (inEnt.attributeIs("classname", "func_wall")) { if (inEnt["rendermode"].Equals("0")) { inEnt["classname"] = "func_detail"; for (int i = 0; i < inEnt.Brushes.Count; i++) { MAPBrush currentBrush = inEnt.Brushes[i]; for (int j = 0; j < currentBrush.NumSides; j++) { MAPBrushSide currentSide = currentBrush[j]; if (currentSide.Texture.Equals("special/TRIGGER", StringComparison.InvariantCultureIgnoreCase)) { currentSide.Texture = "TOOLS/TOOLSHINT"; // Hint is the only thing that still works that doesn't collide with the player } } } inEnt.Remove("rendermode"); } else { inEnt["classname"] = "func_brush"; inEnt["solidity"] = "2"; inEnt.Remove("angles"); } } else { if (inEnt.attributeIs("classname", "func_wall_toggle")) { inEnt["classname"] = "func_brush"; inEnt["solidity"] = "0"; inEnt.Remove("angles"); try { if (inEnt.spawnflagsSet(1)) { inEnt["StartDisabled"] = "1"; inEnt.disableSpawnflags(1); } else { inEnt["StartDisabled"] = "0"; } } catch (System.FormatException) { inEnt["StartDisabled"] = "0"; } } else { if (inEnt.attributeIs("classname", "func_illusionary")) { inEnt["classname"] = "func_brush"; inEnt["solidity"] = "1"; inEnt.Remove("angles"); } else { if (inEnt.attributeIs("classname", "item_generic")) { inEnt["classname"] = "prop_dynamic"; inEnt["solid"] = "0"; inEnt.Remove("effects"); inEnt.Remove("fixedlight"); } else { if (inEnt.attributeIs("classname", "env_glow")) { inEnt["classname"] = "env_sprite"; } else { if (inEnt.attributeIs("classname", "info_teleport_destination")) { inEnt["classname"] = "info_target"; } else { if (inEnt.attributeIs("classname", "info_player_deathmatch") || inEnt.attributeIs("classname", "info_player_start")) { Vector3D origin = inEnt.Origin; inEnt["origin"] = origin.X + " " + origin.Y + " " + (origin.Z - 40); } else { if (inEnt.attributeIs("classname", "info_ctfspawn")) { if (inEnt["team_no"].Equals("1")) { inEnt["classname"] = "ctf_combine_player_spawn"; inEnt.Remove("team_no"); } else { if (inEnt["team_no"].Equals("2")) { inEnt["classname"] = "ctf_rebel_player_spawn"; inEnt.Remove("team_no"); } } Vector3D origin = inEnt.Origin; inEnt["origin"] = origin.X + " " + origin.Y + " " + (origin.Z - 40); } else { if (inEnt.attributeIs("classname", "item_ctfflag")) { inEnt.Remove("skin"); inEnt.Remove("goal_min"); inEnt.Remove("goal_max"); inEnt.Remove("model"); inEnt["SpawnWithCaptureEnabled"] = "1"; if (inEnt["goal_no"].Equals("1")) { inEnt["classname"] = "ctf_combine_flag"; inEnt["targetname"] = "combine_flag"; inEnt.Remove("goal_no"); } else { if (inEnt["goal_no"].Equals("2")) { inEnt["classname"] = "ctf_rebel_flag"; inEnt["targetname"] = "rebel_flag"; inEnt.Remove("goal_no"); } } } else { if (inEnt.attributeIs("classname", "func_ladder")) { for (int i = 0; i < inEnt.Brushes.Count; i++) { MAPBrush currentBrush = inEnt.Brushes[i]; for (int j = 0; j < currentBrush.NumSides; j++) { MAPBrushSide currentSide = currentBrush[j]; currentSide.Texture = "TOOLS/TOOLSINVISIBLELADDER"; } } } else { if (inEnt.attributeIs("classname", "func_door")) { inEnt["movedir"] = inEnt["angles"]; inEnt["noise1"] = inEnt["movement_noise"]; inEnt.Remove("movement_noise"); inEnt.Remove("angles"); if (inEnt.spawnflagsSet(1)) { inEnt["spawnpos"] = "1"; inEnt.disableSpawnflags(1); } inEnt["renderamt"] = "255"; } else { if (inEnt.attributeIs("classname", "func_button")) { inEnt["movedir"] = inEnt["angles"]; inEnt.Remove("angles"); for (int i = 0; i < inEnt.Brushes.Count; i++) { MAPBrush currentBrush = inEnt.Brushes[i]; for (int j = 0; j < currentBrush.NumSides; j++) { MAPBrushSide currentSide = currentBrush[j]; if (currentSide.Texture.Equals("special/TRIGGER", StringComparison.InvariantCultureIgnoreCase)) { currentSide.Texture = "TOOLS/TOOLSHINT"; // Hint is the only thing that still works that doesn't collide with the player } } } if (!inEnt.spawnflagsSet(256)) { // Nightfire's "touch activates" flag, same as source! if (!inEnt["health"].Equals("") && !inEnt["health"].Equals("0")) { inEnt.enableSpawnflags(512); } else { inEnt.enableSpawnflags(1024); } } } else { if (inEnt.attributeIs("classname", "trigger_hurt")) { if (inEnt.spawnflagsSet(2)) { inEnt["StartDisabled"] = "1"; } if (!inEnt.spawnflagsSet(8)) { inEnt["spawnflags"] = "1"; } else { inEnt["spawnflags"] = "0"; } inEnt.renameAttribute("dmg", "damage"); } else { if (inEnt.attributeIs("classname", "trigger_auto")) { inEnt["classname"] = "logic_auto"; } else { if (inEnt.attributeIs("classname", "trigger_once") || inEnt.attributeIs("classname", "trigger_multiple")) { if (inEnt.spawnflagsSet(8) || inEnt.spawnflagsSet(1)) { inEnt.disableSpawnflags(1); inEnt.disableSpawnflags(8); inEnt.enableSpawnflags(2); } if (inEnt.spawnflagsSet(2)) { inEnt.disableSpawnflags(1); } else { inEnt.enableSpawnflags(1); } } else { if (inEnt.attributeIs("classname", "func_door_rotating")) { if (inEnt.spawnflagsSet(1)) { inEnt["spawnpos"] = "1"; inEnt.disableSpawnflags(1); } inEnt["noise1"] = inEnt["movement_noise"]; inEnt.Remove("movement_noise"); } else { if (inEnt.attributeIs("classname", "trigger_push")) { inEnt["pushdir"] = inEnt["angles"]; inEnt.Remove("angles"); } else { if (inEnt.attributeIs("classname", "light_environment")) { Entity newShadowControl = new Entity("shadow_control"); Entity newEnvSun = new Entity("env_sun"); newShadowControl["angles"] = inEnt["angles"]; newEnvSun["angles"] = inEnt["angles"]; newShadowControl["origin"] = inEnt["origin"]; newEnvSun["origin"] = inEnt["origin"]; newShadowControl["color"] ="128 128 128"; data.Add(newShadowControl); data.Add(newEnvSun); } else { if (inEnt.attributeIs("classname", "func_rot_button")) { inEnt.Remove("angles"); for (int i = 0; i < inEnt.Brushes.Count; i++) { MAPBrush currentBrush = inEnt.Brushes[i]; for (int j = 0; j < currentBrush.NumSides; j++) { MAPBrushSide currentSide = currentBrush[j]; if (currentSide.Texture.ToUpper().Equals("special/TRIGGER".ToUpper())) { currentSide.Texture = "TOOLS/TOOLSHINT"; // Hint is the only thing that still works that doesn't collide with the player } } } if (!inEnt.spawnflagsSet(256)) { // Nightfire's "touch activates" flag, same as source! if (!inEnt["health"].Equals("") && !inEnt["health"].Equals("0")) { inEnt.enableSpawnflags(512); } else { inEnt.enableSpawnflags(1024); } } } else { if (inEnt.attributeIs("classname", "func_tracktrain")) { inEnt.renameAttribute("movesnd", "MoveSound"); inEnt.renameAttribute("stopsnd", "StopSound"); } else { if (inEnt.attributeIs("classname", "path_track")) { if (inEnt.spawnflagsSet(1)) { inEnt.Remove("targetname"); } } else { if (inEnt.attributeIs("classname", "trigger_relay")) { inEnt["classname"] = "logic_relay"; } else { if (inEnt.attributeIs("classname", "trigger_counter")) { inEnt["classname"] = "math_counter"; inEnt["max"] = inEnt["count"]; inEnt["min"] = "0"; inEnt["startvalue"] = "0"; inEnt.Remove("count"); } } } } } } } } } // Lol } // so } // many } // closing } // braces } } } } } } } } } } } return inEnt; }
// Multimanagers are also a special case. There are none in Source. Instead, I // need to add EVERY targetted entity in a multimanager to the original trigger // entity as an output with the specified delay. Things get even more complicated // when a multi_manager fires another multi_manager. In this case, this method will // recurse on itself until all the complexity is worked out. // One potential problem is if two multi_managers continuously call each other, this // method will recurse infinitely until there is a stack overflow. This might happen // when there is some sort of cycle going on in the map and multi_managers call each // other recursively to run the cycle with a delay. I solve this with an atrificial // limit of 8 multimanager recursions. // TODO: It would be better to detect this problem when it happens. // TODO: Instead of adding more attributes, parse into connections. private Entity parseMultimanager(Entity inEnt) { mmStackLength++; Entity dummy = new Entity(inEnt); dummy.Remove("classname"); dummy.Remove("origin"); dummy.Remove("angles"); dummy.Remove("targetname"); List<string> delete = new List<string>(); foreach(string st in dummy.Attributes.Keys) { string target = st; double delay = 0.0; try { delay = Double.Parse(dummy[st]); } catch { } for (int j = target.Length - 1; j >= 0; j--) { if (target[j] == '#') { target = target.Substring(0, (j) - (0)); //dummy.renameAttribute(st, target); break; } } Entity[] targets = getTargets(target); delete.Add(st); for (int j = 0; j < targets.Length; j++) { if (inEnt.attributeIs("classname", "multi_kill_manager")) { if (targets.Length > 1) { dummy.Connections.Add(new Tuple<string, string, string, string, double, int, string, Tuple<string>>("condition", target + j, "Kill", "", delay, -1, "", new Tuple<string>(""))); } else { dummy.Connections.Add(new Tuple<string, string, string, string, double, int, string, Tuple<string>>("condition", target, "Kill", "", delay, -1, "", new Tuple<string>(""))); } } else { if (targets[j].attributeIs("classname", "multi_manager") || targets[j].attributeIs("classname", "multi_kill_manager")) { if (mmStackLength <= Settings.MMStackSize) { Entity mm = parseMultimanager(targets[j]); foreach (Tuple<string, string, string, string, double, int, string, Tuple<string>> connection in mm.Connections) { dummy.Connections.Add(new Tuple<string, string, string, string, double, int, string, Tuple<string>>(connection.Item1, connection.Item2, connection.Item3, connection.Item4, connection.Item5 + delay, connection.Item6, connection.Item7, connection.Rest)); } } else { DecompilerThread.OnMessage(this, "WARNING: Multimanager stack overflow on entity " + inEnt["targetname"] + " calling " + targets[j]["targetname"] + "!"); DecompilerThread.OnMessage(this, "This is probably because of multi_managers repeatedly calling eachother. You can increase multimanager stack size in debug options."); } } else { if (targets.Length > 1) { //for(int k = 0; k < targets.Length; k++) { string outputAction = targets[j].onFire(); if (inEnt.attributeIs("triggerstate", "0")) { outputAction = targets[j].onDisable(); } else { if (inEnt.attributeIs("triggerstate", "1")) { outputAction = targets[j].onEnable(); } } dummy.Connections.Add(new Tuple<string, string, string, string, double, int, string, Tuple<string>>("condition", target+j, outputAction, "", delay, -1, "", new Tuple<string>(""))); //} } else if(targets.Length == 1) { string outputAction = targets[0].onFire(); if (inEnt.attributeIs("triggerstate", "0")) { outputAction = targets[0].onDisable(); } else { if (inEnt.attributeIs("triggerstate", "1")) { outputAction = targets[0].onEnable(); } } dummy.Connections.Add(new Tuple<string, string, string, string, double, int, string, Tuple<string>>("condition", target, outputAction, "", delay, -1, "", new Tuple<string>(""))); } else { dummy.Connections.Add(new Tuple<string, string, string, string, double, int, string, Tuple<string>>("condition", target, "Toggle", "", delay, -1, "", new Tuple<string>(""))); } } } } } foreach(string st in delete) { dummy.Remove(st); } mmStackLength--; return dummy; }
// -entityToByteArray() // Converts the entity and its brushes into byte arrays rather than Strings, // which can then be written to a file much faster. Concatenating Strings is // a costly operation, especially when hundreds of thousands of Strings are // in play. This is one of two parts to writing a file quickly. The second // part is to call the FileOutputStream.write() method only once, with a // gigantic array, rather than several times with many small arrays. File I/O // from a hard drive is another costly operation, best done by handling // massive amounts of data in one go, rather than tiny amounts of data thousands // of times. private byte[] entityToByteArray(Entity inEnt) { inEnt["id"] = ((System.Int32) nextID++).ToString(); byte[] outputData; Vector3D origin = Vector3D.ZERO; if (inEnt.BrushBased) { inEnt.Remove("model"); } if (inEnt.Brushes.Count > 0) { origin = inEnt.Origin; } string temp; if(inEnt["classname"].Equals("worldspawn", StringComparison.InvariantCultureIgnoreCase)) { temp = "world"+(char)0x0D+(char)0x0A+"{"+(char)0x0D+(char)0x0A; } else { temp = "entity"+(char)0x0D+(char)0x0A+"{"+(char)0x0D+(char)0x0A; } int len = temp.Length+3; // Closing brace, newline // Get the lengths of all attributes together foreach (string key in inEnt.Attributes.Keys) { len += key.Length + inEnt[key].Length + 8; // Four quotes, a space, a tab and a newline } if (inEnt.Connections.Count > 0) { len += 22; // tab, "connections", newline, tab, "{", newline, tab, "}", newline foreach (Tuple<string, string, string, string, double, int, string, Tuple<string>> connection in inEnt.Connections) { if(connection.Item7 == "" && connection.Rest.Item1 == "") { len += connection.Item1.Length + connection.Item2.Length + connection.Item3.Length + connection.Item4.Length + connection.Item5.ToString().Length + connection.Item6.ToString().Length + 13; // Two tabs, four quotes, space, four commas, newline } else { len += connection.Item1.Length + connection.Item2.Length + connection.Item3.Length + connection.Item4.Length + connection.Item5.ToString().Length + connection.Item6.ToString().Length + connection.Item7.Length + connection.Rest.Item1.Length + 15; // Two tabs, four quotes, space, six commas, newline } } } outputData = new byte[len]; int offset = 0; for (int i = 0; i < temp.Length; i++) { outputData[offset++] = (byte)temp[i]; } foreach (string key in inEnt.Attributes.Keys) { outputData[offset++] = (byte) (0x09); // 1 outputData[offset++] = (byte)'\"'; // 2 for (int j = 0; j < key.Length; j++) { // Then for each byte in the attribute outputData[j + offset] = (byte) key[j]; // add it to the output array } offset += key.Length; outputData[offset++] = (byte)'\"'; // 3 outputData[offset++] = (byte)' '; // 4 outputData[offset++] = (byte)'\"'; // 5 for (int j = 0; j < inEnt.Attributes[key].Length; j++) { // Then for each byte in the attribute outputData[j + offset] = (byte) inEnt.Attributes[key][j]; // add it to the output array } offset += inEnt.Attributes[key].Length; outputData[offset++] = (byte)'\"'; // 6 outputData[offset++] = (byte)0x0D; // 7 outputData[offset++] = (byte)0x0A; // 8 } if (inEnt.Connections.Count > 0) { outputData[offset++] = (byte)0x09; // tab 1 outputData[offset++] = (byte)'c'; // 2 outputData[offset++] = (byte)'o'; // 3 outputData[offset++] = (byte)'n'; // 4 outputData[offset++] = (byte)'n'; // 5 outputData[offset++] = (byte)'e'; // 6 outputData[offset++] = (byte)'c'; // 7 outputData[offset++] = (byte)'t'; // 8 outputData[offset++] = (byte)'i'; // 9 outputData[offset++] = (byte)'o'; // 10 outputData[offset++] = (byte)'n'; // 11 outputData[offset++] = (byte)'s'; // 12 outputData[offset++] = (byte)0x0D; // 13 outputData[offset++] = (byte)0x0A; // newline 14 outputData[offset++] = (byte)0x09; //tab 15 outputData[offset++] = (byte)'{'; // 16 outputData[offset++] = (byte)0x0D; // 17 outputData[offset++] = (byte)0x0A; // newline 18 foreach (Tuple<string, string, string, string, double, int, string, Tuple<string>> connection in inEnt.Connections) { string str = ""; if(connection.Item7 == "" && connection.Rest.Item1 == "") { str = ""+(char)0x09 + (char)0x09 + "\""+connection.Item1+"\" \""+connection.Item2+","+connection.Item3+","+connection.Item4+","+connection.Item5.ToString()+","+connection.Item6.ToString()+"\""+(char)0x0D+(char)0x0A; } else { str = ""+(char)0x09 + (char)0x09 + "\""+connection.Item1+"\" \""+connection.Item2+","+connection.Item3+","+connection.Item4+","+connection.Item5.ToString()+","+connection.Item6.ToString()+","+connection.Item7+","+connection.Rest.Item1+"\""+(char)0x0D+(char)0x0A; } for (int j = 0; j < str.Length; j++) { // Then for each byte in the string outputData[offset++] = (byte)str[j]; // add it to the output array } } outputData[offset++] = (byte)0x09; // tab 19 outputData[offset++] = (byte)'}'; // 20 outputData[offset++] = (byte)0x0D; // 21 outputData[offset++] = (byte)0x0A; // newline 22 } int brushArraySize = 0; byte[][] brushes = new byte[inEnt.Brushes.Count][]; for (int j = 0; j < inEnt.Brushes.Count; j++) { // For each brush in the entity bool containsNonClipSide = false; for (int k=0; k<inEnt.Brushes[j].NumSides; k++) { if (!inEnt.Brushes[j][k].Texture.ToLower().Contains("clip")) { containsNonClipSide = true; break; } } if (inEnt.Brushes[j].Detail && inEnt.attributeIs("classname", "worldspawn") && containsNonClipSide) { inEnt.Brushes[j].Detail = false; // Otherwise it will add an infinite number of func_details to the array Entity newDetailEntity = new Entity("func_detail"); for (int k = 0; k < inEnt.Brushes[j].NumSides; k++) { MAPBrushSide currentSide = inEnt.Brushes[j][k]; if (currentSide.Texture.Equals("special/TRIGGER", StringComparison.InvariantCultureIgnoreCase)) { currentSide.Texture = "TOOLS/TOOLSHINT"; // Hint is the only thing that still works that doesn't collide with the player } } newDetailEntity.Brushes.Add(inEnt.Brushes[j]); data.Add(newDetailEntity); brushes[j] = new byte[0]; // No data here! The brush will be output in its entity instead. } else { inEnt.Brushes[j].translate(new Vector3D(origin)); brushes[j] = brushToByteArray(inEnt.Brushes[j]); brushArraySize += brushes[j].Length; } } int brushoffset = 0; byte[] brushArray = new byte[brushArraySize]; for (int j = 0; j < inEnt.Brushes.Count; j++) { // For each brush in the entity for (int k = 0; k < brushes[j].Length; k++) { brushArray[brushoffset + k] = brushes[j][k]; } brushoffset += brushes[j].Length; } if (brushArray.Length != 0) { len += brushArray.Length; byte[] newOut = new byte[len]; for (int j = 0; j < outputData.Length; j++) { newOut[j] = outputData[j]; } for (int j = 0; j < brushArray.Length; j++) { newOut[j + outputData.Length - 3] = brushArray[j]; } offset += brushArray.Length; outputData = newOut; } outputData[offset++] = (byte)'}'; outputData[offset++] = (byte)0x0D; outputData[offset++] = (byte)0x0A; return outputData; }
// Turn a triggering entity (like a func_button or trigger_multiple) into a Source // engine trigger using entity I/O. There's a few complications to this: There's // no generic output which always acts like the triggers in other engines, and there's // no "Fire" input. I try to figure out which ones are best based on their classnames // but it's not 100% foolproof, and I have to add a case for every specific class. public virtual Entity parseEntityIO(Entity inEnt) { if (!(inEnt["target"]=="")) { double delay = 0.0; try { delay = Double.Parse(inEnt["delay"]); } catch (System.FormatException) { ; } if (!inEnt["target"].Equals("")) { Entity[] targets = getTargets(inEnt["target"]); for (int i = 0; i < targets.Length; i++) { if (targets[i].attributeIs("classname", "multi_manager") || targets[i].attributeIs("classname", "multi_kill_manager")) { Entity mm = parseMultimanager(targets[i]); //for (int j = 0; j < mm.Attributes.Count; j++) { foreach (Tuple<string, string, string, string, double, int, string, Tuple<string>> connection in mm.Connections) { if (inEnt.attributeIs("classname", "logic_relay") && inEnt.Attributes.ContainsKey("delay")) { inEnt.Connections.Add(new Tuple<string, string, string, string, double, int, string, Tuple<string>>("OnTrigger", connection.Item2, connection.Item3, connection.Item4, connection.Item5 + delay, connection.Item6, "", new Tuple<string>(""))); } else { inEnt.Connections.Add(new Tuple<string, string, string, string, double, int, string, Tuple<string>>(inEnt.fireAction(), connection.Item2, connection.Item3, connection.Item4, connection.Item5, connection.Item6, "", new Tuple<string>(""))); } } } else { string outputAction = targets[i].onFire(); if (inEnt.attributeIs("triggerstate", "0")) { outputAction = targets[i].onDisable(); } else { if (inEnt.attributeIs("triggerstate", "1")) { outputAction = targets[i].onEnable(); } } inEnt.Connections.Add(new Tuple<string, string, string, string, double, int, string, Tuple<string>>(inEnt.fireAction(), targets[i]["targetname"], outputAction, "", delay, -1, "", new Tuple<string>(""))); } } } if (!inEnt["killtarget"].Equals("")) { inEnt.Connections.Add(new Tuple<string, string, string, string, double, int, string, Tuple<string>>(inEnt.fireAction(), inEnt["killtarget"], "Kill", "", delay, -1, "", new Tuple<string>(""))); } inEnt.Remove("target"); inEnt.Remove("killtarget"); inEnt.Remove("triggerstate"); inEnt.Remove("delay"); } return inEnt; }
// Turn a Q3 entity into a Gearcraft one (generally for use with nightfire) // This won't magically fix every single thing to work in Gearcraft, for example // the Nightfire engine had no support for area portals. But it should save map // porters some time, especially when it comes to the Capture The Flag mod. public virtual Entity ent46ToEntM510(Entity inputData) { if (inputData.BrushBased) { Vector3D origin = inputData.Origin; inputData.Attributes.Remove("origin"); inputData.Attributes.Remove("model"); if (inputData.attributeIs("classname", "func_rotating") || inputData.attributeIs("classname", "func_rotatingdoor")) { // TODO: What entities require origin brushes in Quake 3? if ((origin[0] != 0 || origin[1] != 0 || origin[2] != 0) && !Settings.noOriginBrushes) { // If this brush uses the "origin" attribute MAPBrush newOriginBrush = MAPBrush.createBrush(new Vector3D(- Settings.originBrushSize, - Settings.originBrushSize, - Settings.originBrushSize), new Vector3D(Settings.originBrushSize, Settings.originBrushSize, Settings.originBrushSize), "special/origin"); inputData.Brushes.Add(newOriginBrush); } } for (int i = 0; i < inputData.Brushes.Count; i++) { inputData.Brushes[i].translate(origin); } } if (inputData["classname"].ToUpper().Equals("team_CTF_blueflag".ToUpper())) { // Blue flag inputData["classname"] = "item_ctfflag"; inputData["skin"] = "1"; // 0 for PHX, 1 for MI6 inputData["goal_no"] = "1"; // 2 for PHX, 1 for MI6 inputData["goal_max"] = "16 16 72"; inputData["goal_min"] = "-16 -16 0"; inputData["model"] = "models/ctf_flag.mdl"; Entity flagBase = new Entity("item_ctfbase"); flagBase["origin"] = inputData["origin"]; flagBase["angles"] = inputData["angles"]; flagBase["angle"] = inputData["angle"]; flagBase["goal_no"] = "1"; flagBase["model"] = "models/ctf_flag_stand_mi6.mdl"; flagBase["goal_max"] = "16 16 72"; flagBase["goal_min"] = "-16 -16 0"; data.Add(flagBase); } else { if (inputData["classname"].ToUpper().Equals("team_CTF_redflag".ToUpper())) { // Red flag inputData["classname"] = "item_ctfflag"; inputData["skin"] = "0"; // 0 for PHX, 1 for MI6 inputData["goal_no"] = "2"; // 2 for PHX, 1 for MI6 inputData["goal_max"] = "16 16 72"; inputData["goal_min"] = "-16 -16 0"; inputData["model"] = "models/ctf_flag.mdl"; Entity flagBase = new Entity("item_ctfbase"); flagBase["origin"] = inputData["origin"]; flagBase["angles"] = inputData["angles"]; flagBase["angle"] = inputData["angle"]; flagBase["goal_no"] = "2"; flagBase["model"] = "models/ctf_flag_stand_phoenix.mdl"; flagBase["goal_max"] = "16 16 72"; flagBase["goal_min"] = "-16 -16 0"; data.Add(flagBase); } else { if (inputData["classname"].ToUpper().Equals("team_CTF_redspawn".ToUpper()) || inputData["classname"].ToUpper().Equals("info_player_axis".ToUpper())) { inputData["classname"] = "info_ctfspawn"; inputData["team_no"] = "2"; Vector3D origin = inputData.Origin; inputData["origin"] = origin[X] + " " + origin[Y] + " " + (origin[Z] + 24); } else { if (inputData["classname"].ToUpper().Equals("team_CTF_bluespawn".ToUpper()) || inputData["classname"].ToUpper().Equals("info_player_allied".ToUpper())) { inputData["classname"] = "info_ctfspawn"; inputData["team_no"] = "1"; Vector3D origin = inputData.Origin; inputData["origin"] = origin[X] + " " + origin[Y] + " " + (origin[Z] + 24); } else { if (inputData["classname"].ToUpper().Equals("info_player_start".ToUpper())) { Vector3D origin = inputData.Origin; inputData["origin"] = origin[X] + " " + origin[Y] + " " + (origin[Z] + 24); } else { if (inputData["classname"].ToUpper().Equals("info_player_coop".ToUpper())) { Vector3D origin = inputData.Origin; inputData["origin"] = origin[X] + " " + origin[Y] + " " + (origin[Z] + 24); } else { if (inputData["classname"].ToUpper().Equals("info_player_deathmatch".ToUpper())) { Vector3D origin = inputData.Origin; inputData["origin"] = origin[X] + " " + origin[Y] + " " + (origin[Z] + 24); } else { if (inputData["classname"].ToUpper().Equals("light".ToUpper())) { string color = inputData["color"]; string intensity = inputData["light"]; string[] nums = color.Split(' '); double[] lightNumbers = new double[4]; for (int j = 0; j < 3 && j < nums.Length; j++) { try { lightNumbers[j] = System.Double.Parse(nums[j]); lightNumbers[j] *= 255; // Quake 3's numbers are from 0 to 1, Nightfire are from 0 to 255 } catch (System.FormatException) { ; } } try { lightNumbers[s] = System.Double.Parse(intensity); } catch (System.FormatException) { ; } inputData.Attributes.Remove("_color"); inputData.Attributes.Remove("light"); inputData["_light"] = lightNumbers[r] + " " + lightNumbers[g] + " " + lightNumbers[b] + " " + lightNumbers[s]; } else { if (inputData["classname"].ToUpper().Equals("func_rotatingdoor".ToUpper())) { inputData["classname"] = "func_door_rotating"; } else { if (inputData["classname"].ToUpper().Equals("info_pathnode".ToUpper())) { inputData["classname"] = "info_node"; } else { if (inputData["classname"].ToUpper().Equals("trigger_ladder".ToUpper())) { inputData["classname"] = "func_ladder"; } else { if (inputData["classname"].ToUpper().Equals("worldspawn".ToUpper())) { if (!inputData["suncolor"].Equals("")) { Entity light_environment = new Entity("light_environment"); light_environment["_light"] = inputData["suncolor"]; light_environment["angles"] = inputData["sundirection"]; light_environment["_fade"] = inputData["sundiffuse"]; inputData.Attributes.Remove("suncolor"); inputData.Attributes.Remove("sundirection"); inputData.Attributes.Remove("sundiffuse"); inputData.Attributes.Remove("sundiffusecolor"); data.Add(light_environment); } } else { if (inputData["classname"].ToUpper().Equals("trigger_use".ToUpper())) { inputData["classname"] = "func_button"; inputData["spawnflags"] = "1"; inputData["wait"] = "1"; } } } } } } } } } } } } } return inputData; }
private Entity entDoomToEntM510(Entity inputData) { if (inputData.attributeIs("classname", "weapon_pistol")) { inputData["classname"] = "weapon_p99"; } else { if (inputData.attributeIs("classname", "ammo_cells_large")) { inputData["classname"] = "ammo_bondmine"; } else { if (inputData.attributeIs("classname", "weapon_shotgun_double")) { inputData["classname"] = "weapon_pdw90"; } else { if (inputData.attributeIs("classname", "weapon_shotgun")) { inputData["classname"] = "weapon_frinesi"; } else { if (inputData.attributeIs("classname", "weapon_chaingun")) { inputData["classname"] = "weapon_minigun"; } else { if (inputData.attributeIs("classname", "weapon_plasmagun")) { inputData["classname"] = "weapon_grenadelauncher"; } else { if (inputData.attributeIs("classname", "weapon_chainsaw")) { inputData["classname"] = "weapon_ronin"; } else { if (inputData.attributeIs("classname", "weapon_bfg")) { inputData["classname"] = "weapon_laserrifle"; } else { if (inputData.attributeIs("classname", "ammo_clip_small")) { inputData["classname"] = "ammo_p99"; } else { if (inputData.attributeIs("classname", "ammo_shells_small")) { inputData["classname"] = "ammo_mini"; } else { if (inputData.attributeIs("classname", "ammo_rockets_small")) { inputData["classname"] = "ammo_darts"; } else { if (inputData.attributeIs("classname", "ammo_rockets_large")) { inputData["classname"] = "ammo_rocketlauncher"; } else { if (inputData.attributeIs("classname", "ammo_cells_small")) { inputData["classname"] = "ammo_grenadelauncher"; } else { if (inputData.attributeIs("classname", "ammo_bullets_large")) { inputData["classname"] = "ammo_mp9"; } else { if (inputData.attributeIs("classname", "ammo_shells_large")) { inputData["classname"] = "ammo_shotgun"; } } } } } } } } } } } } } } } return inputData; }
private Entity entSourceToEntM510(Entity inputData) { if (inputData.BrushBased) { Vector3D origin = inputData.Origin; inputData.Attributes.Remove("origin"); inputData.Attributes.Remove("model"); if (inputData.attributeIs("classname", "func_door_rotating")) { // TODO: What entities require origin brushes? if ((origin[0] != 0 || origin[1] != 0 || origin[2] != 0) && !Settings.noOriginBrushes) { // If this brush uses the "origin" attribute MAPBrush newOriginBrush = MAPBrush.createBrush(new Vector3D(- Settings.originBrushSize, - Settings.originBrushSize, - Settings.originBrushSize), new Vector3D(Settings.originBrushSize, Settings.originBrushSize, Settings.originBrushSize), "special/origin"); inputData.Brushes.Add(newOriginBrush); } } for (int i = 0; i < inputData.Brushes.Count; i++) { MAPBrush currentBrush = inputData.Brushes[i]; currentBrush.translate(new Vector3D(origin)); } } if (inputData["classname"].ToUpper().Equals("func_breakable_surf".ToUpper())) { inputData["classname"] = "func_breakable"; } else { if (inputData["classname"].ToUpper().Equals("func_brush".ToUpper())) { if (inputData["solidity"].Equals("0")) { inputData["classname"] = "func_wall_toggle"; if (inputData["StartDisabled"].Equals("1")) { inputData["spawnflags"] = "1"; } else { inputData["spawnflags"] = "0"; } inputData.Attributes.Remove("StartDisabled"); } else { if (inputData["solidity"].Equals("1")) { inputData["classname"] = "func_illusionary"; } else { inputData["classname"] = "func_wall"; } } inputData.Attributes.Remove("solidity"); } else { if (inputData["classname"].ToUpper().Equals("env_fog_controller".ToUpper())) { inputData["classname"] = "env_fog"; inputData["rendercolor"] = inputData["fogcolor"]; inputData.Attributes.Remove("fogcolor"); } else { if (inputData["classname"].ToUpper().Equals("prop_static".ToUpper())) { inputData["classname"] = "item_generic"; } else { if (inputData["classname"].ToUpper().Equals("info_player_rebel".ToUpper()) || inputData["classname"].ToUpper().Equals("info_player_janus".ToUpper()) || inputData["classname"].ToUpper().Equals("ctf_rebel_player_spawn".ToUpper())) { inputData["classname"] = "info_ctfspawn"; inputData["team_no"] = "2"; Vector3D origin = inputData.Origin; inputData["origin"] = origin[X] + " " + origin[Y] + " " + (origin[Z] + 40); } else { if (inputData["classname"].ToUpper().Equals("info_player_combine".ToUpper()) || inputData["classname"].ToUpper().Equals("info_player_mi6".ToUpper()) || inputData["classname"].ToUpper().Equals("ctf_combine_player_spawn".ToUpper())) { inputData["classname"] = "info_ctfspawn"; inputData["team_no"] = "1"; Vector3D origin = inputData.Origin; inputData["origin"] = origin[X] + " " + origin[Y] + " " + (origin[Z] + 40); } else { if (inputData["classname"].ToUpper().Equals("info_player_deathmatch".ToUpper())) { Vector3D origin = inputData.Origin; inputData["origin"] = origin[X] + " " + origin[Y] + " " + (origin[Z] + 40); } else { if (inputData["classname"].ToUpper().Equals("ctf_combine_flag".ToUpper())) { inputData.Attributes.Remove("targetname"); inputData.Attributes.Remove("SpawnWithCaptureEnabled"); inputData["skin"] = "1"; inputData["goal_max"] = "16 16 72"; inputData["goal_min"] = "-16 -16 0"; inputData["goal_no"] = "1"; inputData["model"] = "models/ctf_flag.mdl"; inputData["classname"] = "item_ctfflag"; Entity newFlagBase = new Entity("item_ctfbase"); newFlagBase["origin"] = inputData["origin"]; newFlagBase["angles"] = inputData["angles"]; newFlagBase["goal_max"] = "16 16 72"; newFlagBase["goal_min"] = "-16 -16 0"; newFlagBase["goal_no"] = "1"; newFlagBase["model"] = "models/ctf_flag_stand_mi6.mdl"; data.Add(newFlagBase); } else { if (inputData["classname"].ToUpper().Equals("ctf_rebel_flag".ToUpper())) { inputData.Attributes.Remove("targetname"); inputData.Attributes.Remove("SpawnWithCaptureEnabled"); inputData["skin"] = "0"; inputData["goal_max"] = "16 16 72"; inputData["goal_min"] = "-16 -16 0"; inputData["goal_no"] = "2"; inputData["model"] = "models/ctf_flag.mdl"; inputData["classname"] = "item_ctfflag"; Entity newFlagBase = new Entity("item_ctfbase"); newFlagBase["origin"] = inputData["origin"]; newFlagBase["angles"] = inputData["angles"]; newFlagBase["goal_max"] = "16 16 72"; newFlagBase["goal_min"] = "-16 -16 0"; newFlagBase["goal_no"] = "2"; newFlagBase["model"] = "models/ctf_flag_stand_phoenix.mdl"; data.Add(newFlagBase); } } } } } } } } } return inputData; }
// Turn a Q2 entity into a Gearcraft one (generally for use with nightfire) // This won't magically fix every single thing to work in Gearcraft, for example // the Nightfire engine had no support for area portals. But it should save map // porters some time, especially when it comes to the Capture The Flag mod. public virtual Entity ent38ToEntM510(Entity inputData) { if (!inputData["angle"].Equals("")) { inputData["angles"] = "0 " + inputData["angle"] + " 0"; inputData.Attributes.Remove("angle"); } if (inputData.BrushBased) { Vector3D origin = inputData.Origin; inputData.Attributes.Remove("origin"); inputData.Attributes.Remove("model"); if (inputData.attributeIs("classname", "func_rotating")) { // TODO: What entities require origin brushes in CoD? if ((origin[0] != 0 || origin[1] != 0 || origin[2] != 0) && !Settings.noOriginBrushes) { // If this brush uses the "origin" attribute MAPBrush newOriginBrush = MAPBrush.createBrush(new Vector3D(- Settings.originBrushSize, - Settings.originBrushSize, - Settings.originBrushSize), new Vector3D(Settings.originBrushSize, Settings.originBrushSize, Settings.originBrushSize), "special/origin"); inputData.Brushes.Add(newOriginBrush); } } for (int i = 0; i < inputData.Brushes.Count; i++) { MAPBrush currentBrush = inputData.Brushes[i]; //currentBrush.translate(new Vector3D(origin)); } } if (inputData["classname"].ToUpper().Equals("func_wall".ToUpper())) { if (!inputData["targetname"].Equals("")) { // Really this should depend on spawnflag 2 or 4 inputData["classname"] = "func_wall_toggle"; } // 2 I believe is "Start enabled" and 4 is "toggleable", or the other way around. Not sure. Could use an OR. } else { if (inputData["classname"].ToUpper().Equals("item_flag_team2".ToUpper()) || inputData["classname"].ToUpper().Equals("ctf_flag_hardcorps".ToUpper())) { // Blue flag inputData["classname"] = "item_ctfflag"; inputData["skin"] = "1"; // 0 for PHX, 1 for MI6 inputData["goal_no"] = "1"; // 2 for PHX, 1 for MI6 inputData["goal_max"] = "16 16 72"; inputData["goal_min"] = "-16 -16 0"; Entity flagBase = new Entity("item_ctfbase"); flagBase["origin"] = inputData["origin"]; flagBase["angles"] = inputData["angles"]; flagBase["angle"] = inputData["angle"]; flagBase["goal_no"] = "1"; flagBase["model"] = "models/ctf_flag_stand_mi6.mdl"; flagBase["goal_max"] = "16 16 72"; flagBase["goal_min"] = "-16 -16 0"; data.Add(flagBase); } else { if (inputData["classname"].ToUpper().Equals("item_flag_team1".ToUpper()) || inputData["classname"].ToUpper().Equals("ctf_flag_sintek".ToUpper())) { // Red flag inputData["classname"] = "item_ctfflag"; inputData["skin"] = "0"; // 0 for PHX, 1 for MI6 inputData["goal_no"] = "2"; // 2 for PHX, 1 for MI6 inputData["goal_max"] = "16 16 72"; inputData["goal_min"] = "-16 -16 0"; Entity flagBase = new Entity("item_ctfbase"); flagBase["origin"] = inputData["origin"]; flagBase["angles"] = inputData["angles"]; flagBase["angle"] = inputData["angle"]; flagBase["goal_no"] = "2"; flagBase["model"] = "models/ctf_flag_stand_phoenix.mdl"; flagBase["goal_max"] = "16 16 72"; flagBase["goal_min"] = "-16 -16 0"; data.Add(flagBase); } else { if (inputData["classname"].ToUpper().Equals("info_player_team1".ToUpper()) || inputData["classname"].ToUpper().Equals("info_player_sintek".ToUpper())) { inputData["classname"] = "info_ctfspawn"; inputData["team_no"] = "2"; } else { if (inputData["classname"].ToUpper().Equals("info_player_team2".ToUpper()) || inputData["classname"].ToUpper().Equals("info_player_hardcorps".ToUpper())) { inputData["classname"] = "info_ctfspawn"; inputData["team_no"] = "1"; } else { if (inputData["classname"].ToUpper().Equals("info_player_start".ToUpper())) { Vector3D origin = inputData.Origin; inputData["origin"] = origin[X] + " " + origin[Y] + " " + (origin[Z] + 18); } else { if (inputData["classname"].ToUpper().Equals("info_player_coop".ToUpper())) { Vector3D origin = inputData.Origin; inputData["origin"] = origin[X] + " " + origin[Y] + " " + (origin[Z] + 18); } else { if (inputData["classname"].ToUpper().Equals("info_player_deathmatch".ToUpper())) { Vector3D origin = inputData.Origin; inputData["origin"] = origin[X] + " " + origin[Y] + " " + (origin[Z] + 18); } else { if (inputData["classname"].ToUpper().Equals("light".ToUpper())) { string color = inputData["color"]; string intensity = inputData["light"]; string[] nums = color.Split(' '); double[] lightNumbers = new double[4]; for (int j = 0; j < 3 && j < nums.Length; j++) { try { lightNumbers[j] = Double.Parse(nums[j]); lightNumbers[j] *= 255; // Quake 2's numbers are from 0 to 1, Nightfire are from 0 to 255 } catch (System.FormatException) { ; } } try { lightNumbers[s] = System.Double.Parse(intensity); } catch (System.FormatException) { ; } inputData.Attributes.Remove("_color"); inputData.Attributes.Remove("light"); inputData["_light"] = lightNumbers[r] + " " + lightNumbers[g] + " " + lightNumbers[b] + " " + lightNumbers[s]; } else { if (inputData["classname"].ToUpper().Equals("misc_teleporter".ToUpper())) { Vector3D origin = inputData.Origin; Vector3D mins = new Vector3D(origin[X] - 24, origin[Y] - 24, origin[Z] - 24); Vector3D maxs = new Vector3D(origin[X] + 24, origin[Y] + 24, origin[Z] + 48); inputData.Brushes.Add(MAPBrush.createBrush(mins, maxs, "special/trigger")); inputData.Attributes.Remove("origin"); inputData["classname"] = "trigger_teleport"; } else { if (inputData["classname"].ToUpper().Equals("misc_teleporter_dest".ToUpper())) { inputData["classname"] = "info_teleport_destination"; } } } } } } } } } } } return inputData; }